diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..e3895d7 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,22 @@ +name: Release Please +on: + push: + branches: + - main + workflow_dispatch: +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false +jobs: + release_please: + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + steps: + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5 + with: + token: ${{ secrets.GH_RELEASE_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5a650c5..8bc122d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,22 +1,13 @@ -# Cut a release whenever a new tag is pushed to the repo. +# Build and publish whenever Release Please creates a version tag. name: Release on: - # Can be triggered from the tag.yaml workflow - workflow_call: - inputs: - tag_name: - required: true - type: string - secrets: - publish_token: - required: true - # Can be triggered from the tag.yaml workflow + # Recover an existing tag whose release did not finish. workflow_dispatch: inputs: tag_name: required: true type: string - # Or, developers can manually push a tag from their clone + # Release Please creates version tags after its release PR lands. push: tags: - "v*.*.*" @@ -41,4 +32,4 @@ jobs: contents: write id-token: write secrets: - publish_token: ${{ secrets.publish_token || secrets.BCR_PUBLISH_TOKEN }} + publish_token: ${{ secrets.BCR_PUBLISH_TOKEN }} diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml deleted file mode 100644 index abb114c..0000000 --- a/.github/workflows/tag.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# Tag a new release using https://github.com/marketplace/actions/conventional-commits-versioner-action -# -# This is easier than having to run manual `git` operations on a local clone. -# It also runs on a schedule so we don't leave commits unreleased indefinitely -# (avoiding users having to ping "hey could someone cut a release"). - -name: Tag a Release -on: - # Allow devs to tag manually through the GitHub UI. - # For example after landing a fix that customers are waiting for. - workflow_dispatch: - schedule: - - cron: "0 15 * * *" # Daily at 3PM UTC -jobs: - tag: - permissions: - contents: write # allow create tag - runs-on: ubuntu-latest - outputs: - new-tag: ${{ steps.ccv.outputs.new-tag }} - new-tag-version: ${{ steps.ccv.outputs.new-tag-version }} - new-tag-version-type: ${{ steps.ccv.outputs.new-tag-version-type }} - recently-tagged: ${{ steps.recent-tag.outputs.recently-tagged }} - steps: - - uses: actions/checkout@v7 - with: - # Need enough history to find the prior release tag - fetch-depth: 0 - - name: Check if there is a recent tag - id: recent-tag - # Only skip on cron trigger, not manual trigger - if: github.event_name == 'schedule' - run: | - # This is a trade-off between making too many releases, - # which overwhelms BCR maintainers and over-notifies users, - # and releasing too infrequently which delays delivery of bugfixes and features. - MAX_AGE=1209600 # 2 weeks - TAG=$(git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]*' --abbrev=0 2>/dev/null) || { - echo "No matching tag — continue workflow." - exit 0 - } - TAG_TIME=$(git log -1 --format=%ct "$TAG") - NOW=$(date +%s) - AGE=$((NOW - TAG_TIME)) - echo "Latest tag: $TAG ($AGE seconds ago)" - if [ "$AGE" -lt "$MAX_AGE" ]; then - echo "recently-tagged=true" >> "$GITHUB_OUTPUT" - fi - - name: Bump tag if necessary - id: ccv - if: github.event_name != 'schedule' || steps.recent-tag.outputs.recently-tagged != 'true' - uses: smlx/ccv@v0.10.0 - release: - needs: tag - uses: ./.github/workflows/release.yaml - with: - tag_name: ${{ needs.tag.outputs.new-tag-version }} - secrets: - publish_token: ${{ secrets.BCR_PUBLISH_TOKEN }} - if: needs.tag.outputs.new-tag == 'true' && needs.tag.outputs.new-tag-version-type != 'major' - permissions: - contents: write - id-token: write - attestations: write diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..d34d0eb --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1 @@ +{ ".": "0.9.1" } diff --git a/AGENTS.md b/AGENTS.md index 13fd023..7e43706 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,18 +4,19 @@ Follow [docs/releasing.md](docs/releasing.md). -- Use `.github/workflows/tag.yaml` as the normal release entrypoint. Dispatch it - on `main` without inputs; it chooses the version from Conventional Commits. -- Before dispatching, confirm `main` CI and Verify Hooks are green, no tag or - release run is active, the latest tag/release is known, and - `BCR_PUBLISH_TOKEN` is a Classic PAT with `repo` and `workflow` scopes that - can push to `formatjs/bazel-central-registry` and open the upstream PR. -- Do not invent a version or push a tag for a normal patch or minor release. -- Use `release.yaml` only to recover an existing tag that was not released. Use - `publish.yaml` only to retry BCR publication for an existing release. -- A major version requires explicit confirmation. The tag workflow may create - the tag, but intentionally skips the release and BCR jobs for a major bump. +- Release Please opens or updates the version bump PR after commits land on + `main`. Merge that PR to publish automatically. +- Before merging, confirm `main` CI and Verify Hooks are green, no release run + is active, and the proposed version is expected. +- `GH_RELEASE_TOKEN` and `BCR_PUBLISH_TOKEN` must be Classic PATs with `repo` + and `workflow` scopes. The BCR token must be able to push to + `formatjs/bazel-central-registry` and open the upstream PR. +- Do not invent a version or push a tag. Merging a major version PR is explicit + confirmation of the major release. +- Release Please tags trigger `release.yaml` automatically. Dispatch it only to + recover an existing tag. Use `publish.yaml` only to retry BCR publication for + an existing release. - Monitor the workflow through the GitHub release and BCR handoff. Tag creation alone is not completion. - If the BCR push rejects the token, rotate the secret and retry `publish.yaml` - with the existing tag. Do not rerun the tag or release jobs. + with the existing tag. Do not rerun Release Please or create another tag. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0889292..1671fb8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,17 +44,10 @@ This means that any usage of `@rules_formatjs` on your system will point to this ## Releasing -The release workflow chooses the next version from Conventional Commits, creates -the tag and GitHub release, and opens the Bazel Central Registry PR. It also -runs on a schedule. +Release Please opens or updates a version bump PR from Conventional Commits. +Merging that PR creates the tag and GitHub release, then builds release assets, +creates attestations, and opens the Bazel Central Registry PR automatically. -To start a normal release without waiting for the schedule, dispatch the -no-input tag workflow on `main`: - -```shell -gh workflow run tag.yaml --repo formatjs/rules_formatjs --ref main -``` - -Do not push a tag manually for a normal patch or minor release. See -[docs/releasing.md](docs/releasing.md) for preflight checks, monitoring, major -releases, and recovery. +Do not choose or push a tag manually. See +[docs/releasing.md](docs/releasing.md) for setup, checks, monitoring, and +recovery. diff --git a/docs/releasing.md b/docs/releasing.md index 88b8326..6589aa8 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -1,115 +1,86 @@ # Releasing -The normal release flow starts from `main` and chooses the next version from -Conventional Commits. +Release Please keeps a version bump PR current from Conventional Commits. Merge +that PR to publish. ```mermaid flowchart LR - tag["Tag a Release"] --> version["Create the next semver tag"] - version --> release["Test, build, attest, and create the GitHub release"] + commit["Conventional commits land"] --> pr["Release Please updates version PR"] + pr -->|merge| tag["Create tag and GitHub release"] + tag --> release["Test, build, attest, and upload assets"] release --> bcr["Open the Bazel Central Registry PR"] ``` -## Normal release +## Setup -Before starting, confirm that CI and Verify Hooks are green on `main`, no tag or -release workflow is already running, and the latest release is the one you -expect. `BCR_PUBLISH_TOKEN` must belong to an account that can push to -`formatjs/bazel-central-registry`. It must be a Classic PAT with `repo` and -`workflow` scopes; fine-grained PATs cannot open the pull request against the -public upstream registry. +Two repository secrets are required: -```sh -gh run list \ - --repo formatjs/rules_formatjs \ - --workflow ci.yaml \ - --branch main \ - --limit 1 +- `GH_RELEASE_TOKEN`: Classic PAT with `repo` and `workflow` scopes. Release + Please uses it so tag pushes trigger `release.yaml`. +- `BCR_PUBLISH_TOKEN`: Classic PAT with `repo` and `workflow` scopes. Its account + must be able to push to `formatjs/bazel-central-registry` and open the + upstream PR. -gh run list \ - --repo formatjs/rules_formatjs \ - --workflow verify-hooks.yml \ - --branch main \ - --limit 1 - -gh run list --repo formatjs/rules_formatjs --workflow tag.yaml --limit 5 -gh release list --repo formatjs/rules_formatjs --limit 5 -``` - -Dispatch the tag workflow. It has no inputs. +Set secrets interactively so tokens do not enter shell history: ```sh -gh workflow run tag.yaml --repo formatjs/rules_formatjs --ref main +gh secret set GH_RELEASE_TOKEN --repo formatjs/rules_formatjs +gh secret set BCR_PUBLISH_TOKEN --repo formatjs/rules_formatjs ``` -The workflow uses `smlx/ccv` to inspect commits since the latest release. A -`fix` normally produces a patch, a `feat` produces a minor, and a breaking -change produces a major. Other commit types might not create a release. - -Manual dispatch bypasses the two-week guard used by the scheduled run. Do not -push a tag or choose a version manually for a normal patch or minor release. +## Normal release -Find and watch the dispatched run: +Before merging the Release Please PR, confirm `main` CI and Verify Hooks are +green, no release run is active, and the proposed version is expected. A `fix` +normally produces a patch, a `feat` produces a minor, and a breaking change +produces a major. ```sh -gh run list \ - --repo formatjs/rules_formatjs \ - --workflow tag.yaml \ - --event workflow_dispatch \ - --limit 1 - -gh run watch RUN_ID --repo formatjs/rules_formatjs --exit-status +gh run list --repo formatjs/rules_formatjs --workflow ci.yaml --branch main --limit 1 +gh run list --repo formatjs/rules_formatjs --workflow verify-hooks.yml --branch main --limit 1 +gh run list --repo formatjs/rules_formatjs --workflow release-please.yml --limit 5 +gh release list --repo formatjs/rules_formatjs --limit 5 ``` -Completion means all of the following are true: - -- the new tag points at the intended `main` commit; -- the GitHub release exists with the source and docs archives; -- release attestations were created; -- the publish job opened or updated the BCR PR. +Merge the Release Please PR. That merge is explicit approval of its version, +including a major bump. Release Please creates the tag and GitHub release. The +tag triggers `release.yaml`, which builds assets, creates attestations, updates +the release, and opens the BCR PR. ```sh +gh run list --repo formatjs/rules_formatjs --workflow release.yaml --limit 1 +gh run watch RUN_ID --repo formatjs/rules_formatjs --exit-status gh release view TAG --repo formatjs/rules_formatjs ``` -## Major releases - -The tag workflow intentionally skips the release and BCR jobs when it computes -a major version. It may still create the tag. Confirm the version and tag before -continuing, then release that existing tag explicitly: - -```sh -gh workflow run release.yaml \ - --repo formatjs/rules_formatjs \ - --ref main \ - -f tag_name=TAG -``` +Completion means the tag points at the Release Please merge, the GitHub release +contains source and docs archives, attestations exist, and the BCR PR is open. ## Recovery -Use the narrower workflows only when resuming an existing release: - -- If the tag exists but the GitHub release does not, dispatch `release.yaml` - with that tag. +- If the Release Please PR is missing or stale, dispatch + `release-please.yml` on `main`. +- If the tag exists but assets or attestations are missing, dispatch + `release.yaml` with that tag. - If the GitHub release exists but BCR publication failed, dispatch `publish.yaml` with that tag. -- If the BCR push reports `Invalid username or token`, rotate - `BCR_PUBLISH_TOKEN` with a valid Classic PAT that has `repo` and `workflow` - scopes, then retry `publish.yaml`. Do not rerun the tag or release workflow. - -Set or rotate the repository secret interactively so the token is not written -to shell history: +- If the BCR push rejects its token, rotate `BCR_PUBLISH_TOKEN`, then retry + `publish.yaml`. Do not rerun Release Please or create another tag. ```sh -gh secret set BCR_PUBLISH_TOKEN --repo formatjs/rules_formatjs -``` +gh workflow run release-please.yml --repo formatjs/rules_formatjs --ref main + +gh workflow run release.yaml \ + --repo formatjs/rules_formatjs \ + --ref main \ + -f tag_name=TAG -```sh gh workflow run publish.yaml \ --repo formatjs/rules_formatjs \ --ref main \ -f tag_name=TAG ``` -Do not use either recovery workflow to invent a new version. Do not set the -version in `MODULE.bazel`; the BCR publisher patches it in the registry PR. +Do not invent a version or push a tag. Keep `MODULE.bazel` version blank; +Release Please updates `version.txt`, while the BCR publisher sets the module +version in its registry PR. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..be25184 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "include-component-in-tag": false, + "changelog-type": "github", + "packages": { + ".": { + "package-name": "rules_formatjs" + } + } +} diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..f374f66 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.9.1