const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const esbuild = require('esbuild'); const JS = (...parts) => path.join(__dirname, 'js', ...parts); const DIST = path.join(__dirname, 'dist'); const INDEX_HTML = path.join(__dirname, 'index.html'); const SW_JS = path.join(__dirname, 'sw.js'); // Bundle definitions — files are concatenated in order, then minified const bundles = { 'core.js': [ // error-handler.js MUST be first — globals.js below does // `const errorHandler = new ErrorHandler()` at top level, which throws // ReferenceError if the ErrorHandler class isn't already on `window`. JS('error-handler.js'), JS('globals.js'), JS('skeleton-loader.js'), JS('theme.js'), // DC-049: pluggable auth gate — claims ownership of the // ?auth=required flow by setting window.__dc_049_handled BEFORE // totp-auth.js runs, so the legacy TOTP-only overlay doesn't flicker // in for multi-provider installs. Single-provider TOTP-only installs // work because this module delegates back to window._showTotpOverlay(). JS('auth-gate.js'), JS('totp-auth.js'), // totp-recovery.js registers window._refreshRecoveryLink which totp-auth.js // calls from showTotpOverlay(). Must come after totp-auth.js. JS('totp-recovery.js'), JS('service-credentials.js'), JS('totp-settings.js'), // DC-048 admin panel — modal-overlay UI for user/invite management. // Renders the "Admin" trigger button into the top bar; only visible // when /api/v1/auth/me returns isAdmin=true. JS('admin.js'), JS('core', 'credentials.js'), JS('core', 'grid.js'), JS('core', 'dns.js'), JS('core', 'logs.js'), JS('core', 'service-modals.js'), JS('core', 'service-infrastructure.js'), JS('core', 'service-crud.js'), JS('core', 'service-create.js'), JS('live-events.js'), JS('service-filter.js'), JS('batch-operations.js'), ], 'features.js': [ JS('logo-customization.js'), JS('setup-wizard.js'), JS('app-selector.js'), JS('recipes.js'), JS('import-export.js'), JS('error-logs.js'), JS('container-logs.js'), JS('snapshot.js'), JS('smart-arr-connect.js'), JS('notification-settings.js'), JS('panel-tabs.js'), JS('backup-restore.js'), JS('resource-monitor.js'), JS('health-check.js'), JS('update-management.js'), JS('docker-resources.js'), JS('compose-import.js'), JS('container-exec.js'), JS('audit-log.js'), JS('security-center.js'), JS('weather.js'), JS('clock.js'), JS('card-badges.js'), JS('theme-builder.js'), JS('license.js'), ], 'onboarding.js': [ JS('driver.min.js'), // error-handler.js moved to core.js bundle; window.ErrorHandler is already // set before this bundle runs. JS('progress-tracker.js'), JS('theme-adapter.js'), JS('tooltip-definitions.js'), JS('dns-template-selector.js'), JS('tour-manager.js'), JS('onboarding.js'), ], 'init.js': [ JS('core', 'init.js'), JS('monitoring-widgets.js'), JS('keyboard-shortcuts.js'), ], }; function updateInlineScriptCspHash() { const html = fs.readFileSync(INDEX_HTML, 'utf8'); // The hash MUST match what the browser computes from the served bytes. // git's text normalization + tar transport strip CRLF on the Linux side, // so the deployed file is always LF-only — even when the dev copy is CRLF // (e.g. cloned on Windows). Normalize before hashing so a Windows-built // index.html produces a CSP allowlist that matches the served LF version. const normalized = html.replace(/\r\n/g, '\n'); const scripts = [...normalized.matchAll(/