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