From a2b8a2c3356868a7dda11dd274c798d209e9cc11 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Fri, 26 Jun 2026 05:14:04 -0700 Subject: [PATCH] [eric] build: bundle npm with node so packaged apps can install custom deps (fixes imported backend apps not starting) --- backend/apps/outputs/view_builder_templates.py | 15 +++++++-------- scripts/build-app-win.ps1 | 8 +++++--- scripts/build-app.sh | 13 ++++++++++--- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/backend/apps/outputs/view_builder_templates.py b/backend/apps/outputs/view_builder_templates.py index 0ec6cce5..a85dc88c 100644 --- a/backend/apps/outputs/view_builder_templates.py +++ b/backend/apps/outputs/view_builder_templates.py @@ -23,18 +23,17 @@ def p_resolve_npm() -> list[str] | None: node_path = os.environ.get("OPENSWARM_NODE_PATH") if node_path and os.path.exists(node_path): node_dir = os.path.dirname(node_path) + # Prefer invoking npm-cli.js through our bundled node so this doesn't depend on a system node for the shim's shebang. Second entry is the canonical Mac-dist layout (lib/node_modules/npm); first is the Windows layout (node_modules/npm beside node.exe). + for cli in ( + os.path.join(node_dir, "node_modules", "npm", "bin", "npm-cli.js"), + os.path.join(os.path.dirname(node_dir), "lib", "node_modules", "npm", "bin", "npm-cli.js"), + ): + if os.path.exists(cli): + return [node_path, cli] for shim in ("npm.cmd", "npm"): cand = os.path.join(node_dir, shim) if os.path.exists(cand): return [cand] - # node.exe with no sibling npm: invoke npm-cli.js directly via node. - for rel in ( - os.path.join("node_modules", "npm", "bin", "npm-cli.js"), - os.path.join(node_dir, "node_modules", "npm", "bin", "npm-cli.js"), - ): - cli = rel if os.path.isabs(rel) else os.path.join(node_dir, rel) - if os.path.exists(cli): - return [node_path, cli] for name in ("npm.cmd", "npm") if sys.platform == "win32" else ("npm",): found = shutil.which(name) if found: diff --git a/scripts/build-app-win.ps1 b/scripts/build-app-win.ps1 index 2755e678..d8a9f98c 100644 --- a/scripts/build-app-win.ps1 +++ b/scripts/build-app-win.ps1 @@ -322,11 +322,13 @@ try { Write-Host "[3b] Downloading $NodeUrl..." Invoke-WebRequest -Uri $NodeUrl -OutFile $NodeZip -UseBasicParsing Expand-Archive -Path $NodeZip -DestinationPath $NodeExtract -Force - # Ship just node.exe — npm/npx are unused at runtime (router + MCP - # bundles are pre-built). Saves ~70 MB from the installer. - $SrcNode = Join-Path $NodeExtract "node-$NodeVersion-win-x64\node.exe" + $SrcRoot = Join-Path $NodeExtract "node-$NodeVersion-win-x64" + $SrcNode = Join-Path $SrcRoot 'node.exe' if (-not (Test-Path $SrcNode)) { throw "node.exe not found at $SrcNode after extract" } Copy-Item -Force $SrcNode (Join-Path $NodeStageDir 'node.exe') + # Bundle npm too so packaged apps with custom deps can `npm install` them. npm.cmd + node_modules\npm sit next to node.exe in the win dist; p_resolve_npm finds node_dir\npm.cmd. + Copy-Item -Force (Join-Path $SrcRoot 'npm.cmd') (Join-Path $NodeStageDir 'npm.cmd') + Copy-Item -Recurse -Force (Join-Path $SrcRoot 'node_modules') (Join-Path $NodeStageDir 'node_modules') $Size = (Get-Item (Join-Path $NodeStageDir 'node.exe')).Length / 1MB Write-Host ("[3b] Node {0} (x64) staged ({1:N1} MB)" -f $NodeVersion, $Size) } finally { diff --git a/scripts/build-app.sh b/scripts/build-app.sh index cb51c573..01040d6c 100755 --- a/scripts/build-app.sh +++ b/scripts/build-app.sh @@ -330,11 +330,18 @@ download_node_for_arch() { local tmp; tmp=$(mktemp -d) curl -fsSL --progress-bar -o "$tmp/node.tar.gz" "$url" tar xzf "$tmp/node.tar.gz" -C "$tmp" - # Ship just the `node` binary. We don't need npm/npx/corepack at runtime — - # all router + MCP code is pre-bundled. ~50 MB per arch -> ~25 MB after - # gzip/dmg compression. cp "$tmp/node-${NODE_VERSION}-darwin-${arch}/bin/node" "$out_dir/bin/node" chmod +x "$out_dir/bin/node" + # Bundle npm too so packaged apps with custom deps can `npm install` them (the bare node can't). + mkdir -p "$out_dir/lib/node_modules" + cp -R "$tmp/node-${NODE_VERSION}-darwin-${arch}/lib/node_modules/npm" "$out_dir/lib/node_modules/npm" + # An explicit wrapper, not the dist's bin/npm symlink (packaging may not preserve symlinks): always runs our bundled node + npm-cli.js, no PATH/system-node reliance. run.sh picks it up as $NODE_DIR/npm. + cat > "$out_dir/bin/npm" <<'NPMSH' +#!/bin/sh +here="$(cd "$(dirname "$0")" && pwd)" +exec "$here/node" "$here/../lib/node_modules/npm/bin/npm-cli.js" "$@" +NPMSH + chmod +x "$out_dir/bin/npm" rm -rf "$tmp" echo "[3b] Node $NODE_VERSION ($arch) staged ($(du -h "$out_dir/bin/node" | cut -f1))" }