|
| 1 | +# Release Strategy: Changesets + Turborepo + GitHub Actions |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Automated package releases using Changesets with: |
| 6 | +- Beta releases on `develop` branch pushes |
| 7 | +- Stable releases on `main` branch merges |
| 8 | +- Automatic changelog generation with GitHub PR/commit links |
| 9 | +- Hierarchical dependency handling (lexicon > core > react) |
| 10 | +- Husky pre-commit warning for missing changesets |
| 11 | + |
| 12 | +## Package Dependency Hierarchy |
| 13 | + |
| 14 | +``` |
| 15 | +@hypercerts-org/lexicon (base - no internal deps) |
| 16 | + | |
| 17 | +@hypercerts-org/sdk-core (depends on lexicon) |
| 18 | + | |
| 19 | +@hypercerts-org/sdk-react (depends on sdk-core) |
| 20 | +``` |
| 21 | + |
| 22 | +## Implementation Steps |
| 23 | + |
| 24 | +### Step 1: Install Changesets |
| 25 | + |
| 26 | +```bash |
| 27 | +pnpm add -Dw @changesets/cli @changesets/changelog-github |
| 28 | +pnpm changeset init |
| 29 | +``` |
| 30 | + |
| 31 | +### Step 2: Create .changeset/config.json |
| 32 | + |
| 33 | +```json |
| 34 | +{ |
| 35 | + "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json", |
| 36 | + "changelog": ["@changesets/changelog-github", { "repo": "hypercerts-org/hypercerts-sdk" }], |
| 37 | + "commit": false, |
| 38 | + "fixed": [], |
| 39 | + "linked": [], |
| 40 | + "access": "public", |
| 41 | + "baseBranch": "main", |
| 42 | + "updateInternalDependencies": "patch", |
| 43 | + "ignore": [] |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +Key setting: `updateInternalDependencies: "patch"` ensures when lexicon bumps, sdk-core dependency updates automatically. |
| 48 | + |
| 49 | +### Step 3: Update Root package.json Scripts |
| 50 | + |
| 51 | +Add these scripts: |
| 52 | + |
| 53 | +```json |
| 54 | +{ |
| 55 | + "scripts": { |
| 56 | + "changeset": "changeset", |
| 57 | + "version-packages": "changeset version", |
| 58 | + "release": "turbo build && changeset publish" |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +### Step 4: Update .husky/pre-commit |
| 64 | + |
| 65 | +Replace contents with: |
| 66 | + |
| 67 | +```bash |
| 68 | +pnpm build |
| 69 | + |
| 70 | +# Warn if committing package changes without a changeset |
| 71 | +if git diff --cached --name-only | grep -q "^packages/" && \ |
| 72 | + ! git diff --cached --name-only | grep -q "^\.changeset/.*\.md$"; then |
| 73 | + echo "" |
| 74 | + echo "Warning: You are committing package changes without a changeset." |
| 75 | + echo "Run 'pnpm changeset' before pushing if this includes user-facing changes." |
| 76 | + echo "" |
| 77 | +fi |
| 78 | +``` |
| 79 | + |
| 80 | +### Step 5: Create GitHub Workflows |
| 81 | + |
| 82 | +#### A. .github/workflows/pr-check.yml |
| 83 | + |
| 84 | +```yaml |
| 85 | +name: PR Check |
| 86 | + |
| 87 | +on: |
| 88 | + pull_request: |
| 89 | + branches: [main, develop] |
| 90 | + |
| 91 | +jobs: |
| 92 | + build-and-test: |
| 93 | + runs-on: ubuntu-latest |
| 94 | + steps: |
| 95 | + - uses: actions/checkout@v4 |
| 96 | + with: |
| 97 | + fetch-depth: 0 |
| 98 | + - uses: pnpm/action-setup@v4 |
| 99 | + - uses: actions/setup-node@v4 |
| 100 | + with: |
| 101 | + node-version: 20 |
| 102 | + cache: "pnpm" |
| 103 | + - run: pnpm install |
| 104 | + - run: pnpm build |
| 105 | + - run: pnpm test |
| 106 | + - run: pnpm lint |
| 107 | + |
| 108 | + changeset-check: |
| 109 | + runs-on: ubuntu-latest |
| 110 | + steps: |
| 111 | + - uses: actions/checkout@v4 |
| 112 | + with: |
| 113 | + fetch-depth: 0 |
| 114 | + - uses: pnpm/action-setup@v4 |
| 115 | + - uses: actions/setup-node@v4 |
| 116 | + with: |
| 117 | + node-version: 20 |
| 118 | + cache: "pnpm" |
| 119 | + - run: pnpm install |
| 120 | + - name: Check for changeset |
| 121 | + run: | |
| 122 | + if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "^packages/"; then |
| 123 | + if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "^\.changeset/.*\.md$"; then |
| 124 | + echo "Changeset found" |
| 125 | + else |
| 126 | + echo "::warning::No changeset found. Run 'pnpm changeset' if this PR includes user-facing changes." |
| 127 | + fi |
| 128 | + else |
| 129 | + echo "No package changes detected" |
| 130 | + fi |
| 131 | +``` |
| 132 | +
|
| 133 | +#### B. .github/workflows/release.yml (Stable from main) |
| 134 | +
|
| 135 | +```yaml |
| 136 | +name: Release |
| 137 | + |
| 138 | +on: |
| 139 | + push: |
| 140 | + branches: [main] |
| 141 | + |
| 142 | +concurrency: ${{ github.workflow }}-${{ github.ref }} |
| 143 | + |
| 144 | +jobs: |
| 145 | + release: |
| 146 | + runs-on: ubuntu-latest |
| 147 | + permissions: |
| 148 | + contents: write |
| 149 | + pull-requests: write |
| 150 | + id-token: write |
| 151 | + |
| 152 | + steps: |
| 153 | + - uses: actions/checkout@v4 |
| 154 | + with: |
| 155 | + fetch-depth: 0 |
| 156 | + - uses: pnpm/action-setup@v4 |
| 157 | + - uses: actions/setup-node@v4 |
| 158 | + with: |
| 159 | + node-version: 20 |
| 160 | + cache: "pnpm" |
| 161 | + registry-url: "https://registry.npmjs.org" |
| 162 | + - run: pnpm install |
| 163 | + - run: pnpm build |
| 164 | + - run: pnpm test |
| 165 | + |
| 166 | + - name: Create Release Pull Request or Publish |
| 167 | + id: changesets |
| 168 | + uses: changesets/action@v1 |
| 169 | + with: |
| 170 | + publish: pnpm release |
| 171 | + version: pnpm version-packages |
| 172 | + title: "chore: release packages" |
| 173 | + commit: "chore: release packages" |
| 174 | + env: |
| 175 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 176 | + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} |
| 177 | + |
| 178 | + - name: Log published packages |
| 179 | + if: steps.changesets.outputs.published == 'true' |
| 180 | + run: echo "Published - ${{ steps.changesets.outputs.publishedPackages }}" |
| 181 | +``` |
| 182 | +
|
| 183 | +#### C. .github/workflows/release-beta.yml (Beta from develop) |
| 184 | +
|
| 185 | +```yaml |
| 186 | +name: Release Beta |
| 187 | + |
| 188 | +on: |
| 189 | + push: |
| 190 | + branches: [develop] |
| 191 | + |
| 192 | +concurrency: ${{ github.workflow }}-${{ github.ref }} |
| 193 | + |
| 194 | +jobs: |
| 195 | + release-beta: |
| 196 | + runs-on: ubuntu-latest |
| 197 | + permissions: |
| 198 | + contents: write |
| 199 | + id-token: write |
| 200 | + |
| 201 | + steps: |
| 202 | + - uses: actions/checkout@v4 |
| 203 | + with: |
| 204 | + fetch-depth: 0 |
| 205 | + token: ${{ secrets.GITHUB_TOKEN }} |
| 206 | + - uses: pnpm/action-setup@v4 |
| 207 | + - uses: actions/setup-node@v4 |
| 208 | + with: |
| 209 | + node-version: 20 |
| 210 | + cache: "pnpm" |
| 211 | + registry-url: "https://registry.npmjs.org" |
| 212 | + - run: pnpm install |
| 213 | + - run: pnpm build |
| 214 | + - run: pnpm test |
| 215 | + |
| 216 | + - name: Enter prerelease mode (if not already) |
| 217 | + run: | |
| 218 | + if [ ! -f .changeset/pre.json ]; then |
| 219 | + pnpm changeset pre enter beta |
| 220 | + git config user.name "github-actions[bot]" |
| 221 | + git config user.email "github-actions[bot]@users.noreply.github.com" |
| 222 | + git add .changeset/pre.json |
| 223 | + git commit -m "chore: enter beta prerelease mode" |
| 224 | + fi |
| 225 | +
|
| 226 | + - name: Version packages |
| 227 | + run: pnpm changeset version |
| 228 | + env: |
| 229 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 230 | + |
| 231 | + - name: Publish beta packages |
| 232 | + run: pnpm changeset publish --tag beta |
| 233 | + env: |
| 234 | + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} |
| 235 | + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
| 236 | + |
| 237 | + - name: Commit and push version changes |
| 238 | + run: | |
| 239 | + git config user.name "github-actions[bot]" |
| 240 | + git config user.email "github-actions[bot]@users.noreply.github.com" |
| 241 | + git add -A |
| 242 | + git diff --staged --quiet || git commit -m "chore: version packages (beta)" |
| 243 | + git push |
| 244 | +``` |
| 245 | +
|
| 246 | +### Step 6: Delete Old Workflows |
| 247 | +
|
| 248 | +Remove these files: |
| 249 | +- .github/workflows/create-release-sdk.yml |
| 250 | +- .github/workflows/create-prerelease-sdk.yml |
| 251 | +- .github/workflows/dryrun-release-ci-sdk.yml |
| 252 | +
|
| 253 | +## Files Summary |
| 254 | +
|
| 255 | +| File | Action | |
| 256 | +|------|--------| |
| 257 | +| .changeset/config.json | Create | |
| 258 | +| package.json (root) | Add 3 scripts | |
| 259 | +| .husky/pre-commit | Update | |
| 260 | +| .github/workflows/pr-check.yml | Create | |
| 261 | +| .github/workflows/release.yml | Create | |
| 262 | +| .github/workflows/release-beta.yml | Create | |
| 263 | +| .github/workflows/create-release-sdk.yml | Delete | |
| 264 | +| .github/workflows/create-prerelease-sdk.yml | Delete | |
| 265 | +| .github/workflows/dryrun-release-ci-sdk.yml | Delete | |
| 266 | +
|
| 267 | +## Developer Workflow |
| 268 | +
|
| 269 | +### Adding a Changeset |
| 270 | +
|
| 271 | +```bash |
| 272 | +pnpm changeset |
| 273 | +# 1. Select packages (space to select, enter to confirm) |
| 274 | +# 2. Choose bump type (major/minor/patch) |
| 275 | +# 3. Write changelog summary |
| 276 | +``` |
| 277 | + |
| 278 | +### Release Flow |
| 279 | + |
| 280 | +**Stable (main):** |
| 281 | +1. Merge PR with changeset to main |
| 282 | +2. Action creates "Release PR" accumulating changes |
| 283 | +3. Merge Release PR to publish @latest |
| 284 | + |
| 285 | +**Beta (develop):** |
| 286 | +1. Merge PR with changeset to develop |
| 287 | +2. Action auto-publishes to npm @beta |
| 288 | +3. Install via: npm install @hypercerts-org/sdk-core@beta |
| 289 | + |
| 290 | +### Exiting Beta (before merging develop to main) |
| 291 | + |
| 292 | +```bash |
| 293 | +pnpm changeset pre exit |
| 294 | +git add .changeset/pre.json |
| 295 | +git commit -m "chore: exit prerelease mode" |
| 296 | +``` |
| 297 | + |
| 298 | +## Required GitHub Secrets |
| 299 | + |
| 300 | +- NPM_TOKEN: npm automation token (npmjs.com > Access Tokens > Automation) |
| 301 | + |
| 302 | +## Optional: Changeset Bot |
| 303 | + |
| 304 | +Install Changeset Bot (https://github.com/apps/changeset-bot) for PR comments about missing changesets. |
0 commit comments