feat(HAI-081): add changesets-based automated versioning and release workflow

- Install and configure @changesets/cli with commit/access settings
- Create version PR workflow (.github/workflows/version.yml) for automated version bumps
- Update existing release and test-release workflows with version validation
- Add example changeset and version tests for CI verification
- Add RELEASING.md guide and update README with versioning documentation
This commit is contained in:
Dustin Byrne
2026-03-26 00:53:13 -04:00
parent 7deca373f7
commit b12d34010c
10 changed files with 961 additions and 3 deletions

8
.changeset/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets).
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).

View File

@@ -0,0 +1,5 @@
---
"hai": minor
---
Add automated versioning pipeline using changesets. Developers now add changeset files to describe changes, and a CI workflow automatically opens version PRs that bump versions and generate changelogs.

11
.changeset/config.json Normal file
View File

@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.3/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}

View File

@@ -45,6 +45,21 @@ jobs:
- name: Install Bun
uses: oven-sh/setup-bun@v2
- name: Extract version from tag
run: |
VERSION="${GITHUB_REF#refs/tags/v}"
echo "RELEASE_VERSION=${VERSION}" >> $GITHUB_ENV
echo "Releasing version: ${VERSION}"
- name: Verify tag matches package version
run: |
PKG_VERSION=$(node -p "require('./packages/cli/package.json').version")
if [ "$RELEASE_VERSION" != "$PKG_VERSION" ]; then
echo "ERROR: Tag version ($RELEASE_VERSION) does not match packages/cli/package.json version ($PKG_VERSION)"
exit 1
fi
echo "Version check passed: $RELEASE_VERSION"
- name: Install dependencies
run: pnpm install

56
.github/workflows/version.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Version Packages
on:
push:
branches:
- main
permissions:
contents: write
pull-requests: write
jobs:
version:
name: Version Packages
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: pnpm
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Create Release Pull Request or Tag
id: changesets
uses: changesets/action@v1
with:
version: pnpm release:version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Push version tag
if: steps.changesets.outputs.hasChangesets == 'false'
run: |
VERSION=$(node -p "require('./packages/cli/package.json').version")
TAG="v${VERSION}"
# Check if tag already exists
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists, skipping"
exit 0
fi
git tag "$TAG"
git push origin "$TAG"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -216,7 +216,11 @@ Download the latest binary from the [GitHub Releases](../../releases) page. Each
### Triggering a release
To create a new release, tag a version and push the tag:
Releases are automated via [changesets](https://github.com/changesets/changesets). See [RELEASING.md](./RELEASING.md) for the full workflow.
In short: add a changeset with `pnpm changeset`, merge to main, then merge the auto-generated Version Packages PR to trigger a release.
Manual fallback — tag a version and push:
```bash
git tag v0.1.0

62
RELEASING.md Normal file
View File

@@ -0,0 +1,62 @@
# Releasing
This project uses [changesets](https://github.com/changesets/changesets) for automated versioning and release management.
## How it works
### 1. Add a changeset
When you make a change that should be included in a release, add a changeset:
```bash
pnpm changeset
```
This will prompt you to:
- Select which packages are affected
- Choose the semver bump type (patch, minor, major)
- Write a summary of the change
A markdown file will be created in the `.changeset/` directory. Commit this file along with your code changes.
### 2. Version PR is created automatically
When changesets are merged to `main`, a GitHub Actions workflow automatically opens (or updates) a **"Version Packages"** pull request. This PR:
- Consumes all pending changeset files
- Bumps package versions according to the changeset declarations
- Generates/updates `CHANGELOG.md` files for affected packages
### 3. Merge the Version PR to release
When you merge the Version Packages PR:
- The workflow detects that all changesets have been consumed
- It creates a git tag `v{version}` based on the `hai` CLI package version
- The tag push triggers the existing release workflow (`.github/workflows/release.yml`)
- The release workflow builds binaries and creates a GitHub Release
## Manual release (fallback)
If you need to release manually, you can still push a version tag directly:
```bash
git tag v0.2.0
git push origin v0.2.0
```
This will trigger the release workflow. Note: the workflow validates that the tag version matches `packages/cli/package.json`, so make sure they're in sync.
## Available scripts
| Script | Description |
|--------|-------------|
| `pnpm changeset` | Add a new changeset |
| `pnpm changeset status` | Check pending changesets |
| `pnpm release:version` | Apply changesets and bump versions (used by CI) |
## Tips
- Every user-facing change should have a changeset — CI will remind you if one is missing
- You can add multiple changesets per PR if you're making changes to multiple packages
- Changeset files are automatically deleted when versions are bumped

View File

@@ -10,12 +10,20 @@
"build": "pnpm -r build",
"build:exe": "pnpm build && pnpm --filter hai build:exe",
"build:exe:all": "pnpm build && pnpm --filter hai build:exe:all",
"typecheck": "pnpm -r typecheck"
"typecheck": "pnpm -r typecheck",
"changeset": "changeset",
"version": "changeset version",
"release:version": "changeset version"
},
"pnpm": {
"onlyBuiltDependencies": ["esbuild", "koffi", "protobufjs"]
"onlyBuiltDependencies": [
"esbuild",
"koffi",
"protobufjs"
]
},
"devDependencies": {
"@changesets/cli": "^2.30.0",
"tsx": "^4.19.0",
"typescript": "^5.7.0"
}

View File

@@ -0,0 +1,46 @@
import { describe, it, expect } from "vitest";
import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
const repoRoot = join(import.meta.dirname!, "..", "..", "..", "..");
describe("Changeset configuration", () => {
it("should have a valid .changeset/config.json", () => {
const configPath = join(repoRoot, ".changeset", "config.json");
expect(existsSync(configPath)).toBe(true);
const config = JSON.parse(readFileSync(configPath, "utf-8"));
expect(config).toBeDefined();
expect(typeof config).toBe("object");
});
it("should have baseBranch set to main", () => {
const configPath = join(repoRoot, ".changeset", "config.json");
const config = JSON.parse(readFileSync(configPath, "utf-8"));
expect(config.baseBranch).toBe("main");
});
it("should have changeset scripts in root package.json", () => {
const pkgPath = join(repoRoot, "package.json");
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
expect(pkg.scripts.changeset).toBe("changeset");
expect(pkg.scripts.version).toBe("changeset version");
expect(pkg.scripts["release:version"]).toBe("changeset version");
});
it("should have .github/workflows/version.yml with expected content", () => {
const workflowPath = join(
repoRoot,
".github",
"workflows",
"version.yml",
);
expect(existsSync(workflowPath)).toBe(true);
const content = readFileSync(workflowPath, "utf-8");
expect(content).toContain("changesets/action");
expect(content).toContain("push");
expect(content).toContain("main");
});
});

743
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff