perf(deploy): stop the app closure retaining its build toolchain

node-gyp leaves its scaffolding beside the addons it compiles, and
several of those files carry absolute store paths to the tools that did
the compiling: build/node_gyp_bins/python3 is an ELF copy of python3
with an RPATH into it, build/config.gypi names python3, nodejs and npm,
the .o.d files under build/Release/.deps name pcsclite's dev output, and
pnpm rewrote a few CLI helpers' shebangs to the full nodejs.

Nix scans $out for store hashes, so each of those became a runtime
reference. Every ATM was carrying python311, nodejs, npm and
pcsclite.dev -- 212MB of closure -- for files nothing reads after the
build. Only build/Release/*.node is ever loaded, through bindings and
node-gyp-build.

Drop the scaffolding, and point the stray shebangs at PATH rather than
deleting files a package might still require. All three addons survive
with their RPATHs intact: better_sqlite3.node, pcsclite.node and the
serialport prebuilds. The derivation's references are now down to bash,
pcsclite.lib and the two gcc runtime libs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-09-24 15:26:58 +02:00
commit 73a77c82b3

View file

@ -190,6 +190,33 @@ pkgs.stdenv.mkDerivation (finalAttrs: {
copy_pnpm_pkg "@serialport/$parser" "$out/node_modules/@serialport/$parser" copy_pnpm_pkg "@serialport/$parser" "$out/node_modules/@serialport/$parser"
done done
# ── Strip node-gyp build detritus ──────────────────────────────────
# node-gyp leaves its scaffolding beside the compiled addons, and several
# of those files embed absolute /nix/store paths to the BUILD toolchain:
# build/node_gyp_bins/python3 an ELF copy of python3 with an RPATH
# build/config.gypi python3 + nodejs + npm paths
# build/Release/.deps/**.o.d pcsclite.dev include paths
# Nix scans $out for store hashes, so each becomes a RUNTIME reference and
# drags python311 + nodejs + npm + pcsclite.dev (~212MB of closure) onto
# every ATM. Nothing reads them at runtime — only build/Release/*.node is
# loaded, via `bindings` / `node-gyp-build`. Keep the addons, drop the
# scaffolding. obj.target/*.node is node-gyp's pre-copy of the same addon;
# the loaded one at build/Release/*.node is untouched.
find $out/node_modules -type d \
\( -name node_gyp_bins -o -name .deps -o -name obj.target -o -name obj \) \
-prune -exec rm -rf {} +
find $out/node_modules -path '*/build/*' -type f \
\( -name config.gypi -o -name '*.mk' -o -name Makefile \
-o -name binding.Makefile -o -name '*.a' -o -name '*.o' \) -delete
# pnpm/node-gyp rewrote these CLI helpers' shebangs to the build nodejs,
# which alone retains the full nodejs (not the slim one Electron needs).
# They are build-time utilities — the runtime entry of each package
# (index.js) carries no shebang — so point them at PATH instead of
# deleting files a package might still require.
find $out/node_modules -type f -name '*.js' \
-exec sed -i '1s|^#!/nix/store/[^ ]*/bin/node$|#!/usr/bin/env node|' {} +
runHook postInstall runHook postInstall
''; '';