Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .rules
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ lockfile:
precedence[6]: aube-lock.yaml, pnpm-lock.yaml, bun.lock, yarn.lock, npm-shrinkwrap.json, package-lock.json
frozen_fast_path: install/frozen.rs short-circuits when lockfile + install state match

crates[11]{name,role}:
crates[12]{name,role}:
aube,CLI binary, clap dispatch, all command impls, progress UI, state tracking
aube-codes,stable ERR_AUBE_*/WARN_AUBE_* code constants + EXIT_TABLE, no runtime deps, every other crate depends on it
aube-settings,build.rs codegen from settings.toml, typed accessors, ResolveCtx (cli > env > npmrc > workspace > default)
Expand All @@ -35,6 +35,7 @@ crates[11]{name,role}:
aube-manifest,package.json parser, workspace glob support, extra: BTreeMap for upstream forward-compat
aube-scripts,root lifecycle runner, BuildPolicy allowlist for dep scripts (default deny), side-effects cache
aube-workspace,pnpm-workspace.yaml discovery, workspace: protocol resolution
aube-runtime,Node version resolution/discovery/install (devEngines.runtime + .nvmrc/.node-version, mise delegation, nodejs.org downloads to $XDG_DATA_HOME/aube/nodejs/, AUBE_RUNTIME_DIR test override)

pipeline:
install: cli -> resolver (+ lockfile read) -> registry (fetch tarballs) -> store (CAS import) -> linker (virtual store + symlinks) -> scripts (root only by default)
Expand Down
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ aube-store = { path = "crates/aube-store", version = "1.18.2" }
aube-linker = { path = "crates/aube-linker", version = "1.18.2" }
aube-lockfile = { path = "crates/aube-lockfile", version = "1.18.2" }
aube-manifest = { path = "crates/aube-manifest", version = "1.18.2" }
aube-runtime = { path = "crates/aube-runtime", version = "1.18.2" }
aube-scripts = { path = "crates/aube-scripts", version = "1.18.2" }
aube-workspace = { path = "crates/aube-workspace", version = "1.18.2" }
aube-util = { path = "crates/aube-util", version = "1.18.2" }
Expand Down
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,13 @@ The recommended path is mise:
mise use -g aube
```

mise can also manage the [Node.js versions](https://mise.jdx.dev/lang/node.html)
your projects need. If your projects already pin Node through `package.json`
(`devEngines.runtime`), `.nvmrc`, or `.node-version`, enable mise's
idiomatic version-file support for Node:

```sh
mise settings add idiomatic_version_file_enable_tools node
```
aube switches [Node.js versions](https://aube.jdx.dev/package-manager/node-runtime)
itself: if a project pins Node through `package.json`
(`devEngines.runtime`), `.nvmrc`, or `.node-version`, every script and
binary run through aube gets that version — no shims or shell hooks.
When a pinned version is missing, aube delegates the install to mise if
you have it (one shared Node store on disk) and downloads from
nodejs.org otherwise.

Check that it is on your `PATH`:

Expand Down Expand Up @@ -259,7 +258,7 @@ aube store path
aube store prune
```

Some pnpm commands are intentionally out of scope. Runtime-management commands such as `env`, `runtime`, `setup`, and `self-update` belong in tools like mise. Registry account helpers such as `whoami`, `token`, `owner`, `search`, `pkg`, and `set-script` are compatibility stubs that point you to the npm command instead.
aube also matches pnpm 11's runtime surface: `aube runtime set node <version>` pins a Node version in `devEngines.runtime` and the lockfile, and `aube runtime list` shows what a project resolves to. Some pnpm commands are intentionally out of scope: `setup` and `self-update` belong in tools like mise, and registry account helpers such as `whoami`, `token`, `owner`, `search`, `pkg`, and `set-script` are compatibility stubs that point you to the npm command instead.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## Learn More

Expand Down
15 changes: 15 additions & 0 deletions aube.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,21 @@ cmd run help="Run a script defined in package.json" {
arg "[SCRIPT]" help="Script or local binary name" help_long="Script or local binary name.\n\nOmit on an interactive TTY to pick from `package.json` scripts. If no script matches, aube falls back to `node_modules/.bin/<name>`." required=#false
arg "[ARGS]…" help="Arguments to pass to the script" required=#false double_dash=automatic var=#true
}
cmd runtime subcommand_required=#true help="Manage the project's Node.js runtime (pin, install, inspect)" {
alias rt
cmd list help="Show the resolved runtime and installed versions" {
alias ls
}
cmd set help="Pin a runtime in package.json devEngines.runtime and install it" {
flag "-g --global" help="Install for the user instead of the project (delegates to `mise use -g node@<version>` when mise manages installs)"
flag --on-fail help="`onFail` policy written to devEngines.runtime" default=download {
arg <POLICY>
}
flag --save-exact help="Pin the exact resolved version instead of a caret range"
arg <NAME> help="Runtime name (only `node` is supported)"
arg <VERSION> help="Version request: an exact version, a range (`^24`, `22`), `lts`, `latest`, or an LTS codename (`lts/jod`)"
}
}
cmd sbom help="Generate a Software Bill of Materials (CycloneDX or SPDX)" {
flag "-D --dev" help="Show only devDependencies"
flag --format help="Output format: `cyclonedx` (default) or `spdx`" default=cyclonedx {
Expand Down
52 changes: 52 additions & 0 deletions crates/aube-codes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ pub const ERR_AUBE_REMOVE_PRIOR_INSTALL_DIR: &str = "ERR_AUBE_REMOVE_PRIOR_INSTA
pub const ERR_AUBE_CONFIG_NESTED_AUBE_KEY: &str = "ERR_AUBE_CONFIG_NESTED_AUBE_KEY";
pub const ERR_AUBE_CONFLICTING_BUILD_FLAGS: &str = "ERR_AUBE_CONFLICTING_BUILD_FLAGS";

// ── node runtime (devEngines.runtime / .node-version / .nvmrc) ──────
#[rustfmt::skip] pub const ERR_AUBE_RUNTIME_VERSION_UNSATISFIED: &str = "ERR_AUBE_RUNTIME_VERSION_UNSATISFIED";
#[rustfmt::skip] pub const ERR_AUBE_RUNTIME_NO_MATCHING_VERSION: &str = "ERR_AUBE_RUNTIME_NO_MATCHING_VERSION";
pub const ERR_AUBE_RUNTIME_DOWNLOAD_FAILED: &str = "ERR_AUBE_RUNTIME_DOWNLOAD_FAILED";
#[rustfmt::skip] pub const ERR_AUBE_RUNTIME_CHECKSUM_MISMATCH: &str = "ERR_AUBE_RUNTIME_CHECKSUM_MISMATCH";
pub const ERR_AUBE_RUNTIME_EXTRACT_FAILED: &str = "ERR_AUBE_RUNTIME_EXTRACT_FAILED";
#[rustfmt::skip] pub const ERR_AUBE_RUNTIME_MISE_INSTALL_FAILED: &str = "ERR_AUBE_RUNTIME_MISE_INSTALL_FAILED";
#[rustfmt::skip] pub const ERR_AUBE_RUNTIME_UNSUPPORTED_PLATFORM: &str = "ERR_AUBE_RUNTIME_UNSUPPORTED_PLATFORM";

// ── misc tracing::error! sites (non-fatal but high-severity) ────────
pub const ERR_AUBE_PATCHES_TRACKING_WRITE: &str = "ERR_AUBE_PATCHES_TRACKING_WRITE";
pub const ERR_AUBE_UNSAFE_SHEBANG_INTERPRETER: &str = "ERR_AUBE_UNSAFE_SHEBANG_INTERPRETER";
Expand Down Expand Up @@ -447,6 +456,49 @@ pub const ALL: &[CodeMeta] = &[
description: "`aube add` was passed the same package name in both `--allow-build` and `--deny-build`.",
exit_code: None,
},
// Node runtime (devEngines.runtime / .node-version / .nvmrc)
CodeMeta {
name: ERR_AUBE_RUNTIME_VERSION_UNSATISFIED,
category: category::ENGINE_CLI,
description: "The project requires a Node.js version that isn't available and policy forbids fetching it (`devEngines.runtime` with `onFail: \"error\"`, `runtimeOnFail=error`, or offline mode with nothing installed).",
exit_code: Some(83),
},
CodeMeta {
name: ERR_AUBE_RUNTIME_NO_MATCHING_VERSION,
category: category::ENGINE_CLI,
description: "No Node.js release in the dist index satisfies the requested version (bad range, unknown LTS codename, or no build for this platform).",
exit_code: Some(84),
},
CodeMeta {
name: ERR_AUBE_RUNTIME_DOWNLOAD_FAILED,
category: category::ENGINE_CLI,
description: "Fetching the Node.js dist index, checksum file, or release archive failed after retries.",
exit_code: Some(85),
},
CodeMeta {
name: ERR_AUBE_RUNTIME_CHECKSUM_MISMATCH,
category: category::ENGINE_CLI,
description: "A downloaded Node.js archive's SHA-256 didn't match the lockfile pin or `SHASUMS256.txt`. The archive is discarded and never retried automatically.",
exit_code: Some(86),
},
CodeMeta {
name: ERR_AUBE_RUNTIME_EXTRACT_FAILED,
category: category::ENGINE_CLI,
description: "A Node.js release archive was corrupt or contained unsafe entry paths.",
exit_code: Some(87),
},
CodeMeta {
name: ERR_AUBE_RUNTIME_MISE_INSTALL_FAILED,
category: category::ENGINE_CLI,
description: "Delegating a Node.js install to mise failed — `mise install node@<version>` exited non-zero or the install wasn't discoverable afterwards. Fatal only under `runtimeInstaller=mise`; `auto` falls back to aube's own download.",
exit_code: Some(88),
},
CodeMeta {
name: ERR_AUBE_RUNTIME_UNSUPPORTED_PLATFORM,
category: category::ENGINE_CLI,
description: "No official Node.js build exists for this OS/architecture/libc. Set `nodeDownloadMirrors` to a mirror that carries one, or install Node via mise/system.",
exit_code: Some(89),
},
// Misc / safety
CodeMeta {
name: ERR_AUBE_UNSAFE_INDEX_KEY,
Expand Down
25 changes: 25 additions & 0 deletions crates/aube-codes/src/warnings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ pub const WARN_AUBE_OSV_MIRROR_REFRESH_FAILED: &str = "WARN_AUBE_OSV_MIRROR_REFR
pub const WARN_AUBE_OSV_BLOOM_REFRESH_FAILED: &str = "WARN_AUBE_OSV_BLOOM_REFRESH_FAILED";
pub const WARN_AUBE_SECURITY_SCANNER_FINDING: &str = "WARN_AUBE_SECURITY_SCANNER_FINDING";

// ── node runtime ────────────────────────────────────────────────────
#[rustfmt::skip] pub const WARN_AUBE_RUNTIME_VERSION_MISMATCH: &str = "WARN_AUBE_RUNTIME_VERSION_MISMATCH";
pub const WARN_AUBE_RUNTIME_MISE_FALLBACK: &str = "WARN_AUBE_RUNTIME_MISE_FALLBACK";
#[rustfmt::skip] pub const WARN_AUBE_RUNTIME_PIN_NOT_RECORDED: &str = "WARN_AUBE_RUNTIME_PIN_NOT_RECORDED";

/// Stable category labels that group codes in the generated docs.
/// Public so the docs generator can iterate them deterministically.
pub mod category {
Expand All @@ -115,6 +120,7 @@ pub mod category {
pub const PROGRESS_UI: &str = "Progress UI";
pub const WORKSPACE_RECURSION: &str = "Workspace recursion";
pub const SUPPLY_CHAIN: &str = "Supply chain (add-time)";
pub const NODE_RUNTIME: &str = "Node runtime";
}

/// Registry of every warning code with its category and description.
Expand Down Expand Up @@ -507,4 +513,23 @@ pub const ALL: &[CodeMeta] = &[
description: "User-configured `securityScanner` returned a `warn`-level advisory. Install continues — only `fatal`-level advisories block.",
exit_code: None,
},
// Node runtime
CodeMeta {
name: WARN_AUBE_RUNTIME_VERSION_MISMATCH,
category: category::NODE_RUNTIME,
description: "The active Node.js doesn't satisfy the project's runtime requirement and `onFail: \"warn\"` keeps execution on the unsatisfying version.",
exit_code: None,
},
CodeMeta {
name: WARN_AUBE_RUNTIME_MISE_FALLBACK,
category: category::NODE_RUNTIME,
description: "`runtimeInstaller=auto` tried delegating a Node.js install to mise but mise failed; aube fell back to its own nodejs.org download.",
exit_code: None,
},
CodeMeta {
name: WARN_AUBE_RUNTIME_PIN_NOT_RECORDED,
category: category::NODE_RUNTIME,
description: "`devEngines.runtime` resolved to an exact Node.js version but the project's lockfile format (npm/yarn/bun) has no runtime entry shape, so the pin wasn't recorded. Subsequent runs re-resolve the range.",
exit_code: None,
},
];
54 changes: 49 additions & 5 deletions crates/aube-lockfile/src/drift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,16 @@ impl LockfileGraph {
workspace_catalogs,
);
let locked = resolve_catalog_refs_in_overrides(&self.overrides, workspace_catalogs);
overrides_drift_reason(&locked, &effective).or_else(|| {
let mut effective_ignored = manifest.pnpm_ignored_optional_dependencies();
effective_ignored.extend(workspace_ignored_optional.iter().cloned());
ignored_optional_drift_reason(&self.ignored_optional_dependencies, &effective_ignored)
})
overrides_drift_reason(&locked, &effective)
.or_else(|| {
let mut effective_ignored = manifest.pnpm_ignored_optional_dependencies();
effective_ignored.extend(workspace_ignored_optional.iter().cloned());
ignored_optional_drift_reason(
&self.ignored_optional_dependencies,
&effective_ignored,
)
})
.or_else(|| runtime_drift_reason(&self.runtimes, manifest))
}

/// Compare this lockfile's catalog snapshot against the current
Expand Down Expand Up @@ -661,6 +666,44 @@ fn ignored_optional_drift_reason(
None
}

/// Compare recorded runtime pins against the manifest's
/// `devEngines.runtime` declarations.
///
/// Only an *existing* pin can drift here: the requested range changed,
/// or the manifest dropped the devEngines entry the pin came from. The
/// inverse case — devEngines present but no pin recorded yet — is
/// deliberately not drift, because formats that can't record runtime
/// pins (npm/yarn/bun) would read as permanently stale. The install
/// driver adds the missing pin on formats that support it.
fn runtime_drift_reason(
runtimes: &BTreeMap<String, crate::RuntimePin>,
manifest: &aube_manifest::PackageJson,
) -> Option<String> {
for (name, pin) in runtimes {
let declared = manifest
.dev_engines
.as_ref()
.and_then(|d| d.runtime.iter().find(|r| r.name == *name))
.and_then(|r| r.version.as_deref());
match declared {
None => {
return Some(format!(
"devEngines.runtime: manifest no longer pins {name} (lockfile records {})",
pin.version
));
}
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
Some(range) if range != pin.specifier => {
return Some(format!(
"devEngines.runtime: {name} changed ({} → {range})",
pin.specifier
));
}
Some(_) => {}
}
}
None
}

/// Result of comparing a lockfile against a manifest.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DriftStatus {
Expand Down Expand Up @@ -696,6 +739,7 @@ mod drift_tests {
update_config: None,
scripts: BTreeMap::new(),
engines: BTreeMap::new(),
dev_engines: None,
workspaces: None,
bundled_dependencies: None,
extra: BTreeMap::new(),
Expand Down
Loading
Loading