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: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
PR TITLE: must follow Conventional Commits — it becomes the squash-merge
commit and feeds release-please. e.g. feat(cloud): add --json output
commit and feeds the release changelog. e.g. feat(cloud): add --json output
Allowed types: feat, fix, perf, deps, revert, refactor, docs, chore, test, ci, build, style
See CONTRIBUTING.md for details.
-->
Expand All @@ -27,7 +27,7 @@
- [ ] `pnpm lint` passes
- [ ] `pnpm typecheck` passes
- [ ] `pnpm build` passes
- [ ] I have **not** bumped the version or edited `CHANGELOG.md` (release-please handles this)
- [ ] I have **not** bumped the version or edited `CHANGELOG.md` (the release workflow handles this)
- [ ] I have signed the CLA (the bot will prompt on first contribution)
- [ ] Docs / `README.md` / `STYLE_GUIDE.md` updated if behaviour or output changed

Expand Down
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ updates:
open-pull-requests-limit: 10
commit-message:
# Conventional Commit prefix so the squashed PR title matches our PR-title
# lint and release-please picks dependency bumps into the changelog.
# lint and the release changelog lists dependency bumps.
prefix: deps
prefix-development: chore
groups:
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ name: CLA Assistant
# the org access policy set. Do not flip it private.
#
# AUTH: mints a token from the shared automation GitHub App (the same App
# release-please uses), so signature commits show as the bot and there's no
# the release workflows use), so signature commits show as the bot and there's no
# personal token to expire.
#
# SETUP REQUIRED before this enforces anything:
# 1. Create/install the automation GitHub App (Contents R/W, Pull requests R/W,
# Issues R/W) and add BOT_APP_ID + BOT_APP_PRIVATE_KEY repo secrets — the
# same secrets release-please uses.
# same secrets the release workflows use.
# 2. Create the `cla-signatures` branch (empty orphan) so the action has
# somewhere to write `signatures/version1/cla.json`.
# 3. Finalise CLA.md (legal review) — it's the document contributors agree to.
Expand Down Expand Up @@ -73,9 +73,9 @@ jobs:
path-to-document: "https://github.com/devicecloud-dev/dcd-cli/blob/dev/CLA.md"
branch: "cla-signatures"
# Do NOT lock the PR on merge (the action's default is true). release-please
# comments on its release PR *after* merge; a locked conversation makes that
# comment fail and takes down the whole Release job (npm publish + binaries
# never run). Keeping this false is load-bearing for the release pipeline.
# used to comment on its release PR *after* merge, and a locked conversation
# failed that comment and the whole Release job with it. Nothing comments
# after merge now, but locking gains nothing, so it stays off.
lock-pullrequest-aftermerge: false
# Internal maintainers (covered by employment/CCLA) + bots skip the prompt.
allowlist: riglar,finalerock44,dependabot[bot],renovate[bot],*[bot]
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
claude-review:
# Skip PRs we shouldn't (or can't) review:
# - Dependabot / forks: no CLAUDE_CODE_OAUTH_TOKEN, so the action would fail.
# - release-please release PRs: just version bumps + changelog — nothing to
# review, and it must never block a release.
# - Back-merge PRs (production -> dev, opened by release-please.yml): only
# carry code already reviewed on its way through dev, and the action
# rejects their bot actor ("Workflow initiated by non-human actor"),
# which blocks the merge since claude-review is a required check.
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' && !startsWith(github.head_ref, 'release-please--') && !startsWith(github.head_ref, 'chore/back-merge-') }}
# - Release PRs (dev -> production, opened by promote.yml) and back-merge
# PRs (production -> dev, opened by release-please.yml): they only carry
# code already reviewed on its way through dev, plus the version and
# changelog commit, and the action rejects their bot actor ("Workflow
# initiated by non-human actor"), which blocks the merge since
# claude-review is a required check.
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' && !(github.base_ref == 'production' && startsWith(github.head_ref, 'promote/cli-')) && !startsWith(github.head_ref, 'chore/back-merge-') }}
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
77 changes: 15 additions & 62 deletions .github/workflows/cli-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,71 +102,24 @@ jobs:
working-directory: ./cli
run: pnpm audit --audit-level moderate

# Promotions to `production` MUST pin the stable version with a `Release-As:`
# footer on a commit. Without it release-please derives the stable version
# from the most recent tag reachable from `production` — and because a
# promotion is a merge commit, every beta tag is reachable, so it picks up a
# `-beta` version. That is exactly how 5.6.0 ended up with `production`
# carrying 5.6.0-beta.1 in package.json and a release PR that could not
# publish. 5.5.0 got a pin (`chore: pin the 5.5.0 promotion`) and came out
# correct; 5.6.0's was abandoned and did not.
#
# It has to be on a NORMAL commit, not the merge commit — release-please's
# commit splitting is unreliable on merges.
#
# Release-please's own release PR also targets `production` but is not a
# promotion: it never carries a pin, so the check would always fail on it.
promotion-pin:
if: github.event_name == 'pull_request' && github.base_ref == 'production' && !startsWith(github.head_ref, 'release-please--')
# Every PR into `production` is a release, built by promote.yml: `dev` with
# `production` merged in, plus a commit that sets a new version in
# package.json and server.json and adds its CHANGELOG.md section. This checks
# that commit is there and consistent, against the merge result, so a hand-made
# PR can't land something the Release workflow would skip (version already
# tagged) or refuse (version files out of step).
release-check:
if: github.event_name == 'pull_request' && github.base_ref == 'production'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# Tags, to check the version is new and above every stable release.
fetch-depth: 0

- name: Require a stable Release-As pin
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
git fetch --quiet origin dev

# Scope matters more than the pattern here. A promotion merges the whole
# of `dev`, whose history carries a `Release-As:` footer from every past
# promotion (5.0.0, 5.1.0, 5.1.1, 5.2.0, 5.3.0, 5.3.1 …). Scanning
# `base..head` therefore always finds one and passes vacuously — checked
# against the real #176, which it waved through. Only commits unique to
# this promotion branch count: everything on `production` and everything
# on `dev` is excluded. `--no-merges` because release-please's commit
# splitting is unreliable on merge commits, so a pin has to sit on an
# ordinary one.
#
# NB `^ref` not `--not ref`: --not is a TOGGLE over everything that
# follows, so `--not A --not B` excludes A and re-includes B.
MESSAGES=$(git log --no-merges --format=%B "$HEAD_SHA" "^$BASE_SHA" "^origin/dev")

if echo "$MESSAGES" | grep -qE '^Release-As:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+[[:space:]]*$'; then
echo "Found $(echo "$MESSAGES" | grep -oE '^Release-As:[[:space:]]*[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
exit 0
fi

if echo "$MESSAGES" | grep -qE '^Release-As:'; then
echo "::error::This promotion pins a PRERELEASE version. The stable line must be pinned to a plain X.Y.Z."
echo "$MESSAGES" | grep -E '^Release-As:' >&2
exit 1
fi

echo "::error::No 'Release-As: X.Y.Z' footer on any non-merge commit unique to this promotion."
{
echo "Add one as its own commit on the promotion branch:"
echo " git commit --allow-empty -m 'chore: pin the X.Y.Z promotion' -m 'Release-As: X.Y.Z'"
echo
echo "Do not skip it on the grounds that the conventional commits since the last"
echo "stable already imply the right bump. They do not: a promotion is a merge, so"
echo "every beta tag becomes reachable from production, and release-please picks the"
echo "newest reachable tag as its base. That is how the 5.6.0 promotion — which"
echo "reasoned exactly that way — produced a 'chore(production): release 5.6.0-beta.1'"
echo "release PR and left production carrying a prerelease in package.json."
} >&2
exit 1
- uses: actions/setup-node@v7
with:
node-version: '24.x'

- name: Check this PR is a well-formed release
run: node scripts/stable-release.mjs verify
16 changes: 9 additions & 7 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Publish Package to npmjs

# Publishes the STABLE package to npm's `latest` tag. Called by
# release-please.yml after a stable Release PR merges on `production`.
# release-please.yml (the Release workflow) after a release PR merges on
# `production`.
#
# Betas do NOT come through here — .github/workflows/release-beta.yml publishes
# those directly. This file used to take a `release_type` of prod or beta, but
Expand Down Expand Up @@ -60,10 +61,10 @@ jobs:
- run: pnpm install --frozen-lockfile

# Stable publishes land on the npm `latest` tag and must only ever come
# from the `production` branch (release-please's prod target branch).
# Without this guard, workflow_dispatch could publish any ref as `latest`,
# bypassing release-please. workflow_call is unaffected: release-please.yml
# only runs on pushes to `production`, so github.ref_name matches there.
# from the `production` branch. Without this guard, workflow_dispatch
# could publish any ref as `latest`, bypassing the release PR.
# workflow_call is unaffected: release-please.yml only runs on
# `production`, so github.ref_name matches there.
- name: Enforce production branch
if: ${{ github.ref_name != 'production' }}
run: |
Expand All @@ -72,8 +73,9 @@ jobs:

# Catches the failure mode that stalled 5.6.0: a promotion merge dragged
# `dev`'s beta version onto `production`, so package.json read
# 5.6.0-beta.1 while the stable manifest still said 5.5.0. Publishing that
# to `latest` would have shipped a prerelease to every default install.
# 5.6.0-beta.1. Publishing that to `latest` would have shipped a
# prerelease to every default install. stable-release.mjs verify refuses
# such a version before the tag is made; this is the last backstop.
- name: Validate Production Version
run: |
VERSION=$(node -p "require('./package.json').version")
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pr-title-lint.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: PR Title

# Enforces Conventional Commits on the PR *title*. Because PRs are squash-merged
# with the title as the commit subject, this is what release-please parses to
# compute version bumps and the changelog — so the allowed types below must stay
# in sync with `changelog-sections` in release-please-config.json.
# with the title as the commit subject, this is what scripts/stable-release.mjs
# parses to compute version bumps and the changelog, so the allowed types below
# must stay in sync with SECTIONS and HIDDEN there.
#
# Uses pull_request_target so it also runs (and reports a required status check)
# on PRs from forks. It only reads the title — no untrusted code is checked out.
Expand All @@ -27,7 +27,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Keep in lockstep with release-please-config.json changelog-sections.
# Keep in lockstep with SECTIONS and HIDDEN in scripts/stable-release.mjs.
types: |
feat
fix
Expand Down
Loading
Loading