output: "standalone" + next build --webpack drops the whole metadata
directory despite a plain top-level require in router-utils/filesystem.js
("../../../lib/metadata/get-metadata-route") - every packaged build
(Electron dmg and the Docker image) crashed on its very first line with
Cannot find module. Verified: a fresh dist:mac build failed to boot at
all; copying the directory by hand (same pattern already used for the
sqlcipher prebuilds and plugin bundles) fixes it, confirmed by booting
.next/standalone directly and getting a real HTTP response.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
101 lines
5.1 KiB
JavaScript
101 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// `next build --webpack` (see next.config.ts's `output: "standalone"`)
|
|
// emits .next/standalone/server.js but - deliberately, per Next's own docs -
|
|
// leaves out public/ and .next/static/. The Dockerfile copies both in by
|
|
// hand for the container image; this does the same thing for local Electron
|
|
// dev and packaging, so every path boots the exact same artifact.
|
|
import { cpSync, existsSync, rmSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const rootDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
const standaloneDir = path.join(rootDir, ".next", "standalone");
|
|
|
|
if (!existsSync(standaloneDir)) {
|
|
console.error(`Missing ${standaloneDir} - run "next build --webpack" first.`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const publicSrc = path.join(rootDir, "public");
|
|
const publicDest = path.join(standaloneDir, "public");
|
|
rmSync(publicDest, { recursive: true, force: true });
|
|
cpSync(publicSrc, publicDest, { recursive: true });
|
|
|
|
const staticSrc = path.join(rootDir, ".next", "static");
|
|
const staticDest = path.join(standaloneDir, ".next", "static");
|
|
rmSync(staticDest, { recursive: true, force: true });
|
|
cpSync(staticSrc, staticDest, { recursive: true });
|
|
|
|
// The native SQLCipher prebuilds for the local search index (lib/mail-index/**).
|
|
//
|
|
// Next's output file tracing DOES pick up @signalapp/sqlcipher's JS
|
|
// (package.json + dist/index.cjs) and its node-gyp-build dependency, but NOT
|
|
// the prebuilds/ directory holding the actual .node binaries - node-gyp-build
|
|
// resolves those by scanning the directory at runtime, which no static tracer
|
|
// can follow. Verified by inspecting a real `build:standalone` output: the
|
|
// package was present, `prebuilds/` was absent, so `require()` would have
|
|
// failed at runtime in every packaged build.
|
|
//
|
|
// Copying the WHOLE prebuilds directory (all six platform/arch pairs, ~11 MB)
|
|
// rather than just this host's is deliberate: electron-builder cross-builds the
|
|
// x64 and arm64 macOS targets from one runner (electron-builder.config.js), so
|
|
// the artifact has to contain a prebuild for an arch this machine isn't.
|
|
//
|
|
// Skipped silently when absent - the package is an OPTIONAL dependency and is
|
|
// legitimately missing on musl/Alpine, where both Dockerfiles build.
|
|
const sqlcipherSrc = path.join(rootDir, "node_modules", "@signalapp", "sqlcipher", "prebuilds");
|
|
if (existsSync(sqlcipherSrc)) {
|
|
const sqlcipherDest = path.join(
|
|
standaloneDir, "node_modules", "@signalapp", "sqlcipher", "prebuilds",
|
|
);
|
|
rmSync(sqlcipherDest, { recursive: true, force: true });
|
|
cpSync(sqlcipherSrc, sqlcipherDest, { recursive: true });
|
|
console.log("Copied @signalapp/sqlcipher prebuilds into the standalone output");
|
|
} else {
|
|
console.log(
|
|
"@signalapp/sqlcipher not installed (optional dependency) - " +
|
|
"the encrypted local index will be disabled at runtime",
|
|
);
|
|
}
|
|
|
|
// The staged first-party plugin bundles (scripts/build-plugins.mjs). The
|
|
// server installs these into its plugin registry at startup
|
|
// (lib/admin/bundled-plugins.ts), reading them from
|
|
// `<cwd>/vnc/plugins/build` - and Next's generated server.js chdir's to its
|
|
// own directory, so "cwd" is this standalone dir in every packaged build.
|
|
//
|
|
// Output file tracing cannot find these: nothing imports them, they are read
|
|
// by path at runtime. Without this copy the Electron/standalone build boots
|
|
// with no S/MIME plugin at all while the policy toggle still says it is on -
|
|
// the same silent-drop failure mode as the sqlcipher prebuilds above.
|
|
const pluginsSrc = path.join(rootDir, "vnc", "plugins", "build");
|
|
if (existsSync(pluginsSrc)) {
|
|
const pluginsDest = path.join(standaloneDir, "vnc", "plugins", "build");
|
|
rmSync(pluginsDest, { recursive: true, force: true });
|
|
cpSync(pluginsSrc, pluginsDest, { recursive: true });
|
|
console.log("Copied bundled first-party plugins into the standalone output");
|
|
} else {
|
|
console.warn(
|
|
"No bundled plugins staged at vnc/plugins/build - " +
|
|
'run "npm run build:plugins" first, or the packaged app ships without S/MIME',
|
|
);
|
|
}
|
|
|
|
// next/dist/lib/metadata/** (get-metadata-route.js and its neighbours).
|
|
//
|
|
// router-utils/filesystem.js has a plain top-level `require("../../../lib/
|
|
// metadata/get-metadata-route")` - not dynamic, not conditional - yet Next's
|
|
// own output-file-tracing for `output: "standalone"` + `next build --webpack`
|
|
// drops the entire directory. Verified by inspecting a real build: the
|
|
// package was present, this one subfolder was missing, so the standalone
|
|
// server crashed on its very first line with "Cannot find module" - every
|
|
// packaged build (Electron and Docker) was affected. Same failure class as
|
|
// the sqlcipher prebuilds above: copy by hand what the tracer misses.
|
|
const metadataSrc = path.join(rootDir, "node_modules", "next", "dist", "lib", "metadata");
|
|
const metadataDest = path.join(standaloneDir, "node_modules", "next", "dist", "lib", "metadata");
|
|
rmSync(metadataDest, { recursive: true, force: true });
|
|
cpSync(metadataSrc, metadataDest, { recursive: true });
|
|
console.log("Copied next/dist/lib/metadata into the standalone output");
|
|
|
|
console.log("Assembled standalone server at", standaloneDir);
|