fix(packaging): ship every lazy chunk (tasks + locale were missing from files)

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).
This commit is contained in:
Menghuan1918
2026-09-27 22:18:19 +08:00
parent f6cf73c4cf
commit da501e478f
+29 -2
View File
@@ -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')