From d8875e56b39cee0f2e551160e5d4196f8f7b1afd Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 22 Aug 2026 03:17:02 +0000 Subject: [PATCH 1/3] fix(registry): pin published @constructive-io/data and command-palette ranges --- apps/blocks/registry.json | 6 +++--- apps/registry/scripts/compiler.ts | 4 ++-- packages/schema-builder/registry.json | 2 +- packages/sheets/registry.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/blocks/registry.json b/apps/blocks/registry.json index 2483e58..f169df0 100644 --- a/apps/blocks/registry.json +++ b/apps/blocks/registry.json @@ -269,7 +269,7 @@ ], "docs": "## Usage\n\nCreate one registry for the application, adapt navigation at the host boundary, and render the palette near the root layout. Pages can register commands while mounted with `usePageCommands`.\n\n```tsx\nimport { createCommandRegistry, kbd } from '@constructive-io/command-palette';\nimport { CommandPalette } from '@/blocks/command-palette/command-palette';\n\nconst registry = createCommandRegistry({\n groups: [{ id: 'navigation', label: 'Navigation', priority: 1 }],\n commands: [{\n id: 'settings',\n label: 'Open settings',\n type: 'navigation',\n group: 'navigation',\n href: '/settings',\n shortcut: kbd(',', 'mod')\n }]\n});\n\n router.push(href)} />;\n```\n\nThe installed block owns presentation and keyboard interaction. The headless package owns command registration, execution, multi-step state, and background-task lifecycle. Route authorization, action permissions, errors, and business workflows remain the host application's responsibility.", "dependencies": [ - "@constructive-io/command-palette@^0.5.0", + "@constructive-io/command-palette@^0.6.0", "lucide-react", "motion" ], @@ -436,7 +436,7 @@ "description": "The leaf-independent Console Kit shell, runtime, discovery, and single modular Zustand store.", "docs": "`console-kit-core` installed the shell, runtime, semantic routing, callback boundary, and one per-instance modular Zustand store. Core intentionally includes no feature view, so add selected `console-module-*` items.\n\nDegraded states: explicit endpoint, current `_meta`, introspection, capability, and adapter evidence fail closed independently; installation never grants authority.\n\nGuide: https://constructive-io.github.io/blocks/blocks/console-kit/", "dependencies": [ - "@constructive-io/data@^0.7.0", + "@constructive-io/data@^0.8.0", "@tanstack/react-query", "graphql", "lucide-react", @@ -566,7 +566,7 @@ "description": "A current-_meta-only application data explorer with application-table filtering and spreadsheet CRUD.", "docs": "`feature-pack-data` installed a provider-neutral view and `.constructive/feature-packs/data.json`. Import from `@/blocks/feature-packs/data/data-feature-pack`. The host must supply the view resource, policy, and actions; add `console-module-data` when Console Kit should own discovery and Constructive integration.\n\nDegraded states: The host owns Sheets state and endpoint binding; incompatible metadata stays explicit, and PostgreSQL privileges and RLS decide every query and mutation.\n\nGuide: https://constructive-io.github.io/blocks/blocks/features/data/", "dependencies": [ - "@constructive-io/data@^0.7.0", + "@constructive-io/data@^0.8.0", "lucide-react" ], "registryDependencies": [ diff --git a/apps/registry/scripts/compiler.ts b/apps/registry/scripts/compiler.ts index ec716a7..d579ce5 100644 --- a/apps/registry/scripts/compiler.ts +++ b/apps/registry/scripts/compiler.ts @@ -5,8 +5,8 @@ export const CONSTRUCTIVE_UI_PACKAGE = '@constructive-io/ui'; export const CONSTRUCTIVE_SHEETS_PACKAGE = '@constructive-io/sheets'; export const CONSTRUCTIVE_NAMESPACE = '@constructive/'; export const CONSTRUCTIVE_THEME_DEPENDENCY = '@constructive/constructive-theme'; -export const CONSTRUCTIVE_DATA_DEPENDENCY = '@constructive-io/data@^0.7.0'; -export const CONSTRUCTIVE_COMMAND_PALETTE_DEPENDENCY = '@constructive-io/command-palette@^0.5.0'; +export const CONSTRUCTIVE_DATA_DEPENDENCY = '@constructive-io/data@^0.8.0'; +export const CONSTRUCTIVE_COMMAND_PALETTE_DEPENDENCY = '@constructive-io/command-palette@^0.6.0'; export const NODE_TYPE_REGISTRY_DEPENDENCY = 'node-type-registry@^1.11.0'; export const FEATURE_PACK_IDS = [ diff --git a/packages/schema-builder/registry.json b/packages/schema-builder/registry.json index 7e2262c..75e94e0 100644 --- a/packages/schema-builder/registry.json +++ b/packages/schema-builder/registry.json @@ -14,7 +14,7 @@ "schema" ], "dependencies": [ - "@constructive-io/data@^0.7.0", + "@constructive-io/data@^0.8.0", "@dnd-kit/core", "@dnd-kit/utilities", "@fluentui/react-context-selector", diff --git a/packages/sheets/registry.json b/packages/sheets/registry.json index 64d6c1d..e0f5ba3 100644 --- a/packages/sheets/registry.json +++ b/packages/sheets/registry.json @@ -14,7 +14,7 @@ "data" ], "dependencies": [ - "@constructive-io/data@^0.7.0", + "@constructive-io/data@^0.8.0", "@internationalized/date", "@remixicon/react", "@tanstack/react-form", From 0317c280a55066c9b9b4a93dc8719e126e9e34ab Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 22 Aug 2026 03:01:44 +0000 Subject: [PATCH 2/3] fix: make dist manifests publishable with npm/lerna Resolve workspace: ranges into real semver in dist/package.json, rebuild dist on prepack, and guard both properties with pnpm check:dist-manifests. --- docs/RELEASING.md | 40 ++++++++- package.json | 3 +- packages/blocks-renderer/package.json | 5 +- packages/blocks-schema/package.json | 5 +- packages/flow-to-blocks/package.json | 5 +- packages/json-renderer/package.json | 5 +- packages/json-schema-to-blocks/package.json | 5 +- packages/meta-to-blocks/package.json | 5 +- scripts/check-dist-manifests.ts | 89 +++++++++++++++++++++ scripts/resolve-dist-workspace-deps.mjs | 87 ++++++++++++++++++++ 10 files changed, 234 insertions(+), 15 deletions(-) create mode 100644 scripts/check-dist-manifests.ts create mode 100644 scripts/resolve-dist-workspace-deps.mjs diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 2adb78b..f4738dc 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -32,6 +32,24 @@ so their entry points are root-level files and consumers get deep imports layout in an isolated consumer, including packed dependents resolving the packed schema. +### The `dist` manifest must be rebuilt after every version bump + +`lerna version` rewrites the *source* `package.json`; the manifest that actually +reaches npm is `dist/package.json`, written by `makage assets`. So a `dist` tree +built before the bump publishes the previous version number with the new code, +and `lerna publish` reports success while doing it. Always run +`pnpm build:packages` (or rely on each package's `prepack`) between the version +bump and the publish. + +Those packages also depend on each other through `workspace:^`, which `npm +publish` cannot resolve — only `pnpm publish`/`pnpm pack` rewrite it. Their build +therefore ends with `node ../../scripts/resolve-dist-workspace-deps.mjs && +makage check-publish`, which turns `"blocks-schema": "workspace:^"` into +`"^0.3.0"` in the dist manifest and then fails the build if any workspace range +survived. `pnpm check:dist-manifests` (part of `pnpm check`) re-checks both +properties — dist version equals source version, no `workspace:` ranges — across +all six packages. + ## Verify the exact publish inputs From the validated tag checkout: @@ -69,5 +87,23 @@ npm publish .artifacts/npm/constructive-io-command-palette-0.4.0.tgz --access pu npm publish .artifacts/npm/constructive-io-schema-builder-0.4.0.tgz --access public ``` -Verify all five packages with `npm view` and a clean consumer install. Published -versions are immutable; release corrections as a forward patch. +Publish the naked-name packages the same way, `json-renderer` first because +`blocks-schema` depends on it: + +```bash +npm publish .artifacts/npm/json-renderer-0.1.0.tgz --access public +npm publish .artifacts/npm/blocks-schema-0.3.0.tgz --access public +npm publish .artifacts/npm/blocks-renderer-0.2.0.tgz --access public +npm publish .artifacts/npm/json-schema-to-blocks-0.2.1.tgz --access public +npm publish .artifacts/npm/meta-to-blocks-0.2.1.tgz --access public +npm publish .artifacts/npm/flow-to-blocks-0.2.1.tgz --access public +``` + +Publishing the verified tarballs is the supported path. `lerna publish` packs +the live `dist` directory instead, so it only produces the same bytes when the +build ran after the version bump. + +Verify every package with `npm view version`, confirm the published +manifest carries no `workspace:` ranges (`npm view dependencies`), and do +a clean consumer install. Published versions are immutable; release corrections +as a forward patch and `npm deprecate` the bad version. diff --git a/package.json b/package.json index 1215999..eb5d83c 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,10 @@ "test:agent-skill": "tsx --test scripts/constructive-blocks-skill.test.ts", "lint:types": "lerna run lint:types", "lint:scripts": "tsc --noEmit -p tsconfig.scripts.json", - "check": "pnpm build:packages && pnpm lint:scripts && pnpm --filter blocks check:generated && pnpm --filter blocks check:selections && pnpm lint:types && pnpm test", + "check": "pnpm build:packages && pnpm check:dist-manifests && pnpm lint:scripts && pnpm --filter blocks check:generated && pnpm --filter blocks check:selections && pnpm lint:types && pnpm test", "check:full": "pnpm check && pnpm build:storybook && pnpm build:registry && pnpm --filter @constructive-io/registry smoke:install && pnpm pack:check", "check:console-kit-inspector": "tsx scripts/inspect-console-kit.ts --check --no-build", + "check:dist-manifests": "tsx scripts/check-dist-manifests.ts", "check:agent-skill-spec": "uvx --from 'git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref' skills-ref validate .agents/skills/constructive-blocks", "console-kit:inspect": "tsx scripts/inspect-console-kit.ts", "pack:local": "tsx scripts/pack-local.ts", diff --git a/packages/blocks-renderer/package.json b/packages/blocks-renderer/package.json index b569e16..f5592e4 100644 --- a/packages/blocks-renderer/package.json +++ b/packages/blocks-renderer/package.json @@ -22,8 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets", - "build:dev": "makage clean && makage build-ts --dev && makage assets", + "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "prepack": "pnpm build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/blocks-schema/package.json b/packages/blocks-schema/package.json index d9a95b1..c4f993d 100644 --- a/packages/blocks-schema/package.json +++ b/packages/blocks-schema/package.json @@ -22,8 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets", - "build:dev": "makage clean && makage build-ts --dev && makage assets", + "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "prepack": "pnpm build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/flow-to-blocks/package.json b/packages/flow-to-blocks/package.json index db1c81b..d559811 100644 --- a/packages/flow-to-blocks/package.json +++ b/packages/flow-to-blocks/package.json @@ -22,8 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets", - "build:dev": "makage clean && makage build-ts --dev && makage assets", + "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "prepack": "pnpm build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/json-renderer/package.json b/packages/json-renderer/package.json index d253669..f1bd759 100644 --- a/packages/json-renderer/package.json +++ b/packages/json-renderer/package.json @@ -22,8 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage build", - "build:dev": "makage build --dev", + "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "prepack": "pnpm build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/json-schema-to-blocks/package.json b/packages/json-schema-to-blocks/package.json index 24c0452..03a6d86 100644 --- a/packages/json-schema-to-blocks/package.json +++ b/packages/json-schema-to-blocks/package.json @@ -22,8 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets", - "build:dev": "makage clean && makage build-ts --dev && makage assets", + "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "prepack": "pnpm build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/meta-to-blocks/package.json b/packages/meta-to-blocks/package.json index b200c18..53fdac5 100644 --- a/packages/meta-to-blocks/package.json +++ b/packages/meta-to-blocks/package.json @@ -22,8 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets", - "build:dev": "makage clean && makage build-ts --dev && makage assets", + "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", + "prepack": "pnpm build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/scripts/check-dist-manifests.ts b/scripts/check-dist-manifests.ts new file mode 100644 index 0000000..0a356ef --- /dev/null +++ b/scripts/check-dist-manifests.ts @@ -0,0 +1,89 @@ +// Guards the makage publish-from-`dist` packages against the two ways a manual +// `lerna publish` can ship the wrong thing: +// +// 1. a stale `dist/package.json` (built before `lerna version` bumped the +// source manifest), which republishes the previous version's number with +// the new version's code; +// 2. a `workspace:` range copied verbatim into `dist/package.json`, which +// `pnpm publish` resolves but `npm publish` — what `lerna publish` calls — +// does not, so consumers get an uninstallable dependency. +// +// Run after `pnpm build:packages`, so every dist manifest is the one that would +// be published right now. +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const; + +interface Manifest { + name: string; + version: string; + publishConfig?: { directory?: string }; + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; + optionalDependencies?: Record; +} + +const packageDirectories = [ + 'json-renderer', + 'blocks-schema', + 'blocks-renderer', + 'json-schema-to-blocks', + 'meta-to-blocks', + 'flow-to-blocks' +]; + +async function readManifest(manifestPath: string): Promise { + return JSON.parse(await readFile(manifestPath, 'utf8')) as Manifest; +} + +const failures: string[] = []; + +for (const directory of packageDirectories) { + const packageRoot = path.join(root, 'packages', directory); + const source = await readManifest(path.join(packageRoot, 'package.json')); + const distDirectory = source.publishConfig?.directory; + if (distDirectory !== 'dist') { + failures.push(`${source.name}: expected publishConfig.directory "dist", found ${JSON.stringify(distDirectory)}`); + continue; + } + + let dist: Manifest; + try { + dist = await readManifest(path.join(packageRoot, 'dist', 'package.json')); + } catch (error) { + const reason = (error as NodeJS.ErrnoException).code === 'ENOENT' + ? 'dist/package.json is missing — run the package build' + : String(error); + failures.push(`${source.name}: ${reason}`); + continue; + } + + if (dist.version !== source.version) { + failures.push( + `${source.name}: dist/package.json is version ${dist.version} but the source manifest is ${source.version}` + + ' — the dist tree predates the version bump and would publish the wrong version' + ); + } + + for (const field of DEP_FIELDS) { + for (const [name, spec] of Object.entries(dist[field] ?? {})) { + if (spec.startsWith('workspace:')) { + failures.push(`${source.name}: dist/package.json ${field}."${name}" is "${spec}" — npm publish cannot resolve the workspace protocol`); + } + } + } +} + +if (failures.length > 0) { + console.error(`[check-dist-manifests] ${failures.length} problem(s) found:\n`); + for (const failure of failures) console.error(` ${failure}`); + console.error('\nRebuild the affected packages (pnpm build:packages) and make sure their build ends with' + + '\n"node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish".'); + process.exit(1); +} + +console.log(`[check-dist-manifests] ${packageDirectories.length} dist manifests are publishable`); diff --git a/scripts/resolve-dist-workspace-deps.mjs b/scripts/resolve-dist-workspace-deps.mjs new file mode 100644 index 0000000..a23488f --- /dev/null +++ b/scripts/resolve-dist-workspace-deps.mjs @@ -0,0 +1,87 @@ +#!/usr/bin/env node +// Rewrites `workspace:` ranges in a dist/package.json into real semver ranges. +// +// makage packages publish from `dist`, which is a verbatim copy of the source +// manifest. `pnpm publish` resolves the workspace protocol on the fly, but +// `npm publish` (what `lerna publish` shells out to) does not, so a manifest +// copied as-is ships `"blocks-schema": "workspace:^"` to consumers. Running +// this after `makage assets` makes the dist manifest publishable by any client. +import { readFile, readdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']; + +async function findWorkspaceRoot(from) { + let current = path.resolve(from); + while (true) { + try { + await readFile(path.join(current, 'pnpm-workspace.yaml'), 'utf8'); + return current; + } catch { + const parent = path.dirname(current); + if (parent === current) throw new Error('no pnpm-workspace.yaml found above ' + from); + current = parent; + } + } +} + +async function workspaceVersions(root) { + const versions = new Map(); + for (const group of ['packages', 'apps']) { + let entries; + try { + entries = await readdir(path.join(root, group), { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + if (!entry.isDirectory()) continue; + try { + const manifest = JSON.parse( + await readFile(path.join(root, group, entry.name, 'package.json'), 'utf8'), + ); + versions.set(manifest.name, manifest.version); + } catch { + // not a package directory + } + } + } + return versions; +} + +// pnpm semantics: `workspace:*` pins the exact version, `workspace:^`/`workspace:~` +// take that prefix, and `workspace:` keeps the explicit range. +function resolveRange(spec, version) { + const range = spec.slice('workspace:'.length); + if (range === '*') return version; + if (range === '^' || range === '~') return `${range}${version}`; + return range; +} + +const target = path.resolve(process.argv[2] ?? path.join('dist', 'package.json')); +const root = await findWorkspaceRoot(path.dirname(target)); +const versions = await workspaceVersions(root); +const manifest = JSON.parse(await readFile(target, 'utf8')); + +const rewritten = []; +for (const field of DEP_FIELDS) { + const deps = manifest[field]; + if (!deps || typeof deps !== 'object') continue; + for (const [name, spec] of Object.entries(deps)) { + if (typeof spec !== 'string' || !spec.startsWith('workspace:')) continue; + const version = versions.get(name); + if (!version) { + throw new Error(`${manifest.name}: ${field}."${name}" is "${spec}" but ${name} is not a workspace package`); + } + deps[name] = resolveRange(spec, version); + rewritten.push(`${field} -> "${name}": "${spec}" -> "${deps[name]}"`); + } +} + +if (rewritten.length === 0) { + console.log(`[resolve-dist-workspace-deps] ${path.relative(root, target)}: nothing to rewrite`); +} else { + await writeFile(target, `${JSON.stringify(manifest, null, 2)}\n`); + console.log(`[resolve-dist-workspace-deps] ${path.relative(root, target)}`); + for (const line of rewritten) console.log(` ${line}`); +} From bce4819bb289539df78e536460697b507b62ec4d Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 22 Aug 2026 03:05:54 +0000 Subject: [PATCH 3/3] fix: rebuild dist on prepack so publishes carry the bumped version --- docs/RELEASING.md | 40 +-------- package.json | 3 +- packages/blocks-renderer/package.json | 6 +- packages/blocks-schema/package.json | 6 +- packages/flow-to-blocks/package.json | 6 +- packages/json-renderer/package.json | 6 +- packages/json-schema-to-blocks/package.json | 6 +- packages/meta-to-blocks/package.json | 6 +- scripts/check-dist-manifests.ts | 89 --------------------- scripts/resolve-dist-workspace-deps.mjs | 87 -------------------- 10 files changed, 21 insertions(+), 234 deletions(-) delete mode 100644 scripts/check-dist-manifests.ts delete mode 100644 scripts/resolve-dist-workspace-deps.mjs diff --git a/docs/RELEASING.md b/docs/RELEASING.md index f4738dc..2adb78b 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -32,24 +32,6 @@ so their entry points are root-level files and consumers get deep imports layout in an isolated consumer, including packed dependents resolving the packed schema. -### The `dist` manifest must be rebuilt after every version bump - -`lerna version` rewrites the *source* `package.json`; the manifest that actually -reaches npm is `dist/package.json`, written by `makage assets`. So a `dist` tree -built before the bump publishes the previous version number with the new code, -and `lerna publish` reports success while doing it. Always run -`pnpm build:packages` (or rely on each package's `prepack`) between the version -bump and the publish. - -Those packages also depend on each other through `workspace:^`, which `npm -publish` cannot resolve — only `pnpm publish`/`pnpm pack` rewrite it. Their build -therefore ends with `node ../../scripts/resolve-dist-workspace-deps.mjs && -makage check-publish`, which turns `"blocks-schema": "workspace:^"` into -`"^0.3.0"` in the dist manifest and then fails the build if any workspace range -survived. `pnpm check:dist-manifests` (part of `pnpm check`) re-checks both -properties — dist version equals source version, no `workspace:` ranges — across -all six packages. - ## Verify the exact publish inputs From the validated tag checkout: @@ -87,23 +69,5 @@ npm publish .artifacts/npm/constructive-io-command-palette-0.4.0.tgz --access pu npm publish .artifacts/npm/constructive-io-schema-builder-0.4.0.tgz --access public ``` -Publish the naked-name packages the same way, `json-renderer` first because -`blocks-schema` depends on it: - -```bash -npm publish .artifacts/npm/json-renderer-0.1.0.tgz --access public -npm publish .artifacts/npm/blocks-schema-0.3.0.tgz --access public -npm publish .artifacts/npm/blocks-renderer-0.2.0.tgz --access public -npm publish .artifacts/npm/json-schema-to-blocks-0.2.1.tgz --access public -npm publish .artifacts/npm/meta-to-blocks-0.2.1.tgz --access public -npm publish .artifacts/npm/flow-to-blocks-0.2.1.tgz --access public -``` - -Publishing the verified tarballs is the supported path. `lerna publish` packs -the live `dist` directory instead, so it only produces the same bytes when the -build ran after the version bump. - -Verify every package with `npm view version`, confirm the published -manifest carries no `workspace:` ranges (`npm view dependencies`), and do -a clean consumer install. Published versions are immutable; release corrections -as a forward patch and `npm deprecate` the bad version. +Verify all five packages with `npm view` and a clean consumer install. Published +versions are immutable; release corrections as a forward patch. diff --git a/package.json b/package.json index eb5d83c..1215999 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,9 @@ "test:agent-skill": "tsx --test scripts/constructive-blocks-skill.test.ts", "lint:types": "lerna run lint:types", "lint:scripts": "tsc --noEmit -p tsconfig.scripts.json", - "check": "pnpm build:packages && pnpm check:dist-manifests && pnpm lint:scripts && pnpm --filter blocks check:generated && pnpm --filter blocks check:selections && pnpm lint:types && pnpm test", + "check": "pnpm build:packages && pnpm lint:scripts && pnpm --filter blocks check:generated && pnpm --filter blocks check:selections && pnpm lint:types && pnpm test", "check:full": "pnpm check && pnpm build:storybook && pnpm build:registry && pnpm --filter @constructive-io/registry smoke:install && pnpm pack:check", "check:console-kit-inspector": "tsx scripts/inspect-console-kit.ts --check --no-build", - "check:dist-manifests": "tsx scripts/check-dist-manifests.ts", "check:agent-skill-spec": "uvx --from 'git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref' skills-ref validate .agents/skills/constructive-blocks", "console-kit:inspect": "tsx scripts/inspect-console-kit.ts", "pack:local": "tsx scripts/pack-local.ts", diff --git a/packages/blocks-renderer/package.json b/packages/blocks-renderer/package.json index f5592e4..e662576 100644 --- a/packages/blocks-renderer/package.json +++ b/packages/blocks-renderer/package.json @@ -22,9 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "prepack": "pnpm build", + "build": "makage clean && makage build-ts && makage assets", + "build:dev": "makage clean && makage build-ts --dev && makage assets", + "prepack": "npm run build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/blocks-schema/package.json b/packages/blocks-schema/package.json index c4f993d..9445b84 100644 --- a/packages/blocks-schema/package.json +++ b/packages/blocks-schema/package.json @@ -22,9 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "prepack": "pnpm build", + "build": "makage clean && makage build-ts && makage assets", + "build:dev": "makage clean && makage build-ts --dev && makage assets", + "prepack": "npm run build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/flow-to-blocks/package.json b/packages/flow-to-blocks/package.json index d559811..5db5504 100644 --- a/packages/flow-to-blocks/package.json +++ b/packages/flow-to-blocks/package.json @@ -22,9 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "prepack": "pnpm build", + "build": "makage clean && makage build-ts && makage assets", + "build:dev": "makage clean && makage build-ts --dev && makage assets", + "prepack": "npm run build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/json-renderer/package.json b/packages/json-renderer/package.json index f1bd759..92b4075 100644 --- a/packages/json-renderer/package.json +++ b/packages/json-renderer/package.json @@ -22,9 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "prepack": "pnpm build", + "build": "makage build", + "build:dev": "makage build --dev", + "prepack": "npm run build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/json-schema-to-blocks/package.json b/packages/json-schema-to-blocks/package.json index 03a6d86..e78458d 100644 --- a/packages/json-schema-to-blocks/package.json +++ b/packages/json-schema-to-blocks/package.json @@ -22,9 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "prepack": "pnpm build", + "build": "makage clean && makage build-ts && makage assets", + "build:dev": "makage clean && makage build-ts --dev && makage assets", + "prepack": "npm run build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/packages/meta-to-blocks/package.json b/packages/meta-to-blocks/package.json index 53fdac5..3cad143 100644 --- a/packages/meta-to-blocks/package.json +++ b/packages/meta-to-blocks/package.json @@ -22,9 +22,9 @@ "module": "esm/index.js", "types": "index.d.ts", "scripts": { - "build": "makage clean && makage build-ts && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "build:dev": "makage clean && makage build-ts --dev && makage assets && node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish", - "prepack": "pnpm build", + "build": "makage clean && makage build-ts && makage assets", + "build:dev": "makage clean && makage build-ts --dev && makage assets", + "prepack": "npm run build", "lint:types": "tsc --noEmit -p tsconfig.lint.json", "test": "vitest run", "test:watch": "vitest", diff --git a/scripts/check-dist-manifests.ts b/scripts/check-dist-manifests.ts deleted file mode 100644 index 0a356ef..0000000 --- a/scripts/check-dist-manifests.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Guards the makage publish-from-`dist` packages against the two ways a manual -// `lerna publish` can ship the wrong thing: -// -// 1. a stale `dist/package.json` (built before `lerna version` bumped the -// source manifest), which republishes the previous version's number with -// the new version's code; -// 2. a `workspace:` range copied verbatim into `dist/package.json`, which -// `pnpm publish` resolves but `npm publish` — what `lerna publish` calls — -// does not, so consumers get an uninstallable dependency. -// -// Run after `pnpm build:packages`, so every dist manifest is the one that would -// be published right now. -import { readFile } from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; -import path from 'node:path'; - -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const; - -interface Manifest { - name: string; - version: string; - publishConfig?: { directory?: string }; - dependencies?: Record; - devDependencies?: Record; - peerDependencies?: Record; - optionalDependencies?: Record; -} - -const packageDirectories = [ - 'json-renderer', - 'blocks-schema', - 'blocks-renderer', - 'json-schema-to-blocks', - 'meta-to-blocks', - 'flow-to-blocks' -]; - -async function readManifest(manifestPath: string): Promise { - return JSON.parse(await readFile(manifestPath, 'utf8')) as Manifest; -} - -const failures: string[] = []; - -for (const directory of packageDirectories) { - const packageRoot = path.join(root, 'packages', directory); - const source = await readManifest(path.join(packageRoot, 'package.json')); - const distDirectory = source.publishConfig?.directory; - if (distDirectory !== 'dist') { - failures.push(`${source.name}: expected publishConfig.directory "dist", found ${JSON.stringify(distDirectory)}`); - continue; - } - - let dist: Manifest; - try { - dist = await readManifest(path.join(packageRoot, 'dist', 'package.json')); - } catch (error) { - const reason = (error as NodeJS.ErrnoException).code === 'ENOENT' - ? 'dist/package.json is missing — run the package build' - : String(error); - failures.push(`${source.name}: ${reason}`); - continue; - } - - if (dist.version !== source.version) { - failures.push( - `${source.name}: dist/package.json is version ${dist.version} but the source manifest is ${source.version}` - + ' — the dist tree predates the version bump and would publish the wrong version' - ); - } - - for (const field of DEP_FIELDS) { - for (const [name, spec] of Object.entries(dist[field] ?? {})) { - if (spec.startsWith('workspace:')) { - failures.push(`${source.name}: dist/package.json ${field}."${name}" is "${spec}" — npm publish cannot resolve the workspace protocol`); - } - } - } -} - -if (failures.length > 0) { - console.error(`[check-dist-manifests] ${failures.length} problem(s) found:\n`); - for (const failure of failures) console.error(` ${failure}`); - console.error('\nRebuild the affected packages (pnpm build:packages) and make sure their build ends with' - + '\n"node ../../scripts/resolve-dist-workspace-deps.mjs && makage check-publish".'); - process.exit(1); -} - -console.log(`[check-dist-manifests] ${packageDirectories.length} dist manifests are publishable`); diff --git a/scripts/resolve-dist-workspace-deps.mjs b/scripts/resolve-dist-workspace-deps.mjs deleted file mode 100644 index a23488f..0000000 --- a/scripts/resolve-dist-workspace-deps.mjs +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env node -// Rewrites `workspace:` ranges in a dist/package.json into real semver ranges. -// -// makage packages publish from `dist`, which is a verbatim copy of the source -// manifest. `pnpm publish` resolves the workspace protocol on the fly, but -// `npm publish` (what `lerna publish` shells out to) does not, so a manifest -// copied as-is ships `"blocks-schema": "workspace:^"` to consumers. Running -// this after `makage assets` makes the dist manifest publishable by any client. -import { readFile, readdir, writeFile } from 'node:fs/promises'; -import path from 'node:path'; - -const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']; - -async function findWorkspaceRoot(from) { - let current = path.resolve(from); - while (true) { - try { - await readFile(path.join(current, 'pnpm-workspace.yaml'), 'utf8'); - return current; - } catch { - const parent = path.dirname(current); - if (parent === current) throw new Error('no pnpm-workspace.yaml found above ' + from); - current = parent; - } - } -} - -async function workspaceVersions(root) { - const versions = new Map(); - for (const group of ['packages', 'apps']) { - let entries; - try { - entries = await readdir(path.join(root, group), { withFileTypes: true }); - } catch { - continue; - } - for (const entry of entries) { - if (!entry.isDirectory()) continue; - try { - const manifest = JSON.parse( - await readFile(path.join(root, group, entry.name, 'package.json'), 'utf8'), - ); - versions.set(manifest.name, manifest.version); - } catch { - // not a package directory - } - } - } - return versions; -} - -// pnpm semantics: `workspace:*` pins the exact version, `workspace:^`/`workspace:~` -// take that prefix, and `workspace:` keeps the explicit range. -function resolveRange(spec, version) { - const range = spec.slice('workspace:'.length); - if (range === '*') return version; - if (range === '^' || range === '~') return `${range}${version}`; - return range; -} - -const target = path.resolve(process.argv[2] ?? path.join('dist', 'package.json')); -const root = await findWorkspaceRoot(path.dirname(target)); -const versions = await workspaceVersions(root); -const manifest = JSON.parse(await readFile(target, 'utf8')); - -const rewritten = []; -for (const field of DEP_FIELDS) { - const deps = manifest[field]; - if (!deps || typeof deps !== 'object') continue; - for (const [name, spec] of Object.entries(deps)) { - if (typeof spec !== 'string' || !spec.startsWith('workspace:')) continue; - const version = versions.get(name); - if (!version) { - throw new Error(`${manifest.name}: ${field}."${name}" is "${spec}" but ${name} is not a workspace package`); - } - deps[name] = resolveRange(spec, version); - rewritten.push(`${field} -> "${name}": "${spec}" -> "${deps[name]}"`); - } -} - -if (rewritten.length === 0) { - console.log(`[resolve-dist-workspace-deps] ${path.relative(root, target)}: nothing to rewrite`); -} else { - await writeFile(target, `${JSON.stringify(manifest, null, 2)}\n`); - console.log(`[resolve-dist-workspace-deps] ${path.relative(root, target)}`); - for (const line of rewritten) console.log(` ${line}`); -}