aube_settings/meta.rs
1//! Static metadata for every setting in `settings.toml`.
2//!
3//! The `SETTINGS` slice is emitted at build time by `build.rs` from
4//! the crate-local `settings.toml`. Each entry is a `'static`
5//! borrow so there's zero runtime allocation and the compiler
6//! verifies the schema shape at compile time.
7//!
8//! Use cases:
9//! - Future `aube settings` subcommand to render a CLI version of
10//! pnpm's settings reference page
11//! - The docs generator that reads this and emits
12//! `docs/settings/*.md`
13
14/// Metadata for one setting. Every field is `'static` because the
15/// containing `SETTINGS` slice is a `const` built from string
16/// literals at compile time.
17#[derive(Debug, Clone, Copy)]
18#[allow(dead_code)] // Fields are consumed by tests and future generators.
19pub struct SettingMeta {
20 /// pnpm-style canonical name (e.g. `preferFrozenLockfile`, `registries`)
21 pub name: &'static str,
22 /// One-line summary
23 pub description: &'static str,
24 /// TOML-ish type spec (e.g. `"bool"`, `"list<string>"`, `"\"highest\" | \"time-based\""`)
25 pub type_: &'static str,
26 /// Default value rendered as a string (e.g. `"true"`, `"undefined"`)
27 pub default: &'static str,
28 /// Longer markdown docs body
29 pub docs: &'static str,
30 /// CLI flags that set this setting
31 pub cli_flags: &'static [&'static str],
32 /// Environment variables that set this setting
33 pub env_vars: &'static [&'static str],
34 /// `.npmrc` keys that set this setting (aliases listed together;
35 /// the first entry is treated as the canonical spelling)
36 pub npmrc_keys: &'static [&'static str],
37 /// `pnpm-workspace.yaml` (and `aube-workspace.yaml`) keys that
38 /// set this setting.
39 pub workspace_yaml_keys: &'static [&'static str],
40 /// Example invocations
41 pub examples: &'static [&'static str],
42 /// Escape hatch for the workspace-level accessor audit in
43 /// `tests/accessor_audit.rs`. `true` means "this setting is
44 /// honored but not via its generated `resolved::<name>` typed
45 /// accessor" — e.g. read through `std::env::var` behind a
46 /// hand-rolled `LazyLock`, looked up in `NpmConfig` by string
47 /// key, or accepted for pnpm parity with no behavior wired.
48 /// Default `false`; the audit fails CI when a setting with a
49 /// supported scalar type has no `resolved::<name>` call site
50 /// anywhere in the workspace and this flag is not set.
51 pub typed_accessor_unused: bool,
52 /// Marks a setting as part of the npm-shared `.npmrc` surface:
53 /// npm (and yarn / pnpm) read it from `.npmrc` too, so
54 /// `aube config set` writes it there to keep the multi-tool
55 /// contract. Aube-specific / pnpm-only settings leave this
56 /// `false` and route to aube's own config (`config.toml`).
57 pub npm_shared: bool,
58}
59
60// Pulls in `pub const SETTINGS: &[SettingMeta] = &[...]` generated by build.rs.
61include!(concat!(env!("OUT_DIR"), "/settings_meta_data.rs"));
62
63/// Return the full slice of settings, alphabetically sorted by name.
64pub fn all() -> &'static [SettingMeta] {
65 SETTINGS
66}
67
68/// Look up a setting by its canonical pnpm name. `SETTINGS` is
69/// generated sorted by name (build.rs guarantees this via
70/// `BTreeMap` iteration), so a binary search drops the lookup from
71/// O(N) to O(log N). With ~120 entries and dozens of lookups per
72/// command, the saving compounds across every `aube run` startup.
73///
74/// The sort invariant is asserted in debug builds. A future hand
75/// edit of `SETTINGS` (or a regression in the build.rs sort) would
76/// silently flip valid lookups to `None`; the assert catches that
77/// in test runs without slowing release builds.
78pub fn find(name: &str) -> Option<&'static SettingMeta> {
79 debug_assert!(
80 SETTINGS.windows(2).all(|w| w[0].name <= w[1].name),
81 "SETTINGS must be sorted by name for find() to work"
82 );
83 SETTINGS
84 .binary_search_by(|s| s.name.cmp(name))
85 .ok()
86 .map(|i| &SETTINGS[i])
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn settings_metadata_has_expected_count() {
95 assert!(
96 SETTINGS.len() >= 100,
97 "expected ≥100 settings, got {}",
98 SETTINGS.len()
99 );
100 }
101
102 #[test]
103 fn every_setting_has_required_fields() {
104 for s in SETTINGS {
105 assert!(!s.name.is_empty(), "empty name in entry");
106 assert!(!s.description.is_empty(), "{}: empty description", s.name);
107 assert!(!s.type_.is_empty(), "{}: empty type", s.name);
108 }
109 }
110
111 #[test]
112 fn known_settings_resolve() {
113 assert!(find("registries").is_some());
114 assert!(find("preferFrozenLockfile").is_some());
115 assert!(find("aubeNoLock").is_some());
116 assert!(find("totally-fake-setting").is_none());
117 }
118
119 #[test]
120 fn find_matches_all() {
121 for s in all() {
122 assert_eq!(find(s.name).map(|x| x.name), Some(s.name));
123 }
124 }
125
126 /// Every setting that advertises a `workspaceYaml` source must
127 /// actually be readable from `pnpm-workspace.yaml` — i.e. the key
128 /// has to deserialize onto `WorkspaceConfig` without falling into
129 /// the `#[serde(flatten)] extra` catch-all. Guards against metadata
130 /// rotting away from real workspace-config support.
131 ///
132 /// Lives here (in `aube-settings` — a crate that `aube-manifest`
133 /// itself does not depend on) so it's a dev-only parity check; the
134 /// dependency edge only exists for tests.
135 #[test]
136 fn workspace_yaml_keys_deserialize_onto_workspace_config() {
137 // Field types on WorkspaceConfig vary, so there's no single
138 // YAML value that parses for every field. Try a handful of
139 // shapes in turn — if at least one parses AND the key doesn't
140 // fall into `extra`, the field is wired up.
141 let shapes = ["null", "true", "\"x\"", "0", "{}", "[]"];
142 for s in SETTINGS {
143 for key in s.workspace_yaml_keys {
144 let mut recognized = false;
145 let top_level_key = key.split('.').next().unwrap_or(key);
146 for shape in shapes {
147 let yaml = format!("{top_level_key}: {shape}\n");
148 if let Ok(cfg) = yaml_serde::from_str::<aube_manifest::WorkspaceConfig>(&yaml)
149 && !cfg.extra.contains_key(top_level_key)
150 {
151 recognized = true;
152 break;
153 }
154 }
155 assert!(
156 recognized,
157 "{}: workspaceYaml key `{key}` is not a real field on WorkspaceConfig \
158 — it fell through to `extra` (or failed to deserialize entirely). \
159 Either add the field or remove the metadata.",
160 s.name
161 );
162 }
163 }
164 }
165}