1use std::ffi::OsString;
2
3use camino::Utf8PathBuf;
4use clap::{Args, Parser, Subcommand, ValueEnum};
5use semver::Version;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Parser)]
9#[command(
10 name = "simit",
11 version,
12 about = "Semver-aware Rust project helper",
13 long_about = "simit helps Rust projects prepare release commits, generate CI workflows, wire formatter/pre-commit hooks, and emit shell integration artifacts."
14)]
15pub struct Cli {
16 #[command(subcommand, help = "Command to run")]
17 pub command: Commands,
18}
19
20#[derive(Debug, Subcommand)]
21pub enum Commands {
22 #[command(about = "Bump package versions, commit the change, and optionally tag it")]
23 Commit(CommitCommand),
24 #[command(about = "Run local release checks, update the changelog, commit, and tag")]
25 Release(ReleaseCommand),
26 #[command(
27 about = "Bootstrap generated project files and distribution skeletons",
28 disable_help_subcommand = true
29 )]
30 Init(InitCommand),
31 #[command(about = "Distribution channel helpers")]
32 Dist(DistCommand),
33 #[command(about = "Manage a Keep a Changelog file")]
34 Changelog(ChangelogCommand),
35 #[command(about = "Install and verify project Git hooks")]
36 Hooks(HooksCommand),
37 #[command(about = "Manage user-scoped simit configuration")]
38 Config(ConfigCommand),
39 #[command(about = "Inspect and maintain the per-user project registry")]
40 Projects(ProjectsCommand),
41 #[command(about = "Apply deterministic simit project upgrades")]
42 Upgrade(UpgradeCommand),
43 #[command(about = "Print shell completion scripts")]
44 Completions(CompletionsCommand),
45 #[command(about = "Print a roff manpage for simit")]
46 Man(ManCommand),
47}
48
49#[derive(Debug, Args)]
50pub struct InitCommand {
51 #[command(subcommand)]
52 pub action: InitAction,
53}
54
55#[derive(Debug, Args)]
56pub struct UpgradeCommand {
57 #[arg(
58 long = "path",
59 value_name = "PATH",
60 action = clap::ArgAction::Append,
61 conflicts_with = "all",
62 help = "Workspace path to upgrade; repeatable. Defaults to the current workspace"
63 )]
64 pub paths: Vec<Utf8PathBuf>,
65 #[arg(
66 long,
67 conflicts_with = "paths",
68 help = "Upgrade every registered non-ephemeral simit-managed project"
69 )]
70 pub all: bool,
71 #[arg(
72 long,
73 conflicts_with = "check",
74 help = "Report intended changes without writing files"
75 )]
76 pub dry_run: bool,
77 #[arg(long, help = "Exit nonzero when an upgrade would change files")]
78 pub check: bool,
79 #[arg(long, help = "Show unified diffs; valid with --dry-run or --check")]
80 pub diff: bool,
81 #[arg(
82 long = "pages-only",
83 help = "Only apply Codeberg Pages workflow upgrades, skipping README badge upgrades"
84 )]
85 pub pages_only: bool,
86}
87
88#[derive(Debug, Args)]
89pub struct DistCommand {
90 #[command(subcommand)]
91 pub action: DistAction,
92}
93
94#[derive(Debug, Subcommand)]
95pub enum InitAction {
96 #[command(about = "Generate or verify Rust CI workflows")]
97 Ci(Box<InitCiCommand>),
98 #[command(about = "Generate or verify a canonical Rust crane flake and hook wiring")]
99 Flake(InitFlakeCommand),
100 #[command(about = "Bootstrap a Homebrew tap repo with a formula skeleton")]
101 HomebrewTap(InitHomebrewTapCommand),
102 #[command(about = "Bootstrap a Chocolatey package directory")]
103 Chocolatey(Box<InitChocolateyCommand>),
104 #[command(about = "Bootstrap a Scoop bucket repo with a manifest skeleton")]
105 ScoopBucket(InitScoopBucketCommand),
106 #[command(about = "Bootstrap AUR PKGBUILDs (source/-bin/-git flavors)")]
107 Aur(InitAurCommand),
108 #[command(about = "Bootstrap a Fedora COPR RPM spec and .copr/Makefile")]
109 Copr(InitCoprCommand),
110 #[command(about = "Bootstrap an apt (reprepro) repository config")]
111 Apt(InitAptCommand),
112 #[command(about = "Generate the comprehensive multi-channel release workflow")]
113 Release(InitReleaseCommand),
114}
115
116#[derive(Debug, Subcommand)]
117pub enum DistAction {
118 #[command(about = "Homebrew formula helpers (render, bump, push)")]
119 Homebrew(HomebrewCommand),
120 #[command(about = "Chocolatey package helpers (render, bump, push)")]
121 Chocolatey(ChocolateyCommand),
122 #[command(about = "Scoop manifest helpers (render, bump, push)")]
123 Scoop(ScoopCommand),
124 #[command(about = "Winget manifest submission helpers")]
125 Winget(WingetCommand),
126 #[command(about = "Windows community packager publishing helpers")]
127 Windows(WindowsCommand),
128 #[command(about = "AUR PKGBUILD helpers (render)")]
129 Aur(AurCommand),
130 #[command(about = "Fedora COPR helpers (render)")]
131 Copr(CoprCommand),
132 #[command(about = "apt repository helpers (render)")]
133 Apt(AptCommand),
134}
135
136#[derive(Debug, Args)]
137pub struct CommitCommand {
138 #[arg(
139 long = "package",
140 value_name = "NAME",
141 help = "Workspace package to bump; may be repeated"
142 )]
143 pub packages: Vec<String>,
144 #[arg(long, help = "Bump every package in the workspace")]
145 pub workspace: bool,
146 #[arg(long = "no-tag", help = "Skip creating the release tag")]
147 pub no_tag: bool,
148 #[arg(
149 long = "no-sign",
150 help = "Create an unsigned tag instead of a signed tag"
151 )]
152 pub no_sign: bool,
153 #[arg(long, help = "Print the planned release commit without changing files")]
154 pub dry_run: bool,
155 #[arg(value_enum, value_name = "BUMP", help = "Version bump to apply")]
156 pub bump: BumpKind,
157 #[arg(
158 long = "pre",
159 value_name = "ID",
160 help = "Prerelease identifier to attach, such as alpha.1 or rc.1"
161 )]
162 pub pre: Option<String>,
163 #[arg(
164 value_name = "GIT_ARGS",
165 trailing_var_arg = true,
166 allow_hyphen_values = true,
167 help = "Arguments passed through to git commit, such as -m <message>"
168 )]
169 pub git_args: Vec<OsString>,
170}
171
172#[derive(Debug, Args)]
173pub struct ReleaseCommand {
174 #[arg(
175 long = "package",
176 value_name = "NAME",
177 help = "Workspace package to release; may be repeated"
178 )]
179 pub packages: Vec<String>,
180 #[arg(long, help = "Release every package in the workspace")]
181 pub workspace: bool,
182 #[arg(long = "no-tag", help = "Skip creating the release tag")]
183 pub no_tag: bool,
184 #[arg(
185 long = "no-sign",
186 help = "Create an unsigned tag instead of a signed tag"
187 )]
188 pub no_sign: bool,
189 #[arg(long, help = "Print the planned release without changing files")]
190 pub dry_run: bool,
191 #[arg(
192 value_enum,
193 value_name = "ACTION",
194 help = "Version bump to apply, or sync-up to rerun a failed release from HEAD"
195 )]
196 pub action: ReleaseAction,
197 #[arg(value_enum, value_name = "TRUST_ACTION", help = "Release trust action")]
198 pub trust_action: Option<ReleaseTrustAction>,
199 #[arg(
200 value_enum,
201 value_name = "SECRETS_ACTION",
202 help = "Release secrets action"
203 )]
204 pub secrets_action: Option<ReleaseSecretsAction>,
205 #[arg(
206 long = "key",
207 value_name = "FINGERPRINT",
208 help = "OpenPGP key fingerprint for `simit release trust`"
209 )]
210 pub trust_key: Option<String>,
211 #[arg(
212 long = "trust-root",
213 value_name = "PATH",
214 help = "Path to the release maintainer public keyring"
215 )]
216 pub trust_root: Option<Utf8PathBuf>,
217 #[arg(
218 long = "pre",
219 value_name = "ID",
220 help = "Prerelease identifier to attach, such as alpha.1 or rc.1"
221 )]
222 pub pre: Option<String>,
223 #[arg(
224 short = 'm',
225 long = "message",
226 value_name = "MESSAGE",
227 help = "Release commit message"
228 )]
229 pub message: Option<String>,
230 #[arg(
231 long = "no-changelog",
232 help = "Skip promoting CHANGELOG.md even when it exists"
233 )]
234 pub no_changelog: bool,
235 #[arg(long, help = "Push a sync-up tag move to the remote")]
236 pub push: bool,
237 #[arg(
238 long,
239 value_name = "REMOTE",
240 default_value = "origin",
241 help = "Remote to push sync-up tag moves to"
242 )]
243 pub remote: String,
244 #[arg(
245 long,
246 help = "Print machine-readable JSON for `simit release verify`, `simit release plan`, or `simit release secrets contract`"
247 )]
248 pub json: bool,
249 #[arg(
250 long = "dry-run-package",
251 help = "Run `cargo package` checks for each crate selected by `simit release plan`"
252 )]
253 pub dry_run_package: bool,
254 #[arg(
255 long = "version",
256 value_name = "VERSION",
257 help = "Version to verify instead of the selected package's current Cargo.toml version"
258 )]
259 pub verify_version: Option<Version>,
260 #[arg(
261 long = "push-target",
262 value_name = "REMOTE",
263 help = "Remote checked by `simit release verify` for the release tag"
264 )]
265 pub push_target: Option<String>,
266 #[arg(
267 long = "repo",
268 value_name = "OWNER/REPO",
269 help = "Forgejo/Codeberg repository for `simit release secrets`"
270 )]
271 pub secrets_repo: Option<String>,
272 #[arg(
273 long = "api-base",
274 value_name = "URL",
275 default_value = "https://codeberg.org/api/v1",
276 help = "Forgejo/Codeberg API base URL for `simit release secrets`"
277 )]
278 pub secrets_api_base: String,
279 #[arg(
280 long = "token-file",
281 value_name = "PATH",
282 help = "File containing the Forgejo/Codeberg API token for `simit release secrets`"
283 )]
284 pub secrets_token_file: Option<Utf8PathBuf>,
285 #[arg(
286 long = "assume-account-secret",
287 value_name = "NAME",
288 action = clap::ArgAction::Append,
289 help = "Secret name managed at account/org scope and not visible in repo secret listings"
290 )]
291 pub assumed_account_secrets: Vec<String>,
292 #[arg(
293 long = "minisign-secret-key-file",
294 value_name = "PATH",
295 help = "Existing minisign secret key file to import as MINISIGN_SECRET_KEY"
296 )]
297 pub minisign_secret_key_file: Option<Utf8PathBuf>,
298 #[arg(
299 long = "minisign-password-file",
300 value_name = "PATH",
301 help = "Existing minisign password file to import as MINISIGN_PASSWORD"
302 )]
303 pub minisign_password_file: Option<Utf8PathBuf>,
304 #[arg(
305 long = "minisign-public-key",
306 value_name = "PATH",
307 default_value = "keys/minisign.pub",
308 help = "Committed minisign public key path"
309 )]
310 pub minisign_public_key: Utf8PathBuf,
311 #[arg(
312 long = "rotate-minisign",
313 help = "Generate a new encrypted minisign keypair and replace the committed public key"
314 )]
315 pub rotate_minisign: bool,
316}
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
319pub enum ReleaseAction {
320 #[value(help = "Increment patch: 1.2.3 -> 1.2.4")]
321 Patch,
322 #[value(help = "Increment minor and reset patch: 1.2.3 -> 1.3.0")]
323 Minor,
324 #[value(help = "Increment major and reset minor/patch: 1.2.3 -> 2.0.0")]
325 Major,
326 #[value(help = "Keep the numeric version and replace prerelease metadata")]
327 Prerelease,
328 #[value(
329 name = "sync-up",
330 help = "Move the current-version release tag to HEAD"
331 )]
332 SyncUp,
333 #[value(name = "trust", help = "Manage release maintainer trust roots")]
334 Trust,
335 #[value(
336 name = "verify",
337 help = "Run the read-only release-bar verification report"
338 )]
339 Verify,
340 #[value(
341 name = "plan",
342 help = "Print the dependency-ordered publish plan for the current workspace"
343 )]
344 Plan,
345 #[value(name = "secrets", help = "Manage remote release workflow secrets")]
346 Secrets,
347}
348
349impl ReleaseAction {
350 pub fn bump_kind(self) -> Option<BumpKind> {
351 match self {
352 Self::Patch => Some(BumpKind::Patch),
353 Self::Minor => Some(BumpKind::Minor),
354 Self::Major => Some(BumpKind::Major),
355 Self::Prerelease => Some(BumpKind::Prerelease),
356 Self::SyncUp | Self::Trust | Self::Verify | Self::Plan | Self::Secrets => None,
357 }
358 }
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
362pub enum ReleaseTrustAction {
363 #[value(help = "Show detected release signing key and trust-root state")]
364 Status,
365 #[value(help = "Export the maintainer public key into the release trust root")]
366 Init,
367 #[value(help = "Verify the release trust root exists and matches the signing key")]
368 Check,
369 #[value(
370 name = "inspect-minisign-input",
371 help = "Classify minisign import inputs without printing secret values"
372 )]
373 InspectMinisignInput,
374 #[value(name = "contract", help = "Print the release secrets contract")]
375 Contract,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
379pub enum ReleaseSecretsAction {
380 #[value(help = "Upload or rotate release workflow secrets")]
381 Init,
382 #[value(help = "Check release workflow secret names without reading values")]
383 Check,
384 #[value(
385 name = "contract",
386 help = "Print the generic release credential contract as JSON"
387 )]
388 Contract,
389 #[value(
390 name = "inspect-minisign-input",
391 help = "Classify minisign import inputs without printing secret values"
392 )]
393 InspectMinisignInput,
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
397pub enum BumpKind {
398 #[value(help = "Increment patch: 1.2.3 -> 1.2.4")]
399 Patch,
400 #[value(help = "Increment minor and reset patch: 1.2.3 -> 1.3.0")]
401 Minor,
402 #[value(help = "Increment major and reset minor/patch: 1.2.3 -> 2.0.0")]
403 Major,
404 #[value(help = "Keep the numeric version and replace prerelease metadata")]
405 Prerelease,
406}
407
408#[derive(Debug, Args)]
409pub struct InitCiCommand {
410 #[arg(
411 long = "package",
412 value_name = "NAME",
413 conflicts_with = "workspace",
414 help = "Workspace package to generate CI for; may be repeated"
415 )]
416 pub packages: Vec<String>,
417 #[arg(long, help = "Generate CI for every package in the workspace")]
418 pub workspace: bool,
419 #[arg(
420 long,
421 value_enum,
422 value_name = "PLATFORM",
423 help = "Workflow platform to generate"
424 )]
425 pub platform: Platform,
426 #[arg(
427 long,
428 value_enum,
429 value_name = "RUNTIME",
430 help = "Command runtime used inside generated workflows"
431 )]
432 pub runtime: Option<RuntimeChoice>,
433 #[arg(
434 long,
435 value_name = "LABEL",
436 help = "Override the generated runner label for Linux jobs"
437 )]
438 pub runner: Option<String>,
439 #[arg(
440 long = "windows-runner",
441 value_name = "LABEL",
442 help = "Override the generated runner label for Windows artifact jobs"
443 )]
444 pub windows_runner: Option<String>,
445 #[arg(
446 long = "step-runner",
447 value_name = "STEP=LABEL",
448 help = "Override runner for a specific step key (repeatable, e.g. --step-runner nix-check=atlas-nix-trusted)"
449 )]
450 pub step_runner: Vec<String>,
451 #[arg(
452 long,
453 help = "Generate granular split of CI steps across Codeberg runner tiers (fmt→tiny, clippy/deny/audit→small, test/doc/package→medium); forgejo-only"
454 )]
455 pub granular: bool,
456 #[arg(
457 long = "maintainer-key",
458 value_name = "FINGERPRINT",
459 help = "OpenPGP key fingerprint to export into keys/maintainers.gpg"
460 )]
461 pub maintainer_key: Option<String>,
462 #[arg(
463 long = "maintainers-gpg",
464 value_name = "PATH",
465 help = "Path to the generated maintainer public keyring"
466 )]
467 pub maintainers_gpg: Option<Utf8PathBuf>,
468 #[arg(
469 long = "release-smoke-command",
470 value_name = "COMMAND",
471 help = "Command to run after signing release artifacts and before publishing"
472 )]
473 pub release_smoke_command: Option<String>,
474 #[arg(long, help = "Verify committed workflow files match generated output")]
475 pub check: bool,
476 #[arg(long, help = "Show a unified diff when --check finds stale files")]
477 pub diff: bool,
478 #[arg(
479 long = "with-nextest",
480 num_args = 0..=1,
481 default_missing_value = "true",
482 action = clap::ArgAction::Set,
483 help = "Use cargo-nextest for test steps; pass `--with-nextest=false` to override project config"
484 )]
485 pub with_nextest: Option<bool>,
486 #[arg(
487 long = "with-msrv",
488 num_args = 0..=1,
489 default_missing_value = "true",
490 action = clap::ArgAction::Set,
491 help = "Add an MSRV check using package.rust-version; pass `--with-msrv=false` to override project config"
492 )]
493 pub with_msrv: Option<bool>,
494 #[arg(
495 long = "with-audit",
496 num_args = 0..=1,
497 default_missing_value = "true",
498 action = clap::ArgAction::Set,
499 help = "Install and run cargo-audit; pass `--with-audit=false` to override project config"
500 )]
501 pub with_audit: Option<bool>,
502 #[arg(
503 long = "with-deny",
504 num_args = 0..=1,
505 default_missing_value = "true",
506 action = clap::ArgAction::Set,
507 help = "Install and run cargo-deny; pass `--with-deny=false` to override project config"
508 )]
509 pub with_deny: Option<bool>,
510 #[arg(
511 long = "with-docs",
512 num_args = 0..=1,
513 default_missing_value = "true",
514 action = clap::ArgAction::Set,
515 help = "Build package documentation in CI; pass `--with-docs=false` to override project config"
516 )]
517 pub with_docs: Option<bool>,
518 #[arg(
519 long = "with-om-ci",
520 num_args = 0..=1,
521 default_missing_value = "true",
522 action = clap::ArgAction::Set,
523 help = "Replace flake check and cargo dev-shell steps with om ci run (requires --runtime nix)"
524 )]
525 pub with_om_ci: Option<bool>,
526 #[arg(
527 long = "om-ci-augment",
528 num_args = 0..=1,
529 default_missing_value = "true",
530 action = clap::ArgAction::Set,
531 help = "Run om ci run alongside the legacy cargo dev-shell steps; implies --with-om-ci"
532 )]
533 pub om_ci_augment: Option<bool>,
534 #[arg(
535 long = "omnix-ref",
536 value_name = "REF",
537 help = "Override the pinned omnix flakeref"
538 )]
539 pub omnix_ref: Option<String>,
540 #[arg(
541 long = "with-artifacts",
542 num_args = 0..=1,
543 default_missing_value = "true",
544 action = clap::ArgAction::Set,
545 help = "Generate a tagged-release artifact workflow; pass `--with-artifacts=false` to override project config"
546 )]
547 pub with_artifacts: Option<bool>,
548 #[arg(
549 long = "with-pypi-publish",
550 num_args = 0..=1,
551 default_missing_value = "true",
552 action = clap::ArgAction::Set,
553 help = "Generate a PyPI publish workflow for Python uv projects; pass `--with-pypi-publish=false` to override project config"
554 )]
555 pub with_pypi_publish: Option<bool>,
556 #[arg(
557 long = "publish-crates",
558 num_args = 0..=1,
559 default_missing_value = "true",
560 action = clap::ArgAction::Set,
561 help = "Generate crates.io publish workflows for publishable Rust packages; pass `--publish-crates=false` for CI-only projects"
562 )]
563 pub publish_crates: Option<bool>,
564 #[arg(
565 long = "with-homebrew",
566 help = "Add a Homebrew tap publishing step (forgejo + nix only)"
567 )]
568 pub with_homebrew: bool,
569 #[arg(
570 long = "with-chocolatey",
571 help = "Validate Chocolatey package publishing configuration"
572 )]
573 pub with_chocolatey: bool,
574 #[arg(
575 long = "with-scoop",
576 help = "Validate Scoop bucket publishing configuration"
577 )]
578 pub with_scoop: bool,
579 #[arg(
580 long = "with-codeberg-pages",
581 help = "Generate a Forgejo workflow that publishes .#site with .#deploy-pages"
582 )]
583 pub with_codeberg_pages: bool,
584 #[arg(
585 long = "with-vscode",
586 help = "Generate a Forgejo workflow that publishes a VS Code/Open VSX extension"
587 )]
588 pub with_vscode: bool,
589 #[arg(
590 long = "pages-repo",
591 value_name = "OWNER/REPO",
592 requires = "with_codeberg_pages",
593 help = "Codeberg repository receiving the generated pages branch"
594 )]
595 pub pages_repo: Option<String>,
596 #[arg(
597 long = "pages-canonical-domain",
598 value_name = "DOMAIN",
599 requires = "with_codeberg_pages",
600 help = "Canonical domain that must appear in the generated Pages .domains file"
601 )]
602 pub pages_canonical_domain: Option<String>,
603 #[arg(
604 long = "pages-site-output",
605 value_name = "INSTALLABLE",
606 requires = "with_codeberg_pages",
607 help = "Nix installable used to build the Pages site for domain validation"
608 )]
609 pub pages_site_output: Option<String>,
610 #[arg(
611 long = "pages-token-secret",
612 value_name = "SECRET",
613 requires = "with_codeberg_pages",
614 help = "Actions secret exposed as CODEBERG_TOKEN for Pages deployment"
615 )]
616 pub pages_token_secret: Option<String>,
617 #[arg(
618 long = "pages-source-branch",
619 value_name = "BRANCH",
620 requires = "with_codeberg_pages",
621 help = "Branch whose pushes publish Codeberg Pages"
622 )]
623 pub pages_source_branch: Option<String>,
624 #[arg(
625 long = "pages-deploy-app",
626 value_name = "INSTALLABLE",
627 requires = "with_codeberg_pages",
628 help = "Nix app used to build and push the generated Pages branch"
629 )]
630 pub pages_deploy_app: Option<String>,
631 #[command(flatten)]
632 pub homebrew: HomebrewOverridesArgs,
633 #[command(flatten)]
634 pub chocolatey: ChocolateyOverridesArgs,
635 #[command(flatten)]
636 pub scoop: ScoopOverridesArgs,
637}
638
639#[derive(Debug, Args)]
640pub struct HomebrewOverridesArgs {
641 #[arg(
642 long = "homebrew-name",
643 value_name = "NAME",
644 help = "Homebrew formula name"
645 )]
646 pub name: Option<String>,
647 #[arg(
648 long = "homebrew-tap",
649 id = "homebrew_tap",
650 value_name = "URL",
651 help = "Homebrew tap repo URL, e.g. https://codeberg.org/foo/homebrew-bar.git"
652 )]
653 pub tap: Option<String>,
654 #[arg(
655 long = "homebrew-binary",
656 value_name = "NAME",
657 help = "Binary to install via the formula; repeatable"
658 )]
659 pub binary: Vec<String>,
660 #[arg(
661 long = "homebrew-description",
662 value_name = "TEXT",
663 help = "Formula description (<= 80 chars)"
664 )]
665 pub description: Option<String>,
666 #[arg(
667 long = "homebrew-homepage",
668 value_name = "URL",
669 help = "Project homepage URL"
670 )]
671 pub homepage: Option<String>,
672 #[arg(
673 long = "homebrew-license",
674 value_name = "SPDX",
675 help = "SPDX license identifier (defaults to package.license)"
676 )]
677 pub license: Option<String>,
678 #[arg(
679 long = "homebrew-download-repo",
680 value_name = "OWNER/REPO",
681 help = "Codeberg/GitHub owner/repo for release downloads"
682 )]
683 pub download_repo: Option<String>,
684 #[arg(
685 long = "homebrew-archive-pattern",
686 value_name = "PATTERN",
687 help = "Filename pattern for release archives"
688 )]
689 pub archive_pattern: Option<String>,
690 #[arg(
691 long = "homebrew-no-platform",
692 value_name = "KEY",
693 help = "Disable a platform (darwin_arm|darwin_intel|linux_arm|linux_intel); repeatable"
694 )]
695 pub no_platform: Vec<String>,
696}
697
698impl HomebrewOverridesArgs {
699 pub fn as_overrides(&self) -> crate::config::HomebrewOverrides<'_> {
700 crate::config::HomebrewOverrides {
701 name: self.name.as_deref(),
702 binaries: Some(&self.binary),
703 tap_url: self.tap.as_deref(),
704 description: self.description.as_deref(),
705 homepage: self.homepage.as_deref(),
706 license: self.license.as_deref(),
707 download_repo: self.download_repo.as_deref(),
708 archive_pattern: self.archive_pattern.as_deref(),
709 disabled_platforms: &self.no_platform,
710 }
711 }
712}
713
714#[derive(Debug, Default, Args)]
715pub struct ChocolateyOverridesArgs {
716 #[arg(
717 long = "choco-name",
718 id = "choco_name",
719 value_name = "NAME",
720 help = "Chocolatey package display name"
721 )]
722 pub name: Option<String>,
723 #[arg(
724 long = "choco-id",
725 id = "choco_id",
726 value_name = "ID",
727 help = "Chocolatey nuspec package id"
728 )]
729 pub id: Option<String>,
730 #[arg(
731 long = "choco-title",
732 id = "choco_title",
733 value_name = "TEXT",
734 help = "Chocolatey nuspec title"
735 )]
736 pub title: Option<String>,
737 #[arg(
738 long = "choco-authors",
739 id = "choco_authors",
740 value_name = "TEXT",
741 help = "Chocolatey nuspec authors"
742 )]
743 pub authors: Option<String>,
744 #[arg(
745 long = "choco-description",
746 id = "choco_description",
747 value_name = "TEXT",
748 help = "Chocolatey package description"
749 )]
750 pub description: Option<String>,
751 #[arg(
752 long = "choco-summary",
753 id = "choco_summary",
754 value_name = "TEXT",
755 help = "Chocolatey package summary"
756 )]
757 pub summary: Option<String>,
758 #[arg(
759 long = "choco-project-url",
760 id = "choco_project_url",
761 value_name = "URL",
762 help = "Chocolatey project URL"
763 )]
764 pub project_url: Option<String>,
765 #[arg(
766 long = "choco-license-url",
767 id = "choco_license_url",
768 value_name = "URL",
769 help = "Chocolatey license URL"
770 )]
771 pub license_url: Option<String>,
772 #[arg(
773 long = "choco-icon-url",
774 id = "choco_icon_url",
775 value_name = "URL",
776 help = "Chocolatey icon URL"
777 )]
778 pub icon_url: Option<String>,
779 #[arg(
780 long = "choco-package-source-url",
781 id = "choco_package_source_url",
782 value_name = "URL",
783 help = "Chocolatey package source URL"
784 )]
785 pub package_source_url: Option<String>,
786 #[arg(
787 long = "choco-docs-url",
788 id = "choco_docs_url",
789 value_name = "URL",
790 help = "Chocolatey documentation URL"
791 )]
792 pub docs_url: Option<String>,
793 #[arg(
794 long = "choco-bug-tracker-url",
795 id = "choco_bug_tracker_url",
796 value_name = "URL",
797 help = "Chocolatey bug tracker URL"
798 )]
799 pub bug_tracker_url: Option<String>,
800 #[arg(
801 long = "choco-project-source-url",
802 id = "choco_project_source_url",
803 value_name = "URL",
804 help = "Chocolatey project source URL"
805 )]
806 pub project_source_url: Option<String>,
807 #[arg(
808 long = "choco-tags",
809 id = "choco_tags",
810 value_name = "TEXT",
811 help = "Chocolatey package tags"
812 )]
813 pub tags: Option<String>,
814 #[arg(
815 long = "choco-release-notes-url",
816 id = "choco_release_notes_url",
817 value_name = "URL",
818 help = "Chocolatey release notes URL"
819 )]
820 pub release_notes_url: Option<String>,
821 #[arg(
822 long = "choco-download-repo",
823 id = "choco_download_repo",
824 value_name = "OWNER/REPO",
825 help = "Codeberg/GitHub owner/repo for Chocolatey release downloads"
826 )]
827 pub download_repo: Option<String>,
828 #[arg(
829 long = "choco-archive-pattern",
830 id = "choco_archive_pattern",
831 value_name = "PATTERN",
832 help = "Filename pattern for Chocolatey release archives"
833 )]
834 pub archive_pattern: Option<String>,
835 #[arg(
836 long = "choco-push-source",
837 id = "choco_push_source",
838 value_name = "URL",
839 help = "Chocolatey push source URL"
840 )]
841 pub push_source: Option<String>,
842}
843
844impl ChocolateyOverridesArgs {
845 pub fn as_overrides(&self) -> crate::config::ChocolateyOverrides<'_> {
846 crate::config::ChocolateyOverrides {
847 name: self.name.as_deref(),
848 id: self.id.as_deref(),
849 title: self.title.as_deref(),
850 authors: self.authors.as_deref(),
851 description: self.description.as_deref(),
852 summary: self.summary.as_deref(),
853 project_url: self.project_url.as_deref(),
854 license_url: self.license_url.as_deref(),
855 icon_url: self.icon_url.as_deref(),
856 package_source_url: self.package_source_url.as_deref(),
857 docs_url: self.docs_url.as_deref(),
858 bug_tracker_url: self.bug_tracker_url.as_deref(),
859 project_source_url: self.project_source_url.as_deref(),
860 tags: self.tags.as_deref(),
861 release_notes_url: self.release_notes_url.as_deref(),
862 download_repo: self.download_repo.as_deref(),
863 archive_pattern: self.archive_pattern.as_deref(),
864 push_source: self.push_source.as_deref(),
865 }
866 }
867}
868
869#[derive(Debug, Default, Args)]
870pub struct ScoopOverridesArgs {
871 #[arg(
872 long = "scoop-name",
873 id = "scoop_name",
874 value_name = "NAME",
875 help = "Scoop manifest name"
876 )]
877 pub name: Option<String>,
878 #[arg(
879 long = "scoop-bucket",
880 id = "scoop_bucket",
881 value_name = "URL",
882 help = "Scoop bucket repo URL"
883 )]
884 pub bucket: Option<String>,
885 #[arg(
886 long = "scoop-description",
887 id = "scoop_description",
888 value_name = "TEXT",
889 help = "Scoop manifest description"
890 )]
891 pub description: Option<String>,
892 #[arg(
893 long = "scoop-homepage",
894 id = "scoop_homepage",
895 value_name = "URL",
896 help = "Scoop manifest homepage"
897 )]
898 pub homepage: Option<String>,
899 #[arg(
900 long = "scoop-license",
901 id = "scoop_license",
902 value_name = "SPDX",
903 help = "Scoop manifest license"
904 )]
905 pub license: Option<String>,
906 #[arg(
907 long = "scoop-download-repo",
908 id = "scoop_download_repo",
909 value_name = "OWNER/REPO",
910 help = "Codeberg/GitHub owner/repo for Scoop release downloads"
911 )]
912 pub download_repo: Option<String>,
913 #[arg(
914 long = "scoop-archive-pattern",
915 id = "scoop_archive_pattern",
916 value_name = "PATTERN",
917 help = "Filename pattern for Scoop release archives"
918 )]
919 pub archive_pattern: Option<String>,
920 #[arg(
921 long = "scoop-binary",
922 id = "scoop_binary",
923 value_name = "NAME",
924 help = "Binary to expose in the Scoop manifest; repeatable"
925 )]
926 pub binary: Vec<String>,
927 #[arg(
928 long = "scoop-no-arch",
929 id = "scoop_no_arch",
930 value_name = "KEY",
931 help = "Disable an architecture (x64|arm64); repeatable"
932 )]
933 pub no_arch: Vec<String>,
934}
935
936impl ScoopOverridesArgs {
937 pub fn as_overrides(&self) -> crate::config::ScoopOverrides<'_> {
938 crate::config::ScoopOverrides {
939 name: self.name.as_deref(),
940 bucket_url: self.bucket.as_deref(),
941 description: self.description.as_deref(),
942 homepage: self.homepage.as_deref(),
943 license: self.license.as_deref(),
944 download_repo: self.download_repo.as_deref(),
945 archive_pattern: self.archive_pattern.as_deref(),
946 binaries: Some(&self.binary),
947 disabled_architectures: &self.no_arch,
948 }
949 }
950}
951
952#[derive(Debug, Args)]
953pub struct InitHomebrewTapCommand {
954 #[arg(
955 long,
956 value_name = "DIR",
957 help = "Path to the tap repo to bootstrap (created if missing)"
958 )]
959 pub target: Utf8PathBuf,
960 #[arg(
961 long,
962 help = "Verify the existing Formula/<name>.rb matches the rendered template"
963 )]
964 pub check: bool,
965 #[arg(long, help = "Show a unified diff when --check finds drift")]
966 pub diff: bool,
967 #[arg(long, help = "Print the rendered formula without writing files")]
968 pub print: bool,
969 #[arg(long, help = "Skip `git init` and remote wiring (formula write only)")]
970 pub no_git: bool,
971 #[command(flatten)]
972 pub homebrew: HomebrewOverridesArgs,
973}
974
975#[derive(Debug, Args)]
976pub struct HomebrewCommand {
977 #[command(subcommand)]
978 pub action: HomebrewAction,
979}
980
981#[derive(Debug, Subcommand)]
982pub enum HomebrewAction {
983 #[command(about = "Render the formula to stdout or a file (no sha256)")]
984 Render(HomebrewRenderArgs),
985 #[command(about = "Render with real sha256 sums and write to a tap repo")]
986 Bump(HomebrewBumpArgs),
987}
988
989#[derive(Debug, Args)]
990pub struct HomebrewRenderArgs {
991 #[arg(
992 long,
993 value_name = "VERSION",
994 help = "Version to render (no leading 'v'). Defaults to cargo package.version"
995 )]
996 pub version: Option<String>,
997 #[arg(long, value_name = "PATH", help = "Write to PATH instead of stdout")]
998 pub output: Option<Utf8PathBuf>,
999 #[command(flatten)]
1000 pub homebrew: HomebrewOverridesArgs,
1001}
1002
1003#[derive(Debug, Args)]
1004pub struct HomebrewBumpArgs {
1005 #[arg(
1006 long,
1007 value_name = "VERSION",
1008 help = "Release version (no leading 'v')"
1009 )]
1010 pub version: String,
1011 #[arg(
1012 long,
1013 value_name = "DIR",
1014 help = "Tap repo directory (Formula/<name>.rb written here)"
1015 )]
1016 pub tap: Utf8PathBuf,
1017 #[arg(
1018 long,
1019 value_name = "PLATFORM=PATH",
1020 help = "Per-platform archive path for sha256 computation"
1021 )]
1022 pub archive: Vec<String>,
1023 #[arg(
1024 long,
1025 help = "After writing, run git add + commit + push. Requires git credentials"
1026 )]
1027 pub push: bool,
1028 #[arg(
1029 long,
1030 value_name = "MESSAGE",
1031 help = "Commit message (defaults to '<name> <version>')"
1032 )]
1033 pub commit_message: Option<String>,
1034 #[command(flatten)]
1035 pub homebrew: HomebrewOverridesArgs,
1036}
1037
1038#[derive(Debug, Args)]
1039pub struct InitScoopBucketCommand {
1040 #[arg(
1041 long,
1042 value_name = "DIR",
1043 help = "Path to the bucket repo to bootstrap (created if missing)"
1044 )]
1045 pub target: Utf8PathBuf,
1046 #[arg(
1047 long,
1048 help = "Verify the existing bucket/<name>.json matches the rendered template"
1049 )]
1050 pub check: bool,
1051 #[arg(long, help = "Show a unified diff when --check finds drift")]
1052 pub diff: bool,
1053 #[arg(long, help = "Print the rendered manifest without writing files")]
1054 pub print: bool,
1055 #[arg(long, help = "Skip `git init` and remote wiring (manifest write only)")]
1056 pub no_git: bool,
1057 #[command(flatten)]
1058 pub scoop: ScoopOverridesArgs,
1059}
1060
1061#[derive(Debug, Args)]
1062pub struct ScoopCommand {
1063 #[command(subcommand)]
1064 pub action: ScoopAction,
1065}
1066
1067#[derive(Debug, Subcommand)]
1068pub enum ScoopAction {
1069 #[command(about = "Render the manifest to stdout or a file with placeholder hashes")]
1070 Render(ScoopRenderArgs),
1071 #[command(about = "Render with real sha256 sums and write to a bucket repo")]
1072 Bump(ScoopBumpArgs),
1073}
1074
1075#[derive(Debug, Args)]
1076pub struct ScoopRenderArgs {
1077 #[arg(
1078 long,
1079 value_name = "VERSION",
1080 help = "Version to render (no leading 'v'). Defaults to cargo package.version"
1081 )]
1082 pub version: Option<String>,
1083 #[arg(long, value_name = "PATH", help = "Write to PATH instead of stdout")]
1084 pub output: Option<Utf8PathBuf>,
1085 #[command(flatten)]
1086 pub scoop: ScoopOverridesArgs,
1087}
1088
1089#[derive(Debug, Args)]
1090pub struct ScoopBumpArgs {
1091 #[arg(
1092 long,
1093 value_name = "VERSION",
1094 help = "Release version (no leading 'v')"
1095 )]
1096 pub version: String,
1097 #[arg(
1098 long,
1099 value_name = "DIR",
1100 help = "Bucket repo directory (bucket/<name>.json written here)"
1101 )]
1102 pub bucket: Option<Utf8PathBuf>,
1103 #[arg(
1104 long = "bucket-url",
1105 value_name = "URL",
1106 help = "Clone this bucket repo URL before writing when --bucket is omitted"
1107 )]
1108 pub bucket_url: Option<String>,
1109 #[arg(
1110 long = "bucket-token-env",
1111 value_name = "ENV",
1112 help = "Environment variable containing a token for authenticated HTTPS bucket pushes"
1113 )]
1114 pub bucket_token_env: Option<String>,
1115 #[arg(
1116 long = "work-dir",
1117 value_name = "DIR",
1118 help = "Working directory for cloned bucket repos (defaults to target/simit-scoop)"
1119 )]
1120 pub work_dir: Option<Utf8PathBuf>,
1121 #[arg(
1122 long,
1123 value_name = "ARCH=PATH",
1124 help = "Per-architecture archive path for sha256 computation"
1125 )]
1126 pub archive: Vec<String>,
1127 #[arg(
1128 long,
1129 help = "After writing, run git add + commit + push. Requires git credentials"
1130 )]
1131 pub push: bool,
1132 #[arg(
1133 long,
1134 value_name = "MESSAGE",
1135 help = "Commit message (defaults to '<name> <version>')"
1136 )]
1137 pub commit_message: Option<String>,
1138 #[arg(
1139 long,
1140 help = "Print the planned bucket update without writing or pushing"
1141 )]
1142 pub dry_run: bool,
1143 #[command(flatten)]
1144 pub scoop: ScoopOverridesArgs,
1145}
1146
1147#[derive(Debug, Args)]
1148pub struct WingetCommand {
1149 #[command(subcommand)]
1150 pub action: WingetAction,
1151}
1152
1153#[derive(Debug, Subcommand)]
1154pub enum WingetAction {
1155 #[command(about = "Submit a winget-pkgs manifest update with wingetcreate")]
1156 Submit(WingetSubmitArgs),
1157}
1158
1159#[derive(Debug, Args)]
1160pub struct WingetSubmitArgs {
1161 #[arg(
1162 long,
1163 value_name = "VERSION",
1164 help = "Release version (no leading 'v')"
1165 )]
1166 pub version: String,
1167 #[arg(
1168 long = "package-id",
1169 value_name = "ID",
1170 help = "Winget PackageIdentifier"
1171 )]
1172 pub package_id: Option<String>,
1173 #[arg(
1174 long = "download-repo",
1175 value_name = "OWNER/REPO",
1176 help = "Release download repo"
1177 )]
1178 pub download_repo: Option<String>,
1179 #[arg(
1180 long = "zip-archive",
1181 value_name = "NAME",
1182 help = "Windows zip filename or pattern containing {version}"
1183 )]
1184 pub zip_archive: Option<String>,
1185 #[arg(long = "url", value_name = "URL", help = "Installer URL to submit")]
1186 pub url: Option<String>,
1187 #[arg(
1188 long = "token-env",
1189 value_name = "ENV",
1190 help = "Environment variable containing the GitHub PAT"
1191 )]
1192 pub token_env: Option<String>,
1193 #[arg(
1194 long = "wingetcreate",
1195 value_name = "PATH",
1196 help = "wingetcreate executable path"
1197 )]
1198 pub wingetcreate: Option<Utf8PathBuf>,
1199 #[arg(
1200 long = "wingetcreate-url",
1201 value_name = "URL",
1202 help = "Download wingetcreate.exe from this URL instead of GitHub latest"
1203 )]
1204 pub wingetcreate_url: Option<String>,
1205 #[arg(
1206 long = "work-dir",
1207 value_name = "DIR",
1208 help = "Working directory for downloaded wingetcreate.exe"
1209 )]
1210 pub work_dir: Option<Utf8PathBuf>,
1211 #[arg(
1212 long = "wine",
1213 value_name = "PATH",
1214 default_value = "wine",
1215 help = "Wine executable used to run wingetcreate"
1216 )]
1217 pub wine: String,
1218 #[arg(long, help = "Print the wingetcreate command without running it")]
1219 pub dry_run: bool,
1220}
1221
1222#[derive(Debug, Args)]
1223pub struct WindowsCommand {
1224 #[command(subcommand)]
1225 pub action: WindowsAction,
1226}
1227
1228#[derive(Debug, Subcommand)]
1229pub enum WindowsAction {
1230 #[command(about = "Publish configured Windows package channels")]
1231 Publish(WindowsPublishArgs),
1232}
1233
1234#[derive(Debug, Args)]
1235pub struct WindowsPublishArgs {
1236 #[arg(
1237 long,
1238 value_name = "VERSION",
1239 help = "Release version (no leading 'v')"
1240 )]
1241 pub version: String,
1242 #[arg(
1243 long,
1244 value_name = "ARCH=PATH_OR_URL",
1245 help = "Per-architecture release archive; local paths are required for checksum-based channels"
1246 )]
1247 pub archive: Vec<String>,
1248 #[arg(
1249 long,
1250 value_name = "DIR",
1251 help = "Working directory for generated package state"
1252 )]
1253 pub work_dir: Utf8PathBuf,
1254 #[arg(long, help = "Publish Chocolatey")]
1255 pub chocolatey: bool,
1256 #[arg(long, help = "Publish Scoop")]
1257 pub scoop: bool,
1258 #[arg(long, help = "Submit Winget")]
1259 pub winget: bool,
1260 #[arg(long, help = "Run all configured Windows channels")]
1261 pub all: bool,
1262 #[arg(long, help = "Print planned work without writing or publishing")]
1263 pub dry_run: bool,
1264 #[arg(long, help = "Bypass Chocolatey duplicate-version protection")]
1265 pub force_resubmit: bool,
1266 #[arg(
1267 long = "choco-push-source",
1268 value_name = "URL",
1269 help = "Chocolatey push source override"
1270 )]
1271 pub choco_push_source: Option<String>,
1272 #[arg(
1273 long = "scoop-bucket-url",
1274 value_name = "URL",
1275 help = "Scoop bucket URL override"
1276 )]
1277 pub scoop_bucket_url: Option<String>,
1278 #[arg(
1279 long = "scoop-bucket-token-env",
1280 value_name = "ENV",
1281 help = "Scoop bucket token environment variable override"
1282 )]
1283 pub scoop_bucket_token_env: Option<String>,
1284}
1285
1286#[derive(Debug, Args)]
1287pub struct InitChocolateyCommand {
1288 #[arg(
1289 long,
1290 value_name = "DIR",
1291 help = "Path to the Chocolatey package directory to bootstrap"
1292 )]
1293 pub target: Utf8PathBuf,
1294 #[arg(long, help = "Verify the package files match the rendered template")]
1295 pub check: bool,
1296 #[arg(long, help = "Show a unified diff when --check finds drift")]
1297 pub diff: bool,
1298 #[arg(long, help = "Print the rendered package files without writing files")]
1299 pub print: bool,
1300 #[command(flatten)]
1301 pub chocolatey: ChocolateyOverridesArgs,
1302}
1303
1304#[derive(Debug, Args)]
1305pub struct ChocolateyCommand {
1306 #[command(subcommand)]
1307 pub action: ChocolateyAction,
1308}
1309
1310#[derive(Debug, Subcommand)]
1311pub enum ChocolateyAction {
1312 #[command(about = "Render package files to stdout or a directory (no sha256)")]
1313 Render(ChocolateyRenderArgs),
1314 #[command(about = "Render with real sha256 sums and optionally push")]
1315 Bump(ChocolateyBumpArgs),
1316}
1317
1318#[derive(Debug, Args)]
1319pub struct ChocolateyRenderArgs {
1320 #[arg(
1321 long,
1322 value_name = "VERSION",
1323 help = "Version to render (no leading 'v'). Defaults to cargo package.version"
1324 )]
1325 pub version: Option<String>,
1326 #[arg(long, value_name = "DIR", help = "Write package files under DIR")]
1327 pub output_dir: Option<Utf8PathBuf>,
1328 #[command(flatten)]
1329 pub chocolatey: ChocolateyOverridesArgs,
1330}
1331
1332#[derive(Debug, Args)]
1333pub struct ChocolateyBumpArgs {
1334 #[arg(
1335 long,
1336 value_name = "VERSION",
1337 help = "Release version (no leading 'v')"
1338 )]
1339 pub version: String,
1340 #[arg(
1341 long,
1342 value_name = "DIR",
1343 help = "Chocolatey package directory to update"
1344 )]
1345 pub package_dir: Utf8PathBuf,
1346 #[arg(
1347 long,
1348 value_name = "ARCH=PATH",
1349 help = "Per-architecture archive path for sha256 computation (x64 or x86)"
1350 )]
1351 pub archive: Vec<String>,
1352 #[arg(long, help = "After writing, run choco pack and choco push")]
1353 pub push: bool,
1354 #[arg(
1355 long,
1356 value_name = "URL",
1357 help = "Chocolatey push source URL (defaults to config or community feed)"
1358 )]
1359 pub push_source: Option<String>,
1360 #[arg(
1361 long,
1362 value_name = "ENV",
1363 help = "Environment variable containing the Chocolatey API key"
1364 )]
1365 pub api_key_env: Option<String>,
1366 #[arg(
1367 long,
1368 help = "Bypass Chocolatey duplicate-version protection before pushing"
1369 )]
1370 pub force_resubmit: bool,
1371 #[arg(long, help = "Print planned work without writing or pushing")]
1372 pub dry_run: bool,
1373 #[command(flatten)]
1374 pub chocolatey: ChocolateyOverridesArgs,
1375}
1376
1377#[derive(Debug, Args)]
1378pub struct AurOverridesArgs {
1379 #[arg(long = "aur-name", value_name = "NAME", help = "AUR pkgbase")]
1380 pub name: Option<String>,
1381 #[arg(
1382 long = "aur-description",
1383 value_name = "TEXT",
1384 help = "Package description (pkgdesc)"
1385 )]
1386 pub description: Option<String>,
1387 #[arg(long = "aur-url", value_name = "URL", help = "Project url")]
1388 pub url: Option<String>,
1389 #[arg(
1390 long = "aur-license",
1391 value_name = "SPDX",
1392 help = "SPDX license identifier"
1393 )]
1394 pub license: Option<String>,
1395 #[arg(
1396 long = "aur-download-repo",
1397 value_name = "OWNER/REPO",
1398 help = "Codeberg/GitHub owner/repo for release downloads"
1399 )]
1400 pub download_repo: Option<String>,
1401}
1402
1403impl AurOverridesArgs {
1404 pub fn as_overrides(&self) -> crate::config::AurOverrides<'_> {
1405 crate::config::AurOverrides {
1406 name: self.name.as_deref(),
1407 description: self.description.as_deref(),
1408 url: self.url.as_deref(),
1409 license: self.license.as_deref(),
1410 download_repo: self.download_repo.as_deref(),
1411 }
1412 }
1413}
1414
1415#[derive(Debug, Args)]
1416pub struct CoprOverridesArgs {
1417 #[arg(long = "copr-name", value_name = "NAME", help = "RPM package name")]
1418 pub name: Option<String>,
1419 #[arg(long = "copr-summary", value_name = "TEXT", help = "RPM Summary")]
1420 pub summary: Option<String>,
1421 #[arg(
1422 long = "copr-description",
1423 value_name = "TEXT",
1424 help = "RPM %description prose"
1425 )]
1426 pub description: Option<String>,
1427 #[arg(
1428 long = "copr-license",
1429 value_name = "SPDX",
1430 help = "SPDX license identifier"
1431 )]
1432 pub license: Option<String>,
1433 #[arg(long = "copr-url", value_name = "URL", help = "Project URL")]
1434 pub url: Option<String>,
1435 #[arg(
1436 long = "copr-download-repo",
1437 value_name = "OWNER/REPO",
1438 help = "Codeberg/GitHub owner/repo for the source archive"
1439 )]
1440 pub download_repo: Option<String>,
1441 #[arg(
1442 long = "copr-project",
1443 value_name = "OWNER/PROJECT",
1444 help = "COPR project for stable releases"
1445 )]
1446 pub project: Option<String>,
1447}
1448
1449impl CoprOverridesArgs {
1450 pub fn as_overrides(&self) -> crate::config::CoprOverrides<'_> {
1451 crate::config::CoprOverrides {
1452 name: self.name.as_deref(),
1453 summary: self.summary.as_deref(),
1454 description: self.description.as_deref(),
1455 license: self.license.as_deref(),
1456 url: self.url.as_deref(),
1457 download_repo: self.download_repo.as_deref(),
1458 project: self.project.as_deref(),
1459 }
1460 }
1461}
1462
1463#[derive(Debug, Args)]
1464pub struct AptOverridesArgs {
1465 #[arg(
1466 long = "apt-repo-url",
1467 value_name = "URL",
1468 help = "Git remote of the apt repository"
1469 )]
1470 pub repo_url: Option<String>,
1471 #[arg(
1472 long = "apt-label",
1473 value_name = "TEXT",
1474 help = "reprepro Origin/Label"
1475 )]
1476 pub label: Option<String>,
1477}
1478
1479impl AptOverridesArgs {
1480 pub fn as_overrides(&self) -> crate::config::AptOverrides<'_> {
1481 crate::config::AptOverrides {
1482 repo_url: self.repo_url.as_deref(),
1483 label: self.label.as_deref(),
1484 }
1485 }
1486}
1487
1488#[derive(Debug, Args)]
1489pub struct InitAurCommand {
1490 #[arg(
1491 long = "package",
1492 value_name = "NAME",
1493 help = "Workspace package providing metadata fallbacks"
1494 )]
1495 pub package: Option<String>,
1496 #[arg(long, help = "Verify dist/aur/*/PKGBUILD match the rendered template")]
1497 pub check: bool,
1498 #[arg(long, help = "Show a unified diff when --check finds drift")]
1499 pub diff: bool,
1500 #[arg(long, help = "Print the rendered PKGBUILDs without writing files")]
1501 pub print: bool,
1502 #[command(flatten)]
1503 pub aur: AurOverridesArgs,
1504}
1505
1506#[derive(Debug, Args)]
1507pub struct InitCoprCommand {
1508 #[arg(
1509 long = "package",
1510 value_name = "NAME",
1511 help = "Workspace package providing metadata fallbacks"
1512 )]
1513 pub package: Option<String>,
1514 #[arg(
1515 long,
1516 help = "Verify the spec and .copr/Makefile match the rendered template"
1517 )]
1518 pub check: bool,
1519 #[arg(long, help = "Show a unified diff when --check finds drift")]
1520 pub diff: bool,
1521 #[arg(long, help = "Print the rendered files without writing them")]
1522 pub print: bool,
1523 #[command(flatten)]
1524 pub copr: CoprOverridesArgs,
1525}
1526
1527#[derive(Debug, Args)]
1528pub struct InitAptCommand {
1529 #[arg(
1530 long = "package",
1531 value_name = "NAME",
1532 help = "Workspace package providing metadata fallbacks"
1533 )]
1534 pub package: Option<String>,
1535 #[arg(
1536 long,
1537 help = "Verify dist/apt/conf/distributions matches the rendered template"
1538 )]
1539 pub check: bool,
1540 #[arg(long, help = "Show a unified diff when --check finds drift")]
1541 pub diff: bool,
1542 #[arg(long, help = "Print the rendered config without writing files")]
1543 pub print: bool,
1544 #[command(flatten)]
1545 pub apt: AptOverridesArgs,
1546}
1547
1548#[derive(Debug, Args)]
1549pub struct InitReleaseCommand {
1550 #[arg(
1551 long = "package",
1552 value_name = "NAME",
1553 help = "Workspace package providing metadata fallbacks"
1554 )]
1555 pub package: Option<String>,
1556 #[arg(
1557 long,
1558 help = "Verify .forgejo/workflows/release.yml matches generated output"
1559 )]
1560 pub check: bool,
1561 #[arg(long, help = "Show a unified diff when --check finds drift")]
1562 pub diff: bool,
1563 #[arg(long, help = "Print the rendered workflow without writing files")]
1564 pub print: bool,
1565}
1566
1567#[derive(Debug, Args)]
1568pub struct AurCommand {
1569 #[command(subcommand)]
1570 pub action: AurAction,
1571}
1572
1573#[derive(Debug, Subcommand)]
1574pub enum AurAction {
1575 #[command(about = "Render PKGBUILDs to stdout for the given version")]
1576 Render(AurRenderArgs),
1577}
1578
1579#[derive(Debug, Args)]
1580pub struct AurRenderArgs {
1581 #[arg(
1582 long,
1583 value_name = "VERSION",
1584 help = "Version to render (no leading 'v')"
1585 )]
1586 pub version: Option<String>,
1587 #[arg(
1588 long = "package",
1589 value_name = "NAME",
1590 help = "Metadata-fallback package"
1591 )]
1592 pub package: Option<String>,
1593 #[command(flatten)]
1594 pub aur: AurOverridesArgs,
1595}
1596
1597#[derive(Debug, Args)]
1598pub struct CoprCommand {
1599 #[command(subcommand)]
1600 pub action: CoprAction,
1601}
1602
1603#[derive(Debug, Subcommand)]
1604pub enum CoprAction {
1605 #[command(about = "Render the RPM spec and .copr/Makefile to stdout")]
1606 Render(CoprRenderArgs),
1607}
1608
1609#[derive(Debug, Args)]
1610pub struct CoprRenderArgs {
1611 #[arg(
1612 long,
1613 value_name = "VERSION",
1614 help = "Version to render (no leading 'v')"
1615 )]
1616 pub version: Option<String>,
1617 #[arg(
1618 long = "package",
1619 value_name = "NAME",
1620 help = "Metadata-fallback package"
1621 )]
1622 pub package: Option<String>,
1623 #[command(flatten)]
1624 pub copr: CoprOverridesArgs,
1625}
1626
1627#[derive(Debug, Args)]
1628pub struct AptCommand {
1629 #[command(subcommand)]
1630 pub action: AptAction,
1631}
1632
1633#[derive(Debug, Subcommand)]
1634pub enum AptAction {
1635 #[command(about = "Render the reprepro distributions config to stdout")]
1636 Render(AptRenderArgs),
1637}
1638
1639#[derive(Debug, Args)]
1640pub struct AptRenderArgs {
1641 #[arg(
1642 long = "package",
1643 value_name = "NAME",
1644 help = "Metadata-fallback package"
1645 )]
1646 pub package: Option<String>,
1647 #[command(flatten)]
1648 pub apt: AptOverridesArgs,
1649}
1650
1651#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
1652pub enum Platform {
1653 #[value(help = "Forgejo Actions workflows under .forgejo/workflows")]
1654 Forgejo,
1655 #[value(help = "GitHub Actions workflows under .github/workflows")]
1656 Github,
1657}
1658
1659impl Platform {
1660 pub fn workflow_dir(self) -> &'static str {
1661 match self {
1662 Self::Forgejo => ".forgejo/workflows",
1663 Self::Github => ".github/workflows",
1664 }
1665 }
1666
1667 pub fn as_str(self) -> &'static str {
1668 match self {
1669 Self::Forgejo => "forgejo",
1670 Self::Github => "github",
1671 }
1672 }
1673}
1674
1675#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ValueEnum)]
1676#[serde(rename_all = "lowercase")]
1677pub enum RuntimeChoice {
1678 #[value(help = "Use simit's default runtime choice for the target platform")]
1679 Auto,
1680 #[value(help = "Use direct cargo commands and Rust toolchain setup")]
1681 Cargo,
1682 #[value(help = "Use nix develop and flake checks; requires flake.nix")]
1683 Nix,
1684}
1685
1686#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
1687#[serde(rename_all = "lowercase")]
1688pub enum Runtime {
1689 Cargo,
1690 Nix,
1691}
1692
1693#[derive(Debug, Args)]
1694pub struct InitFlakeCommand {
1695 #[arg(
1696 long,
1697 value_enum,
1698 help = "Generated ownership scope: hooks-only manages nix/pre-commit.nix; full also manages flake.nix and formatter wiring"
1699 )]
1700 pub scope: Option<FlakeScopeArg>,
1701 #[arg(long, help = "Verify flake and hook files match generated output")]
1702 pub check: bool,
1703 #[arg(
1704 long,
1705 help = "Print generated flake and hook files without writing them"
1706 )]
1707 pub print: bool,
1708 #[arg(long, help = "Show a unified diff when --check finds stale files")]
1709 pub diff: bool,
1710 #[arg(
1711 long,
1712 help = "Emit an rs-harbor multi-target flake that builds via rs-harbor.lib.mkCrossPackages"
1713 )]
1714 pub cross: bool,
1715 #[arg(
1716 long = "target",
1717 value_enum,
1718 value_name = "TARGET",
1719 requires = "cross",
1720 action = clap::ArgAction::Append,
1721 help = "Cross target to build; repeatable. Defaults to all targets when --cross is set without any --target"
1722 )]
1723 pub targets: Vec<FlakeTargetArg>,
1724}
1725
1726#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
1727pub enum FlakeScopeArg {
1728 HooksOnly,
1729 Full,
1730}
1731
1732#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
1733pub enum FlakeTargetArg {
1734 #[value(help = "Native craneLib.buildPackage for the host system")]
1735 Native,
1736 #[value(
1737 name = "aarch64-linux",
1738 help = "Cross-compile to aarch64-unknown-linux-gnu"
1739 )]
1740 Aarch64Linux,
1741 #[value(help = "Cross-compile to x86_64-pc-windows-gnu via mingw")]
1742 Windows,
1743 #[value(
1744 name = "darwin-x86_64",
1745 help = "Cross-compile to x86_64-apple-darwin via osxcross"
1746 )]
1747 DarwinX86_64,
1748 #[value(
1749 name = "darwin-aarch64",
1750 help = "Cross-compile to aarch64-apple-darwin via osxcross"
1751 )]
1752 DarwinAarch64,
1753}
1754
1755impl FlakeTargetArg {
1756 pub fn key(self) -> &'static str {
1758 match self {
1759 Self::Native => "native",
1760 Self::Aarch64Linux => "aarch64-linux",
1761 Self::Windows => "windows",
1762 Self::DarwinX86_64 => "darwin-x86_64",
1763 Self::DarwinAarch64 => "darwin-aarch64",
1764 }
1765 }
1766
1767 pub fn all() -> [Self; 5] {
1770 [
1771 Self::Native,
1772 Self::Aarch64Linux,
1773 Self::Windows,
1774 Self::DarwinX86_64,
1775 Self::DarwinAarch64,
1776 ]
1777 }
1778}
1779
1780#[derive(Debug, Args)]
1781pub struct ChangelogCommand {
1782 #[arg(
1783 long,
1784 default_value = "CHANGELOG.md",
1785 value_name = "PATH",
1786 help = "Path to the changelog file"
1787 )]
1788 pub file: Utf8PathBuf,
1789 #[command(subcommand)]
1790 pub action: ChangelogAction,
1791}
1792
1793#[derive(Debug, Args)]
1794pub struct HooksCommand {
1795 #[command(subcommand, help = "Hooks action to run")]
1796 pub action: HooksAction,
1797}
1798
1799#[derive(Debug, Subcommand)]
1800pub enum HooksAction {
1801 #[command(about = "Install pre-commit hooks into this repository")]
1802 Install(HooksInstallCommand),
1803}
1804
1805#[derive(Debug, Args)]
1806pub struct HooksInstallCommand {
1807 #[arg(long, help = "Verify installed hook wrappers without changing files")]
1808 pub check: bool,
1809 #[arg(long, help = "Show a unified diff for stale hook wrappers")]
1810 pub diff: bool,
1811 #[arg(
1812 long,
1813 help = "Unset rogue repo-local core.hooksPath values that shadow a friendly system dispatcher (no-op otherwise)"
1814 )]
1815 pub fix: bool,
1816}
1817
1818#[derive(Debug, Args)]
1819pub struct ConfigCommand {
1820 #[command(subcommand, help = "User config action to run")]
1821 pub action: ConfigAction,
1822}
1823
1824#[derive(Debug, Args)]
1825pub struct ProjectsCommand {
1826 #[command(subcommand, help = "Project registry action to run")]
1827 pub action: ProjectsAction,
1828}
1829
1830#[derive(Debug, Subcommand)]
1831pub enum ProjectsAction {
1832 #[command(about = "List registered projects")]
1833 List(ProjectsListArgs),
1834 #[command(about = "Show one registered project's feature status")]
1835 Show(ProjectsShowArgs),
1836 #[command(about = "Re-run feature detection across registered projects")]
1837 Scan(ProjectsScanArgs),
1838 #[command(about = "Discover Rust workspaces under a filesystem subtree")]
1839 Discover(ProjectsDiscoverArgs),
1840 #[command(about = "Forget one registered project")]
1841 Forget(ProjectsForgetArgs),
1842 #[command(about = "Remove registered projects whose paths no longer exist")]
1843 Prune(ProjectsPruneArgs),
1844 #[command(about = "Clear all per-user project registry state")]
1845 ClearState(ProjectsClearStateArgs),
1846}
1847
1848#[derive(Debug, Args)]
1849pub struct ProjectsListArgs {
1850 #[arg(
1851 long = "feature",
1852 value_name = "NAME[=STATUS]",
1853 action = clap::ArgAction::Append,
1854 help = "Filter to projects where feature NAME has STATUS, or any non-absent status"
1855 )]
1856 pub features: Vec<String>,
1857 #[arg(
1858 long = "include-ephemeral",
1859 help = "Include ephemeral /tmp project paths in the listing"
1860 )]
1861 pub include_ephemeral: bool,
1862 #[arg(long, help = "Print a machine-readable JSON array")]
1863 pub json: bool,
1864 #[arg(
1865 long,
1866 conflicts_with = "no_issues",
1867 help = "Mark projects with drift, hook conflicts, or missing paths and print an issues footer"
1868 )]
1869 pub issues: bool,
1870 #[arg(
1871 long = "no-issues",
1872 help = "Suppress issue markers and footer in human output"
1873 )]
1874 pub no_issues: bool,
1875 #[arg(
1876 long,
1877 value_enum,
1878 value_name = "KEY",
1879 default_value = "last-seen",
1880 help = "Sort by name, path, last-seen, or first-seen"
1881 )]
1882 pub sort: ProjectsSort,
1883}
1884
1885#[derive(Debug, Args)]
1886pub struct ProjectsShowArgs {
1887 #[arg(
1888 value_name = "PATH",
1889 help = "Project path; defaults to the current workspace root"
1890 )]
1891 pub path: Option<Utf8PathBuf>,
1892 #[arg(long, help = "Print machine-readable JSON")]
1893 pub json: bool,
1894 #[arg(
1895 long,
1896 conflicts_with = "no_issues",
1897 help = "Print an attention header for drift, hook conflicts, or missing paths"
1898 )]
1899 pub issues: bool,
1900 #[arg(long = "no-issues", help = "Suppress the attention header")]
1901 pub no_issues: bool,
1902}
1903
1904#[derive(Debug, Args)]
1905pub struct ProjectsScanArgs {
1906 #[arg(long, help = "Also drop entries whose paths no longer exist")]
1907 pub prune: bool,
1908 #[arg(long, help = "Print intended changes without writing the registry")]
1909 pub dry_run: bool,
1910}
1911
1912#[derive(Debug, Args)]
1913pub struct ProjectsDiscoverArgs {
1914 #[arg(
1915 value_name = "ROOT",
1916 help = "Filesystem subtree to walk; defaults to the current directory"
1917 )]
1918 pub root: Option<Utf8PathBuf>,
1919 #[arg(
1920 long,
1921 help = "Walk and report discoverable projects without writing the registry"
1922 )]
1923 pub dry_run: bool,
1924 #[arg(long, help = "Print the discovery report as machine-readable JSON")]
1925 pub json: bool,
1926 #[arg(
1927 long,
1928 value_name = "NAME",
1929 action = clap::ArgAction::Append,
1930 help = "Also skip directories with this basename, in addition to built-in skips; may be repeated"
1931 )]
1932 pub skip: Vec<String>,
1933 #[arg(
1934 long,
1935 value_name = "N",
1936 help = "Maximum recursion depth from ROOT; defaults to 8"
1937 )]
1938 pub max_depth: Option<usize>,
1939 #[arg(long, help = "Follow symlinked directories during discovery")]
1940 pub follow_symlinks: bool,
1941 #[arg(
1942 long,
1943 help = "Register Cargo workspaces even when no simit features are detected"
1944 )]
1945 pub include_empty: bool,
1946}
1947
1948#[derive(Debug, Args)]
1949pub struct ProjectsForgetArgs {
1950 #[arg(value_name = "PATH", help = "Project path to remove from the registry")]
1951 pub path: Utf8PathBuf,
1952}
1953
1954#[derive(Debug, Args)]
1955pub struct ProjectsPruneArgs {
1956 #[arg(long, help = "Print stale entries without writing the registry")]
1957 pub dry_run: bool,
1958}
1959
1960#[derive(Debug, Args)]
1961pub struct ProjectsClearStateArgs {
1962 #[arg(
1963 long,
1964 help = "Print the registry path that would be cleared without writing"
1965 )]
1966 pub dry_run: bool,
1967}
1968
1969#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
1970pub enum ProjectsSort {
1971 Name,
1972 Path,
1973 #[value(name = "last-seen")]
1974 LastSeen,
1975 #[value(name = "first-seen")]
1976 FirstSeen,
1977}
1978
1979#[derive(Debug, Subcommand)]
1980pub enum ConfigAction {
1981 #[command(about = "Print the resolved user config path")]
1982 Path,
1983 #[command(about = "Print the normalized user config")]
1984 Show,
1985 #[command(about = "Validate the user config")]
1986 Check,
1987 #[command(about = "Write a starter user config if one does not exist")]
1988 Init,
1989}
1990
1991#[derive(Debug, Subcommand)]
1992pub enum ChangelogAction {
1993 #[command(about = "Create a Keep a Changelog skeleton")]
1994 Init,
1995 #[command(about = "Add an entry under [Unreleased]")]
1996 Add {
1997 #[arg(value_enum, value_name = "KIND", help = "Entry kind to append")]
1998 kind: ChangelogEntryKind,
1999 #[arg(value_name = "TEXT", help = "Entry text to add")]
2000 text: String,
2001 },
2002 #[command(about = "Promote [Unreleased] into a dated release section")]
2003 Release {
2004 #[arg(value_name = "VERSION", help = "Release version to create")]
2005 version: Version,
2006 #[arg(
2007 long,
2008 value_name = "YYYY-MM-DD",
2009 help = "Override the release date instead of using today in UTC"
2010 )]
2011 date: Option<String>,
2012 #[arg(
2013 long = "repo-url",
2014 value_name = "URL",
2015 help = "Repository URL used for compare links"
2016 )]
2017 repo_url: Option<String>,
2018 },
2019 #[command(about = "Validate that the file matches simit's Keep a Changelog rules")]
2020 Check,
2021 #[command(about = "Print the body of one changelog section")]
2022 Show {
2023 #[arg(
2024 value_name = "VERSION",
2025 help = "Version to print; omit to show [Unreleased]"
2026 )]
2027 version: Option<String>,
2028 },
2029}
2030
2031#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
2032pub enum ChangelogEntryKind {
2033 Added,
2034 Changed,
2035 Deprecated,
2036 Removed,
2037 Fixed,
2038 Security,
2039}
2040
2041#[derive(Debug, Args)]
2042pub struct CompletionsCommand {
2043 #[arg(
2044 value_enum,
2045 value_name = "SHELL",
2046 help = "Shell to generate completions for"
2047 )]
2048 pub shell: clap_complete::Shell,
2049}
2050
2051#[derive(Debug, Args)]
2052pub struct ManCommand {}