@wipcomputer/wip-ai-devops-toolbox 1.9.66 → 1.9.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/DEV-GUIDE-GENERAL-PUBLIC.md +36 -2
- package/SKILL.md +1 -1
- package/package.json +1 -1
- package/tools/deploy-public/package.json +1 -1
- package/tools/ldm-jobs/backup.sh +1 -1
- package/tools/post-merge-rename/package.json +1 -1
- package/tools/wip-branch-guard/package.json +1 -1
- package/tools/wip-branch-guard/test.sh +1 -1
- package/tools/wip-file-guard/package.json +1 -1
- package/tools/wip-license-guard/package.json +1 -1
- package/tools/wip-license-hook/package.json +1 -1
- package/tools/wip-readme-format/package.json +1 -1
- package/tools/wip-release/REFERENCE.md +154 -9
- package/tools/wip-release/SKILL.md +42 -3
- package/tools/wip-release/cli.js +87 -31
- package/tools/wip-release/core.mjs +466 -0
- package/tools/wip-release/package.json +1 -1
- package/tools/wip-repo-init/package.json +1 -1
- package/tools/wip-repo-permissions-hook/package.json +1 -1
- package/tools/wip-repos/package.json +1 -1
- package/tools/wip-universal-installer/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,57 @@
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
## 1.9.68 (2026-04-01)
|
|
36
|
+
|
|
37
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.68
|
|
38
|
+
|
|
39
|
+
Closes #239
|
|
40
|
+
|
|
41
|
+
## Four-track release pipeline
|
|
42
|
+
|
|
43
|
+
The release tool now supports four tracks: alpha, beta, hotfix, and stable. This replaces the single-track model where every release was public.
|
|
44
|
+
|
|
45
|
+
Alpha is silent (no public release notes by default). Beta publishes prerelease notes to the public repo. Hotfix publishes to npm @latest without syncing code to public. Stable is the full deploy: npm + code sync + release notes. Developers can iterate on private, ship betas to testers, and only go public when ready.
|
|
46
|
+
|
|
47
|
+
Version numbering uses standard semver prereleases: `1.9.68-alpha.1`, `1.9.68-beta.1`. The installer (`ldm install --beta` / `--alpha`) pulls the right tag from npm.
|
|
48
|
+
|
|
49
|
+
## 1.9.67 (2026-03-31)
|
|
50
|
+
|
|
51
|
+
# Release Notes: wip-ai-devops-toolbox v1.9.67
|
|
52
|
+
|
|
53
|
+
**Date:** 2026-03-30
|
|
54
|
+
|
|
55
|
+
## What changed
|
|
56
|
+
|
|
57
|
+
### Hardcoded path removal
|
|
58
|
+
|
|
59
|
+
Two files in the devops toolbox had paths that assumed a specific username or iCloud layout.
|
|
60
|
+
|
|
61
|
+
**ldm-jobs/backup.sh** referenced `/Users/lesa/Library/Mobile Documents/.../ldm/bin/` to find the `ldm` binary for scheduled backup jobs. This iCloud path was fragile (iCloud sync delays, different usernames). The script now uses `$HOME/.ldm/bin/` which is the standard LDM install location and works on any machine (#301).
|
|
62
|
+
|
|
63
|
+
**test.sh** (the branch guard test harness) had `/Users/lesa` hardcoded for creating temp directories. It now uses `$HOME` so tests run correctly under any user account (#301).
|
|
64
|
+
|
|
65
|
+
### Earlier changes included in this release
|
|
66
|
+
|
|
67
|
+
**v1.9.66** added auto-combine for release notes from batched PRs (#237). When multiple PRs are merged between releases, their individual RELEASE-NOTES files are automatically combined into a single changelog entry.
|
|
68
|
+
|
|
69
|
+
**v1.9.65** fixed the scaffold-on-main issue (#223) where scaffolding left untracked files that blocked `git pull` on the main working tree.
|
|
70
|
+
|
|
71
|
+
## Why
|
|
72
|
+
|
|
73
|
+
The backup job is scheduled via LaunchAgent and runs unattended. If the path to `ldm` is wrong, backups silently fail. Moving to `$HOME/.ldm/bin/` aligns with the standard LDM install path and eliminates the iCloud dependency. The test fix ensures CI and local test runs work for all contributors.
|
|
74
|
+
|
|
75
|
+
## Issues closed
|
|
76
|
+
|
|
77
|
+
- #301
|
|
78
|
+
|
|
79
|
+
## How to verify
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
grep -r "/Users/lesa" ldm-jobs/ tools/wip-branch-guard/test.sh
|
|
83
|
+
# Should return zero results
|
|
84
|
+
```
|
|
34
85
|
|
|
35
86
|
## 1.9.66 (2026-03-30)
|
|
36
87
|
|
|
@@ -45,11 +45,45 @@ CLI is the universal fallback. MCP and plugin wrappers are optimizations.
|
|
|
45
45
|
6. Merge PR: gh pr merge <number> --merge --delete-branch
|
|
46
46
|
7. Rename merged branch: (see Post-Merge Branch Rename below)
|
|
47
47
|
8. Pull merged main: git checkout main && git pull origin main
|
|
48
|
-
9. Release: wip-release patch
|
|
49
|
-
|
|
48
|
+
9. Release: wip-release patch (stable: full pipeline)
|
|
49
|
+
wip-release alpha (prerelease: npm @alpha, silent)
|
|
50
|
+
wip-release beta (prerelease: npm @beta, public notes)
|
|
51
|
+
wip-release hotfix (urgent: npm @latest, no deploy-public)
|
|
50
52
|
# flags: --dry-run (preview), --no-publish (bump + tag only)
|
|
51
53
|
```
|
|
52
54
|
|
|
55
|
+
### Release Tracks
|
|
56
|
+
|
|
57
|
+
Four release tracks. Choose the right one for the situation:
|
|
58
|
+
|
|
59
|
+
| Track | Command | npm tag | Public code sync | When to use |
|
|
60
|
+
|-------|---------|---------|-----------------|-------------|
|
|
61
|
+
| Alpha | `wip-release alpha` | @alpha | No | Internal testing, not ready for users |
|
|
62
|
+
| Beta | `wip-release beta` | @beta | No | External testing, release candidate |
|
|
63
|
+
| Hotfix | `wip-release hotfix` | @latest | No | Urgent fix, skip deploy-public |
|
|
64
|
+
| Stable | `wip-release patch/minor/major` | @latest | Yes | Normal release, full pipeline |
|
|
65
|
+
|
|
66
|
+
**Alpha** is silent by default. No public release notes, no code sync. Add `--release-notes` to create a prerelease on the public GitHub repo.
|
|
67
|
+
|
|
68
|
+
**Beta** publishes prerelease notes to the public GitHub repo by default. Add `--no-release-notes` to skip.
|
|
69
|
+
|
|
70
|
+
**Hotfix** publishes to npm @latest and creates a release on the public GitHub repo, but does NOT run deploy-public (no code sync). Use this when you need a fix in npm immediately but the public repo code can wait for the next stable release.
|
|
71
|
+
|
|
72
|
+
**Stable** is the existing behavior. Full pipeline: npm @latest, deploy-public (code sync), full release notes.
|
|
73
|
+
|
|
74
|
+
Version numbering:
|
|
75
|
+
- Alpha: `1.9.68-alpha.1`, `1.9.68-alpha.2` (increments on repeat)
|
|
76
|
+
- Beta: `1.9.68-beta.1`, `1.9.68-beta.2` (increments on repeat)
|
|
77
|
+
- Hotfix: normal patch bump (`1.9.67` -> `1.9.68`)
|
|
78
|
+
- Stable: normal bump (patch/minor/major)
|
|
79
|
+
|
|
80
|
+
Install integration:
|
|
81
|
+
```bash
|
|
82
|
+
ldm install # checks @latest (stable + hotfix)
|
|
83
|
+
ldm install --beta # checks @beta
|
|
84
|
+
ldm install --alpha # checks @alpha
|
|
85
|
+
```
|
|
86
|
+
|
|
53
87
|
**Important:**
|
|
54
88
|
- **Every change goes through a PR.** No direct pushes to main. Not even "just a README fix." Branch, PR, merge. Every time.
|
|
55
89
|
- **Never squash merge.** Every commit has co-authors and tells the story of how something was built. Squashing destroys attribution and history. Always use `--merge` or fast-forward. This applies to `gh pr merge`, manual merges, deploy-public.sh, and any other merge path. No exceptions. Always include `--delete-branch` so the PR branch is cleaned up automatically.
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
interface: [cli, module, mcp, skill, hook, plugin]
|
|
6
6
|
metadata:
|
|
7
7
|
display-name: "WIP AI DevOps Toolbox"
|
|
8
|
-
version: "1.9.
|
|
8
|
+
version: "1.9.68"
|
|
9
9
|
homepage: "https://github.com/wipcomputer/wip-ai-devops-toolbox"
|
|
10
10
|
author: "Parker Todd Brooks"
|
|
11
11
|
category: dev-tools
|
package/package.json
CHANGED
package/tools/ldm-jobs/backup.sh
CHANGED
|
@@ -110,7 +110,7 @@ test_case "mkdir .worktrees" allow Bash "mkdir -p .worktrees/repo--branch"
|
|
|
110
110
|
|
|
111
111
|
echo ""
|
|
112
112
|
echo "--- Plan files (should ALLOW Write/Edit) ---"
|
|
113
|
-
test_case "Edit plan file" allow Edit "
|
|
113
|
+
test_case "Edit plan file" allow Edit "$HOME/.claude/plans/my-plan.md"
|
|
114
114
|
|
|
115
115
|
echo ""
|
|
116
116
|
echo "=== Results: $PASS passed, $FAIL failed, $SKIP skipped ==="
|
|
@@ -3,15 +3,47 @@
|
|
|
3
3
|
|
|
4
4
|
Detailed usage, pipeline steps, flags, auth, and module API.
|
|
5
5
|
|
|
6
|
+
## Release Tracks
|
|
7
|
+
|
|
8
|
+
Four release tracks, each with different behavior:
|
|
9
|
+
|
|
10
|
+
| Track | npm tag | Public code sync | Public release notes | Default notes |
|
|
11
|
+
|-------|---------|-----------------|---------------------|---------------|
|
|
12
|
+
| Alpha | @alpha | No | No (opt in with --release-notes) | Silent |
|
|
13
|
+
| Beta | @beta | No | Yes, prerelease (opt out with --no-release-notes) | Visible |
|
|
14
|
+
| Hotfix | @latest | No | Yes (opt out with --no-release-notes) | Visible |
|
|
15
|
+
| Stable | @latest | Yes, full deploy-public | Yes, full notes | Full deploy |
|
|
16
|
+
|
|
17
|
+
### Version numbering
|
|
18
|
+
|
|
19
|
+
- Alpha: `1.9.68-alpha.1`, `1.9.68-alpha.2` (increments on repeat)
|
|
20
|
+
- Beta: `1.9.68-beta.1`, `1.9.68-beta.2` (increments on repeat)
|
|
21
|
+
- Hotfix: normal patch bump (`1.9.67` -> `1.9.68`)
|
|
22
|
+
- Stable: normal bump (patch/minor/major)
|
|
23
|
+
|
|
6
24
|
## Usage
|
|
7
25
|
|
|
8
26
|
Run from inside any repo:
|
|
9
27
|
|
|
10
28
|
```bash
|
|
29
|
+
# Stable (existing behavior)
|
|
11
30
|
wip-release patch # 1.0.0 -> 1.0.1
|
|
12
31
|
wip-release minor # 1.0.0 -> 1.1.0
|
|
13
32
|
wip-release major # 1.0.0 -> 2.0.0
|
|
14
33
|
|
|
34
|
+
# Alpha
|
|
35
|
+
wip-release alpha # 1.0.1-alpha.1 (npm @alpha, silent)
|
|
36
|
+
wip-release alpha --release-notes # 1.0.1-alpha.1 (npm @alpha + prerelease on public)
|
|
37
|
+
|
|
38
|
+
# Beta
|
|
39
|
+
wip-release beta # 1.0.1-beta.1 (npm @beta + prerelease on public)
|
|
40
|
+
wip-release beta --no-release-notes # 1.0.1-beta.1 (npm @beta, skip notes)
|
|
41
|
+
|
|
42
|
+
# Hotfix
|
|
43
|
+
wip-release hotfix # 1.0.0 -> 1.0.1 (npm @latest + release on public)
|
|
44
|
+
wip-release hotfix --no-release-notes # skip public release notes
|
|
45
|
+
|
|
46
|
+
# Common flags
|
|
15
47
|
wip-release patch --notes="fix auth config" # with changelog note
|
|
16
48
|
wip-release minor --dry-run # preview, no changes
|
|
17
49
|
wip-release patch --no-publish # bump + tag only
|
|
@@ -19,6 +51,8 @@ wip-release patch --no-publish # bump + tag only
|
|
|
19
51
|
|
|
20
52
|
## What It Does
|
|
21
53
|
|
|
54
|
+
### Stable pipeline
|
|
55
|
+
|
|
22
56
|
```
|
|
23
57
|
wip-grok: 1.0.0 -> 1.0.1 (patch)
|
|
24
58
|
────────────────────────────────────────
|
|
@@ -28,32 +62,97 @@ wip-release patch --no-publish # bump + tag only
|
|
|
28
62
|
✓ Committed and tagged v1.0.1
|
|
29
63
|
✓ Pushed to remote
|
|
30
64
|
✓ Published to npm
|
|
31
|
-
|
|
65
|
+
- GitHub Packages: handled by deploy-public.sh
|
|
32
66
|
✓ GitHub release v1.0.1 created
|
|
33
67
|
✓ Published to ClawHub
|
|
34
68
|
|
|
35
69
|
Done. wip-grok v1.0.1 released.
|
|
36
70
|
```
|
|
37
71
|
|
|
72
|
+
### Alpha/beta pipeline
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
wip-grok: 1.0.0 -> 1.0.1-alpha.1 (alpha)
|
|
76
|
+
────────────────────────────────────────
|
|
77
|
+
✓ package.json -> 1.0.1-alpha.1
|
|
78
|
+
✓ CHANGELOG.md updated
|
|
79
|
+
✓ Committed and tagged v1.0.1-alpha.1
|
|
80
|
+
✓ Pushed to remote
|
|
81
|
+
✓ Published to npm @alpha
|
|
82
|
+
- GitHub prerelease: skipped (silent alpha)
|
|
83
|
+
|
|
84
|
+
Done. wip-grok v1.0.1-alpha.1 (alpha) released.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Hotfix pipeline
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
wip-grok: 1.0.0 -> 1.0.1 (hotfix)
|
|
91
|
+
────────────────────────────────────────
|
|
92
|
+
✓ package.json -> 1.0.1
|
|
93
|
+
✓ SKILL.md -> 1.0.1
|
|
94
|
+
✓ CHANGELOG.md updated
|
|
95
|
+
✓ Committed and tagged v1.0.1
|
|
96
|
+
✓ Pushed to remote
|
|
97
|
+
✓ Published to npm @latest
|
|
98
|
+
✓ GitHub release v1.0.1 created on public repo
|
|
99
|
+
- deploy-public: skipped (hotfix)
|
|
100
|
+
|
|
101
|
+
Done. wip-grok v1.0.1 (hotfix) released.
|
|
102
|
+
```
|
|
103
|
+
|
|
38
104
|
## Pipeline Steps
|
|
39
105
|
|
|
106
|
+
### Stable (patch/minor/major)
|
|
107
|
+
|
|
40
108
|
1. **Bump `package.json`** ... patch, minor, or major
|
|
41
109
|
2. **Sync `SKILL.md`** ... updates version in YAML frontmatter (if file exists)
|
|
42
110
|
3. **Update `CHANGELOG.md`** ... prepends new version entry with date and notes
|
|
43
111
|
4. **Git commit + tag** ... commits changed files, creates `vX.Y.Z` tag
|
|
44
112
|
5. **Push** ... pushes commit and tag to remote
|
|
45
|
-
6. **npm publish** ... publishes to npmjs.com (auth via 1Password)
|
|
46
|
-
7. **GitHub Packages** ...
|
|
47
|
-
8. **GitHub release** ... creates release with changelog notes
|
|
113
|
+
6. **npm publish** ... publishes to npmjs.com with @latest (auth via 1Password)
|
|
114
|
+
7. **GitHub Packages** ... handled by deploy-public.sh
|
|
115
|
+
8. **GitHub release** ... creates release on private repo with changelog notes
|
|
48
116
|
9. **ClawHub publish** ... publishes skill to ClawHub (if SKILL.md exists)
|
|
117
|
+
10. **Branch cleanup** ... renames/prunes merged branches
|
|
118
|
+
11. **Worktree cleanup** ... prunes merged worktrees
|
|
119
|
+
|
|
120
|
+
### Alpha/Beta
|
|
121
|
+
|
|
122
|
+
1. **Bump `package.json`** ... adds prerelease suffix (-alpha.N / -beta.N)
|
|
123
|
+
2. **Update `CHANGELOG.md`** ... lightweight prerelease entry
|
|
124
|
+
3. **Git commit + tag** ... commits changed files, creates tag
|
|
125
|
+
4. **Push** ... pushes commit and tag to remote
|
|
126
|
+
5. **npm publish** ... publishes with --tag alpha or --tag beta
|
|
127
|
+
6. **GitHub prerelease** ... (beta: on by default, alpha: opt-in with --release-notes)
|
|
128
|
+
|
|
129
|
+
### Hotfix
|
|
130
|
+
|
|
131
|
+
1. **Bump `package.json`** ... patch bump (no suffix)
|
|
132
|
+
2. **Sync `SKILL.md`** ... updates version in YAML frontmatter
|
|
133
|
+
3. **Update `CHANGELOG.md`** ... prepends new version entry
|
|
134
|
+
4. **Git commit + tag** ... commits changed files, creates tag
|
|
135
|
+
5. **Push** ... pushes commit and tag to remote
|
|
136
|
+
6. **npm publish** ... publishes with --tag latest
|
|
137
|
+
7. **GitHub release** ... creates release on public repo (opt out with --no-release-notes)
|
|
138
|
+
8. **ClawHub publish** ... publishes skill to ClawHub (if SKILL.md exists)
|
|
139
|
+
9. **No deploy-public** ... code sync is skipped
|
|
49
140
|
|
|
50
141
|
## Flags
|
|
51
142
|
|
|
52
143
|
| Flag | What |
|
|
53
144
|
|------|------|
|
|
54
145
|
| `--notes="text"` | Changelog entry text |
|
|
146
|
+
| `--notes-file=path` | Read release narrative from a markdown file |
|
|
147
|
+
| `--release-notes` | Opt in to public release notes (alpha only) |
|
|
148
|
+
| `--no-release-notes` | Opt out of public release notes (beta, hotfix) |
|
|
55
149
|
| `--dry-run` | Show what would happen, change nothing |
|
|
56
150
|
| `--no-publish` | Bump + tag only, skip npm and GitHub release |
|
|
151
|
+
| `--skip-product-check` | Skip product docs gate (stable only) |
|
|
152
|
+
| `--skip-stale-check` | Skip stale remote branch check (stable only) |
|
|
153
|
+
| `--skip-worktree-check` | Skip worktree guard |
|
|
154
|
+
| `--skip-tech-docs-check` | Skip technical docs check (stable only) |
|
|
155
|
+
| `--skip-coverage-check` | Skip interface coverage check (stable only) |
|
|
57
156
|
|
|
58
157
|
## Auth
|
|
59
158
|
|
|
@@ -66,15 +165,26 @@ Requires:
|
|
|
66
165
|
- `gh` CLI authenticated (for GitHub Packages and releases)
|
|
67
166
|
- `clawhub` CLI authenticated (for ClawHub skill publishing)
|
|
68
167
|
|
|
168
|
+
## ldm install Integration
|
|
169
|
+
|
|
170
|
+
The installer checks different npm tags based on the track:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
ldm install # checks @latest (stable + hotfix)
|
|
174
|
+
ldm install --beta # checks @beta tag
|
|
175
|
+
ldm install --alpha # checks @alpha tag
|
|
176
|
+
```
|
|
177
|
+
|
|
69
178
|
## As a Module
|
|
70
179
|
|
|
71
180
|
```javascript
|
|
72
|
-
import { release, detectCurrentVersion, bumpSemver } from '@wipcomputer/wip-release';
|
|
181
|
+
import { release, releasePrerelease, releaseHotfix, detectCurrentVersion, bumpSemver, bumpPrerelease } from '@wipcomputer/wip-release';
|
|
73
182
|
|
|
74
183
|
const current = detectCurrentVersion('/path/to/repo');
|
|
75
184
|
const next = bumpSemver(current, 'minor');
|
|
76
185
|
console.log(`${current} -> ${next}`);
|
|
77
186
|
|
|
187
|
+
// Stable release
|
|
78
188
|
await release({
|
|
79
189
|
repoPath: '/path/to/repo',
|
|
80
190
|
level: 'patch',
|
|
@@ -82,19 +192,54 @@ await release({
|
|
|
82
192
|
dryRun: false,
|
|
83
193
|
noPublish: false,
|
|
84
194
|
});
|
|
195
|
+
|
|
196
|
+
// Alpha prerelease
|
|
197
|
+
await releasePrerelease({
|
|
198
|
+
repoPath: '/path/to/repo',
|
|
199
|
+
track: 'alpha',
|
|
200
|
+
notes: 'testing new feature',
|
|
201
|
+
dryRun: false,
|
|
202
|
+
noPublish: false,
|
|
203
|
+
publishReleaseNotes: false,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Beta prerelease
|
|
207
|
+
await releasePrerelease({
|
|
208
|
+
repoPath: '/path/to/repo',
|
|
209
|
+
track: 'beta',
|
|
210
|
+
notes: 'beta candidate',
|
|
211
|
+
dryRun: false,
|
|
212
|
+
noPublish: false,
|
|
213
|
+
publishReleaseNotes: true,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Hotfix
|
|
217
|
+
await releaseHotfix({
|
|
218
|
+
repoPath: '/path/to/repo',
|
|
219
|
+
notes: 'critical fix',
|
|
220
|
+
dryRun: false,
|
|
221
|
+
noPublish: false,
|
|
222
|
+
publishReleaseNotes: true,
|
|
223
|
+
});
|
|
85
224
|
```
|
|
86
225
|
|
|
87
226
|
## Exports
|
|
88
227
|
|
|
89
228
|
| Function | What |
|
|
90
229
|
|----------|------|
|
|
91
|
-
| `release({ repoPath, level, notes, dryRun, noPublish })` | Full pipeline |
|
|
230
|
+
| `release({ repoPath, level, notes, dryRun, noPublish })` | Full stable pipeline |
|
|
231
|
+
| `releasePrerelease({ repoPath, track, notes, dryRun, noPublish, publishReleaseNotes })` | Alpha/beta pipeline |
|
|
232
|
+
| `releaseHotfix({ repoPath, notes, dryRun, noPublish, publishReleaseNotes })` | Hotfix pipeline |
|
|
92
233
|
| `detectCurrentVersion(repoPath)` | Read version from package.json |
|
|
93
|
-
| `bumpSemver(version, level)` | Bump a semver string |
|
|
234
|
+
| `bumpSemver(version, level)` | Bump a semver string (patch/minor/major) |
|
|
235
|
+
| `bumpPrerelease(version, track)` | Bump a prerelease version (alpha/beta) |
|
|
94
236
|
| `syncSkillVersion(repoPath, newVersion)` | Update SKILL.md frontmatter |
|
|
95
237
|
| `updateChangelog(repoPath, newVersion, notes)` | Prepend to CHANGELOG.md |
|
|
96
|
-
| `publishNpm(repoPath)` | Publish to npmjs.com |
|
|
238
|
+
| `publishNpm(repoPath)` | Publish to npmjs.com (@latest) |
|
|
239
|
+
| `publishNpmWithTag(repoPath, tag)` | Publish to npmjs.com with specific tag |
|
|
97
240
|
| `publishGitHubPackages(repoPath)` | Publish to npm.pkg.github.com |
|
|
98
|
-
| `createGitHubRelease(repoPath, newVersion, notes, currentVersion)` | Create GitHub release
|
|
241
|
+
| `createGitHubRelease(repoPath, newVersion, notes, currentVersion)` | Create GitHub release on private repo |
|
|
242
|
+
| `createGitHubPrerelease(repoPath, newVersion, notes)` | Create GitHub prerelease on public repo |
|
|
243
|
+
| `createGitHubReleaseOnPublic(repoPath, newVersion, notes, currentVersion)` | Create GitHub release on public repo |
|
|
99
244
|
| `buildReleaseNotes(repoPath, currentVersion, newVersion, notes)` | Generate detailed release notes |
|
|
100
245
|
| `publishClawHub(repoPath, newVersion, notes)` | Publish skill to ClawHub |
|
|
@@ -46,6 +46,8 @@ Local release pipeline. One command bumps version, updates all docs, publishes e
|
|
|
46
46
|
- Releasing a new version of any @wipcomputer package
|
|
47
47
|
- After merging a PR to main and you need to publish
|
|
48
48
|
- Bumping version + changelog + SKILL.md in one step
|
|
49
|
+
- Publishing alpha or beta prereleases for testing
|
|
50
|
+
- Pushing hotfixes to npm without full deploy-public
|
|
49
51
|
|
|
50
52
|
**Use --dry-run for:**
|
|
51
53
|
- Previewing what a release would do before committing
|
|
@@ -55,18 +57,48 @@ Local release pipeline. One command bumps version, updates all docs, publishes e
|
|
|
55
57
|
|
|
56
58
|
### Do NOT Use For
|
|
57
59
|
|
|
58
|
-
- Pre-release / alpha versions (not yet supported)
|
|
59
60
|
- Repos without a package.json
|
|
60
61
|
|
|
62
|
+
## Release Tracks
|
|
63
|
+
|
|
64
|
+
Four release tracks. Each has different behavior for npm tags, public repo sync, and release notes.
|
|
65
|
+
|
|
66
|
+
| Track | Command | npm tag | Public code sync | Public release notes |
|
|
67
|
+
|-------|---------|---------|-----------------|---------------------|
|
|
68
|
+
| Alpha | `wip-release alpha` | @alpha | No | No (opt in with --release-notes) |
|
|
69
|
+
| Beta | `wip-release beta` | @beta | No | Yes, prerelease (opt out with --no-release-notes) |
|
|
70
|
+
| Hotfix | `wip-release hotfix` | @latest | No | Yes (opt out with --no-release-notes) |
|
|
71
|
+
| Stable | `wip-release patch/minor/major` | @latest | Yes (deploy-public) | Yes, full notes |
|
|
72
|
+
|
|
73
|
+
### Version numbering
|
|
74
|
+
|
|
75
|
+
- Alpha: `1.9.68-alpha.1`, `1.9.68-alpha.2`, etc.
|
|
76
|
+
- Beta: `1.9.68-beta.1`, `1.9.68-beta.2`, etc.
|
|
77
|
+
- Hotfix: normal version bump (patch)
|
|
78
|
+
- Stable: normal version bump (patch/minor/major)
|
|
79
|
+
|
|
61
80
|
## API Reference
|
|
62
81
|
|
|
63
82
|
### CLI
|
|
64
83
|
|
|
65
84
|
```bash
|
|
66
|
-
|
|
85
|
+
# Stable (existing behavior)
|
|
86
|
+
wip-release patch # full pipeline
|
|
67
87
|
wip-release minor --dry-run # preview only
|
|
68
88
|
wip-release major --no-publish # bump + tag only
|
|
69
89
|
wip-release patch --skip-product-check # skip product docs gate
|
|
90
|
+
|
|
91
|
+
# Alpha
|
|
92
|
+
wip-release alpha # npm @alpha, no public notes
|
|
93
|
+
wip-release alpha --release-notes # npm @alpha + prerelease notes on public
|
|
94
|
+
|
|
95
|
+
# Beta
|
|
96
|
+
wip-release beta # npm @beta + prerelease notes on public
|
|
97
|
+
wip-release beta --no-release-notes # npm @beta, skip public notes
|
|
98
|
+
|
|
99
|
+
# Hotfix
|
|
100
|
+
wip-release hotfix # npm @latest + public release notes
|
|
101
|
+
wip-release hotfix --no-release-notes # npm @latest, skip public notes
|
|
70
102
|
```
|
|
71
103
|
|
|
72
104
|
### Product Docs Gate
|
|
@@ -108,9 +140,16 @@ After publishing, wip-release auto-copies SKILL.md to your website as plain text
|
|
|
108
140
|
### Module
|
|
109
141
|
|
|
110
142
|
```javascript
|
|
111
|
-
import { release, detectCurrentVersion, bumpSemver,
|
|
143
|
+
import { release, releasePrerelease, releaseHotfix, detectCurrentVersion, bumpSemver, bumpPrerelease } from '@wipcomputer/wip-release';
|
|
112
144
|
|
|
145
|
+
// Stable release
|
|
113
146
|
await release({ repoPath: '.', level: 'patch', notes: 'fix', dryRun: false, noPublish: false });
|
|
147
|
+
|
|
148
|
+
// Alpha/beta prerelease
|
|
149
|
+
await releasePrerelease({ repoPath: '.', track: 'alpha', notes: 'testing', dryRun: false, noPublish: false, publishReleaseNotes: false });
|
|
150
|
+
|
|
151
|
+
// Hotfix
|
|
152
|
+
await releaseHotfix({ repoPath: '.', notes: 'critical fix', dryRun: false, noPublish: false, publishReleaseNotes: true });
|
|
114
153
|
```
|
|
115
154
|
|
|
116
155
|
## Troubleshooting
|
package/tools/wip-release/cli.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Release tool CLI. Bumps version, updates docs, publishes.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { release, detectCurrentVersion, collectMergedPRNotes } from './core.mjs';
|
|
8
|
+
import { release, releasePrerelease, releaseHotfix, detectCurrentVersion, collectMergedPRNotes } from './core.mjs';
|
|
9
9
|
|
|
10
10
|
const args = process.argv.slice(2);
|
|
11
|
-
const level = args.find(a => ['patch', 'minor', 'major'].includes(a));
|
|
11
|
+
const level = args.find(a => ['patch', 'minor', 'major', 'alpha', 'beta', 'hotfix'].includes(a));
|
|
12
12
|
|
|
13
13
|
function flag(name) {
|
|
14
14
|
const prefix = `--${name}=`;
|
|
@@ -23,6 +23,8 @@ const skipStaleCheck = args.includes('--skip-stale-check');
|
|
|
23
23
|
const skipWorktreeCheck = args.includes('--skip-worktree-check');
|
|
24
24
|
const skipTechDocsCheck = args.includes('--skip-tech-docs-check');
|
|
25
25
|
const skipCoverageCheck = args.includes('--skip-coverage-check');
|
|
26
|
+
const wantReleaseNotes = args.includes('--release-notes');
|
|
27
|
+
const noReleaseNotes = args.includes('--no-release-notes');
|
|
26
28
|
const notesFilePath = flag('notes-file');
|
|
27
29
|
let notes = flag('notes');
|
|
28
30
|
// Bug fix #121: use strict check, not truthiness. --notes="" is empty, not absent.
|
|
@@ -51,13 +53,15 @@ let notesSource = (notes !== null && notes !== undefined && notes !== '') ? 'fla
|
|
|
51
53
|
}
|
|
52
54
|
notes = readFileSync(resolved, 'utf8').trim();
|
|
53
55
|
notesSource = 'file';
|
|
54
|
-
} else if (level) {
|
|
56
|
+
} else if (level && ['patch', 'minor', 'major', 'hotfix'].includes(level)) {
|
|
55
57
|
// 2. Auto-detect RELEASE-NOTES-v{version}.md (ALWAYS checks, even if --notes provided)
|
|
58
|
+
// Only for stable levels and hotfix. Alpha/beta skip this.
|
|
56
59
|
try {
|
|
57
60
|
const { detectCurrentVersion, bumpSemver } = await import('./core.mjs');
|
|
58
61
|
const cwd = process.cwd();
|
|
59
62
|
const currentVersion = detectCurrentVersion(cwd);
|
|
60
|
-
const
|
|
63
|
+
const bumpLevel = level === 'hotfix' ? 'patch' : level;
|
|
64
|
+
const newVersion = bumpSemver(currentVersion, bumpLevel);
|
|
61
65
|
const dashed = newVersion.replace(/\./g, '-');
|
|
62
66
|
const autoFile = join(cwd, `RELEASE-NOTES-v${dashed}.md`);
|
|
63
67
|
if (existsSync(autoFile)) {
|
|
@@ -75,12 +79,13 @@ let notesSource = (notes !== null && notes !== undefined && notes !== '') ? 'fla
|
|
|
75
79
|
// 2.5. Auto-combine release notes from merged PRs since last tag (#237)
|
|
76
80
|
// Only runs when no single RELEASE-NOTES file was found on disk.
|
|
77
81
|
// Scans git merge history for RELEASE-NOTES files committed on PR branches.
|
|
78
|
-
if (level && notesSource !== 'file') {
|
|
82
|
+
if (level && ['patch', 'minor', 'major', 'hotfix'].includes(level) && notesSource !== 'file') {
|
|
79
83
|
try {
|
|
80
84
|
const { collectMergedPRNotes, detectCurrentVersion: dcv, bumpSemver: bs } = await import('./core.mjs');
|
|
81
85
|
const cwd = process.cwd();
|
|
82
86
|
const cv = dcv(cwd);
|
|
83
|
-
const
|
|
87
|
+
const bumpLevel = level === 'hotfix' ? 'patch' : level;
|
|
88
|
+
const nv = bs(cv, bumpLevel);
|
|
84
89
|
const combined = collectMergedPRNotes(cwd, cv, nv);
|
|
85
90
|
if (combined) {
|
|
86
91
|
if (flagNotes && flagNotes !== combined.notes) {
|
|
@@ -144,27 +149,37 @@ if (!level || args.includes('--help') || args.includes('-h')) {
|
|
|
144
149
|
console.log(`wip-release ... local release tool${current}
|
|
145
150
|
|
|
146
151
|
Usage:
|
|
147
|
-
wip-release patch 1.0.0 -> 1.0.1
|
|
148
|
-
wip-release minor 1.0.0 -> 1.1.0
|
|
149
|
-
wip-release major 1.0.0 -> 2.0.0
|
|
152
|
+
wip-release patch 1.0.0 -> 1.0.1 (stable)
|
|
153
|
+
wip-release minor 1.0.0 -> 1.1.0 (stable)
|
|
154
|
+
wip-release major 1.0.0 -> 2.0.0 (stable)
|
|
155
|
+
wip-release alpha 1.0.1-alpha.1 (prerelease)
|
|
156
|
+
wip-release beta 1.0.1-beta.1 (prerelease)
|
|
157
|
+
wip-release hotfix 1.0.0 -> 1.0.1 (hotfix, no deploy-public)
|
|
158
|
+
|
|
159
|
+
Release tracks:
|
|
160
|
+
alpha npm @alpha tag, no public notes (opt in with --release-notes)
|
|
161
|
+
beta npm @beta tag, prerelease notes on public (opt out with --no-release-notes)
|
|
162
|
+
hotfix npm @latest tag, release notes on public (opt out with --no-release-notes)
|
|
163
|
+
stable npm @latest tag, deploy-public, full notes (patch/minor/major)
|
|
150
164
|
|
|
151
165
|
Flags:
|
|
152
166
|
--notes="description" Release narrative (what was built and why)
|
|
153
167
|
--notes-file=path Read release narrative from a markdown file
|
|
168
|
+
--release-notes Opt in to public release notes (alpha only)
|
|
169
|
+
--no-release-notes Opt out of public release notes (beta, hotfix)
|
|
154
170
|
--dry-run Show what would happen, change nothing
|
|
155
171
|
--no-publish Bump + tag only, skip npm/GitHub
|
|
156
172
|
--skip-product-check Skip product docs check (dev update, roadmap, readme-first)
|
|
157
173
|
--skip-stale-check Skip stale remote branch check
|
|
158
174
|
--skip-worktree-check Skip worktree guard (allow release from worktree)
|
|
159
175
|
|
|
160
|
-
Release notes (REQUIRED
|
|
176
|
+
Release notes (REQUIRED for stable, optional for other tracks):
|
|
161
177
|
1. --notes-file=path Explicit file path
|
|
162
178
|
2. RELEASE-NOTES-v{ver}.md In repo root (auto-detected)
|
|
163
179
|
3. Merged PR notes Auto-combined from git history (#237)
|
|
164
180
|
4. ai/dev-updates/YYYY-MM-DD* Today's dev update (auto-detected)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
When batching multiple PRs, each PR's RELEASE-NOTES are auto-combined.
|
|
181
|
+
For stable releases: the --notes flag is NOT accepted. Write a file.
|
|
182
|
+
For alpha/beta/hotfix: --notes="text" is accepted as a convenience.
|
|
168
183
|
|
|
169
184
|
Skill publish to website:
|
|
170
185
|
Add .publish-skill.json to repo root: { "name": "my-tool" }
|
|
@@ -172,7 +187,7 @@ Skill publish to website:
|
|
|
172
187
|
After release, SKILL.md is copied to {website}/wip.computer/install/{name}.txt
|
|
173
188
|
and deploy.sh is run to push to VPS.
|
|
174
189
|
|
|
175
|
-
Pipeline:
|
|
190
|
+
Pipeline (stable):
|
|
176
191
|
1. Bump package.json version
|
|
177
192
|
2. Sync SKILL.md version (if exists)
|
|
178
193
|
3. Update CHANGELOG.md
|
|
@@ -181,23 +196,64 @@ Pipeline:
|
|
|
181
196
|
6. npm publish (via 1Password)
|
|
182
197
|
7. GitHub Packages publish
|
|
183
198
|
8. GitHub release create
|
|
184
|
-
9. Publish SKILL.md to website (if configured)
|
|
199
|
+
9. Publish SKILL.md to website (if configured)
|
|
200
|
+
|
|
201
|
+
Pipeline (alpha/beta):
|
|
202
|
+
1. Bump version with prerelease suffix (-alpha.N / -beta.N)
|
|
203
|
+
2. npm publish with --tag alpha or --tag beta
|
|
204
|
+
3. GitHub prerelease (beta default, alpha opt-in)
|
|
205
|
+
|
|
206
|
+
Pipeline (hotfix):
|
|
207
|
+
1. Bump patch version (no suffix)
|
|
208
|
+
2. npm publish with --tag latest
|
|
209
|
+
3. GitHub release (no deploy-public)`);
|
|
185
210
|
process.exit(level ? 0 : 1);
|
|
186
211
|
}
|
|
187
212
|
|
|
188
|
-
release
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
213
|
+
// Route to the correct release function based on track
|
|
214
|
+
if (level === 'alpha' || level === 'beta') {
|
|
215
|
+
// Prerelease track: alpha or beta
|
|
216
|
+
releasePrerelease({
|
|
217
|
+
repoPath: process.cwd(),
|
|
218
|
+
track: level,
|
|
219
|
+
notes,
|
|
220
|
+
dryRun,
|
|
221
|
+
noPublish,
|
|
222
|
+
publishReleaseNotes: level === 'alpha' ? wantReleaseNotes : !noReleaseNotes,
|
|
223
|
+
}).catch(err => {
|
|
224
|
+
console.error(` \u2717 ${err.message}`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
227
|
+
} else if (level === 'hotfix') {
|
|
228
|
+
// Hotfix track: patch bump, @latest tag, no deploy-public
|
|
229
|
+
releaseHotfix({
|
|
230
|
+
repoPath: process.cwd(),
|
|
231
|
+
notes,
|
|
232
|
+
notesSource,
|
|
233
|
+
dryRun,
|
|
234
|
+
noPublish,
|
|
235
|
+
publishReleaseNotes: !noReleaseNotes,
|
|
236
|
+
skipWorktreeCheck,
|
|
237
|
+
}).catch(err => {
|
|
238
|
+
console.error(` \u2717 ${err.message}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
241
|
+
} else {
|
|
242
|
+
// Stable track: patch, minor, major
|
|
243
|
+
release({
|
|
244
|
+
repoPath: process.cwd(),
|
|
245
|
+
level,
|
|
246
|
+
notes,
|
|
247
|
+
notesSource,
|
|
248
|
+
dryRun,
|
|
249
|
+
noPublish,
|
|
250
|
+
skipProductCheck,
|
|
251
|
+
skipStaleCheck,
|
|
252
|
+
skipWorktreeCheck,
|
|
253
|
+
skipTechDocsCheck,
|
|
254
|
+
skipCoverageCheck,
|
|
255
|
+
}).catch(err => {
|
|
256
|
+
console.error(` \u2717 ${err.message}`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
@@ -34,6 +34,37 @@ export function bumpSemver(version, level) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Bump a version string for prerelease tracks (alpha, beta).
|
|
39
|
+
*
|
|
40
|
+
* If the current version already has the same prerelease prefix,
|
|
41
|
+
* increment the counter: 1.2.3-alpha.1 -> 1.2.3-alpha.2
|
|
42
|
+
*
|
|
43
|
+
* If the current version is a clean release or a different prerelease,
|
|
44
|
+
* bump patch and start at .1: 1.2.3 -> 1.2.4-alpha.1
|
|
45
|
+
*/
|
|
46
|
+
export function bumpPrerelease(version, track) {
|
|
47
|
+
// Check if current version already has this prerelease prefix
|
|
48
|
+
const preMatch = version.match(new RegExp(`^(\\d+\\.\\d+\\.\\d+)-${track}\\.(\\d+)$`));
|
|
49
|
+
if (preMatch) {
|
|
50
|
+
// Same track: increment the counter
|
|
51
|
+
const base = preMatch[1];
|
|
52
|
+
const counter = parseInt(preMatch[2], 10);
|
|
53
|
+
return `${base}-${track}.${counter + 1}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Strip any existing prerelease suffix to get the base version
|
|
57
|
+
const baseMatch = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
58
|
+
if (!baseMatch) throw new Error(`Cannot parse version: ${version}`);
|
|
59
|
+
|
|
60
|
+
const major = parseInt(baseMatch[1], 10);
|
|
61
|
+
const minor = parseInt(baseMatch[2], 10);
|
|
62
|
+
const patch = parseInt(baseMatch[3], 10);
|
|
63
|
+
|
|
64
|
+
// Bump patch and start prerelease at .1
|
|
65
|
+
return `${major}.${minor}.${patch + 1}-${track}.1`;
|
|
66
|
+
}
|
|
67
|
+
|
|
37
68
|
/**
|
|
38
69
|
* Write new version to package.json.
|
|
39
70
|
*/
|
|
@@ -200,6 +231,18 @@ export function publishNpm(repoPath) {
|
|
|
200
231
|
], { cwd: repoPath, stdio: 'inherit' });
|
|
201
232
|
}
|
|
202
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Publish to npm with a specific dist-tag (alpha, beta, latest).
|
|
236
|
+
*/
|
|
237
|
+
export function publishNpmWithTag(repoPath, tag) {
|
|
238
|
+
const token = getNpmToken();
|
|
239
|
+
execFileSync('npm', [
|
|
240
|
+
'publish', '--access', 'public',
|
|
241
|
+
'--tag', tag,
|
|
242
|
+
`--//registry.npmjs.org/:_authToken=${token}`
|
|
243
|
+
], { cwd: repoPath, stdio: 'inherit' });
|
|
244
|
+
}
|
|
245
|
+
|
|
203
246
|
/**
|
|
204
247
|
* Publish to GitHub Packages.
|
|
205
248
|
*/
|
|
@@ -1018,6 +1061,61 @@ export function createGitHubRelease(repoPath, newVersion, notes, currentVersion)
|
|
|
1018
1061
|
}
|
|
1019
1062
|
}
|
|
1020
1063
|
|
|
1064
|
+
/**
|
|
1065
|
+
* Create a GitHub prerelease on the PUBLIC repo (no code sync).
|
|
1066
|
+
* Used by alpha (opt-in) and beta (default) tracks.
|
|
1067
|
+
*/
|
|
1068
|
+
export function createGitHubPrerelease(repoPath, newVersion, notes) {
|
|
1069
|
+
const repoSlug = detectRepoSlug(repoPath);
|
|
1070
|
+
if (!repoSlug) throw new Error('Cannot detect repo slug from git remote');
|
|
1071
|
+
|
|
1072
|
+
// Target the public repo (strip -private suffix)
|
|
1073
|
+
const publicSlug = repoSlug.replace(/-private$/, '');
|
|
1074
|
+
const body = notes || `Prerelease ${newVersion}`;
|
|
1075
|
+
|
|
1076
|
+
const tmpFile = join(repoPath, '.release-notes-tmp.md');
|
|
1077
|
+
writeFileSync(tmpFile, body);
|
|
1078
|
+
|
|
1079
|
+
try {
|
|
1080
|
+
execFileSync('gh', [
|
|
1081
|
+
'release', 'create', `v${newVersion}`,
|
|
1082
|
+
'--title', `v${newVersion}`,
|
|
1083
|
+
'--notes-file', '.release-notes-tmp.md',
|
|
1084
|
+
'--prerelease',
|
|
1085
|
+
'--repo', publicSlug
|
|
1086
|
+
], { cwd: repoPath, stdio: 'inherit' });
|
|
1087
|
+
} finally {
|
|
1088
|
+
try { execFileSync('rm', ['-f', tmpFile]); } catch {}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Create a GitHub release on the PUBLIC repo (no code sync).
|
|
1094
|
+
* Used by the hotfix track.
|
|
1095
|
+
*/
|
|
1096
|
+
export function createGitHubReleaseOnPublic(repoPath, newVersion, notes, currentVersion) {
|
|
1097
|
+
const repoSlug = detectRepoSlug(repoPath);
|
|
1098
|
+
if (!repoSlug) throw new Error('Cannot detect repo slug from git remote');
|
|
1099
|
+
|
|
1100
|
+
// Target the public repo (strip -private suffix)
|
|
1101
|
+
const publicSlug = repoSlug.replace(/-private$/, '');
|
|
1102
|
+
const body = buildReleaseNotes(repoPath, currentVersion, newVersion, notes);
|
|
1103
|
+
|
|
1104
|
+
const tmpFile = join(repoPath, '.release-notes-tmp.md');
|
|
1105
|
+
writeFileSync(tmpFile, body);
|
|
1106
|
+
|
|
1107
|
+
try {
|
|
1108
|
+
execFileSync('gh', [
|
|
1109
|
+
'release', 'create', `v${newVersion}`,
|
|
1110
|
+
'--title', `v${newVersion}`,
|
|
1111
|
+
'--notes-file', '.release-notes-tmp.md',
|
|
1112
|
+
'--repo', publicSlug
|
|
1113
|
+
], { cwd: repoPath, stdio: 'inherit' });
|
|
1114
|
+
} finally {
|
|
1115
|
+
try { execFileSync('rm', ['-f', tmpFile]); } catch {}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1021
1119
|
/**
|
|
1022
1120
|
* Publish skill to ClawHub.
|
|
1023
1121
|
*/
|
|
@@ -1900,3 +1998,371 @@ export async function release({ repoPath, level, notes, notesSource, dryRun, noP
|
|
|
1900
1998
|
|
|
1901
1999
|
return { currentVersion, newVersion, dryRun: false };
|
|
1902
2000
|
}
|
|
2001
|
+
|
|
2002
|
+
// ── Prerelease Track (Alpha / Beta) ────────────────────────────────
|
|
2003
|
+
|
|
2004
|
+
/**
|
|
2005
|
+
* Release an alpha or beta prerelease.
|
|
2006
|
+
*
|
|
2007
|
+
* Alpha: npm @alpha, no public release notes by default (opt in with publishReleaseNotes).
|
|
2008
|
+
* Beta: npm @beta, prerelease notes on public repo by default (opt out with publishReleaseNotes=false).
|
|
2009
|
+
*
|
|
2010
|
+
* No deploy-public. No code sync. No CHANGELOG gate. No product docs gate.
|
|
2011
|
+
* Lightweight: bump version, npm publish with tag, optional GitHub prerelease.
|
|
2012
|
+
*/
|
|
2013
|
+
export async function releasePrerelease({ repoPath, track, notes, dryRun, noPublish, publishReleaseNotes }) {
|
|
2014
|
+
repoPath = repoPath || process.cwd();
|
|
2015
|
+
const currentVersion = detectCurrentVersion(repoPath);
|
|
2016
|
+
const newVersion = bumpPrerelease(currentVersion, track);
|
|
2017
|
+
const repoName = basename(repoPath);
|
|
2018
|
+
|
|
2019
|
+
console.log('');
|
|
2020
|
+
console.log(` ${repoName}: ${currentVersion} -> ${newVersion} (${track})`);
|
|
2021
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
2022
|
+
|
|
2023
|
+
if (dryRun) {
|
|
2024
|
+
console.log(` [dry run] Would bump package.json to ${newVersion}`);
|
|
2025
|
+
if (!noPublish) {
|
|
2026
|
+
console.log(` [dry run] Would npm publish with --tag ${track}`);
|
|
2027
|
+
if (publishReleaseNotes) {
|
|
2028
|
+
console.log(` [dry run] Would create GitHub prerelease v${newVersion} on public repo`);
|
|
2029
|
+
} else {
|
|
2030
|
+
console.log(` [dry run] No GitHub prerelease (silent)`);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
console.log('');
|
|
2034
|
+
console.log(` Dry run complete. No changes made.`);
|
|
2035
|
+
console.log('');
|
|
2036
|
+
return { currentVersion, newVersion, dryRun: true };
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// 1. Bump package.json
|
|
2040
|
+
writePackageVersion(repoPath, newVersion);
|
|
2041
|
+
console.log(` \u2713 package.json -> ${newVersion}`);
|
|
2042
|
+
|
|
2043
|
+
// 2. Update CHANGELOG.md (lightweight entry)
|
|
2044
|
+
updateChangelog(repoPath, newVersion, notes || `${track} prerelease`);
|
|
2045
|
+
console.log(` \u2713 CHANGELOG.md updated`);
|
|
2046
|
+
|
|
2047
|
+
// 3. Git commit + tag
|
|
2048
|
+
const msg = `v${newVersion}: ${track} prerelease`;
|
|
2049
|
+
for (const f of ['package.json', 'CHANGELOG.md']) {
|
|
2050
|
+
if (existsSync(join(repoPath, f))) {
|
|
2051
|
+
execFileSync('git', ['add', f], { cwd: repoPath, stdio: 'pipe' });
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
execFileSync('git', ['commit', '--no-verify', '-m', msg], { cwd: repoPath, stdio: 'pipe' });
|
|
2055
|
+
execFileSync('git', ['tag', `v${newVersion}`], { cwd: repoPath, stdio: 'pipe' });
|
|
2056
|
+
console.log(` \u2713 Committed and tagged v${newVersion}`);
|
|
2057
|
+
|
|
2058
|
+
// 4. Push commit + tag
|
|
2059
|
+
try {
|
|
2060
|
+
execSync('git push && git push --tags', { cwd: repoPath, stdio: 'pipe' });
|
|
2061
|
+
console.log(` \u2713 Pushed to remote`);
|
|
2062
|
+
} catch {
|
|
2063
|
+
console.log(` ! Push failed. Push manually.`);
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
const distResults = [];
|
|
2067
|
+
|
|
2068
|
+
if (!noPublish) {
|
|
2069
|
+
// 5. npm publish with dist-tag
|
|
2070
|
+
try {
|
|
2071
|
+
publishNpmWithTag(repoPath, track);
|
|
2072
|
+
const pkg = JSON.parse(readFileSync(join(repoPath, 'package.json'), 'utf8'));
|
|
2073
|
+
distResults.push({ target: 'npm', status: 'ok', detail: `${pkg.name}@${newVersion} (tag: ${track})` });
|
|
2074
|
+
console.log(` \u2713 Published to npm @${track}`);
|
|
2075
|
+
} catch (e) {
|
|
2076
|
+
distResults.push({ target: 'npm', status: 'failed', detail: e.message });
|
|
2077
|
+
console.log(` \u2717 npm publish failed: ${e.message}`);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// 6. GitHub prerelease on public repo (if opted in)
|
|
2081
|
+
if (publishReleaseNotes) {
|
|
2082
|
+
try {
|
|
2083
|
+
createGitHubPrerelease(repoPath, newVersion, notes || `${track} prerelease`);
|
|
2084
|
+
distResults.push({ target: 'GitHub', status: 'ok', detail: `v${newVersion} (prerelease)` });
|
|
2085
|
+
console.log(` \u2713 GitHub prerelease v${newVersion} created on public repo`);
|
|
2086
|
+
} catch (e) {
|
|
2087
|
+
distResults.push({ target: 'GitHub', status: 'failed', detail: e.message });
|
|
2088
|
+
console.log(` \u2717 GitHub prerelease failed: ${e.message}`);
|
|
2089
|
+
}
|
|
2090
|
+
} else {
|
|
2091
|
+
console.log(` - GitHub prerelease: skipped (silent ${track})`);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// Distribution summary
|
|
2096
|
+
if (distResults.length > 0) {
|
|
2097
|
+
console.log('');
|
|
2098
|
+
console.log(' Distribution:');
|
|
2099
|
+
for (const r of distResults) {
|
|
2100
|
+
const icon = r.status === 'ok' ? '\u2713' : '\u2717';
|
|
2101
|
+
console.log(` ${icon} ${r.target}: ${r.detail}`);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
console.log('');
|
|
2106
|
+
console.log(` Done. ${repoName} v${newVersion} (${track}) released.`);
|
|
2107
|
+
console.log('');
|
|
2108
|
+
|
|
2109
|
+
return { currentVersion, newVersion, dryRun: false };
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// ── Hotfix Track ────────────────────────────────────────────────────
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* Release a hotfix.
|
|
2116
|
+
*
|
|
2117
|
+
* Same as stable patch but: no deploy-public, no code sync.
|
|
2118
|
+
* Publishes to npm @latest, creates GitHub release on public repo (opt out with publishReleaseNotes=false).
|
|
2119
|
+
*
|
|
2120
|
+
* Lighter gates than stable: no product docs check, no stale branch check.
|
|
2121
|
+
* Still runs: worktree guard, license compliance, tests.
|
|
2122
|
+
*/
|
|
2123
|
+
export async function releaseHotfix({ repoPath, notes, notesSource, dryRun, noPublish, publishReleaseNotes, skipWorktreeCheck }) {
|
|
2124
|
+
repoPath = repoPath || process.cwd();
|
|
2125
|
+
const currentVersion = detectCurrentVersion(repoPath);
|
|
2126
|
+
const newVersion = bumpSemver(currentVersion, 'patch');
|
|
2127
|
+
const repoName = basename(repoPath);
|
|
2128
|
+
|
|
2129
|
+
console.log('');
|
|
2130
|
+
console.log(` ${repoName}: ${currentVersion} -> ${newVersion} (hotfix)`);
|
|
2131
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
2132
|
+
|
|
2133
|
+
// Worktree guard
|
|
2134
|
+
if (!skipWorktreeCheck) {
|
|
2135
|
+
try {
|
|
2136
|
+
const gitDir = execFileSync('git', ['rev-parse', '--git-dir'], {
|
|
2137
|
+
cwd: repoPath, encoding: 'utf8'
|
|
2138
|
+
}).trim();
|
|
2139
|
+
if (gitDir.includes('/worktrees/')) {
|
|
2140
|
+
const worktreeList = execFileSync('git', ['worktree', 'list', '--porcelain'], {
|
|
2141
|
+
cwd: repoPath, encoding: 'utf8'
|
|
2142
|
+
});
|
|
2143
|
+
const mainWorktree = worktreeList.split('\n')
|
|
2144
|
+
.find(line => line.startsWith('worktree '));
|
|
2145
|
+
const mainPath = mainWorktree ? mainWorktree.replace('worktree ', '') : '(unknown)';
|
|
2146
|
+
console.log(` \u2717 wip-release must run from the main working tree, not a worktree.`);
|
|
2147
|
+
console.log(` Current: ${repoPath}`);
|
|
2148
|
+
console.log(` Main working tree: ${mainPath}`);
|
|
2149
|
+
console.log('');
|
|
2150
|
+
return { currentVersion, newVersion, dryRun: false, failed: true };
|
|
2151
|
+
}
|
|
2152
|
+
console.log(' \u2713 Running from main working tree');
|
|
2153
|
+
} catch {}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// License compliance gate
|
|
2157
|
+
const configPath = join(repoPath, '.license-guard.json');
|
|
2158
|
+
if (existsSync(configPath)) {
|
|
2159
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
2160
|
+
const licenseIssues = [];
|
|
2161
|
+
const licensePath = join(repoPath, 'LICENSE');
|
|
2162
|
+
if (!existsSync(licensePath)) {
|
|
2163
|
+
licenseIssues.push('LICENSE file is missing');
|
|
2164
|
+
} else {
|
|
2165
|
+
const licenseText = readFileSync(licensePath, 'utf8');
|
|
2166
|
+
if (!licenseText.includes(config.copyright)) {
|
|
2167
|
+
licenseIssues.push(`LICENSE copyright does not match "${config.copyright}"`);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
if (licenseIssues.length > 0) {
|
|
2171
|
+
console.log(` \u2717 License compliance failed:`);
|
|
2172
|
+
for (const issue of licenseIssues) console.log(` - ${issue}`);
|
|
2173
|
+
console.log('');
|
|
2174
|
+
return { currentVersion, newVersion, dryRun: false, failed: true };
|
|
2175
|
+
}
|
|
2176
|
+
console.log(` \u2713 License compliance passed`);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// Release notes: hotfix accepts --notes flag as convenience (no file-only gate)
|
|
2180
|
+
if (!notes) {
|
|
2181
|
+
console.log(` ! No release notes provided. Hotfix will have minimal notes.`);
|
|
2182
|
+
notes = 'Hotfix release.';
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
// Run tests if they exist
|
|
2186
|
+
{
|
|
2187
|
+
const toolsDir = join(repoPath, 'tools');
|
|
2188
|
+
const testFiles = [];
|
|
2189
|
+
if (existsSync(toolsDir)) {
|
|
2190
|
+
for (const sub of readdirSync(toolsDir)) {
|
|
2191
|
+
const testPath = join(toolsDir, sub, 'test.sh');
|
|
2192
|
+
if (existsSync(testPath)) testFiles.push({ tool: sub, path: testPath });
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
const rootTest = join(repoPath, 'test.sh');
|
|
2196
|
+
if (existsSync(rootTest)) testFiles.push({ tool: '(root)', path: rootTest });
|
|
2197
|
+
|
|
2198
|
+
if (testFiles.length > 0) {
|
|
2199
|
+
let allPassed = true;
|
|
2200
|
+
for (const { tool, path } of testFiles) {
|
|
2201
|
+
try {
|
|
2202
|
+
execFileSync('bash', [path], { cwd: dirname(path), stdio: 'pipe', timeout: 30000 });
|
|
2203
|
+
console.log(` \u2713 Tests passed: ${tool}`);
|
|
2204
|
+
} catch (e) {
|
|
2205
|
+
allPassed = false;
|
|
2206
|
+
console.log(` \u2717 Tests FAILED: ${tool}`);
|
|
2207
|
+
const output = (e.stdout || '').toString().trim();
|
|
2208
|
+
if (output) {
|
|
2209
|
+
for (const line of output.split('\n').slice(-5)) console.log(` ${line}`);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
if (!allPassed) {
|
|
2214
|
+
console.log('');
|
|
2215
|
+
console.log(' Fix failing tests before releasing.');
|
|
2216
|
+
console.log('');
|
|
2217
|
+
return { currentVersion, newVersion, dryRun: false, failed: true };
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
if (dryRun) {
|
|
2223
|
+
console.log(` [dry run] Would bump package.json to ${newVersion}`);
|
|
2224
|
+
console.log(` [dry run] Would update CHANGELOG.md`);
|
|
2225
|
+
if (!noPublish) {
|
|
2226
|
+
console.log(` [dry run] Would npm publish with --tag latest`);
|
|
2227
|
+
if (publishReleaseNotes) {
|
|
2228
|
+
console.log(` [dry run] Would create GitHub release v${newVersion} on public repo`);
|
|
2229
|
+
} else {
|
|
2230
|
+
console.log(` [dry run] No GitHub release (--no-release-notes)`);
|
|
2231
|
+
}
|
|
2232
|
+
console.log(` [dry run] No deploy-public (hotfix)`);
|
|
2233
|
+
}
|
|
2234
|
+
console.log('');
|
|
2235
|
+
console.log(` Dry run complete. No changes made.`);
|
|
2236
|
+
console.log('');
|
|
2237
|
+
return { currentVersion, newVersion, dryRun: true };
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// 1. Bump package.json
|
|
2241
|
+
writePackageVersion(repoPath, newVersion);
|
|
2242
|
+
console.log(` \u2713 package.json -> ${newVersion}`);
|
|
2243
|
+
|
|
2244
|
+
// 1.5. Bump sub-tool versions
|
|
2245
|
+
const toolsDir = join(repoPath, 'tools');
|
|
2246
|
+
if (existsSync(toolsDir)) {
|
|
2247
|
+
let subBumped = 0;
|
|
2248
|
+
try {
|
|
2249
|
+
const entries = readdirSync(toolsDir, { withFileTypes: true });
|
|
2250
|
+
for (const entry of entries) {
|
|
2251
|
+
if (!entry.isDirectory()) continue;
|
|
2252
|
+
const subPkgPath = join(toolsDir, entry.name, 'package.json');
|
|
2253
|
+
if (existsSync(subPkgPath)) {
|
|
2254
|
+
try {
|
|
2255
|
+
const subPkg = JSON.parse(readFileSync(subPkgPath, 'utf8'));
|
|
2256
|
+
subPkg.version = newVersion;
|
|
2257
|
+
writeFileSync(subPkgPath, JSON.stringify(subPkg, null, 2) + '\n');
|
|
2258
|
+
subBumped++;
|
|
2259
|
+
} catch {}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
} catch {}
|
|
2263
|
+
if (subBumped > 0) {
|
|
2264
|
+
console.log(` \u2713 ${subBumped} sub-tool(s) -> ${newVersion}`);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// 2. Sync SKILL.md
|
|
2269
|
+
if (syncSkillVersion(repoPath, newVersion)) {
|
|
2270
|
+
console.log(` \u2713 SKILL.md -> ${newVersion}`);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// 3. Update CHANGELOG.md
|
|
2274
|
+
updateChangelog(repoPath, newVersion, notes);
|
|
2275
|
+
console.log(` \u2713 CHANGELOG.md updated`);
|
|
2276
|
+
|
|
2277
|
+
// 3.5. Move RELEASE-NOTES-v*.md to _trash/
|
|
2278
|
+
const trashed = trashReleaseNotes(repoPath);
|
|
2279
|
+
if (trashed > 0) {
|
|
2280
|
+
console.log(` \u2713 Moved ${trashed} RELEASE-NOTES file(s) to _trash/`);
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// 4. Git commit + tag
|
|
2284
|
+
gitCommitAndTag(repoPath, newVersion, notes);
|
|
2285
|
+
console.log(` \u2713 Committed and tagged v${newVersion}`);
|
|
2286
|
+
|
|
2287
|
+
// 5. Push commit + tag
|
|
2288
|
+
try {
|
|
2289
|
+
execSync('git push && git push --tags', { cwd: repoPath, stdio: 'pipe' });
|
|
2290
|
+
console.log(` \u2713 Pushed to remote`);
|
|
2291
|
+
} catch {
|
|
2292
|
+
console.log(` ! Push failed. Push manually.`);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
const distResults = [];
|
|
2296
|
+
|
|
2297
|
+
if (!noPublish) {
|
|
2298
|
+
// 6. npm publish with @latest tag
|
|
2299
|
+
try {
|
|
2300
|
+
publishNpmWithTag(repoPath, 'latest');
|
|
2301
|
+
const pkg = JSON.parse(readFileSync(join(repoPath, 'package.json'), 'utf8'));
|
|
2302
|
+
distResults.push({ target: 'npm', status: 'ok', detail: `${pkg.name}@${newVersion}` });
|
|
2303
|
+
console.log(` \u2713 Published to npm @latest`);
|
|
2304
|
+
} catch (e) {
|
|
2305
|
+
distResults.push({ target: 'npm', status: 'failed', detail: e.message });
|
|
2306
|
+
console.log(` \u2717 npm publish failed: ${e.message}`);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
// 7. GitHub release on public repo (not prerelease)
|
|
2310
|
+
if (publishReleaseNotes) {
|
|
2311
|
+
try {
|
|
2312
|
+
createGitHubReleaseOnPublic(repoPath, newVersion, notes, currentVersion);
|
|
2313
|
+
distResults.push({ target: 'GitHub', status: 'ok', detail: `v${newVersion}` });
|
|
2314
|
+
console.log(` \u2713 GitHub release v${newVersion} created on public repo`);
|
|
2315
|
+
} catch (e) {
|
|
2316
|
+
distResults.push({ target: 'GitHub', status: 'failed', detail: e.message });
|
|
2317
|
+
console.log(` \u2717 GitHub release failed: ${e.message}`);
|
|
2318
|
+
}
|
|
2319
|
+
} else {
|
|
2320
|
+
console.log(` - GitHub release: skipped (--no-release-notes)`);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// No deploy-public for hotfix
|
|
2324
|
+
console.log(` - deploy-public: skipped (hotfix)`);
|
|
2325
|
+
|
|
2326
|
+
// 8. ClawHub skill publish
|
|
2327
|
+
const rootSkill = join(repoPath, 'SKILL.md');
|
|
2328
|
+
if (existsSync(rootSkill)) {
|
|
2329
|
+
try {
|
|
2330
|
+
publishClawHub(repoPath, newVersion, notes);
|
|
2331
|
+
const slug = detectSkillSlug(repoPath);
|
|
2332
|
+
distResults.push({ target: 'ClawHub', status: 'ok', detail: `${slug}@${newVersion}` });
|
|
2333
|
+
console.log(` \u2713 Published to ClawHub: ${slug}`);
|
|
2334
|
+
} catch (e) {
|
|
2335
|
+
distResults.push({ target: 'ClawHub', status: 'failed', detail: e.message });
|
|
2336
|
+
console.log(` \u2717 ClawHub publish failed: ${e.message}`);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// Distribution summary
|
|
2342
|
+
if (distResults.length > 0) {
|
|
2343
|
+
console.log('');
|
|
2344
|
+
console.log(' Distribution:');
|
|
2345
|
+
for (const r of distResults) {
|
|
2346
|
+
const icon = r.status === 'ok' ? '\u2713' : '\u2717';
|
|
2347
|
+
console.log(` ${icon} ${r.target}: ${r.detail}`);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// Write release marker
|
|
2352
|
+
try {
|
|
2353
|
+
const markerDir = join(process.env.HOME || '', '.ldm', 'state');
|
|
2354
|
+
mkdirSync(markerDir, { recursive: true });
|
|
2355
|
+
writeFileSync(join(markerDir, '.last-release'), JSON.stringify({
|
|
2356
|
+
repo: repoName,
|
|
2357
|
+
version: newVersion,
|
|
2358
|
+
timestamp: new Date().toISOString(),
|
|
2359
|
+
track: 'hotfix',
|
|
2360
|
+
}) + '\n');
|
|
2361
|
+
} catch {}
|
|
2362
|
+
|
|
2363
|
+
console.log('');
|
|
2364
|
+
console.log(` Done. ${repoName} v${newVersion} (hotfix) released.`);
|
|
2365
|
+
console.log('');
|
|
2366
|
+
|
|
2367
|
+
return { currentVersion, newVersion, dryRun: false };
|
|
2368
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/universal-installer",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.68",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Universal Interface specification for agent-native software. Teaches your AI how to build repos with every interface: CLI, Module, MCP Server, OpenClaw Plugin, Skill, Claude Code Hook.",
|
|
6
6
|
"main": "detect.mjs",
|