Skip to content

feat(config): store aube settings outside npmrc#517

Merged
jdx merged 6 commits into
mainfrom
codex/config-xdg-npmrc-symlink
May 5, 2026
Merged

feat(config): store aube settings outside npmrc#517
jdx merged 6 commits into
mainfrom
codex/config-xdg-npmrc-symlink

Conversation

@jdx

@jdx jdx commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a user-scoped aube config file at ~/.config/aube/config.toml for known aube-owned settings
  • make aube config set/get/list/delete read and write that TOML config while keeping unknown registry/auth keys in .npmrc
  • thread the new config source through settings resolution and preserve symlinked .npmrc files for remaining npmrc writes

Context

Addresses Discussions #513 and #516: aube-specific settings no longer need to be written into npm's user config, and .npmrc writes follow the symlink target instead of replacing the symlink.

Validation

  • cargo test
  • cargo clippy --all-targets -- -D warnings
  • cargo fmt --check && cargo check
  • isolated CLI smoke: aube config set minimum-release-age 2880 writes config.toml, aube config get minimum-release-age returns 2880, and stale minimumReleaseAge is removed from user .npmrc while registry config remains

This PR was generated by Codex.


Note

Medium Risk
Changes configuration read/write paths and precedence by introducing ~/.config/aube/config.toml, which can affect effective settings across many commands if ordering or key classification is wrong. Also adjusts .npmrc writes to follow symlink targets, touching auth-token storage paths.

Overview
Adds a new user-scoped TOML config (~/.config/aube/config.toml, XDG-aware) for known aube-owned settings, keeping registry/auth and unknown keys in .npmrc.

Updates aube config set/get/list/delete and the broader settings resolver to treat aubeConfig as an additional source (between .npmrc and workspace YAML), threads it through commands that build a ResolveCtx, and cleans up stale user .npmrc aliases when migrating a known setting to TOML.

Hardens .npmrc persistence by writing atomically to the symlink target (preserving symlinked dotfile setups), and refreshes generated CLI/docs/tests to reflect the new locations and precedence.

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

@greptile-apps

greptile-apps Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces ~/.config/aube/config.toml as a user-scoped store for known aube-owned settings, keeping registry/auth keys in .npmrc, and hardens .npmrc persistence to follow symlink chains via canonicalize rather than a single read_link hop.

  • aube config set/get/list/delete are updated to route known settings to the new TOML store and clean up stale .npmrc aliases on write; aube_config is wired into every ResolveCtx construction site across install, run, deploy, fetch, update, and rebuild.
  • ResolveCtx gains an aube_config field; the codegen in build.rs adds aubeConfig as a valid source with runtime precedence cli > env > npmrc > aubeConfig > workspaceYaml.
  • atomic_write already calls create_dir_all, so first-time writes to ~/.config/aube/ work without extra setup.

Confidence Score: 5/5

Safe to merge; the config split, symlink fix, and precedence wiring are all consistent with runtime behaviour and covered by new unit and integration tests.

The effective read/write precedence (user .npmrc beats aube config, project .npmrc beats both) is implemented correctly and confirmed by the new bats test. All error paths that could leave stale state now propagate with ? or emit tracing::warn!. The only finding is a doc-string walk-order description that is backwards relative to the actual insertion order, which does not affect runtime correctness.

The user-facing doc strings in mod.rs, aube.usage.kdl, and docs/cli/ describe the merged walk order as ~/.npmrc then user aube config, but the actual insertion order is reversed; worth correcting so users are not misled about which store wins on conflict.

Important Files Changed

Filename Overview
crates/aube/src/commands/config/aube_config.rs New module: TOML-backed aube config editor with load/set/remove/save; handles missing file gracefully and warns on parse errors via load_user_entries; atomic writes via aube_util.
crates/aube/src/commands/config/mod.rs Threads aube_config into read_merged and user-location lookups; effective precedence (user_npmrc > aube_config) is correct and matches runtime, but doc strings describe the walk order backwards relative to actual insertion order.
crates/aube/src/commands/config/set.rs Routes known aube settings to config.toml and cleans stale npmrc aliases; save and removal errors are properly propagated.
crates/aube/src/commands/config/delete.rs Deletes from both config.toml and user .npmrc when applicable; success/error messages now correctly report the actual modified paths.
crates/aube/src/commands/npmrc.rs save() now follows the full symlink chain via canonicalize before the atomic write, preserving symlinked dotfile setups; multi-hop test added.
crates/aube-settings/build.rs Adds aubeConfig as a valid source in the codegen precedence table, mapping it to ctx.aube_config with the same accessor call shape as npmrc.
crates/aube-settings/src/values.rs Adds aube_config field to ResolveCtx; default is empty slice; new unit test verifies aube_config is read between npmrc and workspaceYaml.
test/config.bats Integration tests updated to verify config.toml write path; new test explicitly asserts user .npmrc wins over config.toml for get/list.

Fix All in Claude Code

Reviews (3): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile

Comment thread crates/aube/src/commands/config/set.rs
Comment thread crates/aube/src/commands/config/delete.rs
Comment thread crates/aube/src/commands/config/aube_config.rs Outdated
Comment thread crates/aube/src/commands/npmrc.rs

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

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 e1613c3. Configure here.

Comment thread crates/aube/src/commands/npm_fallback.rs Outdated
Comment thread crates/aube/src/commands/config/delete.rs Outdated
Comment thread crates/aube/src/commands/config/aube_config.rs
@jdx jdx merged commit 2120715 into main May 5, 2026
16 checks passed
@jdx jdx deleted the codex/config-xdg-npmrc-symlink branch May 5, 2026 18:03
jdx added a commit that referenced this pull request May 11, 2026
…605)

## Summary

- `aube config set/delete` wrote into a sibling temp file and renamed it
over the path, which replaced a symlinked `~/.config/aube/config.toml`
with a regular file
- Resolve the symlink target before calling `atomic_write`, mirroring
the fix [#517](https://github.com/endevco/aube/pull/517) shipped for
`~/.npmrc`
- Expose `symlink_target_or_self` as `pub(crate)` in `npmrc.rs` so
`aube_config.rs` can reuse it without duplicating

Closes [#603](https://github.com/endevco/aube/discussions/603).

## Test plan

- [x] `cargo test -p aube commands::config::aube_config` — new
`save_preserves_symlink` test passes
- [x] `cargo test -p aube
commands::npmrc::tests::save_preserves_symlink` — existing npmrc symlink
test still passes
- [x] `cargo clippy -p aube --all-targets -- -D warnings`
- [x] `cargo fmt --check`

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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: narrowly changes the write path resolution for `aube config`
saves and adds a Unix-only regression test; behavior for non-symlink
paths should remain unchanged.
> 
> **Overview**
> `aube config` writes now preserve a symlinked
`~/.config/aube/config.toml` by resolving `path` to its symlink target
before calling `atomic_write`, avoiding replacing the symlink with a
regular file during rename-based atomic saves.
> 
> The existing `npmrc` helper `symlink_target_or_self` is made
`pub(crate)` for reuse, and a Unix-only test is added to assert symlink
preservation and correct content updates.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
99215e3. 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>
jdx added a commit that referenced this pull request May 11, 2026
A user upgrading from a pre-#517 aube has aube-owned keys still living
in `~/.npmrc` (where old aube used to write them). `aube config delete
<key>` would then fail with a bare "not set in config.toml", with no
hint about the active value in `~/.npmrc`.

aube still doesn't modify `.npmrc` for aube-known keys — it's shared
with npm/pnpm/yarn, which is the whole point of #601. But the error now
surfaces the stale entry's location and tells the user to edit it
manually (or override it from `config.toml` via `aube config set`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jdx added a commit that referenced this pull request May 12, 2026
… config.toml, support dotted map writes (#634)

## Summary

Three rolled-up changes to `aube config set` / `delete`, motivated by
[Discussion
#617](https://github.com/endevco/aube/discussions/617#discussioncomment-16885128):

1. **Inverted `.npmrc` ↔ `config.toml` routing.** Writes land in
`.npmrc` only for the npm-shared surface — per-host auth/cert templates,
scoped registries, and a curated allowlist of npm-standard scalars.
Everything else (known aube settings, pnpm-only knobs like
`dangerouslyAllowAllBuilds`, unknown free-form keys) goes to aube's own
config so `aube config set` stops polluting `.npmrc` with keys other
npm-family tools warn about.

2. **Project-scope dotted writes for aube map settings.** `aube config
set --local allowBuilds.@scope/pkg true` and `aube config set --local
overrides.lodash 4.17.21` now edit one entry of the map in place,
routing through `pnpm-workspace.yaml` (or
`package.json#<pnpm|aube>.<map>` when no workspace yaml exists) via the
same path `aube approve-builds` takes. User-scope dotted writes for
these maps still error — uniformly, no per-setting special case — with a
`--local` pointer, because aube only reads these maps per project today.

3. **Symmetric deletes.** `aube config delete` sweeps both aube's config
and `.npmrc`, so the call works regardless of which file the value
originally landed in (including legacy writes from older aube versions).

## What lands where

**`.npmrc`** (multi-tool contract — npm, pnpm, yarn all read these):

- Per-host auth/cert templates: `//host/:_authToken`,
`//host/:_password`, `//host/:_auth`, `//host/:certfile`,
`//host/:keyfile`, …
- Scoped registries: `@scope:registry`
- Curated allowlist of npm-standard scalars: `registry`, `email`,
`proxy`/`http-proxy`/`https-proxy`, `cafile`/`ca`/`cert`/`key`,
`strict-ssl`, `maxsockets`, `fetch-retries`/`fetch-timeout`,
`init-author-*`/`init-license`/`init-module`/`init-version`,
`userconfig`/`globalconfig`/`prefix`, `tag`/`scope`/`access`,
`engine-strict`, `package-lock`, `ignore-scripts`, `node-options`,
`audit`/`audit-level`, `loglevel`/`color`/`progress`, `fund`,
`update-notifier`, `_auth`/`_authToken`/`_password`/`username`,
`always-auth`, `before`

**`~/.config/aube/config.toml` (user) / `<cwd>/.config/aube/config.toml`
(project)** — aube-only:

- All known aube/pnpm-only scalar settings (`autoInstallPeers`,
`minimumReleaseAge`, `nodeLinker`, `packageImportMethod`, …)
- `typedAccessorUnused = true` knobs that previously slipped through to
`.npmrc`: `dangerouslyAllowAllBuilds`, `pnpmfilePath`, `globalPnpmfile`,
`ignoredOptionalDependencies`
- Genuinely unknown free-form keys

**`pnpm-workspace.yaml` / `package.json#<pnpm|aube>.<map>`** (via
`--local`):

- Dotted writes for aube map settings: `allowBuilds.<pkg>`,
`overrides.<pkg>`, `packageExtensions.<pkg>`,
`allowedDeprecatedVersions.<pkg>`, `supportedArchitectures.<key>`

**Rejected** (`ERR_AUBE_CONFIG_NESTED_AUBE_KEY`):

- Bare object writes (`allowBuilds <json>`) — these need structural
edits, not a single scalar
- User-scope dotted writes for aube maps — points at `--local`
- Dotted writes against a *scalar* setting (`autoInstallPeers.foo`) — no
nested namespace

## Examples

```
$ aube config set registry https://r.example.com/
set registry=https://r.example.com/ (~/.npmrc)

$ aube config set dangerouslyAllowAllBuilds true
set dangerouslyAllowAllBuilds=true (~/.config/aube/config.toml)

$ aube config set some-experimental-flag value
set some-experimental-flag=value (~/.config/aube/config.toml)

$ aube config set --local allowBuilds.@mongodb-js/zstd true
set allowBuilds.@mongodb-js/zstd=true (./pnpm-workspace.yaml)

$ aube config set --local overrides.lodash 4.17.21
set overrides.lodash=4.17.21 (./pnpm-workspace.yaml)

$ aube config set allowBuilds.@mongodb-js/zstd true   # user scope
ERR_AUBE_CONFIG_NESTED_AUBE_KEY

  × `allowBuilds.@mongodb-js/zstd` only applies at project scope:
  │ `allowBuilds` is read from `pnpm-workspace.yaml` /
  │ `package.json#<pnpm|aube>.allowBuilds`, not user-scope aube config.
  help: use `aube config set --local allowBuilds.@mongodb-js/zstd true`
        to edit `pnpm-workspace.yaml#allowBuilds.@mongodb-js/zstd` …

$ aube config set autoInstallPeers.foo true
ERR_AUBE_CONFIG_NESTED_AUBE_KEY

  × `autoInstallPeers.foo` is not a writable config key:
  │ `autoInstallPeers` is a scalar aube setting and has no nested namespace.
  help: `autoInstallPeers` is type `bool` — set it directly with
        `aube config set autoInstallPeers <value>`.
```

## Reads + deletes

- **Reads are unchanged.** Aube still reads `.npmrc` for compatibility
with existing projects, so legacy writes from older aube versions
continue to take effect.
- **`aube config delete` now sweeps both files** — the call works
regardless of which file the value originally landed in, so users
upgrading from a pre-#517 aube don't have to know where their settings
live.

## New error code

`ERR_AUBE_CONFIG_NESTED_AUBE_KEY` (Engine / CLI category, generic exit
`1`).

## New library helper

`aube_manifest::workspace::upsert_map_entry(project_dir, map_name,
entry_key, yaml_value, json_value)` generalizes the write-routing the
existing `add_to_allow_builds` path uses. Takes both yaml + json values
so the caller controls scalar shape (bool / int / string) without the
helper having to guess.

## Test plan

- [x] `cargo test` — all suites green (478 + 4 + 7 + 77 + 246 + 102 +
129 + 178 + 31 + 49 + 86 + 88 + 45 + 5 + 2)
- [x] `cargo clippy --all-targets -- -D warnings` clean
- [x] `cargo fmt --check` clean
- [x] `mise run test:bats test/config.bats` — 57/57 passing, including:
  - "config set routes unknown keys to user config.toml, not .npmrc"
  - "config get reads free-form unknown keys back from config.toml"
  - "config delete removes a free-form unknown key from config.toml"
- "config set routes pnpm-only knobs (dangerouslyAllowAllBuilds) to
config.toml"
  - "config set rejects bare aube map settings"
  - "config set keeps npm-shared keys in .npmrc"
- "config set --local allowBuilds.<pkg> writes to project workspace
yaml"
- "config set --local allowBuilds.<pkg> appends to existing workspace
yaml"
- "config set --local overrides.<pkg> writes to project workspace yaml"
- "config set allowBuilds.<pkg> at user scope errors with --local hint"
  - "config set overrides.<pkg> at user scope errors with --local hint"
- "config set autoInstallPeers.foo errors: scalar settings have no
nested namespace"
- [x] Manual smoke confirmed for every path (project-scope writes to
existing yaml + creating package.json#aube, user-scope rejection
messages, scalar nested rejection)
- [x] `mise run render` regenerated CLI docs (`docs/cli/commands.json`,
`docs/cli/config/{set,delete}.md`) + `docs/error-codes.data.json`

*This PR was generated by Claude.*

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes config write/delete routing between `.npmrc`, `config.toml`,
and workspace files, which can alter where settings persist and how
precedence/shadowing behaves. Adds new dotted map edit/delete behavior
touching `pnpm-workspace.yaml`/`package.json`, so misclassification of
keys or edit logic could lead to surprising config results.
> 
> **Overview**
> Reworks `aube config set`/`delete` to **only write npm-shared keys to
`.npmrc`** (including new per-setting `npmShared` metadata plus
auth/scoped-registry patterns) and to route **aube-only/pnpm-only and
unknown free-form keys into `~/.config/aube/config.toml`**.
> 
> Adds **project-scope dotted map writes/deletes** (e.g.
`allowBuilds.<pkg>`, `overrides.<pkg>`) that upsert/remove a single
entry in `pnpm-workspace.yaml` or fall back to
`package.json#aube.<map>`, and introduces
`ERR_AUBE_CONFIG_NESTED_AUBE_KEY` for invalid nested writes (user-scope
map edits, bare object writes, or scalar keys with a dot).
> 
> Updates docs/usage text and expands Bats coverage to assert the new
routing, stale-config sweeping for npm-shared keys, and round-trip
behavior for unknown keys and dotted map entries.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a3415fe. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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