Skip to main content

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}