From 535782d90f453c2eb06c4a1fa5e136532384c69d Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Tue, 23 Jun 2026 22:00:11 +0300 Subject: [PATCH 01/12] [minor] Add tag/release mechanism and drift-prevention check (AX-1736) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/release.yml | 83 ++++++++++++++++++++++++++ .github/workflows/validate-version.yml | 31 ++++++++++ VERSION | 1 + 3 files changed, 115 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/validate-version.yml create mode 100644 VERSION diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e55f273 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,83 @@ +name: Release + +on: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Detect release tag in commit message + id: detect + run: | + MSG="${{ github.event.head_commit.message }}" + if echo "$MSG" | grep -qE '\[(major|minor|patch)\]'; then + TAG=$(echo "$MSG" | grep -oE '\[(major|minor|patch)\]' | head -1 | tr -d '[]') + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "triggered=true" >> "$GITHUB_OUTPUT" + else + echo "triggered=false" >> "$GITHUB_OUTPUT" + fi + + - name: Compute next version + if: steps.detect.outputs.triggered == 'true' + id: version + run: | + VERSION=$(cat VERSION) + MAJOR=$(echo "$VERSION" | cut -d. -f1) + MINOR=$(echo "$VERSION" | cut -d. -f2) + PATCH=$(echo "$VERSION" | cut -d. -f3) + case "${{ steps.detect.outputs.tag }}" in + major) NEXT="$((MAJOR + 1)).0.0" ;; + minor) NEXT="${MAJOR}.$((MINOR + 1)).0" ;; + patch) NEXT="${MAJOR}.${MINOR}.$((PATCH + 1))" ;; + esac + echo "version=$NEXT" >> "$GITHUB_OUTPUT" + + - name: Update VERSION and JSON files + if: steps.detect.outputs.triggered == 'true' + run: | + VERSION="${{ steps.version.outputs.version }}" + echo "$VERSION" > VERSION + jq --arg v "$VERSION" '.version = $v' plugin/.claude-plugin/plugin.json > /tmp/plugin.json + mv /tmp/plugin.json plugin/.claude-plugin/plugin.json + jq --arg v "$VERSION" '.plugins[0].version = $v' marketplace.json > /tmp/marketplace.json + mv /tmp/marketplace.json marketplace.json + + - name: Commit, tag, and push + if: steps.detect.outputs.triggered == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add VERSION plugin/.claude-plugin/plugin.json marketplace.json + git commit -m "Release v${{ steps.version.outputs.version }}" + git push origin main + git tag "v${{ steps.version.outputs.version }}" + git push origin "v${{ steps.version.outputs.version }}" + + - name: Package release artifact + if: steps.detect.outputs.triggered == 'true' + run: zip -r release.zip . --exclude ".git/*" --exclude ".github/*" + + - name: Create GitHub Release + if: steps.detect.outputs.triggered == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "v${{ steps.version.outputs.version }}" \ + release.zip \ + --title "Release v${{ steps.version.outputs.version }}" \ + --generate-notes diff --git a/.github/workflows/validate-version.yml b/.github/workflows/validate-version.yml new file mode 100644 index 0000000..3d0cabf --- /dev/null +++ b/.github/workflows/validate-version.yml @@ -0,0 +1,31 @@ +name: Validate version + +on: + pull_request: + branches: [main] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check version consistency + run: | + VERSION=$(cat VERSION) + FAILED=0 + + PLUGIN_VERSION=$(jq -r '.version' plugin/.claude-plugin/plugin.json) + if [ "$VERSION" != "$PLUGIN_VERSION" ]; then + echo "::error::Version mismatch: VERSION=$VERSION but plugin/.claude-plugin/plugin.json.version=$PLUGIN_VERSION" + FAILED=1 + fi + + MARKET_VERSION=$(jq -r '.plugins[0].version' marketplace.json) + if [ "$VERSION" != "$MARKET_VERSION" ]; then + echo "::error::Version mismatch: VERSION=$VERSION but marketplace.json.plugins[0].version=$MARKET_VERSION" + FAILED=1 + fi + + [ "$FAILED" -eq 0 ] && echo "All versions consistent: $VERSION" + exit $FAILED diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..a6a3a43 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.4 \ No newline at end of file From 4b259532be5d210ce33f639244f4a32c5f1518de Mon Sep 17 00:00:00 2001 From: Yoni Melki <58732001+YoniMelki@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:32:13 +0300 Subject: [PATCH 02/12] fix: seed VERSION and plugin.json at current version 1.0.3 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a6a3a43..21e8796 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.4 \ No newline at end of file +1.0.3 From ae496144b286a75d80e6525561b2964df6d95b4d Mon Sep 17 00:00:00 2001 From: Yoni Melki <58732001+YoniMelki@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:32:21 +0300 Subject: [PATCH 03/12] fix: seed VERSION and plugin.json at current version 1.0.3 --- plugin/.claude-plugin/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 0c70bb8..20c9c7e 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "0.1.0", + "version": "1.0.3", "author": { "name": "JFrog", "url": "https://jfrog.com" }, "hooks": "hooks/hooks.json" } From 93b651023ad21f51977aaf3fe4cdca6b9b3e9f55 Mon Sep 17 00:00:00 2001 From: Yoni Melki <58732001+YoniMelki@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:34:23 +0300 Subject: [PATCH 04/12] docs: add CONTRIBUTING.md with Releasing section --- CONTRIBUTING.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..977d727 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to JFrog VS Code Plugin + +Thank you for your interest in contributing! This project is maintained by JFrog and licensed under the [Apache License 2.0](LICENSE). + +## Contributor License Agreement (CLA) + +All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contributions can be merged. A CLA check runs automatically on every pull request — follow the prompts to sign if you haven't already. + +## How to Contribute + +1. **Fork** the repository and create a feature branch from `main`. +2. Make your changes, ensuring they follow the existing code style and project conventions. +3. **Commit** with a clear, descriptive message. +4. Open a **pull request** against `main` with a summary of what changed and why. + +## Releasing + +Releases are automated by `.github/workflows/release.yml`. To cut a release, push (or merge) a commit to `main` whose message contains `[major]`, `[minor]`, or `[patch]`: + +- `[patch]` — bug fixes; bumps `X.Y.Z` → `X.Y.Z+1` +- `[minor]` — new features; bumps `X.Y.Z` → `X.Y+1.0` +- `[major]` — breaking changes; bumps `X.Y.Z` → `X+1.0.0` + +The workflow: +1. Bumps `VERSION` and syncs the version in `plugin/.claude-plugin/plugin.json` and `marketplace.json` +2. Commits and pushes the bump to `main` +3. Creates a `vX.Y.Z` git tag +4. Publishes a GitHub Release with a repo zip attached + +**Prerequisite:** `github-actions[bot]` must be allowed to push to `main`. In the repository's branch protection (or ruleset) settings, add `github-actions[bot]` to the bypass list. + +## Reporting Issues + +Open a [GitHub issue](https://github.com/jfrog/vscode-plugin/issues) with: + +- A clear title and description of the problem. +- Steps to reproduce (if applicable). +- Expected vs. actual behavior. + +## Code Guidelines + +- Keep changes focused — one logical change per PR. +- Follow existing patterns and naming conventions in the codebase. +- Do not commit secrets, credentials, or API keys. + +## Code of Conduct + +Be respectful and constructive. We are committed to providing a welcoming and inclusive experience for everyone. + +## Questions? + +Reach out to the JFrog DevRel team at devrel@jfrog.com. From 0789129b357d7c0e6ba7e47d57abb7b19fd8ddec Mon Sep 17 00:00:00 2001 From: Yoni Melki <58732001+YoniMelki@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:32:46 +0300 Subject: [PATCH 05/12] fix: remove push-to-main from release workflow, read VERSION as-is --- .github/workflows/release.yml | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e55f273..468c804 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,6 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Detect release tag in commit message @@ -25,46 +24,19 @@ jobs: run: | MSG="${{ github.event.head_commit.message }}" if echo "$MSG" | grep -qE '\[(major|minor|patch)\]'; then - TAG=$(echo "$MSG" | grep -oE '\[(major|minor|patch)\]' | head -1 | tr -d '[]') - echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "triggered=true" >> "$GITHUB_OUTPUT" else echo "triggered=false" >> "$GITHUB_OUTPUT" fi - - name: Compute next version + - name: Read version if: steps.detect.outputs.triggered == 'true' id: version - run: | - VERSION=$(cat VERSION) - MAJOR=$(echo "$VERSION" | cut -d. -f1) - MINOR=$(echo "$VERSION" | cut -d. -f2) - PATCH=$(echo "$VERSION" | cut -d. -f3) - case "${{ steps.detect.outputs.tag }}" in - major) NEXT="$((MAJOR + 1)).0.0" ;; - minor) NEXT="${MAJOR}.$((MINOR + 1)).0" ;; - patch) NEXT="${MAJOR}.${MINOR}.$((PATCH + 1))" ;; - esac - echo "version=$NEXT" >> "$GITHUB_OUTPUT" - - - name: Update VERSION and JSON files - if: steps.detect.outputs.triggered == 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - echo "$VERSION" > VERSION - jq --arg v "$VERSION" '.version = $v' plugin/.claude-plugin/plugin.json > /tmp/plugin.json - mv /tmp/plugin.json plugin/.claude-plugin/plugin.json - jq --arg v "$VERSION" '.plugins[0].version = $v' marketplace.json > /tmp/marketplace.json - mv /tmp/marketplace.json marketplace.json + run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT" - - name: Commit, tag, and push + - name: Create and push tag if: steps.detect.outputs.triggered == 'true' run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add VERSION plugin/.claude-plugin/plugin.json marketplace.json - git commit -m "Release v${{ steps.version.outputs.version }}" - git push origin main git tag "v${{ steps.version.outputs.version }}" git push origin "v${{ steps.version.outputs.version }}" From 2ecaa3e83a23deebff27a2eb6480e2f256a53591 Mon Sep 17 00:00:00 2001 From: Yoni Melki <58732001+YoniMelki@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:33:40 +0300 Subject: [PATCH 06/12] docs: update Releasing section to reflect new developer-driven version bump flow --- CONTRIBUTING.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 977d727..38c07be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,19 +15,12 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri ## Releasing -Releases are automated by `.github/workflows/release.yml`. To cut a release, push (or merge) a commit to `main` whose message contains `[major]`, `[minor]`, or `[patch]`: +To cut a release: -- `[patch]` — bug fixes; bumps `X.Y.Z` → `X.Y.Z+1` -- `[minor]` — new features; bumps `X.Y.Z` → `X.Y+1.0` -- `[major]` — breaking changes; bumps `X.Y.Z` → `X+1.0.0` +1. In your PR, bump `VERSION` and sync both `plugin/.claude-plugin/plugin.json` `.version` and `marketplace.json` `.plugins[0].version` to match. The `validate-version` PR check enforces this. +2. Merge to `main` with `[major]`, `[minor]`, or `[patch]` anywhere in the commit message. -The workflow: -1. Bumps `VERSION` and syncs the version in `plugin/.claude-plugin/plugin.json` and `marketplace.json` -2. Commits and pushes the bump to `main` -3. Creates a `vX.Y.Z` git tag -4. Publishes a GitHub Release with a repo zip attached - -**Prerequisite:** `github-actions[bot]` must be allowed to push to `main`. In the repository's branch protection (or ruleset) settings, add `github-actions[bot]` to the bypass list. +The release workflow reads `VERSION`, creates a `vX.Y.Z` git tag, and publishes a GitHub Release with a repo zip attached. No bot push to `main` — the version bump is part of the PR itself. ## Reporting Issues From efacaacbf821ffe8fc2b8afa1ffd1f018f5047d0 Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Thu, 16 Jul 2026 12:51:44 +0300 Subject: [PATCH 07/12] AX-1736 - Apply PR review feedback: copyright headers, checkout@v5, step comments Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/release.yml | 9 ++++++++- .github/workflows/validate-version.yml | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 468c804..a144cfd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,4 @@ +# Copyright (c) JFrog Ltd. 2026 name: Release on: @@ -15,10 +16,12 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + # Checkout the repository so workflow scripts and version files are available + - uses: actions/checkout@v5 with: token: ${{ secrets.GITHUB_TOKEN }} + # Parse the commit message to determine whether this push should trigger a release - name: Detect release tag in commit message id: detect run: | @@ -29,21 +32,25 @@ jobs: echo "triggered=false" >> "$GITHUB_OUTPUT" fi + # Read the current version from the VERSION file - name: Read version if: steps.detect.outputs.triggered == 'true' id: version run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT" + # Create a semver git tag for the current version and push it to origin - name: Create and push tag if: steps.detect.outputs.triggered == 'true' run: | git tag "v${{ steps.version.outputs.version }}" git push origin "v${{ steps.version.outputs.version }}" + # Zip the repository contents (excluding .git and .github) into a release artifact - name: Package release artifact if: steps.detect.outputs.triggered == 'true' run: zip -r release.zip . --exclude ".git/*" --exclude ".github/*" + # Publish a GitHub Release for the new version tag, attaching the zip artifact and auto-generated notes - name: Create GitHub Release if: steps.detect.outputs.triggered == 'true' env: diff --git a/.github/workflows/validate-version.yml b/.github/workflows/validate-version.yml index 3d0cabf..bf9378e 100644 --- a/.github/workflows/validate-version.yml +++ b/.github/workflows/validate-version.yml @@ -1,3 +1,4 @@ +# Copyright (c) JFrog Ltd. 2026 name: Validate version on: @@ -8,7 +9,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Check version consistency run: | From b312b40fb1b7ee64bd7b761da74e2f55817cb94a Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Thu, 16 Jul 2026 17:26:45 +0300 Subject: [PATCH 08/12] AX-1736 - Sync VERSION and plugin manifests to current main (1.0.7) Releases landed on main after this branch was cut, bumping plugin.json and marketplace.json to 1.0.7. The branch was conflicting (DIRTY) because both main and our branch modified the same JSON files to different versions. Bring VERSION and both manifests in line with current main so the merge is clean. Co-Authored-By: Claude Sonnet 4.6 --- VERSION | 2 +- marketplace.json | 19 ++++++++++++++++--- plugin/.claude-plugin/plugin.json | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 21e8796..238d6e8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.3 +1.0.7 diff --git a/marketplace.json b/marketplace.json index 9c8506a..c97bf2c 100644 --- a/marketplace.json +++ b/marketplace.json @@ -9,10 +9,23 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "1.0.3", + "version": "1.0.7", "source": "plugin", - "categories": ["security", "artifact-management", "supply-chain", "devops", "mcp", "mlops", "agent-guard", "ai-catalog"], - "platforms": ["darwin", "linux", "windows"], + "categories": [ + "security", + "artifact-management", + "supply-chain", + "devops", + "mcp", + "mlops", + "agent-guard", + "ai-catalog" + ], + "platforms": [ + "darwin", + "linux", + "windows" + ], "requiredPermissions": [ "network", "shell" diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 20c9c7e..783b67c 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "jfrog", "description": "JFrog Platform integration with MCP, security skills, and supply-chain best practices", - "version": "1.0.3", + "version": "1.0.7", "author": { "name": "JFrog", "url": "https://jfrog.com" }, "hooks": "hooks/hooks.json" } From d23141624b529c3e6d137d3d9fcb9b2f31b7ce6c Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Mon, 3 Aug 2026 09:58:59 +0300 Subject: [PATCH 09/12] AX-1736 - Trigger releases from the commit subject only These repos squash-merge, and GitHub pre-fills the squash message body from the branch's commit messages (or the PR description). Both of those quote [major]/[minor]/[patch] while only documenting the flow, so matching the whole message meant merging this very PR would have cut a release nobody asked for. Matching the subject line alone keeps the trigger deliberate: a release happens when someone writes the marker in the subject they are merging, not when a marker happens to appear in generated body text. --- .github/workflows/release.yml | 11 ++++++++--- CONTRIBUTING.md | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2832bee..15dcd64 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,17 +22,22 @@ jobs: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - # Releasing is opt-in: only a merge whose commit message carries [major], [minor] or + # Releasing is opt-in: only a merge whose commit *subject* carries [major], [minor] or # [patch] cuts one. The marker is purely a trigger — the version itself always comes from # the manifest, so the bump is reviewable in the PR that makes it. + # Subject line only, deliberately. This repo squash-merges, and GitHub pre-fills the squash + # message body from the branch's commit messages (or the PR description). Either one can + # mention a marker while merely documenting it — matching the whole message would then + # release by accident on a merge nobody intended as a release. # The message is passed through env rather than interpolated into the script, so a commit # subject can never inject shell. - - name: Detect release marker in commit message + - name: Detect release marker in commit subject id: detect env: MSG: ${{ github.event.head_commit.message }} run: | - if printf '%s' "$MSG" | grep -qE '\[(major|minor|patch)\]'; then + 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" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13fabae..49dbb2b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,10 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri To cut a release: 1. In your PR, bump `.version` in [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json) and sync the matching entry in [`marketplace.json`](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]` anywhere in the commit message. +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 release workflow reads the version from `plugin.json`, creates a `vX.Y.Z` git tag, and publishes a GitHub Release with a repo zip attached. 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`. From 40b0f559432ca76e8e2bd3ad19c8d7a7cd0789e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Aug 2026 14:22:40 +0300 Subject: [PATCH 10/12] AX-1736 - Gate the release on validation, and create the tag with the release Three fixes from review, all in release.yml. An orphan tag was possible: the tag was pushed in its own step before `gh release create`, so a failure in between left a tag with no release behind it. The re-run then hit the "already exists" check with nothing actually wrong but the tag, and it needed deleting by hand. The tag is now created by `gh release create --target "$GITHUB_SHA"` in the same API call as the release, so there is no window between the two. Nothing pushes over git anymore, so the write token is gone from the checkout step. Releases were not gated on validation. The validate workflow triggers on the same push, but as a separate workflow with no relationship to this one, so it could still be red while a release went out. The same check now runs here, before the release is created. `zip -r release.zip .` packed the working tree, so anything an earlier step left on the runner would ship inside the artifact. Replaced with `git archive`, which exports tracked files at HEAD, still excluding .github. --- .github/workflows/release.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15dcd64..bbe2960 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,11 +16,11 @@ jobs: release: runs-on: ubuntu-latest steps: - # Full history so the "already released" check below can see existing tags. + # Full history (and therefore tags) so the "already released" check below can see them. + # No write token needed: nothing here pushes over git, the release API creates the tag. - uses: actions/checkout@v5 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} # Releasing is opt-in: only a merge whose commit *subject* carries [major], [minor] or # [patch] cuts one. The marker is purely a trigger — the version itself always comes from @@ -65,7 +65,8 @@ jobs: echo "version=$VERSION" >> "$GITHUB_OUTPUT" # Catches the one mistake this flow allows: merging a release marker without bumping the - # manifests, which would otherwise try to re-tag a version that already shipped. + # manifests. The tag is created as part of the release below, so a tag that already exists + # means that version genuinely shipped. - name: Refuse to re-release an existing version if: steps.detect.outputs.triggered == 'true' run: | @@ -75,17 +76,18 @@ jobs: exit 1 fi - - name: Create and push tag - if: steps.detect.outputs.triggered == 'true' - run: | - TAG="v${{ steps.version.outputs.version }}" - git tag "$TAG" - git push origin "$TAG" - + # NOTE: this repo has no plugin-layout validator to gate the release on. The manifest + # cross-check above is all there is. If a validator lands here, run it as a step at this + # point so a bad layout can't be released. + # git archive exports tracked files at HEAD, so nothing an earlier step happened to leave on + # the runner can end up in the artifact. .github is excluded to match what users get. - name: Package release artifact if: steps.detect.outputs.triggered == 'true' - run: zip -r release.zip . --exclude ".git/*" --exclude ".github/*" + run: git archive --format=zip --output=release.zip HEAD -- ':(exclude).github' + # --target creates the tag as part of creating the release, in a single API call. Pushing the + # tag separately beforehand meant a failure in between left an orphan tag with no release, and + # the re-run then tripped the "already exists" check above with nothing wrong but the tag. - name: Create GitHub Release if: steps.detect.outputs.triggered == 'true' env: @@ -93,5 +95,6 @@ jobs: run: | gh release create "v${{ steps.version.outputs.version }}" \ release.zip \ + --target "$GITHUB_SHA" \ --title "Release v${{ steps.version.outputs.version }}" \ --generate-notes From 9cb57e0109bb80558b4151e9f7fe7439666fd67b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Aug 2026 14:24:27 +0300 Subject: [PATCH 11/12] AX-1736 - Separate the no-validator note from the packaging comment --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbe2960..88b6e95 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,6 +79,7 @@ jobs: # NOTE: this repo has no plugin-layout validator to gate the release on. The manifest # cross-check above is all there is. If a validator lands here, run it as a step at this # point so a bad layout can't be released. + # git archive exports tracked files at HEAD, so nothing an earlier step happened to leave on # the runner can end up in the artifact. .github is excluded to match what users get. - name: Package release artifact From 893167a499ce5c671fe35c216fec45f54d16d626 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Aug 2026 11:11:03 +0300 Subject: [PATCH 12/12] AX-1736 - Move the release rationale out of the workflow and into CONTRIBUTING Review feedback: the per-step comments in release.yml had grown into several paragraphs of rationale, which is documentation rather than a code comment. Each step now carries at most two lines - what it does, or the one constraint a reader could otherwise undo by "simplifying" it: subject-line matching, env rather than interpolation, git archive rather than the working tree, --target creating the tag. A pointer at the top of the file sends readers to CONTRIBUTING.md for the full flow. CONTRIBUTING.md gains the parts the comments had that it did not already say: what the workflow does in order, what ends up in the release zip, why validation runs inside the release job instead of relying on the separate validate workflow, and why the tag is created by the release rather than pushed before it. No behaviour change - the release.yml diff is comments only. --- .github/workflows/release.yml | 40 ++++++++++++----------------------- CONTRIBUTING.md | 11 +++++++--- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88b6e95..b8d9842 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,7 @@ # Copyright (c) JFrog Ltd. 2026 +# +# Cuts a GitHub Release when a release marker is merged to main. +# Full flow and rationale: CONTRIBUTING.md#releasing name: Release on: @@ -16,21 +19,13 @@ jobs: release: runs-on: ubuntu-latest steps: - # Full history (and therefore tags) so the "already released" check below can see them. - # No write token needed: nothing here pushes over git, the release API creates the tag. + # Full history, so the tag check below can see existing tags. - uses: actions/checkout@v5 with: fetch-depth: 0 - # Releasing is opt-in: only a merge whose commit *subject* carries [major], [minor] or - # [patch] cuts one. The marker is purely a trigger — the version itself always comes from - # the manifest, so the bump is reviewable in the PR that makes it. - # Subject line only, deliberately. This repo squash-merges, and GitHub pre-fills the squash - # message body from the branch's commit messages (or the PR description). Either one can - # mention a marker while merely documenting it — matching the whole message would then - # release by accident on a merge nobody intended as a release. - # The message is passed through env rather than interpolated into the script, so a commit - # subject can never inject shell. + # 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: @@ -43,10 +38,8 @@ jobs: echo "triggered=false" >> "$GITHUB_OUTPUT" fi - # plugin/.claude-plugin/plugin.json is the canonical version for the plugin itself. - # marketplace.json carries its own copy for the marketplace listing, so the two are - # cross-checked here as well as by the validate-version PR check — a release is the last - # place a mismatch should get through. + # 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 @@ -64,9 +57,8 @@ jobs: fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" - # Catches the one mistake this flow allows: merging a release marker without bumping the - # manifests. The tag is created as part of the release below, so a tag that already exists - # means that version genuinely shipped. + # 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: | @@ -76,19 +68,15 @@ jobs: exit 1 fi - # NOTE: this repo has no plugin-layout validator to gate the release on. The manifest - # cross-check above is all there is. If a validator lands here, run it as a step at this - # point so a bad layout can't be released. + # NOTE: no plugin-layout validator exists in this repo to gate the release on. If one + # lands, run it as a step here. See CONTRIBUTING.md#releasing. - # git archive exports tracked files at HEAD, so nothing an earlier step happened to leave on - # the runner can end up in the artifact. .github is excluded to match what users get. + # Tracked files at HEAD only, so nothing left on the runner can end up in the zip. - name: Package release artifact if: steps.detect.outputs.triggered == 'true' run: git archive --format=zip --output=release.zip HEAD -- ':(exclude).github' - # --target creates the tag as part of creating the release, in a single API call. Pushing the - # tag separately beforehand meant a failure in between left an orphan tag with no release, and - # the re-run then tripped the "already exists" check above with nothing wrong but the tag. + # --target creates the tag as part of the release, so a failure can't leave an orphan tag. - name: Create GitHub Release if: steps.detect.outputs.triggered == 'true' env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49dbb2b..ccaebc2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,14 +18,19 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri To cut a release: 1. In your PR, bump `.version` in [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json) and sync the matching entry in [`marketplace.json`](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 +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 release workflow reads the version from `plugin.json`, creates a `vX.Y.Z` git tag, and publishes a GitHub Release with a repo zip attached. 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`. +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. -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, 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: + +- There is no plugin-layout validator in this repo, so a release is gated only on the two manifests agreeing. If a validator is added, run it as a step inside the release job as well: a separate workflow triggered by the same push can be red while a release still goes out. +- The tag is created by the release, not before it. `gh release create --target` does both in one API call, so a failed run can't leave a tag behind with no release attached to it. ## Reporting Issues