Skip to content

Release Process

Cindy Zhang edited this page Jun 23, 2026 · 4 revisions

Release Process

Status: Active. All packages ship at the same version to the registry. CI handles publishing automatically — merge a version bump to main and packages are published. Canary builds are published on every PR.

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 the registry automatically
  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):

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

@xds/lab, @xds/vega, @xds/theme-brutalist, and the apps/internal packages are private and never published. @xds/storybook and @xds/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 @xds/core@0.0.15, you use @xds/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:

---
'@xds/core': patch
'@xds/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:
---
'@xds/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 @xds/core/Layout@xds/core/Stack

How to audit

git log v0.0.13..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

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. pnpm changeset publish publishes any packages whose version is not yet on the registry
  4. Already-published versions are skipped (npm 409 Conflict → no-op)

No manual npm publish needed. No npm auth tokens on your machine. No corp network requirement. CI handles everything via a registry auth token secret.


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 the registry, 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

CI publishes a canary version on every PR commit automatically via the canary job in ci.yml. No manual steps needed.

Version format

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

How consumers install a canary

npm install @xds/core@canary

Safety guarantee: npm install @xds/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

Pre-Release Checklist

  • All breaking changes have codemods with tests
  • CHANGELOGs follow the standard format
  • All packages at the same version number
  • Previous release is tagged
  • Version bump PR created and reviewed

After merge (CI publishes)

  • Verify: npm view @xds/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 publish that build.

git checkout vX.X.X
pnpm install && pnpm build
# Configure registry auth, then: pnpm changeset publish
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

# 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