mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-07-10 00:14:27 +00:00
## 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
127 lines
4.2 KiB
JavaScript
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)
|
|
}
|