Skip to content

Drive lockstep polyglot publishing via Changesets#93

Merged
brentrager merged 4 commits into
mainfrom
th-changesets-polyglot
Jul 17, 2026
Merged

Drive lockstep polyglot publishing via Changesets#93
brentrager merged 4 commits into
mainfrom
th-changesets-polyglot

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

release.yml runs changesets/action with publish: pnpm ci:publish, but Changesets only bumps the npm package.json and ci:publish was changeset publish — so a release published npm only. The Rust crate, .NET/NuGet package, Python/PyPI package, and Go module each had their own separate publish-*.yml workflow (manual dispatch / version tags) and drifted to independent versions (rust 0.16.2, .NET 1.6.0, python 1.3.2, npm 0.22.0).

Solution

Wire Changesets to publish all polyglot artifacts in lockstep at one canonical version (the npm package @smooai/smooth-operator-core), mirroring the reference pattern in the smooth repo.

  • scripts/sync-versions.mjs — reads the canonical version from typescript/core/package.json and propagates it to every publishable manifest: Rust Cargo.toml [package] version, .NET csproj <Version>, Python pyproject.toml, and go/version.go (the anchor for the go/vX.Y.Z tag). Fails loudly if any anchor is missing — a partial sync aborts the release rather than shipping a mismatched set. (Cargo.lock is gitignored here → not touched; smooth-operator-temporal is publish = false → not a target.)
  • scripts/ci-version.mjschangeset version + version:sync, wired as the version: input so the version PR carries every polyglot bump (not just npm).
  • scripts/ci-publish.mjs — idempotent per-registry orchestrator. Queries each registry (npm packument, crates.io sparse index, NuGet flat-container, PyPI JSON, git tag) and skips already-published versions; supports DRY_RUN (no tokens, no pushes); a failure in one language doesn't skip the others but still exits non-zero. Belt-and-suspenders: --skip-duplicate (NuGet), --check-url (PyPI).
  • release.yml — adds the polyglot toolchains (dotnet / uv / rust) and the publish-secret env (NODE_AUTH_TOKEN/SMOOAI_NPM_TOKEN, CARGO_REGISTRY_TOKEN/SMOOAI_CARGO_REGISTRY_TOKEN, NUGET_API_KEY/SMOOAI_NUGET_API_KEY, UV_PUBLISH_TOKEN/SMOOAI_PYPI_TOKEN) to the changesets step.
  • The per-language publish-*.yml workflows are kept as manual/out-of-band fallbacks, with headers noting the Changesets release now drives the normal path.

Safety — irreversible registries

Every registry is queried first and skipped if the version already exists, so a re-run never double-publishes and never hard-fails on an existing version. This PR publishes nothing.

Verification

  • node --check on all three scripts; all five workflows parse as YAML.
  • sync-versions.mjs run against the tree rewrote all four manifests to 0.22.0, then reverted.
  • DRY_RUN=true ci-publish.mjs (no tokens): npm skipped (0.22.0 already published), crates.io / NuGet / PyPI / Go all packed/validated to would-publish, exit 0.

⚠️ Follow-up before the first real release (does NOT block this PR)

Current versions are not aligned: npm 0.22.0, rust 0.16.2, .NET 1.6.0, python 1.3.2. On the first lockstep release, every artifact is forced to the npm version — which would downgrade .NET (1.6.0 → 0.2x) and Python (1.3.2 → 0.2x) on NuGet/PyPI (new versions, so skip-if-exists won't catch it). Before enabling, do a one-time alignment: bump the canonical npm version above the current max (e.g. to 1.7.0) via a changeset so no registry goes backwards.

🤖 Generated with Claude Code

…sets

Wire the Changesets release to publish ALL smooth-operator-core artifacts
(npm + crates.io + NuGet + PyPI + Go tag) at one lockstep version, instead
of npm-only + separate manual publish-*.yml workflows.

- scripts/sync-versions.mjs: propagate the canonical npm version
  (typescript/core/package.json) to the Rust crate Cargo.toml, .NET csproj
  <Version>, Python pyproject, and go/version.go. Fails loudly if any
  version anchor is missing so a partial sync aborts the release.
- scripts/ci-version.mjs: `changeset version` + `version:sync` so the
  version PR carries every polyglot bump.
- scripts/ci-publish.mjs: idempotent per-registry orchestrator. Queries each
  registry and skips already-published versions; supports DRY_RUN (no tokens,
  no pushes); one language failing doesn't skip the others but still exits
  non-zero. Belt-and-suspenders: --skip-duplicate (NuGet), --check-url (PyPI).
- release.yml: add the polyglot toolchains (dotnet/uv/rust) + the publish
  secret env (same names as the manual workflows), and wire
  version: pnpm ci:version + publish: pnpm ci:publish.
- publish-*.yml: kept as manual fallbacks, headers noted accordingly.

Nothing is published by this PR. Verified via a local DRY_RUN of ci-publish
(npm skipped as already-published; crates/NuGet/PyPI/Go pack+validate to
would-publish) and by running sync-versions against the tree and reverting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01f1331

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/smooth-operator-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

brentrager and others added 3 commits July 17, 2026 14:41
… changeset → 1.7.0 first lockstep release)
Elevate every README (root + Rust/TS/Python/Go/.NET package pages) from a
feature list into a narrative: hook → one engine in five languages →
observe→think→act → the permission gate + deny-policy that makes an agent safe
to point at production → build → get started.

- Each package page leads with an agent+tool quickstart in its own idiom (mock
  scripted to call the tool, then answer), verified against the real API.
- Adds the headline permission system + deny-policy (AutoMode, circuit-breakers,
  declarative TOML rules + semantic predicates) to every features list and gives
  it a dedicated safety-story section with a language-native example
  (with_deny_policy in Rust, denyPolicy/permissionMode in TS/Py, WithDenyPolicy
  in Go/C#).
- Refreshes the root polyglot table (language → package → registry), fixes the
  stale "Planned" statuses (TS/Python/.NET are at parity now) and inconsistent
  test-count claims.

Docs only — no code changes. Registry readme-pointers already wired (Cargo
readme, pyproject readme, csproj PackageReadmeFile+packed README; npm ships
README by default).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brentrager
brentrager merged commit fe882c3 into main Jul 17, 2026
4 of 5 checks 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