Commit de2e0db
committed
Add reusable workflows alongside the existing starter templates
Why this exists
---------------
The workflow templates under workflow-templates/ solve "scaffold me
a CI workflow." They work via copy-on-init: GitHub copies the file
into the repo's .github/workflows/ and from then on the repo owns
that copy. This is great for repos that want to pin the behaviour
and pick up changes deliberately. It's bad for the org as a whole:
when we fix a bug in the Rust template, the 5 repos that already
adopted it do not get the fix unless each maintainer re-copies it.
Reusable workflows solve the same problem from the other side: the
logic lives in ONE place (this repo's .github/workflows/), and
consuming repos reference it with `uses:`. Fixes propagate to every
adopter automatically. The cost is that every caller is coupled to
this repo's main branch (or a pinned SHA).
Rather than pick one model and force every repo into it, we ship
BOTH and document the tradeoff. Each repo chooses per workflow:
- Want to pin the behaviour? Use the starter template. It's a
copy. Upgrades are explicit.
- Want to stay in lockstep with the org? Call the reusable
workflow. Upgrades are automatic. Breakage is also automatic
when we change something.
What's in this commit
---------------------
Eight reusable workflows under .github/workflows/, one per starter
template. Each preserves the exact behaviour of the matching
template (same tools, same commands, same options) but re-shaped
for workflow_call with explicit `inputs:` and `secrets:` blocks.
CI workflows:
reusable-ci-nextjs-monorepo.yml
Inputs: tasks (space-separated, default "lint typecheck test
build"), node-version-file (default .nvmrc).
Secrets: TURBO_TOKEN, TURBO_TEAM (both optional).
Behaviour: matrix job per task, turbo filter auto-computed
from PR base SHA or HEAD^1.
reusable-ci-rust-monorepo.yml
Input: toolchain (default stable).
Behaviour: unchanged from the starter - paths-filter gate,
fmt + clippy + nextest + build + doc jobs with rust-cache.
reusable-ci-python-monorepo.yml
No inputs - convention-based. Assumes uv workspace at the
repo root with .python-version, uv.lock, packages/* layout.
Behaviour: unchanged from the starter - two-layer change
detection, ruff + mypy + per-package pytest matrix.
reusable-ci-docs-mdx.yml
Inputs: build-command (default "pnpm build"),
node-version-file (default .nvmrc), files-glob (default
"**/*.{md,mdx}"). Behaviour: cspell incremental on PRs,
lychee link check with cache, site build.
Security and policy workflows:
reusable-codeql.yml
REQUIRED input: languages - a JSON array of
{language, build-mode} objects. Expressed this way so callers
only declare the languages their repo actually uses, without
us having to ship separate one-language reusables.
reusable-dependency-review.yml
Inputs: fail-on-severity (default moderate),
comment-summary-in-pr (default on-failure).
reusable-pr-title-lint.yml
Input: require-scope (default false). Types list baked in to
match CONTRIBUTING.md - when that list changes, change it
here and in the starter template too.
reusable-stale.yml
Fully parameterised: days-before-*, exempt-*-labels,
operations-per-run. Defaults match what stale.yml currently
uses. Caller provides the schedule (cron) since workflow_call
can't carry schedule triggers through to the reusable.
Notable decisions
-----------------
- Every reusable declares its own permissions: block at the
workflow or job level. Callers must grant AT LEAST those
permissions on the calling job; if they grant less, GitHub
silently downgrades and steps fail in hard-to-debug ways. Each
reusable's header comment spells out what the caller needs.
- No SHA-pinning of internal `uses:` inside the reusables. Tag
pins (@v4, @v5) are acceptable here because Dependabot now
tracks this repo's own Actions dependencies (see commit 44dfac9).
Consuming repos that want SHA-pinning should reference this
repo's reusables by SHA, not tag.
- Reusables do NOT declare `on:` triggers other than
workflow_call. The caller owns the trigger surface. This is
non-negotiable: reusables that try to declare pull_request
will simply never run.
- I kept Rust's dtolnay/rust-toolchain switched from `@stable` to
`@master` with a `toolchain:` input, because the former is an
alias that only points at stable and can't be parameterised.
Functionally identical when the input is left at its default.
What this deliberately does NOT do
----------------------------------
- Does not delete, rewrite, or otherwise touch workflow-templates/.
The starter templates remain valid standalone workflows. Repos
that already adopted them keep working.
- Does not ship .properties.json for the reusables. Reusable
workflows don't show up in the "New workflow" picker - they're
called, not scaffolded - so properties metadata is irrelevant.
- Does not enforce which path a repo picks. Platform team can
recommend; repos choose per workflow.
- Does not set up a release versioning scheme (e.g. v1/v2 major-
version tags pointing at the tip of each major). Callers use
@main today. A future commit can add moving major tags once
we've actually broken something.
Cross-references
----------------
- README.md: the Reusable workflow templates section is now split
into Path A (starter templates) and Path B (reusable workflows)
with a short paragraph explaining when to pick each. Both tables
are populated with every shipped file.
- ORG_SETTINGS.md § Actions permissions: the org Actions allow-list
must include `nyuchitech/*` (workflows from the same org are
allowed by default, but if the allow-list is tightened, the
reusables' paths must remain reachable).
- AGENTS.md § Security: "Pin third-party GitHub Actions by SHA"
applies to the reusables too - consuming repos that care about
this should reference the reusables by SHA, not @main.1 parent bcbf40e commit de2e0db
9 files changed
Lines changed: 697 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
0 commit comments