chore: fail a Lite build that ships the local store's engine

This commit is contained in:
Linus Rath
2026-11-03 05:54:37 +01:00
parent 062a078e1e
commit 7aa12d19b5
3 changed files with 41 additions and 1 deletions
+29
View File
@@ -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();
+7
View File
@@ -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?$/;
+5 -1
View File
@@ -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");