Skip to main content

anodizer_core/config/
mod.rs

1use std::collections::{BTreeMap, HashMap};
2use std::path::PathBuf;
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7// ---------------------------------------------------------------------------
8// Include specification types
9// ---------------------------------------------------------------------------
10
11/// An include specification: either a plain path string or a structured from_file/from_url.
12///
13/// YAML examples:
14/// ```yaml
15/// includes:
16///   - ./defaults.yaml                           # plain string (backward compat)
17///   - from_file:
18///       path: ./config/release.yaml              # structured file path
19///   - from_url:
20///       url: https://example.com/config.yaml     # URL fetch
21///       headers:
22///         x-api-token: "${MYCOMPANY_TOKEN}"       # env var expansion in headers
23/// ```
24#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
25#[serde(untagged)]
26pub enum IncludeSpec {
27    /// Plain string path (backward compatible): "path/to/file.yaml"
28    Path(String),
29    /// Structured file include with `from_file.path`.
30    FromFile { from_file: IncludeFilePath },
31    /// Structured URL include with `from_url.url` and optional headers.
32    FromUrl { from_url: IncludeUrlConfig },
33}
34
35/// File path for a structured include.
36#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
37#[serde(deny_unknown_fields)]
38pub struct IncludeFilePath {
39    /// Path to the include file (relative to the config file).
40    pub path: String,
41}
42
43/// URL configuration for a structured include.
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
45#[serde(deny_unknown_fields)]
46pub struct IncludeUrlConfig {
47    /// URL to fetch. If it does not start with `http://` or `https://`,
48    /// `https://raw.githubusercontent.com/` is prepended (GitHub shorthand).
49    pub url: String,
50    /// Optional HTTP headers. Values support `${VAR_NAME}` environment variable expansion.
51    pub headers: Option<HashMap<String, String>>,
52}
53
54// ---------------------------------------------------------------------------
55// Top-level config
56// ---------------------------------------------------------------------------
57
58/// `deny_unknown_fields` rejects typos and unknown config
59/// fields at parse time (strict YAML unmarshalling).
60#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
61#[serde(default, deny_unknown_fields)]
62pub struct Config {
63    /// Schema version. Currently supports 1 (implicit default) and 2.
64    pub version: Option<u32>,
65    /// Human-readable project name used in templates and release titles.
66    pub project_name: String,
67    /// Output directory for build artifacts (default: ./dist).
68    #[serde(default = "default_dist")]
69    pub dist: PathBuf,
70    /// Additional config files to merge into this config.
71    /// Supports plain string paths, `from_file:` for structured file paths,
72    /// and `from_url:` for fetching configs from URLs with optional headers.
73    pub includes: Option<Vec<IncludeSpec>>,
74    /// Environment file configuration. Accepts either:
75    /// - A list of `.env` file paths: `[".env", ".release.env"]`
76    /// - A struct with token file paths: `{ github_token: "~/.config/goreleaser/github_token" }`
77    pub env_files: Option<EnvFilesConfig>,
78    /// Default values applied to all crates unless overridden.
79    pub defaults: Option<Defaults>,
80    /// Hooks run before the release pipeline starts.
81    ///
82    /// Use `--skip=before` to bypass — one token covers this block and every
83    /// `crates[].before:` block.
84    pub before: Option<HooksConfig>,
85    /// Hooks run after the release pipeline completes SUCCESSFULLY.
86    ///
87    /// A failed run never reaches them — route failure handling through
88    /// `on_error:`, and teardown that must happen either way through
89    /// `always:`.
90    ///
91    /// Use `--skip=after` to bypass — one token covers this block and every
92    /// `crates[].after:` block.
93    ///
94    /// ```yaml
95    /// after:
96    ///   hooks:
97    ///     - cmd: ./notify-release-succeeded.sh
98    /// ```
99    pub after: Option<HooksConfig>,
100    /// Hooks run when the release pipeline fails at ANY stage (build,
101    /// sign, publish, ...). The pipeline holds on failure: published state
102    /// is left exactly where the failed run put it, so `{{ .RolledBack }}`
103    /// is always `false`. Recover by re-running the identical command
104    /// (publishers reconcile against what already shipped and skip it), or
105    /// withdraw the release deliberately with `anodizer tag rollback`.
106    ///
107    /// Notification / cleanup hooks: a hook's own failure is logged as a
108    /// warning and never masks the pipeline error. The failure context is
109    /// exposed both as template vars (`{{ .Error }}`, `{{ .RolledBack }}`)
110    /// and as `ANODIZER_*` env vars (`ANODIZER_ERROR`,
111    /// `ANODIZER_ROLLED_BACK`, `ANODIZER_VERSION`, `ANODIZER_TAG`) so
112    /// hooks can consume the error text without shell interpolation.
113    ///
114    /// Use `--skip=on-error` to bypass — one token covers this block and
115    /// every `publish.on_error:` block.
116    ///
117    /// ```yaml
118    /// on_error:
119    ///   hooks:
120    ///     - cmd: ./notify-release-failed.sh
121    /// ```
122    pub on_error: Option<HooksConfig>,
123    /// Hooks run LAST on every terminal path — the release's `finally`.
124    ///
125    /// Ordering: on success they run after `after:`; on failure they run
126    /// after `on_error:`. They also fire on the one exit neither of those
127    /// reaches — a `before:` hook that failed before the pipeline started.
128    /// Use them for teardown that has to happen either way: removing a
129    /// staging directory, releasing a lock, stopping a sidecar container.
130    ///
131    /// They fire once per `anodizer release` / `anodizer build` invocation,
132    /// pairing 1:1 with `before:` — including on each `--split` shard and on
133    /// `--merge`, which are separate invocations that each run `before:` of
134    /// their own.
135    ///
136    /// Use `--skip=always` to bypass. Skipping the `finally` lane is
137    /// supported on purpose — `--skip=before` already suppresses the lane it
138    /// pairs with — but the consequence is that teardown does not run, so
139    /// anything the run staged stays staged.
140    ///
141    /// The run's outcome is exposed as template vars (`{{ .Success }}`,
142    /// `{{ .Error }}`) and as `ANODIZER_*` env vars (`ANODIZER_SUCCESS`,
143    /// `ANODIZER_ERROR`, `ANODIZER_VERSION`, `ANODIZER_TAG`), so a hook can
144    /// branch on the outcome and read the error text without interpolating
145    /// untrusted text into the shell command. `ANODIZER_ERROR` is empty on
146    /// success.
147    ///
148    /// A failing `always:` hook never masks a release failure: on the
149    /// failure path it is logged as a warning and the original pipeline
150    /// error is still what the run exits with. On the success path there is
151    /// no error to mask, so the hook's own failure fails the run — the same
152    /// contract `after:` has.
153    ///
154    /// ```yaml
155    /// always:
156    ///   hooks:
157    ///     - cmd: ./teardown-staging.sh
158    /// ```
159    pub always: Option<HooksConfig>,
160    /// Hooks run after build/archive/sign/sbom/checksum complete but
161    /// immediately before the publish phase dispatches any publisher.
162    ///
163    /// Use cases: smoke-test artifacts against the staged dist tree,
164    /// run external validators (antivirus, vulnerability scanners),
165    /// stage external state, or abort the release before any
166    /// publisher writes to a registry.
167    ///
168    /// A non-zero exit code from any hook aborts the release before
169    /// publish runs. Hooks fire in declared order. Use `--skip=before-publish`
170    /// to bypass.
171    pub before_publish: Option<HooksConfig>,
172    /// List of crates in this project.
173    pub crates: Vec<CrateConfig>,
174    /// Changelog generation configuration.
175    pub changelog: Option<ChangelogConfig>,
176    /// Signing configurations for binaries, archives, and checksums.
177    #[serde(default, deserialize_with = "deserialize_signs")]
178    #[schemars(schema_with = "signs_schema")]
179    pub signs: Vec<SignConfig>,
180    /// Binary-specific signing configs (same shape as `signs` but only for
181    /// binary artifacts). The `artifacts` field on each entry is constrained
182    /// at parse time to `binary` / `none` (or omitted) — a broader filter on
183    /// `binary_signs` would silently match nothing because the loop only
184    /// iterates Binary artifacts. Constraint lives in `deserialize_binary_signs`.
185    ///
186    /// The detached signature and certificate produced for each binary upload
187    /// as release assets alongside the archives, named after the archive built
188    /// from that binary: a `tar.gz` archive `app-1.2.3-linux-amd64.tar.gz`
189    /// gives `app-1.2.3-linux-amd64.sig`. The raw binary's own file name is
190    /// the same under every target, so it is not the asset name.
191    #[serde(default, deserialize_with = "deserialize_binary_signs")]
192    #[schemars(schema_with = "signs_schema")]
193    pub binary_signs: Vec<SignConfig>,
194    /// Docker image signing configurations.
195    pub docker_signs: Option<Vec<DockerSignConfig>>,
196    // No `alias` attribute needed: unlike `signs`/`sign`, "upx" is already
197    // both singular and plural, so a separate alias adds no value.
198    /// UPX binary compression configurations.
199    #[serde(default, deserialize_with = "deserialize_upx")]
200    #[schemars(schema_with = "upx_schema")]
201    pub upx: Vec<UpxConfig>,
202    /// Snapshot release configuration (local/non-tag builds).
203    pub snapshot: Option<SnapshotConfig>,
204    /// Nightly release configuration.
205    pub nightly: Option<NightlyConfig>,
206    /// Announcement configuration (Slack, Discord, email, etc.).
207    pub announce: Option<AnnounceConfig>,
208    /// When true, log artifact file sizes after building.
209    pub report_sizes: Option<bool>,
210    /// Environment variables available to all template expressions.
211    ///
212    /// List of `KEY=VALUE` strings:
213    /// `env: ["MY_VAR=hello", "DEPLOY_ENV=staging"]`. Order is preserved so
214    /// chained env applications (sign + sbom + notarize) see entries in
215    /// declared order. Values are rendered through the template engine before
216    /// being set, so expressions like `{{ Tag }}` or `{{ Date }}` are
217    /// expanded.
218    #[serde(default)]
219    pub env: Option<Vec<String>>,
220    /// Custom template variables accessible as `{{ Var.<key> }}` in templates.
221    /// Provides a way to define reusable values, especially useful with config includes.
222    ///
223    /// Stored as a `BTreeMap` so rendering iterates in deterministic
224    /// (sorted) key order — without this guarantee, a value that references
225    /// another variable (`b: "{{ Var.a }}_v2"`) could render before its
226    /// dependency on a different process / host. The current resolver is
227    /// single-pass (one render per value), so cross-variable references
228    /// only resolve when the referenced key sorts earlier.
229    pub variables: Option<BTreeMap<String, String>>,
230    /// Generic artifact publisher configurations.
231    pub publishers: Option<Vec<PublisherConfig>>,
232    /// DockerHub description sync configurations.
233    pub dockerhub: Option<Vec<DockerHubConfig>>,
234    /// Artifactory upload configurations.
235    pub artifactories: Option<Vec<ArtifactoryConfig>>,
236    /// CloudSmith publisher configurations.
237    pub cloudsmiths: Option<Vec<CloudSmithConfig>>,
238    /// Top-level Homebrew Cask configurations.
239    /// `homebrew_casks` is a top-level array with its own
240    /// repository, commit_author, directory, skip_upload, hooks, dependencies,
241    /// conflicts, completions, manpages, structured uninstall/zap, etc.
242    pub homebrew_casks: Option<Vec<HomebrewCaskConfig>>,
243    /// Repo-committed files that embed the release version outside
244    /// `Cargo.toml` (e.g. a Helm `Chart.yaml`, an install doc, a README
245    /// badge), given as repo-root-relative path strings. At `tag` time each
246    /// listed file has its occurrences of the old version rewritten to the new
247    /// version — both the bare (`0.1.0`) and `v`-prefixed (`v0.1.0`) forms,
248    /// word-boundary anchored — and is staged into the same bump commit as
249    /// `Cargo.toml` / `Cargo.lock`, so these files never drift from the manifest.
250    ///
251    /// An entry may instead be a `path` + `match` mapping, scoping the rewrite
252    /// to the occurrences the `match` regex selects (`{version}` stands for the
253    /// version being rewritten) so two crates can share one file.
254    ///
255    /// ```yaml
256    /// version_files:
257    ///   - charts/cfgd/Chart.yaml
258    ///   - docs/installation.md
259    ///   - path: chart/cfgd/values.yaml
260    ///     match: 'operator:\s+image:.*:v{version}'
261    /// ```
262    pub version_files: Option<Vec<VersionFileEntry>>,
263    /// Automatic semantic version tagging configuration.
264    pub tag: Option<TagConfig>,
265    /// Git-level tag discovery and sorting settings.
266    pub git: Option<GitConfig>,
267    /// Partial/split build configuration for fan-out CI pipelines.
268    pub partial: Option<PartialConfig>,
269    /// Independent workspace roots in a monorepo.
270    pub workspaces: Option<Vec<WorkspaceConfig>>,
271    /// Source archive configuration.
272    pub source: Option<SourceConfig>,
273    /// Software bill of materials (SBOM) generation configurations.
274    #[serde(default, deserialize_with = "deserialize_sboms")]
275    #[schemars(schema_with = "sboms_schema")]
276    pub sboms: Vec<SbomConfig>,
277    /// SLSA build-provenance / attestation configuration for binaries and
278    /// archives. In the default `subjects` mode, anodizer writes a subjects
279    /// manifest for `actions/attest-build-provenance`; in `emit` mode it
280    /// generates and signs a self-contained in-toto SLSA provenance statement.
281    /// When omitted (or `enabled: false`), the attestation stage is a no-op.
282    pub attestations: Option<AttestationConfig>,
283    /// GitHub release configuration shared by all crates.
284    pub release: Option<ReleaseConfig>,
285    /// Custom GitHub API/upload/download URLs for GitHub Enterprise installations.
286    pub github_urls: Option<GitHubUrlsConfig>,
287    /// Custom GitLab API/download URLs for self-hosted GitLab installations.
288    pub gitlab_urls: Option<GitLabUrlsConfig>,
289    /// Custom Gitea API/download URLs for self-hosted Gitea installations.
290    pub gitea_urls: Option<GiteaUrlsConfig>,
291    /// Force a specific token type for authentication.
292    /// When set, overrides automatic token detection from environment variables.
293    pub force_token: Option<ForceTokenKind>,
294    /// macOS code signing and notarization configuration.
295    pub notarize: Option<NotarizeConfig>,
296    /// Project metadata configuration (applied to metadata.json output files).
297    pub metadata: Option<MetadataConfig>,
298    /// Template files to render and include as release artifacts.
299    /// File contents are processed through the template engine.
300    pub template_files: Option<Vec<TemplateFileConfig>>,
301    /// Monorepo configuration.
302    /// When configured, tag discovery filters by tag_prefix and the working
303    /// directory is scoped to dir.
304    pub monorepo: Option<MonorepoConfig>,
305    /// Makeself self-extracting archive configurations.
306    #[serde(default, deserialize_with = "deserialize_makeselfs")]
307    #[schemars(schema_with = "makeselfs_schema")]
308    pub makeselfs: Vec<MakeselfConfig>,
309    /// `curl | sh` installer-script configurations. Each entry emits a
310    /// deterministic POSIX `install.sh` release asset that detects the host
311    /// OS + arch, downloads and sha256-verifies the matching archive, and
312    /// installs the binary.
313    #[serde(default, deserialize_with = "deserialize_install_scripts")]
314    #[schemars(schema_with = "install_scripts_schema")]
315    pub install_scripts: Vec<InstallScriptConfig>,
316    /// AppImage configurations. Each entry bundles a built Linux binary plus
317    /// its desktop integration into a single self-contained `.AppImage` via
318    /// linuxdeploy.
319    #[serde(default, deserialize_with = "deserialize_appimages")]
320    #[schemars(schema_with = "appimages_schema")]
321    pub appimages: Vec<AppImageConfig>,
322    /// Opt-in post-release verification gate. Runs LAST (after the release is
323    /// created and every publisher has run) and REPORTS post-publish defects —
324    /// missing assets, failed install smoke-tests, glibc-ceiling violations.
325    /// Because it runs after the irreversible publish, a failure exits
326    /// non-zero to flag CI but never undoes the release. Off unless
327    /// `verify_release.enabled: true`.
328    #[serde(default)]
329    pub verify_release: VerifyReleaseConfig,
330    /// Pre-publish preflight tuning. `preflight.strict: true` promotes
331    /// indeterminate probe outcomes (5xx / rate-limit / network failure /
332    /// undeterminable permissions) from warnings to hard blockers. The
333    /// probes themselves always run read-only before any publisher mutates
334    /// a registry; the default (lenient) behavior needs no config.
335    #[serde(default)]
336    pub preflight: PreflightConfig,
337    /// Source RPM configuration. Renamed from `srpm:` (singular) for spelling
338    /// parity with `Defaults.srpms` and the rest of the plural-name packaging
339    /// fields. The `srpm:` spelling is still accepted via serde alias for
340    /// back-compat.
341    #[serde(alias = "srpm")]
342    pub srpms: Option<SrpmConfig>,
343    /// Milestone closing configurations.
344    pub milestones: Option<Vec<MilestoneConfig>>,
345    /// Generic HTTP upload configurations.
346    pub uploads: Option<Vec<UploadConfig>>,
347    /// AUR source package publishing configurations (source-only PKGBUILD, not -bin).
348    pub aur_sources: Option<Vec<AurSourceConfig>>,
349    /// Top-level retry configuration applied to network-bound operations
350    /// (announcers, git providers, HTTP uploads, docker pipes). When omitted,
351    /// `RetryConfig::default()` is used (10 attempts, 10s base, 5m cap —
352    /// the project-level retry policy).
353    pub retry: Option<RetryConfig>,
354    /// MCP (Model Context Protocol) server registry publishing
355    /// configuration. When `name` is empty (the default), the publisher is
356    /// skipped. The `mcp:` publisher block.
357    #[serde(default)]
358    pub mcp: McpConfig,
359    /// SchemaStore publisher. Registers the project's JSON Schema(s) on
360    /// SchemaStore at release time. When `schemas` is empty (the default),
361    /// the publisher is skipped. The `schemastore:` publisher block.
362    #[serde(default)]
363    pub schemastore: crate::config::publishers::SchemastoreConfig,
364    /// NPM package registry publishing configurations. One entry per
365    /// published package. In the default `optional-deps` mode anodizer emits
366    /// npm's native per-platform packages (biome / git-cliff pattern); in
367    /// `postinstall` mode it emits a download shim (the `npms:`
368    /// parity).
369    pub npms: Option<Vec<NpmConfig>>,
370    /// GemFury (fury.io) deb/rpm/apk publishing configurations. Mirrors
371    /// The `gemfury:` block. The legacy spelling
372    /// `furies:` is accepted via serde alias; a one-time deprecation
373    /// warning is emitted by [`warn_on_legacy_furies_alias`].
374    #[serde(alias = "furies")]
375    pub gemfury: Option<Vec<GemFuryConfig>>,
376    /// PyPI publishing configurations. One entry per published project.
377    /// Emits native `py3-none-<platform>` binary wheels from the built
378    /// binaries (plus an optional `maturin sdist`) and uploads them via
379    /// PyPI's legacy (twine-protocol) upload API. The `pypis:` block.
380    pub pypis: Option<Vec<PypiConfig>>,
381    /// homebrew-core formula-bump configurations. One entry per formula.
382    /// Bumps an existing formula in `Homebrew/homebrew-core` (or a formula
383    /// repository override) via the GitHub API and opens a pull request.
384    /// The `homebrew_cores:` block.
385    pub homebrew_cores: Option<Vec<HomebrewCoreConfig>>,
386    /// Per-crate metadata derived from each crate's `Cargo.toml [package]`
387    /// table (description / license / homepage / authors). Populated at
388    /// config-load time by [`Config::populate_derived_metadata`], keyed by
389    /// crate name. NOT a user-facing YAML field — it backs the
390    /// crate-aware `meta_*_for` accessors so a plain Rust project gets its
391    /// publisher metadata without repeating it in a top-level `metadata:`
392    /// block. A hand-written `metadata:` field and per-publisher overrides
393    /// still win.
394    #[serde(skip)]
395    #[schemars(skip)]
396    pub derived_metadata: BTreeMap<String, MetadataConfig>,
397    /// The tag family [`Config::populate_derived_tag_templates`] filled into
398    /// every crate that omitted `tag_template`, when it filled at least one.
399    /// NOT a user-facing YAML field — it lets the config loader report the
400    /// derivation, since a filled crate is indistinguishable from one the
401    /// operator wrote by hand once the fold has run.
402    #[serde(skip)]
403    #[schemars(skip)]
404    pub derived_tag_template: Option<String>,
405}
406
407/// Helper schema function for the signs field (accepts object or array).
408fn signs_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
409    let mut schema = generator.subschema_for::<Vec<SignConfig>>();
410    schema.ensure_object().insert(
411        "description".to_owned(),
412        "Artifact signing configurations (cosign, GPG, etc.). Accepts a single object or array."
413            .into(),
414    );
415    schema
416}
417
418/// Helper schema function for the upx field (accepts object or array).
419fn upx_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
420    let mut schema = generator.subschema_for::<Vec<UpxConfig>>();
421    schema.ensure_object().insert(
422        "description".to_owned(),
423        "UPX binary compression configurations. Accepts a single object or array.".into(),
424    );
425    schema
426}
427
428/// Helper schema function for the sboms field (accepts object or array).
429fn sboms_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
430    let mut schema = generator.subschema_for::<Vec<SbomConfig>>();
431    schema.ensure_object().insert(
432        "description".to_owned(),
433        "SBOM generation configurations. Accepts a single object or array.".into(),
434    );
435    schema
436}
437
438fn default_dist() -> PathBuf {
439    PathBuf::from("./dist")
440}
441
442impl Default for Config {
443    fn default() -> Self {
444        Config {
445            version: None,
446            project_name: String::new(),
447            dist: default_dist(),
448            includes: None,
449            env_files: None,
450            defaults: None,
451            before: None,
452            after: None,
453            on_error: None,
454            always: None,
455            before_publish: None,
456            crates: Vec::new(),
457            changelog: None,
458            signs: Vec::new(),
459            binary_signs: Vec::new(),
460            docker_signs: None,
461            upx: Vec::new(),
462            snapshot: None,
463            nightly: None,
464            announce: None,
465            report_sizes: None,
466            env: None,
467            variables: None,
468            publishers: None,
469            dockerhub: None,
470            artifactories: None,
471            cloudsmiths: None,
472            homebrew_casks: None,
473            version_files: None,
474            tag: None,
475            git: None,
476            partial: None,
477            workspaces: None,
478            source: None,
479            sboms: Vec::new(),
480            attestations: None,
481            release: None,
482            github_urls: None,
483            gitlab_urls: None,
484            gitea_urls: None,
485            force_token: None,
486            notarize: None,
487            metadata: None,
488            template_files: None,
489            monorepo: None,
490            makeselfs: Vec::new(),
491            install_scripts: Vec::new(),
492            appimages: Vec::new(),
493            verify_release: VerifyReleaseConfig::default(),
494            preflight: PreflightConfig::default(),
495            srpms: None,
496            milestones: None,
497            uploads: None,
498            aur_sources: None,
499            retry: None,
500            mcp: McpConfig::default(),
501            schemastore: crate::config::publishers::SchemastoreConfig::default(),
502            npms: None,
503            gemfury: None,
504            pypis: None,
505            homebrew_cores: None,
506            derived_metadata: BTreeMap::new(),
507            derived_tag_template: None,
508        }
509    }
510}
511
512mod accessors;
513pub use accessors::crate_is_selected;
514
515mod schema;
516pub use schema::*;
517
518/// Run a deserialization closure on a worker thread sized large enough that
519/// the `Config` derive (60+ `Option<NestedStruct>` fields) cannot exhaust
520/// the host's main-thread stack.
521///
522/// Background: debug builds of `serde_yaml_ng::from_value::<Config>` and
523/// `toml::from_str::<Config>` consume several MiB of stack because each
524/// generated visitor branch for the giant struct lives in a single
525/// monomorphised frame and debug builds neither inline nor tail-call. The
526/// Windows main-thread default reservation is 1 MiB, so any debug-built
527/// integration test that triggers full-config deserialization overflows
528/// before reaching the visitor's body.
529///
530/// Routing every full-`Config` deserialization through this helper keeps
531/// every entry-point platform-agnostic without resorting to per-platform
532/// linker flags or `RUST_MIN_STACK`.
533pub fn deserialize_on_worker<F, T>(f: F) -> anyhow::Result<T>
534where
535    F: FnOnce() -> anyhow::Result<T> + Send + 'static,
536    T: Send + 'static,
537{
538    use anyhow::Context as _;
539
540    // 8 MiB matches the Linux/macOS process default and comfortably exceeds
541    // the ~2 MiB peak observed for debug `Config` deserialization.
542    const WORKER_STACK_SIZE: usize = 8 * 1024 * 1024;
543
544    let handle = std::thread::Builder::new()
545        .stack_size(WORKER_STACK_SIZE)
546        .name("anodizer-config-deserialize".to_string())
547        .spawn(f)
548        .context("failed to spawn config deserialization worker thread")?;
549    match handle.join() {
550        Ok(result) => result,
551        Err(payload) => std::panic::resume_unwind(payload),
552    }
553}
554
555mod validate;
556pub use validate::*;
557
558mod publish_axis;
559pub(crate) use publish_axis::*;
560
561mod legacy;
562pub use legacy::*;
563
564// ---------------------------------------------------------------------------
565// EnvFilesConfig — accepts list of .env paths OR structured token file paths
566// ---------------------------------------------------------------------------
567
568mod env_files;
569pub use env_files::*;
570
571// ---------------------------------------------------------------------------
572// VersionFileEntry — accepts a bare path OR a path scoped by a `match` anchor
573// ---------------------------------------------------------------------------
574
575mod version_files;
576pub use version_files::*;
577
578// ---------------------------------------------------------------------------
579// Defaults
580// ---------------------------------------------------------------------------
581
582mod defaults;
583pub use defaults::*;
584
585// ---------------------------------------------------------------------------
586// BuildIgnore — exclude specific os/arch combos from builds
587// ---------------------------------------------------------------------------
588
589mod build;
590pub use build::*;
591
592// ---------------------------------------------------------------------------
593// ArchivesConfig — untagged enum: false => Disabled, array => Configs
594// ---------------------------------------------------------------------------
595
596mod archives;
597pub use archives::*;
598
599mod completions;
600pub use completions::*;
601
602// ---------------------------------------------------------------------------
603// ReleaseConfig
604// ---------------------------------------------------------------------------
605
606mod release;
607pub use release::*;
608
609// ---------------------------------------------------------------------------
610// Shared publisher config types: RepositoryConfig, CommitAuthorConfig
611// ---------------------------------------------------------------------------
612
613mod publishers;
614pub use publishers::*;
615
616// ---------------------------------------------------------------------------
617// DockerV2Config
618// ---------------------------------------------------------------------------
619
620mod docker;
621pub use docker::*;
622
623// ---------------------------------------------------------------------------
624// NfpmConfig
625// ---------------------------------------------------------------------------
626
627mod nfpm;
628pub use nfpm::*;
629
630// ---------------------------------------------------------------------------
631// SnapcraftConfig
632// ---------------------------------------------------------------------------
633
634mod snapcraft;
635pub use snapcraft::*;
636// ---------------------------------------------------------------------------
637// DmgConfig / MsiConfig / PkgConfig / NsisConfig / AppBundleConfig / FlatpakConfig
638// ---------------------------------------------------------------------------
639
640mod installers;
641pub use installers::*;
642
643// ---------------------------------------------------------------------------
644// BlobConfig (S3/GCS/Azure cloud storage)
645// ---------------------------------------------------------------------------
646
647mod blob;
648pub use blob::*;
649
650// ---------------------------------------------------------------------------
651// PartialConfig (split/merge CI fan-out)
652// ---------------------------------------------------------------------------
653
654mod partial;
655pub use partial::*;
656
657// ---------------------------------------------------------------------------
658// BinstallConfig
659// ---------------------------------------------------------------------------
660
661mod binstall;
662pub use binstall::*;
663
664// ---------------------------------------------------------------------------
665// NotarizeConfig (macOS code signing and notarization)
666// ---------------------------------------------------------------------------
667
668mod notarize;
669pub use notarize::*;
670// ---------------------------------------------------------------------------
671// SourceConfig
672// ---------------------------------------------------------------------------
673
674mod source;
675pub use source::*;
676
677// ---------------------------------------------------------------------------
678// SbomConfig
679// ---------------------------------------------------------------------------
680
681mod sbom;
682pub use sbom::*;
683
684// ---------------------------------------------------------------------------
685// AttestationConfig
686// ---------------------------------------------------------------------------
687
688mod attestation;
689pub use attestation::*;
690
691// ---------------------------------------------------------------------------
692// VersionSyncConfig
693// ---------------------------------------------------------------------------
694
695mod version_sync;
696pub use version_sync::*;
697
698// ---------------------------------------------------------------------------
699// ChangelogConfig
700// ---------------------------------------------------------------------------
701
702mod changelog;
703pub use changelog::*;
704// ---------------------------------------------------------------------------
705// SignConfig / DockerSignConfig — lifted to `crate::signing`
706// ---------------------------------------------------------------------------
707//
708// see `crate::signing` for the type definitions. The
709// re-exports below preserve the historical
710// `anodizer_core::config::{SignConfig, DockerSignConfig}` import paths
711// used by every stage that consumes a sign config.
712
713pub use crate::signing::{AuthenticodeConfig, DockerSignConfig, SignConfig, SignVerifyConfig};
714
715// ---------------------------------------------------------------------------
716// UpxConfig
717// ---------------------------------------------------------------------------
718
719mod upx;
720pub use upx::*;
721
722// ---------------------------------------------------------------------------
723// SnapshotConfig
724// ---------------------------------------------------------------------------
725
726mod snapshot_nightly;
727pub use snapshot_nightly::*;
728
729mod cargo_metadata;
730pub use cargo_metadata::{
731    derive_metadata_from_cargo_toml, package_version, workspace_package_version,
732};
733
734mod workspace_deps;
735pub use workspace_deps::{
736    derive_depends_on_from_cargo_toml, discover_cargo_workspace_member_names,
737    extract_workspace_deps,
738};
739
740/// Extract the name portion of a `"Name <email>"` maintainer/author string,
741/// dropping any `<…>` email suffix. Returns `None` when the result is empty
742/// (e.g. a bare-email `<ada@example.com>`), so a derived Vendor / OCI `vendor`
743/// value is never emitted blank.
744pub fn maintainer_name_only(maintainer: &str) -> Option<String> {
745    let name = maintainer.split('<').next().unwrap_or(maintainer).trim();
746    (!name.is_empty()).then(|| name.to_string())
747}
748
749// ---------------------------------------------------------------------------
750// TemplateFileConfig
751// ---------------------------------------------------------------------------
752
753mod templatefiles;
754pub use templatefiles::*;
755
756// ---------------------------------------------------------------------------
757// AnnounceConfig
758// ---------------------------------------------------------------------------
759mod announce;
760pub use announce::*;
761// ---------------------------------------------------------------------------
762// DockerHub description sync
763// ---------------------------------------------------------------------------
764
765mod dockerhub;
766pub use dockerhub::*;
767
768// ---------------------------------------------------------------------------
769// Artifactory publisher
770// ---------------------------------------------------------------------------
771
772mod artifactory;
773pub use artifactory::*;
774
775// ---------------------------------------------------------------------------
776// CloudSmith publisher
777// ---------------------------------------------------------------------------
778
779mod cloudsmith;
780pub use cloudsmith::*;
781
782// ---------------------------------------------------------------------------
783// PublisherConfig
784// ---------------------------------------------------------------------------
785
786mod publisher;
787pub use publisher::*;
788
789// ---------------------------------------------------------------------------
790// HooksConfig
791// ---------------------------------------------------------------------------
792
793mod hooks;
794pub use hooks::*;
795
796// ---------------------------------------------------------------------------
797// GitConfig
798// ---------------------------------------------------------------------------
799
800mod git_config;
801pub use git_config::*;
802
803// ---------------------------------------------------------------------------
804// MonorepoConfig
805// ---------------------------------------------------------------------------
806
807mod monorepo;
808pub use monorepo::*;
809
810// ---------------------------------------------------------------------------
811// TagConfig
812// ---------------------------------------------------------------------------
813
814mod tag;
815pub use tag::*;
816
817// ---------------------------------------------------------------------------
818// WorkspaceConfig
819// ---------------------------------------------------------------------------
820
821mod workspace;
822pub use workspace::*;
823
824// ---------------------------------------------------------------------------
825// RetryConfig (top-level `retry:` block — bridges to crate::retry::RetryPolicy)
826// ---------------------------------------------------------------------------
827
828mod retry;
829pub use retry::*;
830
831// ---------------------------------------------------------------------------
832// PostPublishPollConfig (per-publisher post-publish polling)
833// ---------------------------------------------------------------------------
834
835mod post_publish_poll;
836pub use post_publish_poll::*;
837
838// ---------------------------------------------------------------------------
839// VerifyReleaseConfig (top-level `verify_release:` post-publish gate)
840// ---------------------------------------------------------------------------
841
842mod verify_release;
843pub use verify_release::*;
844
845// ---------------------------------------------------------------------------
846// PreflightConfig (top-level `preflight:` pre-publish probe tuning)
847// ---------------------------------------------------------------------------
848
849mod preflight;
850pub use preflight::*;
851
852// ---------------------------------------------------------------------------
853// StringOrBool — accepts bool or template string in YAML
854// ---------------------------------------------------------------------------
855
856mod string_or_bool;
857pub use string_or_bool::*;
858
859// ---------------------------------------------------------------------------
860// MakeselfConfig + SrpmConfig — lifted to `crate::packagers`
861// ---------------------------------------------------------------------------
862//
863// All packaging config types live in their own modules under
864// `crate::packagers`. The re-exports below preserve the historical
865// `anodizer_core::config::{MakeselfConfig, MakeselfFile, SrpmConfig}`
866// import paths used by stages and tests.
867
868pub use crate::packagers::{
869    AppImageConfig, AppImageExtra, InstallScriptConfig, MakeselfConfig, MakeselfFile,
870    RuntimeHarvest, SrpmConfig,
871};
872pub(crate) use crate::packagers::{
873    appimages_schema, deserialize_appimages, deserialize_install_scripts, deserialize_makeselfs,
874    install_scripts_schema, makeselfs_schema,
875};
876
877// ---------------------------------------------------------------------------
878// MilestoneConfig
879// ---------------------------------------------------------------------------
880
881mod milestone;
882pub use milestone::*;
883
884// ---------------------------------------------------------------------------
885// UploadConfig (generic HTTP upload)
886// ---------------------------------------------------------------------------
887
888mod upload;
889pub use upload::*;
890
891// ---------------------------------------------------------------------------
892// AurSourceConfig
893// ---------------------------------------------------------------------------
894
895mod aur_source;
896pub use aur_source::*;
897
898// ---------------------------------------------------------------------------
899// McpConfig (MCP registry publisher)
900// ---------------------------------------------------------------------------
901
902mod mcp;
903pub use mcp::*;
904
905// ---------------------------------------------------------------------------
906// NpmConfig (NPM package registry publisher)
907// ---------------------------------------------------------------------------
908
909mod npm;
910pub use npm::*;
911
912// ---------------------------------------------------------------------------
913// GemFuryConfig (Gemfury / fury.io publisher)
914// ---------------------------------------------------------------------------
915
916mod gemfury;
917pub use gemfury::*;
918
919// ---------------------------------------------------------------------------
920// PypiConfig (PyPI binary-wheel publisher)
921// ---------------------------------------------------------------------------
922
923mod pypi;
924pub use pypi::*;
925
926// ---------------------------------------------------------------------------
927// HomebrewCoreConfig (homebrew-core formula-bump publisher)
928// ---------------------------------------------------------------------------
929
930mod homebrew_core;
931pub use homebrew_core::*;
932
933// ---------------------------------------------------------------------------
934// Well-known config file discovery
935// ---------------------------------------------------------------------------
936
937mod discovery;
938pub use discovery::*;
939
940// ---------------------------------------------------------------------------
941// Tests
942// ---------------------------------------------------------------------------
943
944#[cfg(test)]
945mod tests;