From 51bfc54063d49d971080f70895190973537ec154 Mon Sep 17 00:00:00 2001 From: Nick Juliano Date: Fri, 19 Jun 2026 09:56:48 -0400 Subject: [PATCH] chore: add npm oidc workflow --- .github/PULL_REQUEST_TEMPLATE.md | 8 + .github/workflows/publish-npm.yaml | 150 ++++++++++++++++++ .../workflows/validate-version-selection.yaml | 41 +++++ package-lock.json | 4 +- package.json | 2 +- 5 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/publish-npm.yaml create mode 100644 .github/workflows/validate-version-selection.yaml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6ae1a26 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +## Version Update + +Select exactly one version update type. This choice is used by CI after the PR is merged into +`main` to update `package.json`, create the release tag, and publish the package to npm. + +- [ ] major +- [ ] minor +- [ ] patch diff --git a/.github/workflows/publish-npm.yaml b/.github/workflows/publish-npm.yaml new file mode 100644 index 0000000..bd9b830 --- /dev/null +++ b/.github/workflows/publish-npm.yaml @@ -0,0 +1,150 @@ +name: Auto Version, Publish, and Tag NPM + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + update_type: + description: Version update type + required: true + default: patch + type: choice + options: + - major + - minor + - patch + +concurrency: + group: npm-release + cancel-in-progress: false + +jobs: + publish: + if: "${{ github.event_name == 'workflow_dispatch' || !startsWith(github.event.head_commit.message, 'chore: release ') }}" + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + pull-requests: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.DEPLOY_PAT || github.token }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.14.0' + registry-url: 'https://registry.npmjs.org' + + - name: Install npm with OIDC support + run: npm install --global npm@^11.5.1 + + - name: Get version update type + id: release-type + uses: actions/github-script@v7 + with: + script: | + const updateTypes = ['major', 'minor', 'patch']; + + function selectedUpdateTypes(body) { + return updateTypes.filter((type) => { + const pattern = new RegExp(`^- \\[[xX]\\]\\s*${type}\\s*$`, 'im'); + return pattern.test(body ?? ''); + }); + } + + if (context.eventName === 'workflow_dispatch') { + const updateType = context.payload.inputs?.update_type; + + if (!updateTypes.includes(updateType)) { + core.setFailed(`Invalid version update type: ${updateType}`); + return; + } + + core.setOutput('update-type', updateType); + core.info(`Selected version update: ${updateType}`); + return; + } + + const { owner, repo } = context.repo; + const response = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, + repo, + commit_sha: context.sha, + }); + + const pullRequest = + response.data.find((pr) => pr.merged_at && pr.base.ref === 'main') ?? + response.data.find((pr) => pr.merged_at); + + if (!pullRequest) { + core.setFailed(`No merged pull request found for commit ${context.sha}.`); + return; + } + + const selected = selectedUpdateTypes(pullRequest.body); + + if (selected.length !== 1) { + core.setFailed( + `PR #${pullRequest.number} must have exactly one version update checkbox selected. Found ${selected.length}.`, + ); + return; + } + + core.setOutput('update-type', selected[0]); + core.setOutput('pr-number', String(pullRequest.number)); + core.info(`Selected version update from PR #${pullRequest.number}: ${selected[0]}`); + + - name: Bump package version + id: package-version + run: | + npm version "$UPDATE_TYPE" --no-git-tag-version + PACKAGE_NAME=$(node -p "require('./package.json').name") + VERSION=$(node -p "require('./package.json').version") + echo "package-name=$PACKAGE_NAME" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Bumped $PACKAGE_NAME to $VERSION" + env: + UPDATE_TYPE: ${{ steps.release-type.outputs.update-type }} + + - name: Check release availability + run: | + if git tag --list "v${PACKAGE_VERSION}" | grep -q .; then + echo "::error::Tag v${PACKAGE_VERSION} already exists" + exit 1 + fi + + if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version --registry=https://registry.npmjs.org > /dev/null 2>&1; then + echo "::error::${PACKAGE_NAME}@${PACKAGE_VERSION} already exists on npm" + exit 1 + fi + env: + PACKAGE_NAME: ${{ steps.package-version.outputs.package-name }} + PACKAGE_VERSION: ${{ steps.package-version.outputs.version }} + + - name: Install dependencies + run: npm ci + + - name: Build package + run: npm run build + + - name: Publish to NPM + run: npm publish + + - name: Commit version bump and tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add package.json package-lock.json + git commit -m "chore: release v${PACKAGE_VERSION} [skip ci]" + git tag "v${PACKAGE_VERSION}" + git push origin HEAD:main + git push origin "v${PACKAGE_VERSION}" + env: + PACKAGE_VERSION: ${{ steps.package-version.outputs.version }} diff --git a/.github/workflows/validate-version-selection.yaml b/.github/workflows/validate-version-selection.yaml new file mode 100644 index 0000000..e493b28 --- /dev/null +++ b/.github/workflows/validate-version-selection.yaml @@ -0,0 +1,41 @@ +name: Validate Version Selection + +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + - edited + - ready_for_review + +permissions: + contents: read + +jobs: + validate-version-selection: + name: Validate version selection + runs-on: ubuntu-latest + + steps: + - name: Validate release checkbox + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.pull_request?.body ?? ''; + const updateTypes = ['major', 'minor', 'patch']; + const selected = updateTypes.filter((type) => { + const pattern = new RegExp(`^- \\[[xX]\\]\\s*${type}\\s*$`, 'im'); + return pattern.test(body); + }); + + if (selected.length !== 1) { + core.setFailed( + `Select exactly one version update checkbox in the PR body. Found ${selected.length}.`, + ); + return; + } + + core.info(`Selected version update: ${selected[0]}`); diff --git a/package-lock.json b/package-lock.json index 8a113a7..8c00d63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@permaweb/hyperbalance", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@permaweb/hyperbalance", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "@permaweb/aoconnect": "^0.0.96" diff --git a/package.json b/package.json index fcea1e2..77b1c5a 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/xylophonez/hyperbalance.git" + "url": "https://github.com/xylophonez/hyperbalance" }, "bugs": { "url": "https://github.com/xylophonez/hyperbalance/issues"