From 620732c2c153af4da49db282c2dc8bbebf317058 Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 12 May 2026 18:13:21 -0600 Subject: [PATCH 1/7] docs: add design spec for @wolfcola/changeset-sync-manifest CLI package that syncs package.json version into manifest.json after changeset version, so CWS publishes show real versions. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...26-05-12-changeset-sync-manifest-design.md | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-12-changeset-sync-manifest-design.md diff --git a/docs/superpowers/specs/2026-05-12-changeset-sync-manifest-design.md b/docs/superpowers/specs/2026-05-12-changeset-sync-manifest-design.md new file mode 100644 index 0000000..b787b96 --- /dev/null +++ b/docs/superpowers/specs/2026-05-12-changeset-sync-manifest-design.md @@ -0,0 +1,62 @@ +# @wolfcola/changeset-sync-manifest Design + +**Problem:** The Chrome extension `manifest.json` version is hardcoded at `0.1.0` and never bumped by changesets. Every CWS publish overwrites the same display version. + +**Solution:** A small CLI package that runs after `changeset version` and copies the `package.json` version into `manifest.json` for a given package directory. + +## Package + +- **Name:** `@wolfcola/changeset-sync-manifest` +- **Location:** `packages/changeset-sync-manifest` +- **Private:** yes + +### Files + +| File | Purpose | +| ---------------------- | -------------------------------------------------------------------- | +| `package.json` | Package manifest with `bin` entry | +| `bin/sync-manifest.js` | Compiled CLI entry point | +| `src/sync.ts` | Pure function: read `package.json` version, write to `manifest.json` | +| `src/sync.test.ts` | Unit tests | + +### CLI Interface + +``` +sync-manifest +``` + +- `` — path to a package directory containing both `package.json` and `manifest.json` +- Reads `/package.json` → extracts `version` +- Reads `/manifest.json` → sets `version` field → writes back +- Exits non-zero if either file is missing or JSON is malformed + +### Pure Function + +```ts +syncManifestVersion(dir: string): void +``` + +Reads `package.json` and `manifest.json` from `dir`, copies the version, writes `manifest.json` back with the updated version. Preserves existing formatting (2-space indent, trailing newline). + +## Integration + +### Changesets config (`.changeset/config.json`) + +- Add `"privatePackages": { "version": true }` so changesets bumps private packages +- Remove `@wolfcola/devtools-extension` from `ignore` so it participates in the `@wolfcola/*` fixed group + +### Version script (root `package.json`) + +``` +"version": "changeset version && sync-manifest packages/devtools-extension && prettier --write '**/package.json' pnpm-workspace.yaml" +``` + +### Build pipeline (unchanged) + +`build.mjs` continues reading `manifest.json` and calling `stampVersion()` to append the CI build number as the 4th version segment. The only difference is that the base version in `manifest.json` now reflects the real package version instead of a hardcoded `0.1.0`. + +## What this does NOT do + +- Does not handle the VS Code extension (its `package.json` is its manifest — changesets handles it directly if removed from `ignore`) +- Does not scan the workspace automatically — takes an explicit directory argument +- Does not handle jsonpath or arbitrary file targets — just `package.json` → `manifest.json` version sync From c511d76ecad67aa2f2bca831f92aa2f55ed352c2 Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 12 May 2026 18:20:18 -0600 Subject: [PATCH 2/7] chore: scaffold @wolfcola/changeset-sync-manifest package Co-Authored-By: Claude Sonnet 4.6 --- packages/changeset-sync-manifest/package.json | 24 +++++++++++++++++++ .../changeset-sync-manifest/tsconfig.json | 19 +++++++++++++++ .../changeset-sync-manifest/tsconfig.lib.json | 6 +++++ .../changeset-sync-manifest/vitest.config.mts | 14 +++++++++++ 4 files changed, 63 insertions(+) create mode 100644 packages/changeset-sync-manifest/package.json create mode 100644 packages/changeset-sync-manifest/tsconfig.json create mode 100644 packages/changeset-sync-manifest/tsconfig.lib.json create mode 100644 packages/changeset-sync-manifest/vitest.config.mts diff --git a/packages/changeset-sync-manifest/package.json b/packages/changeset-sync-manifest/package.json new file mode 100644 index 0000000..903b785 --- /dev/null +++ b/packages/changeset-sync-manifest/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wolfcola/changeset-sync-manifest", + "version": "0.0.0", + "description": "Sync package.json version into manifest.json after changeset version", + "license": "MIT", + "type": "module", + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/ryanbas21/devtools.git", + "directory": "packages/changeset-sync-manifest" + }, + "bin": { + "sync-manifest": "./dist/bin.js" + }, + "scripts": { + "build": "tsc -p tsconfig.lib.json", + "lint": "eslint .", + "test": "vitest run" + }, + "devDependencies": { + "vitest": "catalog:vitest" + } +} diff --git a/packages/changeset-sync-manifest/tsconfig.json b/packages/changeset-sync-manifest/tsconfig.json new file mode 100644 index 0000000..f6a4a19 --- /dev/null +++ b/packages/changeset-sync-manifest/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", + "module": "nodenext", + "moduleResolution": "nodenext", + "verbatimModuleSyntax": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [], + "exclude": ["vitest.config.mts", "src/**/*.test.ts"] +} diff --git a/packages/changeset-sync-manifest/tsconfig.lib.json b/packages/changeset-sync-manifest/tsconfig.lib.json new file mode 100644 index 0000000..2d042cc --- /dev/null +++ b/packages/changeset-sync-manifest/tsconfig.lib.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "emitDeclarationOnly": false + } +} diff --git a/packages/changeset-sync-manifest/vitest.config.mts b/packages/changeset-sync-manifest/vitest.config.mts new file mode 100644 index 0000000..b107878 --- /dev/null +++ b/packages/changeset-sync-manifest/vitest.config.mts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/changeset-sync-manifest', + test: { + name: 'changeset-sync-manifest', + watch: false, + globals: true, + environment: 'node', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + }, +})); From a484e21f8bfc7f2bc526dc2abb1bab9f7c6950b2 Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 12 May 2026 18:21:57 -0600 Subject: [PATCH 3/7] feat(changeset-sync-manifest): add syncManifestVersion function with tests Co-Authored-By: Claude Sonnet 4.6 --- .../changeset-sync-manifest/src/sync.test.ts | 75 +++++++++++++++++++ packages/changeset-sync-manifest/src/sync.ts | 31 ++++++++ 2 files changed, 106 insertions(+) create mode 100644 packages/changeset-sync-manifest/src/sync.test.ts create mode 100644 packages/changeset-sync-manifest/src/sync.ts diff --git a/packages/changeset-sync-manifest/src/sync.test.ts b/packages/changeset-sync-manifest/src/sync.test.ts new file mode 100644 index 0000000..e311771 --- /dev/null +++ b/packages/changeset-sync-manifest/src/sync.test.ts @@ -0,0 +1,75 @@ +import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { syncManifestVersion } from './sync.js'; + +describe('syncManifestVersion', () => { + let dir: string; + + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), 'sync-manifest-')); + }); + + afterEach(() => { + rmSync(dir, { recursive: true, force: true }); + }); + + it('copies version from package.json to manifest.json', () => { + writeFileSync(join(dir, 'package.json'), JSON.stringify({ version: '2.0.0' })); + writeFileSync( + join(dir, 'manifest.json'), + JSON.stringify({ manifest_version: 3, version: '0.1.0' }), + ); + + syncManifestVersion(dir); + + const manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8')); + expect(manifest.version).toBe('2.0.0'); + }); + + it('preserves other manifest fields', () => { + writeFileSync(join(dir, 'package.json'), JSON.stringify({ version: '1.0.0' })); + writeFileSync( + join(dir, 'manifest.json'), + JSON.stringify({ manifest_version: 3, name: 'My Extension', version: '0.1.0' }), + ); + + syncManifestVersion(dir); + + const manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8')); + expect(manifest.manifest_version).toBe(3); + expect(manifest.name).toBe('My Extension'); + expect(manifest.version).toBe('1.0.0'); + }); + + it('writes with 2-space indent and trailing newline', () => { + writeFileSync(join(dir, 'package.json'), JSON.stringify({ version: '1.0.0' })); + writeFileSync(join(dir, 'manifest.json'), JSON.stringify({ version: '0.1.0' })); + + syncManifestVersion(dir); + + const raw = readFileSync(join(dir, 'manifest.json'), 'utf8'); + expect(raw).toContain(' "version"'); + expect(raw.endsWith('\n')).toBe(true); + }); + + it('throws if package.json is missing', () => { + writeFileSync(join(dir, 'manifest.json'), JSON.stringify({ version: '0.1.0' })); + + expect(() => syncManifestVersion(dir)).toThrow('package.json'); + }); + + it('throws if manifest.json is missing', () => { + writeFileSync(join(dir, 'package.json'), JSON.stringify({ version: '1.0.0' })); + + expect(() => syncManifestVersion(dir)).toThrow('manifest.json'); + }); + + it('throws if package.json has no version field', () => { + writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'test' })); + writeFileSync(join(dir, 'manifest.json'), JSON.stringify({ version: '0.1.0' })); + + expect(() => syncManifestVersion(dir)).toThrow('version'); + }); +}); diff --git a/packages/changeset-sync-manifest/src/sync.ts b/packages/changeset-sync-manifest/src/sync.ts new file mode 100644 index 0000000..530acdd --- /dev/null +++ b/packages/changeset-sync-manifest/src/sync.ts @@ -0,0 +1,31 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +export const syncManifestVersion = (dir: string): void => { + const pkgPath = join(dir, 'package.json'); + const manifestPath = join(dir, 'manifest.json'); + + let pkgRaw: string; + try { + pkgRaw = readFileSync(pkgPath, 'utf8'); + } catch { + throw new Error(`Cannot read package.json at ${pkgPath}`); + } + + let manifestRaw: string; + try { + manifestRaw = readFileSync(manifestPath, 'utf8'); + } catch { + throw new Error(`Cannot read manifest.json at ${manifestPath}`); + } + + const pkg = JSON.parse(pkgRaw) as { version?: string }; + if (!pkg.version) { + throw new Error(`No version field in ${pkgPath}`); + } + + const manifest = JSON.parse(manifestRaw) as Record; + manifest.version = pkg.version; + + writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n'); +}; From 5a91d7d5a1640654d7a705fdae7a417aaff9b4bd Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 12 May 2026 18:23:42 -0600 Subject: [PATCH 4/7] feat(changeset-sync-manifest): add CLI entry point Co-Authored-By: Claude Sonnet 4.6 --- packages/changeset-sync-manifest/src/bin.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/changeset-sync-manifest/src/bin.ts diff --git a/packages/changeset-sync-manifest/src/bin.ts b/packages/changeset-sync-manifest/src/bin.ts new file mode 100644 index 0000000..bba8e03 --- /dev/null +++ b/packages/changeset-sync-manifest/src/bin.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node +import { resolve } from 'node:path'; +import { syncManifestVersion } from './sync.js'; + +const dir = process.argv[2]; + +if (!dir) { + console.error('Usage: sync-manifest '); + process.exit(1); +} + +try { + syncManifestVersion(resolve(dir)); + console.log(`Synced manifest.json version in ${dir}`); +} catch (e) { + console.error(String(e instanceof Error ? e.message : e)); + process.exit(1); +} From 95b7a50cdb2b2d7e0d215979e2e13b24f95d23ac Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 12 May 2026 18:24:55 -0600 Subject: [PATCH 5/7] feat: wire sync-manifest into changesets version flow - Add privatePackages.version to changeset config - Chain sync-manifest after changeset version in root version script - Add project reference to root tsconfig Co-Authored-By: Claude Sonnet 4.6 --- .changeset/config.json | 5 ++++- package.json | 2 +- tsconfig.json | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index caead5d..bb66bd8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,5 +7,8 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["oidc-devtools", "@wolfcola/e2e", "@wolfcola/devtools-ui"] + "ignore": ["oidc-devtools", "@wolfcola/e2e", "@wolfcola/devtools-ui"], + "privatePackages": { + "version": true + } } diff --git a/package.json b/package.json index 26d089d..436593f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "test": "vitest run", "typecheck": "tsc --build", "changeset": "changeset", - "version": "changeset version && prettier --write '**/package.json' pnpm-workspace.yaml", + "version": "changeset version && sync-manifest packages/devtools-extension && prettier --write '**/package.json' pnpm-workspace.yaml", "release": "pnpm build && changeset publish", "syncpack:lint": "syncpack lint", "syncpack:fix": "syncpack fix-mismatches", diff --git a/tsconfig.json b/tsconfig.json index 91b66bc..3840608 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ { "path": "packages/devtools-extension" }, { "path": "packages/vscode-extension" }, { "path": "packages/treeshake-check" }, - { "path": "packages/eslint-plugin-treeshake" } + { "path": "packages/eslint-plugin-treeshake" }, + { "path": "packages/changeset-sync-manifest" } ] } From b4757bcf7060010938db05b3ab7e7435a1d5a418 Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 12 May 2026 18:31:44 -0600 Subject: [PATCH 6/7] fix: add changeset-sync-manifest as root devDependency Hoists the sync-manifest bin to node_modules/.bin so it's available in the version script. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 1 + pnpm-lock.yaml | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/package.json b/package.json index 436593f..0920d65 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "syncpack": "^15.1.2", "typescript": "5.8.3", "vite": "catalog:vite", + "@wolfcola/changeset-sync-manifest": "workspace:*", "vitest": "catalog:vitest" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7139fce..e2e6ad5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,9 @@ importers: '@typescript-eslint/parser': specifier: ^8.45.0 version: 8.59.2(eslint@9.39.4)(typescript@5.8.3) + '@wolfcola/changeset-sync-manifest': + specifier: workspace:* + version: link:packages/changeset-sync-manifest eslint: specifier: ^9.8.0 version: 9.39.4 @@ -137,6 +140,12 @@ importers: specifier: catalog:vitest version: 3.2.4(@types/node@22.19.18)(jsdom@26.1.0)(terser@5.47.1)(yaml@2.9.0) + packages/changeset-sync-manifest: + devDependencies: + vitest: + specifier: catalog:vitest + version: 3.2.4(@types/node@22.19.18)(jsdom@26.1.0)(terser@5.47.1)(yaml@2.9.0) + packages/devtools-bridge: dependencies: '@forgerock/davinci-client': From f4fcc753d18750443e09550dc4f069b01f2362f3 Mon Sep 17 00:00:00 2001 From: Ryan Bas Date: Tue, 12 May 2026 18:33:07 -0600 Subject: [PATCH 7/7] chore: add changeset for manifest version sync Co-Authored-By: Claude Opus 4.6 (1M context) --- .changeset/sync-manifest-version.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/sync-manifest-version.md diff --git a/.changeset/sync-manifest-version.md b/.changeset/sync-manifest-version.md new file mode 100644 index 0000000..0f11bac --- /dev/null +++ b/.changeset/sync-manifest-version.md @@ -0,0 +1,7 @@ +--- +'@wolfcola/devtools-extension': patch +--- + +Automate manifest.json version sync: after `changeset version` bumps +package.json, the new `sync-manifest` CLI copies the version into +manifest.json so Chrome Web Store publishes show real version numbers.