Skip to content

feat(config): scope .npmrc to npm-shared keys, route aube settings to config.toml, support dotted map writes#634

Merged
jdx merged 9 commits into
mainfrom
claude/compassionate-jones-84ddf3
May 12, 2026
Merged

feat(config): scope .npmrc to npm-shared keys, route aube settings to config.toml, support dotted map writes#634
jdx merged 9 commits into
mainfrom
claude/compassionate-jones-84ddf3

Conversation

@jdx

@jdx jdx commented May 12, 2026

Copy link
Copy Markdown
Owner

Summary

Three rolled-up changes to aube config set / delete, motivated by Discussion #617:

  1. Inverted .npmrcconfig.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-feat(config): store aube settings outside npmrc #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

  • cargo test — all suites green (478 + 4 + 7 + 77 + 246 + 102 + 129 + 178 + 31 + 49 + 86 + 88 + 45 + 5 + 2)
  • cargo clippy --all-targets -- -D warnings clean
  • cargo fmt --check clean
  • 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. writes to project workspace yaml"
    • "config set --local allowBuilds. appends to existing workspace yaml"
    • "config set --local overrides. writes to project workspace yaml"
    • "config set allowBuilds. at user scope errors with --local hint"
    • "config set overrides. at user scope errors with --local hint"
    • "config set autoInstallPeers.foo errors: scalar settings have no nested namespace"
  • Manual smoke confirmed for every path (project-scope writes to existing yaml + creating package.json#aube, user-scope rejection messages, scalar nested rejection)
  • 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.


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.

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

…tings

`aube config set allowBuilds.@scope/pkg true` fell through to ~/.npmrc,
where aube doesn't read the dotted key and npm warns/errors about an
unknown user config. Surface an explicit error
(ERR_AUBE_CONFIG_NESTED_AUBE_KEY) pointing at the right edit site
(`aube approve-builds` for `allowBuilds`; workspace yaml /
`package.json#aube.<name>` for other maps) and leave .npmrc untouched.

Discussion #617 follow-up.
@greptile-apps

greptile-apps Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR inverts the .npmrcconfig.toml routing for aube config set/delete, adds project-scope dotted writes for aube map settings (allowBuilds.<pkg>, overrides.<pkg>, …), and makes config delete sweep both files symmetrically so the operation succeeds regardless of which file a value was originally written to.

  • Routing overhaul: settings tagged npmShared (registries, proxy/TLS, fetch, loglevel, engineStrict, etc.) continue to land in .npmrc; all other known settings and unknown free-form keys now route to ~/.config/aube/config.toml, with a stale-config.toml sweep on every npm-shared write to prevent precedence shadowing.
  • Dotted map writes: upsert_map_entry / remove_map_entry in aube-manifest generalize the existing approve-builds path to any object-typed setting; booleans are typed, everything else is coerced to YAML/JSON strings to avoid integer parse failures on the read side.
  • New error code: ERR_AUBE_CONFIG_NESTED_AUBE_KEY rejects bare object writes, user-scope dotted writes, and dotted writes against scalar settings; 57 new bats tests cover all branches.

Confidence Score: 5/5

Safe to merge; the write routing and symmetric delete are well-tested and logically consistent.

The core routing logic (npm-shared vs config.toml vs workspace yaml) is correct, the stale-config.toml sweep prevents precedence shadowing, and the integer-to-string coercion fix is sound. The two findings are minor UX inconsistencies in the delete path: a missing error code on the user-scope dotted-delete error, and the success message showing the project directory instead of the specific file modified.

crates/aube/src/commands/config/delete.rs — the user-scope error in try_delete_aube_map_entry is missing the ERR_AUBE_CONFIG_NESTED_AUBE_KEY code that the symmetric set.rs path uses, and the success message shows the CWD rather than the specific file written.

Important Files Changed

Filename Overview
crates/aube/src/commands/config/set.rs Major refactor adding try_set_aube_map_entry, scalar_to_yaml_json, sweep_stale_aube_config, and rejection helpers; routing logic is correct, booleans-only type coercion addresses the prior integer-string concern.
crates/aube/src/commands/config/delete.rs Full rewrite of delete routing now sweeps config.toml and .npmrc symmetrically; dotted-map delete is implemented via try_delete_aube_map_entry, but that function's user-scope error omits the ERR_AUBE_CONFIG_NESTED_AUBE_KEY code unlike the set.rs counterpart.
crates/aube-manifest/src/workspace.rs Adds upsert_map_entry and remove_map_entry; YAML removal correctly uses Value::String key lookup and cleans up empty submaps. remove_map_entry sweeps both workspace yaml and package.json as intended.
crates/aube/src/commands/config/mod.rs Adds is_npm_shared_key with pattern guards for //-prefixed and @scope:registry keys, delegating to setting meta.npm_shared for everything else; logic is clean.
crates/aube-settings/settings.toml Adds npmShared = true to registry, proxy/TLS, fetch, loglevel/color, engineStrict, tag, updateNotifier, and cafile surface settings; pnpm-only knobs like dangerouslyAllowAllBuilds are correctly left unmarked.
test/config.bats Comprehensive new test coverage for all routing branches, dotted-map set/delete round-trips, user-scope rejection, string coercion, and stale config.toml sweeping; addresses prior review gaps.
crates/aube-settings/build.rs Adds npm_shared field to SettingDef and emits it into the generated SettingMeta constant array.
crates/aube-settings/src/meta.rs Adds npm_shared: bool to SettingMeta struct with accurate doc comment.
crates/aube-codes/src/errors.rs New ERR_AUBE_CONFIG_NESTED_AUBE_KEY constant and CodeMeta entry added correctly.
crates/aube/src/commands/config/aube_config.rs is_aube_config_setting correctly drops the typed_accessor_unused guard; adds set_unknown for free-form keys routed to config.toml.

Fix All in Claude Code

Reviews (9): Last reviewed commit: "fix(config): support `delete --local <ma..." | Re-trigger Greptile

Comment thread test/config.bats
Comment thread crates/aube/src/commands/config/set.rs Outdated
… writes to the npm-shared surface

Inverts the `aube config set` routing so `.npmrc` writes only happen for
keys that are part of the multi-tool npm contract — per-host auth/cert
templates (`//host/:_authToken`, `//host/:_password`, …), scoped
registries (`@scope:registry`), and a curated allowlist of npm-standard
scalars (`registry`, `email`, `proxy`/`https-proxy`, `cafile`,
`strict-ssl`, `init-author-*`, …).

Everything else now lands in aube's own config
(`~/.config/aube/config.toml` at user-scope, `<cwd>/.config/aube/config.toml`
at project-scope):

  - known aube/pnpm settings whose scalar value was previously routed there
  - `typedAccessorUnused = true` knobs like `dangerouslyAllowAllBuilds`
  - genuinely unknown free-form keys (new behavior, opt-in via direct
    `.npmrc` edits if a third-party tool needs to read them)

Aube map settings (`allowBuilds`, `overrides`, `packageExtensions`, …)
are rejected up front — they need structural edits in workspace yaml or
`package.json#aube.<name>`. `aube approve-builds <pkg>` is suggested in
the error help for `allowBuilds`.

`aube config delete` now sweeps both aube's config and `.npmrc`, so the
call works regardless of which file the value originated in (including
legacy writes from older aube versions).

Reading is unchanged — aube still reads `.npmrc` for compatibility.

Discussion #617 follow-up (broader take on iki's "are there still
non-npm settings going to `.npmrc`?" question).
@jdx jdx changed the title fix(config): reject aube config set <prefix>.<sub> for aube map settings fix(config): scope .npmrc writes to npm-shared keys, route everything else to config.toml May 12, 2026
Comment thread crates/aube/src/commands/config/set.rs
`aube config set --local <map>.<entry> <value>` now edits one entry of
an aube map setting in place — same path `aube approve-builds` and
install-time auto-deny seeding take:

  aube config set --local allowBuilds.@mongodb-js/zstd true
  aube config set --local overrides.lodash 4.17.21

Writes land in `pnpm-workspace.yaml#<map>.<entry>` when a workspace yaml
exists, otherwise `package.json#<pnpm|aube>.<map>.<entry>` via the
existing `edit_setting_map` / `config_write_target` helpers. Bool /
integer / string values round-trip with their natural scalar type.

User-scope still errors (uniformly, no per-setting special case) because
aube only reads these maps per project today — the error points at
`--local` rather than dropping the value into `~/.config/aube/config.toml`
where nothing would read it.

Addresses greptile feedback on PR #634:
  - drops the hard-coded `meta.name == "allowBuilds"` branch (the
    project-scope path succeeds and the user-scope message is uniform
    across all map settings)
  - new tests cover both the `allowBuilds` and `overrides` paths at
    both scopes, plus a scalar-with-dot rejection (`autoInstallPeers.foo`)

aube-manifest gets a new `pub fn upsert_map_entry` that generalizes the
write-routing the existing allowBuilds path uses.
@jdx jdx changed the title fix(config): scope .npmrc writes to npm-shared keys, route everything else to config.toml feat(config): scope .npmrc to npm-shared keys, route aube settings to config.toml, support dotted map writes May 12, 2026
Comment thread crates/aube/src/commands/config/delete.rs Outdated
jdx added 3 commits May 12, 2026 12:43
…rlap settings

Settings like `engineStrict`, `ignoreScripts`, `color`, `loglevel`,
`httpsProxy`, `maxsockets`, and `fetchRetries` are both:
  - in `is_npm_shared_key` (npm reads them from `.npmrc`)
  - known aube settings in settings.toml

Before this fix, an `aube config set engineStrict false` write would
land in `.npmrc` but leave any prior `config.toml` entry intact. Since
the resolver gives `userAubeConfig` higher precedence than `userNpmrc`
(matches the priority install uses), the stale `config.toml` value
silently shadowed the new `.npmrc` value — `aube config get` would
return the old value and the user's write would appear inert.

`write_npmrc` now sweeps the matching `config.toml` entries (canonical
name + every literal alias) on every `.npmrc` write that touches a
known aube setting. No-op for keys that aren't known aube settings
(auth tokens, scoped registries, npm-only scalars).

Spotted by cursor[bot] on PR #634.
`set` routes overlap keys (`engineStrict`, `strict-ssl`,
`ignore-scripts`, `https-proxy`, …) to `.npmrc` because they're in
`is_npm_shared_key`, even though they're also known aube settings.
`delete` was checking `is_aube_config_key` first and only sweeping
`config.toml` + workspace yaml — never `.npmrc` — so after
`aube config set strict-ssl false`, `aube config delete strict-ssl`
failed with the misleading "stale entry" error and left the .npmrc
line in place.

Restructured delete to mirror set's routing:
  - workspace yaml swept first at project scope for known settings
  - `config.toml` swept (canonical name + aliases + raw key) for
    every key, covering both known settings and free-form entries
  - `.npmrc` swept when the key is npm-shared OR free-form; aube-only
    settings (autoInstallPeers, minimumReleaseAge, …) still get the
    `.npmrc`-protection guarantee preserved by the original
    missing-key error path

Spotted by cursor[bot] on PR #634. Dead `AubeConfigEdit::remove`
removed — `remove_aliases` covers every call site now.
`is_npm_shared_key` used a hardcoded match list of ~50 keys to decide
whether `aube config set` should write to `.npmrc` or `config.toml`.
Move that metadata next to the settings it describes:

- new `npmShared: bool` field on `SettingMeta`, codegened by
  `aube-settings/build.rs` from a `npmShared = true` line on each
  applicable `settings.toml` entry
- tagged the npm-shared settings: registries, httpsProxy, httpProxy,
  noProxy, localAddress, maxsockets, strictSsl, fetchRetries family,
  fetchTimeout, color, loglevel, engineStrict, ignoreScripts,
  nodeOptions, tag, updateNotifier
- `is_npm_shared_key` is now: per-host template (`//*`) ∪ scoped
  registry template (`@*:registry`) ∪ `setting.npm_shared` flag

Also drops the history-as-narration phrasing from docs / set.rs /
delete.rs:
- "still **reads** legacy `.npmrc` entries" → describe current
  read+write behavior, not the transition from a prior version
- "aube no longer modifies" / "stale entry" → "aube doesn't modify" /
  "an entry exists"
- removed "Migration case" comment from the aube-only-key delete test
- linked the configuration.md routing rule to the settings.toml file
  so the source of truth is discoverable

Hardcoded scalars I dropped (email, ca, cafile, _auth, init-author-*,
prefix, audit, fund, …) are not in `settings.toml` so they now fall
through to free-form config.toml writes. Users who specifically want
those in `.npmrc` for npm to see can edit `.npmrc` directly or we add
them to `settings.toml` with `npmShared = true` in a follow-up.

@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 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 6282cb0. Configure here.

Comment thread crates/aube/src/commands/config/mod.rs
`is_npm_shared_key` used `rest.contains(":registry")` which would
match `@scope:registry.foo` and `@scope:registryfoo` as if they were
scoped-registry templates and route them to `.npmrc`. The npm pattern
is exactly `@scope:registry` with nothing after — switch to
`ends_with(":registry")`.

Spotted by cursor[bot] on PR #634.
Comment thread crates/aube/src/commands/config/set.rs Outdated
`scalar_to_yaml_json` had an integer branch that would serialize
pure-digit values as YAML / JSON numbers. That breaks aube map writes
like `overrides.express 4` (a valid "any 4.x" version spec) because
pnpm's (and aube's) typed-`String` deserializer rejects integer
values without a custom visitor. Only booleans get a typed
representation now — `allowBuilds` genuinely stores `true` / `false`,
but every other aube map value (override versions,
package-extension ranges, deprecated version specs, …) must
round-trip as a string.

Spotted by greptile-apps[bot] on PR #634.
Comment thread crates/aube/src/commands/config/delete.rs
…ings

`set --local allowBuilds.<pkg> true` writes to `pnpm-workspace.yaml`
(or `package.json#aube.allowBuilds.<pkg>` if no workspace yaml), but
the dotted form had no symmetric delete path. The fall-through error
said "not set in aube config or .npmrc" while the value sat stuck in
the workspace yaml.

Mirrors the existing `try_set_aube_map_entry` flow:
  - new `aube_manifest::workspace::remove_map_entry` helper that
    sweeps `<map>.<entry>` from both the workspace yaml (when present)
    and `<pnpm|aube>.<map>.<entry>` in `package.json`, scrubbing
    empty `<map>:` containers so deletes don't leave a `{}` stub
  - `try_delete_aube_map_entry` in `delete.rs` short-circuits the
    dotted form before the generic delete logic runs, with the same
    `--local` hint at user scope as `set`

Spotted by greptile-apps[bot] on PR #634.
@jdx jdx merged commit 99c9744 into main May 12, 2026
16 checks passed
@jdx jdx deleted the claude/compassionate-jones-84ddf3 branch May 12, 2026 19:05
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