diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b8d9842 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +# 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: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + # Full history, so the tag check below can see existing tags. + - uses: actions/checkout@v5 + 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 + VERSION=$(jq -er '.version' plugin/.claude-plugin/plugin.json) + if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::plugin.json version '$VERSION' is not X.Y.Z — refusing to release" + exit 1 + fi + MARKET_VERSION=$(jq -er '.plugins[] | select(.name == "jfrog") | .version' marketplace.json) + if [ "$VERSION" != "$MARKET_VERSION" ]; then + echo "::error::plugin.json is $VERSION but marketplace.json lists $MARKET_VERSION — sync them before releasing" + exit 1 + 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' + 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 + fi + + # 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. + + # 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 the release, so a failure can't leave an orphan tag. + - 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 \ + --target "$GITHUB_SHA" \ + --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..0449104 --- /dev/null +++ b/.github/workflows/validate-version.yml @@ -0,0 +1,27 @@ +# Copyright (c) JFrog Ltd. 2026 +name: Validate version + +on: + pull_request: + branches: [main] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + # This repo has two manifests that both carry the version: the plugin's own + # plugin/.claude-plugin/plugin.json, and marketplace.json which lists it for the + # marketplace. plugin.json is canonical; this check keeps marketplace.json in step with it + # so a release can't ship two different version numbers. + - name: Check version consistency + run: | + set -euo pipefail + VERSION=$(jq -er '.version' plugin/.claude-plugin/plugin.json) + MARKET_VERSION=$(jq -er '.plugins[] | select(.name == "jfrog") | .version' marketplace.json) + if [ "$VERSION" != "$MARKET_VERSION" ]; then + echo "::error::Version mismatch: plugin/.claude-plugin/plugin.json is $VERSION but marketplace.json lists $MARKET_VERSION" + exit 1 + fi + echo "Versions consistent: $VERSION" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ccaebc2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# 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 + +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 + 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, 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 + +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.