Skip to content

Distribution: CLI releases, install scripts, npm packages#22

Merged
koomen merged 7 commits into
mainfrom
distribution-infra
Jul 22, 2026
Merged

Distribution: CLI releases, install scripts, npm packages#22
koomen merged 7 commits into
mainfrom
distribution-infra

Conversation

@koomen

@koomen koomen commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Implements notes/distribution-plan.md end to end (every item except the first real release, which happens after merge per RELEASING.md).

What's here

  • Version plumbingbun scripts/set-version.ts X.Y.Z stamps root + all workspaces; lockstep is enforced in bun run ci; CHANGELOG.md feeds release notes.
  • CLI releasescli/build.js --all-targets cross-compiles darwin/linux × arm64/x64 (verified locally: all four binaries build and package); release.yml triggers on v* tags, runs the same one gate, asserts tag == version, and creates the GitHub Release with archives + checksums.txt.
  • install.sh / install.md — POSIX, checksum-verified, ~/.local/bin, never sudo; latest-version discovery reads asset names from checksums.txt so no GitHub API call. Hermetically tested in ci (local HTTP fixture, fake uname for platform mapping) plus a serving test pinning .shtext/plain and raw .md round-tripping.
  • npm packages (built JS + .d.ts, per the decision-2 flip) — the five packages tsc-emit to dist/ and stage publish-shaped tarballs; ci packs them, imports every package under plain Node from the real tarballs, and typechecks a NodeNext consumer. scripts/publish-packages.ts publishes from a clean tagged checkout in dependency order.

Discovered work (details in the plan note)

Publishing forced structural fixes the plan hadn't named: cross-package imports moved from relative ../../../shared paths to @scratchwork/* specifiers; publishable packages use explicit .ts import extensions (rewriteRelativeImportExtensions, with a d.ts post-process); the Bun-only svg text import became a generated module; and the server tooling workspace dissolved into server/core/src/deploy/ (proc.ts ported from Bun.spawn to node:child_process so deploys run under plain Node).

⚠️ Two calls to veto

  1. License: MIT — the repo had no LICENSE and npm packages need one. Added MIT at root, © Peter Koomen. Swap it before the first publish if you want something else.
  2. Effect-boundary allowlist grew by twoserver/core/src/deploy/{env,proc}.ts (pre-existing plain-Promise deploy tooling that moved into lint scope; rationale inline). An Effect-native rewrite of the deploy tooling chain would let the baseline shrink again.

Invariants: verified per AGENTS.md (report in the session) — auth/adapters/routes untouched beyond mechanical import-specifier rewrites; full bun run ci green.

🤖 Generated with Claude Code

Pete Koomen and others added 7 commits July 21, 2026 08:13
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 1 — version plumbing: scripts/set-version.ts stamps the lockstep
version across root + all workspaces; scripts/check-versions.ts enforces it
in the gate; CHANGELOG.md seeds the release-notes flow.

Phase 2 — CLI releases: cli/build.js --all-targets cross-compiles the four
release targets; scripts/package-release.ts produces tar.gz archives +
checksums.txt; .github/workflows/release.yml (tag v*) runs the same one gate,
asserts tag == lockstep version, and publishes the GitHub Release with the
CHANGELOG section as notes.

Phase 3 — install entry points: docs/install.sh (POSIX, checksum-verified,
~/.local/bin, no sudo; discovers the latest version from checksums.txt so no
GitHub API call) and docs/install.md (agent-readable manual steps).
scripts/check-install-sh.ts drives the full install loop against a local HTTP
fixture in ci; a shared serving test pins .sh-as-text/plain and raw .md
round-tripping.

Phase 4 — npm packages: the five publishable packages (shared, server-core,
server-deploy-{aws,cloudflare,local}) build to dist/ (tsc ESM +
declarations) and stage publish-shaped tarballs; scripts/check-npm-pack.ts
verifies tarball shape, imports every package under plain Node (Bun for
deploy-local), and typechecks a NodeNext consumer — all hermetically in ci.
scripts/publish-packages.ts publishes from a clean tagged checkout.

Discovered prerequisites shipped along the way: cross-package imports moved
from relative ../../../shared paths to @scratchwork/* specifiers with real
workspace deps; relative imports in publishable packages carry .ts extensions
for rewriteRelativeImportExtensions (d.ts specifiers post-processed); the
Bun-only svg text import became a generated module (freshness-checked); the
server tooling workspace dissolved into server/core/src/deploy/ with proc.ts
ported to node:child_process so deploys run under plain Node; MIT LICENSE
added (packages can't publish without one — flag if you want a different
license).

Phase 5 — RELEASING.md documents the loop end to end. The first real release
(v0.2.0) is the remaining unchecked plan item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bun.spawnSync blocks the event loop that the Bun.serve fixture needs to
answer the install script's curl (which has no timeout), so the check
hung forever — locally and in the GitHub CI run for this branch, which
died at the workflow's 20-minute timeout. Spawning async with awaited
stdout/stderr/exit keeps the fixture responsive; the check now runs in
about a second.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Encodes the release procedure as an agent skill: preflight (version choice,
changelog content, gh/npm auth), bump PR, merge + tag, npm publish from a
clean tag checkout, homepage republish, and a container smoke test of the
real install path. Irreversible steps (tag push, npm publish, homepage
republish) sit behind explicit confirmation gates. RELEASING.md stays the
normative doc and now points at the skill.

The release/ gitignore rule is now anchored to the repo root — unanchored it
also swallowed .claude/skills/release/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The directory was already the published scratchwork.dev site: the landing
page (index.md with the logo and project description), its components, and
the install.sh / install.md entry points. Naming it after the domain makes
that role explicit. References updated across README, renderer README,
RELEASING.md, the release skill (publish command now scratchwork.dev
--project www --public), AGENTS.md's outside-the-gate list, and the
install-sh check's script path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
scratchwork.dev/www is the published homepage project (landing page,
components, install.sh / install.md). scratchwork.dev/server is the
Cloudflare deployment workspace for the scratchwork.dev server, modeled on
deploy/cloudflare-vanilla: worker scratchwork-dev, R2 scratchwork-dev, D1
scratchwork-dev-projects, routes for scratchwork.dev, www.scratchwork.dev,
app.scratchwork.dev, and pages.scratchwork.dev, public OAuth with public
projects allowed. It joins the root workspaces (and therefore the gate),
with deploy:scratchwork-dev / local:scratchwork-dev root scripts.

default-renderer.generated.js is regenerated because bun.lock is a renderer
source-hash input and the new workspace changed the lockfile; the embedded
renderer HTML is byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@koomen
koomen merged commit 2f066ea into main Jul 22, 2026
1 check passed
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.

1 participant