diff --git a/.github/actions/create-check/action.yml b/.github/actions/create-check/action.yml new file mode 100644 index 00000000..0e7d6ce0 --- /dev/null +++ b/.github/actions/create-check/action.yml @@ -0,0 +1,52 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Create Check' +inputs: + name: + required: true + token: + required: true + sha: + required: true + check-name: + default: '' +outputs: + check-id: + value: ${{ steps.create-check.outputs.check_id }} +runs: + using: "composite" + steps: + - name: Get Workflow Job + uses: actions/github-script@v6 + id: workflow + env: + JOB_NAME: "${{ inputs.name }}" + SHA: "${{ inputs.sha }}" + with: + result-encoding: string + script: | + const { repo: { owner, repo}, runId, serverUrl } = context + const { JOB_NAME, SHA } = process.env + + const job = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: runId, + per_page: 100 + }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME))) + + return [ + `This check is assosciated with ${serverUrl}/${owner}/${repo}/commit/${SHA}.`, + 'Run logs:', + job?.html_url || `could not be found for a job ending with: "${JOB_NAME}"`, + ].join(' ') + - name: Create Check + uses: LouisBrunner/checks-action@v1.6.0 + id: create-check + with: + token: ${{ inputs.token }} + sha: ${{ inputs.sha }} + status: in_progress + name: ${{ inputs.check-name || inputs.name }} + output: | + {"summary":"${{ steps.workflow.outputs.result }}"} diff --git a/.github/actions/install-latest-npm/action.yml b/.github/actions/install-latest-npm/action.yml new file mode 100644 index 00000000..f2e86c84 --- /dev/null +++ b/.github/actions/install-latest-npm/action.yml @@ -0,0 +1,57 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Install Latest npm' +description: 'Install the latest version of npm compatible with the Node version' +inputs: + node: + description: 'Current Node version' + required: true +runs: + using: "composite" + steps: + # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows + - name: Update Windows npm + if: | + runner.os == 'Windows' && ( + startsWith(inputs.node, 'v10.') || + startsWith(inputs.node, 'v12.') || + startsWith(inputs.node, 'v14.') + ) + shell: cmd + run: | + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz + tar xf npm-7.5.4.tgz + cd package + node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz + cd .. + rmdir /s /q package + - name: Install Latest npm + shell: bash + env: + NODE_VERSION: ${{ inputs.node }} + run: | + MATCH="" + SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") + + echo "node@$NODE_VERSION" + + for SPEC in ${SPECS[@]}; do + ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') + echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" + + if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then + MATCH=$SPEC + echo "Found compatible version: npm@$MATCH" + break + fi + done + + if [ -z $MATCH ]; then + echo "Could not find a compatible version of npm for node@$NODE_VERSION" + exit 1 + fi + + npm i --prefer-online --no-fund --no-audit -g npm@$MATCH + - name: npm Version + shell: bash + run: npm -v diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 908ae169..fa3163a8 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -29,37 +29,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund --package-lock - name: Run Production Audit diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 47af73ef..165fed0a 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -27,49 +27,6 @@ jobs: run: shell: bash steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Lint All" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ inputs.check-sha }}` - - let summary = `This check is assosciated with ${shaUrl}\n\n` - - if (jobUrl) { - summary += `For run logs, click here: ${jobUrl}` - } else { - summary += `Run logs could not be found for a job with name: "${jobName}"` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: ${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Lint All - sha: ${{ inputs.check-sha }} - output: ${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -78,43 +35,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: ${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Lint All" + token: ${{ secrets.GITHUB_TOKEN }} + sha: ${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -123,11 +61,11 @@ jobs: run: npm run postlint --ignore-scripts -ws -iwr --if-present - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: ${{ secrets.GITHUB_TOKEN }} conclusion: ${{ job.status }} - check_id: ${{ steps.check.outputs.check_id }} + check_id: ${{ steps.create-check.outputs.check-id }} test-all: name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }} @@ -155,49 +93,6 @@ jobs: run: shell: ${{ matrix.platform.shell }} steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Test All" - MATRIX_NAME: " - ${{ matrix.platform.name }} - ${{ matrix.node-version }}" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ inputs.check-sha }}` - - let summary = `This check is assosciated with ${shaUrl}\n\n` - - if (jobUrl) { - summary += `For run logs, click here: ${jobUrl}` - } else { - summary += `Run logs could not be found for a job with name: "${jobName}"` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: ${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }} - sha: ${{ inputs.check-sha }} - output: ${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -206,59 +101,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: ${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}" + token: ${{ secrets.GITHUB_TOKEN }} + sha: ${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: ${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -267,8 +127,8 @@ jobs: run: npm test --ignore-scripts -ws -iwr --if-present - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: ${{ secrets.GITHUB_TOKEN }} conclusion: ${{ job.status }} - check_id: ${{ steps.check.outputs.check_id }} + check_id: ${{ steps.create-check.outputs.check-id }} diff --git a/.github/workflows/ci-test-workspace.yml b/.github/workflows/ci-test-workspace.yml index 10789010..5d05b6d6 100644 --- a/.github/workflows/ci-test-workspace.yml +++ b/.github/workflows/ci-test-workspace.yml @@ -37,37 +37,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -113,53 +86,10 @@ jobs: with: node-version: ${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61f6c92a..a458d931 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,37 +37,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -113,53 +86,10 @@ jobs: with: node-version: ${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher diff --git a/.github/workflows/post-dependabot.yml b/.github/workflows/post-dependabot.yml index 9a4b7611..11a7b7c8 100644 --- a/.github/workflows/post-dependabot.yml +++ b/.github/workflows/post-dependabot.yml @@ -30,37 +30,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Fetch Dependabot Metadata diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3418d4c4..0b5789e0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -33,47 +33,18 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Run Commitlint on Commits id: commit continue-on-error: true - run: | - npx --offline commitlint -V --from 'origin/${{ github.base_ref }}' --to ${{ github.event.pull_request.head.sha }} + run: npx --offline commitlint -V --from 'origin/${{ github.base_ref }}' --to ${{ github.event.pull_request.head.sha }} - name: Run Commitlint on PR Title if: steps.commit.outcome == 'failure' env: PR_TITLE: ${{ github.event.pull_request.title }} - run: | - echo "$PR_TITLE" | npx --offline commitlint -V + run: echo "$PR_TITLE" | npx --offline commitlint -V diff --git a/.github/workflows/release-integration.yml b/.github/workflows/release-integration.yml new file mode 100644 index 00000000..adc52786 --- /dev/null +++ b/.github/workflows/release-integration.yml @@ -0,0 +1,74 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Release Integration + +on: + workflow_dispatch: + inputs: + releases: + required: true + type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + workflow_call: + inputs: + releases: + required: true + type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + defaults: + run: + shell: bash + permissions: + deployments: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ fromJSON(inputs.releases)[0].tagName }} + - name: Setup Git User + run: | + git config --global user.email "npm-cli+bot@github.com" + git config --global user.name "npm CLI robot" + - name: Setup Node + uses: actions/setup-node@v3 + id: node + with: + node-version: 20.x + check-latest: contains('20.x', '.x') + - name: Install Latest npm + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} + - name: Install Dependencies + run: npm i --ignore-scripts --no-audit --no-fund + - name: Set npm authToken + run: npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN} + - name: Publish + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + run: | + EXIT_CODE=0 + + function each_release { + if npm publish --provenance --tag="$1"; then + echo 0 + else + echo 1 + fi + } + + for release in $(echo '${{ inputs.releases }}' | jq -r '.[] | @base64'); do + PUBLISH_TAG=$(echo "$release" | base64 --decode | jq -r .publishTag) + STATUS=$(each_release "$PUBLISH_TAG") + if [[ "$STATUS" -eq 1 ]]; then + EXIT_CODE=$STATUS + fi + done + + exit $EXIT_CODE diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca20ccba..88ea5829 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,11 +3,6 @@ name: Release on: - workflow_dispatch: - inputs: - release-pr: - description: a release PR number to rerun release jobs on - type: string push: branches: - main @@ -21,12 +16,12 @@ jobs: release: outputs: pr: ${{ steps.release.outputs.pr }} - release: ${{ steps.release.outputs.release }} - releases: ${{ steps.release.outputs.releases }} - branch: ${{ steps.release.outputs.pr-branch }} + pr-branch: ${{ steps.release.outputs.pr-branch }} pr-number: ${{ steps.release.outputs.pr-number }} - comment-id: ${{ steps.pr-comment.outputs.result }} - check-id: ${{ steps.check.outputs.check_id }} + pr-sha: ${{ steps.release.outputs.pr-sha }} + releases: ${{ steps.release.outputs.releases }} + comment-id: ${{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }} + check-id: ${{ steps.create-check.outputs.check-id }} name: Release if: github.repository_owner == 'npm' runs-on: ubuntu-latest @@ -46,126 +41,64 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Release Please id: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - npx --offline template-oss-release-please "${{ github.ref_name }}" "${{ inputs.release-pr }}" - - name: Post Pull Request Comment + run: npx --offline template-oss-release-please --branch="${{ github.ref_name }}" --backport="" --defaultTag="latest" + - name: Create Release Manager Comment Text if: steps.release.outputs.pr-number uses: actions/github-script@v6 - id: pr-comment - env: - PR_NUMBER: ${{ steps.release.outputs.pr-number }} - REF_NAME: ${{ github.ref_name }} + id: comment-text with: + result-encoding: string script: | - const { REF_NAME, PR_NUMBER: issue_number } = process.env const { runId, repo: { owner, repo } } = context - const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId }) - - let body = '## Release Manager\n\n' - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - let commentId = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id - - body += `Release workflow run: ${workflow.html_url}\n\n#### Force CI to Update This Release\n\n` - body += `This PR will be updated and CI will run for every non-\`chore:\` commit that is pushed to \`${REF_NAME}\`. ` - body += `To force CI to update this PR, run this command:\n\n` - body += `\`\`\`\ngh workflow run release.yml -r ${REF_NAME} -R ${owner}/${repo} -f release-pr=${issue_number}\n\`\`\`` - - if (commentId) { - await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, body }) - } else { - const { data: comment } = await github.rest.issues.createComment({ owner, repo, issue_number, body }) - commentId = comment?.id - } - - return commentId - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.release.outputs.pr-sha - id: check-output - env: - JOB_NAME: "Release" - MATRIX_NAME: "" + return['## Release Manager', `Release workflow run: ${workflow.html_url}`].join('\n\n') + - name: Find Release Manager Comment + uses: peter-evans/find-comment@v2 + if: steps.release.outputs.pr-number + id: found-comment with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ steps.release.outputs.pr-sha }}` - - let summary = `This check is assosciated with ${shaUrl}\n\n` - - if (jobUrl) { - summary += `For run logs, click here: ${jobUrl}` - } else { - summary += `Run logs could not be found for a job with name: "${jobName}"` - } - - return { summary } + issue-number: ${{ steps.release.outputs.pr-number }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Manager' + - name: Create Release Manager Comment + id: create-comment + if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ steps.release.outputs.pr-number }} + body: ${{ steps.comment-text.outputs.result }} + - name: Update Release Manager Comment + id: update-comment + if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.found-comment.outputs.comment-id }} + body: ${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check + id: create-check + uses: ./.github/actions/create-check if: steps.release.outputs.pr-sha with: + name: "Release" token: ${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: ${{ steps.release.outputs.pr-sha }} - output: ${{ steps.check-output.outputs.result }} update: needs: release outputs: sha: ${{ steps.commit.outputs.sha }} - check-id: ${{ steps.check.outputs.check_id }} + check-id: ${{ steps.create-check.outputs.check-id }} name: Update - Release if: github.repository_owner == 'npm' && needs.release.outputs.pr runs-on: ubuntu-latest @@ -177,7 +110,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - ref: ${{ needs.release.outputs.branch }} + ref: ${{ needs.release.outputs.pr-branch }} - name: Setup Git User run: | git config --global user.email "npm-cli+bot@github.com" @@ -188,47 +121,27 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: ${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: ${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund + - name: Create Release Manager Checklist Text + id: comment-text + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm exec --offline -- template-oss-release-manager --pr="${{ needs.release.outputs.pr-number }}" --backport="" --defaultTag="latest" --publish + - name: Append Release Manager Comment + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ needs.release.outputs.comment-id }} + body: ${{ steps.comment-text.outputs.result }} + edit-mode: 'append' - name: Run Post Pull Request Actions env: - RELEASE_PR_NUMBER: ${{ needs.release.outputs.pr-number }} - RELEASE_COMMENT_ID: ${{ needs.release.outputs.comment-id }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - npm exec --offline -- template-oss-release-manager --lockfile=false --publish=true - npm run rp-pull-request --ignore-scripts -ws -iwr --if-present + run: npm run rp-pull-request --ignore-scripts -ws -iwr --if-present -- --pr="${{ needs.release.outputs.pr-number }}" --commentId="${{ needs.release.outputs.comment-id }}" - name: Commit id: commit env: @@ -237,52 +150,16 @@ jobs: git commit --all --amend --no-edit || true git push --force-with-lease echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.commit.outputs.sha - id: check-output - env: - JOB_NAME: "Update - Release" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ steps.commit.outputs.sha }}` - - let summary = `This check is assosciated with ${shaUrl}\n\n` - - if (jobUrl) { - summary += `For run logs, click here: ${jobUrl}` - } else { - summary += `Run logs could not be found for a job with name: "${jobName}"` - } - - return { summary } - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: steps.commit.outputs.sha + id: create-check + uses: ./.github/actions/create-check with: + name: "Update - Release" + check-name: "Release" token: ${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: ${{ steps.commit.outputs.sha }} - output: ${{ steps.check-output.outputs.result }} - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.release.outputs.check-id && always() with: token: ${{ secrets.GITHUB_TOKEN }} conclusion: ${{ job.status }} @@ -294,7 +171,7 @@ jobs: if: needs.release.outputs.pr uses: ./.github/workflows/ci-release.yml with: - ref: ${{ needs.release.outputs.branch }} + ref: ${{ needs.release.outputs.pr-branch }} check-sha: ${{ needs.update.outputs.sha }} post-ci: @@ -306,8 +183,8 @@ jobs: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get CI Conclusion + id: conclusion run: | result="" if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then @@ -320,14 +197,15 @@ jobs: echo "result=$result" >> $GITHUB_OUTPUT - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.update.outputs.check-id && always() with: token: ${{ secrets.GITHUB_TOKEN }} - conclusion: ${{ steps.needs-result.outputs.result }} + conclusion: ${{ steps.conclusion.outputs.result }} check_id: ${{ needs.update.outputs.check-id }} post-release: needs: release + outputs: + comment-id: ${{ steps.create-comment.outputs.comment-id }} name: Post Release - Release if: github.repository_owner == 'npm' && needs.release.outputs.releases runs-on: ubuntu-latest @@ -335,79 +213,50 @@ jobs: run: shell: bash steps: - - name: Create Release PR Comment + - name: Create Release PR Comment Text + id: comment-text uses: actions/github-script@v6 env: RELEASES: ${{ needs.release.outputs.releases }} with: + result-encoding: string script: | const releases = JSON.parse(process.env.RELEASES) const { runId, repo: { owner, repo } } = context const issue_number = releases[0].prNumber - - let body = '## Release Workflow\n\n' - for (const { pkgName, version, url } of releases) { - body += `- \`${pkgName}@${version}\` ${url}\n` - } - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - .then(cs => cs.map(c => ({ id: c.id, login: c.user.login, body: c.body }))) - console.log(`Found comments: ${JSON.stringify(comments, null, 2)}`) - const releaseComments = comments.filter(c => c.login === 'github-actions[bot]' && c.body.includes('Release is at')) - - for (const comment of releaseComments) { - console.log(`Release comment: ${JSON.stringify(comment, null, 2)}`) - await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id }) - } - const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}` - await github.rest.issues.createComment({ - owner, - repo, - issue_number, - body: `${body}- Workflow run: :arrows_counterclockwise: ${runUrl}`, - }) + + return [ + '## Release Workflow\n', + ...releases.map(r => `- \`${r.pkgName}@${r.version}\` ${r.url}`), + `- Workflow run: :arrows_counterclockwise: ${runUrl}`, + ].join('\n') + - name: Create Release PR Comment + id: create-comment + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + body: ${{ steps.comment-text.outputs.result }} release-integration: needs: release name: Release Integration - if: needs.release.outputs.release - runs-on: ubuntu-latest - defaults: - run: - shell: bash - permissions: - deployments: write - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ fromJSON(needs.release.outputs.release).tagName }} - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 18.x - - name: Install npm@latest - run: | - npm i --prefer-online --no-fund --no-audit -g npm@latest - npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN} - - name: Publish - env: - PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} - run: npm publish --provenance --tag=latest + if: needs.release.outputs.releases + uses: ./.github/workflows/release-integration.yml + with: + releases: ${{ needs.release.outputs.releases }} post-release-integration: - needs: [ release, release-integration ] + needs: [ release, release-integration, post-release ] name: Post Release Integration - Release - if: github.repository_owner == 'npm' && needs.release.outputs.release && always() + if: github.repository_owner == 'npm' && needs.release.outputs.releases && always() runs-on: ubuntu-latest defaults: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get Post Release Conclusion + id: conclusion run: | if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then result="x" @@ -417,39 +266,38 @@ jobs: result="white_check_mark" fi echo "result=$result" >> $GITHUB_OUTPUT - - name: Update Release PR Comment + - name: Find Release PR Comment + uses: peter-evans/find-comment@v2 + id: found-comment + with: + issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Workflow' + - name: Create Release PR Comment Text + id: comment-text + if: steps.found-comment.outputs.comment-id uses: actions/github-script@v6 env: - PR_NUMBER: ${{ fromJSON(needs.release.outputs.release).prNumber }} - RESULT: ${{ steps.needs-result.outputs.result }} + RESULT: ${{ steps.conclusion.outputs.result }} + BODY: ${{ steps.found-comment.outputs.comment-body }} with: + result-encoding: string script: | - const { PR_NUMBER: issue_number, RESULT } = process.env - const { runId, repo: { owner, repo } } = context - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - const updateComment = comments.find(c => - c.user.login === 'github-actions[bot]' && - c.body.startsWith('## Release Workflow\n\n') && - c.body.includes(runId) - ) - - if (updateComment) { - console.log('Found comment to update:', JSON.stringify(updateComment, null, 2)) - let body = updateComment.body.replace(/Workflow run: :[a-z_]+:/, `Workflow run: :${RESULT}:`) - const tagCodeowner = RESULT !== 'white_check_mark' - if (tagCodeowner) { - body += `\n\n:rotating_light:` - body += ` @npm/cli-team: The post-release workflow failed for this release.` - body += ` Manual steps may need to be taken after examining the workflow output` - body += ` from the above workflow run. :rotating_light:` - } - await github.rest.issues.updateComment({ - owner, - repo, - body, - comment_id: updateComment.id, - }) - } else { - console.log('No matching comments found:', JSON.stringify(comments, null, 2)) + const { RESULT, BODY } = process.env + const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, `$1${RESULT}$2`)] + if (RESULT !== 'white_check_mark') { + body.push(':rotating_light::rotating_light::rotating_light:') + body.push([ + '@npm/cli-team: The post-release workflow failed for this release.', + 'Manual steps may need to be taken after examining the workflow output.' + ].join(' ')) + body.push(':rotating_light::rotating_light::rotating_light:') } + return body.join('\n\n').trim() + - name: Update Release PR Comment + if: steps.comment-text.outputs.result + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.found-comment.outputs.comment-id }} + body: ${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' diff --git a/bin/release-manager.js b/bin/release-manager.js index 0a7fcca3..66789d92 100755 --- a/bin/release-manager.js +++ b/bin/release-manager.js @@ -1,283 +1,31 @@ #!/usr/bin/env node -const { Octokit } = require('@octokit/rest') -const semver = require('semver') -const mapWorkspaces = require('@npmcli/map-workspaces') const { join } = require('path') +const core = require('@actions/core') +const { parseArgs } = require('util') +const ReleaseManager = require('../lib/release/release-manager') -const log = (...logs) => console.error('LOG', ...logs) - -const ROOT = process.cwd() -const pkg = require(join(ROOT, 'package.json')) - -const args = process.argv.slice(2).reduce((acc, a) => { - const [k, v] = a.replace(/^--/g, '').split('=') - acc[k] = v === 'true' - return acc -}, {}) - -/* eslint-disable max-len */ -const MANUAL_PUBLISH_STEPS = ` -1. Checkout the release branch and test - - \`\`\`sh - gh pr checkout --force - npm ${args.lockfile ? 'ci' : 'update'} - npm test - gh pr checks -R {NWO} --watch - \`\`\` - -1. Publish workspaces - - \`\`\`sh - npm publish -w - \`\`\` - -1. Publish - - \`\`\`sh - npm publish - \`\`\` - -1. Merge release PR - - \`\`\`sh - gh pr merge -R {NWO} --rebase - git checkout - git fetch - git reset --hard origin/ - \`\`\` -` - -const AUTO_PUBLISH_STEPS = ` -1. Approve this PR - - \`\`\`sh - gh pr review -R {NWO} --approve - \`\`\` - -1. Merge release PR :rotating_light: Merging this will auto publish :rotating_light: - - \`\`\`sh - gh pr merge -R {NWO} --rebase - \`\`\` -` - -const DEFAULT_RELEASE_PROCESS = (args.publish ? AUTO_PUBLISH_STEPS : MANUAL_PUBLISH_STEPS) + ` -1. Check For Release Tags - - Release Please will run on the just pushed release commit and create GitHub releases and tags for each package. - - \`\`\` - gh run watch -R {NWO} $(gh run list -R {NWO} -w release -b -L 1 --json databaseId -q ".[0].databaseId") - \`\`\` -` -/* eslint-enable max-len */ - -const getReleaseProcess = async ({ owner, repo }) => { - const RELEASE_LIST_ITEM = /^\d+\.\s/gm - - log(`Fetching release process from:`, owner, repo, 'wiki') - - let releaseProcess = '' - try { - releaseProcess = await new Promise((resolve, reject) => { - require('https') - .get(`https://raw.githubusercontent.com/wiki/${owner}/${repo}/Release-Process.md`, resp => { - let d = '' - resp.on('data', c => (d += c)) - resp.on('end', () => { - if (resp.statusCode !== 200) { - reject(new Error(`${resp.req.protocol + resp.req.host + resp.req.path}: ${d}`)) - } else { - resolve(d) - } - }) - }) - .on('error', reject) - }) - } catch (e) { - log('Release wiki not found', e.message) - log('Using default release process') - releaseProcess = DEFAULT_RELEASE_PROCESS.replace(/\{NWO\}/g, `${owner}/${repo}`).trim() + '\n' - } - - // XXX: the release steps need to always be the last thing in the doc for this to work - const releaseLines = releaseProcess.split('\n') - const releaseStartLine = releaseLines.reduce((acc, line, index) => - line.match(/^#+\s/) ? index : acc, 0) - const section = releaseLines.slice(releaseStartLine).join('\n') - - return section.split({ - [Symbol.split] (str) { - const [, ...matches] = str.split(RELEASE_LIST_ITEM) - log(`Found ${matches.length} release items`) - return matches.map((m) => `- [ ] . ${m}`.trim()) - }, - }) -} - -const getPrReleases = async (pr) => { - const RELEASE_SEPARATOR = /
.*<\/summary>/g - const MONO_VERSIONS = /
(?:(.*?):\s)?(.*?)<\/summary>/ - const ROOT_VERSION = /\n##\s\[(.*?)\]/ - - const workspaces = [...await mapWorkspaces({ pkg: pkg, cwd: ROOT })].reduce((acc, [k]) => { - const wsComponentName = k.startsWith('@') ? k.split('/')[1] : k - acc[wsComponentName] = k - return acc - }, {}) - - const getReleaseInfo = ({ name, version: rawVersion }) => { - const version = semver.parse(rawVersion) - const prerelease = !!version.prerelease.length - const tag = `${name ? `${name}-` : ''}v${rawVersion}` - const workspace = workspaces[name] - - return { - name, - tag, - prerelease, - version: rawVersion, - major: version.major, - url: `https://github.com/${pr.base.repo.full_name}/releases/tag/${tag}`, - flags: `${name ? `-w ${workspace}` : ''} ${prerelease ? `--tag prerelease` : ''}`.trim(), - } - } - - const releases = pr.body.match(RELEASE_SEPARATOR) - - if (!releases) { - log('Found no monorepo, checking for single root version') - const [, version] = pr.body.match(ROOT_VERSION) || [] - - if (!version) { - throw new Error('Could not find version with:', ROOT_VERSION) - } - - log('Found version', version) - return [getReleaseInfo({ version })] - } - - log(`Found ${releases.length} releases`) - - return releases.reduce((acc, r) => { - const [, name, version] = r.match(MONO_VERSIONS) - const release = getReleaseInfo({ name, version }) - - if (!name) { - log('Found root', release) - acc[0] = release - } else { - log('Found workspace', release) - acc[1].push(release) - } - - return acc - }, [null, []]) -} - -const appendToComment = async ({ github, commentId, title, body }) => { - if (!commentId) { - log(`No comment id, skipping append to comment`) - return - } - - const { data: comment } = await github.rest.issues.getComment({ - ...github.repo, - comment_id: commentId, - }) - - const hasAppended = comment.body.includes(title) - - log('Found comment with id:', commentId) - log(hasAppended ? 'Comment has aready been appended, replacing' : 'Appending to comment') - - const prefix = hasAppended - ? comment.body.split(title)[0] - : comment.body - - return github.rest.issues.updateComment({ - ...github.repo, - comment_id: commentId, - body: [prefix, title, body].join('\n\n'), - }) -} - -const main = async (env) => { +ReleaseManager.run({ // These env vars are set by the release.yml workflow from template-oss - const { - CI, - GITHUB_TOKEN, - GITHUB_REPOSITORY, - RELEASE_PR_NUMBER, - RELEASE_COMMENT_ID, // comment is optional for testing - } = env - - if (!CI || !GITHUB_TOKEN || !GITHUB_REPOSITORY || !RELEASE_PR_NUMBER) { - throw new Error('This script is designed to run in CI. If you want to test it, set the ' + - `following env vars: \`CI, GITHUB_TOKEN, GITHUB_REPOSITORY, RELEASE_PR_NUMBER\``) - } - - const [owner, repo] = GITHUB_REPOSITORY.split('/') - const github = new Octokit({ auth: GITHUB_TOKEN }) - github.repo = { owner, repo } - - const { data: pr } = await github.rest.pulls.get({ - ...github.repo, - pull_number: RELEASE_PR_NUMBER, + token: process.env.GITHUB_TOKEN, + repo: process.env.GITHUB_REPOSITORY, + cwd: process.cwd(), + pkg: require(join(process.cwd(), 'package.json')), + ...parseArgs({ + options: { + pr: { type: 'string' }, + backport: { type: 'string' }, + defaultTag: { type: 'string' }, + lockfile: { type: 'boolean' }, + publish: { type: 'boolean' }, + }, + }).values, +}) + .then((result) => { + core.setOutput('result', result) + return null }) - - const [release, workspaces = []] = await getPrReleases(pr) - - const RELEASE_OMIT_PRERELEASE = '> NOT FOR PRERELEASE' - const RELEASE_OMIT_WORKSPACES = 'Publish workspaces' - const releaseItems = (await getReleaseProcess({ owner, repo })) - .filter((item) => { - if (release.prerelease && item.includes(RELEASE_OMIT_PRERELEASE)) { - return false - } - - if (!workspaces.length && item.includes(RELEASE_OMIT_WORKSPACES)) { - return false - } - - return true - }) - .map((item, index) => item.replace('', index + 1)) - - log( - `Filtered ${releaseItems.length} release process items:\n`, - releaseItems.map(r => r.split('\n')[0].replace('- [ ] ', '')).join(', ') - ) - - const releaseTitle = `### Release Checklist for ${release.tag}` - const releaseChecklist = releaseItems - .join('\n\n') - .replace(//g, RELEASE_PR_NUMBER) - .replace(//g, pr.head.ref) - .replace(//g, pr.base.ref) - .replace(//g, release.major) - .replace(//g, release.version) - .replace(//g, release.url) - .replace(//g, release.flags) - .replace(/^(\s*\S.*)(-w )$/gm, workspaces.map(w => `$1${w.flags}`).join('\n')) - .trim() - - await appendToComment({ - github, - commentId: RELEASE_COMMENT_ID, - title: releaseTitle, - body: releaseChecklist, + .catch(err => { + core.setFailed('Release Manager failed') + core.error(err) }) - - if (!RELEASE_COMMENT_ID) { - console.log(releaseChecklist) - } -} - -main(process.env) - // This is part of the release CI and is for posting a release manager - // comment to the issue but we dont want it to ever fail the workflow so - // just log but dont set the error code - .catch(err => console.error(err)) diff --git a/bin/release-please.js b/bin/release-please.js index 6237d02b..f11a4dc9 100755 --- a/bin/release-please.js +++ b/bin/release-please.js @@ -1,75 +1,40 @@ #!/usr/bin/env node const core = require('@actions/core') -const main = require('../lib/release-please/index.js') +const { parseArgs } = require('util') +const ReleasePlease = require('../lib/release/release-please.js') -const dryRun = !process.env.CI -const [branch, forcePullRequest] = process.argv.slice(2) - -const debugPr = (val) => { - if (dryRun) { - console.log('PR:', val.title.toString()) - console.log('='.repeat(40)) - console.log(val.body.toString()) - console.log('='.repeat(40)) - for (const update of val.updates.filter(u => u.updater.changelogEntry)) { - console.log('CHANGELOG:', update.path) - console.log('-'.repeat(40)) - console.log(update.updater.changelogEntry) - console.log('-'.repeat(40)) - } - for (const update of val.updates.filter(u => u.updater.rawContent)) { - console.log('package:', update.path) - console.log('-'.repeat(40)) - console.log(JSON.parse(update.updater.rawContent).name) - console.log(JSON.parse(update.updater.rawContent).version) - console.log('-'.repeat(40)) - } - } -} - -const debugRelease = (val) => { - if (dryRun) { - console.log('ROOT RELEASE:', JSON.stringify(val, null, 2)) - } -} - -const debugReleases = (val) => { - if (dryRun) { - console.log('ALL RELEASES:', JSON.stringify(val, null, 2)) - } -} - -main({ +ReleasePlease.run({ token: process.env.GITHUB_TOKEN, repo: process.env.GITHUB_REPOSITORY, - dryRun, - branch, - forcePullRequest: forcePullRequest ? +forcePullRequest : null, -}).then(({ pr, release, releases }) => { + ...parseArgs({ + options: { + branch: { type: 'string' }, + backport: { type: 'string' }, + defaultTag: { type: 'string' }, + }, + }).values, + // This is mostly for testing and debugging. Use environs with the format + // `RELEASE_PLEASE_` + // (eg`RELEASE_PLEASE_lastReleaseSha=`) to set one-off config items for + // the release please run without needing to commit and push the config. + overrides: Object.fromEntries(Object.entries(process.env) + .filter(([k, v]) => k.startsWith('RELEASE_PLEASE_') && v != null) + .map(([k, v]) => [k.replace('RELEASE_PLEASE_', ''), v])), +}).then(({ pr, releases }) => { if (pr) { - debugPr(pr) core.setOutput('pr', JSON.stringify(pr)) core.setOutput('pr-branch', pr.headBranchName) core.setOutput('pr-number', pr.number) core.setOutput('pr-sha', pr.sha) } - if (release) { - debugRelease(release) - core.setOutput('release', JSON.stringify(release)) - } - if (releases) { - debugReleases(releases) core.setOutput('releases', JSON.stringify(releases)) } return null }).catch(err => { - if (dryRun) { - console.error(err) - } else { - core.setFailed(`failed: ${err}`) - } + core.setFailed('Release Please failed') + core.error(err) }) diff --git a/lib/config.js b/lib/config.js index 789cb5c2..f8d51dc7 100644 --- a/lib/config.js +++ b/lib/config.js @@ -119,7 +119,6 @@ const getFullConfig = async ({ const gitBranches = await git.getBranches(rootPkg.path, branches) const defaultBranch = await git.defaultBranch(rootPkg.path) ?? 'main' const isReleaseBranch = !!pkgConfig.backport - const publishTag = isReleaseBranch ? `next-${pkgConfig.backport}` : 'latest' const releaseBranch = isReleaseBranch ? pkgConfig.releaseBranch.replace(/\*/g, pkgConfig.backport) : defaultBranch @@ -148,7 +147,6 @@ const getFullConfig = async ({ branchPatterns: gitBranches.patterns, isReleaseBranch, releaseBranch, - publishTag, dependabot: parseDependabot(pkgConfig, defaultConfig, gitBranches.branches), // paths repoDir: rootPkg.path, @@ -168,6 +166,7 @@ const getFullConfig = async ({ deleteJsExt: esm ? 'js' : 'cjs', // tap tap18: semver.coerce(pkg.pkgJson?.devDependencies?.tap)?.major === 18, + tap16: semver.coerce(pkg.pkgJson?.devDependencies?.tap)?.major === 16, // booleans to control application of updates isForce, isDogFood, @@ -199,12 +198,14 @@ const getFullConfig = async ({ if (pkgConfig.typescript) { defaultsDeep(pkgConfig, { allowPaths: [], requiredPackages: { devDependencies: [] } }) - pkgConfig.distPaths = null + pkgConfig.distPaths = ['dist/'] + pkgConfig.allowDistPaths = false pkgConfig.allowPaths.push('/src/') pkgConfig.requiredPackages.devDependencies.push( 'typescript', 'tshy', - '@typescript-eslint/parser' + '@typescript-eslint/parser', + ...derived.tap16 ? ['c8', 'ts-node'] : [] ) } @@ -254,7 +255,7 @@ const getFullConfig = async ({ ]), ...isRoot && pkgConfig.lockfile ? ['!/package-lock.json'] : [], ...(pkgConfig.allowPaths || []).map((p) => `!${p}`), - ...(pkgConfig.distPaths || []).map((p) => `!/${p}`), + ...(pkgConfig.allowDistPaths ? pkgConfig.distPaths : []).map((p) => `!/${p}`), ...(pkgConfig.ignorePaths || []), ]), // these cant be sorted since they rely on order diff --git a/lib/content/_job-release-integration-yml.hbs b/lib/content/_job-release-integration-yml.hbs index 2cd8787f..6c1e2d78 100644 --- a/lib/content/_job-release-integration-yml.hbs +++ b/lib/content/_job-release-integration-yml.hbs @@ -1,59 +1,52 @@ -{{#if publish}} +name: {{#if publish}}Publish{{else}}Check Publish{{/if}} runs-on: ubuntu-latest defaults: run: shell: bash +{{#if publish}} permissions: deployments: write id-token: write +{{/if}} steps: - - name: Checkout - uses: actions/checkout@v3 - with: - ref: $\{{ fromJSON(needs.release.outputs.release).tagName }} - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 18.x - - name: Install npm@latest - run: | - npm i --prefer-online --no-fund --no-audit -g npm@latest - npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN} + {{#if publish}} + {{> stepsSetupYml jobCheckout=(obj ref="${{ fromJSON(inputs.releases)[0].tagName }}") }} + - name: Set npm authToken + run: npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN} - name: Publish env: PUBLISH_TOKEN: $\{{ secrets.PUBLISH_TOKEN }} - run: npm publish --provenance --tag={{ publishTag }} -{{else}} -runs-on: ubuntu-latest -defaults: - run: - shell: bash -steps: - {{> stepNodeYml lockfile=false }} - - name: View in Registry + {{else}} + {{> stepsSetupYml }} + - name: Check If Published + {{/if}} run: | EXIT_CODE=0 - function is_published { - if npm view "$@" --loglevel=error > /dev/null; then + function each_release { + if {{#if publish}}npm publish --provenance --tag="$1"{{else}}npm view "$@" --loglevel=error > /dev/null{{/if}}; then echo 0 else echo 1 fi } - for release in $(echo '$\{{ needs.release.outputs.releases }}' | jq -r '.[] | @base64'); do - name=$(echo "$release" | base64 --decode | jq -r .pkgName) - version=$(echo "$release" | base64 --decode | jq -r .version) - spec="$name@$version" - status=$(is_published "$spec") - if [[ "$status" -eq 1 ]]; then - echo "$spec ERROR" - EXIT_CODE=$status + for release in $(echo '$\{{ inputs.releases }}' | jq -r '.[] | @base64'); do + {{#if publish}} + PUBLISH_TAG=$(echo "$release" | base64 --decode | jq -r .publishTag) + STATUS=$(each_release "$PUBLISH_TAG") + {{else}} + SPEC="$(echo "$release" | base64 --decode | jq -r .pkgName)@$(echo "$release" | base64 --decode | jq -r .version)" + STATUS=$(each_release "$SPEC") + {{/if}} + if [[ "$STATUS" -eq 1 ]]; then + EXIT_CODE=$STATUS + {{#unless publish}} + echo "$SPEC ERROR" else - echo "$spec OK" + echo "$SPEC OK" + {{/unless}} fi done exit $EXIT_CODE -{{/if}} diff --git a/lib/content/_step-checks-yml.hbs b/lib/content/_step-checks-yml.hbs deleted file mode 100644 index 469456bf..00000000 --- a/lib/content/_step-checks-yml.hbs +++ /dev/null @@ -1,54 +0,0 @@ -{{#if jobCheck.sha}} -- name: Get Workflow Job - uses: actions/github-script@v6 - if: {{ jobCheck.sha }} - id: check-output - env: - JOB_NAME: "{{#if jobName}}{{ jobName }}{{else}}{{ jobCheck.name }}{{/if}}" - MATRIX_NAME: "{{#if jobIsMatrix}} - $\{{ matrix.platform.name }} - $\{{ matrix.node-version }}{{/if}}" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/$\{{ {{ jobCheck.sha }} }}` - - let summary = `This check is assosciated with ${shaUrl}\n\n` - - if (jobUrl) { - summary += `For run logs, click here: ${jobUrl}` - } else { - summary += `Run logs could not be found for a job with name: "${jobName}"` - } - - return { summary } -{{/if}} -- name: {{#if jobCheck.sha}}Create{{else}}Conclude{{/if}} Check - uses: LouisBrunner/checks-action@v1.6.0 - {{#if jobCheck.sha}} - id: check - if: {{ jobCheck.sha }} - {{else}} - if: {{#if jobCheck.id}}{{ jobCheck.id }}{{else}}steps.check.outputs.check_id{{/if}} && always() - {{/if}} - with: - token: $\{{ secrets.GITHUB_TOKEN }} - {{#if jobCheck.sha}} - status: {{#if jobCheck.status}}{{ jobCheck.status }}{{else}}in_progress{{/if}} - name: {{#if jobCheck.name}}{{ jobCheck.name }}{{else}}{{ jobName }}{{/if}}{{#if jobIsMatrix}} - $\{{ matrix.platform.name }} - $\{{ matrix.node-version }}{{/if}} - sha: $\{{ {{ jobCheck.sha }} }} - output: $\{{ steps.check-output.outputs.result }} - {{else}} - conclusion: $\{{ {{#if jobCheck.status}}{{ jobCheck.status }}{{else}}job.status{{/if}} }} - check_id: $\{{ {{#if jobCheck.id}}{{ jobCheck.id }}{{else}}steps.check.outputs.check_id{{/if}} }} - {{/if}} diff --git a/lib/content/_step-node-yml.hbs b/lib/content/_step-node-yml.hbs index 0458fd95..7e9af4fc 100644 --- a/lib/content/_step-node-yml.hbs +++ b/lib/content/_step-node-yml.hbs @@ -7,54 +7,9 @@ {{#if lockfile}} cache: npm {{/if}} - {{#if updateNpm}} -{{#if jobIsMatrix}} -# node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows -- name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz - cd .. - rmdir /s /q package -{{/if}} - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: $\{{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in ${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - -- name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: $\{{ steps.node.outputs.node-version }} {{/if}} diff --git a/lib/content/_steps-setup-yml.hbs b/lib/content/_steps-setup-yml.hbs index 962f2885..3b537577 100644 --- a/lib/content/_steps-setup-yml.hbs +++ b/lib/content/_steps-setup-yml.hbs @@ -1,6 +1,15 @@ -{{~#if jobCheck}}{{> stepChecksYml }}{{/if}} {{~#unless jobSkipSetup}} {{> stepGitYml }} +{{~#if jobCreateCheck}} +- name: Create Check + id: create-check + if: {{ jobCreateCheck.sha }} + uses: ./.github/actions/create-check + with: + name: "{{ jobName }}{{#if jobIsMatrix}} - $\{{ matrix.platform.name }} - $\{{ matrix.node-version }}{{/if}}" + token: $\{{ secrets.GITHUB_TOKEN }} + sha: {{ jobCreateCheck.sha }} +{{/if}} {{> stepNodeYml }} {{> stepDepsYml }} {{/unless}} diff --git a/lib/content/action-create-check-yml.hbs b/lib/content/action-create-check-yml.hbs new file mode 100644 index 00000000..44aeeef9 --- /dev/null +++ b/lib/content/action-create-check-yml.hbs @@ -0,0 +1,50 @@ +name: 'Create Check' +inputs: + name: + required: true + token: + required: true + sha: + required: true + check-name: + default: '' +outputs: + check-id: + value: $\{{ steps.create-check.outputs.check_id }} +runs: + using: "composite" + steps: + - name: Get Workflow Job + uses: actions/github-script@v6 + id: workflow + env: + JOB_NAME: "$\{{ inputs.name }}" + SHA: "$\{{ inputs.sha }}" + with: + result-encoding: string + script: | + const { repo: { owner, repo}, runId, serverUrl } = context + const { JOB_NAME, SHA } = process.env + + const job = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: runId, + per_page: 100 + }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME))) + + return [ + `This check is assosciated with ${serverUrl}/${owner}/${repo}/commit/${SHA}.`, + 'Run logs:', + job?.html_url || `could not be found for a job ending with: "${JOB_NAME}"`, + ].join(' ') + - name: Create Check + uses: LouisBrunner/checks-action@v1.6.0 + id: create-check + with: + token: $\{{ inputs.token }} + sha: $\{{ inputs.sha }} + status: in_progress + name: $\{{ inputs.check-name || inputs.name }} + output: | + {"summary":"$\{{ steps.workflow.outputs.result }}"} diff --git a/lib/content/action-install-latest-npm-yml.hbs b/lib/content/action-install-latest-npm-yml.hbs new file mode 100644 index 00000000..75c7ac78 --- /dev/null +++ b/lib/content/action-install-latest-npm-yml.hbs @@ -0,0 +1,55 @@ +name: 'Install Latest npm' +description: 'Install the latest version of npm compatible with the Node version' +inputs: + node: + description: 'Current Node version' + required: true +runs: + using: "composite" + steps: + # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows + - name: Update Windows npm + if: | + runner.os == 'Windows' && ( + startsWith(inputs.node, 'v10.') || + startsWith(inputs.node, 'v12.') || + startsWith(inputs.node, 'v14.') + ) + shell: cmd + run: | + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz + tar xf npm-7.5.4.tgz + cd package + node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz + cd .. + rmdir /s /q package + - name: Install Latest npm + shell: bash + env: + NODE_VERSION: $\{{ inputs.node }} + run: | + MATCH="" + SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") + + echo "node@$NODE_VERSION" + + for SPEC in ${SPECS[@]}; do + ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') + echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" + + if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then + MATCH=$SPEC + echo "Found compatible version: npm@$MATCH" + break + fi + done + + if [ -z $MATCH ]; then + echo "Could not find a compatible version of npm for node@$NODE_VERSION" + exit 1 + fi + + npm i --prefer-online --no-fund --no-audit -g npm@$MATCH + - name: npm Version + shell: bash + run: npm -v diff --git a/lib/content/ci-release-yml.hbs b/lib/content/ci-release-yml.hbs index 6253ffe9..54f95f08 100644 --- a/lib/content/ci-release-yml.hbs +++ b/lib/content/ci-release-yml.hbs @@ -21,17 +21,29 @@ jobs: lint-all: {{> jobYml jobName="Lint All" - jobCheck=(obj sha="inputs.check-sha") jobCheckout=(obj ref="${{ inputs.ref }}") + jobCreateCheck=(obj sha="${{ inputs.check-sha }}") }} {{> stepLintYml jobRunFlags=allFlags }} - {{> stepChecksYml jobCheck=true }} + - name: Conclude Check + uses: LouisBrunner/checks-action@v1.6.0 + if: always() + with: + token: $\{{ secrets.GITHUB_TOKEN }} + conclusion: $\{{ job.status }} + check_id: $\{{ steps.create-check.outputs.check-id }} test-all: {{> jobMatrixYml jobName="Test All" - jobCheck=(obj sha="inputs.check-sha") jobCheckout=(obj ref="${{ inputs.ref }}") + jobCreateCheck=(obj sha="${{ inputs.check-sha }}") }} {{> stepTestYml jobRunFlags=allFlags }} - {{> stepChecksYml jobCheck=true }} + - name: Conclude Check + uses: LouisBrunner/checks-action@v1.6.0 + if: always() + with: + token: $\{{ secrets.GITHUB_TOKEN }} + conclusion: $\{{ job.status }} + check_id: $\{{ steps.create-check.outputs.check-id }} diff --git a/lib/content/index.js b/lib/content/index.js index 5dda526f..af99173b 100644 --- a/lib/content/index.js +++ b/lib/content/index.js @@ -12,6 +12,10 @@ const sharedRootAdd = (name) => ({ file: 'ci-release-yml.hbs', filter: isPublic, }, + '.github/workflows/release-integration.yml': { + file: 'release-integration-yml.hbs', + filter: isPublic, + }, '.release-please-manifest.json': { file: 'release-please-manifest-json.hbs', filter: isPublic, @@ -48,6 +52,9 @@ const sharedRootAdd = (name) => ({ file: 'settings-yml.hbs', filter: (p) => !p.config.isReleaseBranch, }, + // composite actions + '.github/actions/install-latest-npm/action.yml': 'action-install-latest-npm-yml.hbs', + '.github/actions/create-check/action.yml': 'action-create-check-yml.hbs', }) const sharedRootRm = () => ({ @@ -140,11 +147,15 @@ module.exports = { branches: ['main', 'latest'], // set this to the major version to backport backport: null, + // this tag will be used for publishing when not prerelease or backport. use + // {{major}} to have the major version being published replaced in the string. + defaultPublishTag: 'latest', releaseBranch: 'release/v*', distPaths: [ 'bin/', 'lib/', ], + allowDistPaths: true, allowPaths: [ '/.eslintrc.local.*', '**/.gitignore', @@ -192,6 +203,6 @@ module.exports = { { type: 'fix', section: 'Bug Fixes', hidden: false }, { type: 'docs', section: 'Documentation', hidden: false }, { type: 'deps', section: 'Dependencies', hidden: false }, - { type: 'chore', hidden: true }, + { type: 'chore', section: 'Chores', hidden: false }, ], } diff --git a/lib/content/package-json.hbs b/lib/content/package-json.hbs index bba7ebdf..a115f51e 100644 --- a/lib/content/package-json.hbs +++ b/lib/content/package-json.hbs @@ -1,14 +1,14 @@ { "author": "GitHub Inc.", - "files": {{#if typescript}}{{{ del }}}{{else}}{{{ json distPaths }}}{{/if}}, + "files": {{{ json distPaths }}}, "type": {{#if esm}}"module"{{else}}{{{ del }}}{{/if}}, "scripts": { "lint": "{{#if eslint}}eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"{{else}}echo linting disabled{{/if}}", "postlint": "template-oss-check", "template-oss-apply": "template-oss-apply --force", "lintfix": "{{ localNpmPath }} run lint -- --fix", - "snap": "tap", - "test": "tap", + "snap": "{{#if typescript}}{{#if tap16}}c8 {{/if}}{{/if}}tap", + "test": "{{#if typescript}}{{#if tap16}}c8 {{/if}}{{/if}}tap", "posttest": "{{ localNpmPath }} run lint", {{#if isRootMono}} "test-all": "{{ localNpmPath }} run test {{ allFlags }}", @@ -34,6 +34,16 @@ {{#if workspacePaths}} "test-ignore": "^({{ join workspacePaths "|" }})/", {{/if}} + {{#if typescript}} + {{#if tap16}} + "coverage": false, + "node-arg": [ + "--no-warnings", + "--loader", + "ts-node/esm" + ], + {{/if}} + {{/if}} "nyc-arg": {{#if tap18}}{{{ del }}}{{else}}[ {{#each workspaceGlobs}} "--exclude", diff --git a/lib/content/pull-request-yml.hbs b/lib/content/pull-request-yml.hbs index a4db6afe..f7552b76 100644 --- a/lib/content/pull-request-yml.hbs +++ b/lib/content/pull-request-yml.hbs @@ -14,11 +14,9 @@ jobs: - name: Run Commitlint on Commits id: commit continue-on-error: true - run: | - {{ rootNpxPath }} --offline commitlint -V --from 'origin/$\{{ github.base_ref }}' --to $\{{ github.event.pull_request.head.sha }} + run: {{ rootNpxPath }} --offline commitlint -V --from 'origin/$\{{ github.base_ref }}' --to $\{{ github.event.pull_request.head.sha }} - name: Run Commitlint on PR Title if: steps.commit.outcome == 'failure' env: PR_TITLE: $\{{ github.event.pull_request.title }} - run: | - echo "$PR_TITLE" | {{ rootNpxPath }} --offline commitlint -V + run: echo "$PR_TITLE" | {{ rootNpxPath }} --offline commitlint -V diff --git a/lib/content/release-integration-yml.hbs b/lib/content/release-integration-yml.hbs new file mode 100644 index 00000000..d11f6a25 --- /dev/null +++ b/lib/content/release-integration-yml.hbs @@ -0,0 +1,19 @@ +name: Release Integration + +on: + workflow_dispatch: + inputs: + releases: + required: true + type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + workflow_call: + inputs: + releases: + required: true + type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + +jobs: + publish: + {{> jobReleaseIntegrationYml }} diff --git a/lib/content/release-yml.hbs b/lib/content/release-yml.hbs index 43e7acbc..47ac0ca0 100644 --- a/lib/content/release-yml.hbs +++ b/lib/content/release-yml.hbs @@ -1,11 +1,6 @@ name: Release on: - workflow_dispatch: - inputs: - release-pr: - description: a release PR number to rerun release jobs on - type: string push: branches: {{#each branchPatterns}} @@ -21,71 +16,85 @@ jobs: release: outputs: pr: $\{{ steps.release.outputs.pr }} - release: $\{{ steps.release.outputs.release }} - releases: $\{{ steps.release.outputs.releases }} - branch: $\{{ steps.release.outputs.pr-branch }} + pr-branch: $\{{ steps.release.outputs.pr-branch }} pr-number: $\{{ steps.release.outputs.pr-number }} - comment-id: $\{{ steps.pr-comment.outputs.result }} - check-id: $\{{ steps.check.outputs.check_id }} + pr-sha: $\{{ steps.release.outputs.pr-sha }} + releases: $\{{ steps.release.outputs.releases }} + comment-id: $\{{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }} + check-id: $\{{ steps.create-check.outputs.check-id }} {{> jobYml jobName="Release" }} - name: Release Please id: release env: GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }} - run: | - {{ rootNpxPath }} --offline template-oss-release-please "$\{{ github.ref_name }}" "$\{{ inputs.release-pr }}" - - name: Post Pull Request Comment + run: {{ rootNpxPath }} --offline template-oss-release-please --branch="$\{{ github.ref_name }}" --backport="{{ backport }}" --defaultTag="{{ defaultPublishTag }}" + - name: Create Release Manager Comment Text if: steps.release.outputs.pr-number uses: actions/github-script@v6 - id: pr-comment - env: - PR_NUMBER: $\{{ steps.release.outputs.pr-number }} - REF_NAME: $\{{ github.ref_name }} + id: comment-text with: + result-encoding: string script: | - const { REF_NAME, PR_NUMBER: issue_number } = process.env const { runId, repo: { owner, repo } } = context - const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId }) - - let body = '## Release Manager\n\n' - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - let commentId = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id - - body += `Release workflow run: ${workflow.html_url}\n\n#### Force CI to Update This Release\n\n` - body += `This PR will be updated and CI will run for every non-\`chore:\` commit that is pushed to \`${REF_NAME}\`. ` - body += `To force CI to update this PR, run this command:\n\n` - body += `\`\`\`\ngh workflow run release.yml -r ${REF_NAME} -R ${owner}/${repo} -f release-pr=${issue_number}\n\`\`\`` - - if (commentId) { - await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, body }) - } else { - const { data: comment } = await github.rest.issues.createComment({ owner, repo, issue_number, body }) - commentId = comment?.id - } - - return commentId - {{> stepChecksYml jobCheck=(obj name="Release" sha="steps.release.outputs.pr-sha") }} + return['## Release Manager', `Release workflow run: ${workflow.html_url}`].join('\n\n') + - name: Find Release Manager Comment + uses: peter-evans/find-comment@v2 + if: steps.release.outputs.pr-number + id: found-comment + with: + issue-number: $\{{ steps.release.outputs.pr-number }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Manager' + - name: Create Release Manager Comment + id: create-comment + if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: $\{{ steps.release.outputs.pr-number }} + body: $\{{ steps.comment-text.outputs.result }} + - name: Update Release Manager Comment + id: update-comment + if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: $\{{ steps.found-comment.outputs.comment-id }} + body: $\{{ steps.comment-text.outputs.result }} + edit-mode: 'replace' + - name: Create Check + id: create-check + uses: ./.github/actions/create-check + if: steps.release.outputs.pr-sha + with: + name: "Release" + token: $\{{ secrets.GITHUB_TOKEN }} + sha: $\{{ steps.release.outputs.pr-sha }} update: needs: release outputs: sha: $\{{ steps.commit.outputs.sha }} - check-id: $\{{ steps.check.outputs.check_id }} + check-id: $\{{ steps.create-check.outputs.check-id }} {{> jobYml jobName="Update - Release" jobIf="needs.release.outputs.pr" - jobCheckout=(obj ref="${{ needs.release.outputs.branch }}" fetch-depth=0) + jobCheckout=(obj ref="${{ needs.release.outputs.pr-branch }}" fetch-depth=0) }} + - name: Create Release Manager Checklist Text + id: comment-text + env: + GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }} + run: {{ rootNpmPath }} exec --offline -- template-oss-release-manager --pr="$\{{ needs.release.outputs.pr-number }}" --backport="{{ backport }}" --defaultTag="{{ defaultPublishTag }}" {{~#if lockfile}} --lockfile{{/if}} {{~#if publish}} --publish{{/if}} + - name: Append Release Manager Comment + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: $\{{ needs.release.outputs.comment-id }} + body: $\{{ steps.comment-text.outputs.result }} + edit-mode: 'append' - name: Run Post Pull Request Actions env: - RELEASE_PR_NUMBER: $\{{ needs.release.outputs.pr-number }} - RELEASE_COMMENT_ID: $\{{ needs.release.outputs.comment-id }} GITHUB_TOKEN: $\{{ secrets.GITHUB_TOKEN }} - run: | - {{ rootNpmPath }} exec --offline -- template-oss-release-manager --lockfile={{ lockfile }} --publish={{ publish }} - {{ rootNpmPath }} run rp-pull-request --ignore-scripts {{~#if allFlags}} {{ allFlags }}{{else}} --if-present{{/if}} + run: {{ rootNpmPath }} run rp-pull-request --ignore-scripts {{~#if allFlags}} {{ allFlags }}{{else}} --if-present{{/if}} -- --pr="$\{{ needs.release.outputs.pr-number }}" --commentId="$\{{ needs.release.outputs.comment-id }}" - name: Commit id: commit env: @@ -94,8 +103,20 @@ jobs: git commit --all --amend --no-edit || true git push --force-with-lease echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - {{> stepChecksYml jobName="Update - Release" jobCheck=(obj sha="steps.commit.outputs.sha" name="Release" )}} - {{> stepChecksYml jobCheck=(obj id="needs.release.outputs.check-id" )}} + - name: Create Check + id: create-check + uses: ./.github/actions/create-check + with: + name: "Update - Release" + check-name: "Release" + token: $\{{ secrets.GITHUB_TOKEN }} + sha: $\{{ steps.commit.outputs.sha }} + - name: Conclude Check + uses: LouisBrunner/checks-action@v1.6.0 + with: + token: $\{{ secrets.GITHUB_TOKEN }} + conclusion: $\{{ job.status }} + check_id: $\{{ needs.release.outputs.check-id }} ci: name: CI - Release @@ -103,14 +124,14 @@ jobs: if: needs.release.outputs.pr uses: ./.github/workflows/ci-release.yml with: - ref: $\{{ needs.release.outputs.branch }} + ref: $\{{ needs.release.outputs.pr-branch }} check-sha: $\{{ needs.update.outputs.sha }} post-ci: needs: [release, update, ci] {{> jobYml jobName="Post CI - Release" jobIf="needs.release.outputs.pr && always()" jobSkipSetup=true }} - - name: Get Needs Result - id: needs-result + - name: Get CI Conclusion + id: conclusion run: | result="" if [[ "$\{{ contains(needs.*.result, 'failure') }}" == "true" ]]; then @@ -121,55 +142,56 @@ jobs: result="success" fi echo "result=$result" >> $GITHUB_OUTPUT - {{> stepChecksYml jobCheck=(obj id="needs.update.outputs.check-id" status="steps.needs-result.outputs.result") }} + - name: Conclude Check + uses: LouisBrunner/checks-action@v1.6.0 + with: + token: $\{{ secrets.GITHUB_TOKEN }} + conclusion: $\{{ steps.conclusion.outputs.result }} + check_id: $\{{ needs.update.outputs.check-id }} post-release: needs: release + outputs: + comment-id: $\{{ steps.create-comment.outputs.comment-id }} {{> jobYml jobName="Post Release - Release" jobIf="needs.release.outputs.releases" jobSkipSetup=true }} - - name: Create Release PR Comment + - name: Create Release PR Comment Text + id: comment-text uses: actions/github-script@v6 env: RELEASES: $\{{ needs.release.outputs.releases }} with: + result-encoding: string script: | const releases = JSON.parse(process.env.RELEASES) const { runId, repo: { owner, repo } } = context const issue_number = releases[0].prNumber - - let body = '## Release Workflow\n\n' - for (const { pkgName, version, url } of releases) { - body += `- \`${pkgName}@${version}\` ${url}\n` - } - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - .then(cs => cs.map(c => ({ id: c.id, login: c.user.login, body: c.body }))) - console.log(`Found comments: ${JSON.stringify(comments, null, 2)}`) - const releaseComments = comments.filter(c => c.login === 'github-actions[bot]' && c.body.includes('Release is at')) - - for (const comment of releaseComments) { - console.log(`Release comment: ${JSON.stringify(comment, null, 2)}`) - await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id }) - } - const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}` - await github.rest.issues.createComment({ - owner, - repo, - issue_number, - body: `${body}- Workflow run: :arrows_counterclockwise: ${runUrl}`, - }) + + return [ + '## Release Workflow\n', + ...releases.map(r => `- \`${r.pkgName}@${r.version}\` ${r.url}`), + `- Workflow run: :arrows_counterclockwise: ${runUrl}`, + ].join('\n') + - name: Create Release PR Comment + id: create-comment + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: $\{{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + body: $\{{ steps.comment-text.outputs.result }} release-integration: needs: release name: Release Integration - if: needs.release.outputs.release - {{> jobReleaseIntegrationYml }} + if: needs.release.outputs.releases + uses: ./.github/workflows/release-integration.yml + with: + releases: $\{{ needs.release.outputs.releases }} post-release-integration: - needs: [release, release-integration] - {{> jobYml jobName="Post Release Integration - Release" jobIf="needs.release.outputs.release && always()" jobSkipSetup=true }} - - name: Get Needs Result - id: needs-result + needs: [release, release-integration, post-release] + {{> jobYml jobName="Post Release Integration - Release" jobIf="needs.release.outputs.releases && always()" jobSkipSetup=true }} + - name: Get Post Release Conclusion + id: conclusion run: | if [[ "$\{{ contains(needs.*.result, 'failure') }}" == "true" ]]; then result="x" @@ -179,39 +201,39 @@ jobs: result="white_check_mark" fi echo "result=$result" >> $GITHUB_OUTPUT - - name: Update Release PR Comment + - name: Find Release PR Comment + uses: peter-evans/find-comment@v2 + id: found-comment + with: + issue-number: $\{{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Workflow' + - name: Create Release PR Comment Text + id: comment-text + if: steps.found-comment.outputs.comment-id uses: actions/github-script@v6 env: - PR_NUMBER: $\{{ fromJSON(needs.release.outputs.release).prNumber }} - RESULT: $\{{ steps.needs-result.outputs.result }} + RESULT: $\{{ steps.conclusion.outputs.result }} + BODY: $\{{ steps.found-comment.outputs.comment-body }} with: + result-encoding: string script: | - const { PR_NUMBER: issue_number, RESULT } = process.env - const { runId, repo: { owner, repo } } = context - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - const updateComment = comments.find(c => - c.user.login === 'github-actions[bot]' && - c.body.startsWith('## Release Workflow\n\n') && - c.body.includes(runId) - ) - - if (updateComment) { - console.log('Found comment to update:', JSON.stringify(updateComment, null, 2)) - let body = updateComment.body.replace(/Workflow run: :[a-z_]+:/, `Workflow run: :${RESULT}:`) - const tagCodeowner = RESULT !== 'white_check_mark' - if (tagCodeowner) { - body += `\n\n:rotating_light:` - body += ` {{ codeowner }}: The post-release workflow failed for this release.` - body += ` Manual steps may need to be taken after examining the workflow output` - body += ` from the above workflow run. :rotating_light:` - } - await github.rest.issues.updateComment({ - owner, - repo, - body, - comment_id: updateComment.id, - }) - } else { - console.log('No matching comments found:', JSON.stringify(comments, null, 2)) + const { RESULT, BODY } = process.env + const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, `$1${RESULT}$2`)] + if (RESULT !== 'white_check_mark') { + body.push(':rotating_light::rotating_light::rotating_light:') + body.push([ + '{{ codeowner }}: The post-release workflow failed for this release.', + 'Manual steps may need to be taken after examining the workflow output.' + ].join(' ')) + body.push(':rotating_light::rotating_light::rotating_light:') } + return body.join('\n\n').trim() + - name: Update Release PR Comment + if: steps.comment-text.outputs.result + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: $\{{ steps.found-comment.outputs.comment-id }} + body: $\{{ steps.comment-text.outputs.result }} + edit-mode: 'replace' + diff --git a/lib/release-please/changelog.js b/lib/release-please/changelog.js deleted file mode 100644 index 3d40862b..00000000 --- a/lib/release-please/changelog.js +++ /dev/null @@ -1,92 +0,0 @@ -const makeGh = require('./github.js') -const { link, code, specRe, list, dateFmt } = require('./util') - -module.exports = class ChangelogNotes { - constructor (options) { - this.gh = makeGh(options.github) - } - - buildEntry (commit, authors = []) { - const breaking = commit.notes - .filter(n => n.title === 'BREAKING CHANGE') - .map(n => n.text) - - const entry = [] - - if (commit.sha) { - // A link to the commit - entry.push(link(code(commit.sha.slice(0, 7)), this.gh.commit(commit.sha))) - } - - // A link to the pull request if the commit has one - const prNumber = commit.pullRequest?.number - if (prNumber) { - entry.push(link(`#${prNumber}`, this.gh.pull(prNumber))) - } - - // The title of the commit, with the optional scope as a prefix - const scope = commit.scope && `${commit.scope}:` - const subject = commit.bareMessage.replace(specRe, code('$1')) - entry.push([scope, subject].filter(Boolean).join(' ')) - - // A list og the authors github handles or names - if (authors.length && commit.type !== 'deps') { - entry.push(`(${authors.join(', ')})`) - } - - return { - entry: entry.join(' '), - breaking, - } - } - - async buildNotes (rawCommits, { version, previousTag, currentTag, changelogSections }) { - const changelog = changelogSections.reduce((acc, c) => { - if (!c.hidden) { - acc[c.type] = { - title: c.section, - entries: [], - } - } - return acc - }, { - breaking: { - title: '⚠️ BREAKING CHANGES', - entries: [], - }, - }) - - // Only continue with commits that will make it to our changelog - const commits = rawCommits.filter(c => changelog[c.type]) - - const authorsByCommit = await this.gh.authors(commits) - - // Group commits by type - for (const commit of commits) { - // when rebase merging multiple commits with a single PR, only the first commit - // will have a pr number when coming from release-please. this check will manually - // lookup commits without a pr number and find one if it exists - if (!commit.pullRequest?.number) { - commit.pullRequest = { number: await this.gh.commitPrNumber(commit) } - } - const { entry, breaking } = this.buildEntry( - commit, - authorsByCommit[commit.sha] - ) - - // Collect commits by type - changelog[commit.type].entries.push(entry) - - // And push breaking changes to its own section - changelog.breaking.entries.push(...breaking) - } - - const sections = Object.values(changelog) - .filter((s) => s.entries.length) - .map(({ title, entries }) => [`### ${title}`, entries.map(list).join('\n')].join('\n\n')) - - const title = `## ${link(version, this.gh.compare(previousTag, currentTag))} (${dateFmt()})` - - return [title, ...sections].join('\n\n').trim() - } -} diff --git a/lib/release-please/github.js b/lib/release-please/github.js deleted file mode 100644 index 07b2f28e..00000000 --- a/lib/release-please/github.js +++ /dev/null @@ -1,72 +0,0 @@ -module.exports = (gh) => { - const { owner, repo } = gh.repository - - const authors = async (commits) => { - const response = {} - - const shas = commits.map(c => c.sha).filter(Boolean) - - if (!shas.length) { - return response - } - - try { - const { repository } = await gh.graphql( - `fragment CommitAuthors on GitObject { - ... on Commit { - authors (first:10) { - nodes { - user { login } - name - } - } - } - } - query { - repository (owner:"${owner}", name:"${repo}") { - ${shas.map((s) => { - return `_${s}: object (expression: "${s}") { ...CommitAuthors }` - })} - } - }` - ) - - for (const [key, commit] of Object.entries(repository)) { - if (commit) { - response[key.slice(1)] = commit.authors.nodes - .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name) - .filter(Boolean) - } - } - - return response - } catch { - return response - } - } - - const commitPrNumber = async (commit) => { - try { - const res = await gh.octokit.rest.repos.listPullRequestsAssociatedWithCommit({ - owner, - repo, - commit_sha: commit.sha, - per_page: 1, - }) - return res.data[0].number - } catch { - return null - } - } - - const url = (...p) => `https://github.com/${owner}/${repo}/${p.join('/')}` - - return { - authors, - commitPrNumber, - pull: (number) => url('pull', number), - commit: (sha) => url('commit', sha), - compare: (a, b) => a ? url('compare', `${a.toString()}...${b.toString()}`) : null, - release: (tag) => url('releases', 'tag', tag.toString()), - } -} diff --git a/lib/release-please/index.js b/lib/release-please/index.js deleted file mode 100644 index 8c8c603b..00000000 --- a/lib/release-please/index.js +++ /dev/null @@ -1,262 +0,0 @@ -const RP = require('release-please') -const { CheckpointLogger } = require('release-please/build/src/util/logger.js') -const ChangelogNotes = require('./changelog.js') -const Version = require('./version.js') -const NodeWs = require('./node-workspace.js') - -const logger = new CheckpointLogger(true, true) -RP.setLogger(logger) -RP.registerChangelogNotes('default', (o) => new ChangelogNotes(o)) -RP.registerVersioningStrategy('default', (o) => new Version(o)) -RP.registerPlugin('node-workspace', (o) => new NodeWs(o.github, o.targetBranch, o.repositoryConfig)) - -const omit = (obj, ...keys) => { - const res = {} - for (const [key, value] of Object.entries(obj)) { - if (!keys.includes(key)) { - res[key] = value - } - } - return res -} - -const getManifest = async ({ repo: fullRepo, token, branch }) => { - const fullRepoParts = fullRepo.split('/') - const github = await RP.GitHub.create({ - owner: fullRepoParts[0], - repo: fullRepoParts[1], - token, - }) - - const { octokit, repository: { owner, repo, defaultBranch } } = github - - const baseBranch = branch ?? defaultBranch - - // This is mostly for testing and debugging. Use environs with the - // format `RELEASE_PLEASE_` (eg - // `RELEASE_PLEASE_lastReleaseSha=`) to set one-off config items - // for the release please run without needing to commit and push the config. - const manifestOverrides = Object.entries(process.env) - .filter(([k, v]) => k.startsWith('RELEASE_PLEASE_') && v != null) - .map(([k, v]) => [k.replace('RELEASE_PLEASE_', ''), v]) - - const manifest = await RP.Manifest.fromManifest( - github, - baseBranch, - undefined, - undefined, - Object.fromEntries(manifestOverrides) - ) - - return { - github, - manifest, - octokit, - owner, - repo, - baseBranch, - } -} - -const getReleasesFromPr = async ({ manifest, github, number }) => { - const baseUrl = `https://github.com/${github.repository.owner}/${github.repository.repo}` - // get the release please formatted pull request - let pullRequest - const prGenerator = github.pullRequestIterator(this.targetBranch, 'MERGED', 200, false) - for await (const pr of prGenerator) { - if (pr.number === number) { - pullRequest = pr - break - } - } - const strategiesByPath = await manifest.getStrategiesByPath() - const releases = [] - for (const path in manifest.repositoryConfig) { - const config = manifest.repositoryConfig[path] - const release = await strategiesByPath[path].buildRelease(pullRequest) - if (release) { - const { tag, ...rest } = release - releases.push({ - ...rest, - ...tag.version, - tagName: tag.toString(), - version: tag.version.toString(), - path, - draft: false, - url: `${baseUrl}/releases/tag/${tag.toString()}`, - prerelease: config.prerelease && !!tag.version.preRelease, - }) - } - } - return releases -} - -const getReleaseArtifacts = async ({ dryRun, manifest, forceReleases }) => { - let pullRequests = [] - let releases = [] - - if (forceReleases) { - releases = forceReleases - } else if (dryRun) { - pullRequests = await manifest.buildPullRequests() - releases = await manifest.buildReleases() - } else { - pullRequests = await manifest.createPullRequests() - releases = await manifest.createReleases() - } - - return { - pullRequests: pullRequests.filter(Boolean), - releases: releases.filter(Boolean), - } -} - -// XXX(hack): to get release please to recreate a pull request it needs -// to have a different body string so we append a message a message that CI -// is running. This will force release-please to rebase the PR but it -// wont update the body again, so we only append to it. -const touchPullRequest = async ({ octokit, owner, repo, releasePr }) => { - const id = process.env.GITHUB_RUN_ID - ? `by https://github.com/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}` - : `manually starting at ${new Date().toJSON()}` - - await octokit.pulls.update({ - owner, - repo, - pull_number: releasePr.number, - body: `${releasePr.body.trim()}\n- This PR is being recreated ${id}`, - }) -} - -const main = async ({ repo: fullRepo, token, dryRun, branch, forcePullRequest }) => { - if (!token) { - throw new Error('Token is required') - } - - if (!fullRepo) { - throw new Error('Repo is required') - } - - const { - github, - octokit, - manifest, - owner, - repo, - baseBranch, - } = await getManifest({ repo: fullRepo, token, branch }) - - let forceReleases = null - - if (forcePullRequest) { - const { data: releasePr } = await octokit.rest.pulls.get({ - owner, - repo, - pull_number: forcePullRequest, - }) - - if (!releasePr) { - throw new Error(`Could not find PR from number: ${forcePullRequest}`) - } - - if (releasePr.state === 'open') { - await touchPullRequest({ octokit, owner, repo, releasePr }) - } else if (releasePr.state === 'closed' && releasePr.merged) { - forceReleases = await getReleasesFromPr({ manifest, github, number: releasePr.number }) - } else { - throw new Error(`Could not run workflow on PR with wrong state: ${JSON.stringify( - releasePr, - null, - 2 - )}`) - } - } - - const { pullRequests, releases } = await getReleaseArtifacts({ dryRun, manifest, forceReleases }) - - // We only ever get a single pull request with our current release-please settings - // Update this if we start creating individual PRs per workspace release - const rootPr = pullRequests[0] - let rootRelease = releases[0] - - logger.debug(`pull requests: ${pullRequests.length}`) - logger.debug(`releases: ${releases.length}`) - - if (rootPr) { - logger.debug(`root pr: ${JSON.stringify(omit(rootPr, 'body'), null, 2)}`) - } - - if (rootPr?.number) { - const commits = await octokit.paginate(octokit.rest.pulls.listCommits, { - owner, - repo, - pull_number: rootPr.number, - }) - - const prSha = commits?.[commits.length - 1]?.sha - if (!prSha) { - throw new Error(`Could not find a latest sha for pull request: ${rootPr.number}`) - } - - rootPr.sha = prSha - } - - for (const release of releases) { - const { path, sha } = release - const prefix = path === '.' ? '' : path - const isRoot = !prefix - const packagePath = `${prefix}/package.json` - - logger.debug(`release: ${JSON.stringify({ - ...omit(release, 'notes'), - isRoot, - prefix, - }, null, 2)}`) - - const releasePrNumber = await octokit.rest.repos.listPullRequestsAssociatedWithCommit({ - owner, - repo, - commit_sha: sha, - per_page: 1, - }).then(r => r.data[0]?.number) - - if (!releasePrNumber) { - throw new Error(`Could not find release PR number from commit: "${sha}"`) - } - - logger.debug(`pr from ${sha}: ${releasePrNumber}`) - - const releasePkgName = await octokit.rest.repos.getContent({ - owner, - repo, - ref: baseBranch, - path: packagePath, - }).then(r => { - try { - return JSON.parse(Buffer.from(r.data.content, r.data.encoding)).name - } catch { - return null - } - }) - - if (!releasePkgName) { - throw new Error(`Could not find package name for release at: "${packagePath}#${baseBranch}"`) - } - - logger.debug(`pkg name from ${packagePath}#${baseBranch}: "${releasePkgName}"`) - - release.prNumber = releasePrNumber - release.pkgName = releasePkgName - if (isRoot) { - rootRelease = release - } - } - - return { - pr: rootPr ?? null, - release: rootRelease ?? null, - releases: releases.length ? releases : null, - } -} - -module.exports = main diff --git a/lib/release-please/util.js b/lib/release-please/util.js deleted file mode 100644 index 7fd527e5..00000000 --- a/lib/release-please/util.js +++ /dev/null @@ -1,14 +0,0 @@ -const semver = require('semver') - -module.exports.specRe = new RegExp(`([^\\s]+@${semver.src[semver.tokens.FULLPLAIN]})`, 'g') - -module.exports.code = (c) => `\`${c}\`` -module.exports.link = (text, url) => url ? `[${text}](${url})` : text -module.exports.list = (text) => `* ${text}` - -module.exports.dateFmt = (date = new Date()) => { - const year = date.getFullYear() - const month = (date.getMonth() + 1).toString().padStart(2, '0') - const day = date.getDate().toString().padStart(2, '0') - return [year, month, day].join('-') -} diff --git a/lib/release/changelog.js b/lib/release/changelog.js new file mode 100644 index 00000000..60f59dc8 --- /dev/null +++ b/lib/release/changelog.js @@ -0,0 +1,184 @@ +const { link, code, specRe, list, dateFmt, makeGitHubUrl } = require('./util') + +class Changelog { + static BREAKING = 'breaking' + + #title + #entries = {} + #types = new Set([Changelog.BREAKING]) + #titles = { + [Changelog.BREAKING]: '⚠️ BREAKING CHANGES', + } + + constructor ({ version, url, sections }) { + this.#title = `## ${url ? link(version, url) : version} (${dateFmt()})` + for (const section of sections) { + this.#types.add(section.type) + this.#titles[section.type] = section.section + } + } + + add (type, ...entries) { + if (!this.#types.has(type) || !entries.length) { + return + } + this.#entries[type] ??= [] + this.#entries[type].push(...entries) + } + + toString () { + const body = [this.#title] + for (const type of this.#types) { + const title = this.#titles[type] + if (this.#entries[type]?.length) { + body.push( + `### ${title}`, + this.#entries[type].map(list).join('\n') + ) + } + } + return body.join('\n\n').trim() + } +} + +class ChangelogNotes { + #owner + #repo + #rest + #graphql + #ghUrl + + constructor (github) { + this.#owner = github.repository.owner + this.#repo = github.repository.repo + this.#rest = github.octokit.rest + this.#graphql = github.graphql + this.#ghUrl = makeGitHubUrl(this.#owner, this.#repo) + } + + async #getAuthorsForCommits (commits) { + const shas = commits + .filter(c => c.type !== 'deps') + .map(c => c.sha) + .filter(Boolean) + + if (!shas.length) { + return {} + } + + const authorsByCommit = {} + const { repository } = await this.#graphql( + `fragment CommitAuthors on GitObject { + ... on Commit { + authors (first:10) { + nodes { + user { login } + name + } + } + } + } + query { + repository (owner:"${this.#owner}", name:"${this.#repo}") { + ${shas.map((s) => { + return `_${s}: object (expression: "${s}") { ...CommitAuthors }` + })} + } + }` + ) + for (const [key, commit] of Object.entries(repository)) { + if (commit) { + authorsByCommit[key.slice(1)] = commit.authors.nodes + .map((a) => a.user && a.user.login ? `@${a.user.login}` : a.name) + .filter(Boolean) + } + } + return authorsByCommit + } + + async #getPullRequestForCommits (commits) { + const shas = commits + .filter(c => !c.pullRequest?.number) + .map(c => c.sha) + .filter(Boolean) + + if (!shas.length) { + return {} + } + + const pullRequestsByCommit = {} + for (const sha of shas) { + pullRequestsByCommit[sha] = await this.#rest.repos.listPullRequestsAssociatedWithCommit({ + owner: this.#owner, + repo: this.#repo, + commit_sha: sha, + per_page: 1, + }) + .then((r) => r.data[0].number) + .catch(() => null) + } + return pullRequestsByCommit + } + + #buildEntry (commit, { authors = [], pullRequest }) { + const entry = [] + + if (commit.sha) { + // A link to the commit + entry.push(link(code(commit.sha.slice(0, 7)), this.#ghUrl('commit', commit.sha))) + } + + // A link to the pull request if the commit has one + const commitPullRequest = commit.pullRequest?.number ?? pullRequest + if (commitPullRequest) { + entry.push(link(`#${commitPullRequest}`, this.#ghUrl('pull', commitPullRequest))) + } + + // The title of the commit, with the optional scope as a prefix + const scope = commit.scope && `${commit.scope}:` + const subject = commit.bareMessage.replace(specRe, code('$1')) + entry.push([scope, subject].filter(Boolean).join(' ')) + + // A list og the authors github handles or names + if (authors.length) { + entry.push(`(${authors.join(', ')})`) + } + + return entry.join(' ') + } + + async buildNotes (commits, { version, previousTag, currentTag, changelogSections }) { + // get authors for commits for each sha + const authorsByCommit = await this.#getAuthorsForCommits(commits) + + // when rebase merging multiple commits with a single PR, only the first commit + // will have a pr number when coming from release-please. this check will manually + // lookup commits without a pr number and find one if it exists + const pullRequestByCommit = await this.#getPullRequestForCommits(commits) + + const changelog = new Changelog({ + version, + url: previousTag + ? this.#ghUrl('compare', `${previousTag.toString()}...${currentTag.toString()}`) + : null, + sections: changelogSections, + }) + + for (const commit of commits) { + // Collect commits by type + changelog.add(commit.type, this.#buildEntry(commit, { + authors: authorsByCommit[commit.sha], + pullRequest: pullRequestByCommit[commit.sha], + })) + + // And breaking changes to its own section + changelog.add(Changelog.BREAKING, ...commit.notes + .filter(n => n.title === 'BREAKING CHANGE') + .map(n => n.text)) + } + + return changelog.toString() + } +} + +module.exports = ChangelogNotes diff --git a/lib/release-please/node-workspace.js b/lib/release/node-workspace.js similarity index 89% rename from lib/release-please/node-workspace.js rename to lib/release/node-workspace.js index 3192ed36..2c96c50d 100644 --- a/lib/release-please/node-workspace.js +++ b/lib/release/node-workspace.js @@ -5,16 +5,19 @@ const { jsonStringify } = require('release-please/build/src/util/json-stringify. const { addPath } = require('release-please/build/src/plugins/workspace.js') const { TagName } = require('release-please/build/src/util/tag-name.js') const { ROOT_PROJECT_PATH } = require('release-please/build/src/manifest.js') -const makeGh = require('./github.js') -const { link, code } = require('./util.js') +const { link, code, makeGitHubUrl } = require('./util.js') const SCOPE = '__REPLACE_WORKSPACE_DEP__' const WORKSPACE_DEP = new RegExp(`${SCOPE}: (\\S+) (\\S+)`, 'gm') -module.exports = class extends NodeWorkspace { +class NpmNodeWorkspace extends NodeWorkspace { + #ghUrl + #releasesByPackage = new Map() + #pathsByComponent = new Map() + constructor (github, ...args) { super(github, ...args) - this.gh = makeGh(github) + this.#ghUrl = makeGitHubUrl(github.repository.owner, github.repository.repo) } async preconfigure (strategiesByPath, commitsByPath, releasesByPath) { @@ -65,10 +68,8 @@ module.exports = class extends NodeWorkspace { updatedVersions.set(packageName, version) } - // Save some data about the preconfiugred releases so we can look it up later + // Now save some data about the preconfiugred releases so we can look it up later // when rewriting the changelogs - this.releasesByPackage = new Map() - this.pathsByComponent = new Map() // Then go through all the packages again and add deps commits // for each updated workspace @@ -95,8 +96,8 @@ module.exports = class extends NodeWorkspace { } const component = await strategiesByPath[path].getComponent() - this.pathsByComponent.set(component, path) - this.releasesByPackage.set(packageName, { + this.#pathsByComponent.set(component, path) + this.#releasesByPackage.set(packageName, { path, component, }) @@ -138,20 +139,18 @@ module.exports = class extends NodeWorkspace { // Update notes with a link to each workspaces release notes // now that we have all of the releases in a single pull request release.notes = release.notes.replace(WORKSPACE_DEP, (_, depName, depVersion) => { - const { path, component } = this.releasesByPackage.get(depName) - - const url = this.gh.release(new TagName( + const { path, component } = this.#releasesByPackage.get(depName) + const url = this.#ghUrl('releases/tag', new TagName( depVersion, component, this.repositoryConfig[path].tagSeparator, this.repositoryConfig[path].includeVInTag - )) - + ).toString()) return `${link('Workspace', url)}: ${code(`${depName}@${depVersion}`)}` }) // Find the associated changelog and update that too - const path = this.pathsByComponent.get(release.component) + const path = this.#pathsByComponent.get(release.component) for (const update of candidate.pullRequest.updates) { if (update.path === addPath(path, 'CHANGELOG.md')) { update.updater.changelogEntry = release.notes @@ -161,8 +160,8 @@ module.exports = class extends NodeWorkspace { // Sort root release to the top of the pull request candidate.pullRequest.body.releaseData.sort((a, b) => { - const aPath = this.pathsByComponent.get(a.component) - const bPath = this.pathsByComponent.get(b.component) + const aPath = this.#pathsByComponent.get(a.component) + const bPath = this.#pathsByComponent.get(b.component) if (aPath === ROOT_PROJECT_PATH) { return -1 } @@ -190,3 +189,5 @@ module.exports = class extends NodeWorkspace { throw new Error('Should not create new candidates. This should be done in preconfigure.') } } + +module.exports = NpmNodeWorkspace diff --git a/lib/release/release-manager.js b/lib/release/release-manager.js new file mode 100644 index 00000000..b4f0025c --- /dev/null +++ b/lib/release/release-manager.js @@ -0,0 +1,250 @@ +const { Octokit } = require('@octokit/rest') +const core = require('@actions/core') +const semver = require('semver') +const assert = require('assert') +const dedent = require('dedent') +const mapWorkspaces = require('@npmcli/map-workspaces') +const { getPublishTag } = require('./util') + +class ReleaseManager { + #octokit + #owner + #repo + #cwd + #pkg + #pr + #backport + #defaultTag + #lockfile + #publish + + constructor ({ + token, + repo, + cwd, + pkg, + pr, + backport, + defaultTag, + lockfile, + publish, + }) { + assert(token, 'GITHUB_TOKEN is required') + assert(repo, 'GITHUB_REPOSITORY is required') + assert(cwd, 'cwd is required') + assert(pkg, 'pkg is required') + assert(pr, 'pr is required') + + this.#octokit = new Octokit({ auth: token }) + this.#owner = repo.split('/')[0] + this.#repo = repo.split('/')[1] + this.#cwd = cwd + this.#pkg = pkg + this.#pr = pr + this.#backport = backport + this.#defaultTag = defaultTag + this.#lockfile = lockfile + this.#publish = publish + } + + static async run (options) { + const manager = new ReleaseManager(options) + return manager.run() + } + + async run () { + const { data: pullRequest } = await this.#octokit.rest.pulls.get({ + owner: this.#owner, + repo: this.#repo, + pull_number: this.#pr, + }) + + const [release, workspaces = []] = await this.#getPrReleases({ pullRequest }) + const releaseItems = await this.#getReleaseProcess({ release, workspaces }) + + core.info( + `Filtered ${releaseItems.length} release process items:\n`, + releaseItems.map(r => r.split('\n')[0].replace('- [ ] ', '')).join(', ') + ) + + const checklist = releaseItems + .join('\n\n') + .replace(//g, pullRequest.number) + .replace(//g, pullRequest.head.ref) + .replace(//g, pullRequest.base.ref) + .replace(//g, release.major) + .replace(//g, release.version) + .replace(//g, release.url) + .replace(//g, release.flags) + .replace(/^(\s*\S.*)(-w )$/gm, workspaces.map(w => `$1${w.flags}`).join('\n')) + .trim() + + return `### Release Checklist for ${release.tag}\n\n${checklist}` + } + + async #getPrReleases ({ pullRequest }) { + const RELEASE_SEPARATOR = /
.*<\/summary>/g + const MONO_VERSIONS = /
(?:(.*?):\s)?(.*?)<\/summary>/ + const ROOT_VERSION = /\n##\s\[(.*?)\]/ + + const workspaces = [...await mapWorkspaces({ pkg: this.#pkg, cwd: this.#cwd })] + .reduce((acc, [k]) => { + const wsComponentName = k.startsWith('@') ? k.split('/')[1] : k + acc[wsComponentName] = k + return acc + }, {}) + + const getReleaseInfo = ({ name, version: rawVersion }) => { + const version = semver.parse(rawVersion) + const prerelease = !!version.prerelease.length + const tag = `${name ? `${name}-` : ''}v${rawVersion}` + const workspace = workspaces[name] + const publishTag = getPublishTag(rawVersion, { + backport: this.#backport, + defaultTag: this.#defaultTag, + }) + + return { + name, + tag, + prerelease, + version: rawVersion, + major: version.major, + url: `https://github.com/${pullRequest.base.repo.full_name}/releases/tag/${tag}`, + flags: `${name ? `-w ${workspace}` : ''} --tag=${publishTag}`.trim(), + } + } + + const releases = pullRequest.body.match(RELEASE_SEPARATOR) + + if (!releases) { + core.info('Found no monorepo, checking for single root version') + const [, version] = pullRequest.body.match(ROOT_VERSION) || [] + + if (!version) { + throw new Error('Could not find version with:', ROOT_VERSION) + } + + core.info('Found version', version) + return [getReleaseInfo({ version })] + } + + core.info(`Found ${releases.length} releases`) + + return releases.reduce((acc, r) => { + const [, name, version] = r.match(MONO_VERSIONS) + const release = getReleaseInfo({ name, version }) + + if (!name) { + core.info('Found root', release) + acc[0] = release + } else { + core.info('Found workspace', release) + acc[1].push(release) + } + + return acc + }, [null, []]) + } + + async #getReleaseProcess ({ release, workspaces }) { + const RELEASE_LIST_ITEM = /^\d+\.\s/gm + + core.info(`Fetching release process from:`, this.#owner, this.#repo, 'wiki') + + const releaseProcess = await fetch( + `https://raw.githubusercontent.com/wiki/${this.#owner}/${this.#repo}/Release-Process.md` + ) + .then(r => r.text()) + .catch(() => this.#getReleaseSteps()) + + // XXX: the release steps need to always be the last thing in the doc for this to work + const releaseLines = releaseProcess.split('\n') + const releaseStartLine = releaseLines.reduce((acc, l, i) => l.match(/^#+\s/) ? i : acc, 0) + const section = releaseLines.slice(releaseStartLine).join('\n') + + return section + .split({ + [Symbol.split] (str) { + const [, ...matches] = str.split(RELEASE_LIST_ITEM) + core.info(`Found ${matches.length} release items`) + return matches.map((m) => `- [ ] . ${m}`.trim()) + }, + }) + .filter((item) => { + if (release.prerelease && item.includes('> NOT FOR PRERELEASE')) { + return false + } + + if (!workspaces.length && item.includes('Publish workspaces')) { + return false + } + + return true + }) + .map((item, index) => item.replace('', index + 1)) + } + + #getReleaseSteps () { + const R = `-R ${this.#owner}/${this.#repo}` + /* eslint-disable max-len */ + const MANUAL_PUBLISH_STEPS = dedent` + 1. Checkout the release branch and test + + \`\`\`sh + gh pr checkout --force + npm ${this.#lockfile ? 'ci' : 'update'} + npm test + gh pr checks ${R} --watch + \`\`\` + + 1. Publish workspaces + + \`\`\`sh + npm publish -w + \`\`\` + + 1. Publish + + \`\`\`sh + npm publish + \`\`\` + + 1. Merge release PR + + \`\`\`sh + gh pr merge ${R} --rebase + git checkout + git fetch + git reset --hard origin/ + \`\`\` + ` + + const AUTO_PUBLISH_STEPS = dedent` + 1. Approve this PR + + \`\`\`sh + gh pr review ${R} --approve + \`\`\` + + 1. Merge release PR :rotating_light: Merging this will auto publish :rotating_light: + + \`\`\`sh + gh pr merge ${R} --rebase + \`\`\` + ` + + return (this.#publish ? AUTO_PUBLISH_STEPS : MANUAL_PUBLISH_STEPS) + dedent` + 1. Check For Release Tags + + Release Please will run on the just pushed release commit and create GitHub releases and tags for each package. + + \`\`\` + gh run watch ${R} $(gh run list ${R} -w release -b -L 1 --json databaseId -q ".[0].databaseId") + \`\`\` + ` + /* eslint-enable max-len */ + } +} + +module.exports = ReleaseManager diff --git a/lib/release/release-please.js b/lib/release/release-please.js new file mode 100644 index 00000000..4e9300fe --- /dev/null +++ b/lib/release/release-please.js @@ -0,0 +1,154 @@ +const { + Manifest, + GitHub, + registerChangelogNotes, + registerVersioningStrategy, + registerPlugin, +} = require('release-please') +const assert = require('assert') +const core = require('@actions/core') +const omit = require('just-omit') +const ChangelogNotes = require('./changelog.js') +const Version = require('./version.js') +const NodeWs = require('./node-workspace.js') +const { getPublishTag } = require('./util.js') + +class ReleasePlease { + #token + #owner + #repo + #branch + #backport + #defaultTag + #overrides + + #github + #octokit + #manifest + + constructor ({ + token, + repo, + branch, + backport, + defaultTag, + overrides, + }) { + assert(token, 'token is required') + assert(repo, 'repo is required') + assert(branch, 'branch is required') + + this.#token = token + this.#owner = repo.split('/')[0] + this.#repo = repo.split('/')[1] + this.#branch = branch + this.#backport = backport + this.#defaultTag = defaultTag + this.#overrides = overrides + } + + static async run (options) { + const releasePlease = new ReleasePlease(options) + await releasePlease.init() + return releasePlease.run() + } + + async init () { + registerChangelogNotes('default', ({ github, ...options }) => + new ChangelogNotes(github, options)) + registerVersioningStrategy('default', ({ github, ...options }) => + new Version(github, options)) + registerPlugin('node-workspace', ({ github, targetBranch, repositoryConfig, ...options }) => + new NodeWs(github, targetBranch, repositoryConfig, options)) + + this.#github = await GitHub.create({ + owner: this.#owner, + repo: this.#repo, + token: this.#token, + }) + this.#octokit = this.#github.octokit + this.#manifest = await Manifest.fromManifest( + this.#github, + this.#branch, + undefined, + undefined, + this.#overrides + ) + } + + async run () { + const rootPr = await this.#getRootPullRequest() + const releases = await this.#getReleases() + + if (rootPr) { + core.info(`root pr: ${JSON.stringify(omit(rootPr, 'body'), null, 2)}`) + + // release please does not guarantee that the release PR will have the latest sha, + // but we always need it so we can attach the relevant checks to the sha. + rootPr.sha = await this.#octokit.paginate(this.#octokit.rest.pulls.listCommits, { + owner: this.#owner, + repo: this.#repo, + pull_number: rootPr.number, + }).then(r => r[r.length - 1].sha) + } + + if (releases) { + core.info(`found releases: ${releases.length}`) + + for (const release of releases) { + core.info(`release: ${JSON.stringify(omit(release, 'notes'), null, 2)}`) + + release.publishTag = getPublishTag(release.version, { + backport: this.#backport, + defaultTag: this.#defaultTag, + }) + + release.prNumber = await this.#octokit.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: this.#owner, + repo: this.#repo, + commit_sha: release.sha, + per_page: 1, + }).then(r => r.data[0]?.number) + + release.pkgName = await this.#octokit.rest.repos.getContent({ + owner: this.#owner, + repo: this.#repo, + ref: this.#branch, + path: `${release.path === '.' ? '' : release.path}/package.json`, + }).then(r => JSON.parse(Buffer.from(r.data.content, r.data.encoding)).name) + } + } + + return { + pr: rootPr, + releases: releases, + } + } + + async #getRootPullRequest () { + // We only ever get a single pull request with our current release-please settings + // Update this if we start creating individual PRs per workspace release + const pullRequests = await this.#manifest.createPullRequests() + return pullRequests.filter(Boolean)[0] ?? null + } + + async #getReleases () { + // if we have a root release, always put it as the first item in the array + const rawReleases = await this.#manifest.createReleases().then(r => r.filter(Boolean)) + let rootRelease = null + const workspaceReleases = [] + + for (const release of rawReleases) { + if (!rootRelease && release.path === '.') { + rootRelease = release + } else { + workspaceReleases.push(release) + } + } + + const releases = [rootRelease, ...workspaceReleases].filter(Boolean) + return releases.length ? releases : null + } +} + +module.exports = ReleasePlease diff --git a/lib/release/util.js b/lib/release/util.js new file mode 100644 index 00000000..33c21d82 --- /dev/null +++ b/lib/release/util.js @@ -0,0 +1,27 @@ +const semver = require('semver') + +module.exports.specRe = new RegExp(`([^\\s]+@${semver.src[semver.tokens.FULLPLAIN]})`, 'g') + +module.exports.code = (c) => `\`${c}\`` +module.exports.link = (text, url) => `[${text}](${url})` +module.exports.list = (text) => `* ${text}` + +module.exports.dateFmt = (date = new Date()) => { + const year = date.getFullYear() + const month = (date.getMonth() + 1).toString().padStart(2, '0') + const day = date.getDate().toString().padStart(2, '0') + return [year, month, day].join('-') +} + +// TODO: test this +/* istanbul ignore next */ +module.exports.getPublishTag = (v, { backport, defaultTag }) => { + const version = semver.parse(v) + return version.prerelease.length + ? `prerelease-${version.major}` + : backport ? `latest-${backport}` + : defaultTag.replace(/{{\s*major\s*}}/, version.major) +} + +module.exports.makeGitHubUrl = (owner, repo) => + (...p) => `https://github.com/${owner}/${repo}/${p.join('/')}` diff --git a/lib/release-please/version.js b/lib/release/version.js similarity index 97% rename from lib/release-please/version.js rename to lib/release/version.js index 29960ee7..acdb2dd2 100644 --- a/lib/release-please/version.js +++ b/lib/release/version.js @@ -68,7 +68,7 @@ const semverToVersion = (v) => { // This does not account for pre v1 semantics since we don't publish those // Always 1.0.0 your initial versions! module.exports = class DefaultVersioningStrategy { - constructor (options) { + constructor (github, options) { this.prerelease = options.prerelease } @@ -99,5 +99,3 @@ module.exports = class DefaultVersioningStrategy { return semverToVersion(releaseVersion) } } - -module.exports.semverToVersion = semverToVersion diff --git a/package.json b/package.json index 7ada91a6..e69181b3 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@npmcli/map-workspaces": "^3.0.0", "@npmcli/package-json": "^5.0.0", "@octokit/rest": "^19.0.4", + "dedent": "^1.5.1", "diff": "^5.0.0", "glob": "^10.1.0", "handlebars": "^4.7.7", @@ -49,6 +50,7 @@ "json-parse-even-better-errors": "^3.0.0", "just-deep-map-values": "^1.1.1", "just-diff": "^6.0.0", + "just-omit": "^2.2.0", "lodash": "^4.17.21", "minimatch": "^9.0.2", "npm-package-arg": "^11.0.1", diff --git a/release-please-config.json b/release-please-config.json index e7ea5c84..39a04599 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -22,7 +22,8 @@ }, { "type": "chore", - "hidden": true + "section": "Chores", + "hidden": false } ], "packages": { diff --git a/tap-snapshots/test/apply/files-snapshots.js.test.cjs b/tap-snapshots/test/apply/files-snapshots.js.test.cjs index ef9298fd..79bd2bae 100644 --- a/tap-snapshots/test/apply/files-snapshots.js.test.cjs +++ b/tap-snapshots/test/apply/files-snapshots.js.test.cjs @@ -8,6 +8,8 @@ exports[`test/apply/files-snapshots.js TAP private workspace > expect resolving Promise 1`] = ` .commitlintrc.js .eslintrc.js +.github/actions/create-check/action.yml +.github/actions/install-latest-npm/action.yml .github/CODEOWNERS .github/dependabot.yml .github/ISSUE_TEMPLATE/bug.yml @@ -22,6 +24,7 @@ exports[`test/apply/files-snapshots.js TAP private workspace > expect resolving .github/workflows/codeql-analysis.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml +.github/workflows/release-integration.yml .github/workflows/release.yml .gitignore .npmrc @@ -41,6 +44,8 @@ workspaces/b/package.json exports[`test/apply/files-snapshots.js TAP turn off add/rm types > expect resolving Promise 1`] = ` .commitlintrc.js +.github/actions/create-check/action.yml +.github/actions/install-latest-npm/action.yml .github/CODEOWNERS .github/dependabot.yml .github/ISSUE_TEMPLATE/bug.yml @@ -53,6 +58,7 @@ exports[`test/apply/files-snapshots.js TAP turn off add/rm types > expect resolv .github/workflows/codeql-analysis.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml +.github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json package.json @@ -61,6 +67,8 @@ release-please-config.json exports[`test/apply/files-snapshots.js TAP turn off module > expect resolving Promise 1`] = ` .commitlintrc.js +.github/actions/create-check/action.yml +.github/actions/install-latest-npm/action.yml .github/CODEOWNERS .github/dependabot.yml .github/ISSUE_TEMPLATE/bug.yml @@ -73,6 +81,7 @@ exports[`test/apply/files-snapshots.js TAP turn off module > expect resolving Pr .github/workflows/codeql-analysis.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml +.github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json package.json @@ -95,6 +104,8 @@ package.json exports[`test/apply/files-snapshots.js TAP turn off specific files > expect resolving Promise 1`] = ` .eslintrc.yml +.github/actions/create-check/action.yml +.github/actions/install-latest-npm/action.yml .github/CODEOWNERS .github/dependabot.yml .github/ISSUE_TEMPLATE/bug.yml @@ -107,6 +118,7 @@ exports[`test/apply/files-snapshots.js TAP turn off specific files > expect reso .github/workflows/codeql-analysis.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml +.github/workflows/release-integration.yml .github/workflows/release-test.yml .github/workflows/release.yml .gitignore @@ -120,6 +132,8 @@ SECURITY.md ` exports[`test/apply/files-snapshots.js TAP workspaces > expect resolving Promise 1`] = ` +.github/actions/create-check/action.yml +.github/actions/install-latest-npm/action.yml .github/dependabot.yml .github/matchers/tap.json .github/settings.yml @@ -127,6 +141,7 @@ exports[`test/apply/files-snapshots.js TAP workspaces > expect resolving Promise .github/workflows/ci-release.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml +.github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json package.json @@ -144,6 +159,8 @@ workspaces/d/package.json ` exports[`test/apply/files-snapshots.js TAP workspaces only (like npm/cli) > expect resolving Promise 1`] = ` +.github/actions/create-check/action.yml +.github/actions/install-latest-npm/action.yml .github/dependabot.yml .github/matchers/tap.json .github/settings.yml @@ -152,6 +169,7 @@ exports[`test/apply/files-snapshots.js TAP workspaces only (like npm/cli) > expe .github/workflows/ci-release.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml +.github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json package.json diff --git a/tap-snapshots/test/apply/source-snapshots.js.test.cjs b/tap-snapshots/test/apply/source-snapshots.js.test.cjs index 901b2f94..36d27f52 100644 --- a/tap-snapshots/test/apply/source-snapshots.js.test.cjs +++ b/tap-snapshots/test/apply/source-snapshots.js.test.cjs @@ -42,6 +42,121 @@ module.exports = { ], } +.github/actions/create-check/action.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Create Check' +inputs: + name: + required: true + token: + required: true + sha: + required: true + check-name: + default: '' +outputs: + check-id: + value: \${{ steps.create-check.outputs.check_id }} +runs: + using: "composite" + steps: + - name: Get Workflow Job + uses: actions/github-script@v6 + id: workflow + env: + JOB_NAME: "\${{ inputs.name }}" + SHA: "\${{ inputs.sha }}" + with: + result-encoding: string + script: | + const { repo: { owner, repo}, runId, serverUrl } = context + const { JOB_NAME, SHA } = process.env + + const job = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: runId, + per_page: 100 + }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME))) + + return [ + \`This check is assosciated with \${serverUrl}/\${owner}/\${repo}/commit/\${SHA}.\`, + 'Run logs:', + job?.html_url || \`could not be found for a job ending with: "\${JOB_NAME}"\`, + ].join(' ') + - name: Create Check + uses: LouisBrunner/checks-action@v1.6.0 + id: create-check + with: + token: \${{ inputs.token }} + sha: \${{ inputs.sha }} + status: in_progress + name: \${{ inputs.check-name || inputs.name }} + output: | + {"summary":"\${{ steps.workflow.outputs.result }}"} + +.github/actions/install-latest-npm/action.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Install Latest npm' +description: 'Install the latest version of npm compatible with the Node version' +inputs: + node: + description: 'Current Node version' + required: true +runs: + using: "composite" + steps: + # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows + - name: Update Windows npm + if: | + runner.os == 'Windows' && ( + startsWith(inputs.node, 'v10.') || + startsWith(inputs.node, 'v12.') || + startsWith(inputs.node, 'v14.') + ) + shell: cmd + run: | + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz + tar xf npm-7.5.4.tgz + cd package + node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz + cd .. + rmdir /s /q package + - name: Install Latest npm + shell: bash + env: + NODE_VERSION: \${{ inputs.node }} + run: | + MATCH="" + SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") + + echo "node@$NODE_VERSION" + + for SPEC in \${SPECS[@]}; do + ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') + echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" + + if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then + MATCH=$SPEC + echo "Found compatible version: npm@$MATCH" + break + fi + done + + if [ -z $MATCH ]; then + echo "Could not find a compatible version of npm for node@$NODE_VERSION" + exit 1 + fi + + npm i --prefer-online --no-fund --no-audit -g npm@$MATCH + - name: npm Version + shell: bash + run: npm -v + .github/CODEOWNERS ======================================== # This file is automatically added by @npmcli/template-oss. Do not edit. @@ -257,37 +372,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund --package-lock - name: Run Production Audit @@ -326,49 +414,6 @@ jobs: run: shell: bash steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Lint All" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ inputs.check-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Lint All - sha: \${{ inputs.check-sha }} - output: \${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -377,43 +422,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: \${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Lint All" + token: \${{ secrets.GITHUB_TOKEN }} + sha: \${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -422,11 +448,11 @@ jobs: run: npm run postlint --ignore-scripts - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} - check_id: \${{ steps.check.outputs.check_id }} + check_id: \${{ steps.create-check.outputs.check-id }} test-all: name: Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }} @@ -451,49 +477,6 @@ jobs: run: shell: \${{ matrix.platform.shell }} steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Test All" - MATRIX_NAME: " - \${{ matrix.platform.name }} - \${{ matrix.node-version }}" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ inputs.check-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }} - sha: \${{ inputs.check-sha }} - output: \${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -502,59 +485,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: \${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }}" + token: \${{ secrets.GITHUB_TOKEN }} + sha: \${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -563,11 +511,11 @@ jobs: run: npm test --ignore-scripts - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} - check_id: \${{ steps.check.outputs.check_id }} + check_id: \${{ steps.create-check.outputs.check-id }} .github/workflows/ci.yml ======================================== @@ -608,37 +556,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -681,53 +602,10 @@ jobs: with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -812,37 +690,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Fetch Dependabot Metadata @@ -968,63 +819,100 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Run Commitlint on Commits id: commit continue-on-error: true - run: | - npx --offline commitlint -V --from 'origin/\${{ github.base_ref }}' --to \${{ github.event.pull_request.head.sha }} + run: npx --offline commitlint -V --from 'origin/\${{ github.base_ref }}' --to \${{ github.event.pull_request.head.sha }} - name: Run Commitlint on PR Title if: steps.commit.outcome == 'failure' env: PR_TITLE: \${{ github.event.pull_request.title }} - run: | - echo "$PR_TITLE" | npx --offline commitlint -V + run: echo "$PR_TITLE" | npx --offline commitlint -V -.github/workflows/release.yml +.github/workflows/release-integration.yml ======================================== # This file is automatically added by @npmcli/template-oss. Do not edit. -name: Release +name: Release Integration on: workflow_dispatch: inputs: - release-pr: - description: a release PR number to rerun release jobs on + releases: + required: true + type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + workflow_call: + inputs: + releases: + required: true type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + +jobs: + publish: + name: Check Publish + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Git User + run: | + git config --global user.email "npm-cli+bot@github.com" + git config --global user.name "npm CLI robot" + - name: Setup Node + uses: actions/setup-node@v3 + id: node + with: + node-version: 20.x + check-latest: contains('20.x', '.x') + - name: Install Latest npm + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} + - name: Install Dependencies + run: npm i --ignore-scripts --no-audit --no-fund + - name: Check If Published + run: | + EXIT_CODE=0 + + function each_release { + if npm view "$@" --loglevel=error > /dev/null; then + echo 0 + else + echo 1 + fi + } + + for release in $(echo '\${{ inputs.releases }}' | jq -r '.[] | @base64'); do + SPEC="$(echo "$release" | base64 --decode | jq -r .pkgName)@$(echo "$release" | base64 --decode | jq -r .version)" + STATUS=$(each_release "$SPEC") + if [[ "$STATUS" -eq 1 ]]; then + EXIT_CODE=$STATUS + echo "$SPEC ERROR" + else + echo "$SPEC OK" + fi + done + + exit $EXIT_CODE + +.github/workflows/release.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Release + +on: push: branches: - main @@ -1040,12 +928,12 @@ jobs: release: outputs: pr: \${{ steps.release.outputs.pr }} - release: \${{ steps.release.outputs.release }} - releases: \${{ steps.release.outputs.releases }} - branch: \${{ steps.release.outputs.pr-branch }} + pr-branch: \${{ steps.release.outputs.pr-branch }} pr-number: \${{ steps.release.outputs.pr-number }} - comment-id: \${{ steps.pr-comment.outputs.result }} - check-id: \${{ steps.check.outputs.check_id }} + pr-sha: \${{ steps.release.outputs.pr-sha }} + releases: \${{ steps.release.outputs.releases }} + comment-id: \${{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }} + check-id: \${{ steps.create-check.outputs.check-id }} name: Release if: github.repository_owner == 'npm' runs-on: ubuntu-latest @@ -1065,126 +953,64 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Release Please id: release env: GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} - run: | - npx --offline template-oss-release-please "\${{ github.ref_name }}" "\${{ inputs.release-pr }}" - - name: Post Pull Request Comment + run: npx --offline template-oss-release-please --branch="\${{ github.ref_name }}" --backport="" --defaultTag="latest" + - name: Create Release Manager Comment Text if: steps.release.outputs.pr-number uses: actions/github-script@v6 - id: pr-comment - env: - PR_NUMBER: \${{ steps.release.outputs.pr-number }} - REF_NAME: \${{ github.ref_name }} + id: comment-text with: + result-encoding: string script: | - const { REF_NAME, PR_NUMBER: issue_number } = process.env const { runId, repo: { owner, repo } } = context - const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId }) - - let body = '## Release Manager/n/n' - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - let commentId = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id - - body += \`Release workflow run: \${workflow.html_url}/n/n#### Force CI to Update This Release/n/n\` - body += \`This PR will be updated and CI will run for every non-/\`chore:/\` commit that is pushed to /\`\${REF_NAME}/\`. \` - body += \`To force CI to update this PR, run this command:/n/n\` - body += \`/\`/\`/\`/ngh workflow run release.yml -r \${REF_NAME} -R \${owner}/\${repo} -f release-pr=\${issue_number}/n/\`/\`/\`\` - - if (commentId) { - await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, body }) - } else { - const { data: comment } = await github.rest.issues.createComment({ owner, repo, issue_number, body }) - commentId = comment?.id - } - - return commentId - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.release.outputs.pr-sha - id: check-output - env: - JOB_NAME: "Release" - MATRIX_NAME: "" + return['## Release Manager', \`Release workflow run: \${workflow.html_url}\`].join('/n/n') + - name: Find Release Manager Comment + uses: peter-evans/find-comment@v2 + if: steps.release.outputs.pr-number + id: found-comment with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ steps.release.outputs.pr-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } + issue-number: \${{ steps.release.outputs.pr-number }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Manager' + - name: Create Release Manager Comment + id: create-comment + if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: \${{ steps.release.outputs.pr-number }} + body: \${{ steps.comment-text.outputs.result }} + - name: Update Release Manager Comment + id: update-comment + if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ steps.found-comment.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check + id: create-check + uses: ./.github/actions/create-check if: steps.release.outputs.pr-sha with: + name: "Release" token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: \${{ steps.release.outputs.pr-sha }} - output: \${{ steps.check-output.outputs.result }} update: needs: release outputs: sha: \${{ steps.commit.outputs.sha }} - check-id: \${{ steps.check.outputs.check_id }} + check-id: \${{ steps.create-check.outputs.check-id }} name: Update - Release if: github.repository_owner == 'npm' && needs.release.outputs.pr runs-on: ubuntu-latest @@ -1196,7 +1022,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - ref: \${{ needs.release.outputs.branch }} + ref: \${{ needs.release.outputs.pr-branch }} - name: Setup Git User run: | git config --global user.email "npm-cli+bot@github.com" @@ -1207,47 +1033,27 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund + - name: Create Release Manager Checklist Text + id: comment-text + env: + GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} + run: npm exec --offline -- template-oss-release-manager --pr="\${{ needs.release.outputs.pr-number }}" --backport="" --defaultTag="latest" + - name: Append Release Manager Comment + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ needs.release.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'append' - name: Run Post Pull Request Actions env: - RELEASE_PR_NUMBER: \${{ needs.release.outputs.pr-number }} - RELEASE_COMMENT_ID: \${{ needs.release.outputs.comment-id }} GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} - run: | - npm exec --offline -- template-oss-release-manager --lockfile=false --publish=false - npm run rp-pull-request --ignore-scripts --if-present + run: npm run rp-pull-request --ignore-scripts --if-present -- --pr="\${{ needs.release.outputs.pr-number }}" --commentId="\${{ needs.release.outputs.comment-id }}" - name: Commit id: commit env: @@ -1256,52 +1062,16 @@ jobs: git commit --all --amend --no-edit || true git push --force-with-lease echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.commit.outputs.sha - id: check-output - env: - JOB_NAME: "Update - Release" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ steps.commit.outputs.sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: steps.commit.outputs.sha + id: create-check + uses: ./.github/actions/create-check with: + name: "Update - Release" + check-name: "Release" token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: \${{ steps.commit.outputs.sha }} - output: \${{ steps.check-output.outputs.result }} - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.release.outputs.check-id && always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} @@ -1313,7 +1083,7 @@ jobs: if: needs.release.outputs.pr uses: ./.github/workflows/ci-release.yml with: - ref: \${{ needs.release.outputs.branch }} + ref: \${{ needs.release.outputs.pr-branch }} check-sha: \${{ needs.update.outputs.sha }} post-ci: @@ -1325,8 +1095,8 @@ jobs: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get CI Conclusion + id: conclusion run: | result="" if [[ "\${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then @@ -1339,14 +1109,15 @@ jobs: echo "result=$result" >> $GITHUB_OUTPUT - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.update.outputs.check-id && always() with: token: \${{ secrets.GITHUB_TOKEN }} - conclusion: \${{ steps.needs-result.outputs.result }} + conclusion: \${{ steps.conclusion.outputs.result }} check_id: \${{ needs.update.outputs.check-id }} post-release: needs: release + outputs: + comment-id: \${{ steps.create-comment.outputs.comment-id }} name: Post Release - Release if: github.repository_owner == 'npm' && needs.release.outputs.releases runs-on: ubuntu-latest @@ -1354,123 +1125,50 @@ jobs: run: shell: bash steps: - - name: Create Release PR Comment + - name: Create Release PR Comment Text + id: comment-text uses: actions/github-script@v6 env: RELEASES: \${{ needs.release.outputs.releases }} with: + result-encoding: string script: | const releases = JSON.parse(process.env.RELEASES) const { runId, repo: { owner, repo } } = context const issue_number = releases[0].prNumber - - let body = '## Release Workflow/n/n' - for (const { pkgName, version, url } of releases) { - body += \`- /\`\${pkgName}@\${version}/\` \${url}/n\` - } - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - .then(cs => cs.map(c => ({ id: c.id, login: c.user.login, body: c.body }))) - console.log(\`Found comments: \${JSON.stringify(comments, null, 2)}\`) - const releaseComments = comments.filter(c => c.login === 'github-actions[bot]' && c.body.includes('Release is at')) - - for (const comment of releaseComments) { - console.log(\`Release comment: \${JSON.stringify(comment, null, 2)}\`) - await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id }) - } - const runUrl = \`https://github.com/\${owner}/\${repo}/actions/runs/\${runId}\` - await github.rest.issues.createComment({ - owner, - repo, - issue_number, - body: \`\${body}- Workflow run: :arrows_counterclockwise: \${runUrl}\`, - }) + + return [ + '## Release Workflow/n', + ...releases.map(r => \`- /\`\${r.pkgName}@\${r.version}/\` \${r.url}\`), + \`- Workflow run: :arrows_counterclockwise: \${runUrl}\`, + ].join('/n') + - name: Create Release PR Comment + id: create-comment + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: \${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + body: \${{ steps.comment-text.outputs.result }} release-integration: needs: release name: Release Integration - if: needs.release.outputs.release - runs-on: ubuntu-latest - defaults: - run: - shell: bash - steps: - - name: Setup Node - uses: actions/setup-node@v3 - id: node - with: - node-version: 20.x - check-latest: contains('20.x', '.x') - - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v - - name: View in Registry - run: | - EXIT_CODE=0 - - function is_published { - if npm view "$@" --loglevel=error > /dev/null; then - echo 0 - else - echo 1 - fi - } - - for release in $(echo '\${{ needs.release.outputs.releases }}' | jq -r '.[] | @base64'); do - name=$(echo "$release" | base64 --decode | jq -r .pkgName) - version=$(echo "$release" | base64 --decode | jq -r .version) - spec="$name@$version" - status=$(is_published "$spec") - if [[ "$status" -eq 1 ]]; then - echo "$spec ERROR" - EXIT_CODE=$status - else - echo "$spec OK" - fi - done - - exit $EXIT_CODE + if: needs.release.outputs.releases + uses: ./.github/workflows/release-integration.yml + with: + releases: \${{ needs.release.outputs.releases }} post-release-integration: - needs: [ release, release-integration ] + needs: [ release, release-integration, post-release ] name: Post Release Integration - Release - if: github.repository_owner == 'npm' && needs.release.outputs.release && always() + if: github.repository_owner == 'npm' && needs.release.outputs.releases && always() runs-on: ubuntu-latest defaults: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get Post Release Conclusion + id: conclusion run: | if [[ "\${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then result="x" @@ -1480,42 +1178,41 @@ jobs: result="white_check_mark" fi echo "result=$result" >> $GITHUB_OUTPUT - - name: Update Release PR Comment + - name: Find Release PR Comment + uses: peter-evans/find-comment@v2 + id: found-comment + with: + issue-number: \${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Workflow' + - name: Create Release PR Comment Text + id: comment-text + if: steps.found-comment.outputs.comment-id uses: actions/github-script@v6 env: - PR_NUMBER: \${{ fromJSON(needs.release.outputs.release).prNumber }} - RESULT: \${{ steps.needs-result.outputs.result }} + RESULT: \${{ steps.conclusion.outputs.result }} + BODY: \${{ steps.found-comment.outputs.comment-body }} with: + result-encoding: string script: | - const { PR_NUMBER: issue_number, RESULT } = process.env - const { runId, repo: { owner, repo } } = context - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - const updateComment = comments.find(c => - c.user.login === 'github-actions[bot]' && - c.body.startsWith('## Release Workflow/n/n') && - c.body.includes(runId) - ) - - if (updateComment) { - console.log('Found comment to update:', JSON.stringify(updateComment, null, 2)) - let body = updateComment.body.replace(/Workflow run: :[a-z_]+:/, \`Workflow run: :\${RESULT}:\`) - const tagCodeowner = RESULT !== 'white_check_mark' - if (tagCodeowner) { - body += \`/n/n:rotating_light:\` - body += \` @npm/cli-team: The post-release workflow failed for this release.\` - body += \` Manual steps may need to be taken after examining the workflow output\` - body += \` from the above workflow run. :rotating_light:\` - } - await github.rest.issues.updateComment({ - owner, - repo, - body, - comment_id: updateComment.id, - }) - } else { - console.log('No matching comments found:', JSON.stringify(comments, null, 2)) + const { RESULT, BODY } = process.env + const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, \`$1\${RESULT}$2\`)] + if (RESULT !== 'white_check_mark') { + body.push(':rotating_light::rotating_light::rotating_light:') + body.push([ + '@npm/cli-team: The post-release workflow failed for this release.', + 'Manual steps may need to be taken after examining the workflow output.' + ].join(' ')) + body.push(':rotating_light::rotating_light::rotating_light:') } + return body.join('/n/n').trim() + - name: Update Release PR Comment + if: steps.comment-text.outputs.result + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ steps.found-comment.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' .gitignore ======================================== @@ -1687,7 +1384,8 @@ release-please-config.json }, { "type": "chore", - "hidden": true + "section": "Chores", + "hidden": false } ], "packages": { @@ -1771,6 +1469,121 @@ module.exports = { ], } +.github/actions/create-check/action.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Create Check' +inputs: + name: + required: true + token: + required: true + sha: + required: true + check-name: + default: '' +outputs: + check-id: + value: \${{ steps.create-check.outputs.check_id }} +runs: + using: "composite" + steps: + - name: Get Workflow Job + uses: actions/github-script@v6 + id: workflow + env: + JOB_NAME: "\${{ inputs.name }}" + SHA: "\${{ inputs.sha }}" + with: + result-encoding: string + script: | + const { repo: { owner, repo}, runId, serverUrl } = context + const { JOB_NAME, SHA } = process.env + + const job = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: runId, + per_page: 100 + }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME))) + + return [ + \`This check is assosciated with \${serverUrl}/\${owner}/\${repo}/commit/\${SHA}.\`, + 'Run logs:', + job?.html_url || \`could not be found for a job ending with: "\${JOB_NAME}"\`, + ].join(' ') + - name: Create Check + uses: LouisBrunner/checks-action@v1.6.0 + id: create-check + with: + token: \${{ inputs.token }} + sha: \${{ inputs.sha }} + status: in_progress + name: \${{ inputs.check-name || inputs.name }} + output: | + {"summary":"\${{ steps.workflow.outputs.result }}"} + +.github/actions/install-latest-npm/action.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Install Latest npm' +description: 'Install the latest version of npm compatible with the Node version' +inputs: + node: + description: 'Current Node version' + required: true +runs: + using: "composite" + steps: + # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows + - name: Update Windows npm + if: | + runner.os == 'Windows' && ( + startsWith(inputs.node, 'v10.') || + startsWith(inputs.node, 'v12.') || + startsWith(inputs.node, 'v14.') + ) + shell: cmd + run: | + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz + tar xf npm-7.5.4.tgz + cd package + node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz + cd .. + rmdir /s /q package + - name: Install Latest npm + shell: bash + env: + NODE_VERSION: \${{ inputs.node }} + run: | + MATCH="" + SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") + + echo "node@$NODE_VERSION" + + for SPEC in \${SPECS[@]}; do + ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') + echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" + + if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then + MATCH=$SPEC + echo "Found compatible version: npm@$MATCH" + break + fi + done + + if [ -z $MATCH ]; then + echo "Could not find a compatible version of npm for node@$NODE_VERSION" + exit 1 + fi + + npm i --prefer-online --no-fund --no-audit -g npm@$MATCH + - name: npm Version + shell: bash + run: npm -v + .github/CODEOWNERS ======================================== # This file is automatically added by @npmcli/template-oss. Do not edit. @@ -1986,37 +1799,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund --package-lock - name: Run Production Audit @@ -2067,37 +1853,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -2140,53 +1899,10 @@ jobs: with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -2237,37 +1953,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -2310,53 +1999,10 @@ jobs: with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -2395,49 +2041,6 @@ jobs: run: shell: bash steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Lint All" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ inputs.check-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Lint All - sha: \${{ inputs.check-sha }} - output: \${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -2446,43 +2049,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: \${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Lint All" + token: \${{ secrets.GITHUB_TOKEN }} + sha: \${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -2491,11 +2075,11 @@ jobs: run: npm run postlint --ignore-scripts -ws -iwr --if-present - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} - check_id: \${{ steps.check.outputs.check_id }} + check_id: \${{ steps.create-check.outputs.check-id }} test-all: name: Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }} @@ -2520,49 +2104,6 @@ jobs: run: shell: \${{ matrix.platform.shell }} steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Test All" - MATRIX_NAME: " - \${{ matrix.platform.name }} - \${{ matrix.node-version }}" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ inputs.check-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }} - sha: \${{ inputs.check-sha }} - output: \${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -2571,59 +2112,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: \${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }}" + token: \${{ secrets.GITHUB_TOKEN }} + sha: \${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -2632,11 +2138,11 @@ jobs: run: npm test --ignore-scripts -ws -iwr --if-present - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} - check_id: \${{ steps.check.outputs.check_id }} + check_id: \${{ steps.create-check.outputs.check-id }} .github/workflows/ci.yml ======================================== @@ -2683,37 +2189,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -2756,53 +2235,10 @@ jobs: with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -2887,37 +2323,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Fetch Dependabot Metadata @@ -3043,63 +2452,100 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Run Commitlint on Commits id: commit continue-on-error: true - run: | - npx --offline commitlint -V --from 'origin/\${{ github.base_ref }}' --to \${{ github.event.pull_request.head.sha }} + run: npx --offline commitlint -V --from 'origin/\${{ github.base_ref }}' --to \${{ github.event.pull_request.head.sha }} - name: Run Commitlint on PR Title if: steps.commit.outcome == 'failure' env: PR_TITLE: \${{ github.event.pull_request.title }} - run: | - echo "$PR_TITLE" | npx --offline commitlint -V + run: echo "$PR_TITLE" | npx --offline commitlint -V -.github/workflows/release.yml +.github/workflows/release-integration.yml ======================================== # This file is automatically added by @npmcli/template-oss. Do not edit. -name: Release +name: Release Integration on: workflow_dispatch: inputs: - release-pr: - description: a release PR number to rerun release jobs on + releases: + required: true + type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + workflow_call: + inputs: + releases: + required: true type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + +jobs: + publish: + name: Check Publish + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Git User + run: | + git config --global user.email "npm-cli+bot@github.com" + git config --global user.name "npm CLI robot" + - name: Setup Node + uses: actions/setup-node@v3 + id: node + with: + node-version: 20.x + check-latest: contains('20.x', '.x') + - name: Install Latest npm + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} + - name: Install Dependencies + run: npm i --ignore-scripts --no-audit --no-fund + - name: Check If Published + run: | + EXIT_CODE=0 + + function each_release { + if npm view "$@" --loglevel=error > /dev/null; then + echo 0 + else + echo 1 + fi + } + + for release in $(echo '\${{ inputs.releases }}' | jq -r '.[] | @base64'); do + SPEC="$(echo "$release" | base64 --decode | jq -r .pkgName)@$(echo "$release" | base64 --decode | jq -r .version)" + STATUS=$(each_release "$SPEC") + if [[ "$STATUS" -eq 1 ]]; then + EXIT_CODE=$STATUS + echo "$SPEC ERROR" + else + echo "$SPEC OK" + fi + done + + exit $EXIT_CODE + +.github/workflows/release.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Release + +on: push: branches: - main @@ -3115,12 +2561,12 @@ jobs: release: outputs: pr: \${{ steps.release.outputs.pr }} - release: \${{ steps.release.outputs.release }} - releases: \${{ steps.release.outputs.releases }} - branch: \${{ steps.release.outputs.pr-branch }} + pr-branch: \${{ steps.release.outputs.pr-branch }} pr-number: \${{ steps.release.outputs.pr-number }} - comment-id: \${{ steps.pr-comment.outputs.result }} - check-id: \${{ steps.check.outputs.check_id }} + pr-sha: \${{ steps.release.outputs.pr-sha }} + releases: \${{ steps.release.outputs.releases }} + comment-id: \${{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }} + check-id: \${{ steps.create-check.outputs.check-id }} name: Release if: github.repository_owner == 'npm' runs-on: ubuntu-latest @@ -3140,126 +2586,64 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Release Please id: release env: GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} - run: | - npx --offline template-oss-release-please "\${{ github.ref_name }}" "\${{ inputs.release-pr }}" - - name: Post Pull Request Comment + run: npx --offline template-oss-release-please --branch="\${{ github.ref_name }}" --backport="" --defaultTag="latest" + - name: Create Release Manager Comment Text if: steps.release.outputs.pr-number uses: actions/github-script@v6 - id: pr-comment - env: - PR_NUMBER: \${{ steps.release.outputs.pr-number }} - REF_NAME: \${{ github.ref_name }} + id: comment-text with: + result-encoding: string script: | - const { REF_NAME, PR_NUMBER: issue_number } = process.env const { runId, repo: { owner, repo } } = context - const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId }) - - let body = '## Release Manager/n/n' - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - let commentId = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id - - body += \`Release workflow run: \${workflow.html_url}/n/n#### Force CI to Update This Release/n/n\` - body += \`This PR will be updated and CI will run for every non-/\`chore:/\` commit that is pushed to /\`\${REF_NAME}/\`. \` - body += \`To force CI to update this PR, run this command:/n/n\` - body += \`/\`/\`/\`/ngh workflow run release.yml -r \${REF_NAME} -R \${owner}/\${repo} -f release-pr=\${issue_number}/n/\`/\`/\`\` - - if (commentId) { - await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, body }) - } else { - const { data: comment } = await github.rest.issues.createComment({ owner, repo, issue_number, body }) - commentId = comment?.id - } - - return commentId - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.release.outputs.pr-sha - id: check-output - env: - JOB_NAME: "Release" - MATRIX_NAME: "" + return['## Release Manager', \`Release workflow run: \${workflow.html_url}\`].join('/n/n') + - name: Find Release Manager Comment + uses: peter-evans/find-comment@v2 + if: steps.release.outputs.pr-number + id: found-comment with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ steps.release.outputs.pr-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } + issue-number: \${{ steps.release.outputs.pr-number }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Manager' + - name: Create Release Manager Comment + id: create-comment + if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: \${{ steps.release.outputs.pr-number }} + body: \${{ steps.comment-text.outputs.result }} + - name: Update Release Manager Comment + id: update-comment + if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ steps.found-comment.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check + id: create-check + uses: ./.github/actions/create-check if: steps.release.outputs.pr-sha with: + name: "Release" token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: \${{ steps.release.outputs.pr-sha }} - output: \${{ steps.check-output.outputs.result }} update: needs: release outputs: sha: \${{ steps.commit.outputs.sha }} - check-id: \${{ steps.check.outputs.check_id }} + check-id: \${{ steps.create-check.outputs.check-id }} name: Update - Release if: github.repository_owner == 'npm' && needs.release.outputs.pr runs-on: ubuntu-latest @@ -3271,7 +2655,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - ref: \${{ needs.release.outputs.branch }} + ref: \${{ needs.release.outputs.pr-branch }} - name: Setup Git User run: | git config --global user.email "npm-cli+bot@github.com" @@ -3282,47 +2666,27 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund + - name: Create Release Manager Checklist Text + id: comment-text + env: + GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} + run: npm exec --offline -- template-oss-release-manager --pr="\${{ needs.release.outputs.pr-number }}" --backport="" --defaultTag="latest" + - name: Append Release Manager Comment + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ needs.release.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'append' - name: Run Post Pull Request Actions env: - RELEASE_PR_NUMBER: \${{ needs.release.outputs.pr-number }} - RELEASE_COMMENT_ID: \${{ needs.release.outputs.comment-id }} GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} - run: | - npm exec --offline -- template-oss-release-manager --lockfile=false --publish=false - npm run rp-pull-request --ignore-scripts -ws -iwr --if-present + run: npm run rp-pull-request --ignore-scripts -ws -iwr --if-present -- --pr="\${{ needs.release.outputs.pr-number }}" --commentId="\${{ needs.release.outputs.comment-id }}" - name: Commit id: commit env: @@ -3331,52 +2695,16 @@ jobs: git commit --all --amend --no-edit || true git push --force-with-lease echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.commit.outputs.sha - id: check-output - env: - JOB_NAME: "Update - Release" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ steps.commit.outputs.sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: steps.commit.outputs.sha + id: create-check + uses: ./.github/actions/create-check with: + name: "Update - Release" + check-name: "Release" token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: \${{ steps.commit.outputs.sha }} - output: \${{ steps.check-output.outputs.result }} - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.release.outputs.check-id && always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} @@ -3388,7 +2716,7 @@ jobs: if: needs.release.outputs.pr uses: ./.github/workflows/ci-release.yml with: - ref: \${{ needs.release.outputs.branch }} + ref: \${{ needs.release.outputs.pr-branch }} check-sha: \${{ needs.update.outputs.sha }} post-ci: @@ -3400,8 +2728,8 @@ jobs: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get CI Conclusion + id: conclusion run: | result="" if [[ "\${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then @@ -3414,14 +2742,15 @@ jobs: echo "result=$result" >> $GITHUB_OUTPUT - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.update.outputs.check-id && always() with: token: \${{ secrets.GITHUB_TOKEN }} - conclusion: \${{ steps.needs-result.outputs.result }} + conclusion: \${{ steps.conclusion.outputs.result }} check_id: \${{ needs.update.outputs.check-id }} post-release: needs: release + outputs: + comment-id: \${{ steps.create-comment.outputs.comment-id }} name: Post Release - Release if: github.repository_owner == 'npm' && needs.release.outputs.releases runs-on: ubuntu-latest @@ -3429,123 +2758,50 @@ jobs: run: shell: bash steps: - - name: Create Release PR Comment + - name: Create Release PR Comment Text + id: comment-text uses: actions/github-script@v6 env: RELEASES: \${{ needs.release.outputs.releases }} with: + result-encoding: string script: | const releases = JSON.parse(process.env.RELEASES) const { runId, repo: { owner, repo } } = context const issue_number = releases[0].prNumber - - let body = '## Release Workflow/n/n' - for (const { pkgName, version, url } of releases) { - body += \`- /\`\${pkgName}@\${version}/\` \${url}/n\` - } - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - .then(cs => cs.map(c => ({ id: c.id, login: c.user.login, body: c.body }))) - console.log(\`Found comments: \${JSON.stringify(comments, null, 2)}\`) - const releaseComments = comments.filter(c => c.login === 'github-actions[bot]' && c.body.includes('Release is at')) - - for (const comment of releaseComments) { - console.log(\`Release comment: \${JSON.stringify(comment, null, 2)}\`) - await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id }) - } - const runUrl = \`https://github.com/\${owner}/\${repo}/actions/runs/\${runId}\` - await github.rest.issues.createComment({ - owner, - repo, - issue_number, - body: \`\${body}- Workflow run: :arrows_counterclockwise: \${runUrl}\`, - }) + + return [ + '## Release Workflow/n', + ...releases.map(r => \`- /\`\${r.pkgName}@\${r.version}/\` \${r.url}\`), + \`- Workflow run: :arrows_counterclockwise: \${runUrl}\`, + ].join('/n') + - name: Create Release PR Comment + id: create-comment + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: \${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + body: \${{ steps.comment-text.outputs.result }} release-integration: needs: release name: Release Integration - if: needs.release.outputs.release - runs-on: ubuntu-latest - defaults: - run: - shell: bash - steps: - - name: Setup Node - uses: actions/setup-node@v3 - id: node - with: - node-version: 20.x - check-latest: contains('20.x', '.x') - - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v - - name: View in Registry - run: | - EXIT_CODE=0 - - function is_published { - if npm view "$@" --loglevel=error > /dev/null; then - echo 0 - else - echo 1 - fi - } - - for release in $(echo '\${{ needs.release.outputs.releases }}' | jq -r '.[] | @base64'); do - name=$(echo "$release" | base64 --decode | jq -r .pkgName) - version=$(echo "$release" | base64 --decode | jq -r .version) - spec="$name@$version" - status=$(is_published "$spec") - if [[ "$status" -eq 1 ]]; then - echo "$spec ERROR" - EXIT_CODE=$status - else - echo "$spec OK" - fi - done - - exit $EXIT_CODE + if: needs.release.outputs.releases + uses: ./.github/workflows/release-integration.yml + with: + releases: \${{ needs.release.outputs.releases }} post-release-integration: - needs: [ release, release-integration ] + needs: [ release, release-integration, post-release ] name: Post Release Integration - Release - if: github.repository_owner == 'npm' && needs.release.outputs.release && always() + if: github.repository_owner == 'npm' && needs.release.outputs.releases && always() runs-on: ubuntu-latest defaults: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get Post Release Conclusion + id: conclusion run: | if [[ "\${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then result="x" @@ -3555,42 +2811,41 @@ jobs: result="white_check_mark" fi echo "result=$result" >> $GITHUB_OUTPUT - - name: Update Release PR Comment + - name: Find Release PR Comment + uses: peter-evans/find-comment@v2 + id: found-comment + with: + issue-number: \${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Workflow' + - name: Create Release PR Comment Text + id: comment-text + if: steps.found-comment.outputs.comment-id uses: actions/github-script@v6 env: - PR_NUMBER: \${{ fromJSON(needs.release.outputs.release).prNumber }} - RESULT: \${{ steps.needs-result.outputs.result }} + RESULT: \${{ steps.conclusion.outputs.result }} + BODY: \${{ steps.found-comment.outputs.comment-body }} with: + result-encoding: string script: | - const { PR_NUMBER: issue_number, RESULT } = process.env - const { runId, repo: { owner, repo } } = context - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - const updateComment = comments.find(c => - c.user.login === 'github-actions[bot]' && - c.body.startsWith('## Release Workflow/n/n') && - c.body.includes(runId) - ) - - if (updateComment) { - console.log('Found comment to update:', JSON.stringify(updateComment, null, 2)) - let body = updateComment.body.replace(/Workflow run: :[a-z_]+:/, \`Workflow run: :\${RESULT}:\`) - const tagCodeowner = RESULT !== 'white_check_mark' - if (tagCodeowner) { - body += \`/n/n:rotating_light:\` - body += \` @npm/cli-team: The post-release workflow failed for this release.\` - body += \` Manual steps may need to be taken after examining the workflow output\` - body += \` from the above workflow run. :rotating_light:\` - } - await github.rest.issues.updateComment({ - owner, - repo, - body, - comment_id: updateComment.id, - }) - } else { - console.log('No matching comments found:', JSON.stringify(comments, null, 2)) + const { RESULT, BODY } = process.env + const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, \`$1\${RESULT}$2\`)] + if (RESULT !== 'white_check_mark') { + body.push(':rotating_light::rotating_light::rotating_light:') + body.push([ + '@npm/cli-team: The post-release workflow failed for this release.', + 'Manual steps may need to be taken after examining the workflow output.' + ].join(' ')) + body.push(':rotating_light::rotating_light::rotating_light:') } + return body.join('/n/n').trim() + - name: Update Release PR Comment + if: steps.comment-text.outputs.result + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ steps.found-comment.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' .gitignore ======================================== @@ -3782,7 +3037,8 @@ release-please-config.json }, { "type": "chore", - "hidden": true + "section": "Chores", + "hidden": false } ], "packages": { @@ -3972,6 +3228,121 @@ workspaces/b/package.json ` exports[`test/apply/source-snapshots.js TAP workspaces only > expect resolving Promise 1`] = ` +.github/actions/create-check/action.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Create Check' +inputs: + name: + required: true + token: + required: true + sha: + required: true + check-name: + default: '' +outputs: + check-id: + value: \${{ steps.create-check.outputs.check_id }} +runs: + using: "composite" + steps: + - name: Get Workflow Job + uses: actions/github-script@v6 + id: workflow + env: + JOB_NAME: "\${{ inputs.name }}" + SHA: "\${{ inputs.sha }}" + with: + result-encoding: string + script: | + const { repo: { owner, repo}, runId, serverUrl } = context + const { JOB_NAME, SHA } = process.env + + const job = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: runId, + per_page: 100 + }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME))) + + return [ + \`This check is assosciated with \${serverUrl}/\${owner}/\${repo}/commit/\${SHA}.\`, + 'Run logs:', + job?.html_url || \`could not be found for a job ending with: "\${JOB_NAME}"\`, + ].join(' ') + - name: Create Check + uses: LouisBrunner/checks-action@v1.6.0 + id: create-check + with: + token: \${{ inputs.token }} + sha: \${{ inputs.sha }} + status: in_progress + name: \${{ inputs.check-name || inputs.name }} + output: | + {"summary":"\${{ steps.workflow.outputs.result }}"} + +.github/actions/install-latest-npm/action.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: 'Install Latest npm' +description: 'Install the latest version of npm compatible with the Node version' +inputs: + node: + description: 'Current Node version' + required: true +runs: + using: "composite" + steps: + # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows + - name: Update Windows npm + if: | + runner.os == 'Windows' && ( + startsWith(inputs.node, 'v10.') || + startsWith(inputs.node, 'v12.') || + startsWith(inputs.node, 'v14.') + ) + shell: cmd + run: | + curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz + tar xf npm-7.5.4.tgz + cd package + node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz + cd .. + rmdir /s /q package + - name: Install Latest npm + shell: bash + env: + NODE_VERSION: \${{ inputs.node }} + run: | + MATCH="" + SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") + + echo "node@$NODE_VERSION" + + for SPEC in \${SPECS[@]}; do + ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') + echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" + + if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then + MATCH=$SPEC + echo "Found compatible version: npm@$MATCH" + break + fi + done + + if [ -z $MATCH ]; then + echo "Could not find a compatible version of npm for node@$NODE_VERSION" + exit 1 + fi + + npm i --prefer-online --no-fund --no-audit -g npm@$MATCH + - name: npm Version + shell: bash + run: npm -v + .github/dependabot.yml ======================================== # This file is automatically added by @npmcli/template-oss. Do not edit. @@ -4128,37 +3499,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -4201,53 +3545,10 @@ jobs: with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -4298,37 +3599,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -4371,63 +3645,20 @@ jobs: with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') + - name: Install Latest npm + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} + - name: Install Dependencies + run: npm i --ignore-scripts --no-audit --no-fund + - name: Add Problem Matcher + run: echo "::add-matcher::.github/matchers/tap.json" + - name: Test + run: npm test --ignore-scripts -w b - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v - - name: Install Dependencies - run: npm i --ignore-scripts --no-audit --no-fund - - name: Add Problem Matcher - run: echo "::add-matcher::.github/matchers/tap.json" - - name: Test - run: npm test --ignore-scripts -w b - -.github/workflows/ci-release.yml -======================================== -# This file is automatically added by @npmcli/template-oss. Do not edit. +.github/workflows/ci-release.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. name: CI - Release @@ -4456,49 +3687,6 @@ jobs: run: shell: bash steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Lint All" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ inputs.check-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Lint All - sha: \${{ inputs.check-sha }} - output: \${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -4507,43 +3695,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: \${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Lint All" + token: \${{ secrets.GITHUB_TOKEN }} + sha: \${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Lint @@ -4552,11 +3721,11 @@ jobs: run: npm run postlint --ignore-scripts -ws -iwr --if-present - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} - check_id: \${{ steps.check.outputs.check_id }} + check_id: \${{ steps.create-check.outputs.check-id }} test-all: name: Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }} @@ -4581,49 +3750,6 @@ jobs: run: shell: \${{ matrix.platform.shell }} steps: - - name: Get Workflow Job - uses: actions/github-script@v6 - if: inputs.check-sha - id: check-output - env: - JOB_NAME: "Test All" - MATRIX_NAME: " - \${{ matrix.platform.name }} - \${{ matrix.node-version }}" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ inputs.check-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: inputs.check-sha - with: - token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }} - sha: \${{ inputs.check-sha }} - output: \${{ steps.check-output.outputs.result }} - name: Checkout uses: actions/checkout@v3 with: @@ -4632,59 +3758,24 @@ jobs: run: | git config --global user.email "npm-cli+bot@github.com" git config --global user.name "npm CLI robot" + - name: Create Check + id: create-check + if: \${{ inputs.check-sha }} + uses: ./.github/actions/create-check + with: + name: "Test All - \${{ matrix.platform.name }} - \${{ matrix.node-version }}" + token: \${{ secrets.GITHUB_TOKEN }} + sha: \${{ inputs.check-sha }} - name: Setup Node uses: actions/setup-node@v3 id: node with: node-version: \${{ matrix.node-version }} check-latest: contains(matrix.node-version, '.x') - - # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows - - name: Update Windows npm - if: | - matrix.platform.os == 'windows-latest' && ( - startsWith(steps.node.outputs.node-version, 'v10.') || - startsWith(steps.node.outputs.node-version, 'v12.') || - startsWith(steps.node.outputs.node-version, 'v14.') - ) - run: | - curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz - tar xf npm-7.5.4.tgz - cd package - node lib/npm.js install --no-fund --no-audit -g ../npm-7.5.4.tgz - cd .. - rmdir /s /q package - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Add Problem Matcher @@ -4693,11 +3784,11 @@ jobs: run: npm test --ignore-scripts -ws -iwr --if-present - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: steps.check.outputs.check_id && always() + if: always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} - check_id: \${{ steps.check.outputs.check_id }} + check_id: \${{ steps.create-check.outputs.check-id }} .github/workflows/post-dependabot.yml ======================================== @@ -4733,37 +3824,10 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Fetch Dependabot Metadata @@ -4889,63 +3953,100 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Run Commitlint on Commits id: commit continue-on-error: true - run: | - npx --offline commitlint -V --from 'origin/\${{ github.base_ref }}' --to \${{ github.event.pull_request.head.sha }} + run: npx --offline commitlint -V --from 'origin/\${{ github.base_ref }}' --to \${{ github.event.pull_request.head.sha }} - name: Run Commitlint on PR Title if: steps.commit.outcome == 'failure' env: PR_TITLE: \${{ github.event.pull_request.title }} - run: | - echo "$PR_TITLE" | npx --offline commitlint -V + run: echo "$PR_TITLE" | npx --offline commitlint -V -.github/workflows/release.yml +.github/workflows/release-integration.yml ======================================== # This file is automatically added by @npmcli/template-oss. Do not edit. -name: Release +name: Release Integration on: workflow_dispatch: inputs: - release-pr: - description: a release PR number to rerun release jobs on + releases: + required: true type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + workflow_call: + inputs: + releases: + required: true + type: string + description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' + +jobs: + publish: + name: Check Publish + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Git User + run: | + git config --global user.email "npm-cli+bot@github.com" + git config --global user.name "npm CLI robot" + - name: Setup Node + uses: actions/setup-node@v3 + id: node + with: + node-version: 20.x + check-latest: contains('20.x', '.x') + - name: Install Latest npm + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} + - name: Install Dependencies + run: npm i --ignore-scripts --no-audit --no-fund + - name: Check If Published + run: | + EXIT_CODE=0 + + function each_release { + if npm view "$@" --loglevel=error > /dev/null; then + echo 0 + else + echo 1 + fi + } + + for release in $(echo '\${{ inputs.releases }}' | jq -r '.[] | @base64'); do + SPEC="$(echo "$release" | base64 --decode | jq -r .pkgName)@$(echo "$release" | base64 --decode | jq -r .version)" + STATUS=$(each_release "$SPEC") + if [[ "$STATUS" -eq 1 ]]; then + EXIT_CODE=$STATUS + echo "$SPEC ERROR" + else + echo "$SPEC OK" + fi + done + + exit $EXIT_CODE + +.github/workflows/release.yml +======================================== +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Release + +on: push: branches: - main @@ -4961,12 +4062,12 @@ jobs: release: outputs: pr: \${{ steps.release.outputs.pr }} - release: \${{ steps.release.outputs.release }} - releases: \${{ steps.release.outputs.releases }} - branch: \${{ steps.release.outputs.pr-branch }} + pr-branch: \${{ steps.release.outputs.pr-branch }} pr-number: \${{ steps.release.outputs.pr-number }} - comment-id: \${{ steps.pr-comment.outputs.result }} - check-id: \${{ steps.check.outputs.check_id }} + pr-sha: \${{ steps.release.outputs.pr-sha }} + releases: \${{ steps.release.outputs.releases }} + comment-id: \${{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }} + check-id: \${{ steps.create-check.outputs.check-id }} name: Release if: github.repository_owner == 'npm' runs-on: ubuntu-latest @@ -4986,126 +4087,64 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund - name: Release Please id: release env: GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} - run: | - npx --offline template-oss-release-please "\${{ github.ref_name }}" "\${{ inputs.release-pr }}" - - name: Post Pull Request Comment + run: npx --offline template-oss-release-please --branch="\${{ github.ref_name }}" --backport="" --defaultTag="latest" + - name: Create Release Manager Comment Text if: steps.release.outputs.pr-number uses: actions/github-script@v6 - id: pr-comment - env: - PR_NUMBER: \${{ steps.release.outputs.pr-number }} - REF_NAME: \${{ github.ref_name }} + id: comment-text with: + result-encoding: string script: | - const { REF_NAME, PR_NUMBER: issue_number } = process.env const { runId, repo: { owner, repo } } = context - const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId }) - - let body = '## Release Manager/n/n' - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - let commentId = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id - - body += \`Release workflow run: \${workflow.html_url}/n/n#### Force CI to Update This Release/n/n\` - body += \`This PR will be updated and CI will run for every non-/\`chore:/\` commit that is pushed to /\`\${REF_NAME}/\`. \` - body += \`To force CI to update this PR, run this command:/n/n\` - body += \`/\`/\`/\`/ngh workflow run release.yml -r \${REF_NAME} -R \${owner}/\${repo} -f release-pr=\${issue_number}/n/\`/\`/\`\` - - if (commentId) { - await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, body }) - } else { - const { data: comment } = await github.rest.issues.createComment({ owner, repo, issue_number, body }) - commentId = comment?.id - } - - return commentId - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.release.outputs.pr-sha - id: check-output - env: - JOB_NAME: "Release" - MATRIX_NAME: "" + return['## Release Manager', \`Release workflow run: \${workflow.html_url}\`].join('/n/n') + - name: Find Release Manager Comment + uses: peter-evans/find-comment@v2 + if: steps.release.outputs.pr-number + id: found-comment with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ steps.release.outputs.pr-sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } + issue-number: \${{ steps.release.outputs.pr-number }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Manager' + - name: Create Release Manager Comment + id: create-comment + if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: \${{ steps.release.outputs.pr-number }} + body: \${{ steps.comment-text.outputs.result }} + - name: Update Release Manager Comment + id: update-comment + if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ steps.found-comment.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check + id: create-check + uses: ./.github/actions/create-check if: steps.release.outputs.pr-sha with: + name: "Release" token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: \${{ steps.release.outputs.pr-sha }} - output: \${{ steps.check-output.outputs.result }} update: needs: release outputs: sha: \${{ steps.commit.outputs.sha }} - check-id: \${{ steps.check.outputs.check_id }} + check-id: \${{ steps.create-check.outputs.check-id }} name: Update - Release if: github.repository_owner == 'npm' && needs.release.outputs.pr runs-on: ubuntu-latest @@ -5117,7 +4156,7 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - ref: \${{ needs.release.outputs.branch }} + ref: \${{ needs.release.outputs.pr-branch }} - name: Setup Git User run: | git config --global user.email "npm-cli+bot@github.com" @@ -5128,47 +4167,27 @@ jobs: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund + - name: Create Release Manager Checklist Text + id: comment-text + env: + GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} + run: npm exec --offline -- template-oss-release-manager --pr="\${{ needs.release.outputs.pr-number }}" --backport="" --defaultTag="latest" + - name: Append Release Manager Comment + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ needs.release.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'append' - name: Run Post Pull Request Actions env: - RELEASE_PR_NUMBER: \${{ needs.release.outputs.pr-number }} - RELEASE_COMMENT_ID: \${{ needs.release.outputs.comment-id }} GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} - run: | - npm exec --offline -- template-oss-release-manager --lockfile=false --publish=false - npm run rp-pull-request --ignore-scripts -ws -iwr --if-present + run: npm run rp-pull-request --ignore-scripts -ws -iwr --if-present -- --pr="\${{ needs.release.outputs.pr-number }}" --commentId="\${{ needs.release.outputs.comment-id }}" - name: Commit id: commit env: @@ -5177,52 +4196,16 @@ jobs: git commit --all --amend --no-edit || true git push --force-with-lease echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - name: Get Workflow Job - uses: actions/github-script@v6 - if: steps.commit.outputs.sha - id: check-output - env: - JOB_NAME: "Update - Release" - MATRIX_NAME: "" - with: - script: | - const { owner, repo } = context.repo - - const { data } = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: context.runId, - per_page: 100 - }) - - const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME - const job = data.jobs.find(j => j.name.endsWith(jobName)) - const jobUrl = job?.html_url - - const shaUrl = \`\${context.serverUrl}/\${owner}/\${repo}/commit/\${{ steps.commit.outputs.sha }}\` - - let summary = \`This check is assosciated with \${shaUrl}/n/n\` - - if (jobUrl) { - summary += \`For run logs, click here: \${jobUrl}\` - } else { - summary += \`Run logs could not be found for a job with name: "\${jobName}"\` - } - - return { summary } - name: Create Check - uses: LouisBrunner/checks-action@v1.6.0 - id: check - if: steps.commit.outputs.sha + id: create-check + uses: ./.github/actions/create-check with: + name: "Update - Release" + check-name: "Release" token: \${{ secrets.GITHUB_TOKEN }} - status: in_progress - name: Release sha: \${{ steps.commit.outputs.sha }} - output: \${{ steps.check-output.outputs.result }} - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.release.outputs.check-id && always() with: token: \${{ secrets.GITHUB_TOKEN }} conclusion: \${{ job.status }} @@ -5234,7 +4217,7 @@ jobs: if: needs.release.outputs.pr uses: ./.github/workflows/ci-release.yml with: - ref: \${{ needs.release.outputs.branch }} + ref: \${{ needs.release.outputs.pr-branch }} check-sha: \${{ needs.update.outputs.sha }} post-ci: @@ -5246,8 +4229,8 @@ jobs: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get CI Conclusion + id: conclusion run: | result="" if [[ "\${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then @@ -5260,14 +4243,15 @@ jobs: echo "result=$result" >> $GITHUB_OUTPUT - name: Conclude Check uses: LouisBrunner/checks-action@v1.6.0 - if: needs.update.outputs.check-id && always() with: token: \${{ secrets.GITHUB_TOKEN }} - conclusion: \${{ steps.needs-result.outputs.result }} + conclusion: \${{ steps.conclusion.outputs.result }} check_id: \${{ needs.update.outputs.check-id }} post-release: needs: release + outputs: + comment-id: \${{ steps.create-comment.outputs.comment-id }} name: Post Release - Release if: github.repository_owner == 'npm' && needs.release.outputs.releases runs-on: ubuntu-latest @@ -5275,123 +4259,50 @@ jobs: run: shell: bash steps: - - name: Create Release PR Comment + - name: Create Release PR Comment Text + id: comment-text uses: actions/github-script@v6 env: RELEASES: \${{ needs.release.outputs.releases }} with: + result-encoding: string script: | const releases = JSON.parse(process.env.RELEASES) const { runId, repo: { owner, repo } } = context const issue_number = releases[0].prNumber - - let body = '## Release Workflow/n/n' - for (const { pkgName, version, url } of releases) { - body += \`- /\`\${pkgName}@\${version}/\` \${url}/n\` - } - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - .then(cs => cs.map(c => ({ id: c.id, login: c.user.login, body: c.body }))) - console.log(\`Found comments: \${JSON.stringify(comments, null, 2)}\`) - const releaseComments = comments.filter(c => c.login === 'github-actions[bot]' && c.body.includes('Release is at')) - - for (const comment of releaseComments) { - console.log(\`Release comment: \${JSON.stringify(comment, null, 2)}\`) - await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id }) - } - const runUrl = \`https://github.com/\${owner}/\${repo}/actions/runs/\${runId}\` - await github.rest.issues.createComment({ - owner, - repo, - issue_number, - body: \`\${body}- Workflow run: :arrows_counterclockwise: \${runUrl}\`, - }) + + return [ + '## Release Workflow/n', + ...releases.map(r => \`- /\`\${r.pkgName}@\${r.version}/\` \${r.url}\`), + \`- Workflow run: :arrows_counterclockwise: \${runUrl}\`, + ].join('/n') + - name: Create Release PR Comment + id: create-comment + uses: peter-evans/create-or-update-comment@v3 + with: + issue-number: \${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + body: \${{ steps.comment-text.outputs.result }} release-integration: needs: release name: Release Integration - if: needs.release.outputs.release - runs-on: ubuntu-latest - defaults: - run: - shell: bash - steps: - - name: Setup Node - uses: actions/setup-node@v3 - id: node - with: - node-version: 20.x - check-latest: contains('20.x', '.x') - - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v - - name: View in Registry - run: | - EXIT_CODE=0 - - function is_published { - if npm view "$@" --loglevel=error > /dev/null; then - echo 0 - else - echo 1 - fi - } - - for release in $(echo '\${{ needs.release.outputs.releases }}' | jq -r '.[] | @base64'); do - name=$(echo "$release" | base64 --decode | jq -r .pkgName) - version=$(echo "$release" | base64 --decode | jq -r .version) - spec="$name@$version" - status=$(is_published "$spec") - if [[ "$status" -eq 1 ]]; then - echo "$spec ERROR" - EXIT_CODE=$status - else - echo "$spec OK" - fi - done - - exit $EXIT_CODE + if: needs.release.outputs.releases + uses: ./.github/workflows/release-integration.yml + with: + releases: \${{ needs.release.outputs.releases }} post-release-integration: - needs: [ release, release-integration ] + needs: [ release, release-integration, post-release ] name: Post Release Integration - Release - if: github.repository_owner == 'npm' && needs.release.outputs.release && always() + if: github.repository_owner == 'npm' && needs.release.outputs.releases && always() runs-on: ubuntu-latest defaults: run: shell: bash steps: - - name: Get Needs Result - id: needs-result + - name: Get Post Release Conclusion + id: conclusion run: | if [[ "\${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then result="x" @@ -5401,42 +4312,41 @@ jobs: result="white_check_mark" fi echo "result=$result" >> $GITHUB_OUTPUT - - name: Update Release PR Comment + - name: Find Release PR Comment + uses: peter-evans/find-comment@v2 + id: found-comment + with: + issue-number: \${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} + comment-author: 'github-actions[bot]' + body-includes: '## Release Workflow' + - name: Create Release PR Comment Text + id: comment-text + if: steps.found-comment.outputs.comment-id uses: actions/github-script@v6 env: - PR_NUMBER: \${{ fromJSON(needs.release.outputs.release).prNumber }} - RESULT: \${{ steps.needs-result.outputs.result }} + RESULT: \${{ steps.conclusion.outputs.result }} + BODY: \${{ steps.found-comment.outputs.comment-body }} with: + result-encoding: string script: | - const { PR_NUMBER: issue_number, RESULT } = process.env - const { runId, repo: { owner, repo } } = context - - const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }) - const updateComment = comments.find(c => - c.user.login === 'github-actions[bot]' && - c.body.startsWith('## Release Workflow/n/n') && - c.body.includes(runId) - ) - - if (updateComment) { - console.log('Found comment to update:', JSON.stringify(updateComment, null, 2)) - let body = updateComment.body.replace(/Workflow run: :[a-z_]+:/, \`Workflow run: :\${RESULT}:\`) - const tagCodeowner = RESULT !== 'white_check_mark' - if (tagCodeowner) { - body += \`/n/n:rotating_light:\` - body += \` @npm/cli-team: The post-release workflow failed for this release.\` - body += \` Manual steps may need to be taken after examining the workflow output\` - body += \` from the above workflow run. :rotating_light:\` - } - await github.rest.issues.updateComment({ - owner, - repo, - body, - comment_id: updateComment.id, - }) - } else { - console.log('No matching comments found:', JSON.stringify(comments, null, 2)) + const { RESULT, BODY } = process.env + const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, \`$1\${RESULT}$2\`)] + if (RESULT !== 'white_check_mark') { + body.push(':rotating_light::rotating_light::rotating_light:') + body.push([ + '@npm/cli-team: The post-release workflow failed for this release.', + 'Manual steps may need to be taken after examining the workflow output.' + ].join(' ')) + body.push(':rotating_light::rotating_light::rotating_light:') } + return body.join('/n/n').trim() + - name: Update Release PR Comment + if: steps.comment-text.outputs.result + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: \${{ steps.found-comment.outputs.comment-id }} + body: \${{ steps.comment-text.outputs.result }} + edit-mode: 'replace' .release-please-manifest.json ======================================== @@ -5493,7 +4403,8 @@ release-please-config.json }, { "type": "chore", - "hidden": true + "section": "Chores", + "hidden": false } ], "packages": { diff --git a/tap-snapshots/test/check/diff-snapshots.js.test.cjs b/tap-snapshots/test/check/diff-snapshots.js.test.cjs index c37c69c5..9bd10de1 100644 --- a/tap-snapshots/test/check/diff-snapshots.js.test.cjs +++ b/tap-snapshots/test/check/diff-snapshots.js.test.cjs @@ -99,23 +99,23 @@ The repo file audit.yml needs to be updated: [@npmcli/template-oss ERROR] There was an erroring getting the target file [@npmcli/template-oss ERROR] Error: {{ROOT}}/test/check/tap-testdir-diff-snapshots-update-and-remove-errors/.github/workflows/audit.yml - YAMLParseError: Implicit keys need to be on a single line at line 69, column 1: + YAMLParseError: Implicit keys need to be on a single line at line 42, column 1: run: npm audit --audit-level=none >>>>I HOPE THIS IS NOT VALID YAML<<<<<<<<<<< ^ - YAMLParseError: Block scalar header includes extra characters: >>>>I at line 69, column 2: + YAMLParseError: Block scalar header includes extra characters: >>>>I at line 42, column 2: >>>>I HOPE THIS IS NOT VALID YAML<<<<<<<<<<< ^ - YAMLParseError: Not a YAML token: HOPE THIS IS NOT VALID YAML<<<<<<<<<<< at line 69, column 7: + YAMLParseError: Not a YAML token: HOPE THIS IS NOT VALID YAML<<<<<<<<<<< at line 42, column 7: >>>>I HOPE THIS IS NOT VALID YAML<<<<<<<<<<< ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - YAMLParseError: Implicit map keys need to be followed by map values at line 69, column 1: + YAMLParseError: Implicit map keys need to be followed by map values at line 42, column 1: run: npm audit --audit-level=none >>>>I HOPE THIS IS NOT VALID YAML<<<<<<<<<<< @@ -155,37 +155,10 @@ The repo file audit.yml needs to be updated: with: node-version: 20.x check-latest: contains('20.x', '.x') - - name: Install Latest npm - shell: bash - env: - NODE_VERSION: \${{ steps.node.outputs.node-version }} - run: | - MATCH="" - SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") - - echo "node@$NODE_VERSION" - - for SPEC in \${SPECS[@]}; do - ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - echo "Found compatible version: npm@$MATCH" - break - fi - done - - if [ -z $MATCH ]; then - echo "Could not find a compatible version of npm for node@$NODE_VERSION" - exit 1 - fi - - npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - - - name: npm Version - run: npm -v + uses: ./.github/actions/install-latest-npm + with: + node: \${{ steps.node.outputs.node-version }} - name: Install Dependencies run: npm i --ignore-scripts --no-audit --no-fund --package-lock - name: Run Production Audit @@ -202,25 +175,25 @@ The repo file ci.yml needs to be updated: .github/workflows/ci.yml ======================================== - @@ -146,4 +146,24 @@ - echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" - - if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then - MATCH=$SPEC - + echo "Found compatible version: npm@$MATCH" - + break - + fi - + done - + - + if [ -z $MATCH ]; then - + echo "Could not find a compatible version of npm for node@$NODE_VERSION" - + exit 1 - + fi - + - + npm i --prefer-online --no-fund --no-audit -g npm@$MATCH - + - + - name: npm Version - + run: npm -v + @@ -76,4 +76,24 @@ + shell: \${{ matrix.platform.shell }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Git User + + run: | + + git config --global user.email "npm-cli+bot@github.com" + + git config --global user.name "npm CLI robot" + + - name: Setup Node + + uses: actions/setup-node@v3 + + id: node + + with: + + node-version: \${{ matrix.node-version }} + + check-latest: contains(matrix.node-version, '.x') + + - name: Install Latest npm + + uses: ./.github/actions/install-latest-npm + + with: + + node: \${{ steps.node.outputs.node-version }} + - name: Install Dependencies + run: npm i --ignore-scripts --no-audit --no-fund + - name: Add Problem Matcher diff --git a/tap-snapshots/test/check/snapshots.js.test.cjs b/tap-snapshots/test/check/snapshots.js.test.cjs index 245c3bcd..a59000a1 100644 --- a/tap-snapshots/test/check/snapshots.js.test.cjs +++ b/tap-snapshots/test/check/snapshots.js.test.cjs @@ -30,6 +30,8 @@ Some problems were detected: The following repo files need to be added: .commitlintrc.js + .github/actions/create-check/action.yml + .github/actions/install-latest-npm/action.yml .github/CODEOWNERS .github/dependabot.yml .github/ISSUE_TEMPLATE/bug.yml @@ -42,6 +44,7 @@ The following repo files need to be added: .github/workflows/codeql-analysis.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml + .github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json release-please-config.json @@ -284,6 +287,8 @@ Some problems were detected: The following repo files need to be added: .commitlintrc.js + .github/actions/create-check/action.yml + .github/actions/install-latest-npm/action.yml .github/CODEOWNERS .github/dependabot.yml .github/ISSUE_TEMPLATE/bug.yml @@ -296,6 +301,7 @@ The following repo files need to be added: .github/workflows/codeql-analysis.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml + .github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json release-please-config.json @@ -369,6 +375,8 @@ To correct it: npm rm @npmcli/template-oss @npmcli/eslint-config tap && npm i @n The following repo files need to be added: + .github/actions/create-check/action.yml + .github/actions/install-latest-npm/action.yml .github/dependabot.yml .github/matchers/tap.json .github/settings.yml @@ -376,6 +384,7 @@ The following repo files need to be added: .github/workflows/ci-release.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml + .github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json release-please-config.json @@ -438,6 +447,8 @@ To correct it: npm rm @npmcli/template-oss @npmcli/eslint-config tap && npm i @n The following repo files need to be added: + .github/actions/create-check/action.yml + .github/actions/install-latest-npm/action.yml .github/dependabot.yml .github/matchers/tap.json .github/settings.yml @@ -445,6 +456,7 @@ The following repo files need to be added: .github/workflows/ci-release.yml .github/workflows/post-dependabot.yml .github/workflows/pull-request.yml + .github/workflows/release-integration.yml .github/workflows/release.yml .release-please-manifest.json release-please-config.json diff --git a/test/apply/release.js b/test/apply/release.js index 622fb4bd..dcc5762a 100644 --- a/test/apply/release.js +++ b/test/apply/release.js @@ -18,7 +18,8 @@ t.test('no workspace flags in commands', async (t) => { t.notMatch(ciRelease, '--ignore-scripts -ws -iwr --if-present\n') const release = await s.readFile(join('.github', 'workflows', 'release.yml')) - t.match(release, 'npm publish --provenance --tag=latest\n') + t.match(release, '--publish') + t.match(release, '--backport=""') }) t.test('uses workspace flags in commands', async (t) => { @@ -51,5 +52,6 @@ t.test('backport', async (t) => { t.match(ciRelease, 'default: release/v8\n') const release = await s.readFile(join('.github', 'workflows', 'release.yml')) - t.match(release, 'npm publish --provenance --tag=next-8\n') + t.match(release, '--publish') + t.match(release, '--backport="8"') }) diff --git a/test/apply/typescript.js b/test/apply/typescript.js index 63aae412..7bf5f458 100644 --- a/test/apply/typescript.js +++ b/test/apply/typescript.js @@ -55,3 +55,25 @@ t.test('no default content', async (t) => { t.strictSame(checks[0].body, ['typescript', 'tshy', '@typescript-eslint/parser']) }) + +t.test('with tap 16', async (t) => { + const s = await setup(t, { + ok: true, + package: { + devDependencies: { + tap: '^16', + }, + templateOSS: { + typescript: true, + }, + }, + }) + await s.apply() + const checks = await s.check() + const pkg = await s.readJson('package.json') + + t.equal(pkg.scripts.test, 'c8 tap') + t.equal(pkg.scripts.snap, 'c8 tap') + t.strictSame(pkg.tap['node-arg'], ['--no-warnings', '--loader', 'ts-node/esm']) + t.strictSame(checks[0].body, ['typescript', 'tshy', '@typescript-eslint/parser', 'c8', 'ts-node']) +}) diff --git a/test/release-please/changelog.js b/test/release-please/changelog.js index dee6f118..10a3160a 100644 --- a/test/release-please/changelog.js +++ b/test/release-please/changelog.js @@ -1,5 +1,40 @@ const t = require('tap') -const ChangelogNotes = require('../../lib/release-please/changelog.js') +const ChangelogNotes = require('../../lib/release/changelog.js') + +const mockGitHub = ({ commits, authors }) => ({ + repository: { owner: 'npm', repo: 'cli' }, + graphql: () => ({ + repository: commits.reduce((acc, c, i) => { + if (c.sha) { + if (c.sha === 'd' || c.type === 'deps') { + // simulate a bad sha passed in that doesnt return a commit + acc[`_${c.sha}`] = null + } else { + const author = i % 2 + ? { user: { login: 'username' } } + : { name: 'Name' } + acc[`_${c.sha}`] = { authors: { nodes: authors ? [author] : [] } } + } + } + return acc + }, {}), + }), + octokit: { + rest: { + repos: { + listPullRequestsAssociatedWithCommit: async (commit) => { + if (commit.commit_sha === 'd') { + return { + data: [{ + number: 50, + }], + } + } + }, + }, + }, + }, +}) const mockChangelog = async ({ shas = true, authors = true, previousTag = true } = {}) => { const commits = [{ @@ -28,42 +63,8 @@ const mockChangelog = async ({ shas = true, authors = true, previousTag = true } notes: [], }].map(({ sha, ...rest }) => shas ? { sha, ...rest } : rest) - const github = { - repository: { owner: 'npm', repo: 'cli' }, - graphql: () => ({ - repository: commits.reduce((acc, c, i) => { - if (c.sha) { - if (c.sha === 'd') { - // simulate a bad sha passed in that doesnt return a commit - acc[`_${c.sha}`] = null - } else { - const author = i % 2 - ? { user: { login: 'username' } } - : { name: 'Name' } - acc[`_${c.sha}`] = { authors: { nodes: authors ? [author] : [] } } - } - } - return acc - }, {}), - }), - octokit: { - rest: { - repos: { - listPullRequestsAssociatedWithCommit: async (commit) => { - if (commit.commit_sha === 'd') { - return { - data: [{ - number: 50, - }], - } - } - }, - }, - }, - }, - } - - const changelog = new ChangelogNotes({ github }) + const github = mockGitHub({ commits, authors }) + const changelog = new ChangelogNotes(github) const notes = await changelog.buildNotes(commits, { version: '1.0.0', diff --git a/test/release-please/node-workspace.js b/test/release-please/node-workspace.js index 09e523eb..63311fbf 100644 --- a/test/release-please/node-workspace.js +++ b/test/release-please/node-workspace.js @@ -1,12 +1,46 @@ const t = require('tap') -const { setLogger } = require('release-please') // this avoids a release-please cycle when testing +// silence all logs during tests from release-please. this needs to come before +// the rest of the release-please requires to avoid a require cycle. +require('release-please').setLogger({ + error () {}, + warn () {}, + info () {}, + debug () {}, + trace () {}, +}) const { Node } = require('release-please/build/src/strategies/node') const { Version } = require('release-please/build/src/version') const { TagName } = require('release-please/build/src/util/tag-name') -const NodeWorkspace = require('../../lib/release-please/node-workspace') -const Changelog = require('../../lib/release-please/changelog') - -setLogger({ error () {}, warn () {}, info () {}, debug () {}, trace () {} }) +const NodeWorkspace = require('../../lib/release/node-workspace') +const Changelog = require('../../lib/release/changelog') + +const mockGitHub = ({ names, versions, paths }) => ({ + repository: { owner: 'npm', repo: 'cli' }, + graphql: () => ({ + repository: {}, + }), + octokit: { + rest: { + repos: { + listPullRequestsAssociatedWithCommit: async () => {}, + }, + }, + }, + getFileContentsOnBranch: (file) => { + const path = file.replace(/\/?package.json$/, '') || '.' + const dependencies = path === '.' ? paths.filter(p => p !== '.').reduce((acc, ws) => { + acc[names[ws]] = `^${versions[ws]}` + return acc + }, {}) : {} + return { + parsedContent: JSON.stringify({ + name: names[path], + version: versions[path].toString(), + dependencies, + }), + } + }, +}) const mockNodeWorkspace = async (workspaceNames = ['a']) => { const names = { '.': 'npm' } @@ -18,24 +52,7 @@ const mockNodeWorkspace = async (workspaceNames = ['a']) => { } const paths = Object.keys(names) - - const github = { - repository: { owner: 'npm', repo: 'cli' }, - getFileContentsOnBranch: (file) => { - const path = file.replace(/\/?package.json$/, '') || '.' - const dependencies = path === '.' ? paths.filter(p => p !== '.').reduce((acc, ws) => { - acc[names[ws]] = `^${versions[ws]}` - return acc - }, {}) : {} - return { - parsedContent: JSON.stringify({ - name: names[path], - version: versions[path].toString(), - dependencies, - }), - } - }, - } + const github = mockGitHub({ names, versions, paths }) const workspaces = (fn) => paths.reduce((acc, p) => { acc[p] = fn(p) @@ -61,7 +78,7 @@ const mockPullRequests = async (workspace, updates = workspace.paths) => { { type: 'deps', section: 'Dependencies' }, { type: 'fix', section: 'Fixes' }, ], - changelogNotes: new Changelog({ github }), + changelogNotes: new Changelog(github), })) const commitsByPath = workspaces((path) => updates.includes(path) ? [{ diff --git a/test/release-please/version.js b/test/release-please/version.js index 063a8514..0fcbb7d4 100644 --- a/test/release-please/version.js +++ b/test/release-please/version.js @@ -1,5 +1,5 @@ const t = require('tap') -const Version = require('../../lib/release-please/version.js') +const Version = require('../../lib/release/version.js') const COMMITS = { major: [{ type: 'feat' }, {}, {}, { breaking: true }], @@ -52,7 +52,7 @@ t.test('bumps', async (t) => { for (const [version, commits, prerelease, expected] of checks) { const name = [version, COMMIT_NAME(commits), prerelease ? 'pre' : 'normal', expected] - const r = new Version({ prerelease }).bump(version, commits) + const r = new Version(null, { prerelease }).bump(version, commits) t.equal( `${r.major}.${r.minor}.${r.patch}${r.preRelease ? `-${r.preRelease}` : ''}`, expected,