From baef968ca3e0ae7c07c5ec5483e68ce56bba77bc Mon Sep 17 00:00:00 2001 From: Cody Spath Date: Fri, 23 Oct 2026 13:15:25 +0000 Subject: [PATCH 1/4] chore: move to release-please for release management Replace the hand-written "prepare X.Y.Z release" PRs with release-please, driven by Conventional Commit PR titles, matching ld-find-code-refs. Merging the release PR now creates a draft release and the vX.Y.Z tag, builds and pushes the Docker Hub image, publishes the release and moves v2. publish.yml replaces publish-image.yml (tags pushed with GITHUB_TOKEN never triggered it) and is shared with manual-publish.yml for dry runs and recovery. Co-Authored-By: Claude Opus 5.5 Generated-by: termag --- .github/workflows/lint-pr-title.yml | 21 ++++ .github/workflows/main.yml | 7 +- .github/workflows/manual-publish.yml | 35 ++++++ .github/workflows/publish-image.yml | 118 -------------------- .github/workflows/publish.yml | 157 +++++++++++++++++++++++++++ .github/workflows/release-please.yml | 85 +++++++++++++++ .release-please-manifest.json | 3 + AGENTS.md | 15 +-- CHANGELOG.md | 8 +- DEVELOPMENT.md | 27 +++-- README.md | 2 + docker/action.yml | 2 + internal/version/version.go | 2 +- release-please-config.json | 19 ++++ 14 files changed, 352 insertions(+), 149 deletions(-) create mode 100644 .github/workflows/lint-pr-title.yml create mode 100644 .github/workflows/manual-publish.yml delete mode 100644 .github/workflows/publish-image.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml new file mode 100644 index 00000000..fa5950d3 --- /dev/null +++ b/.github/workflows/lint-pr-title.yml @@ -0,0 +1,21 @@ +name: "Enforce Semantic Pull Request Titles" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + +permissions: + pull-requests: read + +jobs: + main: + name: Validate Semantic PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3be62064..4c3883e1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,12 @@ name: Test and Generate Docs -on: pull_request +on: + pull_request: + # release-please opens its PR with GITHUB_TOKEN, which does not trigger + # pull_request runs; release-please.yml dispatches this workflow instead. + # The e2e jobs need a pull_request event, so they skip on dispatch. + workflow_dispatch: jobs: go-test: diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml new file mode 100644 index 00000000..856e73f9 --- /dev/null +++ b/.github/workflows/manual-publish.yml @@ -0,0 +1,35 @@ +name: Manual Publish + +# Dry-run the image build, or re-publish a release whose publish job failed. +on: + workflow_dispatch: + inputs: + tag: + description: Existing release tag to build (vX.Y.Z) + required: true + type: string + dry_run: + description: Build the image only; push nothing + required: false + type: boolean + default: true + publish_release: + description: Un-draft the release, push latest and move the major tag (v2) to it + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + publish: + permissions: + id-token: write + contents: write + uses: ./.github/workflows/publish.yml + with: + tag: ${{ inputs.tag }} + dry_run: ${{ inputs.dry_run }} + publish_release: ${{ inputs.publish_release }} + secrets: inherit diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml deleted file mode 100644 index 341fbb67..00000000 --- a/.github/workflows/publish-image.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Publish Docker image - -on: - workflow_dispatch: - inputs: - version: - description: Image tag without v prefix (e.g. 2.3.0). Must match an existing git tag vX.Y.Z. - required: true - type: string - push_latest: - description: Also tag and push latest - required: false - type: boolean - default: true - push: - tags: - - "v*.*.*" - -permissions: - contents: read - id-token: write - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - name: Resolve version - id: version - run: | - set -euo pipefail - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - VERSION="${{ inputs.version }}" - PUSH_LATEST="${{ inputs.push_latest }}" - else - VERSION="${GITHUB_REF_NAME#v}" - PUSH_LATEST="true" - fi - if [[ -z "${VERSION}" || "${VERSION}" == v* ]]; then - echo "Invalid version: ${VERSION}" - exit 1 - fi - if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Invalid version (expected X.Y.Z): ${VERSION}" - exit 1 - fi - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "git_tag=v${VERSION}" >> "$GITHUB_OUTPUT" - echo "image=launchdarkly/find-code-references-in-pull-request:${VERSION}" >> "$GITHUB_OUTPUT" - # String "true"/"false" for metadata-action enable= (must be exact). - if [[ "${PUSH_LATEST}" == "true" ]]; then - echo "push_latest=true" >> "$GITHUB_OUTPUT" - else - echo "push_latest=false" >> "$GITHUB_OUTPUT" - fi - - - name: Checkout release tag - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - with: - ref: ${{ steps.version.outputs.git_tag }} - - - name: Get Docker Hub credentials - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 - with: - aws_assume_role: ${{ vars.AWS_ROLE_ARN }} - ssm_parameter_pairs: >- - /global/services/docker/public/username = DOCKER_USERNAME, - /global/services/docker/public/token = DOCKER_TOKEN - - - name: Verify Docker credentials loaded - run: | - set -euo pipefail - if [[ -z "${DOCKER_USERNAME:-}" || -z "${DOCKER_TOKEN:-}" ]]; then - echo "::error::Docker Hub credentials were not loaded from SSM. Ensure vars.AWS_ROLE_ARN is configured for this repository (same release role used by ld-find-code-refs)." - exit 1 - fi - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 - with: - username: ${{ env.DOCKER_USERNAME }} - password: ${{ env.DOCKER_TOKEN }} - - - name: Docker metadata - id: meta - uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 - with: - images: launchdarkly/find-code-references-in-pull-request - # Only the tags we set below (matches docker/action.yml default image name). - flavor: | - latest=false - tags: | - type=raw,value=${{ steps.version.outputs.version }} - type=raw,value=latest,enable=${{ steps.version.outputs.push_latest }} - - - name: Build and push - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - provenance: false - sbom: false - - - name: Verify published image - run: | - set -euo pipefail - IMAGE="${{ steps.version.outputs.image }}" - echo "Inspecting ${IMAGE}" - docker buildx imagetools inspect "${IMAGE}" - docker pull "${IMAGE}" - docker run --rm --entrypoint git "${IMAGE}" --version - echo "OK: published ${IMAGE} (matches docker/action.yml default image repository)" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..a25352d5 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,157 @@ +name: Publish + +# Shared by release-please.yml and manual-publish.yml so releases and +# recovery runs build with the same steps. +on: + workflow_call: + inputs: + tag: + description: Existing release tag to build (vX.Y.Z) + required: true + type: string + dry_run: + description: Build the image only; push nothing + required: false + type: boolean + default: true + publish_release: + description: Un-draft the release, push latest and move the major tag (v2) to it + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + image: + runs-on: ubuntu-latest + permissions: + id-token: write # assume vars.AWS_ROLE_ARN for the Docker Hub credentials + contents: read + steps: + - name: Checkout release tag + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + with: + ref: ${{ inputs.tag }} + + # The image tag, version.go and the docker/ Action's default image must + # all name the same version, or /docker users pull an image that is not there. + - name: Check tag matches version + id: version + env: + TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid tag (expected vX.Y.Z): $TAG" + exit 1 + fi + version="${TAG#v}" + code_version=$(sed -nE 's/^const Version = "([^"]+)".*/\1/p' internal/version/version.go) + if [[ "$code_version" != "$version" ]]; then + echo "::error::internal/version/version.go is $code_version but tag is $TAG" + exit 1 + fi + if ! grep -qF "default: \"launchdarkly/find-code-references-in-pull-request:${version}\"" docker/action.yml; then + echo "::error::docker/action.yml default dockerImage does not match $TAG" + exit 1 + fi + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "image=launchdarkly/find-code-references-in-pull-request:${version}" >> "$GITHUB_OUTPUT" + + - name: Get Docker Hub credentials + if: ${{ !inputs.dry_run }} + uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 + with: + aws_assume_role: ${{ vars.AWS_ROLE_ARN }} + ssm_parameter_pairs: >- + /global/services/docker/public/username = DOCKER_USERNAME, + /global/services/docker/public/token = DOCKER_TOKEN + + - name: Verify Docker credentials loaded + if: ${{ !inputs.dry_run }} + run: | + set -euo pipefail + if [[ -z "${DOCKER_USERNAME:-}" || -z "${DOCKER_TOKEN:-}" ]]; then + echo "::error::Docker Hub credentials were not loaded from SSM. Ensure vars.AWS_ROLE_ARN is configured for this repository (same release role used by ld-find-code-refs)." + exit 1 + fi + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 + + - name: Log in to Docker Hub + if: ${{ !inputs.dry_run }} + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + username: ${{ env.DOCKER_USERNAME }} + password: ${{ env.DOCKER_TOKEN }} + + - name: Docker metadata + id: meta + uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 + with: + images: launchdarkly/find-code-references-in-pull-request + # Only the tags we set below (matches docker/action.yml default image name). + # latest follows the published release, so a recovery run that only + # re-pushes an older image does not move it. + flavor: | + latest=false + tags: | + type=raw,value=${{ steps.version.outputs.version }} + type=raw,value=latest,enable=${{ inputs.publish_release }} + + - name: Build and push + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: ${{ !inputs.dry_run }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + provenance: false + sbom: false + + - name: Verify published image + if: ${{ !inputs.dry_run }} + env: + IMAGE: ${{ steps.version.outputs.image }} + run: | + set -euo pipefail + echo "Inspecting ${IMAGE}" + docker buildx imagetools inspect "${IMAGE}" + docker pull "${IMAGE}" + docker run --rm --entrypoint git "${IMAGE}" --version + echo "OK: published ${IMAGE} (matches docker/action.yml default image repository)" + + # Only release once the image exists: the docker/ Action at this tag defaults to it. + release: + needs: image + if: ${{ !inputs.dry_run && inputs.publish_release }} + runs-on: ubuntu-latest + permissions: + contents: write # publish the release and move the major tag + steps: + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ inputs.tag }} + run: gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest + + # Users reference the Action as @v2, so this is the step that ships the release. + - name: Move major version tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + major="${TAG%%.*}" + sha=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${TAG}" --jq .sha) + if gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${major}" >/dev/null 2>&1; then + gh api -X PATCH "repos/${GITHUB_REPOSITORY}/git/refs/tags/${major}" -f sha="${sha}" -F force=true >/dev/null + else + gh api "repos/${GITHUB_REPOSITORY}/git/refs" -f ref="refs/tags/${major}" -f sha="${sha}" >/dev/null + fi + echo "Moved ${major} to ${TAG} (${sha})." | tee -a "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..ce68614d --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,85 @@ +name: Release Please + +on: + push: + branches: + - main + +# Serialize runs so two quick pushes cannot both create the tag or publish. +concurrency: + group: release-please + cancel-in-progress: false + +permissions: + contents: read + +jobs: + release-please: + runs-on: ubuntu-latest + permissions: + actions: write # dispatch main.yml on the release PR branch + contents: write + pull-requests: write + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + # Create the draft release for a merged release PR first. Draft releases do + # not create their tag, so push it below before opening any new release PR. + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + skip-github-pull-request: true + + # Tag the release commit release-please reports, not github.sha: a later + # push to main can be the run that creates the release. + - name: Create release tag + if: ${{ steps.release.outputs.release_created == 'true' }} + env: + TAG_NAME: ${{ steps.release.outputs.tag_name }} + SHA: ${{ steps.release.outputs.sha }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then + echo "Tag ${TAG_NAME} already exists, skipping creation." + else + echo "Creating tag ${TAG_NAME} at ${SHA}." + gh api "repos/${{ github.repository }}/git/refs" -f ref="refs/tags/${TAG_NAME}" -f sha="${SHA}" >/dev/null + fi + + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + if: ${{ steps.release.outputs.release_created != 'true' }} + id: release-prs + with: + token: ${{ secrets.GITHUB_TOKEN }} + skip-github-release: true + + # PR events from GITHUB_TOKEN start no workflows, so the required go-test, + # e2e-tests and generate-docs checks would never report on the release PR. + # workflow_dispatch is exempt; its checks land on the PR's head commit. + # Parse the PR in the script: step env is evaluated even when the step is + # skipped, and fromJSON fails on the empty output when no PR was opened. + - name: Run required checks on the release PR + if: ${{ steps.release-prs.outputs.prs_created == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ steps.release-prs.outputs.pr }} + run: | + set -euo pipefail + branch=$(jq -r .headBranchName <<<"$PR") + gh workflow run main.yml --repo "${{ github.repository }}" --ref "$branch" + + publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + permissions: + id-token: write # assume vars.AWS_ROLE_ARN for the Docker Hub credentials + contents: write # publish the release and move the major tag + uses: ./.github/workflows/publish.yml + with: + tag: ${{ needs.release-please.outputs.tag_name }} + dry_run: false + publish_release: true + secrets: inherit diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..8d10c8a3 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "2.3.4" +} diff --git a/AGENTS.md b/AGENTS.md index 2455b923..2fb68cee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ When you add/change an input or output: 1. Update `action.yml` 2. Wire parsing/defaults in `config/config.go` (and use in `main.go` / packages) 3. Run `make docs` so README tables stay in sync (`` blocks) -4. Add a `CHANGELOG.md` entry under `## [Unreleased]` +4. Give the PR a Conventional Commit title (`fix:`, `feat:`); release-please builds `CHANGELOG.md` from it 5. Add/extend unit tests near the logic (`diff/`, `comments/`, `internal/references/`) When bumping dependencies: @@ -101,13 +101,13 @@ Recent merges use conventional-ish prefixes: - `feat:`, `fix:`, `chore:`, `docs:`, `chore(deps):` - Optional Jira: `[REL-xxxxx]` in title/body when tracked -- Release prep: `prepare X.Y.Z release` — bump `CHANGELOG.md` + `internal/version/version.go` together +- Titles must be Conventional Commits (`lint-pr-title.yml`); release-please derives the version and changelog from them **PR checklist for agents:** - [ ] Scope is focused; avoid mixing feature work with large unrelated vendor bumps - [ ] Tests for classification / comment / config behavior when logic changes -- [ ] `CHANGELOG.md` `[Unreleased]` updated for user-visible changes +- [ ] PR title uses `fix:` / `feat:` for user-visible changes (do not hand-edit `CHANGELOG.md` outside a release PR) - [ ] `make docs` if `action.yml` changed - [ ] `go mod vendor` committed when modules change - [ ] No hand-edits under `vendor/` except via `go mod vendor` @@ -122,14 +122,7 @@ Do not add reviewers; CODEOWNERS / maintainers handle that ([CONTRIBUTING.md](CO ## Releases -Follow [DEVELOPMENT.md](DEVELOPMENT.md): - -1. Move `[Unreleased]` notes into a new version section in `CHANGELOG.md` -2. Set `internal/version/version.go` to match -3. Bump default `dockerImage` in `docker/action.yml` (+ README pins) to the new semver -4. Publish via GitHub Marketplace release flow (manual publish step) -5. Maintain major floating tag (`v2` for 2.x) in addition to semver tags -6. Publish the runtime image `launchdarkly/find-code-references-in-pull-request:X.Y.Z` via `.github/workflows/publish-image.yml` (tag push or `workflow_dispatch`; Hub creds via `release-secrets` + `vars.AWS_ROLE_ARN` / SSM, same as `ld-find-code-refs`) +release-please handles releases; see [DEVELOPMENT.md](DEVELOPMENT.md#publishing-a-release). Do not open `prepare X.Y.Z release` PRs or bump `internal/version/version.go`, `docker/action.yml` or README pins by hand: merging the `chore(main): release X.Y.Z` PR tags the release, publishes `launchdarkly/find-code-references-in-pull-request:X.Y.Z`, publishes the GitHub release and moves `v2`. Optional follow-up: thin the root `Dockerfile` to `FROM` that published image so default `@v2` users also skip compile-on-run. diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e38d86..a07a4fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,6 @@ -## [Unreleased] +# Changelog -### Added - -### Changed - -### Fixed +All notable changes to find-code-references-in-pull-request are documented in this file. This project adheres to [Semantic Versioning](https://semver.org). ## 2.3.4 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index dac4b452..f9d34982 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -19,18 +19,21 @@ _Read more: [Example commands](https://github.com/nektos/act#example-commands)_ ## Publishing a release -1. Move `[Unreleased]` notes into a new version section in [CHANGELOG.md](CHANGELOG.md) -2. Set [internal/version/version.go](internal/version/version.go) to match -3. Update the default `dockerImage` tag in [docker/action.yml](docker/action.yml) (and README examples) to the new semver -4. Create the GitHub release / Marketplace publish (manual publish step — see [GitHub docs](https://docs.github.com/en/actions/creating-actions/publishing-actions-in-github-marketplace#publishing-an-action)) -5. Maintain the major floating tag (`v2` for 2.x) in addition to the semver tag -6. Publish the runtime Docker image to Docker Hub (required for the optional `/docker` entry point): - - Prefer tagging `vX.Y.Z` on `main` (triggers [.github/workflows/publish-image.yml](.github/workflows/publish-image.yml)), **or** - - Run **Publish Docker image** via `workflow_dispatch` with `version: X.Y.Z` (builds from existing git tag `vX.Y.Z`) - - Credentials: same path as other public LD images — `release-secrets` assumes `vars.AWS_ROLE_ARN` and reads `/global/services/docker/public/username` + `token` from SSM (no repo `DOCKERHUB_*` secrets) - - Image: `launchdarkly/find-code-references-in-pull-request:X.Y.Z` (and `latest` when enabled) — must match the default `dockerImage` in [docker/action.yml](docker/action.yml) - -**Publishing** to the Marketplace is a manual step even if automation is used to create a release. +Versions come from [release-please](https://github.com/googleapis/release-please), which reads [Conventional Commit](https://www.conventionalcommits.org) PR titles (enforced by `lint-pr-title.yml`): `fix:` bumps the patch version, `feat:` the minor version, and `feat!:` or a `BREAKING CHANGE:` footer the major version. `chore:`, `docs:`, `ci:` and changes under `.github/` do not trigger a release. + +1. Merge PRs to `main` as usual. release-please keeps a `chore(main): release X.Y.Z` PR open that bumps [internal/version/version.go](internal/version/version.go), the default `dockerImage` in [docker/action.yml](docker/action.yml) and the README pin, and adds the [CHANGELOG.md](CHANGELOG.md) entry. +2. Review the release PR. Edit `CHANGELOG.md` on its branch if the generated notes need more context. +3. Merge the release PR. [.github/workflows/release-please.yml](.github/workflows/release-please.yml) then: + - creates a draft GitHub release and pushes the `vX.Y.Z` tag at the release commit + - builds and pushes `launchdarkly/find-code-references-in-pull-request:X.Y.Z` and `latest` to Docker Hub (via the shared [.github/workflows/publish.yml](.github/workflows/publish.yml)). Credentials come from `release-secrets`, which assumes `vars.AWS_ROLE_ARN` and reads `/global/services/docker/public/username` + `token` from SSM, same as `ld-find-code-refs` + - publishes the release and moves the major tag (`v2`) to it, which is when `@v2` users pick it up. If the image push fails, the release stays a draft and `v2` does not move +4. Publish the release to the Marketplace by hand if needed (see [GitHub docs](https://docs.github.com/en/actions/creating-actions/publishing-actions-in-github-marketplace#publishing-an-action)); releases created by the workflow are not listed automatically. + +Every file that carries the version is annotated with `x-release-please-version` and listed under `extra-files` in [release-please-config.json](release-please-config.json). Add any new one there. + +### Dry runs and recovery + +**Manual Publish** ([.github/workflows/manual-publish.yml](.github/workflows/manual-publish.yml)) runs the same `publish.yml` against an existing tag. Leave `dry_run` checked to build the image without pushing anything. To recover a release whose publish job failed, uncheck `dry_run` and check `publish_release`; that pushes the image, publishes the release and moves `v2`. ### Versioning diff --git a/README.md b/README.md index f711d27c..e88c9832 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ The root Action (`launchdarkly/find-code-references-in-pull-request@v2`) is a Do If your organization must pull images through an internal registry or Docker Hub proxy, use the optional **`docker`** entry point. It runs a **prebuilt** runtime image via `docker run` and accepts a `dockerImage` input. Mirror `launchdarkly/find-code-references-in-pull-request` into your registry first (pin the tag you mirrored). + ```yaml on: pull_request @@ -116,6 +117,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} dockerImage: your.registry.example/launchdarkly/find-code-references-in-pull-request:2.3.4 ``` + This entry point requires a Docker CLI on the runner (included on GitHub-hosted `ubuntu-*` runners). Existing workflows that use the root Action do not need to change. diff --git a/docker/action.yml b/docker/action.yml index 98b19971..4b547e90 100644 --- a/docker/action.yml +++ b/docker/action.yml @@ -51,6 +51,7 @@ inputs: description: Skip commenting on PR. If set to true, the action will not comment on the PR, but will still set outputs. required: false default: "false" + # x-release-please-start-version dockerImage: description: >- Container image to run. Defaults to the public Docker Hub runtime image. @@ -61,6 +62,7 @@ inputs: ubuntu-* runners include one). required: false default: "launchdarkly/find-code-references-in-pull-request:2.3.4" + # x-release-please-end outputs: any-modified: diff --git a/internal/version/version.go b/internal/version/version.go index 3fa6b5e0..5dc6eb7b 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,3 +1,3 @@ package version -const Version = "2.3.4" +const Version = "2.3.4" // {{ x-release-please-version }} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..36ff316c --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,19 @@ +{ + "bootstrap-sha": "beb8579e2a1f27acb9aaffdd73682e3733495cbc", + "packages": { + ".": { + "release-type": "go", + "versioning": "default", + "draft": true, + "include-component-in-tag": false, + "exclude-paths": [ + ".github" + ], + "extra-files": [ + "internal/version/version.go", + "docker/action.yml", + "README.md" + ] + } + } +} From 9caba5588cbac245a8de1fb9e4bb9828539758ba Mon Sep 17 00:00:00 2001 From: Cody Spath Date: Fri, 23 Oct 2026 10:08:01 -0400 Subject: [PATCH 2/4] Apply suggestion from @cspath1 --- .github/workflows/lint-pr-title.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index fa5950d3..57cda79c 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -18,4 +18,5 @@ jobs: steps: - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + \ No newline at end of file From b55b604f84f6bd5ab9eead64a81dc9b964f795ee Mon Sep 17 00:00:00 2001 From: Cody Spath Date: Fri, 23 Oct 2026 14:09:47 +0000 Subject: [PATCH 3/4] ci: end lint-pr-title.yml with a newline Co-Authored-By: Claude Opus 5.5 Generated-by: termag --- .github/workflows/lint-pr-title.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 57cda79c..369b8da1 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -19,4 +19,3 @@ jobs: - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - \ No newline at end of file From 551e1e9ea1926edd47467714ab11ca8a4d29adef Mon Sep 17 00:00:00 2001 From: Cody Spath Date: Fri, 23 Oct 2026 14:37:40 +0000 Subject: [PATCH 4/4] ci: only move latest and v2 for the newest release Manual Publish with publish_release on an older tag would mark it the latest release, push it as the latest image and move v2 back to it. Check whether the tag is the newest vMAJOR.x.y and skip all three if not. Co-Authored-By: Claude Opus 5.5 Generated-by: termag --- .github/workflows/publish.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a25352d5..74372b9e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,6 +26,8 @@ permissions: jobs: image: runs-on: ubuntu-latest + outputs: + newest: ${{ steps.version.outputs.newest }} permissions: id-token: write # assume vars.AWS_ROLE_ARN for the Docker Hub credentials contents: read @@ -40,6 +42,7 @@ jobs: - name: Check tag matches version id: version env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ inputs.tag }} run: | set -euo pipefail @@ -57,6 +60,17 @@ jobs: echo "::error::docker/action.yml default dockerImage does not match $TAG" exit 1 fi + # Only the newest release in the series gets latest and the major tag, + # so publishing an older tag again cannot move them backwards. + major="${TAG%%.*}" + newest=$(gh api "repos/${GITHUB_REPOSITORY}/git/matching-refs/tags/${major}." --paginate --jq '.[].ref' \ + | sed -nE 's|^refs/tags/(v[0-9]+\.[0-9]+\.[0-9]+)$|\1|p' | sort -V | tail -n1) + if [[ "$newest" == "$TAG" ]]; then + echo "newest=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::${TAG} is not the newest ${major} release (${newest}); not moving latest or ${major}." + echo "newest=false" >> "$GITHUB_OUTPUT" + fi echo "version=${version}" >> "$GITHUB_OUTPUT" echo "image=launchdarkly/find-code-references-in-pull-request:${version}" >> "$GITHUB_OUTPUT" @@ -94,13 +108,13 @@ jobs: with: images: launchdarkly/find-code-references-in-pull-request # Only the tags we set below (matches docker/action.yml default image name). - # latest follows the published release, so a recovery run that only - # re-pushes an older image does not move it. + # latest follows the newest published release, so a recovery run that + # only re-pushes an image, or publishes an older tag, does not move it. flavor: | latest=false tags: | type=raw,value=${{ steps.version.outputs.version }} - type=raw,value=latest,enable=${{ inputs.publish_release }} + type=raw,value=latest,enable=${{ inputs.publish_release && steps.version.outputs.newest == 'true' }} - name: Build and push uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 @@ -138,10 +152,12 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ inputs.tag }} - run: gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest + LATEST: ${{ needs.image.outputs.newest }} + run: gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest="$LATEST" # Users reference the Action as @v2, so this is the step that ships the release. - name: Move major version tag + if: ${{ needs.image.outputs.newest == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ inputs.tag }}