From 986f5a080db751c88892b37396713b665866ebda Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Tue, 25 Aug 2026 22:37:07 +0300 Subject: [PATCH] AX-2170 - Remove plugin versions from READMEs Versions live in manifests, tags, and GitHub Releases. CI now fails if READMEs reintroduce version strings. Co-authored-by: Cursor --- .github/workflows/validate.yml | 5 ++ README.md | 8 +-- scripts/validate-readme-no-version.mjs | 78 ++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 scripts/validate-readme-no-version.mjs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a115d20..252df93 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,3 +22,8 @@ jobs: - name: Validate plugin layout run: node scripts/validate-devin-plugin.mjs + + - name: Validate README has no plugin version + env: + PLUGIN_MANIFEST: .devin-plugin/plugin.json + run: node scripts/validate-readme-no-version.mjs diff --git a/README.md b/README.md index a63bba3..f842184 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ JFrog plugin for [Devin](https://devin.ai/): JFrog Platform skills for artifact management, security scanning, and supply-chain workflows, plus the JFrog Platform MCP server (remote HTTP + OAuth). -> **Current version:** `0.3.0` — skills from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) (pinned at `v0.25.0`) and a bundled JFrog MCP entry. +> Skills from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) (pin in [`.github/scripts/sync-skills-vendor.json`](.github/scripts/sync-skills-vendor.json)) and a bundled JFrog MCP entry. Plugin releases are tagged on GitHub — do not duplicate version numbers here. ## Skills @@ -44,7 +44,7 @@ The plugin registers this MCP server (declared in `mcp.json` and referenced from - **Devin CLI** — see [Devin docs](https://docs.devin.ai/) - **Devin CLI plugins enabled** for your organization (`devin plugins install` must be allowed) - **`JFROG_PLATFORM_URL`** — JFrog platform host only (no `https://`, no trailing `/`). Required for the bundled MCP entry. -- **Skill runtime** (when using the skills) — `jf` CLI, `jq`, and `curl` on `PATH`, plus a configured JFrog instance (`jf config add`). See [jfrog-skills requirements](https://github.com/jfrog/jfrog-skills/blob/v0.25.0/README.md#requirements). +- **Skill runtime** (when using the skills) — `jf` CLI, `jq`, and `curl` on `PATH`, plus a configured JFrog instance (`jf config add`). See [jfrog-skills requirements](https://github.com/jfrog/jfrog-skills/blob/main/README.md#requirements). ## Installation @@ -95,9 +95,9 @@ devin-plugin/ node scripts/validate-devin-plugin.mjs ``` -## Versioning +## Releasing -Bump `version` in [`.devin-plugin/plugin.json`](.devin-plugin/plugin.json) when you publish a new release, then tag (for example `v0.3.0`). +Bump `version` in [`.devin-plugin/plugin.json`](.devin-plugin/plugin.json) when you publish a new release, then tag on GitHub. See [`.github/workflows/release.yml`](.github/workflows/release.yml). ## License diff --git a/scripts/validate-readme-no-version.mjs b/scripts/validate-readme-no-version.mjs new file mode 100644 index 0000000..513ce95 --- /dev/null +++ b/scripts/validate-readme-no-version.mjs @@ -0,0 +1,78 @@ +#!/usr/bin/env node + +// Copyright (c) JFrog Ltd. 2026 +// Licensed under the Apache License, Version 2.0 +// https://www.apache.org/licenses/LICENSE-2.0 + +import { readFileSync, existsSync } from "node:fs"; +import process from "node:process"; + +const docPaths = (process.env.DOCS || process.env.README_PATH || "README.md") + .split(/\s+/) + .filter(Boolean); + +const errors = []; + +for (const docPath of docPaths) { + const content = readFileSync(docPath, "utf8"); + validateDoc(docPath, content, errors); +} + +function validateDoc(docPath, content, errors) { + +const bannedPatterns = [ + { + re: /current version/i, + msg: 'README must not include a "Current version" callout — use GitHub Releases/tags.', + }, + { + re: /^## Versioning\s*$/m, + msg: 'README must not include a "## Versioning" section — versions live in the manifest and GitHub Releases.', + }, + { + re: /then tag \(for example `v/i, + msg: "README must not include example release tags.", + }, + { + re: /github\.com\/jfrog\/jfrog-skills\/blob\/v\d+\.\d+\.\d+/i, + msg: "README must not pin jfrog-skills doc links to a release tag — use main README or sync-skills-vendor.json.", + }, + { + re: /codeload\.github\.com\/jfrog\/jfrog-skills\/(tar\.gz|zip)\/v\d+\.\d+\.\d+/i, + msg: "README must not embed jfrog-skills release tags in download URLs.", + }, +]; + + for (const { re, msg } of bannedPatterns) { + if (re.test(content)) { + errors.push(`${docPath}: ${msg}`); + } + } + + const manifestPath = process.env.PLUGIN_MANIFEST; + if (docPath.endsWith("README.md") && manifestPath && existsSync(manifestPath)) { + let version; + if (manifestPath.endsWith(".json")) { + version = JSON.parse(readFileSync(manifestPath, "utf8")).version; + } else if (manifestPath.endsWith("gradle.properties")) { + const match = readFileSync(manifestPath, "utf8").match(/^version\s*=\s*(.+)$/m); + version = match?.[1]?.trim(); + } + + if (version && content.includes(version)) { + errors.push( + `${docPath}: contains plugin version "${version}" — authoritative source is ${manifestPath}.` + ); + } + } +} + +if (errors.length > 0) { + console.error("README version validation failed:"); + for (const error of errors) { + console.error(`- ${error}`); + } + process.exit(1); +} + +console.log("README version validation passed.");