Skip to content

Release Process

cixzhang edited this page Jun 24, 2026 · 4 revisions

Release Process

Status: Active. All packages ship at the same version to public npm (npmjs.com), under the @astryxdesign/* scope. CI handles publishing automatically — merge a version bump to main and packages are published. Publishing uses npm trusted publishing (OIDC) — there is no NPM_TOKEN anywhere. Current published baseline: @astryxdesign/core@0.0.15.

Overview

Astryx releases all packages at the same version number for clear compatibility. The release process:

  1. Accumulate changes — PRs land on main with changesets
  2. Identify codemods — Breaking changes get AST-based codemods
  3. Version bumppnpm version-packages applies changesets, create a PR
  4. Merge — CI builds and publishes to public npm automatically (tokenless OIDC)
  5. Post-release — Tag, update agent docs, internal sync, announce

Packages

All non-private packages are published together at the same version (a Changesets fixed group). All 12 publishable packages share the @astryxdesign/* scope and are published to public npm:

Package Contents
@astryxdesign/core Components, hooks, utilities, tokens, CSS
@astryxdesign/cli CLI commands: init, swizzle, upgrade, agent-docs, component, docs, template
@astryxdesign/build Shared build tooling (tsup/StyleX config)
@astryxdesign/theme-default Default theme (Heroicons)
@astryxdesign/theme-neutral Neutral theme (Lucide icons)
@astryxdesign/theme-butter · theme-chocolate · theme-daily · theme-gothic · theme-matcha · theme-stone · theme-y2k Additional published themes

@astryxdesign/lab, @astryxdesign/vega, @astryxdesign/theme-brutalist, and the apps/internal packages are private and never published. @astryxdesign/storybook and @astryxdesign/sandbox are in the changesets ignore list. (Source of truth: the fixed array in .changeset/config.json.)

Rule: All packages bump to the same version. This is enforced by the fixed group — a single changeset co-bumps every publishable package, so if you're on @astryxdesign/core@0.0.15, you use @astryxdesign/theme-default@0.0.15.


Step 1: Changesets

Every PR that changes published package behavior should include a changeset.

Creating a changeset

pnpm changeset:new

This Astryx wrapper (around the Changesets CLI):

  1. Auto-detects which publishable packages your working tree touched and pre-selects them — no hand-enumerating the frontmatter.
  2. Prompts for a category (breaking, component, feat, fix, perf, docs, chore) — this drives changelog grouping, not the semver bump.
  3. Captures the contributor(s) — defaults to your gh/git identity, so credit is recorded at authoring time.
  4. Forces a patch bump while we're pre-1.0 (see the rule below).

The changeset file is committed with the PR. The Astryx body convention is a [category] headline followed by a @handle line:

---
'@astryxdesign/core': patch
'@astryxdesign/cli': patch
---

[component] Added CommandPalette component and `xds upgrade` CLI command (#2717)
@yourhandle

You can pass everything as flags for non-interactive use:

pnpm changeset:new --category fix --summary "" --pr 2717 --contributor yourhandle

The bare pnpm changeset CLI still works, but then you must follow the body convention by hand. CI (pnpm check:changesets, part of check:repo) rejects any changeset that is missing a category or contributor, declares a minor/major bump while pre-1.0, or names a private/ignored package.

⚠️ Pre-1.0 rule: Always use patch for changesets while we're on 0.0.x. The Changesets CLI treats minor as a semver-minor bump, which jumps 0.0.x → 0.1.0. Signal a breaking change with the [breaking] category, not a minor/major bump. pnpm changeset:new enforces patch automatically; pnpm check:changesets is the CI backstop. (When we hit 1.0, the patch-only gate lifts automatically — it keys off whether publishable packages are still 0.x.)

When to create a changeset

Change type Category Bump (pre-1.0) Changeset needed?
New component component patch Yes
New feature/prop feat patch Yes
Bug fix fix patch Yes
Prop rename breaking patch Yes — also needs a codemod
Component removal breaking patch Yes — also needs a codemod
Perf improvement perf patch Yes
Docs only No
Tests only No
Internal tooling (storybook, vibe-tests) No (these are in ignore list)

Changeset conventions

  • The first body line is [category] one-line user-facing summary (#PR).
  • The second line is the contributor handle(s): @yourhandle (space-separated for multiple).
  • If there's a breaking change, use the [breaking] category and mention the codemod:
---
'@astryxdesign/core': patch
---

[breaking] Renamed `items` prop to `options` on Selector for clarity (#2717)
@yourhandle

**Codemod:** `npx xds upgrade --codemod rename-selector-items-to-options`

Step 2: Identify Codemods

Before releasing, audit all changesets for breaking API changes that need codemods.

What needs a codemod

Change Codemod needed? Example
Prop renamed ✅ Yes itemsoptions on Selector
Prop removed ✅ Yes Remove deprecated prop, add TODO comment
Component renamed ✅ Yes HStackStack direction="horizontal"
Component removed ✅ Yes Replace with new component + direction/variant prop
Callback signature changed ✅ Yes onHide: () => voidonOpenChange: (isOpen: boolean) => void
Two props merged into one ✅ Yes onShow/onHideonOpenChange
New required prop added ⚠️ Maybe If there's a sensible default, no codemod needed
New optional prop ❌ No Additive, non-breaking
New component ❌ No Additive
Bug fix ❌ No Unless it changes expected behavior
Import path changed ✅ Yes @astryxdesign/core/Layout@astryxdesign/core/Stack

How to audit

git log v0.0.15..HEAD --oneline -- packages/core/src/ | grep -iE 'rename|remove|refactor|unify|deprecat|breaking'

Or check the accumulated changesets:

ls .changeset/*.md

Writing a codemod

Codemods live in packages/cli/src/codemods/transforms/v{VERSION}/. Each transform is a jscodeshift module. See existing transforms for patterns (prop rename, component rename, import rewrite).

Registering a codemod

  1. Create the transform file in transforms/v{VERSION}/
  2. Create a test file in transforms/v{VERSION}/__tests__/
  3. Add it to transforms/v{VERSION}/index.mjs manifest
  4. If it's a new version directory, add it to codemods/registry.mjs

Testing codemods

pnpm test --run packages/cli/src/codemods/

Step 3: Version Bump

Once all PRs are merged and codemods are ready:

pnpm version-packages

This runs changeset version (bumps versions, generates CHANGELOGs, deletes the consumed changesets) and then scripts/format-changelogs.mjs to format the output.

All publishable packages are a Changesets fixed group, so a single changeset co-bumps all of them to the same version automatically — no need to manually bump packages to match. Only genuinely-affected packages receive a changelog entry; the rest get a clean version-only bump.

CHANGELOG formatting (automatic)

pnpm version-packages already runs scripts/format-changelogs.mjs, which rewrites each just-bumped package CHANGELOG into the doc-site format:

Element Heading level Example
Version # 0.0.16 (h1) # 0.0.16
Section #### Breaking Changes (h4) #### Fixes
Divider --- between versions ---

Category sections render in canonical order (Breaking Changes → New Components → New Features → Fixes → Performance → Documentation → Other Changes). The formatter is idempotent and has a --check mode (node scripts/format-changelogs.mjs --check) for CI drift detection. You normally don't touch CHANGELOGs by hand — just review the generated output.

This aligns with the doc site's Markdown rendering which uses headingLevelStart={1}, giving version numbers prominent h1 sizing.

Contributors (automatic)

The #### Contributors section is generated from the @handle lines captured in each changeset at authoring time — so it credits the real humans, not the release bot. No git log / gh pr list reconstruction needed.

If a contributor is missing (e.g. an old-format changeset slipped through), add their @handle to the relevant changeset body and re-run pnpm version-packages, or edit the generated #### Contributors list directly.

Create a version bump PR

git checkout -b chore/version-packages-vX.X.X
git add .
git commit -m "chore: version packages for vX.X.X"
git push -u origin chore/version-packages-vX.X.X
gh pr create --title "chore: version packages for vX.X.X" --body "Version bump for release X.X.X"

Merge the PR. CI takes it from here.


Step 4: CI Publishes Automatically (Trusted Publishing)

When the version bump PR merges to main, the Deploy workflow (deploy.yml) runs automatically:

  1. Tests pass
  2. All packages are built (pnpm build)
  3. The publish job publishes any @astryxdesign/* package whose version is not yet on npm, via pnpm publish ... --provenance --access public --no-git-checks
  4. Already-published versions are skipped (npm 409 Conflict → no-op), so re-running main is safe

No manual npm publish needed. No npm auth tokens — anywhere. Publishing uses npm trusted publishing (OIDC):

  • There is no NPM_TOKEN secret in CI or on anyone's machine. The publish (and canary) jobs are granted id-token: write; pnpm exchanges a short-lived GitHub OIDC token for a registry credential at publish time.
  • npm trusts exactly one GitHub Actions workflow — deploy.yml in facebook/astryx — and nothing else. The trust is matched against the OIDC token's workflow_ref (the calling workflow), so the file that must be trusted is the one that runs the publish (deploy.yml), not any reusable workflow.
  • Every published package carries provenance (--provenance) — a cryptographic attestation of where and how it was built, linking the npm tarball back to the exact GitHub commit + workflow run.

Background: PR #3037 migrated publishing to public npm; PR #3043 replaced the legacy token with OIDC trusted publishing. The repo doc docs/release.md is the deeper reference for the OIDC mechanics, pnpm/npm version floors, and the 2FA flow during setup.

Adding a NEW package (one-time bootstrap by an npm org owner)

Trusted publishing is currently configured per-package — there is no org-wide trusted publishing yet. npm also can't register trust on a name that doesn't exist on the registry (unlike PyPI, there is no "pending publisher"). So whenever a new @astryxdesign/* package is added to the publishable set, an npm org owner (e.g. @cixzhang) must bootstrap it before CI can publish it:

npm i -g npm@latest
npm login --registry https://registry.npmjs.org   # must be an @astryxdesign org owner
pnpm run setup-trusted-publishing                  # audit: shows which packages need bootstrap/trust
pnpm run setup-trusted-publishing --bootstrap --setup-trust

What this does:

  1. Bootstrap — publishes a deprecated placeholder 0.0.0-bootstrap.0 stub (under the bootstrap dist-tag, never latest) to claim the package name on npm. This is why a brand-new package first appears on npm at version 0.0.0-bootstrap.0.
  2. Setup-trust — runs npm trust github <pkg> --file deploy.yml --repo facebook/astryx to register deploy.yml as the trusted OIDC publisher for that package.

After the first real OIDC publish from CI, the bootstrap stub is superseded by the real version (it lingers only under the deprecated bootstrap dist-tag). Skip this step and CI's publish of the new package will fail — npm rejects the OIDC publish for a name it has no trust config for. This is a required manual step in the "add a package" path until org-wide trusted publishing exists.

The pnpm run setup-trusted-publishing script is a maintainer-only, run-locally tool with an interactive npm session — it is decoupled from CI and never publishes real releases. It supports --dry-run and an audit-only default (no flags). Requires npm ≥ 11.10 for npm trust.


Step 5: Post-Release

  1. Tag the release in git:

    git tag vX.X.X
    git push --tags
  2. Update agent docs in consumer projects:

    npx xds agent-docs
  3. Notify consumers — post in the Astryx chat spaces with a summary of changes and the upgrade command.


Step 6: Internal Sync

After CI publishes to public npm, internal consumers need to be updated. This involves updating shared libraries in the internal monorepo and submitting a diff.

How this enables the update loop

Once the internal sync lands:

  1. When an AI agent runs xds component <Name>, the CLI reads LATEST_VERSION via the xds.versionFile config
  2. If the installed version is older, the CLI prints an upgrade nudge
  3. The user decides when to upgrade — the agent won't upgrade automatically

Step 7: Release Announcement

Post a release announcement to the Astryx Open Source Workplace group and GChat space (spaces/AAQAmz6AQ8Y).

Write every line item from the builder's perspective — how they experience the change in their product or dev workflow.

Rules:

  1. Lead with the outcome — what's better for the builder now?
  2. One sentence max per highlight
  3. Skip internal-only changes
  4. Breaking changes: reassure, don't alarm — mention the codemod handles it
  5. Group related fixes

Canary Builds

⚠️ Canaries are not publishing right now (repo is still private). The canary job publishes with --provenance, and npm provenance requires a public source repo — it fails with HTTP 422 while facebook/astryx is private. Canary publishes will start working automatically once the repo goes public. The mechanics below describe the intended behavior post-go-live.

The canary job in deploy.yml publishes a canary version on every push to main. The @canary dist-tag always points at the latest main commit. No manual steps needed.

Version format

<base-version>-canary.<short-sha>
e.g. 0.0.15-canary.fd7c751

How consumers install a canary

npm install @astryxdesign/core@canary

Safety guarantee: npm install @astryxdesign/core (no tag) always gets the stable @latest release. Canary versions live on a separate dist-tag.

When to use canaries

Scenario Use canary?
Test codemods on internal apps before a stable release ✅ Yes
Validate a breaking change on a feature branch ✅ Yes
Quick fix for a codemod bug post-release ✅ Yes
Routine release with no breaking changes ❌ No — just publish stable

Provenance (pending go-live)

Astryx is configured to publish with provenance — a signed, cryptographic attestation that links each npm tarball to the exact GitHub commit and workflow run that built it. Because provenance does not work on private repos, the full provenance story is gated on the repo going public:

  • While facebook/astryx is private, provenance fails (HTTP 422). This is why canaries don't publish yet (see above).
  • PR #3050 ([DO NOT LAND] until go-live) switches the stable publish job to a per-package loop that enforces provenance on the latest tag — a publish that can't attach provenance fails the job rather than silently shipping an unattested package. It lands once the repo is public.

Once the repo is public, both stable and canary publishes attach provenance automatically with no further action.


Pre-Release Checklist

  • All breaking changes have codemods with tests
  • CHANGELOGs follow the standard format
  • All packages at the same version number
  • Any new package has been bootstrapped + trust-configured by an npm org owner (pnpm run setup-trusted-publishing)
  • Previous release is tagged
  • Version bump PR created and reviewed

After merge (CI publishes)

  • Verify: npm view @astryxdesign/core dist-tags
  • Tag: git tag vX.X.X && git push --tags
  • Internal sync diff submitted
  • Release announcement posted

Republishing an Older Version

⚠️ Never publish old version numbers from current main. Always checkout the exact tag, build from that commit, and re-run the deploy. Because publishing is tokenless OIDC trusted publishing (no local npm token), the supported path is to re-trigger deploy.yml for that commit rather than publishing by hand:

git checkout vX.X.X
pnpm install && pnpm build
# Publishing happens in CI via deploy.yml (OIDC trusted publishing) — there is no
# local npm token. Re-run the Deploy workflow for this ref (workflow_dispatch) so the
# tokenless publish/provenance path runs; the publish job no-ops any already-live version.
git checkout main

Quick Reference

# Add a changeset to your PR (auto-detects packages, prompts category + contributor)
pnpm changeset:new

# Check pending changesets
ls .changeset/*.md

# Validate changesets (category, contributor, patch-only) — also runs in CI
pnpm check:changesets

# Version bump (changeset version + CHANGELOG formatting)
pnpm version-packages

# Create version bump PR, merge → CI publishes automatically (OIDC trusted publishing, no token)

# Adding a NEW package: an npm org owner bootstraps + registers trust (one-time, local)
pnpm run setup-trusted-publishing                            # audit
pnpm run setup-trusted-publishing --bootstrap --setup-trust  # claim name + register deploy.yml as trusted publisher

# Consumers upgrade
npx xds upgrade --apply

Related

  • Distribution — Packages, versioning, source and dist bundles
  • API Conventions — Naming and prop conventions that inform when codemods are needed

Clone this wiki locally