From da501e478ff77c488128aad96f648da3f8b5c139 Mon Sep 17 00:00:00 2001 From: Menghuan1918 Date: Thu, 17 Sep 2026 17:49:29 +0800 Subject: [PATCH] fix(packaging): ship every lazy chunk (tasks + locale were missing from files) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit package.json#files enumerated chunks by hand, so the new lib/client-tasks.js never entered the tarball — a published install would have failed to open the Tasks tab. The same list was also missing lib/client-locale.js since that chunk landed. Replace the enumeration with the lib/client-*.js glob, derive the test's chunk list from CHUNK_NAMES instead of a hand-mirrored copy, and add an assertion that every chunk is covered by the publish list (verified against the packed tarball and the live /sidebar/bundle route). --- tests/manifest-consistency.spec.ts | 31 ++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/manifest-consistency.spec.ts b/tests/manifest-consistency.spec.ts index 2d5f460..37930c3 100644 --- a/tests/manifest-consistency.spec.ts +++ b/tests/manifest-consistency.spec.ts @@ -18,6 +18,7 @@ import { describe, expect, it } from 'vitest' import { existsSync, readFileSync } from 'node:fs' import { resolve } from 'node:path' +import { CHUNK_NAMES } from '../src/bundle-route.ts' import { fileURLToPath } from 'node:url' const ROOT = resolve(fileURLToPath(new URL('..', import.meta.url))) @@ -80,8 +81,24 @@ function bundleId(file: string): string { return match[1]! } -/** The lazy chunk bundle names (mirror of src/bundle-route.ts CHUNK_NAMES). */ -const CHUNK_FILES = ['editor', 'mermaid'].map(name => `lib/client-${name}.js`) +/** + * The lazy chunk bundles, DERIVED from the host route's registry (never + * hand-mirrored: the hand-written list drifted twice — `locale` was missing + * from both this list and a later `package.json#files` cleanup, and `tasks` + * shipped without being listed at all, which would have made the Tasks page + * fail to load from a published tarball). + */ +const CHUNK_FILES = CHUNK_NAMES.map(name => `lib/client-${name}.js`) + +/** Whether one `package.json#files` entry covers a built artifact path. */ +function publishListCovers(entry: string, file: string): boolean { + if (entry === file) return true + if (!entry.includes('*')) return false + // Only the `lib/client-*.js` shape is in play; a regex built from the entry + // is enough (escape everything else). + const pattern = new RegExp(`^${entry.split('*').map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*')}$`) + return pattern.test(file) +} /** The global registry slot a built chunk script assigns (its factory key). */ function chunkSlot(file: string): string { @@ -120,6 +137,16 @@ describe.skipIf(!libBuilt)('registry manifest consistency (dsh.plugin.json)', () } }) + it('every chunk is covered by package.json#files (a published tarball must serve it)', () => { + const files = (pkg as { files?: string[] }).files ?? [] + for (const file of CHUNK_FILES) { + expect( + files.some(entry => publishListCovers(entry, file)), + `${file} is not covered by package.json#files (${files.join(', ')})`, + ).toBe(true) + } + }) + it('client bundles require only frozen module-table entries', () => { for (const file of ['lib/client.js', manifest.client!.main!, ...CHUNK_FILES]) { const source = readFileSync(resolve(ROOT, file), 'utf8')