Set up project tooling to match the rest of ~/dev's Rust fleet

Surveyed sporah/doubleo7/feedsignal/uy-immigration-watcher/notif-picker
for conventions and replicated the current dominant pattern rather than
inventing a new one:

- Forgejo CI (.forgejo/workflows/ci.yml): build/test/audit jobs on the
  rust-ci runner label, cargo+sccache caching, cargo fmt --check, cargo
  clippy --all-targets -- -D warnings, cargo audit. Matches
  sporah/doubleo7/feedsignal/uy-immigration-watcher; notif-picker's
  docker-label/manual-toolchain-install variant looks like an earlier
  iteration superseded by this one.
- Makefile.toml with format/format-check/lint/test/audit/build tasks and
  a `ci` task chaining them — copied from notif-picker's clean version,
  the only project that had this pattern. `cargo make ci` now runs the
  same checks locally that CI runs.
- Explicit empty [workspace] in Cargo.toml (doubleo7's pattern) so a
  nested git-worktree checkout can't accidentally inherit an ancestor
  directory's workspace manifest.
- rustfmt: no rustfmt.toml, matching every sibling project — default
  style is the established convention here, not an oversight.

New for this fleet, since nothing else in ~/dev has it: a git-native
pre-commit hook (.githooks/pre-commit, activated via `cargo make
install-hooks` / `git config core.hooksPath .githooks`) that runs `cargo
fmt` and re-stages whatever it reformats. Chose git's native hooksPath
over the pre-commit(.com) framework or cargo-husky — no extra runtime
dependency, hook is tracked and shareable, and nothing else here needs
Python. Kept to formatting only; clippy/audit stay in CI, which already
covers them and can run heavier checks than a commit hook should.

Fixed one clippy finding (useless format! in checker.rs) and reformatted
the existing code to match the now-enforced default rustfmt style.
`cargo make ci` passes clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2FEut5tVMNjeVjqhgVZbr
This commit is contained in:
Austin Schaefer 2026-09-11 10:13:29 +02:00
parent bf46cbf073
commit 388096264b
8 changed files with 149 additions and 13 deletions

80
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,80 @@
name: CI
on:
push:
branches: [master]
pull_request:
jobs:
build:
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Cache sccache compilation objects
uses: actions/cache@v4
with:
path: /root/.cache/sccache
# Not keyed to Cargo.lock: sccache caches individual compiler
# invocations by content hash, so it should accumulate across
# dependency bumps rather than reset like the target/ cache above.
key: sccache-${{ runner.os }}-${{ github.run_id }}
restore-keys: |
sccache-${{ runner.os }}-
- name: Format check
run: cargo fmt --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Check release profile compiles
run: cargo check --release
test:
needs: build
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Cache sccache compilation objects
uses: actions/cache@v4
with:
path: /root/.cache/sccache
key: sccache-${{ runner.os }}-${{ github.run_id }}
restore-keys: |
sccache-${{ runner.os }}-
- name: Test
run: cargo test
audit:
needs: test
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
- name: cargo audit
run: cargo audit

15
.githooks/pre-commit Executable file
View file

@ -0,0 +1,15 @@
#!/bin/sh
# Auto-formats the project before every commit, using rustfmt's default
# style (no rustfmt.toml override — matches every sibling project in
# ~/dev). Tracked here rather than living only in .git/hooks/ so it's
# shareable across clones; activate once per clone with
# `cargo make install-hooks` (or `git config core.hooksPath .githooks`).
#
# Reformats the whole tree, not just staged files — simplest option for a
# single-person repo. Re-stages whatever rustfmt touched so the formatted
# version is what actually gets committed.
set -e
cargo fmt
git diff --name-only --diff-filter=M -- '*.rs' | xargs -r git add

View file

@ -3,6 +3,12 @@ name = "pkgwatch"
version = "0.1.0"
edition = "2024"
# Not part of a Cargo workspace — this crate is the whole repo. Declared
# explicitly (rather than just omitting it) so that checking this repo out
# as a nested git worktree can't accidentally pick up an ancestor
# directory's workspace manifest.
[workspace]
[dependencies]
anyhow = "1.0.104"
hex = "0.4.3"

37
Makefile.toml Normal file
View file

@ -0,0 +1,37 @@
[config]
default_to_workspace = false
[tasks.format]
command = "cargo"
args = ["fmt"]
[tasks.format-check]
command = "cargo"
args = ["fmt", "--check"]
[tasks.lint]
command = "cargo"
args = ["clippy", "--all-targets", "--", "-D", "warnings"]
[tasks.test]
command = "cargo"
args = ["test"]
[tasks.audit]
command = "cargo"
args = ["audit"]
[tasks.build]
command = "cargo"
args = ["build", "--release"]
[tasks.install-hooks]
description = "One-time setup: point git at the tracked hooks in .githooks/"
command = "git"
args = ["config", "core.hooksPath", ".githooks"]
[tasks.ci]
dependencies = ["format-check", "lint", "test", "audit"]
[tasks.default]
alias = "ci"

View file

@ -11,8 +11,7 @@ pub fn latest_github_release(client: &reqwest::blocking::Client, repo: &str) ->
let url = format!("https://github.com/{repo}/releases.atom");
let body = client.get(&url).send()?.error_for_status()?.text()?;
let pattern = format!(r#"releases/tag/([^"]+)""#);
let re = Regex::new(&pattern)?;
let re = Regex::new(r#"releases/tag/([^"]+)""#)?;
match re.captures(&body) {
Some(caps) => Ok(caps[1].to_string()),
None => bail!("no release tag found in {url}"),

View file

@ -46,8 +46,8 @@ pub fn load_packages_dir(dir: &Path) -> Result<Vec<(String, Package)>> {
}
let text = std::fs::read_to_string(&path)
.with_context(|| format!("reading {}", path.display()))?;
let file: PackageFile = toml::from_str(&text)
.with_context(|| format!("parsing {}", path.display()))?;
let file: PackageFile =
toml::from_str(&text).with_context(|| format!("parsing {}", path.display()))?;
out.extend(file.package);
}
Ok(out)

View file

@ -65,7 +65,9 @@ fn main() -> Result<()> {
}
(_, true) => {
println!(" tier 4-6 pass: flagging for human review, not auto-publishing");
println!(" (review-queue persistence not yet implemented — this is where it plugs in)");
println!(
" (review-queue persistence not yet implemented — this is where it plugs in)"
);
}
(_, false) => {
println!(" verification failed — not publishing, not updating state");

View file

@ -26,13 +26,8 @@ pub fn verify(
Verification::SameOriginSha256 {
checksum_asset_pattern,
} => {
let checksum_path = fetcher::download_asset(
client,
repo,
tag,
checksum_asset_pattern,
dest_dir,
)?;
let checksum_path =
fetcher::download_asset(client, repo, tag, checksum_asset_pattern, dest_dir)?;
let checksum_text = std::fs::read_to_string(&checksum_path)?;
let expected = checksum_text
.split_whitespace()
@ -66,7 +61,9 @@ pub fn verify(
repo,
])
.output()
.context("running `gh attestation verify` (is `gh` installed and authenticated?)")?;
.context(
"running `gh attestation verify` (is `gh` installed and authenticated?)",
)?;
let passed = output.status.success();
Ok(VerificationResult {