Skip to content

chore(release): publish @robrain/shared to npm; bump to 2.4.9 - #13

Open
radubusuioc wants to merge 1 commit into
mainfrom
claude/publish-robrain-shared-npm-59e3db
Open

chore(release): publish @robrain/shared to npm; bump to 2.4.9#13
radubusuioc wants to merge 1 commit into
mainfrom
claude/publish-robrain-shared-npm-59e3db

Conversation

@radubusuioc

@radubusuioc radubusuioc commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

What

Makes @robrain/shared publishable to npm. Rebased onto main and renumbered 2.4.8 → 2.4.9: robrain@2.4.8 is already published and v2.4.8 is tagged on origin, so the original version was dead.

Today the cloud repo consumes shared as a git-pin, which forces every consumer install to download the full monorepo tarball and compile TypeScript at install time. Publishing a prebuilt package removes that.

Review fixes folded in

🔴 Test exclusion didn't take effect. Correct — tsc never cleans outDir, so a release machine's populated dist/ keeps every previously-compiled *.test.js. Reproduced: seeded dist/ with the old config (28 test artifacts), rebuilt with the exclusion, all 28 survived.

Fixed, but the suggested prepack: "rm -rf dist && tsc" alone isn't sufficient — npm runs prepare after prepack, so prepare: "tsc" re-emitted everything the clean had just removed. My first attempt at this shipped maps for exactly that reason. Both hooks now run rm -rf dist && tsc -p tsconfig.publish.json.

🔴 Publishing shared before the guard can strand a version. Correct, and the consequence chain (24h unpublish block → bump to next patch → orphan version → broken lockstep invariant) is right. Applied your fix, build-first as you noted. Verified it actually gates:

$ pnpm --filter robrain build && node packages/cli/scripts/verify-release-artifacts.mjs
release-guard FAILED for robrain@2.4.9:
  ✗ git tag v2.4.9 is not on origin — …
  ✗ ghcr.io/adelinamart/robrain-perception:2.4.9 is not published — …
guard exit=1

Under the old ordering @robrain/shared@2.4.9 would already be public at this point.

🟡 Broken sourcemaps. Took the "emit without maps" option — shipping src would also drag in test sources. tsconfig.publish.json disables sourceMap/declarationMap; plain build keeps them, so local dev and the git-pin path are unaffected. Tarball dropped 41.7 kB → 31.3 kB.

🟡 LICENSE — added packages/shared/LICENSE (copy of the CLI's) and listed it in files.

🟡 README — added, with install, usage, the schema.sql resolution snippet, and links back.

Verification

  • pnpm -r build, pnpm -r typecheck (7/7) clean
  • pnpm test 159/159 · bun test src 127/127
  • Seeded dist/ dirty (28 test artifacts, 34 maps), then packed: 24 files, 0 tests, 0 mapsdist/*.js, dist/*.d.ts, schema.sql, README.md, LICENSE, package.json
  • Installed the tarball in a scratch dir: import('@robrain/shared') → 40 exports; import('@robrain/shared/load-env') works; require.resolve('@robrain/shared/schema.sql') works; LICENSE present

Merge conflict

Rebase hit one conflict, packages/shared/package.json (main bumped it to 2.4.8 while keeping private: true). Resolved in favour of this branch. install.ts and index.ts auto-merged cleanly despite the in-flight install-messaging and robrain status work.

Coordination

  • Blocked on the @robrain org existing. publish:npm leads with the shared publish, so without the scope the release stops after the guard passes. Registry side is clear: no @robrain/* packages exist.
  • Two OTP prompts with 2FA, one per package.
  • Inert until the cloud repo swaps its git-pin for "@robrain/shared": "2.4.9" — worth landing both in the same window.
  • Ownership stays asymmetric: shared belongs to the org, the unscoped robrain CLI stays on the personal account unless transferred.

🤖 Generated with Claude Code

@adelinamart adelinamart left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against the repo rather than the diff — the packaging reasoning is sound and most of it checks out, but two things would bite on the first real release.

🔴 The test exclusion doesn't actually take effect

tsc doesn't clean its output directory. I applied the tsconfig change locally and rebuilt:

$ pnpm --filter @robrain/shared build
$ ls packages/shared/dist | grep -c '\.test\.'
28
db.test.d.ts   db.test.d.ts.map   db.test.js   db.test.js.map   …

Excluding src/**/*.test.ts stops new test files being emitted; every existing one stays. Since dist/ is already populated on the release machine, the published tarball would still contain compiled tests — the thing this change is meant to prevent. The npm pack --dry-run preflight would surface it, but it shouldn't depend on catching it by eye.

-    "prepack": "tsc",
+    "prepack": "rm -rf dist && tsc",

🔴 Publishing shared before the release guard can strand a version

publish:npm publishes @robrain/shared first, and the guard only runs later in the CLI's prepublishOnly. If it fails (missing tag or GHCR image — the 2.3.6/2.3.7 case it was built for), @robrain/shared@2.4.8 is already public with no matching CLI. npm blocks reusing that version for 24h after an unpublish, so the fix is bumping everything to 2.4.9 — leaving an orphan 2.4.8 the cloud could pin, which breaks the lockstep invariant the other 13 files maintain.

The stated goal (a cloud repin shouldn't wait on a CLI publish failure) still holds if the guard runs first — it works standalone and already has ROBRAIN_SKIP_RELEASE_GUARD=1 for emergencies:

-"publish:npm": "pnpm --filter @robrain/shared publish --access public --no-git-checks && …"
+"publish:npm": "pnpm --filter robrain build && node packages/cli/scripts/verify-release-artifacts.mjs && pnpm --filter @robrain/shared publish --access public --no-git-checks && …"

(The build has to come first — the guard imports dist/lib/release-guard.js.)

✅ Verified, no action needed

  • No runtime dependencies is correct. Everything under packages/shared/src imports only node: builtins, and db.ts deliberately uses a structural PoolLike "to avoid a pg dependency in shared". Nothing to declare.
  • exports already has a "." entry (the diff hides it in context), so import { … } from '@robrain/shared' resolves for registry consumers.
  • The exclusion is genuinely neededdist/ really does carry 28 test artifacts today.
  • No CI publishes recursively (only publish-perception-image.yml), so dropping private: true can't cause an accidental publish elsewhere.
  • prepare + prepack both running tsc is redundant but right: prepare keeps the git-pin path working, prepack covers registry publishes.

🟡 Worth folding in

  • Broken sourcemaps. declarationMap/sourceMap are on and files omits src, so consumers get .js.map / .d.ts.map pointing at a nonexistent ../src. Either add "src" to files or emit the published build without maps.
  • LICENSE isn't in the tarball. The CLI lists it in files; shared doesn't. Apache-2.0 §4(a) asks that recipients get a copy, and it's inconsistent between our two published packages.
  • No README → blank npm page. A few lines (install, "part of the robrain monorepo", link back) would do.

Coordination

  • Don't merge until the @robrain org existspublish:npm now leads with the shared publish, so without the scope the first command fails and the release stops half-done. (Scope is clear on the registry side: no @robrain/* packages published, and robrain's maintainer is mart.adelina, so that's the account that needs to create it.)
  • Decide who publishes. The org-creation steps skip inviting members, which is fine if I cut the releases. If you're going to publish @robrain/shared, you need to be an org member with publish rights — and note the ownership will be asymmetric: shared belongs to the org, the unscoped robrain CLI stays on my personal account unless we transfer it.
  • This is inert until the cloud repo swaps its git-pin for "@robrain/shared": "2.4.8" — worth landing both in the same window so the version doesn't sit unused.
  • It touches packages/cli/src/commands/install.ts and src/index.ts, which have in-flight changes on main (plan-required install messaging, robrain status registration state). Expect a version-string conflict; whichever lands second rebases.
  • Two OTP prompts with 2FA now, one per package.

Make `@robrain/shared` a publishable package so consumers (the cloud repo)
can install a prebuilt tarball from the registry instead of the git-pin
`github:adelinamart/robrain#vX.Y.Z&path:packages/shared`, which forces a
full-monorepo download plus a TypeScript compile at install time.

packages/shared/package.json:
- drop `private: true`; add license/repository/homepage/bugs mirroring the CLI
- `files: ["dist", "schema.sql", "README.md", "LICENSE"]` — schema.sql stays at
  package root because the cloud mounts and resolves it from there
- `publishConfig.access: "public"` — scoped packages default to restricted,
  which the free `@robrain` org cannot host
- export `./schema.sql` and `./package.json` so consumers can resolve the
  schema directly; both additions are backwards compatible

Build hygiene. Excluding `src/**/*.test.ts` in tsconfig.json is not enough on
its own: `tsc` never cleans its output directory, so a release machine with a
populated `dist/` would keep every previously-compiled `*.test.js` and ship it.
`prepare` and `prepack` are therefore `rm -rf dist && tsc -p
tsconfig.publish.json`. Both hooks are needed because npm runs `prepare` after
`prepack`, so `prepare` determines what actually lands in the tarball.
tsconfig.publish.json also disables sourceMap/declarationMap: `files` does not
ship `src/`, so published `.js.map` / `.d.ts.map` would point at a `../src`
that is not in the tarball. Plain `build` keeps maps for local dev, and the new
tsconfig.typecheck.json re-includes tests so `pnpm typecheck` still covers them.

Root package.json: `publish:npm` builds the CLI and runs
verify-release-artifacts.mjs *before* publishing @robrain/shared. Publishing
shared first is deliberate — it has no coupling to the Docker image, so a cloud
repin should not wait on a CLI publish failure — but the CLI's own
prepublishOnly guard runs last in the chain. Without the up-front check, a guard
failure there (the 2.3.6/2.3.7 case) would leave @robrain/shared already public
with no matching CLI, and npm blocks reusing a version for 24h after an
unpublish. The only fix would be bumping to the next patch, orphaning a version
the cloud could pin and breaking the lockstep invariant.

docs/release.md: document the two-package npm step, the one-time @robrain org
creation, the pack preflight, and why the guard runs up front.

Version 2.4.9 across the 13 lockstep files — 2.4.8 is already tagged and
published from main, so this packaging change takes the next patch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@radubusuioc
radubusuioc force-pushed the claude/publish-robrain-shared-npm-59e3db branch from bbc87d5 to 6f25325 Compare July 28, 2026 16:42
@radubusuioc radubusuioc changed the title chore(release): publish @robrain/shared to npm; bump to 2.4.8 chore(release): publish @robrain/shared to npm; bump to 2.4.9 Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants