diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ee1c313..44421ade 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,51 +1,55 @@ +name: Release + on: push: branches: [main] paths: [package.json] workflow_dispatch: -concurrency: release + inputs: + action: + description: Operation + type: choice + options: [verify, stage, finalize] + default: verify + required: true + version: + description: "Required for finalize only, e.g. 1.2.3" + type: string + default: "" + required: false + +permissions: + contents: read + +concurrency: + group: release + cancel-in-progress: false + jobs: - Release: - runs-on: ubuntu-latest - environment: deployment + Verify: + if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'verify' permissions: - contents: write # create the tag and the GitHub Release - id-token: write # npm Trusted Publishing - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - id: version - run: | - tag="v$(node -p 'require("./package.json").version')" - echo "tag=$tag" >> "$GITHUB_OUTPUT" - if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then - echo "released=true" >> "$GITHUB_OUTPUT" - fi - - if: steps.version.outputs.released != 'true' - uses: actions/setup-node@v7 - with: - node-version: 24.x - package-manager-cache: false - registry-url: https://registry.npmjs.org - - if: steps.version.outputs.released != 'true' - run: npm install --global npm@11.21.0 - - if: steps.version.outputs.released != 'true' - run: npm ci - - if: steps.version.outputs.released != 'true' - run: npm run build - - if: steps.version.outputs.released != 'true' - run: npx @nlib/cleanup-package-json --file package.json - # Trusted Publishing (OIDC); a maintainer approves the staged version on npmjs.com. - - if: steps.version.outputs.released != 'true' - run: | - name=$(node -p 'require("./package.json").name') - version=$(node -p 'require("./package.json").version') - npm view "$name@$version" version >/dev/null 2>&1 || npm stage publish - - if: steps.version.outputs.released != 'true' - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.version.outputs.tag }} - run: | - awk -v h="## $TAG " 'index($0,h)==1{f=1;next} /^## v/{if(f)exit} f' CHANGELOG.md > notes.md - gh release create "$TAG" --target "$GITHUB_SHA" --title "$TAG" --notes-file notes.md + contents: read + uses: nlibjs/actions/.github/workflows/npm-release.yml@986b87befafad2f4f990c934165a90ea37ddbf36 + with: + action: verify + + Stage: + if: github.event_name == 'push' || github.event.inputs.action == 'stage' + permissions: + contents: write + id-token: write + uses: nlibjs/actions/.github/workflows/npm-release.yml@986b87befafad2f4f990c934165a90ea37ddbf36 + with: + action: stage + environment: deployment + + Finalize: + if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'finalize' + permissions: + contents: write + uses: nlibjs/actions/.github/workflows/npm-release.yml@986b87befafad2f4f990c934165a90ea37ddbf36 + with: + action: finalize + environment: deployment + version: ${{ github.event.inputs.version }} diff --git a/README.md b/README.md index 0d02785f..3c676e52 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,7 @@ Then, set `@nlib/lint-commit` to `commit-msg` hook. #!/bin/sh npx @nlib/lint-commit --input "$1" ``` + +## Releases + +Maintainers can follow the [release guide](docs/releases.md). diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 00000000..51b4ba74 --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,34 @@ +# Maintainer release guide + +The Release workflow pins `nlibjs/actions` to `986b87befafad2f4f990c934165a90ea37ddbf36`, with helper `70dd80ae972e812efcc9606d0ec5d514c8be3982`. + +## Configuration + +Configure npm Trusted Publisher for `@nlib/lint-commit` with these values: + +| Setting | Value | +| --- | --- | +| Organization or user | `nlibjs` | +| Repository | `lint-commit` | +| Workflow filename | `release.yml` | +| Environment | `deployment` | + +Stage and Finalize use the repository's `deployment` environment. Each operation requires its environment approval if protection rules are configured. Verify runs without that environment. + +## Operations + +1. For a nonpublishing check, select **Release → Run workflow**, choose a branch, select `action=verify`, and leave version empty. Verify runs lint, tests, build, cleanup and tarball checks. +2. Prepare a normal version and changelog update, then merge to main. A package.json push starts Stage automatically. Manual Stage on main accepts the current package version; leave version empty. +3. Check the Stage run and its saved draft for the intended package/version. +4. Approve the exact version on npmjs.com's **Staged Packages** page with maintainer 2FA. +5. Run Release on `main` with `action=finalize` and the approved version without `v`. + +Manual dispatch defaults to Verify. Stage and Finalize require main. This package publishes to npm and GitHub Releases. + +## Recovery + +Follow the shared workflow's [recovery procedure](https://github.com/nlibjs/actions/blob/986b87befafad2f4f990c934165a90ea37ddbf36/README.md#existing-versions-and-failures) for failed or partial releases. + +## Updating the workflow + +Update all three full SHA references in `.github/workflows/release.yml` together. Confirm that the workflow and its helper pin are preserved in shared main history, Stage and Finalize still use `deployment`, and the existing Lint and Test checks pass before merging.