diff --git a/.github/workflows/analyse-nextjs-release.yml b/.github/workflows/analyse-nextjs-release.yml index f0f3a9629..be5943484 100644 --- a/.github/workflows/analyse-nextjs-release.yml +++ b/.github/workflows/analyse-nextjs-release.yml @@ -11,12 +11,25 @@ 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: | + 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 @@ -26,7 +39,7 @@ 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: MAILPACE_API_TOKEN: ${{ secrets.MAILPACE_API_TOKEN }} diff --git a/.github/workflows/milestone-automation.yml b/.github/workflows/milestone-automation.yml index 5a05ecc74..5070cfe58 100644 --- a/.github/workflows/milestone-automation.yml +++ b/.github/workflows/milestone-automation.yml @@ -6,8 +6,13 @@ 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 jobs: update_milestones: @@ -20,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 @@ -33,6 +38,94 @@ 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 }} + + - name: Update linked issues milestone to "shipping next" + if: steps.check_milestone.outputs.backlog_milestone == 'true' + run: | + set -euo pipefail # 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, 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 + ') + + # 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=${{ env.SHIPPING_NEXT_MILESTONE_ID }} \ + --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 }} 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 diff --git a/.github/workflows/pr-base-enforcement.yml b/.github/workflows/pr-base-enforcement.yml index aa072e393..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\`." + 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 diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 4008e0635..8b729b4b7 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' 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 === false) { + for (const { message } of result.errors) { + process.env.GITHUB_STEP_SUMMARY += \`- Error: \${message}\n\`; + console.error(message); + } + process.exit(1); + } + " 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 }} diff --git a/.github/workflows/test-against-nextjs-release.yml b/.github/workflows/test-against-nextjs-release.yml index ec87a4932..578d71afd 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: @@ -22,6 +23,18 @@ 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: | + 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 @@ -30,8 +43,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 +58,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 +86,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 }}