From b15c2296f3ea9f7211558ea4be1dc7f3509df78c Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 18 Aug 2026 11:52:21 +0300 Subject: [PATCH 1/3] Publish untagged plugin.json versions instead of requiring a [patch] marker. --- .github/workflows/release.yml | 65 +++++++++++++++++++++-------------- CONTRIBUTING.md | 26 +++++++++----- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a94aba8..fa7fe63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,13 @@ # Copyright (c) JFrog Ltd. 2026 # -# Cuts a GitHub Release when a release marker is merged to main. +# Cuts a GitHub Release when main's plugin.json version is not yet tagged. # Full flow and rationale: CONTRIBUTING.md#releasing name: Release on: push: branches: [main] + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -24,24 +25,9 @@ jobs: with: fetch-depth: 0 - # Subject line only, not the whole message. MSG goes through env rather than string - # interpolation, so a crafted commit subject can't inject shell. - - name: Detect release marker in commit subject - id: detect - env: - MSG: ${{ github.event.head_commit.message }} - run: | - SUBJECT=$(printf '%s\n' "$MSG" | head -1) - if printf '%s' "$SUBJECT" | grep -qE '\[(major|minor|patch)\]'; then - echo "triggered=true" >> "$GITHUB_OUTPUT" - else - echo "triggered=false" >> "$GITHUB_OUTPUT" - fi - # plugin.json is canonical; marketplace.json carries its own copy, so the two are # cross-checked here as well as by the validate-version PR check. - name: Read version from the plugin manifest - if: steps.detect.outputs.triggered == 'true' id: version run: | set -euo pipefail @@ -55,18 +41,47 @@ jobs: echo "::error::plugin.json is $VERSION but marketplace.json .metadata.version is $MARKET_VERSION — sync them before releasing" exit 1 fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" + TAG="v${VERSION}" + if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then + echo "tagged=true" >> "$GITHUB_OUTPUT" + else + echo "tagged=false" >> "$GITHUB_OUTPUT" + fi + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - # A tag exists only if that version was released, so this catches a marker that was merged - # without a manifest bump. - - name: Refuse to re-release an existing version - if: steps.detect.outputs.triggered == 'true' + # Publish whenever the manifest version has no tag. The [major]|[minor]|[patch] + # subject marker is optional: this repo squash-merges with the PR title as the + # commit subject, so a rewritten title used to skip the release while the job + # stayed green. An explicit marker (or workflow_dispatch) against an already + # tagged version still fails, so a forgotten bump cannot re-tag a shipped release. + # Subject is matched only on the first line. MSG goes through env rather than + # string interpolation, so a crafted commit subject can't inject shell. + - name: Decide whether to release + id: detect + env: + MSG: ${{ github.event.head_commit.message }} + EVENT_NAME: ${{ github.event_name }} + TAGGED: ${{ steps.version.outputs.tagged }} + VERSION: ${{ steps.version.outputs.version }} run: | - TAG="v${{ steps.version.outputs.version }}" - if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then - echo "::error::$TAG already exists — bump the plugin manifests before merging a release marker" - exit 1 + set -euo pipefail + SUBJECT=$(printf '%s\n' "$MSG" | head -1) + MARKER=false + if printf '%s' "$SUBJECT" | grep -qE '\[(major|minor|patch)\]'; then + MARKER=true + fi + TAG="v${VERSION}" + if [ "$TAGGED" = true ]; then + if [ "$MARKER" = true ] || [ "$EVENT_NAME" = workflow_dispatch ]; then + echo "::error::${TAG} already exists — bump the plugin manifests before requesting a release" + exit 1 + fi + echo "triggered=false" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping: ${TAG} already released" + exit 0 fi + echo "triggered=true" >> "$GITHUB_OUTPUT" + echo "::notice::Publishing ${TAG} (manifest is untagged on main)" - uses: actions/setup-node@v5 if: steps.detect.outputs.triggered == 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 865f0af..14a34f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,14 +41,24 @@ See [`VENDOR.md`](VENDOR.md) for the full picture. To cut a release: 1. In your PR, bump `.version` in [`plugins/jfrog/.cursor-plugin/plugin.json`](plugins/jfrog/.cursor-plugin/plugin.json) and sync `.metadata.version` in [`.cursor-plugin/marketplace.json`](.cursor-plugin/marketplace.json) to match. `plugin.json` is canonical; the `validate-version` PR check enforces that the two agree. -2. Merge to `main` with `[major]`, `[minor]`, or `[patch]` in the commit **subject** - the first - line. A marker further down in the body is ignored on purpose: this repo squash-merges, and - GitHub pre-fills the squash body from the branch commits or the PR description, either of - which may quote a marker while only documenting it. - -The marker only decides *whether* to release; the version comes from the manifest either way, so the bump is reviewed in the PR that makes it. There is no bot push to `main`. Merging a marker without bumping the manifests fails the release rather than re-tagging a shipped version. - -The workflow reads the version from `plugin.json`, confirms `marketplace.json` agrees, refuses to continue if that version is already tagged, runs the same marketplace-template check as the `validate-template` PR workflow, packages the tracked files at `HEAD` (minus `.github/`) into `release.zip`, and creates the `vX.Y.Z` tag as part of publishing the GitHub Release. +2. Merge to `main`. If that version is not tagged yet, the Release workflow publishes it. The + version comes from the manifest, so the bump stays reviewable in the PR that makes it. There + is no bot push to `main`. + +A `[major]`, `[minor]`, or `[patch]` marker in the commit **subject** (first line) is optional. +This repo squash-merges with the PR title as the subject, so requiring the marker used to skip +the release whenever a title was rewritten — while the job stayed green. The workflow now +publishes whenever `plugin.json` is ahead of the latest tag. A marker (or a manual +`workflow_dispatch`) against a version that is already tagged still fails, rather than +re-tagging a shipped release. A marker further down in the body is ignored on purpose: GitHub +pre-fills the squash body from the branch commits or the PR description, either of which may +quote a marker while only documenting it. + +The workflow reads the version from `plugin.json`, confirms `marketplace.json` agrees, skips +when that version is already tagged (or fails if a marker / dispatch asked to re-release it), +runs the same marketplace-template check as the `validate-template` PR workflow, packages the +tracked files at `HEAD` (minus `.github/`) into `release.zip`, and creates the `vX.Y.Z` tag as +part of publishing the GitHub Release. Two things to know before changing it: From 47e578ba7d42dfeddeba39c52d72c2c4cd50f3d7 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 18 Aug 2026 11:58:15 +0300 Subject: [PATCH 2/3] [patch] Publish v0.5.14 using the Claude/VS Code release marker. --- .github/workflows/release.yml | 65 ++++++++++++++--------------------- CONTRIBUTING.md | 32 ++++++++--------- 2 files changed, 39 insertions(+), 58 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa7fe63..a94aba8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,12 @@ # Copyright (c) JFrog Ltd. 2026 # -# Cuts a GitHub Release when main's plugin.json version is not yet tagged. +# Cuts a GitHub Release when a release marker is merged to main. # Full flow and rationale: CONTRIBUTING.md#releasing name: Release on: push: branches: [main] - workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -25,9 +24,24 @@ jobs: with: fetch-depth: 0 + # Subject line only, not the whole message. MSG goes through env rather than string + # interpolation, so a crafted commit subject can't inject shell. + - name: Detect release marker in commit subject + id: detect + env: + MSG: ${{ github.event.head_commit.message }} + run: | + SUBJECT=$(printf '%s\n' "$MSG" | head -1) + if printf '%s' "$SUBJECT" | grep -qE '\[(major|minor|patch)\]'; then + echo "triggered=true" >> "$GITHUB_OUTPUT" + else + echo "triggered=false" >> "$GITHUB_OUTPUT" + fi + # plugin.json is canonical; marketplace.json carries its own copy, so the two are # cross-checked here as well as by the validate-version PR check. - name: Read version from the plugin manifest + if: steps.detect.outputs.triggered == 'true' id: version run: | set -euo pipefail @@ -41,47 +55,18 @@ jobs: echo "::error::plugin.json is $VERSION but marketplace.json .metadata.version is $MARKET_VERSION — sync them before releasing" exit 1 fi - TAG="v${VERSION}" - if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then - echo "tagged=true" >> "$GITHUB_OUTPUT" - else - echo "tagged=false" >> "$GITHUB_OUTPUT" - fi - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - # Publish whenever the manifest version has no tag. The [major]|[minor]|[patch] - # subject marker is optional: this repo squash-merges with the PR title as the - # commit subject, so a rewritten title used to skip the release while the job - # stayed green. An explicit marker (or workflow_dispatch) against an already - # tagged version still fails, so a forgotten bump cannot re-tag a shipped release. - # Subject is matched only on the first line. MSG goes through env rather than - # string interpolation, so a crafted commit subject can't inject shell. - - name: Decide whether to release - id: detect - env: - MSG: ${{ github.event.head_commit.message }} - EVENT_NAME: ${{ github.event_name }} - TAGGED: ${{ steps.version.outputs.tagged }} - VERSION: ${{ steps.version.outputs.version }} + # A tag exists only if that version was released, so this catches a marker that was merged + # without a manifest bump. + - name: Refuse to re-release an existing version + if: steps.detect.outputs.triggered == 'true' run: | - set -euo pipefail - SUBJECT=$(printf '%s\n' "$MSG" | head -1) - MARKER=false - if printf '%s' "$SUBJECT" | grep -qE '\[(major|minor|patch)\]'; then - MARKER=true - fi - TAG="v${VERSION}" - if [ "$TAGGED" = true ]; then - if [ "$MARKER" = true ] || [ "$EVENT_NAME" = workflow_dispatch ]; then - echo "::error::${TAG} already exists — bump the plugin manifests before requesting a release" - exit 1 - fi - echo "triggered=false" >> "$GITHUB_OUTPUT" - echo "::notice::Skipping: ${TAG} already released" - exit 0 + TAG="v${{ steps.version.outputs.version }}" + if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then + echo "::error::$TAG already exists — bump the plugin manifests before merging a release marker" + exit 1 fi - echo "triggered=true" >> "$GITHUB_OUTPUT" - echo "::notice::Publishing ${TAG} (manifest is untagged on main)" - uses: actions/setup-node@v5 if: steps.detect.outputs.triggered == 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14a34f3..d1a7971 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,24 +41,20 @@ See [`VENDOR.md`](VENDOR.md) for the full picture. To cut a release: 1. In your PR, bump `.version` in [`plugins/jfrog/.cursor-plugin/plugin.json`](plugins/jfrog/.cursor-plugin/plugin.json) and sync `.metadata.version` in [`.cursor-plugin/marketplace.json`](.cursor-plugin/marketplace.json) to match. `plugin.json` is canonical; the `validate-version` PR check enforces that the two agree. -2. Merge to `main`. If that version is not tagged yet, the Release workflow publishes it. The - version comes from the manifest, so the bump stays reviewable in the PR that makes it. There - is no bot push to `main`. - -A `[major]`, `[minor]`, or `[patch]` marker in the commit **subject** (first line) is optional. -This repo squash-merges with the PR title as the subject, so requiring the marker used to skip -the release whenever a title was rewritten — while the job stayed green. The workflow now -publishes whenever `plugin.json` is ahead of the latest tag. A marker (or a manual -`workflow_dispatch`) against a version that is already tagged still fails, rather than -re-tagging a shipped release. A marker further down in the body is ignored on purpose: GitHub -pre-fills the squash body from the branch commits or the PR description, either of which may -quote a marker while only documenting it. - -The workflow reads the version from `plugin.json`, confirms `marketplace.json` agrees, skips -when that version is already tagged (or fails if a marker / dispatch asked to re-release it), -runs the same marketplace-template check as the `validate-template` PR workflow, packages the -tracked files at `HEAD` (minus `.github/`) into `release.zip`, and creates the `vX.Y.Z` tag as -part of publishing the GitHub Release. +2. Merge to `main` with `[major]`, `[minor]`, or `[patch]` in the commit **subject** - the first + line. A marker further down in the body is ignored on purpose: this repo squash-merges, and + GitHub pre-fills the squash body from the branch commits or the PR description, either of + which may quote a marker while only documenting it. + + Keep that squash setting aligned with [`jfrog/claude-plugin`](https://github.com/jfrog/claude-plugin) + and [`jfrog/vscode-plugin`](https://github.com/jfrog/vscode-plugin): default squash commit title + `COMMIT_OR_PR_TITLE` (the branch commit subject when there is a single commit), not `PR_TITLE`. + Using the PR title drops the marker whenever the title is rewritten and the Release job stays + green without publishing. + +The marker only decides *whether* to release; the version comes from the manifest either way, so the bump is reviewed in the PR that makes it. There is no bot push to `main`. Merging a marker without bumping the manifests fails the release rather than re-tagging a shipped version. + +The workflow reads the version from `plugin.json`, confirms `marketplace.json` agrees, refuses to continue if that version is already tagged, runs the same marketplace-template check as the `validate-template` PR workflow, packages the tracked files at `HEAD` (minus `.github/`) into `release.zip`, and creates the `vX.Y.Z` tag as part of publishing the GitHub Release. Two things to know before changing it: From 4b31b87474825971b9932b0ea29b783cfcf57f68 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 18 Aug 2026 12:02:33 +0300 Subject: [PATCH 3/3] Release when plugin.json is untagged, not from a squash-subject marker. --- .github/workflows/release.yml | 55 +++++++++++++++++++---------------- CONTRIBUTING.md | 29 +++++++++--------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a94aba8..67b79ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,13 @@ # Copyright (c) JFrog Ltd. 2026 # -# Cuts a GitHub Release when a release marker is merged to main. +# Cuts a GitHub Release when main's plugin.json version is not yet tagged. # Full flow and rationale: CONTRIBUTING.md#releasing name: Release on: push: branches: [main] + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -24,24 +25,9 @@ jobs: with: fetch-depth: 0 - # Subject line only, not the whole message. MSG goes through env rather than string - # interpolation, so a crafted commit subject can't inject shell. - - name: Detect release marker in commit subject - id: detect - env: - MSG: ${{ github.event.head_commit.message }} - run: | - SUBJECT=$(printf '%s\n' "$MSG" | head -1) - if printf '%s' "$SUBJECT" | grep -qE '\[(major|minor|patch)\]'; then - echo "triggered=true" >> "$GITHUB_OUTPUT" - else - echo "triggered=false" >> "$GITHUB_OUTPUT" - fi - # plugin.json is canonical; marketplace.json carries its own copy, so the two are # cross-checked here as well as by the validate-version PR check. - name: Read version from the plugin manifest - if: steps.detect.outputs.triggered == 'true' id: version run: | set -euo pipefail @@ -55,18 +41,37 @@ jobs: echo "::error::plugin.json is $VERSION but marketplace.json .metadata.version is $MARKET_VERSION — sync them before releasing" exit 1 fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" + TAG="v${VERSION}" + if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then + echo "tagged=true" >> "$GITHUB_OUTPUT" + else + echo "tagged=false" >> "$GITHUB_OUTPUT" + fi + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - # A tag exists only if that version was released, so this catches a marker that was merged - # without a manifest bump. - - name: Refuse to re-release an existing version - if: steps.detect.outputs.triggered == 'true' + # Deterministic: the tag is the source of truth, not the squash subject. + # Untagged manifest → publish. Already tagged → skip (or fail on a manual + # dispatch, so a forgotten bump cannot re-tag a shipped release). + - name: Decide whether to release + id: detect + env: + EVENT_NAME: ${{ github.event_name }} + TAGGED: ${{ steps.version.outputs.tagged }} + VERSION: ${{ steps.version.outputs.version }} run: | - TAG="v${{ steps.version.outputs.version }}" - if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then - echo "::error::$TAG already exists — bump the plugin manifests before merging a release marker" - exit 1 + set -euo pipefail + TAG="v${VERSION}" + if [ "$TAGGED" = true ]; then + if [ "$EVENT_NAME" = workflow_dispatch ]; then + echo "::error::${TAG} already exists — bump the plugin manifests before requesting a release" + exit 1 + fi + echo "triggered=false" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping: ${TAG} already released" + exit 0 fi + echo "triggered=true" >> "$GITHUB_OUTPUT" + echo "::notice::Publishing ${TAG} (manifest is untagged on main)" - uses: actions/setup-node@v5 if: steps.detect.outputs.triggered == 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1a7971..dccc7a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,20 +41,21 @@ See [`VENDOR.md`](VENDOR.md) for the full picture. To cut a release: 1. In your PR, bump `.version` in [`plugins/jfrog/.cursor-plugin/plugin.json`](plugins/jfrog/.cursor-plugin/plugin.json) and sync `.metadata.version` in [`.cursor-plugin/marketplace.json`](.cursor-plugin/marketplace.json) to match. `plugin.json` is canonical; the `validate-version` PR check enforces that the two agree. -2. Merge to `main` with `[major]`, `[minor]`, or `[patch]` in the commit **subject** - the first - line. A marker further down in the body is ignored on purpose: this repo squash-merges, and - GitHub pre-fills the squash body from the branch commits or the PR description, either of - which may quote a marker while only documenting it. - - Keep that squash setting aligned with [`jfrog/claude-plugin`](https://github.com/jfrog/claude-plugin) - and [`jfrog/vscode-plugin`](https://github.com/jfrog/vscode-plugin): default squash commit title - `COMMIT_OR_PR_TITLE` (the branch commit subject when there is a single commit), not `PR_TITLE`. - Using the PR title drops the marker whenever the title is rewritten and the Release job stays - green without publishing. - -The marker only decides *whether* to release; the version comes from the manifest either way, so the bump is reviewed in the PR that makes it. There is no bot push to `main`. Merging a marker without bumping the manifests fails the release rather than re-tagging a shipped version. - -The workflow reads the version from `plugin.json`, confirms `marketplace.json` agrees, refuses to continue if that version is already tagged, runs the same marketplace-template check as the `validate-template` PR workflow, packages the tracked files at `HEAD` (minus `.github/`) into `release.zip`, and creates the `vX.Y.Z` tag as part of publishing the GitHub Release. +2. Merge to `main`. If that version is not tagged yet, the Release workflow publishes it. The + version comes from the manifest, so the bump stays reviewable in the PR that makes it. There + is no bot push to `main`. + +The gate is the tag, not the commit subject. Squash-merge titles, PR-title rewrites, and +`[major]` / `[minor]` / `[patch]` markers do not decide whether to release. An untagged +`plugin.json` on `main` always publishes; a version that already has `vX.Y.Z` is skipped. A +manual `workflow_dispatch` against a tagged version fails rather than re-tagging a shipped +release. If a publish fails, the tag is not created, so the next push to `main` retries. + +The workflow reads the version from `plugin.json`, confirms `marketplace.json` agrees, skips +when that version is already tagged (or fails if dispatch asked to re-release it), runs the +same marketplace-template check as the `validate-template` PR workflow, packages the tracked +files at `HEAD` (minus `.github/`) into `release.zip`, and creates the `vX.Y.Z` tag as part +of publishing the GitHub Release. Two things to know before changing it: