Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 37 additions & 53 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: release
description: Prepare and execute a release to pub.dev. Bumps versions, validates changelogs, checks pub.dev status, and guides through the tiered publishing process.
description: Prepare and execute a release to pub.dev. Stamps placeholder versions from the tag in-runner (zero source churn), validates changelogs, checks pub.dev status, and guides through the tiered publishing process.
argument-hint: "<version>"
disable-model-invocation: true
allowed-tools: Bash, Read, Grep, Glob, WebFetch
Expand All @@ -10,6 +10,18 @@ allowed-tools: Bash, Read, Grep, Glob, WebFetch

Prepare and execute a dart_node release. This is a multi-step process that publishes packages in tiers due to interdependencies.

## Version model — placeholders only ([SWR-VERSION-BUILD-STAMPING])

**Every `version:` in `packages/*/pubspec.yaml` is `0.0.0-dev` at all times, on every branch.** The real
version is stamped from the tag **in the CI runner's working tree only** at publish time and is **never
committed, pushed, branched, or PR'd back**. A release therefore produces **ZERO churn** on tracked source.

- Do NOT bump `version:` in source. Do NOT merge a "release" PR that changes a placeholder to a real version
— there is no such PR anymore, and per the spec it must be rejected in review.
- Inter-package deps stay as local `path:` deps in source; `tools/prepare_publish.dart <version>` switches
them to `^<version>` in-runner at publish time.
- The only things committed for a release are **CHANGELOG** entries (release notes) — never version fields.

## Arguments

`$ARGUMENTS` = version to release (e.g., `0.12.0-beta`, `1.0.0`)
Expand All @@ -25,8 +37,10 @@ git status
git branch --show-current
```

- Must be on `main` branch (or create a release branch)
- Must be on `main` branch
- Working directory should be clean
- Every `packages/*/pubspec.yaml` `version:` must read `0.0.0-dev` (placeholder). If any shows a real
version, that is churn — reset it to `0.0.0-dev` before tagging.

### 1.2 Check current versions

Expand All @@ -42,34 +56,12 @@ dart run tools/prepare_publish.dart 2>&1 || true

This shows any packages missing from `tools/lib/packages.dart`.

## Step 2: Switch dependencies to pub.dev versions

**CRITICAL**: Before publishing, all internal dependencies must point to pub.dev versions, NOT local paths!

Use `tools/switch_deps.dart` to switch:

```bash
# Switch from local paths to pub.dev versioned dependencies
dart run tools/switch_deps.dart release
```

This converts:
```yaml
# FROM (local development):
dart_node_core:
path: ../dart_node_core

# TO (release):
dart_node_core: ^0.11.0-beta
```

To switch back to local for development:

```bash
dart run tools/switch_deps.dart local
```
## Step 2: Dependency switching is automatic (no action)

**Note**: The CI workflow handles this automatically on the release branch.
Internal deps stay as local `path:` deps in source. Each tier workflow runs
`dart run tools/prepare_publish.dart <version>` **in the runner working tree** before publishing, which
stamps `0.0.0-dev` → `<version>` and rewrites `path:` deps to `^<version>`. Nothing is committed — the
runner is discarded after publish. You do not switch deps by hand and there is no release branch.

## Step 3: Validate changelogs

Expand Down Expand Up @@ -147,20 +139,17 @@ Or run specific tiers:
./tools/test.sh --tier 3
```

## Step 7: Bump versions (dry run)
## Step 7: Preview the stamp (optional, local dry run)

Preview what changes the prepare script will make:
To see what the in-runner stamp will produce, run it locally then **discard the changes** (never commit):

```bash
dart run tools/prepare_publish.dart $ARGUMENTS
git restore packages # throw the stamp away — source must stay 0.0.0-dev
```

This updates:
- `version:` in all pubspec.yaml files
- Interdependencies to use `^$ARGUMENTS` (pub.dev versions)
- Removes `publish_to: none`

**Do NOT commit these changes yet** - they are made on a release branch by CI.
It stamps `0.0.0-dev` → `$ARGUMENTS` and rewrites `path:` deps to `^$ARGUMENTS`. CI does exactly this in the
runner working tree at publish time.

## Step 8: Create release tag

Expand All @@ -171,11 +160,12 @@ git tag "Release/$ARGUMENTS"
git push origin "Release/$ARGUMENTS"
```

This triggers the **publish-tier1** workflow which:
1. Creates a `release/$ARGUMENTS` branch
2. Runs `prepare_publish.dart`
3. Creates a PR to main
4. Publishes dart_logging and dart_node_core
This triggers the **publish-tier1** workflow which, in a single throwaway runner:
1. Validates the tag is on `main` and changelogs have `## $ARGUMENTS`
2. Stamps versions/deps in the working tree (NOT committed)
3. Publishes dart_logging and dart_node_core

No release branch, no commit, no PR — zero churn.

## Step 9: Tier 2 publishing

Expand All @@ -199,15 +189,8 @@ git push origin "Release-Tier3/$ARGUMENTS"

Publishes: dart_node_react, dart_node_react_native

After tier 3 completes, it switches dependencies back to local and pushes to the release branch.

## Step 11: Merge the release PR

After all tiers complete successfully:

1. Go to the PR created by tier 1
2. Verify all packages are published on pub.dev
3. Merge the PR to main
Each tier stamps in its own runner and publishes — nothing is committed, so there is no release branch to
merge and no dependency "switch back" step. The release is complete once tier 3's packages are live.

## Verification

Expand Down Expand Up @@ -239,7 +222,8 @@ Add the missing `## X.Y.Z` header to the package's CHANGELOG.md with release not
## Checklist summary

- [ ] On main branch, clean working directory
- [ ] **No local path dependencies** (all point to pub.dev versions)
- [ ] Every `packages/*/pubspec.yaml` `version:` is `0.0.0-dev` (placeholder)
- [ ] Internal deps are local `path:` deps (CI switches them in-runner)
- [ ] All changelogs have `## $ARGUMENTS` entries
- [ ] All READMEs are present and up-to-date
- [ ] All tests pass
Expand All @@ -249,4 +233,4 @@ Add the missing `## X.Y.Z` header to the package's CHANGELOG.md with release not
- [ ] Tier 2 complete, packages live on pub.dev
- [ ] `Release-Tier3/$ARGUMENTS` tag pushed
- [ ] Tier 3 complete, packages live on pub.dev
- [ ] Release PR merged to main
- [ ] Source unchanged — every `version:` still `0.0.0-dev`, no release branch/PR created
68 changes: 10 additions & 58 deletions .github/workflows/publish-tier1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,19 @@ on:
type: string

permissions:
contents: write
pull-requests: write
contents: read
id-token: write

jobs:
prepare:
publish:
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
version: ${{ steps.version.outputs.VERSION }}
environment: pub.dev-tier1
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Validate tag is on main (tag push only)
if: github.event_name == 'push'
Expand Down Expand Up @@ -89,59 +86,14 @@ jobs:
with:
dart-version: ${{ vars.DART_VERSION }}

- name: Prepare for publishing (switch to pub.dev deps)
run: dart run tools/prepare_publish.dart ${{ steps.version.outputs.VERSION }}

- name: Create release branch and commit
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b release/${{ steps.version.outputs.VERSION }}
git add -A
git commit -m "chore: prepare release ${{ steps.version.outputs.VERSION }} with pub.dev dependencies"
git push origin release/${{ steps.version.outputs.VERSION }}

- name: Create PR to main
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create \
--title "Release ${{ steps.version.outputs.VERSION }}" \
--body "## Release ${{ steps.version.outputs.VERSION }}

This PR is auto-created from the release tag.

**Do not merge yet!** Publishing happens in tier workflows (tier1 → tier2 → tier3).

After tier3 completes, dependencies will be switched back to local and this PR will be updated.

### Publishing Tiers
- Tier 1: dart_logging, dart_node_core (this workflow)
- Tier 2: reflux, dart_node_express, dart_node_ws, dart_node_better_sqlite3, dart_node_mcp
- Tier 3: dart_node_react, dart_node_react_native" \
--base main \
--head release/${{ steps.version.outputs.VERSION }}

publish:
needs: prepare
runs-on: ubuntu-latest
timeout-minutes: 10
environment: pub.dev-tier1
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Checkout release branch
run: |
git fetch origin "release/${{ needs.prepare.outputs.version }}"
git checkout "release/${{ needs.prepare.outputs.version }}"

- uses: ./.github/actions/setup
with:
dart-version: ${{ vars.DART_VERSION }}
# [SWR-VERSION-BUILD-STAMPING] Stamp the version from the tag in the
# runner working tree ONLY. Source stays 0.0.0-dev; nothing is committed,
# pushed, branched, or PR'd. The runner is discarded after publish, so a
# release produces ZERO churn on tracked source.
- name: Stamp versions in the working tree (not committed)
run: dart run tools/prepare_publish.dart "${{ steps.version.outputs.VERSION }}"

- name: Publish packages
run: |
chmod +x .github/scripts/publish-packages.sh
.github/scripts/publish-packages.sh "${{ needs.prepare.outputs.version }}" dart_logging dart_node_core
.github/scripts/publish-packages.sh "${{ steps.version.outputs.VERSION }}" dart_logging dart_node_core
11 changes: 6 additions & 5 deletions .github/workflows/publish-tier2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ jobs:
with:
fetch-depth: 0

- name: Checkout release branch
run: |
git fetch origin "release/${{ steps.version.outputs.VERSION }}"
git checkout "release/${{ steps.version.outputs.VERSION }}"

- uses: ./.github/actions/setup
with:
dart-version: ${{ vars.DART_VERSION }}

# [SWR-VERSION-BUILD-STAMPING] Stamp the version from the tag in the
# runner working tree ONLY. Source stays 0.0.0-dev; nothing is committed,
# pushed, or branched. The runner is discarded after publish.
- name: Stamp versions in the working tree (not committed)
run: dart run tools/prepare_publish.dart "${{ steps.version.outputs.VERSION }}"

- name: Publish packages
run: |
chmod +x .github/scripts/publish-packages.sh
Expand Down
29 changes: 7 additions & 22 deletions .github/workflows/publish-tier3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- 'Release-Tier3/[0-9]+.[0-9]+.[0-9]+*'

permissions:
contents: write
contents: read
id-token: write

jobs:
Expand All @@ -22,33 +22,18 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Checkout release branch
run: |
git fetch origin "release/${{ steps.version.outputs.VERSION }}"
git checkout "release/${{ steps.version.outputs.VERSION }}"

- uses: ./.github/actions/setup
with:
dart-version: ${{ vars.DART_VERSION }}

# [SWR-VERSION-BUILD-STAMPING] Stamp the version from the tag in the
# runner working tree ONLY. Source stays 0.0.0-dev; nothing is committed,
# pushed, or branched. The runner is discarded after publish.
- name: Stamp versions in the working tree (not committed)
run: dart run tools/prepare_publish.dart "${{ steps.version.outputs.VERSION }}"

- name: Publish packages
run: |
chmod +x .github/scripts/publish-packages.sh
.github/scripts/publish-packages.sh "${{ steps.version.outputs.VERSION }}" dart_node_react dart_node_react_native

- name: Switch dependencies to local
run: dart run tools/switch_deps.dart local

- name: Commit local dependencies to release branch
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "chore: switch to local dependencies after publish"
git push origin release/${{ steps.version.outputs.VERSION }}
fi
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ Dart packages for building Node.js apps. Strongly Typed Dart layer over JS inter
- Use the template: .github/PULL_REQUEST_TEMPLATE.md
- Only use git diff with main. Ignore commit messages

**Releases (Shipwright `[SWR-VERSION-BUILD-STAMPING]`)**
- Every `version:` in `packages/*/pubspec.yaml` MUST stay `0.0.0-dev` on every branch. The real version is
stamped from the git tag in the CI runner working tree at publish time — NEVER committed, pushed, branched,
or PR'd back. Releases produce ZERO source churn.
- ⛔️ ILLEGAL: bumping a `version:` in source, or a PR that turns a `0.0.0-dev` placeholder into a real version.
- Internal deps stay as local `path:` deps in source; `tools/prepare_publish.dart <version>` rewrites them
in-runner. Only CHANGELOG entries are committed for a release.

# Web & Translation

- Optimize for AI Search and SEO
Expand Down
2 changes: 1 addition & 1 deletion packages/dart_jsx/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_jsx
description: JSX transpiler for Dart - transforms JSX syntax to dart_node_react calls
version: 0.1.0
version: 0.0.0-dev
publish_to: none
repository: https://github.com/user/dart_node/tree/main/packages/dart_jsx

Expand Down
2 changes: 1 addition & 1 deletion packages/dart_logging/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_logging
description: A logging framework with structured logging, child loggers, and console output.
version: 0.11.0-beta
version: 0.0.0-dev
repository: https://github.com/MelbourneDeveloper/dart_node

environment:
Expand Down
2 changes: 1 addition & 1 deletion packages/dart_node_better_sqlite3/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_node_better_sqlite3
description: Typed Dart bindings for better-sqlite3 npm package
version: 0.11.0-beta
version: 0.0.0-dev
repository: https://github.com/MelbourneDeveloper/dart_node

environment:
Expand Down
2 changes: 1 addition & 1 deletion packages/dart_node_core/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_node_core
description: Core JS interop utilities for dart_node packages
version: 0.11.0-beta
version: 0.0.0-dev
repository: https://github.com/MelbourneDeveloper/dart_node

environment:
Expand Down
2 changes: 1 addition & 1 deletion packages/dart_node_coverage/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_node_coverage
description: Coverage collection for Dart tests running on Node.js (dart2js)
version: 0.9.0-beta
version: 0.0.0-dev
publish_to: none
repository: https://github.com/MelbourneDeveloper/dart_node

Expand Down
2 changes: 1 addition & 1 deletion packages/dart_node_express/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_node_express
description: Express.js bindings for Dart
version: 0.11.0-beta
version: 0.0.0-dev
repository: https://github.com/MelbourneDeveloper/dart_node

environment:
Expand Down
2 changes: 1 addition & 1 deletion packages/dart_node_mcp/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_node_mcp
description: Typed Dart bindings for @modelcontextprotocol/sdk
version: 0.11.0-beta
version: 0.0.0-dev
repository: https://github.com/MelbourneDeveloper/dart_node

environment:
Expand Down
2 changes: 1 addition & 1 deletion packages/dart_node_react/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: dart_node_react
description: React bindings for Dart
version: 0.11.0-beta
version: 0.0.0-dev
repository: https://github.com/MelbourneDeveloper/dart_node

environment:
Expand Down
Loading
Loading