feat(cli): add --lockfile-dir / lockfileDir setting#431
Conversation
Greptile SummaryAdds pnpm-compatible
Confidence Score: 3/5Not safe to merge as-is: the foreign-importer guard has a logic hole that allows silent lockfile corruption, and the docs describe a multi-project sharing use-case the implementation actively rejects. Two P1 findings: a guard bypass that can corrupt a legitimate project's lockfile entry, and documentation that directly contradicts runtime behavior. Both affect the correctness and safety of the core feature being added. crates/aube/src/commands/install/mod.rs (guard filter) and crates/aube-settings/settings.toml + docs/settings/index.md (doc contradiction) Important Files Changed
Reviews (3): Last reviewed commit: "fix(cli): address cursor review on --loc..." | Re-trigger Greptile |
Benchmark changesVersions:
Public ratios: warm installs vs Bun 4x -> 3x; warm installs vs pnpm 5x -> 7x.
bd65f92 vs 63fcb9a | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex. |
Adds the misc.ts:112 port to test/pnpm_install_misc.bats now that #431 implements `aube install --lockfile-dir`. is-positive substituted with is-odd; pnpm's `install <pkg> --lockfile-dir ../` becomes aube's `install --lockfile-dir ..` with the dep already declared in package.json (aube's flag is install-only and `aube install` doesn't take a package argument). Brings the misc.ts port to 11/37; moves `--lockfile-dir` out of the documented-divergences list in the TODO. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0bc1d4d to
a3addcc
Compare
Adds the misc.ts:112 port to test/pnpm_install_misc.bats now that #431 implements `aube install --lockfile-dir`. is-positive substituted with is-odd; pnpm's `install <pkg> --lockfile-dir ../` becomes aube's `install --lockfile-dir ..` with the dep already declared in package.json (aube's flag is install-only and `aube install` doesn't take a package argument). Brings the misc.ts port to 11/37; moves `--lockfile-dir` out of the documented-divergences list in the TODO. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the misc.ts:112 port to test/pnpm_install_misc.bats now that #431 implements `aube install --lockfile-dir`. is-positive substituted with is-odd; pnpm's `install <pkg> --lockfile-dir ../` becomes aube's `install --lockfile-dir ..` with the dep already declared in package.json (aube's flag is install-only and `aube install` doesn't take a package argument). Brings the misc.ts port to 11/37; moves `--lockfile-dir` out of the documented-divergences list in the TODO. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a336678 to
7fb0e20
Compare
Adds the misc.ts:112 port to test/pnpm_install_misc.bats now that #431 implements `aube install --lockfile-dir`. is-positive substituted with is-odd; pnpm's `install <pkg> --lockfile-dir ../` becomes aube's `install --lockfile-dir ..` with the dep already declared in package.json (aube's flag is install-only and `aube install` doesn't take a package argument). Brings the misc.ts port to 11/37; moves `--lockfile-dir` out of the documented-divergences list in the TODO. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7fb0e20 to
3dc7727
Compare
Mirrors pnpm's --lockfile-dir: relocates aube-lock.yaml to a different directory than the project root. The project becomes an importer keyed by its relative path from the lockfile directory, so several unrelated projects can share one committed lockfile without declaring a pnpm-workspace.yaml. Implementation: a thin importer-key shim around the existing aube-lockfile read/write API. The install pipeline keeps treating the project as the `.` root importer in-memory; the relative key only appears in the on-disk lockfile, transparent to every downstream consumer (resolver, linker, scripts runner). Scope: install path's main read/write sites. Workspace per-project lockfile writes (sharedWorkspaceLockfile=false) and merge-git-branch flows continue to use the project root, since combining --lockfile-dir with workspace-shared lockfiles requires a separate design decision. Resolves the --lockfile-dir divergence noted in the misc.ts port TODO ([pnpm/test/install/misc.ts:112]). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Auto-create the lockfile dir when missing (pnpm parity), instead of aborting in canonicalize with ENOENT. - Normalize the importer key to forward slashes so Windows-written lockfiles stay portable to Unix CI. - Hard-error in `write_lockfile_dir_remapped` when the in-memory graph is missing the `.` importer, instead of silently writing a lockfile without the project's entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the misc.ts:112 port to test/pnpm_install_misc.bats now that #431 implements `aube install --lockfile-dir`. is-positive substituted with is-odd; pnpm's `install <pkg> --lockfile-dir ../` becomes aube's `install --lockfile-dir ..` with the dep already declared in package.json (aube's flag is install-only and `aube install` doesn't take a package argument). Brings the misc.ts port to 11/37; moves `--lockfile-dir` out of the documented-divergences list in the TODO. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Guard against multi-project shared lockfiles outside a workspace. Reading another project's lockfile from the same `--lockfile-dir` and re-resolving against it would orphan-strip the other project's package entries (the resolver only sees the current importer's deps). Fail loudly upfront, before any FrozenMode branch, so the guard fires regardless of `--no-frozen-lockfile` / `--lockfile-only` shortcuts. Workspace installs (legitimate multi-importer case) are unaffected because the guard is gated on a non-`.` importer key, which only happens when `--lockfile-dir` actually points outside the project root. - Canonicalize `cwd` before comparing to the canonicalized lockfile dir, so symlinks or `./project/..`-style inputs no longer make the equality check fail or produce a wrong `pathdiff` importer key. - Drop the misleading "several unrelated projects can share one committed lockfile" claim from the inline comment; that case requires a workspace. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
42141c2 to
bd65f92
Compare
| .importers | ||
| .keys() | ||
| .map(String::as_str) | ||
| .filter(|k| *k != "." && *k != importer_key) |
There was a problem hiding this comment.
"." is incorrectly excluded from foreign-importer detection
When importer_key != ".", a "." entry already in the lockfile means the lockfile directory is itself a project root (someone ran aube install there without --lockfile-dir). That entry is foreign and should be rejected, but the current filter silently exempts it. The guard then passes, parse_lockfile_dir_remapped finds no importer_key match (since the disk has "."), and write_lockfile_dir_remapped removes the old "." entry and replaces it with the subdirectory project — silently orphan-stripping exactly the project the guard is designed to protect.
| .filter(|k| *k != "." && *k != importer_key) | |
| .filter(|k| *k != importer_key) |
| (e.g. `project` if the lockfile is one directory above), so several | ||
| unrelated projects can share a single committed lockfile without | ||
| declaring a `pnpm-workspace.yaml`. |
There was a problem hiding this comment.
Docs claim multi-project sharing is supported; implementation rejects it
The sentence "so several unrelated projects can share a single committed lockfile without declaring a pnpm-workspace.yaml" describes exactly the configuration that guard_against_foreign_importers hard-errors on. A user who sets lockfileDir based on this description will hit the "lockfile already records importers from other projects" error the first time a second project tries to install. The same incorrect text is mirrored verbatim into docs/settings/index.md.
This sentence should be replaced with a note that lockfileDir is for single-project relocation only, and multi-project shared lockfiles require a pnpm-workspace.yaml workspace.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bd65f92. Configure here.
| .keys() | ||
| .map(String::as_str) | ||
| .filter(|k| *k != "." && *k != importer_key) | ||
| .collect(); |
There was a problem hiding this comment.
Guard allows "." importer through, missing foreign project
Medium Severity
The guard_against_foreign_importers filter excludes "." from the foreign-importer check (*k != "."). When lockfile_dir already contains a lockfile from a standalone project (which uses "." as its importer key), the guard silently passes. Both projects then alternate overwriting each other's lockfile data — the exact data-loss scenario the guard exists to prevent. Filtering "." serves no purpose here: workspace installs skip the guard entirely (lockfile_importer_key == "."), and --lockfile-dir installs always write non-"." keys.
Reviewed by Cursor Bugbot for commit bd65f92. Configure here.
…cs (#442) Follow-up to [#431](#431) addressing two greptile P1s left over after merge. ## Summary - **Foreign-importer guard previously exempted `"."`.** When the current project's importer key is non-`.`, a `"."` entry on disk is itself an unrelated project that ran `aube install` in the lockfile dir directly. Dropping it on write is the same data-loss class the guard exists to prevent. Drop the `"."` exemption from the filter; the caller already gates on `importer_key != "."` so the default install path stays untouched. - **Replace stale `lockfileDir` docs.** [crates/aube-settings/settings.toml](crates/aube-settings/settings.toml) and the generated [docs/settings/index.md](docs/settings/index.md) still claimed "several unrelated projects can share a single committed lockfile", which is the exact configuration the guard rejects. Replaced with the correct scope: single-project relocation only, multi-project sharing requires a workspace. ## Test plan - [x] New: `refuses to clobber a parent dir that is itself a project` in [test/lockfile_dir.bats](test/lockfile_dir.bats) — covers the `.` foreign-importer case the previous filter missed. - [x] `mise run test:bats test/lockfile_dir.bats` (6/6). - [x] `mise run test:bats test/install.bats test/workspace.bats` (79/79) — no regressions; workspace installs with multiple importers stay healthy because the guard is gated on a non-`.` importer key. - [x] `cargo clippy -p aube --all-targets -- -D warnings` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes `aube install --lockfile-dir` to fail in an additional scenario (when the target lockfile already contains a `.` importer), which could break previously-working but unsafe setups. Scope is small and covered by a new bats test, but it affects install-time behavior and lockfile safety checks. > > **Overview** > Tightens the `--lockfile-dir` foreign-importer guard so a lockfile containing a `.` importer is treated as *foreign* when installing from a subproject, preventing silent loss of the parent project’s lockfile entries. > > Updates `lockfileDir` documentation (settings registry + generated docs) to remove the claim that unrelated projects can share a lockfile, clarifying that multi-project sharing requires a `pnpm-workspace.yaml` workspace. Adds a bats regression test to ensure installs refuse to target a parent directory that is itself a project. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ffa91ac. 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>


Summary
Mirrors pnpm's
--lockfile-dir: relocatesaube-lock.yamlto a different directory than the project root. The project becomes an importer keyed by its relative path from the lockfile directory.Resolves the
--lockfile-dirdivergence noted in the misc.ts port TODO (pnpm/test/install/misc.ts:112).Implementation
A thin importer-key shim around the existing
aube-lockfileread/write API. The install pipeline keeps treating the project as the.root importer in-memory; the relative key only appears in the on-disk lockfile, transparent to every downstream consumer (resolver, linker, scripts runner).Three local helpers in
install/mod.rs:parse_lockfile_dir_remapped— reads fromlockfile_dir, remaps the project's relative-path importer back to.for the in-memory graph.parse_lockfile_dir_remapped_with_kind— same, plus preserves the detected kind.write_lockfile_dir_remapped— writes tolockfile_dir, remaps.to the relative-path key on the way out. Hard-errors if the in-memory graph is missing the.importer (defensive guard against silent data loss in future code paths).When
lockfile_dir == project_root, both shims short-circuit (no clone, no map mutation), so the default install path is unchanged.Path-resolution (pnpm parity):
create_dir_allbeforecanonicalize, so--lockfile-dir <missing>materializes the directory instead of aborting with ENOENT.cwdandlockfile_dircanonicalized before equality /pathdiff, so symlinks and./project/..-style inputs don't break key derivation.Scope
Single-project lockfile relocation. Multi-project shared lockfiles outside a
pnpm-workspace.yamlworkspace are explicitly out of scope: pointing two unrelated projects at the same--lockfile-dirwould have the second install orphan-strip the first project's package entries when re-resolving. An upfront read-side guard (guard_against_foreign_importers) detects this configuration and fails loudly with a message pointing the user at workspaces or per-project lockfile dirs. Workspace installs (legitimate multi-importer cases) are unaffected because the guard is gated on a non-.importer key, which only happens when--lockfile-dirpoints outside the project root.Wired through the install path's main read/write sites (8 call sites). Two boundaries deferred to follow-up PRs:
sharedWorkspaceLockfile=falseper-project lockfile writes (line ~4118) still targetproject_root— combining--lockfile-dirwith workspace-shared lockfiles needs a separate design decision.merge_git_branch_lockfilesflow likewise targetsproject_root.Settings
--lockfile-dir <PATH>AUBE_LOCKFILE_DIR,npm_config_lockfile_dir,NPM_CONFIG_LOCKFILE_DIR.npmrclockfile-dir,lockfileDirpnpm-workspace.yamllockfileDirRelative paths resolve against the project root (pnpm convention).
Test plan
--lockfile-dirfrom the documented-divergences list).mise run test:bats test/install.bats(61/61) — no regressions.mise run test:bats test/lockfile_settings.bats test/workspace.bats— no regressions.cargo test --workspace— all green (after sorting--lockfile-diralphabetically before--lockfile-onlyto satisfycli_ordering_tests::test_cli_ordering).cargo clippy -p aube --all-targets -- -D warningsclean.🤖 Generated with Claude Code
Note
Medium Risk
Changes where
aube installreads/writes lockfiles and how importer keys are mapped, which could affect install reproducibility and overwrite behavior if path handling is wrong. Guardrails and tests reduce risk, but this touches core install/lockfile I/O paths.Overview
Adds
--lockfile-dir <PATH>(andlockfileDirsetting) to relocateaube-lock.yamlto an arbitrary directory, mirroring pnpm.aube installnow resolves/canonicalizes the target directory, creates it if missing, and remaps lockfileimporterskeys between.in-memory and the project’s relative-path key on disk; lockfile read/write/detect call sites are updated to use the relocated directory.Includes a fail-fast guard that errors if the relocated lockfile already contains importers from other projects (multi-project shared lockfiles outside a workspace remain unsupported), plus new Bats coverage and CLI/settings documentation updates.
Reviewed by Cursor Bugbot for commit bd65f92. Bugbot is set up for automated code reviews on this repo. Configure here.