cameras/scripts/bundle-standalone.mjs
Michael Mainguy 30b1f36e1b Ship a standalone build and an onvif-dashboard start command
- next.config.ts builds standalone output with image optimization off,
  and scripts/bundle-standalone.mjs puts the static files beside the
  server, then drops what tracing swept in but the app never runs (the
  MediaMTX binary and sharp's platform binaries, 79 MB). The build is
  27 MB and npm pack is 4.9 MB.
- The CLI gained start, which runs that server from any directory with
  the remembered data folder and stored key in its environment, and
  admin, so the messages in the UI can name a command that exists.
- Messages that said "npm run …" now say "onvif-dashboard …".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 19:09:13 -05:00

47 lines
2.0 KiB
JavaScript

// After next build, the standalone server needs the static files beside it; Next leaves
// that copy to us (Next docs, config/output.md). Also drops in the CLI's own modules, so
// the published package can run from anywhere (vrek iss-ej4ahga).
import { access, cp, mkdir, readdir, rm, stat } from "node:fs/promises";
import path from "node:path";
const root = process.cwd();
const standalone = path.join(root, ".next", "standalone");
const exists = (file) => access(file).then(() => true, () => false);
/** Bytes under a path, so the build can say what it dropped. */
async function size(target) {
const info = await stat(target);
if (!info.isDirectory()) return info.size;
let total = 0;
for (const entry of await readdir(target)) total += await size(path.join(target, entry));
return total;
}
if (!(await exists(standalone))) {
console.error("No .next/standalone: is output: 'standalone' still set in next.config.ts?");
process.exit(1);
}
await mkdir(path.join(standalone, ".next"), { recursive: true });
await cp(path.join(root, ".next", "static"), path.join(standalone, ".next", "static"), { recursive: true });
if (await exists(path.join(root, "public"))) {
await cp(path.join(root, "public"), path.join(standalone, "public"), { recursive: true });
}
// Next's tracing sweeps in things the app never runs: the MediaMTX binary that happens to
// sit in bin/, and sharp's platform binaries, which are only for next/image (unused here).
// outputFileTracingExcludes didn't keep them out, so they go now, before publishing.
const prune = ["bin", "node_modules/@img", "node_modules/sharp"];
let freed = 0;
for (const name of prune) {
const target = path.join(standalone, name);
if (!(await exists(target))) continue;
freed += await size(target);
await rm(target, { recursive: true, force: true });
}
console.log(
`Bundled the standalone server with its static files` +
(freed ? `, and dropped ${(freed / 1024 / 1024).toFixed(0)} MB it doesn't need.` : "."),
);