Skip to content

Automate mainline next publish and patch bump PR flow (#8) #1

Automate mainline next publish and patch bump PR flow (#8)

Automate mainline next publish and patch bump PR flow (#8) #1

name: Publish Package
on:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
packages: write
jobs:
require-pr-merge:
name: Require PR Merge
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: read
steps:
- name: Ensure push commit came from merged PR
uses: actions/github-script@v7
with:
script: |
const query = `
query($owner: String!, $repo: String!, $oid: GitObjectID!) {
repository(owner: $owner, name: $repo) {
object(oid: $oid) {
... on Commit {
oid
associatedPullRequests(first: 10) {
nodes {
number
state
mergedAt
url
}
}
}
}
}
}
`;
const maxAttempts = 6;
const retryDelayMs = 5000;
const findMergedPr = async () => {
const data = await github.graphql(query, {
owner: context.repo.owner,
repo: context.repo.repo,
oid: context.sha,
});
const commit = data.repository?.object;
if (!commit) {
throw new Error(`Could not load commit ${context.sha}`);
}
const pullRequests = commit.associatedPullRequests?.nodes ?? [];
return pullRequests.find(
(pr) =>
pr.state === "MERGED" &&
typeof pr.mergedAt === "string" &&
pr.mergedAt.length > 0,
);
};
let mergedPr;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
mergedPr = await findMergedPr();
if (mergedPr) {
break;
}
if (attempt < maxAttempts) {
core.info(
`No merged PR associated with ${context.sha} yet (attempt ${attempt}/${maxAttempts}); retrying in ${retryDelayMs / 1000}s.`,
);
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
}
if (!mergedPr) {
core.setFailed(
`Direct push detected on main at ${context.sha}. Publish flow requires merged PR commits only.`,
);
return;
}
core.info(
`Publish gate passed via merged PR #${mergedPr.number} (${mergedPr.url})`,
);
publish-and-bump:
name: Publish Next + Open Bump PR
runs-on: blacksmith-4vcpu-ubuntu-2404
needs: require-pr-merge
permissions:
contents: write
pull-requests: write
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.8
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Validate package
run: |
bun run check
bun run typecheck
bun test
bun run build
- name: Resolve package metadata
id: meta
run: |
node <<'NODE'
const fs = require("node:fs");
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
const match = /^0\.0\.(\d+)$/.exec(pkg.version ?? "");
if (!pkg.name) {
console.error("Missing package name in package.json");
process.exit(1);
}
if (!match) {
console.error(
`package.json version must match 0.0.x for this release flow. Received: ${pkg.version}`,
);
process.exit(1);
}
const nextVersion = `0.0.${Number(match[1]) + 1}`;
fs.appendFileSync(process.env.GITHUB_OUTPUT, `name=${pkg.name}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${pkg.version}\n`);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `next_version=${nextVersion}\n`);
NODE
- name: Configure npm for GitHub Packages
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm config set @shpitdev:registry https://npm.pkg.github.com
npm config set //npm.pkg.github.com/:_authToken "$NODE_AUTH_TOKEN"
npm config set always-auth true
- name: Publish package with next tag
id: publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PACKAGE_REF="${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}"
if npm view "$PACKAGE_REF" version --registry https://npm.pkg.github.com >/dev/null 2>&1; then
echo "Version $PACKAGE_REF already exists in GitHub Packages; skipping publish."
echo "published=false" >> "$GITHUB_OUTPUT"
exit 0
fi
npm publish --registry https://npm.pkg.github.com --tag next
echo "published=true" >> "$GITHUB_OUTPUT"
- name: Verify registry install
if: steps.publish.outputs.published == 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PACKAGE_REF="${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}"
mkdir -p e2e-install
cd e2e-install
npm init -y
for attempt in 1 2 3 4 5 6; do
if npm install "$PACKAGE_REF"; then
break
fi
if [ "$attempt" -eq 6 ]; then
echo "Failed to install $PACKAGE_REF after retries" >&2
exit 1
fi
sleep 10
done
./node_modules/.bin/opencode-sandboxed-research-analyze --help
./node_modules/.bin/opencode-sandboxed-research-start --help
./node_modules/.bin/opencode-sandboxed-research-setup --help
- name: Prepare next patch bump
run: |
node <<'NODE'
const fs = require("node:fs");
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
pkg.version = "${{ steps.meta.outputs.next_version }}";
fs.writeFileSync("package.json", `${JSON.stringify(pkg, null, 2)}\n`);
NODE
- name: Open bump PR
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
branch: ci/version-bump-${{ steps.meta.outputs.next_version }}
delete-branch: true
commit-message: "chore: bump package version to ${{ steps.meta.outputs.next_version }}"
title: "chore: bump package version to ${{ steps.meta.outputs.next_version }}"
body: |
Automated post-publish bump.
- Published `${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}` with npm tag `next`
- Next patch version prepared: `${{ steps.meta.outputs.next_version }}`
add-paths: |
package.json