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
55 changes: 55 additions & 0 deletions .github/workflows/back-merge-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Opens a PR from master → development after changes land on master (back-merge).

name: Back-merge master to development

on:
push:
branches: [master]
workflow_dispatch:

permissions:
contents: read
pull-requests: write

jobs:
open-back-merge-pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Open back-merge PR if needed
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
git fetch origin development master

# Different tips do not mean master has commits development lacks — after a back-merge,
# development contains master and moves ahead. Skip whenever master is already contained.
if git merge-base --is-ancestor origin/master origin/development; then
echo "development already contains master; nothing to back-merge."
exit 0
fi

EXISTING=$(gh pr list --repo "${{ github.repository }}" \
--base development \
--head master \
--state open \
--json number \
--jq 'length')

if [ "$EXISTING" -gt 0 ]; then
echo "An open PR from master to development already exists; skipping."
exit 0
fi

gh pr create --repo "${{ github.repository }}" \
--base development \
--head master \
--title "chore: back-merge master into development" \
--body "Automated back-merge after changes landed on \`master\`. Review and merge to keep \`development\` in sync."

echo "Created back-merge PR master → development."
88 changes: 88 additions & 0 deletions .github/workflows/check-version-bump.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Catches when developers forget to bump package.json for release-affecting changes.
# Library changes (src/, typings/, package.json) require a version above the latest v* release tag.
# Skips for: test-only, docs and .github changes, and back-merge PRs from master (already released).
name: Check Version Bump

on:
pull_request:

jobs:
version-bump:
name: Version bump
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Detect release-affecting changes
id: detect
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_REF: ${{ github.head_ref }}
run: |
if [ "$HEAD_REF" = "master" ]; then
echo "Back-merge from master carries released code; skipping version-bump check."
echo "code_changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Only what this PR introduces: diff from where it branched off, not from the base branch tip.
MERGE_BASE=$(git merge-base "$BASE_SHA" "$HEAD_SHA")
FILES=$(git diff --name-only "$MERGE_BASE" "$HEAD_SHA")
# Library source, published typings and the manifest; test/, docs and .github/ do not count.
CODE_CHANGED=false
echo "$FILES" | grep -qE '^src/|^typings/|^package\.json$' && CODE_CHANGED=true
echo "code_changed=$CODE_CHANGED" >> "$GITHUB_OUTPUT"

- name: Skip when only test/docs/.github changed
if: steps.detect.outputs.code_changed != 'true'
run: echo "No release-affecting files changed. Skipping version-bump check."

- name: Setup Node
if: steps.detect.outputs.code_changed == 'true'
uses: actions/setup-node@v4
with:
node-version: '22.x'

- name: Check version is above the latest release
if: steps.detect.outputs.code_changed == 'true'
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -e
git fetch --tags --force 2>/dev/null || true
HEAD_VERSION=$(git show "$HEAD_SHA:package.json" | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).version")
cat > "$RUNNER_TEMP/version-check.js" <<'EOF'
const [head, ...tags] = process.argv.slice(2);
const parse = (v) => {
const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/.exec(v);
return m ? { raw: v, core: [+m[1], +m[2], +m[3]], pre: m[4] ? m[4].split('.') : [] } : null;
};
// SemVer 2.0.0 precedence: major.minor.patch first; a pre-release ranks below its release.
const cmp = (a, b) => {
for (let i = 0; i < 3; i++) if (a.core[i] !== b.core[i]) return a.core[i] - b.core[i];
if (!a.pre.length || !b.pre.length) return b.pre.length - a.pre.length;
for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
const x = a.pre[i], y = b.pre[i];
if (x === undefined) return -1;
if (y === undefined) return 1;
const nx = /^\d+$/.test(x), ny = /^\d+$/.test(y);
if (nx && ny) { if (+x !== +y) return +x - +y; }
else if (nx !== ny) return nx ? -1 : 1;
else if (x !== y) return x < y ? -1 : 1;
}
return 0;
};
const current = parse(head);
if (!current) { console.log(`::error::package.json version "${head}" is not valid SemVer`); process.exit(1); }
const latest = tags.map(parse).filter(Boolean).sort(cmp).pop();
if (!latest) { console.log('No v* release tags yet; skipping (first release).'); process.exit(0); }
if (cmp(current, latest) <= 0) {
console.log(`::error::Version bump required: package.json is at ${head}, which is not above the latest release ${latest.raw}. Please bump the version in package.json.`);
process.exit(1);
}
console.log(`Version bump check passed: package.json is at ${head} (latest release: ${latest.raw}).`);
EOF
node "$RUNNER_TEMP/version-check.js" "$HEAD_VERSION" $(git tag --list 'v*')
8 changes: 6 additions & 2 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
fileignoreconfig:
- filename: package-lock.json
ignore_detectors:
- filecontent
- base64content
- filename: .github/workflows/secrets-scan.yml
ignore_detectors:
- filecontent
- filename: package-lock.json
checksum: b9989022679f3ffdc44f7bae43618855ee966f41561bd0a804bff91fe9c370b2
- filename: .github/workflows/check-version-bump.yml
checksum: 30a90ef45ab0a11b74040163e6baff12c922c47139c09dd7a62eb69f7b551c1f
version: ""
Loading
Loading