@wipcomputer/wip-release 1.1.0
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 +20 -0
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/REFERENCE.md +96 -0
- package/SKILL.md +74 -0
- package/cli.mjs +61 -0
- package/core.mjs +340 -0
- package/package.json +33 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## 1.1.0 (2026-02-21)
|
|
5
|
+
|
|
6
|
+
Rich release notes, agent-driven install prompt, REFERENCE.md
|
|
7
|
+
|
|
8
|
+
## 1.0.0 (2026-02-21)
|
|
9
|
+
|
|
10
|
+
Initial release. Local release pipeline tool.
|
|
11
|
+
|
|
12
|
+
- `release()` ... full pipeline: bump, changelog, skill sync, commit, tag, publish
|
|
13
|
+
- `detectCurrentVersion()` ... read version from package.json
|
|
14
|
+
- `syncSkillVersion()` ... update SKILL.md frontmatter
|
|
15
|
+
- `updateChangelog()` ... prepend version entry
|
|
16
|
+
- `publishNpm()` ... npm publish via 1Password
|
|
17
|
+
- `publishGitHubPackages()` ... GitHub Packages publish
|
|
18
|
+
- `createGitHubRelease()` ... gh release create
|
|
19
|
+
|
|
20
|
+
CLI: `wip-release patch|minor|major [--notes --dry-run --no-publish]`
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Parker Todd Brooks
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
###### WIP Computer
|
|
2
|
+
# wip-release
|
|
3
|
+
|
|
4
|
+
You ship a fix. Now you have to bump package.json, update CHANGELOG.md, sync the version in SKILL.md, commit, tag, push, publish to npm, publish to GitHub Packages, and create a GitHub release. Every time. Miss a step and versions drift.
|
|
5
|
+
|
|
6
|
+
`wip-release` does all of it in one command.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Open your AI coding tool and say:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Read the REFERENCE.md and SKILL.md at github.com/wipcomputer/wip-release.
|
|
14
|
+
Then explain to me:
|
|
15
|
+
1. What is this tool?
|
|
16
|
+
2. What does it do?
|
|
17
|
+
3. What would it change or fix in our current release process?
|
|
18
|
+
|
|
19
|
+
Then ask me:
|
|
20
|
+
- Do you have more questions?
|
|
21
|
+
- Do you want to integrate it into our system?
|
|
22
|
+
- Do you want to clone it (use as-is) or fork it (so you can contribute back if you find bugs)?
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Your agent will read the repo, explain the tool, and walk you through integration interactively.
|
|
26
|
+
|
|
27
|
+
See [REFERENCE.md](REFERENCE.md) for full usage, pipeline steps, flags, auth, and module API.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
MIT
|
|
34
|
+
|
|
35
|
+
Built by Parker Todd Brooks, with Claude Code and Lēsa (OpenClaw).
|
package/REFERENCE.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
###### WIP Computer
|
|
2
|
+
# wip-release ... Reference
|
|
3
|
+
|
|
4
|
+
Detailed usage, pipeline steps, flags, auth, and module API.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
Run from inside any repo:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
wip-release patch # 1.0.0 -> 1.0.1
|
|
12
|
+
wip-release minor # 1.0.0 -> 1.1.0
|
|
13
|
+
wip-release major # 1.0.0 -> 2.0.0
|
|
14
|
+
|
|
15
|
+
wip-release patch --notes="fix auth config" # with changelog note
|
|
16
|
+
wip-release minor --dry-run # preview, no changes
|
|
17
|
+
wip-release patch --no-publish # bump + tag only
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## What It Does
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
wip-grok: 1.0.0 -> 1.0.1 (patch)
|
|
24
|
+
────────────────────────────────────────
|
|
25
|
+
✓ package.json -> 1.0.1
|
|
26
|
+
✓ SKILL.md -> 1.0.1
|
|
27
|
+
✓ CHANGELOG.md updated
|
|
28
|
+
✓ Committed and tagged v1.0.1
|
|
29
|
+
✓ Pushed to remote
|
|
30
|
+
✓ Published to npm
|
|
31
|
+
✓ Published to GitHub Packages
|
|
32
|
+
✓ GitHub release v1.0.1 created
|
|
33
|
+
|
|
34
|
+
Done. wip-grok v1.0.1 released.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Pipeline Steps
|
|
38
|
+
|
|
39
|
+
1. **Bump `package.json`** ... patch, minor, or major
|
|
40
|
+
2. **Sync `SKILL.md`** ... updates version in YAML frontmatter (if file exists)
|
|
41
|
+
3. **Update `CHANGELOG.md`** ... prepends new version entry with date and notes
|
|
42
|
+
4. **Git commit + tag** ... commits changed files, creates `vX.Y.Z` tag
|
|
43
|
+
5. **Push** ... pushes commit and tag to remote
|
|
44
|
+
6. **npm publish** ... publishes to npmjs.com (auth via 1Password)
|
|
45
|
+
7. **GitHub Packages** ... publishes to npm.pkg.github.com
|
|
46
|
+
8. **GitHub release** ... creates release with changelog notes
|
|
47
|
+
|
|
48
|
+
## Flags
|
|
49
|
+
|
|
50
|
+
| Flag | What |
|
|
51
|
+
|------|------|
|
|
52
|
+
| `--notes="text"` | Changelog entry text |
|
|
53
|
+
| `--dry-run` | Show what would happen, change nothing |
|
|
54
|
+
| `--no-publish` | Bump + tag only, skip npm and GitHub release |
|
|
55
|
+
|
|
56
|
+
## Auth
|
|
57
|
+
|
|
58
|
+
npm token is fetched from 1Password at publish time. No `.npmrc` files stored. No credentials in repos.
|
|
59
|
+
|
|
60
|
+
Requires:
|
|
61
|
+
- `op` CLI installed and configured
|
|
62
|
+
- 1Password SA token at `~/.openclaw/secrets/op-sa-token`
|
|
63
|
+
- "npm Token" item in "Agent Secrets" vault
|
|
64
|
+
- `gh` CLI authenticated (for GitHub Packages and releases)
|
|
65
|
+
|
|
66
|
+
## As a Module
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
import { release, detectCurrentVersion, bumpSemver } from '@wipcomputer/wip-release';
|
|
70
|
+
|
|
71
|
+
const current = detectCurrentVersion('/path/to/repo');
|
|
72
|
+
const next = bumpSemver(current, 'minor');
|
|
73
|
+
console.log(`${current} -> ${next}`);
|
|
74
|
+
|
|
75
|
+
await release({
|
|
76
|
+
repoPath: '/path/to/repo',
|
|
77
|
+
level: 'patch',
|
|
78
|
+
notes: 'fix auth',
|
|
79
|
+
dryRun: false,
|
|
80
|
+
noPublish: false,
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Exports
|
|
85
|
+
|
|
86
|
+
| Function | What |
|
|
87
|
+
|----------|------|
|
|
88
|
+
| `release({ repoPath, level, notes, dryRun, noPublish })` | Full pipeline |
|
|
89
|
+
| `detectCurrentVersion(repoPath)` | Read version from package.json |
|
|
90
|
+
| `bumpSemver(version, level)` | Bump a semver string |
|
|
91
|
+
| `syncSkillVersion(repoPath, newVersion)` | Update SKILL.md frontmatter |
|
|
92
|
+
| `updateChangelog(repoPath, newVersion, notes)` | Prepend to CHANGELOG.md |
|
|
93
|
+
| `publishNpm(repoPath)` | Publish to npmjs.com |
|
|
94
|
+
| `publishGitHubPackages(repoPath)` | Publish to npm.pkg.github.com |
|
|
95
|
+
| `createGitHubRelease(repoPath, newVersion, notes, currentVersion)` | Create GitHub release with rich notes |
|
|
96
|
+
| `buildReleaseNotes(repoPath, currentVersion, newVersion, notes)` | Generate detailed release notes |
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wip-release
|
|
3
|
+
version: 1.1.0
|
|
4
|
+
description: Local release tool. Bumps version, updates changelog + SKILL.md, publishes to npm + GitHub.
|
|
5
|
+
homepage: https://github.com/wipcomputer/wip-release
|
|
6
|
+
metadata:
|
|
7
|
+
category: dev-tools
|
|
8
|
+
capabilities:
|
|
9
|
+
- version-bump
|
|
10
|
+
- changelog-update
|
|
11
|
+
- skill-sync
|
|
12
|
+
- npm-publish
|
|
13
|
+
- github-release
|
|
14
|
+
dependencies: []
|
|
15
|
+
interface: CLI
|
|
16
|
+
openclaw:
|
|
17
|
+
emoji: "🚀"
|
|
18
|
+
install:
|
|
19
|
+
env: []
|
|
20
|
+
author:
|
|
21
|
+
name: Parker Todd Brooks
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# wip-release
|
|
25
|
+
|
|
26
|
+
Local release pipeline. One command bumps version, updates all docs, publishes everywhere.
|
|
27
|
+
|
|
28
|
+
## When to Use This Skill
|
|
29
|
+
|
|
30
|
+
**Use wip-release for:**
|
|
31
|
+
- Releasing a new version of any @wipcomputer package
|
|
32
|
+
- After merging a PR to main and you need to publish
|
|
33
|
+
- Bumping version + changelog + SKILL.md in one step
|
|
34
|
+
|
|
35
|
+
**Use --dry-run for:**
|
|
36
|
+
- Previewing what a release would do before committing
|
|
37
|
+
|
|
38
|
+
**Use --no-publish for:**
|
|
39
|
+
- Bumping version and tagging without publishing to registries
|
|
40
|
+
|
|
41
|
+
### Do NOT Use For
|
|
42
|
+
|
|
43
|
+
- Pre-release / alpha versions (not yet supported)
|
|
44
|
+
- Repos without a package.json
|
|
45
|
+
- Publishing to ClawHub (separate step)
|
|
46
|
+
|
|
47
|
+
## API Reference
|
|
48
|
+
|
|
49
|
+
### CLI
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
wip-release patch --notes="fix X" # full pipeline
|
|
53
|
+
wip-release minor --dry-run # preview only
|
|
54
|
+
wip-release major --no-publish # bump + tag only
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Module
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
import { release, detectCurrentVersion, bumpSemver, syncSkillVersion } from '@wipcomputer/wip-release';
|
|
61
|
+
|
|
62
|
+
await release({ repoPath: '.', level: 'patch', notes: 'fix', dryRun: false, noPublish: false });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Troubleshooting
|
|
66
|
+
|
|
67
|
+
### "Could not fetch npm token from 1Password"
|
|
68
|
+
Check that `~/.openclaw/secrets/op-sa-token` exists and `op` CLI is installed.
|
|
69
|
+
|
|
70
|
+
### "Push failed"
|
|
71
|
+
Branch protection may prevent direct pushes. Make sure you're on main after merging a PR.
|
|
72
|
+
|
|
73
|
+
### SKILL.md not updated
|
|
74
|
+
Only updates if the file has a YAML frontmatter `version:` field between `---` markers.
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wip-release/cli.mjs
|
|
5
|
+
* Release tool CLI. Bumps version, updates docs, publishes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { release, detectCurrentVersion } from './core.mjs';
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const level = args.find(a => ['patch', 'minor', 'major'].includes(a));
|
|
12
|
+
|
|
13
|
+
function flag(name) {
|
|
14
|
+
const prefix = `--${name}=`;
|
|
15
|
+
const found = args.find(a => a.startsWith(prefix));
|
|
16
|
+
return found ? found.slice(prefix.length) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dryRun = args.includes('--dry-run');
|
|
20
|
+
const noPublish = args.includes('--no-publish');
|
|
21
|
+
const notes = flag('notes');
|
|
22
|
+
|
|
23
|
+
if (!level || args.includes('--help') || args.includes('-h')) {
|
|
24
|
+
const cwd = process.cwd();
|
|
25
|
+
let current = '';
|
|
26
|
+
try { current = ` (current: ${detectCurrentVersion(cwd)})`; } catch {}
|
|
27
|
+
|
|
28
|
+
console.log(`wip-release ... local release tool${current}
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
wip-release patch 1.0.0 -> 1.0.1
|
|
32
|
+
wip-release minor 1.0.0 -> 1.1.0
|
|
33
|
+
wip-release major 1.0.0 -> 2.0.0
|
|
34
|
+
|
|
35
|
+
Flags:
|
|
36
|
+
--notes="description" Changelog entry text
|
|
37
|
+
--dry-run Show what would happen, change nothing
|
|
38
|
+
--no-publish Bump + tag only, skip npm/GitHub
|
|
39
|
+
|
|
40
|
+
Pipeline:
|
|
41
|
+
1. Bump package.json version
|
|
42
|
+
2. Sync SKILL.md version (if exists)
|
|
43
|
+
3. Update CHANGELOG.md
|
|
44
|
+
4. Git commit + tag
|
|
45
|
+
5. Push to remote
|
|
46
|
+
6. npm publish (via 1Password)
|
|
47
|
+
7. GitHub Packages publish
|
|
48
|
+
8. GitHub release create`);
|
|
49
|
+
process.exit(level ? 0 : 1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
release({
|
|
53
|
+
repoPath: process.cwd(),
|
|
54
|
+
level,
|
|
55
|
+
notes,
|
|
56
|
+
dryRun,
|
|
57
|
+
noPublish,
|
|
58
|
+
}).catch(err => {
|
|
59
|
+
console.error(` ✗ ${err.message}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
package/core.mjs
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wip-release/core.mjs
|
|
3
|
+
* Local release tool. Bumps version, updates changelog + SKILL.md,
|
|
4
|
+
* commits, tags, publishes to npm + GitHub Packages, creates GitHub release.
|
|
5
|
+
* Zero dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
10
|
+
import { join, basename } from 'node:path';
|
|
11
|
+
|
|
12
|
+
// ── Version ─────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Read current version from package.json.
|
|
16
|
+
*/
|
|
17
|
+
export function detectCurrentVersion(repoPath) {
|
|
18
|
+
const pkgPath = join(repoPath, 'package.json');
|
|
19
|
+
if (!existsSync(pkgPath)) throw new Error(`No package.json found at ${repoPath}`);
|
|
20
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
21
|
+
return pkg.version;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bump a semver string by level.
|
|
26
|
+
*/
|
|
27
|
+
export function bumpSemver(version, level) {
|
|
28
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
29
|
+
switch (level) {
|
|
30
|
+
case 'major': return `${major + 1}.0.0`;
|
|
31
|
+
case 'minor': return `${major}.${minor + 1}.0`;
|
|
32
|
+
case 'patch': return `${major}.${minor}.${patch + 1}`;
|
|
33
|
+
default: throw new Error(`Invalid level: ${level}. Use major, minor, or patch.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Write new version to package.json.
|
|
39
|
+
*/
|
|
40
|
+
function writePackageVersion(repoPath, newVersion) {
|
|
41
|
+
const pkgPath = join(repoPath, 'package.json');
|
|
42
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
43
|
+
pkg.version = newVersion;
|
|
44
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── SKILL.md ────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update version in SKILL.md YAML frontmatter.
|
|
51
|
+
*/
|
|
52
|
+
export function syncSkillVersion(repoPath, newVersion) {
|
|
53
|
+
const skillPath = join(repoPath, 'SKILL.md');
|
|
54
|
+
if (!existsSync(skillPath)) return false;
|
|
55
|
+
|
|
56
|
+
let content = readFileSync(skillPath, 'utf8');
|
|
57
|
+
// Match version: X.Y.Z in YAML frontmatter (between --- markers)
|
|
58
|
+
const updated = content.replace(
|
|
59
|
+
/^(---[\s\S]*?)(version:\s*)\S+([\s\S]*?---)/,
|
|
60
|
+
`$1$2${newVersion}$3`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (updated === content) return false;
|
|
64
|
+
writeFileSync(skillPath, updated);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── CHANGELOG.md ────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Prepend a new version entry to CHANGELOG.md.
|
|
72
|
+
*/
|
|
73
|
+
export function updateChangelog(repoPath, newVersion, notes) {
|
|
74
|
+
const changelogPath = join(repoPath, 'CHANGELOG.md');
|
|
75
|
+
const date = new Date().toISOString().split('T')[0];
|
|
76
|
+
const entry = `## ${newVersion} (${date})\n\n${notes || 'Release.'}\n`;
|
|
77
|
+
|
|
78
|
+
if (!existsSync(changelogPath)) {
|
|
79
|
+
writeFileSync(changelogPath, `# Changelog\n\n${entry}\n`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let content = readFileSync(changelogPath, 'utf8');
|
|
84
|
+
// Insert after the # Changelog header
|
|
85
|
+
const headerMatch = content.match(/^# Changelog\s*\n/);
|
|
86
|
+
if (headerMatch) {
|
|
87
|
+
const insertPoint = headerMatch[0].length;
|
|
88
|
+
content = content.slice(0, insertPoint) + '\n' + entry + '\n' + content.slice(insertPoint);
|
|
89
|
+
} else {
|
|
90
|
+
content = `# Changelog\n\n${entry}\n${content}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
writeFileSync(changelogPath, content);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Git ─────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function gitCommitAndTag(repoPath, newVersion, notes) {
|
|
99
|
+
const msg = `v${newVersion}: ${notes || 'Release'}`;
|
|
100
|
+
execSync(`git add package.json CHANGELOG.md SKILL.md 2>/dev/null; git add package.json CHANGELOG.md`, { cwd: repoPath, stdio: 'pipe' });
|
|
101
|
+
execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, { cwd: repoPath, stdio: 'pipe' });
|
|
102
|
+
execSync(`git tag v${newVersion}`, { cwd: repoPath, stdio: 'pipe' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Publish ─────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Publish to npm via 1Password for auth.
|
|
109
|
+
*/
|
|
110
|
+
export function publishNpm(repoPath) {
|
|
111
|
+
const token = getNpmToken();
|
|
112
|
+
execSync(
|
|
113
|
+
`npm publish --access public --//registry.npmjs.org/:_authToken="${token}"`,
|
|
114
|
+
{ cwd: repoPath, stdio: 'inherit' }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Publish to GitHub Packages.
|
|
120
|
+
*/
|
|
121
|
+
export function publishGitHubPackages(repoPath) {
|
|
122
|
+
const ghToken = execSync('gh auth token', { encoding: 'utf8' }).trim();
|
|
123
|
+
execSync(
|
|
124
|
+
`npm publish --registry https://npm.pkg.github.com --//npm.pkg.github.com/:_authToken="${ghToken}"`,
|
|
125
|
+
{ cwd: repoPath, stdio: 'inherit' }
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build detailed release notes from git history and repo metadata.
|
|
131
|
+
*/
|
|
132
|
+
export function buildReleaseNotes(repoPath, currentVersion, newVersion, notes) {
|
|
133
|
+
const slug = detectRepoSlug(repoPath);
|
|
134
|
+
const pkg = JSON.parse(readFileSync(join(repoPath, 'package.json'), 'utf8'));
|
|
135
|
+
const lines = [];
|
|
136
|
+
|
|
137
|
+
// What changed section
|
|
138
|
+
lines.push('## What changed\n');
|
|
139
|
+
if (notes) {
|
|
140
|
+
lines.push(notes);
|
|
141
|
+
lines.push('');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Commits since last tag
|
|
145
|
+
const prevTag = `v${currentVersion}`;
|
|
146
|
+
let commits = '';
|
|
147
|
+
try {
|
|
148
|
+
commits = execSync(
|
|
149
|
+
`git log ${prevTag}..HEAD --pretty=format:"- %s (%h)" 2>/dev/null`,
|
|
150
|
+
{ cwd: repoPath, encoding: 'utf8' }
|
|
151
|
+
).trim();
|
|
152
|
+
} catch {
|
|
153
|
+
// No previous tag ... show all commits on branch
|
|
154
|
+
try {
|
|
155
|
+
commits = execSync(
|
|
156
|
+
`git log --pretty=format:"- %s (%h)" -20`,
|
|
157
|
+
{ cwd: repoPath, encoding: 'utf8' }
|
|
158
|
+
).trim();
|
|
159
|
+
} catch {}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (commits) {
|
|
163
|
+
lines.push('### Commits\n');
|
|
164
|
+
lines.push(commits);
|
|
165
|
+
lines.push('');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Files changed
|
|
169
|
+
let filesChanged = '';
|
|
170
|
+
try {
|
|
171
|
+
filesChanged = execSync(
|
|
172
|
+
`git diff ${prevTag}..HEAD --stat 2>/dev/null`,
|
|
173
|
+
{ cwd: repoPath, encoding: 'utf8' }
|
|
174
|
+
).trim();
|
|
175
|
+
} catch {}
|
|
176
|
+
|
|
177
|
+
if (filesChanged) {
|
|
178
|
+
lines.push('### Files changed\n');
|
|
179
|
+
lines.push('```');
|
|
180
|
+
lines.push(filesChanged);
|
|
181
|
+
lines.push('```');
|
|
182
|
+
lines.push('');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Install section
|
|
186
|
+
lines.push('### Install\n');
|
|
187
|
+
lines.push('```bash');
|
|
188
|
+
lines.push(`npm install -g ${pkg.name}@${newVersion}`);
|
|
189
|
+
lines.push('```');
|
|
190
|
+
lines.push('');
|
|
191
|
+
lines.push('Or update your local clone:');
|
|
192
|
+
lines.push('```bash');
|
|
193
|
+
lines.push('git pull origin main');
|
|
194
|
+
lines.push('```');
|
|
195
|
+
lines.push('');
|
|
196
|
+
|
|
197
|
+
// Compare URL
|
|
198
|
+
if (slug) {
|
|
199
|
+
lines.push('---');
|
|
200
|
+
lines.push('');
|
|
201
|
+
lines.push(`Full changelog: https://github.com/${slug}/compare/v${currentVersion}...v${newVersion}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return lines.join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Create a GitHub release with detailed notes.
|
|
209
|
+
*/
|
|
210
|
+
export function createGitHubRelease(repoPath, newVersion, notes, currentVersion) {
|
|
211
|
+
const repoSlug = detectRepoSlug(repoPath);
|
|
212
|
+
const body = buildReleaseNotes(repoPath, currentVersion, newVersion, notes);
|
|
213
|
+
|
|
214
|
+
// Write notes to a temp file to avoid shell escaping issues
|
|
215
|
+
const tmpFile = join(repoPath, '.release-notes-tmp.md');
|
|
216
|
+
writeFileSync(tmpFile, body);
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
execSync(
|
|
220
|
+
`gh release create v${newVersion} --title "v${newVersion}" --notes-file .release-notes-tmp.md --repo ${repoSlug}`,
|
|
221
|
+
{ cwd: repoPath, stdio: 'inherit' }
|
|
222
|
+
);
|
|
223
|
+
} finally {
|
|
224
|
+
try { execSync(`rm -f "${tmpFile}"`); } catch {}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
function getNpmToken() {
|
|
231
|
+
try {
|
|
232
|
+
return execSync(
|
|
233
|
+
`OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) op item get "npm Token" --vault "Agent Secrets" --fields label=password --reveal 2>/dev/null`,
|
|
234
|
+
{ encoding: 'utf8' }
|
|
235
|
+
).trim();
|
|
236
|
+
} catch {
|
|
237
|
+
throw new Error('Could not fetch npm token from 1Password. Check op CLI and SA token.');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function detectRepoSlug(repoPath) {
|
|
242
|
+
try {
|
|
243
|
+
const url = execSync('git remote get-url origin', { cwd: repoPath, encoding: 'utf8' }).trim();
|
|
244
|
+
// git@github.com:wipcomputer/wip-grok.git or https://github.com/wipcomputer/wip-grok.git
|
|
245
|
+
const match = url.match(/github\.com[:/](.+?)(?:\.git)?$/);
|
|
246
|
+
return match ? match[1] : null;
|
|
247
|
+
} catch {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Main ────────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Run the full release pipeline.
|
|
256
|
+
*/
|
|
257
|
+
export async function release({ repoPath, level, notes, dryRun, noPublish }) {
|
|
258
|
+
repoPath = repoPath || process.cwd();
|
|
259
|
+
const currentVersion = detectCurrentVersion(repoPath);
|
|
260
|
+
const newVersion = bumpSemver(currentVersion, level);
|
|
261
|
+
const repoName = basename(repoPath);
|
|
262
|
+
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log(` ${repoName}: ${currentVersion} -> ${newVersion} (${level})`);
|
|
265
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
266
|
+
|
|
267
|
+
if (dryRun) {
|
|
268
|
+
console.log(` [dry run] Would bump package.json to ${newVersion}`);
|
|
269
|
+
const skillPath = join(repoPath, 'SKILL.md');
|
|
270
|
+
if (existsSync(skillPath)) console.log(` [dry run] Would update SKILL.md version`);
|
|
271
|
+
console.log(` [dry run] Would update CHANGELOG.md`);
|
|
272
|
+
console.log(` [dry run] Would commit and tag v${newVersion}`);
|
|
273
|
+
if (!noPublish) {
|
|
274
|
+
console.log(` [dry run] Would publish to npm (@wipcomputer scope)`);
|
|
275
|
+
console.log(` [dry run] Would publish to GitHub Packages`);
|
|
276
|
+
console.log(` [dry run] Would create GitHub release v${newVersion}`);
|
|
277
|
+
}
|
|
278
|
+
console.log('');
|
|
279
|
+
console.log(` Dry run complete. No changes made.`);
|
|
280
|
+
console.log('');
|
|
281
|
+
return { currentVersion, newVersion, dryRun: true };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 1. Bump package.json
|
|
285
|
+
writePackageVersion(repoPath, newVersion);
|
|
286
|
+
console.log(` ✓ package.json -> ${newVersion}`);
|
|
287
|
+
|
|
288
|
+
// 2. Sync SKILL.md
|
|
289
|
+
if (syncSkillVersion(repoPath, newVersion)) {
|
|
290
|
+
console.log(` ✓ SKILL.md -> ${newVersion}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 3. Update CHANGELOG.md
|
|
294
|
+
updateChangelog(repoPath, newVersion, notes);
|
|
295
|
+
console.log(` ✓ CHANGELOG.md updated`);
|
|
296
|
+
|
|
297
|
+
// 4. Git commit + tag
|
|
298
|
+
gitCommitAndTag(repoPath, newVersion, notes);
|
|
299
|
+
console.log(` ✓ Committed and tagged v${newVersion}`);
|
|
300
|
+
|
|
301
|
+
// 5. Push commit + tag
|
|
302
|
+
try {
|
|
303
|
+
execSync('git push && git push --tags', { cwd: repoPath, stdio: 'pipe' });
|
|
304
|
+
console.log(` ✓ Pushed to remote`);
|
|
305
|
+
} catch {
|
|
306
|
+
console.log(` ! Push failed (maybe branch protection). Push manually.`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!noPublish) {
|
|
310
|
+
// 6. npm publish
|
|
311
|
+
try {
|
|
312
|
+
publishNpm(repoPath);
|
|
313
|
+
console.log(` ✓ Published to npm`);
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.log(` ✗ npm publish failed: ${e.message}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 7. GitHub Packages
|
|
319
|
+
try {
|
|
320
|
+
publishGitHubPackages(repoPath);
|
|
321
|
+
console.log(` ✓ Published to GitHub Packages`);
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.log(` ✗ GitHub Packages publish failed: ${e.message}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 8. GitHub release
|
|
327
|
+
try {
|
|
328
|
+
createGitHubRelease(repoPath, newVersion, notes, currentVersion);
|
|
329
|
+
console.log(` ✓ GitHub release v${newVersion} created`);
|
|
330
|
+
} catch (e) {
|
|
331
|
+
console.log(` ✗ GitHub release failed: ${e.message}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log(` Done. ${repoName} v${newVersion} released.`);
|
|
337
|
+
console.log('');
|
|
338
|
+
|
|
339
|
+
return { currentVersion, newVersion, dryRun: false };
|
|
340
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wipcomputer/wip-release",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Local release tool. Bumps version, updates changelog + SKILL.md, publishes to npm + GitHub.",
|
|
6
|
+
"main": "core.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"wip-release": "./cli.mjs"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./core.mjs",
|
|
12
|
+
"./cli": "./cli.mjs"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node cli.mjs --help"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"release",
|
|
19
|
+
"version",
|
|
20
|
+
"publish",
|
|
21
|
+
"npm",
|
|
22
|
+
"agent-native",
|
|
23
|
+
"sensor",
|
|
24
|
+
"actuator"
|
|
25
|
+
],
|
|
26
|
+
"author": "Parker Todd Brooks",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/wipcomputer/wip-release.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/wipcomputer/wip-release"
|
|
33
|
+
}
|