diff --git a/lib/__tests__/lite-scripts.test.ts b/lib/__tests__/lite-scripts.test.ts index 8e103daa4..1262e7206 100644 --- a/lib/__tests__/lite-scripts.test.ts +++ b/lib/__tests__/lite-scripts.test.ts @@ -58,6 +58,8 @@ import { stalwartPrefixProblems, stalwartRewriteIndex, stalwartZipEntryProblems, + unexpectedEngineFiles, + LITE_REMOVED_PATHS, unexpectedApiStrings, withScriptHashCsp, } from '../../scripts/lite/lib.mjs'; @@ -342,6 +344,33 @@ describe('lite build helpers', () => { }); }); +describe('the local store in an export', () => { + it('finds every engine file, and nothing else', () => { + expect(unexpectedEngineFiles([ + 'store-worker.js', + 'sqlite3mc/2.6.1-3.54.0/sqlite3.mjs', + 'sqlite3mc\\2.6.1-3.54.0\\sqlite3.wasm', + '_next/static/media/sqlite3.wasm', + 'pst-worker.js', + 'en/mail/index.html', + '_next/static/chunks/app.js', + ])).toEqual([ + 'store-worker.js', + 'sqlite3mc/2.6.1-3.54.0/sqlite3.mjs', + 'sqlite3mc/2.6.1-3.54.0/sqlite3.wasm', + '_next/static/media/sqlite3.wasm', + ]); + }); + + it('removes what a dev checkout holds before a build, and offline mode never starts in Lite', () => { + expect(LITE_REMOVED_PATHS).toEqual(expect.arrayContaining(['public/store-worker.js', 'public/sqlite3mc'])); + const offlineMode = readFileSync(join(process.cwd(), 'lib/offline/offline-mode.ts'), 'utf8'); + expect(offlineMode).toMatch(/if \(IS_LITE \|\| !hasIndexedDb\(\)\) return false;/); + const build = readFileSync(join(process.cwd(), 'scripts/lite/build.mjs'), 'utf8'); + expect(build).not.toMatch(/build-store-worker/); + }); +}); + describe('prepare / postbuild / verify against a fake checkout', () => { it('plans the server-only trees and every test file, skipping node_modules, repos and scripts', () => { const root = makeRepo(); diff --git a/scripts/lite/lib.mjs b/scripts/lite/lib.mjs index 18bdc6cdf..9f64367d7 100644 --- a/scripts/lite/lib.mjs +++ b/scripts/lite/lib.mjs @@ -48,6 +48,13 @@ export const LITE_REMOVED_PATHS = [ "public/sqlite3mc", ]; +/** The local store's engine in an export: Lite has no offline mode, and must not ship or load it. */ +export function unexpectedEngineFiles(files) { + return files + .map((file) => file.replace(/\\/g, "/")) + .filter((file) => /(^|\/)store-worker\.js$/.test(file) || /(^|\/)sqlite3mc\//.test(file) || /(^|\/)sqlite3\.(wasm|mjs)$/.test(file)); +} + /** Test trees are pruned too: they import the routes removed above. */ export const LITE_TEST_DIR_NAME = "__tests__"; export const LITE_TEST_FILE_PATTERN = /\.test\.[cm]?[jt]sx?$/; diff --git a/scripts/lite/verify.mjs b/scripts/lite/verify.mjs index 3e2800168..4a19c6e30 100644 --- a/scripts/lite/verify.mjs +++ b/scripts/lite/verify.mjs @@ -10,7 +10,7 @@ import { existsSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; import { LITE_PENDING_PATH_KEY, STALWART_EXCLUDED_FILES, STALWART_MAX_BUNDLE_BYTES, STALWART_SAFE_BUNDLE_BYTES, STALWART_ZIP_NAME, - collectApiStrings, collectZipEntries, discoverBuiltLocales, flightTextRowsWithNeedles, inlineScriptHashes, isMainModule, listZipEntries, parseLiteTarget, resolveRepoRoot, stalwartEntryProblems, stalwartZipEntryProblems, unexpectedApiStrings, + collectApiStrings, collectZipEntries, discoverBuiltLocales, flightTextRowsWithNeedles, inlineScriptHashes, isMainModule, listZipEntries, parseLiteTarget, resolveRepoRoot, stalwartEntryProblems, stalwartZipEntryProblems, unexpectedApiStrings, unexpectedEngineFiles, } from "./lib.mjs"; const repoRoot = resolveRepoRoot(import.meta.url); @@ -83,6 +83,10 @@ export function verifyExport({ root = repoRoot, target, safeBundleBytes = STALWA const unexpected = unexpectedApiStrings(apiStrings); for (const s of unexpected) problems.push(`client chunk references undocumented server endpoint ${s} (add an IS_LITE gate or document it in scripts/lite/lib.mjs)`); + if (existsSync(outDir)) { + for (const file of unexpectedEngineFiles(collectZipEntries(outDir).map(({ name }) => name))) problems.push(`out/${file} is the local store's engine, which Lite must not ship`); + } + let zipBytes = 0; if (resolvedTarget === "stalwart") { const entry = join(outDir, "index.html");