'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const { auditLegacyPanelBundle, bundleLegacyPanel, } = require('../../scripts/ql3-legacy-panel-bundle.cjs'); function fixture() { const root = fs.realpathSync( fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-panel-bundle-')), ); const source = path.join(root, 'source'); const output = path.join(root, 'output'); fs.mkdirSync(path.join(source, 'monaco-editor'), { recursive: true }); fs.writeFileSync( path.join(source, 'index.html'), '\n' + '\n' + '\n' + '\n' + '\n' + '
\n' + '\n', ); fs.writeFileSync( path.join(source, 'umi.1234abcd.css'), 'body { color: #123; }\n', ); fs.writeFileSync( path.join(source, 'umi.1234abcd.js'), 'globalThis.__panel = true;\n', ); fs.writeFileSync(path.join(source, 'umi.1234abcd.js.gz'), 'not shipped'); fs.writeFileSync( path.join(source, 'monaco-editor', 'editor.1234abcd.js'), 'not shipped', ); return { root, source, output, close() { fs.rmSync(root, { recursive: true, force: true }); }, }; } test('materializes one bounded offline legacy panel closure', (t) => { const current = fixture(); t.after(() => current.close()); const report = bundleLegacyPanel(current.source, current.output); assert.deepEqual(report, { schema: 'qinglong/local-legacy-panel-assets@v1', files: 4, bytes: report.bytes, maxFiles: 256, maxBytes: 13 * 1024 * 1024, supportedRoutes: ['/login', '/crontab', '/error'], compatible: true, }); assert.ok(report.bytes > 200); assert.equal( fs.existsSync(path.join(current.output, 'umi.1234abcd.js.gz')), false, ); assert.equal( fs.existsSync(path.join(current.output, 'monaco-editor')), false, ); const index = fs.readFileSync( path.join(current.output, 'index.html'), 'utf8', ); assert.equal(index.includes('https://'), false); const environment = fs.readFileSync( path.join(current.output, 'api/env.js'), 'utf8', ); assert.equal(environment.includes("QlBaseUrl: '/'"), true); const manifest = JSON.parse( fs.readFileSync(path.join(current.output, 'manifest.json'), 'utf8'), ); assert.equal(manifest.files[0].requestPath, '/'); assert.equal( manifest.files.find(({ requestPath }) => requestPath === '/api/env.js') .cacheControl, 'no-store', ); assert.equal( manifest.files.find(({ requestPath }) => /\.[0-9a-f]{8}\.js$/u.test(requestPath), ).cacheControl, 'public, max-age=31536000, immutable', ); assert.deepEqual(auditLegacyPanelBundle(current.output), report); }); test('rejects bundle replacement and post-build asset drift', (t) => { const current = fixture(); t.after(() => current.close()); bundleLegacyPanel(current.source, current.output); assert.throws( () => bundleLegacyPanel(current.source, current.output), /output must be an absent directory/u, ); const assetPath = path.join(current.output, 'umi.1234abcd.js'); fs.chmodSync(assetPath, 0o600); fs.appendFileSync(assetPath, 'drift'); assert.throws( () => auditLegacyPanelBundle(current.output), /asset identity drifted|asset digest drifted/u, ); }); test('rejects mutable assets and external entrypoint drift', (t) => { const mutable = fixture(); t.after(() => mutable.close()); fs.writeFileSync(path.join(mutable.source, 'runtime.js'), 'mutable'); assert.throws( () => bundleLegacyPanel(mutable.source, mutable.output), /mutable source asset/u, ); const external = fixture(); t.after(() => external.close()); fs.appendFileSync( path.join(external.source, 'index.html'), '\n', ); assert.throws( () => bundleLegacyPanel(external.source, external.output), /retains an external origin/u, ); });