mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
* nix: share npmDeps FOD between paseo and paseo-desktop
The daemon and desktop derivations share package-lock.json, so their
npmDeps FODs contain byte-identical content. But Nix names the FOD
store path with the consuming pname prefix, so paseo-<v>-npm-deps and
paseo-desktop-<v>-npm-deps resolve to different store paths despite
identical bytes. Building both runs prefetch-npm-deps twice in
parallel; on a clean store this downloads the entire registry
(~1-2 GB of tarballs) twice over the wire.
Wire paseo through as a callPackage arg of the desktop drv and
inherit (paseo) npmDeps. One FOD, one fetch, one store path.
Override path becomes paseo.override { npmDepsHash = "..."; } — the
desktop drv picks up the overridden npmDeps transitively. Downstream
flakes that previously did paseo-desktop.override { npmDepsHash }
need to either drop the desktop-side override (recommended) or pass
the overridden daemon through as paseo-desktop.override { paseo }.
* nix: trace daemon runtime closure with @vercel/nft
The daemon Nix installPhase used to `cp -a node_modules $out/lib/paseo/`,
shipping every package in the hoisted root — Expo, React Native, Metro,
Electron, ML stacks, ~1700 third-party deps the daemon never `require()`s
at runtime. Result: ~2.6 GB store path for a daemon whose actual runtime
closure is a tiny fraction of that.
Replace it with static module-graph tracing via @vercel/nft (the same
library Vercel and Next.js use for serverless bundling). The new
installPhase runs `node scripts/trace-daemon.mjs`, which:
1. Calls nodeFileTrace on three entry points — cli/dist/index.js,
server/dist/scripts/supervisor-entrypoint.js, and the forked
server/dist/server/terminal/terminal-worker-process.js. The worker
needs to be a separate entry because nft does not follow fork()
process boundaries.
2. Unions the trace result with an explicit list of non-JS runtime
inputs nft does not detect: shell-integration assets read via
readFileSync, the Silero VAD ONNX model loaded by the sherpa
provider, .env.example, the CLI shebang script, and node-pty's
compiled prebuilt binary for the host platform.
3. Ignores cross-platform native variants we explicitly do not ship
(sherpa-onnx-*, @mariozechner/clipboard-*) and node-fetch's
optional `encoding` peer.
installPhase consumes the file list, copies each entry into
$out/lib/paseo/ preserving its repo-relative path, and wraps two bin
entries (paseo, paseo-server) via makeWrapper.
Verified end-to-end:
- daemon $out drops from ~2.6 GB to ~131 MB (≈95% reduction)
- paseo --version, paseo --help work from the new $out
- paseo-server boots, HTTP server reaches "Server listening" within
~40 ms, no missing-module errors in the bootstrap log
- speech features degrade gracefully when sherpa-onnx-* / model files
are absent (the documented behaviour for the Nix build, unchanged)
@vercel/nft added to root devDependencies (1.5.0); used only at
build time by the Nix derivation.
* fix(nix): gitignore result
* fix(nix): refresh npm deps hash post-rebase
The conflict resolution during rebase kept the trace-daemon hash, but
the merged package-lock.json combines upstream's lockfile updates with
the @vercel/nft addition, so the hash needed to be recomputed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
4.6 KiB
Nix
132 lines
4.6 KiB
Nix
{
|
|
lib,
|
|
stdenv,
|
|
buildNpmPackage,
|
|
nodejs_22,
|
|
python3,
|
|
makeWrapper,
|
|
# node-pty needs libuv headers on Linux
|
|
libuv,
|
|
# Exposed so downstream flakes that follow a different nixpkgs revision
|
|
# (where `fetchNpmDeps` may produce a different hash for the same lockfile)
|
|
# can override via `.override { npmDepsHash = "sha256-..."; }` without
|
|
# `overrideAttrs` gymnastics — `npmDepsHash` is destructured from
|
|
# `buildNpmPackage`'s args, so `overrideAttrs` cannot reach it.
|
|
#
|
|
# The default is read from a sidecar file so the CI auto-updater can replace
|
|
# the hash with a single file write instead of a sed against this source.
|
|
npmDepsHash ? lib.fileContents ./npm-deps.hash,
|
|
}:
|
|
|
|
buildNpmPackage rec {
|
|
pname = "paseo";
|
|
version = (builtins.fromJSON (builtins.readFile ../package.json)).version;
|
|
|
|
src = lib.cleanSourceWith {
|
|
src = ./..;
|
|
filter = path: type:
|
|
let
|
|
baseName = builtins.baseNameOf path;
|
|
relPath = lib.removePrefix (toString ./..) path;
|
|
in
|
|
# Exclude non-daemon workspace contents (keep package.json for workspace resolution)
|
|
!(lib.hasPrefix "/packages/app/src" relPath)
|
|
&& !(lib.hasPrefix "/packages/app/assets" relPath)
|
|
&& !(lib.hasPrefix "/packages/app/android" relPath)
|
|
&& !(lib.hasPrefix "/packages/app/ios" relPath)
|
|
&& !(lib.hasPrefix "/packages/website/src" relPath)
|
|
&& !(lib.hasPrefix "/packages/website/public" relPath)
|
|
&& !(lib.hasPrefix "/packages/desktop/src" relPath)
|
|
&& !(lib.hasPrefix "/packages/desktop/src-tauri" relPath)
|
|
# Exclude test fixtures and debug files
|
|
&& !(lib.hasSuffix ".test.ts" baseName)
|
|
&& !(lib.hasSuffix ".e2e.test.ts" baseName)
|
|
&& baseName != "node_modules"
|
|
&& baseName != ".git"
|
|
&& baseName != ".paseo"
|
|
&& baseName != ".DS_Store";
|
|
};
|
|
|
|
nodejs = nodejs_22;
|
|
|
|
# Default hash lives in nix/npm-deps.hash (see arg default above).
|
|
# CI auto-updates that file when package-lock.json changes (see .github/workflows/).
|
|
inherit npmDepsHash;
|
|
|
|
# Prevent onnxruntime-node's install script from running during automatic
|
|
# npm rebuild (it tries to download from api.nuget.org, which fails in the sandbox).
|
|
# We manually rebuild only node-pty in buildPhase.
|
|
npmRebuildFlags = [ "--ignore-scripts" ];
|
|
|
|
nativeBuildInputs = [
|
|
python3 # for node-gyp (node-pty compilation)
|
|
makeWrapper
|
|
];
|
|
|
|
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [
|
|
libuv
|
|
];
|
|
|
|
# Don't use the default npm build hook — we need a custom build sequence
|
|
dontNpmBuild = true;
|
|
|
|
buildPhase = ''
|
|
runHook preBuild
|
|
|
|
# Rebuild only node-pty (native addon for terminal emulation).
|
|
# Speech-related native modules (sherpa-onnx, onnxruntime-node) are
|
|
# intentionally left unbuilt — they're lazily loaded and gracefully
|
|
# degrade when unavailable.
|
|
npm rebuild node-pty
|
|
|
|
# Build all daemon packages in dependency order (defined in package.json)
|
|
npm run build:daemon
|
|
|
|
runHook postBuild
|
|
'';
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
# Compute the daemon's runtime closure by static module-graph tracing
|
|
# (@vercel/nft from supervisor-entrypoint.js, cli/dist/index.js, and the
|
|
# forked terminal-worker-process.js) plus an explicit list of non-JS
|
|
# assets read at runtime. The trace script is the single source of
|
|
# truth for what the daemon needs at $out — auditable in plain JS, no
|
|
# npm hoisting / .bin / workspace-symlink footguns.
|
|
mkdir -p $out/lib/paseo
|
|
node scripts/trace-daemon.mjs > daemon-files.txt
|
|
|
|
while IFS= read -r path; do
|
|
[ -z "$path" ] && continue
|
|
mkdir -p "$out/lib/paseo/$(dirname "$path")"
|
|
cp -a "$path" "$out/lib/paseo/$path"
|
|
done < daemon-files.txt
|
|
|
|
# Root package.json lets node resolve the workspace layout when the
|
|
# CLI/server bin starts from $out.
|
|
cp package.json $out/lib/paseo/
|
|
|
|
# Create wrapper for the server entry point (for systemd / direct use)
|
|
mkdir -p $out/bin
|
|
makeWrapper ${nodejs}/bin/node $out/bin/paseo-server \
|
|
--add-flags "$out/lib/paseo/packages/server/dist/scripts/supervisor-entrypoint.js" \
|
|
--set NODE_ENV production
|
|
|
|
# Create wrapper for the CLI
|
|
makeWrapper ${nodejs}/bin/node $out/bin/paseo \
|
|
--add-flags "$out/lib/paseo/packages/cli/dist/index.js" \
|
|
--set NODE_PATH "$out/lib/paseo/node_modules"
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
meta = {
|
|
description = "Self-hosted daemon for Claude Code, Codex, and OpenCode";
|
|
homepage = "https://github.com/getpaseo/paseo";
|
|
license = lib.licenses.agpl3Plus;
|
|
mainProgram = "paseo";
|
|
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
|
};
|
|
}
|