CodeNomad/scripts/smoke-packaged-resources.cjs
Shantur Rathore 3ccdd36ad8
Reduce App package sizes (#453)
## Summary
- Trim Electron app packaging to only include runtime shell assets and
remove unused workspace runtime dependencies.
- Copy server resources explicitly, filtering stale build artifacts and
pruning known non-runtime dependency files.
- Bundle only the Node executable instead of the full Node distribution.

## Validation
- Built macOS Electron artifacts with `npm run build:mac --workspace
@neuralnomads/codenomad-electron-app`.
- Ran packaged server CLI smoke test: `dist/bin.js --version` returned
`0.16.0`.
- Ran packaged dependency import smoke test for pruned runtime
dependencies.

## Size Impact
- macOS x64 zip: 161M
- macOS arm64 zip: 161M
- macOS arm64 app bundle: 435M
- Packaged server node_modules: 25M
2026-05-15 19:13:13 +01:00

127 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
const fs = require("fs")
const path = require("path")
const { spawnSync } = require("child_process")
const requiredPackages = [
"yaml",
"fastify",
"@fastify/static",
"@fastify/cors",
"@fastify/reply-from",
"openai",
"pino",
"undici",
"zod",
"node-forge",
]
function parseArgs(argv) {
const options = {}
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index]
if (arg === "--resources") options.resources = argv[++index]
else if (arg === "--loading") options.loading = argv[++index]
else if (arg === "--target") options.target = argv[++index]
else throw new Error(`Unknown argument: ${arg}`)
}
if (!options.resources) throw new Error("Missing --resources <path>")
return options
}
function platformDirName() {
const platform = process.platform === "darwin" ? "darwin" : process.platform
const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : process.arch
return `${platform}-${arch}`
}
function targetParts(target) {
const [platform, ...archParts] = target.split("-")
const arch = archParts.join("-")
if (!platform || !arch) throw new Error(`Invalid target: ${target}`)
return { platform, arch }
}
function nodeBinary(resourcesRoot, target) {
const { platform } = targetParts(target)
const executable = platform === "win32" ? "node.exe" : path.join("bin", "node")
return path.join(resourcesRoot, "node", target, executable)
}
function canExecuteTarget(target) {
return target === platformDirName()
}
function run(command, args, options = {}) {
const result = spawnSync(command, args, { stdio: "inherit", ...options })
if (result.error) throw result.error
if (result.status !== 0) {
throw new Error(`${command} exited with code ${result.status ?? 1}`)
}
}
function smokeServer(resourcesRoot, target) {
const serverRoot = path.join(resourcesRoot, "server")
const entrypoint = path.join(serverRoot, "dist", "bin.js")
const node = nodeBinary(resourcesRoot, target)
for (const requiredPath of [node, entrypoint, path.join(serverRoot, "node_modules")]) {
if (!fs.existsSync(requiredPath)) throw new Error(`Missing packaged runtime path: ${requiredPath}`)
}
for (const packageName of requiredPackages) {
const packageRoot = path.join(serverRoot, "node_modules", ...packageName.split("/"))
if (!fs.existsSync(packageRoot)) throw new Error(`Missing packaged dependency: ${packageName}`)
}
console.log(`packaged server static checks ok for ${target}`)
if (!canExecuteTarget(target)) {
console.log(`skipping executable smoke for ${target} on host ${platformDirName()}`)
return
}
run(node, [entrypoint, "--version"])
const requireScript = [
"import { createRequire } from 'module';",
"import path from 'path';",
`const root = ${JSON.stringify(serverRoot)};`,
"const req = createRequire(path.join(root, 'dist/bin.js'));",
`${JSON.stringify(requiredPackages)}.forEach((name) => req(name));`,
"console.log('packaged dependency imports ok');",
].join(" ")
run(node, ["--input-type=module", "-e", requireScript])
}
function smokeLoadingAssets(loadingRoot) {
if (!loadingRoot) return
const htmlPath = path.join(loadingRoot, "loading.html")
if (!fs.existsSync(htmlPath)) throw new Error(`Missing loading HTML: ${htmlPath}`)
const html = fs.readFileSync(htmlPath, "utf8")
const refs = [...html.matchAll(/(?:src|href)=["']([^"']+)["']/g)].map((match) => match[1])
for (const ref of refs) {
if (/^(?:https?:)?\/\//.test(ref)) continue
if (!ref.startsWith("/assets/") && !ref.startsWith("assets/")) continue
const relative = ref.replace(/^\//, "")
const target = path.join(loadingRoot, relative)
if (!fs.existsSync(target)) throw new Error(`Missing loading asset referenced by ${htmlPath}: ${ref}`)
}
console.log("loading asset references ok")
}
try {
const options = parseArgs(process.argv.slice(2))
const resourcesRoot = path.resolve(options.resources)
const target = options.target ?? platformDirName()
smokeServer(resourcesRoot, target)
smokeLoadingAssets(options.loading ? path.resolve(options.loading) : null)
} catch (error) {
console.error("[smoke-packaged-resources] failed:", error)
process.exit(1)
}