1use clap::Subcommand;
7use std::path::PathBuf;
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
11pub enum ChangelogFormat {
12 #[default]
16 #[value(name = "keep-a-changelog", alias = "kac")]
17 KeepAChangelog,
18 ReleaseNotes,
21 Json,
24}
25
26#[derive(Subcommand)]
33pub enum TagSub {
34 Rollback {
43 #[arg(
44 value_name = "sha",
45 help = "Commit SHA to roll back from. Defaults to HEAD."
46 )]
47 sha: Option<String>,
48 #[arg(long, help = "Print what would happen without mutating anything")]
49 dry_run: bool,
50 #[arg(
51 long = "no-push",
52 help = "Skip remote tag delete and branch push (local-only)"
53 )]
54 no_push: bool,
55 #[arg(
56 long,
57 help = "Override the published-state guard: roll back even when the tag's run summary shows a one-way-door publisher (crates.io, chocolatey, winget, snapcraft, ...) accepted the version, when the crates.io index shows the tag's crate@version live (GLOBAL state — published by any prior run, not just this one; an unreachable index also refuses), or — when no summary exists — when a published (non-draft) GitHub release exists for the tag. Without it, rollback refuses because those registries never accept the same version twice: the version is burned and the only clean recovery is fixing forward"
58 )]
59 force: bool,
60 #[arg(
61 long,
62 default_value = "all",
63 help = "Tag-shape filter: all | lockstep | per-crate"
64 )]
65 scope: String,
66 #[arg(
67 long,
68 default_value = "revert",
69 help = "Rollback strategy: revert (default; history-preserving) | reset (opt-in; rewrites history, requires --force-with-lease to push)"
70 )]
71 mode: String,
72 #[arg(
73 long,
74 value_name = "name",
75 help = "Branch name to push the revert commit to. Usually unnecessary: the branch is auto-resolved from the bump commit via `git branch -r --contains <sha>`, which covers the ordinary CI tag-push case (detached HEAD, GITHUB_REF_NAME set to the tag). Needed only when that resolution is ambiguous or empty — the bump commit is on two or more remote branches, or on none and HEAD cannot be resolved either. Both cases fail with an error naming this flag. Pass --branch master (or whichever branch the bump commit was created on)."
76 )]
77 branch: Option<String>,
78 },
79}
80
81#[derive(Subcommand)]
84pub enum CheckCmd {
85 Config {
87 #[arg(long, help = "Validate a specific workspace in a monorepo config")]
88 workspace: Option<String>,
89 #[arg(
90 long,
91 value_delimiter = ',',
92 help = "Validate these skip tokens (stages or publishers) against the known set \
93 without running anything (comma-separated). Unified denylist: a stage name \
94 skips the stage, a publisher name (npm, homebrew, chocolatey, …) skips \
95 that publisher."
96 )]
97 skip: Vec<String>,
98 #[arg(
99 long = "publishers",
100 value_delimiter = ',',
101 help = concat!(
102 "Validate-only: check that each name is a publisher the active config \
103 actually enables (a known but unconfigured publisher is rejected). ",
104 "Comma-separated publishers to run (default: all configured). \
105 --skip always wins over --publishers.",
106 )
107 )]
108 publishers: Vec<String>,
109 },
110 Determinism(CheckDeterminismArgs),
112 VersionFiles,
114}
115
116#[derive(clap::Args)]
119pub struct CheckDeterminismArgs {
120 #[arg(
121 long,
122 default_value = "2",
123 help = "Number of from-clean rebuilds to diff"
124 )]
125 pub runs: u32,
126 #[arg(
127 long,
128 value_name = "stages",
129 help = "Optional stage subset (build,source,upx,archive,nfpm,makeself,snapcraft,sbom,sign,checksum,cargo-package,docker,msi,nsis,dmg,pkg,srpm,appbundle,appimage,flatpak, plus the `installers` family selector expanding to nfpm,makeself,srpm,msi,nsis,dmg,pkg). Omit the flag to byte-verify the full OS-native partition for this host (Linux adds nfpm/makeself/snapcraft/srpm/docker/appimage/flatpak; macOS adds appbundle/dmg/pkg; Windows adds msi/nsis). The list is also the build filter: stages NOT named here are added to the child release's `--skip=` set, so a stage must be requested (or in the host default) to be byte-verified. `cargo-package` is harness-only — drives `cargo package --no-verify --allow-dirty` per workspace member to probe `.crate` byte-stability without hitting a registry; it is NOT in the host default and stays opt-in. `docker` is harness-only — drives `docker buildx build --output=type=oci,rewrite-timestamp=true,dest=…` against each configured `dockers_v2` entry's rendered dockerfile (with its `extra_files` and `build_args`, mirroring the production `docker` stage) to probe OCI image byte-stability without pushing to a registry; skipped when `docker buildx` is unavailable or the crate configures no `dockers_v2`. Installer stages (msi/nsis/dmg/pkg/srpm) plus appimage (needs `linuxdeploy`) and flatpak (needs `flatpak-builder`) are skipped at the gate when their backing tool is absent — a host-default stage warn-skips, an explicitly typed one hard-fails; `appbundle` is pure file assembly and always runs when requested."
130 )]
131 pub stages: Option<String>,
132 #[arg(
133 long,
134 value_name = "csv",
135 help = "Restrict the harness to a comma-separated subset of configured target triples. Used by the sharded release workflow so each runner only validates targets it can natively build (Linux runner skips macOS targets, etc.). Forwarded to the child `anodizer release --snapshot` subprocess."
136 )]
137 pub targets: Option<String>,
138 #[arg(
139 long,
140 value_name = "path",
141 help = "JSON report path; default dist/run-<id>/determinism.json"
142 )]
143 pub report: Option<PathBuf>,
144 #[arg(
145 long,
146 conflicts_with = "no_snapshot",
147 help = "Force snapshot mode on the child release subprocess (artifacts get a `-SNAPSHOT-<sha>` suffix). Default: auto — snapshot off when HEAD is at a tag, on otherwise."
148 )]
149 pub snapshot: bool,
150 #[arg(
151 long = "no-snapshot",
152 conflicts_with = "snapshot",
153 help = "Force snapshot mode OFF on the child release subprocess (artifacts emit the actual release version). Default: auto — see --snapshot."
154 )]
155 pub no_snapshot: bool,
156 #[arg(
157 long = "inject-drift",
158 value_name = "stage",
159 hide = true,
160 help = "(TEST HARNESS) Append 1 random byte to the first artifact emitted by <stage>. Gated by ANODIZE_TEST_HARNESS=1."
161 )]
162 pub inject_drift: Option<String>,
163 #[arg(
164 long = "preserve-dist",
165 value_name = "path",
166 help = "On a passing run, copy run-0's `<worktree>/dist/**` to <path> and emit `<path>/context.json` describing the artifact set. The release workflow's publish-only path consumes this to ship the determinism step's output directly (eliminates the redundant `build:` recompilation). Local operators can pass this too — useful for inspecting a hermetic dist tree without re-running the release pipeline."
167 )]
168 pub preserve_dist: Option<PathBuf>,
169 #[arg(
170 long = "crate",
171 value_name = "name",
172 help = "When --preserve-dist is set, write the preserved dist tree to \
173 <dest>/<name>/ instead of directly into <dest>/. Used by the \
174 sharded matrix to produce per-crate subdirectories so a \
175 `release --publish-only` job can merge all crates into a single \
176 dist/ without context.json collision."
177 )]
178 pub crate_name: Option<String>,
179 #[arg(
192 long = "require-tools",
193 help = "Fail (not warn-skip) if any selected stage's backing tool is missing — used by CI so a default host-OS run cannot silently skip an OS-native producer."
194 )]
195 pub require_tools: bool,
196}