Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ name: publish-npm
# in tier order (a dependency never lands after its dependent). Mirrors
# publish-csharp.yml / publish-python.yml: tag-triggered or manual.
#
# WHICH packages, and in WHAT ORDER, is NOT decided here — `scripts/publish-set.mjs`
# is the single source of truth, shared with the local `bun run release` path. This
# workflow used to carry its own hardcoded list of 13 directories, which drifted from
# that derivation: @metaobjectsdev/docs-site is a runtime dependency of
# @metaobjectsdev/cli but was never in the list, so a release cut through here would
# have shipped a cli pinning a docs-site version nobody published — an uninstallable
# tarball, discoverable only by an external `npm install`. A list that is derived
# cannot drift from the derivation.
#
# This publishes the versions ALREADY COMMITTED in each package.json at the tagged
# commit — bump + commit them first (locally: `bun run release <ver>` does the
# bump/build/verify/publish in one shot WITH a confirm gate; this workflow is the
Expand Down Expand Up @@ -38,6 +47,9 @@ jobs:
bun install --frozen-lockfile
bun run build

- name: Derive + verify the publish set
run: node scripts/publish-set.mjs --check

- name: Pack-verify the cli tarball pins siblings (no workspace:*)
run: |
set -euo pipefail
Expand All @@ -60,15 +72,15 @@ jobs:
run: |
set -euo pipefail
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
trap 'rm -f ~/.npmrc' EXIT
pub() { ( cd "$1" && bun publish --access public ) && echo " ✓ $1"; }
# tier 0 → 4 (deps before dependents)
for d in server/typescript/packages/metadata server/typescript/packages/render \
server/typescript/packages/codegen-ts server/typescript/packages/runtime-ts \
server/typescript/packages/migrate-ts server/typescript/packages/sdk \
client/web/packages/runtime-web \
server/typescript/packages/codegen-ts-react server/typescript/packages/codegen-ts-tanstack \
client/web/packages/react client/web/packages/tanstack \
server/typescript/packages/cli server/typescript/packages/ai-runtime ; do
# The set and its tier order (deps before dependents) come from the shared
# derivation, never from a list maintained here. Written to a file first so a
# non-zero exit fails the step instead of yielding a silently short list.
node scripts/publish-set.mjs > "${RUNNER_TEMP}/publish-set.txt"
mapfile -t DIRS < "${RUNNER_TEMP}/publish-set.txt"
[ "${#DIRS[@]}" -gt 0 ] || { echo "::error::derived publish set is empty"; exit 1; }
echo "publishing ${#DIRS[@]} packages in tier order"
for d in "${DIRS[@]}"; do
pub "$d"
done
rm -f ~/.npmrc
592 changes: 592 additions & 0 deletions AGENTS.md

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ anything depends on the version, and deprecation does not free the number. The p
checks every package in the set (in parallel, so it stays fast), and `bun run prerelease`
skips numbers already burned on public npm when choosing an iteration.

### Fixed — `publish-npm.yml` would have published an uninstallable `@metaobjectsdev/cli`

The workflow carried its **own hardcoded list of 13 package directories**;
`scripts/release.mjs` **derived** the same set (every non-private package at the CLI's
version). Two answers to one question, and they had drifted:
`@metaobjectsdev/docs-site` is a runtime `dependencies` entry of `@metaobjectsdev/cli`
(`workspace:*`, rewritten to the concrete version at pack time) and was not in the
workflow's list. A release cut through the workflow would therefore have published a
`cli` pinning `@metaobjectsdev/docs-site@<version>` that nobody published — `npm i
@metaobjectsdev/cli` → `ETARGET`, discoverable only by an external install. It stayed
latent because the local `bun run release` path publishes all 14, so `docs-site@0.23.2`
is on npm today; **nothing had ever compared the two answers.**

`scripts/publish-set.mjs` is now the single source of truth for which packages ship and
in what order, and both paths read it — the workflow's list is gone, so it cannot drift
from a derivation it no longer has. The derivation also fails loudly rather than
returning a wrong set: a member with no declared tier, a set not closed over its own
sibling runtime deps, or a tier order that would publish a dependency after its
dependent. Wired into the `gates` lane (`publish-set parity`) beside
`check-publish-intent.sh`, which enforces the same rule from the other side.

`TIER_ORDER` omitted `docs-site` too, and that was **not** the harmless oversight it
looked like: `indexOf()` returns `-1`, which does not sort last — it sorts **first**, so
the local release path published `docs-site` ahead of `metadata` and `render`, the two
packages it depends on. The tier is declared now, and an undeclared one is an error
instead of an accidental position.


## [0.23.2] — npm `0.23.2` · PyPI `0.23.2` · NuGet `0.23.2` · Maven `7.23.2`

Expand Down
Loading
Loading