Skip to content
Open
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
18 changes: 15 additions & 3 deletions .github/workflows/release-start.yml
Original file line number Diff line number Diff line change
Expand Up @@ -502,16 +502,28 @@ jobs:

${{ inputs.body-extras }}

# Mirror into Linear: the release is created with its issues linked (sync scans the history
# since the previous tag) and moved to in_progress; only when the named package was bumped,
# since the Linear version is the tag. Linear never fails a release that is already cut.
# Mirror into Linear: the release is created with its issues linked and moved to in_progress;
# only when the named package was bumped, since the Linear version is the tag. The sync scans
# from the latest '<tag-prefix>*' tag — the summary's boundary — not from the pipeline's last
# release: a fresh pipeline has none, and its first sync would then cover the release commit
# alone. Linear never fails a release that is already cut.
- name: Resolve the previous release tag
id: previous-tag
if: ${{ steps.version.outputs.released == 'true' && steps.version.outputs.tag != '' && inputs.op-linear-pipeline-key-path != '' }}
env:
TAG_PREFIX: ${{ inputs.tag-prefix }}
run: |
set -euo pipefail
echo "tag=$(git tag --list "${TAG_PREFIX}[0-9]*" --sort=-v:refname | head -n 1)" >> "$GITHUB_OUTPUT"

- name: Linear — create the release
if: ${{ steps.version.outputs.released == 'true' && steps.version.outputs.tag != '' && inputs.op-linear-pipeline-key-path != '' }}
uses: aragon/github-templates/steps/linear-release@main
with:
access-key: ${{ fromJSON(steps.op.outputs.secrets).LINEAR_PIPELINE_ACCESS_KEY }}
command: sync
version: ${{ steps.version.outputs.tag }}
base-ref: ${{ steps.previous-tag.outputs.tag }}

- name: Linear — move the release to in_progress
if: ${{ steps.version.outputs.released == 'true' && steps.version.outputs.tag != '' && inputs.op-linear-pipeline-key-path != '' }}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ downloads, an inline npm publish). Nothing that a single repo needs belongs here
| `slack-notify` | post / thread / edit a Slack message |
| `extract-slack-ts` | parse the `<!-- slack_ts -->` marker from text |
| `resolve-slack-thread` | find the release thread from an explicit ts, a PR number or a release tag (+ the body without markers) |
| `linear-release` | mirror a release into a Linear release pipeline: `sync`, `update` (stage), `complete`; a no-op without an access key |
| `linear-release` | mirror a release into a Linear release pipeline: `sync` (optionally from an explicit `base-ref`), `update` (stage), `complete`; a no-op without an access key |
| `parse-playwright-results` | classify a Playwright JSON report |
| `gh-ensure-pr` / `gh-ensure-tag` / `gh-ensure-release` | idempotent PR / tag / release |
| `gh-ensure-pr-comment` | one tagged PR comment that reruns edit in place (preview URLs, test results) |
Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces.json
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,10 @@
"required": false,
"default": ""
},
"base-ref": {
"required": false,
"default": ""
},
"command": {
"required": false,
"default": "sync"
Expand Down
10 changes: 6 additions & 4 deletions docs/release-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ versus the per-repo originals, which assumed the script lived in the same checko
| Module | Purpose | Key inputs → outputs |
|---|---|---|
| `credential-retrieval` *(existing, extended)* | the only module that talks to 1Password: bulk vault → env/file, or named `op://` refs → JSON | `op-token`, `mode`, `op-vault` (bulk modes), `secret-refs` (`byref`) → `secrets` (JSON, `byref`) |
| `setup` | checkout + pnpm + Node + install | `ref`, `node-version`, `registry-url` |
| `setup` | checkout + pnpm + Node (`.nvmrc` when present) + install | `ref`, `node-version-file`, `node-version`, `registry-url` |
| `compute-version` | next version + bump + changelog(s); scope→`--ignore` inversion in monorepos | `engine`, `package-dir`, `scope`/`ignore-packages`, `tag-prefix`, `prettier-changelog` → `version`, `released`, `tag` |
| `generate-release-summary` | git-log → categorised summary (+Linear); per-package tag boundary + path filtering | `tag-glob`, `path-filter`/`path-patterns`, `linear-api-token`, `base-ref`, `repo` → `summary` |
| `generate-version-summary` | per-package `## name@version` + CHANGELOG sections after `changeset version` | `scope`/`packages`/`package-dir` → `summary` |
Expand Down Expand Up @@ -288,9 +288,11 @@ the PR number on PR events, the release tag on `release: published`, an explicit
Without a thread the first message opens one and the run threads under it (a manual deploy).

The Linear mirror follows the same moments with `steps/linear-release`: `sync` + `in_progress` at
start, `canceled` on cancel, `complete` on complete. One Linear release pipeline per release
lineage, its access key in the repo's vault (`op-linear-pipeline-key-path`); without a key the
steps are skipped. Linear never fails a release.
start, `canceled` on cancel, `complete` on complete. The sync scans from the previous release tag
(the summary's boundary), not from the pipeline's last release, so a fresh pipeline's first
release is linked in full. One Linear release pipeline per release lineage, its access key in the
repo's vault (`op-linear-pipeline-key-path`); without a key the steps are skipped. Linear never
fails a release.

Schedules are the consumer's: a cron on the caller plus `on-active-release: skip`, so a train with
an open release PR or nothing to release green-skips — never silently: the channel gets the ⏭️
Expand Down
8 changes: 7 additions & 1 deletion steps/linear-release/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ description: |
the release and links the issues the commits reference, `update` moves it to a stage
(`in_progress`, `canceled`), `complete` marks it released. Linear is a mirror, never a gate: the
step is skipped without an access key and a Linear error only warns. `sync` scans the git
history, so the checkout must have full depth.
history from `base-ref` (or, without one, from the pipeline's last release — the current commit
alone when the pipeline has none yet), so the checkout must have full depth.

inputs:
access-key:
Expand All @@ -22,6 +23,10 @@ inputs:
description: "Stage for 'update' (in_progress, canceled)"
required: false
default: ""
base-ref:
description: "Exclusive lower bound of the 'sync' scan (<base-ref>..HEAD), e.g. the previous release tag; empty lets the CLI pick the pipeline's last release"
required: false
default: ""

runs:
using: composite
Expand Down Expand Up @@ -57,3 +62,4 @@ runs:
version: ${{ inputs.version }}
name: ${{ inputs.command == 'sync' && inputs.version || '' }}
stage: ${{ inputs.stage }}
base_ref: ${{ inputs.command == 'sync' && inputs.base-ref || '' }}
22 changes: 18 additions & 4 deletions steps/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ inputs:
required: false
default: ""
node-version:
description: "The version of Node.js to use (ignored when node-version-file is set)"
description: "The version of Node.js to use when no version file applies (see node-version-file)"
required: false
default: "24"
node-version-file:
description: "Path to a file containing the Node version (e.g. .nvmrc). Takes precedence over node-version."
description: |
Path to a file containing the Node version. Takes precedence over node-version. Left empty, the
repository's own .nvmrc is used when it exists, so a consumer pins Node once; node-version is
the fallback for repositories without one.
required: false
default: ""
node-cache:
Expand Down Expand Up @@ -112,11 +115,22 @@ runs:
shell: bash
run: corepack enable

# The version file wins over node-version: the explicit input, else the repository's .nvmrc.
- name: Resolve the Node version file
id: node-version-file
shell: bash
env:
NODE_VERSION_FILE: ${{ inputs.node-version-file }}
run: |
file="$NODE_VERSION_FILE"
if [ -z "$file" ] && [ -f .nvmrc ]; then file=".nvmrc"; fi
echo "file=$file" >> "$GITHUB_OUTPUT"

- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ inputs.node-version-file == '' && inputs.node-version || '' }}
node-version-file: ${{ inputs.node-version-file }}
node-version: ${{ steps.node-version-file.outputs.file == '' && inputs.node-version || '' }}
node-version-file: ${{ steps.node-version-file.outputs.file }}
cache: ${{ inputs.node-cache }}
registry-url: ${{ inputs.registry-url }}

Expand Down
Loading