Skip to content

feat: add a reusable citation.yaml workflow - #35

Merged
eliotmcintire merged 1 commit into
mainfrom
feat/citation-reusable-workflow
Sep 3, 2026
Merged

feat: add a reusable citation.yaml workflow#35
eliotmcintire merged 1 commit into
mainfrom
feat/citation-reusable-workflow

Conversation

@eliotmcintire

Copy link
Copy Markdown
Contributor

Adds .github/workflows/citation.yaml, so the "Update CITATION.cff" job stops being hand-rolled per repo.

Four repos carried a near-identical copy of this job — reproducible, SpaDES, SpaDES.core, SpaDES.tools. All four call this template with no overrides at all. Thin callers follow in each repo.

Why this one is worth templating

Two things were per-repo that shouldn't have been.

1. The install-spatial-deps pin. A hand-rolled caller names that action directly, so each repo carried its own ref — the four had drifted to @v0.1, @v0.2, and a raw SHA. That matters beyond tidiness: every tag v0.1v0.5 still runs add-apt-repository ppa:ubuntugis/ubuntugis-unstable, which installs libgdal37 and is ABI-incompatible with the binaries in Posit's noble cache. Only @main dropped it (#25). Reaching the action through setup-r-deps makes the pin internal to this repository, so it moves once instead of in N PRs.

2. The privilege split. SpaDES.tools had it; the other three did not. Generating CITATION.cff installs and runs third-party R code — the dependency install builds V8 and the package's own tree, and a source install runs that package's configure. Committing the result needs a write-scoped token. Those must not share a job. So build runs with contents: read and uploads an artifact; commit runs with contents: write and nothing but git, pushing with an explicit token rather than a credential left in .git/config.

Callers must grant contents: write on the calling job — a called workflow can only reduce the caller's permissions, never widen them.

Inputs

input default why it exists
extra-packages "" cffr and V8 are always installed; this appends
dependencies '"all"' decides how complete references: is — see below
system-deps true a pure-R adopter sets false and skips the apt step

dependencies is the interesting one. cffr's cff_write(dependencies = TRUE) emits a references: entry for each dependency that happens to be installed — from R/utils-create.R:

av_deps <- deps[deps$package %in% c("R", instpack), ]

Uninstalled dependencies are silently dropped, not errors. So "all" produces the long list these packages have today (39 entries for reproducible), and "hard" would shorten it — and, for a package whose Imports are pure R, remove the geospatial install entirely. That's a content decision, so it's an input rather than a default change.

pandoc is deliberately not an input: it's hardcoded off. Nothing in this job renders — cff_write() reads DESCRIPTION and inst/CITATION and writes YAML. SpaDES.core's copy had a setup-pandoc step that was doing nothing.

Not covered

self-test.yaml still cannot exercise a reusable workflow (it checks out the caller and runs at its root, and this repo has no root DESCRIPTION), so this lands with the same verification gap as the other four templates. The thin-caller PRs are the real test.

🤖 Generated with Claude Code

https://claude.ai/code/session_015ZoicL7829pkb5ZbS5Ciww

Four repos (reproducible, SpaDES, SpaDES.core, SpaDES.tools) each carried a
near-identical hand-rolled "Update CITATION.cff" job. All four can now call
this template with no overrides.

Centralises two things that were per-repo:

1. The install-spatial-deps pin. A hand-rolled caller names that action
   directly, so the four had drifted to @v0.1, @v0.2 and a raw SHA -- and
   every tag v0.1-v0.5 still adds the ubuntugis-unstable PPA (libgdal37),
   ABI-incompatible with the binaries in Posit's noble cache. Reaching the
   action through setup-r-deps makes the pin internal to this repository.

2. The privilege split, which until now existed only in SpaDES.tools.
   Generating CITATION.cff installs and runs third-party R code; committing
   it needs a write-scoped token. `build` gets contents: read and uploads an
   artifact, `commit` gets contents: write and runs nothing but git.

pandoc is hardcoded off rather than exposed: cff_write() reads DESCRIPTION
and inst/CITATION and writes YAML, and nothing in the job renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZoicL7829pkb5ZbS5Ciww
@eliotmcintire
eliotmcintire merged commit 2f9ddeb into main Sep 3, 2026
1 check passed
eliotmcintire added a commit that referenced this pull request Sep 4, 2026
Every SpaDES module repository carries a hand-generated copy of this job,
written once by SpaDES.core::use_gha() and never regenerated. The survey in
#36 found 74 repositories carrying it across 96 repository/branch
combinations, in two generations that had drifted apart: 46 combinations
still on ubuntu-20.04 with checkout@v2 and setup-r@v1, and 50 on the newer
shape pinning five actions from this repository at @v0.2, @v0 or @v0.0.1.

Those pins are the main thing blocking retirement of v0.1-v0.5. A caller now
supplies its triggers and the module name; the pins live here and move once.

Fixes carried in by the move:

- [skip-ci] tested commits[0], the OLDEST commit in a push, and there is no
  commits field at all on pull_request -- so it worked on neither. Now uses
  head_commit, as in #34.
- No concurrency group, so superseded runs were never cancelled (#30).
- The hand-written apt-get install had no apt-get update, no retries and no
  timeout. That is the shape that left two LandWebUtils jobs sitting 6h on
  2026-08-19. Now uses the same retry wrapper as install-spatial-deps.
- The commit step ran on pull_request too, where github.ref is
  refs/pull/N/merge and the push cannot succeed; it failed silently every
  time, swallowed by `|| echo "No changes to commit"`. Now push-only.

Rendering and committing are separate jobs, as in citation.yaml (#35):
rendering installs the module's dependency tree and then executes the
module's own .Rmd, which must not share a job with a write-scoped token.

setup-r-deps is deliberately not used here. It delegates to
r-lib/actions/setup-r-dependencies, which resolves from a DESCRIPTION, and
modules have none -- their dependencies come from SpaDES.core::packages().
The apt hardening is reproduced instead.

The second install-Rmd-pkgs call is preserved rather than dropped: the
generated file had it, installing SpaDES can move versions the first call
settled, and #36 flags it for someone with the history to confirm or remove.

Refs #36.


Claude-Session: https://claude.ai/code/session_015ZoicL7829pkb5ZbS5Ciww

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant