Skip to content

feat(scripts): match pnpm's full npm_* env for lifecycle & run scripts - #879

Merged
jdx merged 3 commits into
aubepkg:mainfrom
rubnogueira:feat/set-npm-env-variables
Jun 13, 2026
Merged

jdx merged 3 commits into
aubepkg:mainfrom
rubnogueira:feat/set-npm-env-variables

Conversation

@rubnogueira

@rubnogueira rubnogueira commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Lifecycle and aube run scripts now receive the same npm_* environment pnpm/npm export, so PM-detection bridges and build tooling behave identically under aube. Previously aube omitted npm_execpath, npm_node_execpath, npm_package_json, npm_command, npm_config_node_gyp, npm_package_engines_*, and npm_lifecycle_script.

  • Adds PackageJson::npm_package_env() — pnpm's exact allowlist (name, version, deep-flattened engines/config/bin), envified the npm way (non-[A-Za-z0-9_] → _, case preserved). String bin flattens to the unsuffixed npm_package_bin, matching pnpm (not npm's unscoped-name rule).

  • npm_command is stamped per command family (run-script, install, rebuild, pack); re-asserted at the run spawn site so a preceding auto-install doesn't clobber it.

  • npm_config_node_gyp points at a lazy, dependency-free node-gyp stand-in written into aube's cache. It bootstraps the real node-gyp only when a tool actually executes it — no eager install — and self-heals on each write.

  • npm_node_execpath/NODE fall back to the ambient node on PATH when no runtime switch is active, so they're always populated.

Implementation notes

  • Manifest-derived vars are applied last in run_script, after the build jail's env_clear, so jailed dependency builds see them too.

  • Dedups the node.exe/node selection in aube-runtime/discover.rs behind a
    single node_exe_name() and adds a process-free node_on_path() PATH lookup.

Test plan

  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo fmt --check clean
  • Unit tests: aube-manifest, aube-scripts, aube-runtime
  • bats: run, lifecycle_scripts (new pnpm-parity tests), node_gyp_bootstrap, node_gyp_rebuild
  • Empirically diffed the full npm_package_* set against pnpm 11.5 — byte-for-byte identical

Summary by CodeRabbit

  • New Features

    • Enhanced npm lifecycle environment parity: deterministic export of npm_package_* (flattened package fields), npm_lifecycle_script, npm_execpath, npm_node_execpath and npm_command; lifecycle env now survives jailed env_clear
    • Improved script runtime selection so scripts use the pinned runtime or PATH-resolved node when needed
    • Added lazy shim support and npm_config_node_gyp support for node-gyp bootstrapping
  • Tests

    • Added and tightened tests validating npm lifecycle env export, run/install behavior, and node-gyp bootstrap handling

@coderabbitai

coderabbitai Bot commented Jun 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements pnpm-compatible npm_package_* environment variable export across manifest parsing, runtime node discovery, script settings propagation, node-gyp shim wiring, and lifecycle command execution and tests.

Changes

pnpm environment variable parity and script lifecycle wiring

Layer / File(s) Summary
Manifest environment variable export foundation
crates/aube-manifest/src/lib.rs
PackageJson::npm_package_env() generates deterministic pnpm-compatible npm_package_* pairs from name, version, engines, extra.config, and extra.bin via recursive JSON flattening. Helpers envify_env_key and flatten_json_env sanitize keys, deep-flatten objects/arrays, stringify scalars, and skip nulls. Tests validate pnpm parity and string-form bin behavior.
Node executable discovery foundation
crates/aube-runtime/src/discover.rs, crates/aube-runtime/src/lib.rs
Centralizes platform-specific node executable naming via node_exe_name(). Adds node_on_path() to discover PATH node without spawning. Refactors node_paths_in()/probe_path_node to use the helper and updates test helpers and re-exports.
Script settings and npm environment infrastructure
crates/aube-scripts/src/lib.rs
ScriptSettings gains command (maps to npm_command) and node_gyp_js (npm_config_node_gyp). apply_script_settings_env now stamps npm_execpath, npm_node_execpath/NODE, npm_command, and optionally npm_config_node_gyp + AUBE_NODE_GYP_EXE. New apply_npm_manifest_env scrubs existing npm_package_*, sets npm_lifecycle_script and npm_package_json, then applies manifest.npm_package_env().
Node-gyp lazy shim generation
crates/aube/src/commands/install/node_gyp_bootstrap.rs
lazy_js_shim_path() ensures lazy-bin exists and returns path to generated node-gyp.js shim. write_lazy_shims now atomically writes executable Node shim that calls __node-gyp-bootstrap when aube markers exist or falls back to PATH node-gyp/node-gyp.cmd.
Script settings command parameter and runtime integration
crates/aube/src/commands/script_settings.rs, crates/aube/src/runtime.rs
configure_script_settings and configure_script_settings_for_cwd accept command: Option<&str> and propagate it into ScriptSettings. Node selection prefers pinned runtime and falls back to aube_runtime::node_on_path. node_gyp_js populated from lazy shim path with non-fatal .ok(). apply_child_env falls back to PATH-discovered node when runtime node is absent.
Command entry points wired with script context
crates/aube/src/commands/install/mod.rs, crates/aube/src/commands/pack.rs, crates/aube/src/commands/rebuild.rs
Install/pack/rebuild callers now pass explicit command context ("install", "pack", "rebuild") to script settings configuration.
Run command explicit npm lifecycle variable handling
crates/aube/src/commands/run.rs
aube run uses "run-script" context, captures raw script as lifecycle_script, spawns via aube_scripts::spawn_shell(&shell_cmd), forces npm_command="run-script" at spawn, and calls aube_scripts::apply_npm_manifest_env to apply npm_lifecycle_script, npm_package_json, and full manifest.npm_package_env() pairs.
Lifecycle environment parity tests
test/lifecycle_scripts.bats, test/run.bats, test/node_gyp_bootstrap.bats, test/node_gyp_rebuild.bats
New tests assert that aube install and aube run export comprehensive npm_* env sets (including npm_command, exec paths, npm_package_json, npm_config_node_gyp, flattened engines/config/bin, and npm_lifecycle_script). Node-gyp bootstrap tests tightened to assert absence of the v12/ bootstrapped bucket specifically.

Sequence Diagram

sequenceDiagram
  participant CLI as Aube CLI
  participant Settings as ScriptSettings
  participant Manifest as PackageJson
  participant Runner as spawn_shell Process
  CLI->>Settings: configure_script_settings(command)
  CLI->>Manifest: read package.json
  CLI->>Settings: apply_script_settings_env(cmd)
  Settings->>Runner: set npm_execpath/npm_node_execpath/NODE/npm_command/npm_config_node_gyp
  Manifest->>Runner: provide npm_package_env() pairs
  CLI->>Runner: spawn_shell(shell_cmd) with npm_lifecycle_script & npm_package_json
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • jdx/aube#861: Both PRs touch runtime Node executable/path discovery and env propagation; this PR adds node_on_path/node_exe_name and updates apply_child_env fallback behavior.

Suggested labels

keep-open

Poem

🐰 A manifest's whispers now fill the air,
npm_package_* vars everywhere!
Node paths dance and find their way,
Lazy shims hop in to play.
Scripts run true — parity today!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding pnpm-compatible npm_* environment variables to lifecycle and run scripts.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds full npm_* environment parity with pnpm/npm for lifecycle and aube run scripts, closing the gap that caused PM-detection bridges and native build tools to misbehave under aube. The implementation is thorough: a new PackageJson::npm_package_env() method with pnpm's exact allowlist, a apply_npm_manifest_env helper that scrubs-then-restamps on both jailed and non-jailed paths, and a lazy node-gyp.js shim for npm_config_node_gyp parity.

  • npm_package_env() correctly uses BTreeMap for engines (sorted, deterministic) and delegates config/bin to JSON insertion-order preserving serde_json::Map; the scrub-then-stamp pattern in apply_npm_manifest_env correctly handles both env_clear() jailed builds and the unjailed aube run path.
  • The node-gyp.js shim's execFileSync bootstrap call lacks a try-catch; a bootstrap failure surfaces as a raw Node.js stack trace rather than a clean error message.

Confidence Score: 5/5

Safe to merge — the env-var plumbing is well-tested and the one rough edge (unhandled bootstrap exception in the JS shim) only affects error-path UX.

The core logic — scrub-then-stamp, BTreeMap-ordered engines, jail/non-jail path handling, lazy shim writes — is correct and backed by unit tests and bats integration tests. The only finding is a missing try-catch in the node-gyp.js shim that degrades the error message when bootstrap fails; it does not affect the happy path or data correctness.

crates/aube/src/commands/install/node_gyp_bootstrap.rs — the node-gyp.js shim's bootstrap error path.

Important Files Changed

Filename Overview
crates/aube-manifest/src/lib.rs Adds npm_package_env() method returning pnpm-compatible env pairs, plus envify_env_key and flatten_json_env helpers; well-tested with unit tests covering the allowlist and string-bin edge case
crates/aube-scripts/src/lib.rs Adds apply_npm_manifest_env (public) and extends apply_script_settings_env with npm_execpath, npm_command, npm_node_execpath/NODE, and npm_config_node_gyp; scrub-then-stamp pattern is correct for both jailed and non-jailed paths
crates/aube-runtime/src/discover.rs Deduplicates node.exe/node selection behind node_exe_name() const fn and adds node_on_path() for process-free PATH lookup; refactored node_paths_in logic is functionally equivalent
crates/aube/src/commands/install/node_gyp_bootstrap.rs Adds lazy_js_shim_path() and writes a node-gyp.js shim for npm_config_node_gyp; the shim's execFileSync call has no try-catch so bootstrap failures surface as raw Node.js stack traces
crates/aube/src/commands/script_settings.rs Threads command and node_gyp_js through to ScriptSettings; adds node_on_path fallback for node_exe when no runtime switch is active
crates/aube/src/commands/run.rs Removes redundant apply_child_env call (now handled via ScriptSettings in spawn_shell), re-asserts npm_command and applies manifest env via apply_npm_manifest_env; lifecycle_script captured before node-arg injection correctly
crates/aube/src/runtime.rs Updated apply_child_env to fall back to node_on_path() when no runtime switch is active, matching the new ScriptSettings fallback
test/lifecycle_scripts.bats Adds pnpm-parity install lifecycle test asserting all new npm_* vars are present and well-formed
test/run.bats Adds pnpm-parity run test covering npm_execpath, npm_node_execpath, npm_package_json, npm_command, npm_config_node_gyp, npm_package_engines_*, npm_lifecycle_script

Reviews (2): Last reviewed commit: "fix: apply ai review suggestions" | Re-trigger Greptile

Comment thread crates/aube-scripts/src/lib.rs Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aube-runtime/src/discover.rs`:
- Around line 208-210: node_on_path() currently returns find_on_path(...)
directly which can yield relative or non-executable paths; update node_on_path
to canonicalize the returned PathBuf and verify it is an absolute file and
executable before returning. Specifically: call find_on_path(node_exe_name()),
if Some(p) then attempt std::fs::canonicalize(&p) (handle errors by returning
None), verify the canonical path metadata is a file and that it is executable
(on Unix check permission bits, on Windows use an appropriate fallback such as
checking extensions/exists), and only return Some(canonical_path) when those
checks pass; otherwise return None so npm_node_execpath / NODE can't be set to a
non-absolute or non-runnable path.

In `@crates/aube-scripts/src/lib.rs`:
- Around line 564-575: The helper apply_npm_manifest_env currently overwrites
only allowlisted keys but leaves inherited npm_package_* vars intact; update
apply_npm_manifest_env to first remove/clear any existing environment entries
whose names start with "npm_package_" before calling cmd.env(...) and before
iterating manifest.npm_package_env(); likewise, in the other site where
manifest.npm_package_env() is applied (the manifest.npm_package_env() loop in
crates/aube/src/commands/run.rs), add the same cleanup step to remove existing
npm_package_* entries on the Command/environment before stamping the allowlisted
pairs so no disallowed inherited npm_package_* variables remain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e159ec23-04a9-48a6-b029-93e58e6cfea6

📥 Commits

Reviewing files that changed from the base of the PR and between d7ed45c and 09be709.

📒 Files selected for processing (15)
  • crates/aube-manifest/src/lib.rs
  • crates/aube-runtime/src/discover.rs
  • crates/aube-runtime/src/lib.rs
  • crates/aube-scripts/src/lib.rs
  • crates/aube/src/commands/install/mod.rs
  • crates/aube/src/commands/install/node_gyp_bootstrap.rs
  • crates/aube/src/commands/pack.rs
  • crates/aube/src/commands/rebuild.rs
  • crates/aube/src/commands/run.rs
  • crates/aube/src/commands/script_settings.rs
  • crates/aube/src/runtime.rs
  • test/lifecycle_scripts.bats
  • test/node_gyp_bootstrap.bats
  • test/node_gyp_rebuild.bats
  • test/run.bats

Comment thread crates/aube-runtime/src/discover.rs Resolved
Comment thread crates/aube-scripts/src/lib.rs Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/aube/src/commands/run.rs (1)

923-930: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't make aube run fail hard on current_exe() lookup.

apply_script_settings_env in crates/aube-scripts/src/lib.rs treats the same lookup as best-effort, but this path turns it into a hard error. That means a current_exe() failure now aborts every aube run, even when the script never touches node-gyp. Stamp AUBE_NODE_GYP_EXE conditionally instead of bubbling the error.

Suggested fix
     let mut command = aube_scripts::spawn_shell(&shell_cmd);
-    command
-        .env("PATH", &new_path)
-        .current_dir(cwd)
-        .env(
-            "AUBE_NODE_GYP_EXE",
-            std::env::current_exe().into_diagnostic()?,
-        )
-        .env("AUBE_NODE_GYP_PROJECT_DIR", node_gyp_project_dir)
+    command.env("PATH", &new_path).current_dir(cwd);
+    if let Ok(aube_exe) = std::env::current_exe() {
+        command.env("AUBE_NODE_GYP_EXE", aube_exe);
+    }
+    command
+        .env("AUBE_NODE_GYP_PROJECT_DIR", node_gyp_project_dir)
         .env("npm_lifecycle_event", script)
         .env("npm_command", "run-script")
         .stderr(aube_scripts::child_stderr());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aube/src/commands/run.rs` around lines 923 - 930, The code currently
unconditionally calls std::env::current_exe().into_diagnostic()? while setting
the AUBE_NODE_GYP_EXE env on the Command in run.rs, so a current_exe() failure
will abort aube run; change this to a best-effort lookup and only set the env
when the lookup succeeds: call std::env::current_exe().ok() (or handle the
Result without returning an error), convert the PathBuf to a string/os-str as
needed, and call command.env("AUBE_NODE_GYP_EXE", exe_path) only when Some(path)
is present; leave AUBE_NODE_GYP_PROJECT_DIR unchanged. This mirrors
apply_script_settings_env’s best-effort behavior and prevents current_exe()
failures from bubbling out as hard errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/aube/src/commands/run.rs`:
- Around line 923-930: The code currently unconditionally calls
std::env::current_exe().into_diagnostic()? while setting the AUBE_NODE_GYP_EXE
env on the Command in run.rs, so a current_exe() failure will abort aube run;
change this to a best-effort lookup and only set the env when the lookup
succeeds: call std::env::current_exe().ok() (or handle the Result without
returning an error), convert the PathBuf to a string/os-str as needed, and call
command.env("AUBE_NODE_GYP_EXE", exe_path) only when Some(path) is present;
leave AUBE_NODE_GYP_PROJECT_DIR unchanged. This mirrors
apply_script_settings_env’s best-effort behavior and prevents current_exe()
failures from bubbling out as hard errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 670a955d-fd1e-4913-9b73-81cca2c3bdde

📥 Commits

Reviewing files that changed from the base of the PR and between 09be709 and d71269e.

📒 Files selected for processing (3)
  • crates/aube-runtime/src/discover.rs
  • crates/aube-scripts/src/lib.rs
  • crates/aube/src/commands/run.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/aube-runtime/src/discover.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants