From ffaa3a871253f92acc91a4e11b093e5769b570c4 Mon Sep 17 00:00:00 2001 From: Tony Ketcham Date: Sat, 15 Aug 2026 15:54:04 -0700 Subject: [PATCH] release: bump all public packages to 1.0.1 Keep the public package set on one release version. Make the bump command update every public manifest together and stop publishing before npm receives any package when versions differ. Test plan: - pnpm verify Co-authored-by: Cursor Change-Id: I8244e2195ce2d20b0310e7eb7feae51d9a994f5c --- .agents/skills/proof/release.json | 6 +- ...-one-lockstep-version--0c4eg8frxys4fv2s.md | 9 +++ CONTRIBUTING.md | 30 +++++----- packages/codegen/package.json | 2 +- packages/config/package.json | 2 +- packages/core/package.json | 2 +- packages/explorer/package.json | 2 +- packages/flatbread/package.json | 2 +- packages/proof/package.json | 2 +- packages/proof/skills/proof/release.json | 6 +- packages/resolver-svimg/package.json | 2 +- packages/source-filesystem/package.json | 2 +- packages/transformer-markdown/package.json | 2 +- packages/transformer-yaml/package.json | 2 +- packages/utils/package.json | 2 +- scripts/bumpVersions.ts | 59 ++++++++----------- scripts/publish.test.ts | 34 +++++++++++ scripts/publish.ts | 54 +++++++++++++++-- 18 files changed, 148 insertions(+), 72 deletions(-) create mode 100644 .flatbread-proof/constraints/con-public-npm-releases-use-one-lockstep-version--0c4eg8frxys4fv2s.md diff --git a/.agents/skills/proof/release.json b/.agents/skills/proof/release.json index 882486f6..1158a213 100644 --- a/.agents/skills/proof/release.json +++ b/.agents/skills/proof/release.json @@ -1,6 +1,6 @@ { "format": 1, - "flatbreadVersion": "1.0.0", - "proofVersion": "1.0.0", - "gitTag": "v1.0.0" + "flatbreadVersion": "1.0.1", + "proofVersion": "1.0.1", + "gitTag": "v1.0.1" } diff --git a/.flatbread-proof/constraints/con-public-npm-releases-use-one-lockstep-version--0c4eg8frxys4fv2s.md b/.flatbread-proof/constraints/con-public-npm-releases-use-one-lockstep-version--0c4eg8frxys4fv2s.md new file mode 100644 index 00000000..1744e22e --- /dev/null +++ b/.flatbread-proof/constraints/con-public-npm-releases-use-one-lockstep-version--0c4eg8frxys4fv2s.md @@ -0,0 +1,9 @@ +--- +id: con-public-npm-releases-use-one-lockstep-version--0c4eg8frxys4fv2s +effort: eff-proof-and-contributor-operating-system--ahhgtafvdhg4dfve +title: Public npm releases use one lockstep version +kind: hard +created_at: '2026-08-15T23:14:13.605Z' +--- + +Every public package in the Flatbread monorepo must declare the same version before any package in a release is published. The bump command updates all public package manifests in one operation, and the publish preflight must reject missing or mixed versions before it calls npm publish. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4cee09f2..7ef90c87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,15 +90,14 @@ https://github.com/FlatbreadLabs/oven. There are two steps: -1. Bump versions where there are changes -2. Publish the changed packages +1. Bump every public package to one version +2. Publish the release -### One-time note for 1.0 +### Lockstep versions -The 1.0 release put every published package on `1.0.0` at once, rather than -letting each package carry its own alpha number. `pnpm bump` still works the -same way: it preselects the packages that changed plus their workspace -dependents. Only bump every package together again when a release calls for it. +Every public package shares one version. A release bumps the whole set even +when only one package changed. This keeps package combinations, the Proof skill +manifest, and the Git tag tied to one release. `packages/proof/skills/proof/release.json` records the version an end user installs. Edit that file, not the copy in `.agents/`. Then run @@ -107,7 +106,7 @@ which fails unless `flatbreadVersion` and `proofVersion` match the current `package.json` versions and `gitTag` equals `v`. `pnpm verify` runs both checks. -### 1) Bump versions only where there are changes +### 1) Bump every public package Use the interactive bump script: @@ -117,14 +116,14 @@ pnpm bump What the script does: -- Detects changes since last publish per package by: +- Detects whether any public package changed since the last publish by: - Querying npm for the package's latest published version and its publish time - Comparing git commits in `packages/` since that time - Ignoring commits that only change the `version` field in `package.json` - Skipping packages that are not yet published on npm -- Preselects changed packages and their workspace dependents for you to bump -- Required workspace dependents must remain selected when a changed dependency is selected -- Runs `pnpm bumpp --no-commit --no-push --no-tag` in each selected package directory +- Passes every public package manifest to one `bumpp` command so one chosen + version is written across the set +- Stops before publishing if any public package version differs Notes: @@ -132,7 +131,7 @@ Notes: ```bash git add packages/**/package.json - git commit -m "release: bump versions for changed packages" + git commit -m "release: bump public packages" ``` - Debugging: set `FLATBREAD_BUMP_DEBUG=1` to see detection details @@ -141,7 +140,7 @@ Notes: FLATBREAD_BUMP_DEBUG=1 pnpm bump ``` -- New (unpublished) packages: these are excluded from the bump prompt. Ensure their `package.json` has the desired starting version before publishing (see below). +- New public packages join the same version as the rest of the release set. ### 2) Publish packages @@ -162,7 +161,8 @@ skill files and package contents first. It then publishes ordinary packages, `@flatbread/proof`, and finally `flatbread`, stopping at the first failure. -Publish all public packages (the script builds first and then attempts to publish each package): +Publish all public packages (the script checks for one shared version, builds, +then attempts to publish each package): ```bash pnpm publish:ci diff --git a/packages/codegen/package.json b/packages/codegen/package.json index 76f11c1a..02bc355c 100644 --- a/packages/codegen/package.json +++ b/packages/codegen/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/codegen", - "version": "1.0.0", + "version": "1.0.1", "description": "Generates TypeScript types, typed document nodes, and a prototype read API from a Flatbread content model.", "type": "module", "scripts": { diff --git a/packages/config/package.json b/packages/config/package.json index 34c97921..9945d061 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/config", - "version": "1.0.0", + "version": "1.0.1", "description": "Loads, validates, and types flatbread.config.* files. Provides defineConfig().", "type": "module", "publishConfig": { diff --git a/packages/core/package.json b/packages/core/package.json index 61431888..b733b38b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/core", - "version": "1.0.0", + "version": "1.0.1", "description": "Flatbread's engine: runs source and transformer plugins, validates records and refs, and builds the GraphQL schema plus JSON and CSV snapshots.", "type": "module", "scripts": { diff --git a/packages/explorer/package.json b/packages/explorer/package.json index 23adf6d5..743011ee 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/explorer", - "version": "1.0.0", + "version": "1.0.1", "description": "Single-page app for browsing a Flatbread content graph, served by flatbread start. Ships an Proof preset.", "type": "module", "scripts": { diff --git a/packages/flatbread/package.json b/packages/flatbread/package.json index 4531ad23..40193ce8 100644 --- a/packages/flatbread/package.json +++ b/packages/flatbread/package.json @@ -1,6 +1,6 @@ { "name": "flatbread", - "version": "1.0.0", + "version": "1.0.1", "description": "Git-native memory for coding agents and relational content for TypeScript apps. Files in your repo become a typed graph you read over GraphQL, generated TypeScript, or the CLI.", "type": "module", "scripts": { diff --git a/packages/proof/package.json b/packages/proof/package.json index 61eda4ca..75869d50 100644 --- a/packages/proof/package.json +++ b/packages/proof/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/proof", - "version": "1.0.0", + "version": "1.0.1", "description": "Git-native memory records for coding agents. Writes typed Effort, Issue, Finding, Decision, Constraint, Risk, Citation, and Blob records to Markdown as journaled changes, and reads them back in bounded pages.", "type": "module", "scripts": { diff --git a/packages/proof/skills/proof/release.json b/packages/proof/skills/proof/release.json index 882486f6..1158a213 100644 --- a/packages/proof/skills/proof/release.json +++ b/packages/proof/skills/proof/release.json @@ -1,6 +1,6 @@ { "format": 1, - "flatbreadVersion": "1.0.0", - "proofVersion": "1.0.0", - "gitTag": "v1.0.0" + "flatbreadVersion": "1.0.1", + "proofVersion": "1.0.1", + "gitTag": "v1.0.1" } diff --git a/packages/resolver-svimg/package.json b/packages/resolver-svimg/package.json index efa05206..39a96c63 100644 --- a/packages/resolver-svimg/package.json +++ b/packages/resolver-svimg/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/resolver-svimg", - "version": "1.0.0", + "version": "1.0.1", "description": "Flatbread field resolver that turns an image path into svimg attributes: responsive srcsets, WebP and AVIF variants, and a blurred placeholder.", "type": "module", "repository": { diff --git a/packages/source-filesystem/package.json b/packages/source-filesystem/package.json index 703a0b2b..7ff994ca 100644 --- a/packages/source-filesystem/package.json +++ b/packages/source-filesystem/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/source-filesystem", - "version": "1.0.0", + "version": "1.0.1", "description": "Flatbread source plugin that reads content files from directories on disk.", "type": "module", "repository": { diff --git a/packages/transformer-markdown/package.json b/packages/transformer-markdown/package.json index da9180e2..6b3c2e0e 100644 --- a/packages/transformer-markdown/package.json +++ b/packages/transformer-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/transformer-markdown", - "version": "1.0.0", + "version": "1.0.1", "description": "Flatbread transformer plugin that parses Markdown frontmatter and body into content records.", "type": "module", "repository": { diff --git a/packages/transformer-yaml/package.json b/packages/transformer-yaml/package.json index b61e73e9..9386f605 100644 --- a/packages/transformer-yaml/package.json +++ b/packages/transformer-yaml/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/transformer-yaml", - "version": "1.0.0", + "version": "1.0.1", "description": "Flatbread transformer plugin that parses YAML files into content records.", "type": "module", "repository": { diff --git a/packages/utils/package.json b/packages/utils/package.json index d1fbc056..dfd6c40e 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@flatbread/utils", - "version": "1.0.0", + "version": "1.0.1", "description": "Shared helpers for Flatbread packages, including package manager and lockfile detection.", "type": "module", "scripts": { diff --git a/scripts/bumpVersions.ts b/scripts/bumpVersions.ts index ee1d3dad..e1526a9a 100644 --- a/scripts/bumpVersions.ts +++ b/scripts/bumpVersions.ts @@ -1,6 +1,5 @@ -import { execSync } from 'child_process'; +import { execFileSync, execSync } from 'child_process'; import { promises as fs } from 'node:fs'; -import inquirer from 'inquirer'; import colors from 'kleur'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; @@ -318,40 +317,30 @@ async function main(): Promise { return; } - const { selectedPackages }: Record = - await inquirer.prompt([ - { - type: 'checkbox', - name: 'selectedPackages', - message: - 'Packages changed since last publish (including workspace dependents). Select which to bump:', - choices: changedPackages.map((pkg) => ({ - name: `${getPkgName(pkg)}`, - value: pkg, - checked: true, - })), - }, - ]); - const missingDependents = validateBumpSelection( - allPackages, - changedPackages.map(getPkgName), - selectedPackages.map(getPkgName) + console.log( + colors + .bold() + .yellow( + `Changes detected in: ${changedPackages.map(getPkgName).join(', ')}` + ) + ); + console.log( + colors.bold().yellow('Bumping every public package as one release') + ); + execFileSync( + 'pnpm', + [ + 'exec', + 'bumpp', + '--no-commit', + '--no-push', + '--no-tag', + ...allPackages.map((pkg) => + path.join('packages', pkg.dirName, 'package.json') + ), + ], + { stdio: 'inherit' } ); - if (missingDependents.length > 0) { - throw new Error( - `Cannot deselect required workspace dependents: ${missingDependents.join( - ', ' - )}` - ); - } - - for (const selectedPackage of selectedPackages) { - console.log(colors.bold(colors.yellow(`Bumping ${selectedPackage.name}`))); - execSync('pnpm bumpp --no-commit --no-push --no-tag', { - stdio: 'inherit', - cwd: path.resolve(path.join('packages', selectedPackage.dirName)), - }); - } const flatbreadManifest = JSON.parse( await fs.readFile('packages/flatbread/package.json', 'utf8') diff --git a/scripts/publish.test.ts b/scripts/publish.test.ts index 22d94144..e0d8a285 100644 --- a/scripts/publish.test.ts +++ b/scripts/publish.test.ts @@ -1,5 +1,6 @@ import test from 'ava'; import { + assertLockstepVersions, classifyNpmViewResult, parseNpmViewVersion, sortPackages, @@ -56,6 +57,39 @@ test('publish ordering rejects local dependency cycles', (t) => { t.regex(error?.message ?? '', /@flatbread\/a/); }); +test('publish preflight accepts one shared public package version', (t) => { + t.is( + assertLockstepVersions([ + { name: '@flatbread/core', dirName: 'core', version: '1.0.1' }, + { name: 'flatbread', dirName: 'flatbread', version: '1.0.1' }, + ]), + '1.0.1' + ); +}); + +test('publish preflight rejects fragmented public package versions', (t) => { + const error = t.throws(() => + assertLockstepVersions([ + { name: '@flatbread/core', dirName: 'core', version: '1.0.0' }, + { name: 'flatbread', dirName: 'flatbread', version: '1.0.1' }, + ]) + ); + t.regex(error?.message ?? '', /one version across every public package/); + t.regex(error?.message ?? '', /1\.0\.0: @flatbread\/core/); + t.regex(error?.message ?? '', /1\.0\.1: flatbread/); +}); + +test('publish preflight rejects a public package without a version', (t) => { + const error = t.throws(() => + assertLockstepVersions([ + { name: '@flatbread/core', dirName: 'core' }, + { name: 'flatbread', dirName: 'flatbread', version: '1.0.1' }, + ]) + ); + t.regex(error?.message ?? '', /must declare a version/); + t.regex(error?.message ?? '', /@flatbread\/core/); +}); + test('npm view preflight recognizes an exact published version', (t) => { t.is(parseNpmViewVersion('"1.0.0-alpha.1"\n'), '1.0.0-alpha.1'); t.is( diff --git a/scripts/publish.ts b/scripts/publish.ts index 932ce9c7..6c6125a6 100644 --- a/scripts/publish.ts +++ b/scripts/publish.ts @@ -12,7 +12,7 @@ export type NpmViewResult = { export type PreflightStatus = 'publish' | 'already-published'; -type PublishPackage = { +export type PublishPackage = { name: string; dirName: string; version?: string; @@ -130,6 +130,46 @@ export function sortPackages(packages: T[]): T[] { return result; } +export function assertLockstepVersions( + packages: readonly PublishPackage[] +): string { + const missingVersions = packages + .filter((pkg) => !pkg.version) + .map((pkg) => pkg.name) + .sort(); + if (missingVersions.length > 0) { + throw new Error( + `Every public package must declare a version. Missing: ${missingVersions.join( + ', ' + )}` + ); + } + + const versions = new Map(); + for (const pkg of packages) { + const names = versions.get(pkg.version!) ?? []; + names.push(pkg.name); + versions.set(pkg.version!, names); + } + if (versions.size !== 1) { + const groups = [...versions.entries()] + .sort(([left], [right]) => left.localeCompare(right)) + .map( + ([version, names]) => + `${version}: ${names + .sort((left, right) => left.localeCompare(right)) + .join(', ')}` + ); + throw new Error( + `Release requires one version across every public package:\n${groups.join( + '\n' + )}` + ); + } + + return versions.keys().next().value!; +} + function comparePackageNames( left: PublishPackage, right: PublishPackage @@ -183,13 +223,17 @@ export function preflightPackage( export async function publishPackages(): Promise { const releaseSha = assertCleanRelease(); - execSync('pnpm run build', { stdio: 'inherit' }); - execSync('pnpm run skills:check', { stdio: 'inherit' }); - execSync('pnpm run skills:pack-check', { stdio: 'inherit' }); - const packages = sortPackages( (await getMonorepoPublicPackages()) as unknown as PublishPackage[] ); + const releaseVersion = assertLockstepVersions(packages); + console.log( + colors.bold().green(`Public package release version: ${releaseVersion}`) + ); + + execSync('pnpm run build', { stdio: 'inherit' }); + execSync('pnpm run skills:check', { stdio: 'inherit' }); + execSync('pnpm run skills:pack-check', { stdio: 'inherit' }); for (const { dirName, name, version } of packages) { try {