anodizer_core/context/options.rs
1use super::*;
2
3pub struct ContextOptions {
4 pub snapshot: bool,
5 pub nightly: bool,
6 pub dry_run: bool,
7 pub quiet: bool,
8 pub verbose: bool,
9 pub debug: bool,
10 pub skip_stages: Vec<String>,
11 /// `--publishers`: per-publisher allowlist. Empty means "no allowlist" —
12 /// every publisher runs (subject to `skip_stages`). Non-empty restricts
13 /// the publish stage to exactly the named publishers. Entries are
14 /// canonical publisher names (`Publisher::name()`, e.g. `npm`, `cargo`).
15 /// Orthogonal to `selected_crates` (which scopes crates, not publishers)
16 /// and to `skip_stages` (the unified denylist, which always wins — see
17 /// [`Context::publisher_deselected`]).
18 pub publisher_allowlist: Vec<String>,
19 pub selected_crates: Vec<String>,
20 pub token: Option<String>,
21 /// Maximum number of parallel build jobs (minimum 1).
22 pub parallelism: usize,
23 /// When set, build only for this single host target triple.
24 pub single_target: Option<String>,
25 /// Path to a custom release notes file (overrides changelog).
26 pub release_notes_path: Option<PathBuf>,
27 /// When true, abort immediately on first error during publishing.
28 pub fail_fast: bool,
29 /// Partial build target for split/merge mode. When set, the build stage
30 /// filters targets to only those matching this partial target.
31 pub partial_target: Option<PartialTarget>,
32 /// When true, running with `--merge` flag (merging artifacts from split builds).
33 pub merge: bool,
34 /// `--publish-only`: load artifacts from a preserved dist (written
35 /// by `anodize check determinism --preserve-dist=...`) and run
36 /// only the sign + publish pipeline. The CLI dispatcher uses this
37 /// flag in `setup_env` to defer the GitHub-token check to the
38 /// config-derived environment preflight (the github-release
39 /// publisher's token ladder plus the sign stage's `KeyEnv`
40 /// requirements), which validates token and sign-key material in
41 /// one collect-all pass and bails fail-closed on missing values.
42 /// Without this deferral, `setup_env`'s token check would fire FIRST
43 /// and pre-empt that richer, per-publisher preflight.
44 pub publish_only: bool,
45 /// `--preflight-secrets`: a check-only secrets gate. Like
46 /// [`Self::publish_only`], it defers `setup_env`'s GitHub-token hard
47 /// error to the config-derived environment preflight (run in
48 /// `SecretsOnly` scope), which validates the token ladder alongside
49 /// every other runner-agnostic credential and then exits with zero
50 /// mutations. Without this deferral, `setup_env` would bail on the
51 /// missing token before the secrets gate could report the full set.
52 pub preflight_secrets: bool,
53 /// Explicit project root directory. When set, stages use this instead of
54 /// discovering the repo root via `git rev-parse --show-toplevel`.
55 pub project_root: Option<PathBuf>,
56 /// Strict mode: configured features that would silently skip become errors.
57 pub strict: bool,
58 /// `--resume-release`: opt-in to continue into a release left over from
59 /// a prior failed attempt. Bypasses the leftover-assets pre-check that
60 /// bails when an existing release already has assets and
61 /// `replace_existing_artifacts` is false.
62 pub resume_release: bool,
63 /// `--replace-existing`: CLI override that forces
64 /// `release.replace_existing_artifacts: true` regardless of config.
65 /// The release stage ORs this with the config value.
66 pub replace_existing_artifacts: bool,
67 /// `--no-post-publish-poll`: skip post-publish polling for the
68 /// chocolatey moderation queue and the winget PR validation pipeline.
69 /// When `true`, the polling runner emits `PostPublishStatus::NotPolled`
70 /// (pending immediately) for every publisher rather than waiting on a
71 /// terminal state. Lets CI users with no patience for long-running
72 /// waits opt out without scattering `post_publish_poll.enabled: false`
73 /// across every publisher block.
74 pub skip_post_publish_poll: bool,
75 /// Whether the publisher dispatcher gates irreversible Submitter
76 /// publishers (chocolatey, winget, AUR-source, krew, snapcraft) on
77 /// the success of every required Assets/Manager publisher that ran
78 /// before them. `None` defaults to `Some(true)` (gate on). The CLI
79 /// flag `--no-gate-submitter` flips this to `Some(false)`. See
80 /// `stage-publish::dispatch::DispatchOptions::gate_submitter` for
81 /// the gating mechanics.
82 pub gate_submitter: Option<bool>,
83 /// `--simulate-failure=<publisher>` (repeatable, hidden, env-gated
84 /// behind `ANODIZE_TEST_HARNESS=1`): names of publishers whose
85 /// `run()` should be skipped and a synthetic `Failed("simulated
86 /// failure: <name>")` recorded in the report instead. Lets the
87 /// failure-mode test harness exercise gate / rollback / report
88 /// paths deterministically without monkey-patching production
89 /// publisher code.
90 pub simulate_failure_publishers: Vec<String>,
91 /// `--show-skipped`: surface the per-crate "no `<publisher>` config
92 /// block" skip lines at default verbosity. In workspace mode every
93 /// PR-based publisher (homebrew / nix / scoop / aur / winget / krew /
94 /// chocolatey) visits every selected crate and skips the ones whose
95 /// config lacks its block; at default verbosity those no-op skips are
96 /// routed to debug (invisible unless `--debug`) so they do not bury the
97 /// real output. Setting this flag forces them back to status — the
98 /// diagnostic escape hatch for "why didn't publisher X run for crate Y?".
99 pub show_skipped: bool,
100 /// `--allow-nondeterministic <name>=<reason>` (repeatable):
101 /// runtime non-determinism opt-outs for specific artifacts. The
102 /// determinism stage suppresses its non-determinism error for
103 /// any matching artifact name, recording the supplied reason in
104 /// the report. Mutually exclusive with `--strict` at the clap
105 /// layer.
106 pub runtime_nondeterministic_allowlist: Vec<(String, String)>,
107 /// `--summary-json=<path>`: when set, the per-publisher run
108 /// summary is written to this path. Consumed by the run-summary
109 /// task.
110 pub summary_json_path: Option<PathBuf>,
111 /// `--allow-ai-failure`: when true, a failure inside the
112 /// `changelog.ai` enhancement step (transport, non-2xx, parse) is
113 /// logged as a warning and the pre-AI release notes are kept
114 /// verbatim. Default `false` (fail-closed) follows the conventional
115 /// "any hook failure aborts" pattern: a silent fall-back to the
116 /// raw notes ships the wrong body without the operator noticing.
117 pub allow_ai_failure: bool,
118 /// `changelog --from <ref>`: explicit lower bound (range start) for
119 /// changelog commit collection. When set, the changelog stage uses this
120 /// ref as the previous tag instead of auto-discovering the latest matching
121 /// tag. A dedicated option (rather than the always-auto-populated
122 /// `PreviousTag` template var) so a full release run's per-crate
123 /// auto-discovery is never overridden — only an explicit `--from` is.
124 pub changelog_from: Option<String>,
125 /// `changelog ..` / `changelog ..<ref>`: an explicit empty lower bound,
126 /// meaning "from the beginning of history" with no auto-discovered
127 /// previous tag. When `true`, the changelog stage skips tag
128 /// auto-discovery entirely so the range covers all reachable commits up
129 /// to the upper bound — distinguishing the explicit empty-from form from
130 /// an omitted range (which still resolves to the last release tag).
131 pub changelog_full_history: bool,
132 /// `changelog <from>..<to>` / `changelog <tag>`: an explicit UPPER bound
133 /// (range end) for changelog commit collection. When set, the changelog
134 /// stage walks `<from>..<to>` instead of `<from>..HEAD`, so commits AFTER
135 /// `<to>` are excluded. A dedicated option (rather than the always-populated
136 /// `Tag` template var) so the pending / snapshot window — where `Tag`
137 /// resolves to the latest EXISTING tag yet the range must still run to
138 /// HEAD — is never silently bounded to that tag. `None` keeps the upper
139 /// bound at `HEAD` (the pending window since the last release).
140 pub changelog_to: Option<String>,
141 /// Marks the run as the standalone `changelog --format release-notes`
142 /// LOCAL preview, NOT the `release`/`tag` pipeline. The standalone command
143 /// is an inspection tool: it must render the pending window from local git
144 /// with no release-time preconditions, so this flag relaxes three guards
145 /// that are correct for a real release but wrong for a preview:
146 /// - the tag-must-point-at-HEAD + dirty-tree bails in
147 /// `resolve_git_context` (a preview must not require a checkout or a
148 /// clean tree),
149 /// - the snapshot-skip config gate in the changelog stage (a preview
150 /// must render without `changelog.snapshot: true`),
151 /// - the `use: github-native` branch (a preview renders from local git
152 /// instead of requiring a token / emitting empty bodies).
153 ///
154 /// ONLY the standalone changelog command sets this; the release/tag
155 /// pipelines leave it `false` so their guards stay fully intact.
156 pub changelog_preview: bool,
157 /// Marks the run as the standalone `anodizer notify` command — a
158 /// side-channel that sends a one-off message through the configured
159 /// announce integrations, NOT part of the `release` pipeline.
160 ///
161 /// notify is routinely invoked as an `on_error:` hook AFTER a release has
162 /// failed mid-flight, when the working tree is dirty (partial `dist/`,
163 /// in-flight writeback) and HEAD may not sit on the release tag. A
164 /// notification must never be blocked by repo state — losing the alert is
165 /// the worst outcome — so this flag relaxes the three release-time git
166 /// preconditions in `resolve_git_context` that are correct for a real
167 /// release but wrong for a notification: the no-tag bail (falls back to the
168 /// `v0.0.0` synthetic tag), the tag-must-point-at-HEAD bail, and the
169 /// dirty-tree bail. ONLY the notify command sets this; every release/tag
170 /// path leaves it `false` so their guards stay intact.
171 pub notify: bool,
172 /// `--allow-snapshot-publish`: downgrade the publish stage's non-release
173 /// version guard from a hard bail to a warning.
174 ///
175 /// By default the publish, blob, and announce stages REFUSE to ship a
176 /// non-release version (snapshot / dirty / `0.0.0`-sentinel — see
177 /// [`crate::version::guard_release_version`] /
178 /// [`crate::version::is_release_version`]) to an external, often
179 /// irreversible, channel. The canonical accident this prevents: a CI run
180 /// that resolved `0.0.0~SNAPSHOT-<sha>` and pushed it to a package
181 /// registry. This flag is the deliberate opt-in for the legitimate
182 /// "publish a snapshot to a private channel" case; it is the ONLY thing
183 /// required to opt in (the version is not re-stated). Default `false`
184 /// (fail-closed).
185 pub allow_snapshot_publish: bool,
186}
187
188impl Default for ContextOptions {
189 fn default() -> Self {
190 Self {
191 snapshot: false,
192 nightly: false,
193 dry_run: false,
194 quiet: false,
195 verbose: false,
196 debug: false,
197 skip_stages: Vec::new(),
198 publisher_allowlist: Vec::new(),
199 selected_crates: Vec::new(),
200 token: None,
201 parallelism: 4,
202 single_target: None,
203 release_notes_path: None,
204 fail_fast: false,
205 partial_target: None,
206 merge: false,
207 publish_only: false,
208 preflight_secrets: false,
209 project_root: None,
210 strict: false,
211 resume_release: false,
212 replace_existing_artifacts: false,
213 skip_post_publish_poll: false,
214 gate_submitter: None,
215 simulate_failure_publishers: Vec::new(),
216 show_skipped: false,
217 runtime_nondeterministic_allowlist: Vec::new(),
218 summary_json_path: None,
219 allow_ai_failure: false,
220 changelog_from: None,
221 changelog_full_history: false,
222 changelog_to: None,
223 changelog_preview: false,
224 notify: false,
225 allow_snapshot_publish: false,
226 }
227 }
228}