Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/_selftest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jobs:
uses: ./steps/setup
with:
install: "false"
# No pnpm-lock.yaml exists here either, and setup-node FAILS when its pnpm cache finds no
# lockfile to hash — disabling it also exercises the lockfile-less consumer shape
# (Deno repos) that install/node-cache exist for.
node-cache: ""

- name: Unit tests (lib + action scripts)
run: node --test 'lib/*.test.js' 'steps/**/*.test.js'
Expand Down
43 changes: 42 additions & 1 deletion .github/workflows/deploy-vercel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,41 @@ on:
required: false
type: string
default: ""
submodules:
description: "Pass-through to steps/setup's `submodules` (in turn actions/checkout's: false / true / recursive)."
required: false
type: string
default: "false"
op-submodules-token-path:
description: |
Optional 1Password path to a GitHub PAT used ONLY for fetching submodules — separate from
the main checkout, which keeps using the default job token. Needed when `submodules` pulls
in a PRIVATE submodule the default token (scoped to this repo only) can't read, e.g. a
fine-grained PAT that can read only the submodule's repo. Leave empty for public submodules
or no submodules at all.
required: false
type: string
default: ""
setup-install:
description: |
Whether steps/setup runs `pnpm install --frozen-lockfile`. Defaults to true (this
workflow always installs pnpm + Node either way, since `pnpm vercel build/deploy/alias`
below needs pnpm present). Set to "false" for a project that doesn't manage its own
dependencies via pnpm (no pnpm-lock.yaml) — e.g. a Deno-based project — so this step
doesn't fail looking for a lockfile that was never meant to exist.
required: false
type: string
default: "true"
setup-node-cache:
description: |
Pass-through to steps/setup's `node-cache` (in turn actions/setup-node's `cache`).
Defaults to "pnpm". actions/setup-node's own caching looks for a matching lockfile
(pnpm-lock.yaml) to hash for the cache key and FAILS the step if none exists — set this
to "" to disable caching entirely for a project with no pnpm-lock.yaml (see
setup-install above; the two normally go together).
required: false
type: string
default: "pnpm"
setup-command:
description: "Optional command run in <workspace> before the Vercel build (e.g. 'pnpm run setup production')"
required: false
Expand Down Expand Up @@ -114,7 +149,8 @@ jobs:
steps:
# The only place 1Password is read in this workflow — see steps/credential-retrieval. All
# credentials come from the same vault, one item per name (see op-infra-vault). The Sentry
# refs resolve to empty strings unless source-map upload is requested.
# refs resolve to empty strings unless source-map upload is requested. SUBMODULES_TOKEN is
# separate and optional — see op-submodules-token-path.
- name: Resolve Vercel credentials
id: op
uses: aragon/github-templates/steps/credential-retrieval@main
Expand All @@ -128,11 +164,16 @@ jobs:
SENTRY_AUTH_TOKEN=${{ inputs.upload-sentry-source-maps && format('op://{0}/SENTRY_AUTH_TOKEN/credential', inputs.op-infra-vault) || '' }}
SENTRY_ORG=${{ inputs.upload-sentry-source-maps && format('op://{0}/SENTRY_ORG/credential', inputs.op-infra-vault) || '' }}
SENTRY_PROJECT=${{ inputs.upload-sentry-source-maps && format('op://{0}/SENTRY_PROJECT/credential', inputs.op-infra-vault) || '' }}
SUBMODULES_TOKEN=${{ inputs.op-submodules-token-path }}

- name: Setup
uses: aragon/github-templates/steps/setup@main
with:
ref: ${{ inputs.ref }}
submodules: ${{ inputs.submodules }}
submodules-token: ${{ fromJSON(steps.op.outputs.secrets).SUBMODULES_TOKEN }}
install: ${{ inputs.setup-install }}
node-cache: ${{ inputs.setup-node-cache }}

- name: Run setup command
if: inputs.setup-command != ''
Expand Down
35 changes: 35 additions & 0 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Interface contracts

Backward-compatibility guard for the public interface of this repo: the `inputs`, `secrets` and
`outputs` of every reusable workflow (`.github/workflows/*.yml` with `on: workflow_call`) and
every composite action (`steps/*/action.yml`). Consumer repos call these `@main`, so anything
that lands on `main` must keep existing callers working.

`lib/backcompat.test.js` runs in the normal CI test suite (every PR and push to `main`) and
compares the interfaces extracted from the YAML sources (`lib/workflowInterfaces.js`) against
two committed files:

- **`interfaces.json`** — snapshot of the full published interface. CI fails on any
**breaking** difference:
- an input, secret or output was removed (callers passing a removed input fail GitHub's
validation outright; composite actions instead silently ignore it, which is worse);
- an optional input/secret became required, or a **new** required one appeared
(`workflow_call` enforces `required` even when a default exists);
- an input's `default` or `type` changed (silently changes behavior for callers that omit it).

Backward-compatible **additions** (new workflow/action, new optional input, new output) don't
break anything, but CI asks you to register them — run `npm run contracts:update` and commit
the diff. That keeps the snapshot complete so a future removal of your new input is caught.

For an *intentional* breaking change, run `ALLOW_BREAKING=1 npm run contracts:update`: the
snapshot diff makes the break explicit in PR review. Describe the consumer migration in the PR.

- **`consumers.json`** — the exact call shapes of known consumer repos. CI fails if a called
workflow stops defining an input/secret a consumer passes, or starts requiring one it doesn't
pass. Add an entry when a repo starts consuming a workflow `@main`; update it when the
consumer's `with:`/`secrets:` blocks change.

The YAML parsing is a vendored strict-subset parser (same policy as `lib/flatYaml.js`): no
dependencies, and a hard error on shapes it doesn't recognize rather than a silent misparse. If
you add YAML constructs it rejects, extend `lib/workflowInterfaces.js` (with tests) rather than
loosening it.
39 changes: 39 additions & 0 deletions contracts/consumers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"description": "Known consumer call shapes, checked by lib/backcompat.test.js: every input/secret listed here must stay defined on the called workflow, and every required input/secret of the called workflow must be listed here. Add an entry when a consumer repo starts calling a reusable workflow @main; keep the lists in sync with the consumer's `with:`/`secrets:` blocks.",
"consumers": [
{
"repo": "aragon/protocol-doc-ui",
"workflowFile": ".github/workflows/deploy-staging.yml",
"calls": "deploy-vercel.yml",
"inputs": [
"env",
"ref",
"domain",
"vercel-scope",
"op-infra-vault",
"op-submodules-token-path",
"submodules",
"setup-install",
"setup-node-cache",
"setup-command"
],
"secrets": ["OP_SERVICE_ACCOUNT_TOKEN"]
},
{
"repo": "aragon/protocol-doc-ui",
"workflowFile": ".github/workflows/deploy-preview.yml",
"calls": "deploy-vercel.yml",
"inputs": [
"env",
"vercel-scope",
"op-infra-vault",
"op-submodules-token-path",
"submodules",
"setup-install",
"setup-node-cache",
"setup-command"
],
"secrets": ["OP_SERVICE_ACCOUNT_TOKEN"]
}
]
}
Loading