Skip to content

fix(scripts): close 3 lifecycle parity gaps with pnpm#421

Merged
jdx merged 6 commits intomainfrom
claude/lifecycle-scripts-port
Apr 30, 2026
Merged

fix(scripts): close 3 lifecycle parity gaps with pnpm#421
jdx merged 6 commits intomainfrom
claude/lifecycle-scripts-port

Conversation

@jdx
Copy link
Copy Markdown
Contributor

@jdx jdx commented Apr 30, 2026

Summary

Ports the relevant tests from pnpm/test/install/lifecycleScripts.ts into test/lifecycle_scripts.bats, per the plan in test/PNPM_TEST_IMPORT.md (#416), and closes the parity gaps surfaced by the port.

Test ports

3 green ports — orthogonal stdout-visibility checks that complement aube's existing filesystem-marker tests by proving the script's echoed output reaches the user through aube's progress UI:

  • preinstall echo (pnpm L43)
  • postinstall echo (pnpm L56)
  • prepare echo on argumentless install (pnpm L95)

3 ports that previously documented parity gaps, now green:

  • npm_config_user_agent exported to lifecycle scripts (pnpm L29)
  • root postinstall is NOT triggered when adding a named dep (pnpm L69)
  • root prepare is NOT triggered when adding a named dep (pnpm L82)

2 extra coverage tests for aube remove / aube update — the same pnpm contract extended to all chained-call commands (added in response to PR review).

Gap fixes

1. npm_config_user_agent exported

crates/aube-scripts/src/lib.rs — adds aube_user_agent() returning "aube/<version> <os> <arch>" (mirrors pnpm's format) and sets it via apply_script_settings_env, which covers root + dep, jailed + non-jailed paths. Dep build scripts that sniff this env var to detect the running PM (husky, unrs-resolver, node-pre-gyp) now recognize aube instead of falling back to npm-mode.

2. Root hooks gated to argumentless aube install only

pnpm's contract: root lifecycle hooks fire only on the literal pnpm install invocation (no package args). Every other entry point — add, remove, update, dedupe, dlx, patch tooling, the ensure_installed auto-install before run/test, nested git-prepare installs — must skip them.

  • crates/aube/src/commands/install/mod.rs — adds InstallOptions.skip_root_lifecycle: bool and gates both run_root_lifecycle blocks on it. The chained-call constructor with_mode() defaults to true so every chained site (every command except argumentless install) inherits the correct behavior automatically. Argumentless aube install (via InstallArgs::into_options) and ci.rs / deploy.rs (literal struct) keep false.

Test plan

  • mise run test:bats test/lifecycle_scripts.bats — 21 ok, no skips
  • cargo test -p aube-scripts — 29 ok
  • Combined run across install, add, remove, update, lifecycle_scripts, dedupe, patch, ci suites — 141 ok, 0 failures
  • Pre-commit hooks (cargo-fmt, cargo-clippy, shfmt, shellcheck) — green

🤖 Generated with Claude Code


Note

Medium Risk
Changes when root lifecycle hooks run across multiple commands and adds a new env var for all lifecycle scripts, which may affect projects relying on prior hook behavior or script environment.

Overview
Improves pnpm parity for lifecycle scripts by exporting npm_config_user_agent (Node-style platform/arch) to all spawned scripts so dependency postinstalls can correctly detect aube.

Updates the install pipeline to skip root preinstall/install/postinstall/prepare hooks for chained installs (e.g. add/remove/update), while keeping them enabled for argumentless aube install, ci, deploy, and nested git-dep prepare installs.

Ports/extends pnpm lifecycle tests in test/lifecycle_scripts.bats and adds a unit test for the new user-agent formatting.

Reviewed by Cursor Bugbot for commit 3a16e90. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds three green ports of pnpm/test/install/lifecycleScripts.ts —
preinstall/postinstall/prepare echo reaches the user — and three
skip tests documenting parity gaps surfaced by porting the suite:
npm_config_user_agent not exported, root postinstall fires on
`aube add <pkg>`, root prepare fires on `aube add <pkg>`. Each
skip cites the pnpm source line and the underlying contract so a
future PM-side fix knows what to enable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This PR closes three pnpm lifecycle parity gaps: exports npm_config_user_agent to all lifecycle scripts, adds InstallOptions.skip_root_lifecycle to gate root hooks to argumentless aube install only, and ports corresponding pnpm test cases plus coverage for aube remove and aube update.

Confidence Score: 5/5

Safe to merge; all InstallOptions construction paths correctly set skip_root_lifecycle and the gate logic is straightforward.

Only P2 findings: the simplified npm_config_user_agent format (missing node/ field) and an incomplete arch allowlist in one unit test. No logic errors, no missing guards on hot paths, and the previous P1 about remove/update not inheriting the flag is correctly resolved by making with_mode() the authoritative default.

crates/aube-scripts/src/lib.rs — user-agent format and test arch allowlist are worth a second look before future tool-compatibility claims are made.

Important Files Changed

Filename Overview
crates/aube-scripts/src/lib.rs Adds aube_user_agent() with Node-style platform/arch mapping and injects npm_config_user_agent into every lifecycle script via apply_script_settings_env; includes unit tests. The UA format is a simplified subset of npm/pnpm's (omits node/<version> field).
crates/aube/src/commands/install/mod.rs Adds skip_root_lifecycle: bool to InstallOptions; gates both run_root_lifecycle call sites on it; with_mode() defaults to true so all chained commands inherit skip automatically. Explicit false only for argumentless install, ci, deploy, install-test, and git-prepare.
crates/aube/src/commands/install/git_prepare.rs Overrides skip_root_lifecycle back to false after with_mode() for nested git-dep prepare installs, correctly treating the git dep's own root as the install target.
crates/aube/src/commands/install_test.rs Correctly overrides skip_root_lifecycle = false after with_mode() so the install phase of install-test runs root lifecycle hooks, consistent with its semantics as install && test.
crates/aube/src/commands/ci.rs Adds skip_root_lifecycle: false to the struct literal to preserve existing ci behavior after the new field was introduced.
crates/aube/src/commands/deploy.rs Adds skip_root_lifecycle: false to the struct literal so deploy's explicit install continues to run root hooks.
crates/aube/src/commands/add.rs No code changes; adds a comment explaining that with_mode() already provides the correct skip_root_lifecycle: true default for aube add.
test/lifecycle_scripts.bats Adds 8 new bats tests: 3 stdout-visibility ports from pnpm's suite, 3 parity-gap tests (user-agent, no postinstall/prepare on aube add), and 2 extra coverage tests for aube remove and aube update.

Fix All in Claude Code

Reviews (6): Last reviewed commit: "fix(scripts): add ppc/ppc64/loong64 mapp..." | Re-trigger Greptile

- Export npm_config_user_agent to every lifecycle script so dep
  postinstalls (husky, unrs-resolver, node-pre-gyp, etc.) can detect
  the running PM. Format mirrors pnpm: "aube/<version> <os> <arch>".
- Gate the root preinstall/install/postinstall/prepare hooks behind a
  new InstallOptions.skip_root_lifecycle flag and set it on `aube add`
  (single-project + workspace paths). Matches pnpm's contract: hooks
  fire only on argumentless `aube install`, not on `aube install <pkg>`
  / `aube add`. Removes the perf hit of re-running an expensive root
  postinstall on every incremental add.
- Drop the corresponding skip-with-citation tests from
  test/lifecycle_scripts.bats — all 3 ports now pass green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

Benchmark changes

Versions:

  • pnpm: 11.0.1 -> 11.0.3

Public ratios: warm installs vs Bun 3x -> 4x; warm installs vs pnpm 9x -> 9x.

Benchmark aube bun pnpm
Fresh install (warm cache) 237ms -> 627ms (+165%) 728ms -> 2513ms (+245%) 2104ms -> 5708ms (+171%)
CI install (warm cache, GVS disabled) 564ms -> 2805ms (+397%) 742ms -> 2627ms (+254%) 2094ms -> 5743ms (+174%)
CI install (cold cache, GVS disabled) 2282ms -> 9596ms (+321%) 1895ms -> 11323ms (+498%) 5439ms -> 9845ms (+81%)

3a16e90 vs 113eb8b | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex.

@jdx jdx changed the title test(lifecycle): port stdout-visibility + parity-gap cases from pnpm fix(scripts): close 3 lifecycle parity gaps with pnpm Apr 30, 2026
Comment thread crates/aube/src/commands/install/mod.rs Outdated
Comment thread crates/aube/src/commands/install/mod.rs
Greptile flagged that the previous fix only covered \`aube add\`. The
contract is broader: pnpm fires root lifecycle hooks only on the
literal argumentless \`pnpm install\`, never on any other command.

Flip \`InstallOptions::with_mode()\` (the chained-call constructor) to
default \`skip_root_lifecycle: true\`. Every chained-call site already
goes through that constructor — \`add\`, \`remove\`, \`update\`,
\`dedupe\`, \`dlx\`, patch tooling, the \`ensure_installed\`
auto-install before \`run\`/\`test\`, and nested git-prepare installs —
so the parity contract is now enforced uniformly. The argumentless
\`aube install\` path (\`InstallArgs::into_options\`) and the
literal-struct constructions in \`ci.rs\` / \`deploy.rs\` keep
\`false\`, which are the only sites that should still run hooks.

Drop the now-redundant explicit \`skip_root_lifecycle = true\` lines
from add.rs and add bats tests covering \`aube remove\` and
\`aube update\` to lock in the broader contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread crates/aube-scripts/src/lib.rs
Comment thread crates/aube/src/commands/install/mod.rs
jdx and others added 2 commits April 30, 2026 16:51
The previous commit flipped \`with_mode()\` to default
\`skip_root_lifecycle: true\` and broke three \`git_deps.bats\` tests
that exercise git deps with a \`prepare\` script. The nested install
spawned by \`git_prepare.rs\` is special: its "root" IS the cloned
git dep itself, and running that dep's preinstall/postinstall/prepare
is the entire point of git-dep preparation — equivalent to an
argumentless \`aube install\` against the dep's clone directory, not
a chained call from the user's perspective.

Override the chained-call default explicitly in \`git_prepare.rs\`
and update the field doc to call out the exception.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on install-test

Two follow-up fixes from PR review:

- Map Rust's std::env::consts::OS/ARCH (macos/windows, x86_64/aarch64)
  to Node's process.platform/arch vocabulary (darwin/win32, x64/arm64)
  in npm_config_user_agent so dep build scripts that parse the full
  string identify the platform the same way npm/yarn/pnpm do. Add a
  unit test that asserts the emitted tokens match Node's set.

- aube install-test (pnpm-compat alias for install && test) is the
  explicit install entry point, so its install phase needs root
  lifecycle hooks. Override the chained-call default that with_mode()
  sets back to false on this one path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e5b0476. Configure here.

Comment thread crates/aube-scripts/src/lib.rs
Cursor flagged that the user_agent test asserts \`ppc64\` is a valid
arch token but the mapping function fell through to Rust's raw
\`powerpc64\` for that target — the test would fail on a ppc64 build.
Add explicit mappings for the rare arches that disagree between Rust
and Node (powerpc → ppc, powerpc64 → ppc64, loongarch64 → loong64)
and align the test allowlist with the values \`node_arch\` can
actually produce.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 4e2ff38 into main Apr 30, 2026
18 checks passed
@jdx jdx deleted the claude/lifecycle-scripts-port branch April 30, 2026 22:08
jdx added a commit that referenced this pull request Apr 30, 2026
## Summary
- Check off Phase 0 — `@pnpm.e2e/*` fixtures
([#424](#424)) and `add_dist_tag`
helper ([#422](#422)) both landed.
- Record `lifecycleScripts.ts` 8/21 ported via
[#421](#421) with a done/remaining
split.
- Relabel Phase 2 as unblocked (was "depends on add_dist_tag helper").
- Refresh the conventions note now that the `@pnpm.e2e/*` fixtures are
in-tree.

## Test plan
- [ ] doc-only — render check on GitHub

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Documentation-only updates to the pnpm test-import tracking doc; no
runtime or test behavior changes.
> 
> **Overview**
> Updates `test/PNPM_TEST_IMPORT.md` to mark Phase 0 infrastructure work
as complete (mirrored `@pnpm.e2e/*` fixtures and the `add_dist_tag`
helper), and records current Tier 1 progress for `lifecycleScripts.ts`
(8/21 ported with done/remaining notes).
> 
> Renames Phase 2 as *unblocked* now that `add_dist_tag` exists, and
refreshes the translation conventions to prefer using the in-tree
`@pnpm.e2e/*` fixtures when specific package shapes are required.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
270cfc9. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant