diff --git a/.github/workflows/ext-vscode-ab-package.yml b/.github/workflows/ext-vscode-ab-package.yml index 3539377065..c75b7ce99f 100644 --- a/.github/workflows/ext-vscode-ab-package.yml +++ b/.github/workflows/ext-vscode-ab-package.yml @@ -65,8 +65,28 @@ jobs: with: node-version: 22 - - name: Build next bundle + - name: Install next workspace dependencies working-directory: next-src + run: bun install + + # @cline/* are local workspace symlinks to source packages; apps/vscode's + # `package` script does NOT build them, so without this the esbuild step + # fails on a fresh checkout. (The nightly workflow already does this.) + - name: Build SDK packages + working-directory: next-src + run: bun run build:sdk + + # Stamp the combined version into each bundle's package.json AFTER + # install and BEFORE its build: the About tab and telemetry + # extension_version read the bundle's own manifest, so without this + # the VSIX reports three different versions depending on where you + # look. (The nightly workflow gets the same alignment via nightlify.mjs.) + - name: Align next bundle version + working-directory: next-src/apps/vscode-rollout + run: node scripts/set-version.mjs --dir "$GITHUB_WORKSPACE/next-src/apps/vscode" --version "${{ github.event.inputs.version }}" + + - name: Build next bundle + working-directory: next-src/apps/vscode env: CLINE_ENVIRONMENT: production TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }} @@ -82,10 +102,7 @@ jobs: OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }} OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} - run: | - bun install - cd apps/vscode - bun run package + run: bun run package - name: Install legacy dependencies working-directory: legacy-src @@ -93,6 +110,10 @@ jobs: npm --prefix apps/vscode install --include=optional npm --prefix apps/vscode/webview-ui install --include=optional + - name: Align legacy bundle version + working-directory: next-src/apps/vscode-rollout + run: node scripts/set-version.mjs --dir "$GITHUB_WORKSPACE/legacy-src/apps/vscode" --version "${{ github.event.inputs.version }}" + - name: Build legacy bundle working-directory: legacy-src/apps/vscode env: @@ -134,15 +155,25 @@ jobs: # This workflow publishes the STABLE identity. If nightlify ever leaks # into this path the union manifest would ship under the wrong name. + # The bundle sub-manifest checks guard the set-version.mjs stamping: + # the About tab and telemetry extension_version read those files. - name: Assert stable manifest identity working-directory: staging + env: + EXPECTED_VERSION: ${{ github.event.inputs.version }} run: | node -e ' - const pkg = require("./package.json"); const assert = require("node:assert"); + const expected = process.env.EXPECTED_VERSION; + const pkg = require("./package.json"); assert.equal(pkg.name, "claude-dev", `unexpected name ${pkg.name}`); assert.equal(pkg.publisher, "saoudrizwan", `unexpected publisher ${pkg.publisher}`); - console.log(`stable identity ok: ${pkg.publisher}.${pkg.name}@${pkg.version}`); + assert.equal(pkg.version, expected, `unexpected union version ${pkg.version}`); + for (const bundle of ["next", "legacy"]) { + const sub = require(`./${bundle}/package.json`); + assert.equal(sub.version, expected, `unexpected ${bundle} bundle version ${sub.version}`); + } + console.log(`stable identity ok: ${pkg.publisher}.${pkg.name}@${pkg.version} (bundle versions aligned)`); ' - name: Package VSIX diff --git a/.github/workflows/ext-vscode-publish-nightly.yml b/.github/workflows/ext-vscode-publish-nightly.yml index 8132ba5e0b..1e4a2d260f 100644 --- a/.github/workflows/ext-vscode-publish-nightly.yml +++ b/.github/workflows/ext-vscode-publish-nightly.yml @@ -215,17 +215,26 @@ jobs: # The nightly identity must have fully propagated (nightlify -> both # bundle manifests -> union manifest) or we'd publish over the stable - # extension ID. + # extension ID. The bundle sub-manifest checks guard the version + # stamping: the About tab and telemetry extension_version read those. - name: Assert nightly manifest identity working-directory: staging + env: + EXPECTED_VERSION: ${{ steps.version.outputs.version }} run: | node -e ' - const pkg = require("./package.json"); const assert = require("node:assert"); + const expected = process.env.EXPECTED_VERSION; + const pkg = require("./package.json"); assert.equal(pkg.name, "cline-nightly", `unexpected name ${pkg.name}`); assert.equal(pkg.publisher, "saoudrizwan", `unexpected publisher ${pkg.publisher}`); - assert.equal(pkg.version, "${{ steps.version.outputs.version }}", `unexpected version ${pkg.version}`); - console.log(`nightly identity ok: ${pkg.publisher}.${pkg.name}@${pkg.version}`); + assert.equal(pkg.version, expected, `unexpected union version ${pkg.version}`); + for (const bundle of ["next", "legacy"]) { + const sub = require(`./${bundle}/package.json`); + assert.equal(sub.name, "cline-nightly", `unexpected ${bundle} bundle name ${sub.name}`); + assert.equal(sub.version, expected, `unexpected ${bundle} bundle version ${sub.version}`); + } + console.log(`nightly identity ok: ${pkg.publisher}.${pkg.name}@${pkg.version} (bundle identities aligned)`); ' - name: Install Publishing Tools @@ -267,6 +276,12 @@ jobs: - name: Tag published commit if: github.ref == 'refs/heads/main' && inputs.dry-run != true + # Best-effort bookkeeping: the default GITHUB_TOKEN cannot create a ref + # whose commit modifies workflow files (no workflows permission exists + # for it), so this step fails whenever HEAD touched .github/workflows. + # The publish already succeeded by this point — don't mark the run red; + # push the tag manually with user credentials when it matters. + continue-on-error: true working-directory: next-src env: GH_TOKEN: ${{ github.token }} diff --git a/apps/vscode-rollout/scripts/set-version.mjs b/apps/vscode-rollout/scripts/set-version.mjs new file mode 100644 index 0000000000..22bba4159f --- /dev/null +++ b/apps/vscode-rollout/scripts/set-version.mjs @@ -0,0 +1,55 @@ +/** + * Stamp the combined VSIX's version into a bundle checkout's package.json, + * in place, BEFORE that bundle builds. + * + * Why: the union manifest's version (what the Marketplace and auto-update + * see) is supplied at stitch time, but each bundle's runtime reads its OWN + * package.json — the About tab and every telemetry event's extension_version + * come from there. Without this stamp the stable combined VSIX would report + * three different versions (union input, main's base version, legacy's base + * version) depending on where you look, which turns user bug reports into + * archaeology. The nightly path gets the same alignment via nightlify.mjs + * (which also rewrites identity); this script is the identity-preserving + * version-only equivalent for the stable channel. + * + * Usage: node set-version.mjs --dir --version + */ +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +export function setPackageVersion(rawContent, version) { + if (!version) { + throw new Error("version is required"); + } + const pkg = JSON.parse(rawContent); + pkg.version = version; + return `${JSON.stringify(pkg, null, "\t")}\n`; +} + +function parseArgs(argv) { + const args = {}; + for (let i = 2; i < argv.length; i += 2) { + args[argv[i].replace(/^--/, "")] = argv[i + 1]; + } + return args; +} + +if ( + process.argv[1] && + import.meta.url === `file://${path.resolve(process.argv[1])}` +) { + const { dir, version } = parseArgs(process.argv); + if (!dir || !version) { + console.error( + "usage: node set-version.mjs --dir --version ", + ); + process.exit(1); + } + const packageJsonPath = path.join(dir, "package.json"); + const before = JSON.parse(readFileSync(packageJsonPath, "utf8")).version; + writeFileSync( + packageJsonPath, + setPackageVersion(readFileSync(packageJsonPath, "utf8"), version), + ); + console.log(`set ${packageJsonPath} version: ${before} -> ${version}`); +} diff --git a/apps/vscode-rollout/scripts/set-version.test.mjs b/apps/vscode-rollout/scripts/set-version.test.mjs new file mode 100644 index 0000000000..7b8e621875 --- /dev/null +++ b/apps/vscode-rollout/scripts/set-version.test.mjs @@ -0,0 +1,34 @@ +import { describe, expect, it } from "bun:test"; +import { setPackageVersion } from "./set-version.mjs"; + +const fixture = JSON.stringify( + { + name: "claude-dev", + displayName: "Cline", + publisher: "saoudrizwan", + version: "4.0.0", + contributes: { + commands: [{ command: "cline.plusButtonClicked", title: "New Task" }], + }, + }, + null, + "\t", +); + +describe("setPackageVersion", () => { + it("stamps the version and touches nothing else", () => { + const pkg = JSON.parse(setPackageVersion(fixture, "4.1.0")); + expect(pkg.version).toBe("4.1.0"); + expect(pkg.name).toBe("claude-dev"); + expect(pkg.displayName).toBe("Cline"); + expect(pkg.publisher).toBe("saoudrizwan"); + expect(pkg.contributes.commands[0].command).toBe( + "cline.plusButtonClicked", + ); + }); + + it("requires a version", () => { + expect(() => setPackageVersion(fixture, undefined)).toThrow(/version/); + expect(() => setPackageVersion(fixture, "")).toThrow(/version/); + }); +});