Skip to content

Commit cef6daf

Browse files
committed
feat: Implement automated release workflow for version bumping, publishing, and GitHub Releases, complemented by a version change check.
1 parent aa427c6 commit cef6daf

4 files changed

Lines changed: 331 additions & 7 deletions

File tree

.github/workflows/publish.yml

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,54 @@
11
name: Publish to NPM
22

33
on:
4-
release:
5-
types: [published]
64
workflow_dispatch:
75
inputs:
8-
version:
9-
description: 'Version to publish (e.g., 1.0.0)'
6+
release-type:
7+
description: 'Release type'
8+
required: true
9+
type: choice
10+
options:
11+
- patch
12+
- minor
13+
- major
14+
- prepatch
15+
- preminor
16+
- premajor
17+
- prerelease
18+
default: 'patch'
19+
prerelease-id:
20+
description: 'Prerelease identifier (beta, alpha, rc, etc.)'
1021
required: false
22+
default: 'rc'
23+
dist-tag:
24+
description: 'NPM dist-tag'
25+
required: true
26+
type: choice
27+
options:
28+
- latest
29+
- beta
30+
- alpha
31+
- next
32+
- canary
33+
default: 'latest'
1134

1235
jobs:
1336
publish:
1437
runs-on: ubuntu-latest
1538
permissions:
16-
contents: read
39+
contents: write
1740
id-token: write
1841
steps:
1942
- name: Checkout
2043
uses: actions/checkout@v4
44+
with:
45+
fetch-depth: 0
46+
token: ${{ secrets.GITHUB_TOKEN }}
47+
48+
- name: Setup Git
49+
run: |
50+
git config user.name "github-actions[bot]"
51+
git config user.email "github-actions[bot]@users.noreply.github.com"
2152
2253
- name: Setup Bun
2354
uses: oven-sh/setup-bun@v1
@@ -33,7 +64,71 @@ jobs:
3364
- name: Install dependencies
3465
run: bun install
3566

67+
- name: Bump version
68+
id: version
69+
run: |
70+
# Determine version bump command
71+
if [[ "${{ inputs.release-type }}" == pre* ]]; then
72+
NEW_VERSION=$(npm version ${{ inputs.release-type }} --preid=${{ inputs.prerelease-id }} --no-git-tag-version)
73+
else
74+
NEW_VERSION=$(npm version ${{ inputs.release-type }} --no-git-tag-version)
75+
fi
76+
77+
# Remove 'v' prefix for output
78+
VERSION=${NEW_VERSION#v}
79+
echo "version=$VERSION" >> $GITHUB_OUTPUT
80+
echo "tag=$NEW_VERSION" >> $GITHUB_OUTPUT
81+
82+
echo "📦 New version: $VERSION"
83+
84+
- name: Build package
85+
run: |
86+
# Add build step if needed
87+
echo "Building package..."
88+
3689
- name: Publish to NPM
37-
run: npm publish --provenance --access public
90+
run: |
91+
if [ "${{ inputs.dist-tag }}" == "latest" ]; then
92+
npm publish --provenance --access public
93+
else
94+
npm publish --provenance --access public --tag ${{ inputs.dist-tag }}
95+
fi
3896
env:
3997
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
98+
99+
- name: Commit version bump
100+
run: |
101+
git add package.json
102+
git commit -m "chore(release): bump version to ${{ steps.version.outputs.version }}"
103+
104+
- name: Create Git Tag
105+
run: |
106+
git tag -a ${{ steps.version.outputs.tag }} -m "Release ${{ steps.version.outputs.version }}"
107+
108+
- name: Push changes
109+
run: |
110+
git push origin HEAD:master
111+
git push origin ${{ steps.version.outputs.tag }}
112+
113+
- name: Create GitHub Release
114+
uses: actions/create-release@v1
115+
env:
116+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
117+
with:
118+
tag_name: ${{ steps.version.outputs.tag }}
119+
release_name: Release ${{ steps.version.outputs.version }}
120+
body: |
121+
## 🚀 Release ${{ steps.version.outputs.version }}
122+
123+
**Release Type:** ${{ inputs.release-type }}
124+
**Dist Tag:** ${{ inputs.dist-tag }}
125+
126+
### Installation
127+
```bash
128+
npm install moicle@${{ steps.version.outputs.version }}
129+
```
130+
131+
### Changes
132+
See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/master/CHANGELOG.md) for details.
133+
draft: false
134+
prerelease: ${{ inputs.dist-tag != 'latest' }}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Version Check
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
paths:
8+
- 'package.json'
9+
10+
jobs:
11+
check:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout PR
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: Check version change
20+
id: version-check
21+
run: |
22+
# Get version from PR
23+
PR_VERSION=$(node -p "require('./package.json').version")
24+
25+
# Get version from master
26+
git fetch origin master
27+
git checkout origin/master -- package.json
28+
MASTER_VERSION=$(node -p "require('./package.json').version")
29+
git checkout HEAD -- package.json
30+
31+
echo "Master version: $MASTER_VERSION"
32+
echo "PR version: $PR_VERSION"
33+
34+
if [ "$PR_VERSION" != "$MASTER_VERSION" ]; then
35+
echo "::error::❌ Do not manually change version in package.json!"
36+
echo "::error::Use 'workflow_dispatch' on Publish workflow to bump versions automatically."
37+
echo "::error::Master: $MASTER_VERSION -> PR: $PR_VERSION"
38+
exit 1
39+
fi
40+
41+
echo "✅ Version check passed!"
42+
43+
- name: Comment on PR
44+
if: failure()
45+
uses: actions/github-script@v7
46+
with:
47+
script: |
48+
github.rest.issues.createComment({
49+
issue_number: context.issue.number,
50+
owner: context.repo.owner,
51+
repo: context.repo.repo,
52+
body: '❌ **Version Change Detected**\n\nPlease do not manually change the version in `package.json`.\n\nUse the **[Publish to NPM](../../actions/workflows/publish.yml)** workflow to automatically bump versions.'
53+
})

RELEASE.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Release Guide
2+
3+
This project uses automated releases via GitHub Actions. **DO NOT manually change version in `package.json`**.
4+
5+
## 📦 Publishing a Release
6+
7+
### Option 1: Via GitHub UI (Recommended)
8+
9+
1. Go to **Actions** tab in GitHub
10+
2. Select **"Publish to NPM"** workflow
11+
3. Click **"Run workflow"** button
12+
4. Select options:
13+
- **Release Type**: `patch`, `minor`, `major`, `prepatch`, `preminor`, `premajor`, `prerelease`
14+
- **Prerelease ID**: `beta`, `alpha`, `rc`, etc. (for prerelease versions)
15+
- **Dist Tag**: `latest`, `beta`, `alpha`, `next`, `canary`
16+
5. Click **"Run workflow"**
17+
18+
### Option 2: Via GitHub CLI
19+
20+
```bash
21+
# Patch release (1.0.0 -> 1.0.1)
22+
gh workflow run publish.yml -f release-type=patch -f dist-tag=latest
23+
24+
# Minor release (1.0.0 -> 1.1.0)
25+
gh workflow run publish.yml -f release-type=minor -f dist-tag=latest
26+
27+
# Major release (1.0.0 -> 2.0.0)
28+
gh workflow run publish.yml -f release-type=major -f dist-tag=latest
29+
30+
# Beta release (1.0.0 -> 1.0.1-beta.0)
31+
gh workflow run publish.yml -f release-type=prepatch -f prerelease-id=beta -f dist-tag=beta
32+
33+
# Increment existing beta (1.0.1-beta.0 -> 1.0.1-beta.1)
34+
gh workflow run publish.yml -f release-type=prerelease -f prerelease-id=beta -f dist-tag=beta
35+
```
36+
37+
## 📋 Release Types
38+
39+
### Standard Releases
40+
41+
| Type | Example | Dist Tag | Description |
42+
| ------- | ---------------------- | -------- | --------------------- |
43+
| `patch` | `1.0.0``1.0.1` | `latest` | Bug fixes |
44+
| `minor` | `1.0.0``1.1.0` | `latest` | New features |
45+
| `major` | `1.0.0``2.0.0` | `latest` | Breaking changes |
46+
47+
### Prerelease Versions
48+
49+
| Type | Example | Dist Tag | Description |
50+
| ----------- | -------------------------- | -------- | ---------------------------- |
51+
| `prepatch` | `1.0.0``1.0.1-beta.0` | `beta` | Beta patch |
52+
| `preminor` | `1.0.0``1.1.0-beta.0` | `beta` | Beta minor |
53+
| `premajor` | `1.0.0``2.0.0-beta.0` | `beta` | Beta major |
54+
| `prerelease`| `1.0.1-beta.0``1.0.1-beta.1` | `beta` | Increment existing prerelease |
55+
56+
## 🎯 Common Scenarios
57+
58+
### Scenario 1: Bug Fix Release
59+
```bash
60+
# Current: 1.2.3
61+
# Target: 1.2.4
62+
gh workflow run publish.yml -f release-type=patch -f dist-tag=latest
63+
```
64+
65+
### Scenario 2: New Feature Release
66+
```bash
67+
# Current: 1.2.3
68+
# Target: 1.3.0
69+
gh workflow run publish.yml -f release-type=minor -f dist-tag=latest
70+
```
71+
72+
### Scenario 3: Breaking Change Release
73+
```bash
74+
# Current: 1.2.3
75+
# Target: 2.0.0
76+
gh workflow run publish.yml -f release-type=major -f dist-tag=latest
77+
```
78+
79+
### Scenario 4: Beta Testing
80+
```bash
81+
# Current: 1.2.3
82+
# Target: 1.2.4-beta.0
83+
gh workflow run publish.yml -f release-type=prepatch -f prerelease-id=beta -f dist-tag=beta
84+
85+
# Users install with:
86+
npm install moicle@beta
87+
```
88+
89+
### Scenario 5: Alpha Release
90+
```bash
91+
# Current: 1.2.3
92+
# Target: 1.3.0-alpha.0
93+
gh workflow run publish.yml -f release-type=preminor -f prerelease-id=alpha -f dist-tag=alpha
94+
95+
# Users install with:
96+
npm install moicle@alpha
97+
```
98+
99+
### Scenario 6: Release Candidate
100+
```bash
101+
# Current: 1.2.3
102+
# Target: 2.0.0-rc.0
103+
gh workflow run publish.yml -f release-type=premajor -f prerelease-id=rc -f dist-tag=next
104+
105+
# Users install with:
106+
npm install moicle@next
107+
```
108+
109+
## 🔄 What Happens Automatically?
110+
111+
When you run the workflow, it will:
112+
113+
1.**Bump version** in `package.json` based on release type
114+
2.**Build package** (if build step exists)
115+
3.**Publish to NPM** with specified dist-tag
116+
4.**Commit changes** back to master branch
117+
5.**Create Git tag** (e.g., `v1.2.4`)
118+
6.**Push tag** to GitHub
119+
7.**Create GitHub Release** with release notes
120+
121+
## 🚫 Don't Do This
122+
123+
**Never manually edit version in package.json**
124+
```bash
125+
# DON'T DO THIS:
126+
git commit -m "bump version to 1.2.4" # ❌ WRONG
127+
```
128+
129+
**Always use the workflow**
130+
```bash
131+
# DO THIS INSTEAD:
132+
gh workflow run publish.yml -f release-type=patch -f dist-tag=latest
133+
```
134+
135+
## 📊 Dist Tags Explained
136+
137+
- **`latest`**: Default, stable releases (recommended for production)
138+
- **`beta`**: Beta testing, may have bugs
139+
- **`alpha`**: Early testing, unstable
140+
- **`next`**: Release candidates, near-stable
141+
- **`canary`**: Bleeding edge, experimental
142+
143+
Users install with:
144+
```bash
145+
npm install moicle # latest (default)
146+
npm install moicle@beta # beta version
147+
npm install moicle@alpha # alpha version
148+
npm install moicle@1.2.3 # specific version
149+
```
150+
151+
## 🔧 Prerequisites
152+
153+
1. **NPM Token**: Set `NPM_TOKEN` secret in GitHub repository settings
154+
- Use **Automation Token** (no OTP required)
155+
- Go to https://www.npmjs.com/ → Access Tokens → Generate New Token → Automation
156+
157+
2. **Permissions**: Workflow needs `contents: write` permission (already configured)
158+
159+
## 📝 Notes
160+
161+
- Version bumps are **automatic** and follow [Semantic Versioning](https://semver.org/)
162+
- Git tags are created automatically with `v` prefix (e.g., `v1.2.3`)
163+
- GitHub Releases are created automatically
164+
- Prerelease versions are marked as "Pre-release" on GitHub
165+
- All changes are pushed back to `master` branch
166+
167+
## 🆘 Troubleshooting
168+
169+
### Error: "This operation requires a one-time password"
170+
➡️ Update `NPM_TOKEN` secret with an **Automation Token** instead of Classic Token
171+
172+
### Error: "refused to update ref"
173+
➡️ Check GitHub token has `contents: write` permission
174+
175+
### Wrong version published
176+
➡️ Don't panic! Just publish another version with the correct release type

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "moicle",
3-
"version": "1.0.0",
3+
"version": "1.0.0-rc.1",
44
"description": "Reusable AI agents, commands, skills, and architecture references for Claude Code",
55
"type": "module",
66
"main": "dist/index.js",

0 commit comments

Comments
 (0)