From fd0346b45ddbc3ee4bd030ba19f838c141d8761f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sat, 30 Aug 2025 21:44:26 +0200 Subject: [PATCH 01/16] chore: Update linked issues milestone on PR merge --- .github/workflows/milestone-automation.yml | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/.github/workflows/milestone-automation.yml b/.github/workflows/milestone-automation.yml index 5a05ecc74..e54f804a7 100644 --- a/.github/workflows/milestone-automation.yml +++ b/.github/workflows/milestone-automation.yml @@ -8,6 +8,7 @@ on: permissions: pull-requests: write + issues: write jobs: update_milestones: @@ -36,3 +37,91 @@ jobs: -f milestone=2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update linked issues milestone to "shipping next" + if: steps.check_milestone.outputs.backlog_milestone == 'true' + run: | + set -e # Exit on any error + + # Validate PR number is numeric + if ! [[ "${{ github.event.pull_request.number }}" =~ ^[0-9]+$ ]]; then + echo "Invalid PR number: ${{ github.event.pull_request.number }}" + exit 1 + fi + + # Get linked issues using GraphQL API + query='query LinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $prNumber) { + closingIssuesReferences(first: 20) { + nodes { + number + milestone { + number + } + } + } + } + } + }' + + # Extract owner and repo from repository + owner=$(echo "${{ github.repository }}" | cut -d'/' -f1) + repo=$(echo "${{ github.repository }}" | cut -d'/' -f2) + + # Execute GraphQL query with error handling + echo "Fetching linked issues for PR #${{ github.event.pull_request.number }}" + + if ! response=$(gh api graphql \ + -f query="$query" \ + -f owner="$owner" \ + -f repo="$repo" \ + -F prNumber="${{ github.event.pull_request.number }}" 2>&1); then + echo "Failed to fetch linked issues: $response" + exit 1 + fi + + # Extract issue numbers, filtering out those already in milestone 2 + linked_issues=$(echo "$response" | jq -r ' + .data.repository.pullRequest.closingIssuesReferences.nodes[] + | select(.milestone.number != 2) + | .number' + ) + + # Check if there are any issues to update + if [[ -z "$linked_issues" ]]; then + echo "No linked issues found or all issues already have the correct milestone" + exit 0 + fi + + echo "Found linked issues to update: $(echo "$linked_issues" | tr '\n' ' ')" + + # Update milestone for each linked issue + failed_updates=0 + updated_count=0 + + while IFS= read -r issue_number; do + [[ -z "$issue_number" ]] && continue + + echo "Updating milestone for issue #$issue_number" + if gh api -X PATCH \ + "repos/${{ github.repository }}/issues/$issue_number" \ + -f milestone=2 \ + --silent; then + echo "✓ Successfully updated milestone for issue #$issue_number" + updated_count=$((updated_count + 1)) + else + echo "✗ Failed to update milestone for issue #$issue_number" + failed_updates=$((failed_updates + 1)) + fi + done <<< "$linked_issues" + + echo "Summary: Updated $updated_count issue(s), $failed_updates failure(s)" + + # Fail the step if any updates failed + if [[ $failed_updates -gt 0 ]]; then + echo "Failed to update $failed_updates issue(s)" + exit 1 + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b45af756d898ba9b45d877f0d193df7356e21f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sat, 30 Aug 2025 21:46:12 +0200 Subject: [PATCH 02/16] chore: Add hint to change base branch _before_ reopening Otherwise you end up in an infinite loop of the bot closing your PR. --- .github/workflows/pr-base-enforcement.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-base-enforcement.yml b/.github/workflows/pr-base-enforcement.yml index aa072e393..7398ae65f 100644 --- a/.github/workflows/pr-base-enforcement.yml +++ b/.github/workflows/pr-base-enforcement.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Post comment to update base branch - run: gh pr comment ${{ github.event.pull_request.number }} --body "⚠️ Pull requests targetting the \`master\` branch are not allowed. Please update the base branch to \`next\`." + run: gh pr comment ${{ github.event.pull_request.number }} --body "⚠️ Pull requests targetting the \`master\` branch are not allowed. Please update the base branch to \`next\` before reopeningm." env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Close PR From d3a6262ab4d30eda50804461f29936c77aa7906a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sat, 30 Aug 2025 21:56:49 +0200 Subject: [PATCH 03/16] chore: Remove risky workflows --- .github/workflows/ship-it.yml | 41 -------------------- .github/workflows/snapshot-release.yml | 53 -------------------------- 2 files changed, 94 deletions(-) delete mode 100644 .github/workflows/ship-it.yml delete mode 100644 .github/workflows/snapshot-release.yml diff --git a/.github/workflows/ship-it.yml b/.github/workflows/ship-it.yml deleted file mode 100644 index 1d9348d24..000000000 --- a/.github/workflows/ship-it.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Ship it - -on: - workflow_dispatch: - -jobs: - ship-it: - name: Ship it - runs-on: ubuntu-24.04-arm - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - with: - # Ensure full history is fetched to have a clear - # path between next and master - fetch-depth: 0 - ref: next - # This is required to allow the final push to trigger dependent workflows - # The token is a PAT with the following permissions: - # - Read access to metadata - # - Read and Write access to code - # See https://stackoverflow.com/a/64078507/1618881 - token: ${{ secrets.SHIP_IT_TOKEN }} - - - name: Checkout master branch - run: | - git fetch origin master - git checkout master - - - name: Log commits to be shipped - id: commits-to-ship - run: | - git log --oneline master..next - echo "count=$(git log --oneline master..next | wc -l)" >> $GITHUB_OUTPUT - - - name: Fast-forward master to next - if: steps.commits-to-ship.outputs.count != '0' - run: git merge --ff-only next - - - name: Push updated master to origin - if: steps.commits-to-ship.outputs.count != '0' - run: git push origin master diff --git a/.github/workflows/snapshot-release.yml b/.github/workflows/snapshot-release.yml deleted file mode 100644 index 5a82b9605..000000000 --- a/.github/workflows/snapshot-release.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Snapshot release -run-name: 'Snapshot release ${{ inputs.version }}' - -on: - workflow_dispatch: - inputs: - version: - description: Base version to use for the release - type: string - default: '0.0.0' -env: - FORCE_COLOR: 3 # Diplay chalk colors - -jobs: - snapshot: - name: Deployment - runs-on: ubuntu-24.04-arm - permissions: - id-token: write # to enable use of OIDC for NPM provenance - steps: - - name: Generate snapshot version - id: version - run: | - version="${{ inputs.version }}-snapshot.$(date +%Y-%m-%d).$(echo $GITHUB_SHA | cut -c1-8)" - echo "version=${version}" >> $GITHUB_OUTPUT - echo "::notice title=Version::${version}" - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 - with: - node-version-file: .node-version - cache: pnpm - - name: Install dependencies - run: pnpm install --ignore-scripts --frozen-lockfile --filter nuqs... - - name: Build package - run: pnpm build --filter nuqs - - name: Publish package - working-directory: packages/nuqs - run: | - pnpm pkg set version=${{ steps.version.outputs.version }} - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc - pnpm publish --access public --provenance --tag snapshot --no-git-checks - rm -f .npmrc - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - uses: 47ng/actions-slack-notify@main - name: Notify on Slack - if: failure() - with: - status: ${{ job.status }} - jobName: Snapshot release ${{ steps.version.outputs.version }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From 6559d60151a48d531e55af33fd804abe87ffbed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sat, 30 Aug 2025 22:32:00 +0200 Subject: [PATCH 04/16] chore: Typo --- .github/workflows/pr-base-enforcement.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-base-enforcement.yml b/.github/workflows/pr-base-enforcement.yml index 7398ae65f..c4f5ed497 100644 --- a/.github/workflows/pr-base-enforcement.yml +++ b/.github/workflows/pr-base-enforcement.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Post comment to update base branch - run: gh pr comment ${{ github.event.pull_request.number }} --body "⚠️ Pull requests targetting the \`master\` branch are not allowed. Please update the base branch to \`next\` before reopeningm." + run: gh pr comment ${{ github.event.pull_request.number }} --body "⚠️ Pull requests targetting the \`master\` branch are not allowed. Please update the base branch to \`next\` before reopening." env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Close PR From 2eb47dc15b3a449e54c2eab4cc460ad6a4b3bc16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sat, 30 Aug 2025 22:35:51 +0200 Subject: [PATCH 05/16] chore: Simplify pkg.pr.new trigger logic Use the on.pull_request.paths to filter out relevant PRs, and trigger only on: - PRs by maintainers - PRs labeled with `deploy:preview` --- .github/workflows/pkg.pr.new.yml | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index 661370f91..ba7bbe34c 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -2,38 +2,16 @@ name: PR Preview on: pull_request: types: [opened, synchronize, labeled] - pull_request_review: - types: [submitted] + paths: + - 'packages/nuqs/**' jobs: - check-changes: - name: Check for relevant changes to deploy + deploy-preview: + name: Deploy to pkg.pr.new if: | - github.event.review.state == 'APPROVED' || contains(github.event.pull_request.author_association, 'MEMBER') || contains(github.event.pull_request.labels.*.name, 'deploy:preview') runs-on: ubuntu-24.04-arm - outputs: - enable: ${{ steps.check-for-changes.outputs.enable }} - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - with: - fetch-depth: 2 - - name: Check for relevant changes - id: check-for-changes - run: | - if git diff --quiet HEAD^ HEAD ./packages/nuqs; then - echo "No changes to nuqs package, skipping preview deployment." - echo "enable=false" >> $GITHUB_OUTPUT - else - echo "enable=true" >> $GITHUB_OUTPUT - fi - - deploy-preview: - name: Deploy to pkg.pr.new - needs: check-changes - if: needs.check-changes.outputs.enable == 'true' - runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda From 69ec0ce7c90c9e8f49ef135ed0dd66d2dae2abd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 05:51:46 +0200 Subject: [PATCH 06/16] fix: Handle issues without milestones Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/milestone-automation.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/milestone-automation.yml b/.github/workflows/milestone-automation.yml index e54f804a7..d5f05b546 100644 --- a/.github/workflows/milestone-automation.yml +++ b/.github/workflows/milestone-automation.yml @@ -82,9 +82,7 @@ jobs: fi # Extract issue numbers, filtering out those already in milestone 2 - linked_issues=$(echo "$response" | jq -r ' - .data.repository.pullRequest.closingIssuesReferences.nodes[] - | select(.milestone.number != 2) + | select(.milestone == null or .milestone.number != 2) | .number' ) From 1e7924beecbdc994284451d72edbd412e9914f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 06:16:51 +0200 Subject: [PATCH 07/16] chore: Exit on any error --- .github/workflows/milestone-automation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/milestone-automation.yml b/.github/workflows/milestone-automation.yml index d5f05b546..1ca5cca6d 100644 --- a/.github/workflows/milestone-automation.yml +++ b/.github/workflows/milestone-automation.yml @@ -41,7 +41,7 @@ jobs: - name: Update linked issues milestone to "shipping next" if: steps.check_milestone.outputs.backlog_milestone == 'true' run: | - set -e # Exit on any error + set -euo pipefail # Exit on any error # Validate PR number is numeric if ! [[ "${{ github.event.pull_request.number }}" =~ ^[0-9]+$ ]]; then From 1e75dde8438b3decd280943a2343f8465a2c41d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 06:17:35 +0200 Subject: [PATCH 08/16] chore: Only target issues in the backlog --- .github/workflows/milestone-automation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/milestone-automation.yml b/.github/workflows/milestone-automation.yml index 1ca5cca6d..74728ffb6 100644 --- a/.github/workflows/milestone-automation.yml +++ b/.github/workflows/milestone-automation.yml @@ -81,8 +81,8 @@ jobs: exit 1 fi - # Extract issue numbers, filtering out those already in milestone 2 - | select(.milestone == null or .milestone.number != 2) + # Extract issue numbers, keep only those with milestone "Backlog" (number 3) + | select(.milestone != null and .milestone.number == 3) | .number' ) From 47021644ee8ae4b2f875d4687afe2ed0f62655d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 06:19:35 +0200 Subject: [PATCH 09/16] chore: Use variables for milestone IDs --- .github/workflows/milestone-automation.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/milestone-automation.yml b/.github/workflows/milestone-automation.yml index 74728ffb6..a6839e410 100644 --- a/.github/workflows/milestone-automation.yml +++ b/.github/workflows/milestone-automation.yml @@ -6,6 +6,10 @@ on: branches: - next +env: + BACKLOG_MILESTONE_ID: 3 # ID for "Backlog" milestone + SHIPPING_NEXT_MILESTONE_ID: 2 # ID for "Shipping Next" milestone + permissions: pull-requests: write issues: write @@ -21,7 +25,7 @@ jobs: id: check_milestone run: | milestone_id=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} --jq '.milestone.number') - if [[ "$milestone_id" == "3" ]]; then + if [[ "$milestone_id" == "${{ env.BACKLOG_MILESTONE_ID }}" ]]; then echo "backlog_milestone=true" >> $GITHUB_OUTPUT else echo "backlog_milestone=false" >> $GITHUB_OUTPUT @@ -34,7 +38,7 @@ jobs: run: | gh api -X PATCH \ repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }} \ - -f milestone=2 + -f milestone=${{ env.SHIPPING_NEXT_MILESTONE_ID }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -82,7 +86,7 @@ jobs: fi # Extract issue numbers, keep only those with milestone "Backlog" (number 3) - | select(.milestone != null and .milestone.number == 3) + | select(.milestone != null and .milestone.number == ${{ env.BACKLOG_MILESTONE_ID }}) | .number' ) @@ -104,7 +108,7 @@ jobs: echo "Updating milestone for issue #$issue_number" if gh api -X PATCH \ "repos/${{ github.repository }}/issues/$issue_number" \ - -f milestone=2 \ + -f milestone=${{ env.SHIPPING_NEXT_MILESTONE_ID }} \ --silent; then echo "✓ Successfully updated milestone for issue #$issue_number" updated_count=$((updated_count + 1)) From d4d36e53af00bd1cc2d53979c3ec10f25a0b18bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 09:27:32 +0200 Subject: [PATCH 10/16] chore: Fix jq filter --- .github/workflows/milestone-automation.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/milestone-automation.yml b/.github/workflows/milestone-automation.yml index a6839e410..5070cfe58 100644 --- a/.github/workflows/milestone-automation.yml +++ b/.github/workflows/milestone-automation.yml @@ -86,9 +86,11 @@ jobs: fi # Extract issue numbers, keep only those with milestone "Backlog" (number 3) + linked_issues=$(echo "$response" | jq -r ' + .data.repository.pullRequest.closingIssuesReferences.nodes[] | select(.milestone != null and .milestone.number == ${{ env.BACKLOG_MILESTONE_ID }}) - | .number' - ) + | .number + ') # Check if there are any issues to update if [[ -z "$linked_issues" ]]; then From b2f1959b79965b518b021be84ffd1c6e47fd0d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 12:18:57 +0200 Subject: [PATCH 11/16] fix: Use quoted env for the version argument To avoid invalid expansions into arbitrary commands --- .github/workflows/analyse-nextjs-release.yml | 3 ++- .github/workflows/test-against-nextjs-release.yml | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/analyse-nextjs-release.yml b/.github/workflows/analyse-nextjs-release.yml index f0f3a9629..f23d7cc52 100644 --- a/.github/workflows/analyse-nextjs-release.yml +++ b/.github/workflows/analyse-nextjs-release.yml @@ -26,9 +26,10 @@ jobs: - name: Install dependencies run: pnpm install - name: Check for changes in app router - run: ./next-release-analyser.mjs --version ${{ inputs.version }} + run: ./next-release-analyser.mjs --version "${{ env.VERSION }}" working-directory: packages/scripts env: + VERSION: ${{ inputs.version }} MAILPACE_API_TOKEN: ${{ secrets.MAILPACE_API_TOKEN }} EMAIL_ADDRESS_TO: ${{ secrets.EMAIL_ADDRESS_TO }} EMAIL_ADDRESS_FROM: ${{ secrets.EMAIL_ADDRESS_FROM }} diff --git a/.github/workflows/test-against-nextjs-release.yml b/.github/workflows/test-against-nextjs-release.yml index ec87a4932..7ed936a43 100644 --- a/.github/workflows/test-against-nextjs-release.yml +++ b/.github/workflows/test-against-nextjs-release.yml @@ -11,6 +11,7 @@ on: env: FORCE_COLOR: 3 # Diplay chalk colors + VERSION: ${{ inputs.version }} jobs: test_against_nextjs_release: @@ -30,8 +31,8 @@ jobs: cache: pnpm - name: Install dependencies run: pnpm install - - name: Install Next.js version ${{ inputs.version }} - run: pnpm add --filter e2e-next --filter nuqs next@${{ inputs.version }} + - name: Install Next.js version ${{ env.VERSION }} + run: pnpm add --filter e2e-next --filter nuqs "next@${{ env.VERSION }}" - name: Run integration tests run: pnpm run test --filter e2e-next env: @@ -45,13 +46,13 @@ jobs: if: failure() with: path: packages/e2e-next/cypress/screenshots - name: ci-next-${{ inputs.version }}${{ matrix.base-path && '-basePath' || ''}}${{ matrix.react-compiler && '-react-compiler' || ''}} + name: ci-next-${{ env.VERSION }}${{ matrix.base-path && '-basePath' || ''}}${{ matrix.react-compiler && '-react-compiler' || ''}} - uses: 47ng/actions-slack-notify@main name: Notify on Slack if: failure() with: status: ${{ job.status }} - jobName: next@${{ inputs.version }}${{ matrix.base-path && ' ⚾' || ''}}${{ matrix.react-compiler && ' ⚛️' || ''}} + jobName: next@${{ env.VERSION }}${{ matrix.base-path && ' ⚾' || ''}}${{ matrix.react-compiler && ' ⚛️' || ''}} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -73,6 +74,6 @@ jobs: - uses: 47ng/actions-slack-notify@main with: status: ${{ job.status }} - jobName: ${{ inputs.version }} + jobName: '${{ env.VERSION }}' env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} From 5e1f651d244cf1c0ac5c2b3b79d3b96cb5f47281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 12:29:22 +0200 Subject: [PATCH 12/16] fix: Validate inputs --- .github/workflows/analyse-nextjs-release.yml | 5 ++++- .github/workflows/test-against-nextjs-release.yml | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/analyse-nextjs-release.yml b/.github/workflows/analyse-nextjs-release.yml index f23d7cc52..9a5c89fac 100644 --- a/.github/workflows/analyse-nextjs-release.yml +++ b/.github/workflows/analyse-nextjs-release.yml @@ -11,12 +11,16 @@ on: env: FORCE_COLOR: 3 # Diplay chalk colors + VERSION: ${{ inputs.version }} jobs: analyse-release: runs-on: ubuntu-24.04-arm name: Check for relevant Next.js core changes steps: + - name: Ensure input follows SemVer + # Source: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + run: echo "${{ env.VERSION }}" | grep -P '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 @@ -29,7 +33,6 @@ jobs: run: ./next-release-analyser.mjs --version "${{ env.VERSION }}" working-directory: packages/scripts env: - VERSION: ${{ inputs.version }} MAILPACE_API_TOKEN: ${{ secrets.MAILPACE_API_TOKEN }} EMAIL_ADDRESS_TO: ${{ secrets.EMAIL_ADDRESS_TO }} EMAIL_ADDRESS_FROM: ${{ secrets.EMAIL_ADDRESS_FROM }} diff --git a/.github/workflows/test-against-nextjs-release.yml b/.github/workflows/test-against-nextjs-release.yml index 7ed936a43..9d323f2d6 100644 --- a/.github/workflows/test-against-nextjs-release.yml +++ b/.github/workflows/test-against-nextjs-release.yml @@ -23,6 +23,9 @@ jobs: base-path: [false, '/base'] react-compiler: [true, false] steps: + - name: Ensure input follows SemVer + # Source: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + run: echo "${{ env.VERSION }}" | grep -P '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 From 14bbca35d1ef6ce0d06c49fc535f92af2b868176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 12:38:48 +0200 Subject: [PATCH 13/16] chore: Use node to validate SemVer inputs --- .github/workflows/analyse-nextjs-release.yml | 11 ++++++++++- .github/workflows/test-against-nextjs-release.yml | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/analyse-nextjs-release.yml b/.github/workflows/analyse-nextjs-release.yml index 9a5c89fac..be5943484 100644 --- a/.github/workflows/analyse-nextjs-release.yml +++ b/.github/workflows/analyse-nextjs-release.yml @@ -20,7 +20,16 @@ jobs: steps: - name: Ensure input follows SemVer # Source: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - run: echo "${{ env.VERSION }}" | grep -P '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' + run: | + node -e " + const version = process.env.VERSION; + const semverRegex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; + if (!semverRegex.test(version)) { + console.error(\`Error: Version '\${version}' does not follow SemVer format\`); + process.exit(1); + } + console.log(\`Version '\${version}' is valid SemVer\`); + " - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 diff --git a/.github/workflows/test-against-nextjs-release.yml b/.github/workflows/test-against-nextjs-release.yml index 9d323f2d6..578d71afd 100644 --- a/.github/workflows/test-against-nextjs-release.yml +++ b/.github/workflows/test-against-nextjs-release.yml @@ -25,7 +25,16 @@ jobs: steps: - name: Ensure input follows SemVer # Source: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - run: echo "${{ env.VERSION }}" | grep -P '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' + run: | + node -e " + const version = process.env.VERSION; + const semverRegex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; + if (!semverRegex.test(version)) { + console.error(\`Error: Version '\${version}' does not follow SemVer format\`); + process.exit(1); + } + console.log(\`Version '\${version}' is valid SemVer\`); + " - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 From 5db40e9988c443adea1eb3d7c1350ea3ccaaaa50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 12:56:38 +0200 Subject: [PATCH 14/16] chore: Prevent injection from PR title --- .github/workflows/pr-lint.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 4008e0635..a4f572f89 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -18,6 +18,23 @@ jobs: node-version-file: .node-version cache: pnpm - name: Install dependencies - run: pnpm install --ignore-scripts --frozen-lockfile --workspace-root + run: pnpm add -D @commitlint/load @commitlint/lint - name: Lint PR title - run: echo "${{ github.event.pull_request.title }}" | ./node_modules/.bin/commitlint > $GITHUB_STEP_SUMMARY + env: + TITLE: ${{ github.event.pull_request.title }} + run: | + node -e " + import loadConfig from '@commitlint/load' + import lint from '@commitlint/lint' + import pkgJson from './package.json' assert { type: 'json' } + const config = await loadConfig(pkgJson.commitlint) + const result = await lint(process.env.TITLE, config.rules, config.parserPreset ? { parserOpts: config.parserPreset.parserOpts } : {}) + process.env.GITHUB_STEP_SUMMARY += `## Linting Result\n- Valid: ${result.valid}\n` + if (!result.valid) { + for (const { message } of result.errors) { + process.env.GITHUB_STEP_SUMMARY += `- Error: ${message}\n` + console.error(message) + } + process.exit(1) + } + " From ebd3a22181217081e5b037e20bc1907dc13fc8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 13:09:37 +0200 Subject: [PATCH 15/16] chore: Formatting --- .github/workflows/pr-lint.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index a4f572f89..b064e6f93 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -24,17 +24,17 @@ jobs: TITLE: ${{ github.event.pull_request.title }} run: | node -e " - import loadConfig from '@commitlint/load' - import lint from '@commitlint/lint' - import pkgJson from './package.json' assert { type: 'json' } - const config = await loadConfig(pkgJson.commitlint) - const result = await lint(process.env.TITLE, config.rules, config.parserPreset ? { parserOpts: config.parserPreset.parserOpts } : {}) - process.env.GITHUB_STEP_SUMMARY += `## Linting Result\n- Valid: ${result.valid}\n` - if (!result.valid) { + import loadConfig from '@commitlint/load'; + import lint from '@commitlint/lint'; + import pkgJson from './package.json' with { type: 'json' }; + const config = await loadConfig(pkgJson.commitlint); + const result = await lint(process.env.TITLE, config.rules, config.parserPreset ? { parserOpts: config.parserPreset.parserOpts } : {}); + process.env.GITHUB_STEP_SUMMARY += \`## Linting Result\n- Valid: \${result.valid}\n\`; + if (\!result.valid) { for (const { message } of result.errors) { - process.env.GITHUB_STEP_SUMMARY += `- Error: ${message}\n` - console.error(message) + process.env.GITHUB_STEP_SUMMARY += \`- Error: \${message}\n\`; + console.error(message); } - process.exit(1) + process.exit(1); } " From 2632fe2d7d7db4c5ee58840bd3ac3b17766d8c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Sun, 31 Aug 2025 13:12:41 +0200 Subject: [PATCH 16/16] chore: Don't use ! --- .github/workflows/pr-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index b064e6f93..8b729b4b7 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -30,7 +30,7 @@ jobs: const config = await loadConfig(pkgJson.commitlint); const result = await lint(process.env.TITLE, config.rules, config.parserPreset ? { parserOpts: config.parserPreset.parserOpts } : {}); process.env.GITHUB_STEP_SUMMARY += \`## Linting Result\n- Valid: \${result.valid}\n\`; - if (\!result.valid) { + if (result.valid === false) { for (const { message } of result.errors) { process.env.GITHUB_STEP_SUMMARY += \`- Error: \${message}\n\`; console.error(message);