Add a Forgejo release source #5

Merged
schaefera merged 5 commits from worktree-forgejo-source into master 2026-09-20 09:20:00 +00:00
Collaborator

Why

Item 1 of making pkgwatch install itself from its own Forgejo releases: pkgwatch could only track GitHub-hosted projects. This lets a package be checked, fetched and verified against a Forgejo instance's releases API, so code.austinschaefer.com/schaefera/pkgwatch can be tracked like any other package.

What

  • Config (src/config.rs): a source key, following the schema already sketched in docs/SPEC.md. github-release is the default when omitted, so existing packages.d/*.toml files are unchanged. forgejo-release also requires a base_url. Validation runs once at load and rejects: a Forgejo source with no base_url, a base_url on a GitHub source (a mistyped source would otherwise be silently ignored), a URL without http(s), and github-attestation on a Forgejo source (gh only speaks GitHub).

    [package.mytool]
    source = "forgejo-release"
    base_url = "https://code.austinschaefer.com"
    repo = "schaefera/mytool"
    asset_pattern = "mytool-linux-x86_64.tar.gz"
    
    [package.mytool.verification]
    method = "same-origin-sha256"
    checksum_asset_pattern = "SHA256SUMS"
    
  • Polymorphic release sources, in one module (src/release_source/): a ReleaseSource trait (latest_release plus the releases api root), with each part in its own file: contract.rs (the trait), github.rs (GithubEndpoints, the existing Atom-feed check, moved unchanged) and forgejo.rs (ForgejoEndpoints, new). The submodules are private and mod.rs re-exports their types, so the rest of the crate imports everything from crate::release_source and never names a host's file. mod.rs also holds for_package(&Package) -> Result<Box<dyn ReleaseSource>>, the one place that knows which hosts exist. Per-host logic no longer accumulates in checker.rs, which keeps only version_from_tag, and adding a host doesn't touch the existing ones. Named release_source rather than source because source reads like source code next to the config key.

  • Forgejo check: one call to releases/latest, which already returns only the newest non-draft, non-prerelease release, so it doesn't need GitHub's confirm-each-tag workaround.

  • Fetcher and verifier: take the releases API root instead of a GitHub-specific type. GitHub and Forgejo serve the same releases/tags/<tag> endpoint and JSON shape, so these needed no per-host logic.

  • Pipeline: the host is resolved per package and passed as &dyn ReleaseSource; a bad one fails only that package.

  • Docs: the SPEC documents source, the trait and module, and why the HTTP is hand-rolled rather than using an API-client crate (octocrab is async against our blocking reqwest; forgejo-api is a generated binding of the whole API for one endpoint; the GitHub check uses an Atom feed no crate covers). Revisit if pkgwatch needs authenticated or write API calls. ARCHITECTURE.md is updated for the new boundary.

Testing

cargo fmt --check, cargo clippy --all-targets -- -D warnings and the complexity lint are clean, and cargo check --release passes. 117 tests pass in 20 of 20 runs: config parsing and each validation rule, the factory, both hosts' latest_release (including the Forgejo no-releases 404 and server-error cases), and an end-to-end Forgejo pipeline test that fails verification, proving check, fetch and verify all ran through Forgejo.

Also verified against a real Forgejo: forgejo/forgejo on code.forgejo.org, using scratch dirs. pkgwatch checked, downloaded the 122 MB release binary and passed same-origin sha256 verification (tier 4). That run stopped before build/publish, and predates the trait refactor and module move, which moved code without changing behaviour.

Not exercised live: this repo's own releases/latest currently returns 404 because it has no releases yet. The Forgejo source turns that into a clear "no published release found" error, covered by a test.

Review

Self-reviews by a single agent: the first found no bugs (docs, wording and a duplicate test helper were addressed), and a second on the trait refactor confirmed the moved code is line-for-line identical with no tests lost. Its remaining nit, naming the implementors GithubEndpoints/ForgejoEndpoints even though they are now full sources, was left as is to keep the diff small. The later module grouping and rename were pure moves covered by the compiler and the full test suite.

Next

Item 2: a Forgejo Actions release workflow that builds the binary and publishes it with SHA256SUMS on a v* tag (the first tag will be v0.1.0, matching Cargo.toml), plus a --version flag. Then item 3, packages.d/pkgwatch.toml.

🤖 Generated with Claude Code

## Why Item 1 of making pkgwatch install itself from its own Forgejo releases: pkgwatch could only track GitHub-hosted projects. This lets a package be checked, fetched and verified against a Forgejo instance's releases API, so `code.austinschaefer.com/schaefera/pkgwatch` can be tracked like any other package. ## What - **Config** (`src/config.rs`): a `source` key, following the schema already sketched in `docs/SPEC.md`. `github-release` is the default when omitted, so existing `packages.d/*.toml` files are unchanged. `forgejo-release` also requires a `base_url`. Validation runs once at load and rejects: a Forgejo source with no `base_url`, a `base_url` on a GitHub source (a mistyped `source` would otherwise be silently ignored), a URL without http(s), and `github-attestation` on a Forgejo source (`gh` only speaks GitHub). ```toml [package.mytool] source = "forgejo-release" base_url = "https://code.austinschaefer.com" repo = "schaefera/mytool" asset_pattern = "mytool-linux-x86_64.tar.gz" [package.mytool.verification] method = "same-origin-sha256" checksum_asset_pattern = "SHA256SUMS" ``` - **Polymorphic release sources, in one module** (`src/release_source/`): a `ReleaseSource` trait (`latest_release` plus the releases `api` root), with each part in its own file: `contract.rs` (the trait), `github.rs` (`GithubEndpoints`, the existing Atom-feed check, moved unchanged) and `forgejo.rs` (`ForgejoEndpoints`, new). The submodules are private and `mod.rs` re-exports their types, so the rest of the crate imports everything from `crate::release_source` and never names a host's file. `mod.rs` also holds `for_package(&Package) -> Result<Box<dyn ReleaseSource>>`, the one place that knows which hosts exist. Per-host logic no longer accumulates in `checker.rs`, which keeps only `version_from_tag`, and adding a host doesn't touch the existing ones. Named `release_source` rather than `source` because `source` reads like source code next to the config key. - **Forgejo check**: one call to `releases/latest`, which already returns only the newest non-draft, non-prerelease release, so it doesn't need GitHub's confirm-each-tag workaround. - **Fetcher and verifier**: take the releases API root instead of a GitHub-specific type. GitHub and Forgejo serve the same `releases/tags/<tag>` endpoint and JSON shape, so these needed no per-host logic. - **Pipeline**: the host is resolved per package and passed as `&dyn ReleaseSource`; a bad one fails only that package. - **Docs**: the SPEC documents `source`, the trait and module, and why the HTTP is hand-rolled rather than using an API-client crate (`octocrab` is async against our blocking `reqwest`; `forgejo-api` is a generated binding of the whole API for one endpoint; the GitHub check uses an Atom feed no crate covers). Revisit if pkgwatch needs authenticated or write API calls. `ARCHITECTURE.md` is updated for the new boundary. ## Testing `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings` and the complexity lint are clean, and `cargo check --release` passes. 117 tests pass in 20 of 20 runs: config parsing and each validation rule, the factory, both hosts' `latest_release` (including the Forgejo no-releases 404 and server-error cases), and an end-to-end Forgejo pipeline test that fails verification, proving check, fetch and verify all ran through Forgejo. Also verified against a real Forgejo: `forgejo/forgejo` on `code.forgejo.org`, using scratch dirs. pkgwatch checked, downloaded the 122 MB release binary and passed same-origin sha256 verification (tier 4). That run stopped before build/publish, and predates the trait refactor and module move, which moved code without changing behaviour. Not exercised live: this repo's own `releases/latest` currently returns 404 because it has no releases yet. The Forgejo source turns that into a clear "no published release found" error, covered by a test. ## Review Self-reviews by a single agent: the first found no bugs (docs, wording and a duplicate test helper were addressed), and a second on the trait refactor confirmed the moved code is line-for-line identical with no tests lost. Its remaining nit, naming the implementors `GithubEndpoints`/`ForgejoEndpoints` even though they are now full sources, was left as is to keep the diff small. The later module grouping and rename were pure moves covered by the compiler and the full test suite. ## Next Item 2: a Forgejo Actions release workflow that builds the binary and publishes it with `SHA256SUMS` on a `v*` tag (the first tag will be `v0.1.0`, matching `Cargo.toml`), plus a `--version` flag. Then item 3, `packages.d/pkgwatch.toml`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 2 commits 2026-09-20 08:41:20 +00:00
Packages can now declare source = "forgejo-release" plus a base_url and be
checked, fetched and verified against a Forgejo instance's releases API,
alongside the existing GitHub source. This is what lets pkgwatch track its
own releases from the self-hosted Forgejo.

- config: Source enum (github-release default, forgejo-release) + base_url,
  validated once at load (base_url pairing, http(s) scheme, and no
  github-attestation on a Forgejo source).
- source: new module mapping a package to its Endpoints.
- checker: latest_forgejo_release, one call to releases/latest; latest_release
  dispatches per source.
- fetcher/verifier: take the releases API root instead of GithubEndpoints,
  since GitHub and Forgejo serve the same releases/tags/<tag> shape.
- pipeline: endpoints are resolved per package.
- docs: SPEC documents the source key and why the HTTP is hand-rolled rather
  than an API-client crate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Apply self-review feedback on the Forgejo source
All checks were successful
CI / build (pull_request) Successful in 36s
CI / test (pull_request) Successful in 2m35s
CI / audit (pull_request) Successful in 10s
CI / coverage (pull_request) Successful in 5m8s
e25ff8f6da
Fix stale ARCHITECTURE/config docs, soften source.rs's overclaim, trim the
SPEC's crate paragraph (no brittle counts), and merge the duplicate
same-origin Package test helper into test_support.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
schaefera approved these changes 2026-09-20 08:55:07 +00:00
Dismissed
schaefera added 1 commit 2026-09-20 08:58:29 +00:00
Make release sources polymorphic via a ReleaseSource trait
All checks were successful
CI / build (pull_request) Successful in 35s
CI / test (pull_request) Successful in 2m27s
CI / audit (pull_request) Successful in 10s
CI / coverage (pull_request) Successful in 5m8s
ba2c5c2d02
Replaces the Endpoints enum and checker's per-host dispatch: checker.rs now
holds only the ReleaseSource trait (latest release + API root), each host
implements it in its own module (github.rs, forgejo.rs), and source.rs is a
factory returning a Box<dyn ReleaseSource> per package. Adding a host no
longer touches existing ones, and the pipeline only sees the trait.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
schaefera dismissed schaefera's review 2026-09-20 08:58:29 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

schaefera added 1 commit 2026-09-20 09:08:28 +00:00
Group release sources into a source module with re-exports
All checks were successful
CI / build (pull_request) Successful in 35s
CI / test (pull_request) Successful in 2m26s
CI / audit (pull_request) Successful in 13s
CI / coverage (pull_request) Successful in 6m11s
6459856aab
Move the ReleaseSource trait, GithubEndpoints and ForgejoEndpoints under
src/source/, each in its own file (release_source.rs, github.rs,
forgejo.rs). The submodules are private; mod.rs re-exports their types and
holds for_package, so the rest of the crate imports from crate::source and
never names a host's file. checker.rs keeps only version_from_tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
schaefera added 1 commit 2026-09-20 09:10:03 +00:00
Rename the source module to release_source
All checks were successful
CI / build (pull_request) Successful in 37s
CI / test (pull_request) Successful in 2m41s
CI / audit (pull_request) Successful in 11s
CI / coverage (pull_request) Successful in 4m38s
984c11066f
'source' read like source code next to the config's source key. The trait
file becomes contract.rs to avoid release_source::release_source, and the
pipeline's local variables become 'host'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
schaefera scheduled this pull request to auto merge when all checks succeed 2026-09-20 09:11:25 +00:00
schaefera approved these changes 2026-09-20 09:12:06 +00:00
schaefera scheduled this pull request to auto merge when all checks succeed 2026-09-20 09:13:27 +00:00
schaefera merged commit 34ef7be4e7 into master 2026-09-20 09:20:00 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: schaefera/pkgwatch#5
No description provided.