@heart-of-gold/toolkit 0.1.49 → 0.1.50
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/package.json +3 -2
- package/scripts/release.mjs +193 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heart-of-gold/toolkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Cross-platform installer for Heart of Gold skills
|
|
5
|
+
"description": "Cross-platform installer for Heart of Gold skills — works with Codex, OpenCode, Pi, Claude Code, and more",
|
|
6
6
|
"bin": {
|
|
7
7
|
"heart-of-gold": "src/index.ts"
|
|
8
8
|
},
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"check:compat": "python3 scripts/check-harness-compatibility.py",
|
|
13
13
|
"test:pi-guided": "node --test tests/pi-guided-workflows.test.mjs",
|
|
14
14
|
"test:visualize": "node --test tests/visualize-smart-render.test.mjs",
|
|
15
|
+
"release": "node scripts/release.mjs",
|
|
15
16
|
"prepublishOnly": "npm run check:publish-safety && npm run check:compat"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// One-shot release tool. Bumps a plugin (plugin.json + marketplace.json) and
|
|
3
|
+
// the root package.json in lockstep, runs the prepublish checks, commits, and
|
|
4
|
+
// — only with --ship — pushes and publishes to NPM.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node scripts/release.mjs <plugin|root>:<patch|minor|major> [flags]
|
|
8
|
+
//
|
|
9
|
+
// Examples:
|
|
10
|
+
// node scripts/release.mjs marvin:patch # bump + commit, no push/publish
|
|
11
|
+
// node scripts/release.mjs marvin:minor --ship -m "new skill" # bump + commit + push + npm publish
|
|
12
|
+
// node scripts/release.mjs root:patch # root only (CLI fix; no plugin)
|
|
13
|
+
// node scripts/release.mjs --dry-run marvin:patch # preview, mutate nothing
|
|
14
|
+
//
|
|
15
|
+
// Flags:
|
|
16
|
+
// --ship after commit, run `git push` and `npm publish --access public`
|
|
17
|
+
// --dry-run print the plan, change no files
|
|
18
|
+
// -m, --message TEXT description after the standard "Release X.Y.Z — " prefix
|
|
19
|
+
// -h, --help this text
|
|
20
|
+
|
|
21
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { execSync } from "node:child_process";
|
|
23
|
+
import { dirname, join } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
27
|
+
const ROOT_PKG = join(ROOT, "package.json");
|
|
28
|
+
const MARKETPLACE = join(ROOT, ".claude-plugin/marketplace.json");
|
|
29
|
+
const PLUGINS = ["guide", "deep-thought", "marvin", "babel-fish", "quellis"];
|
|
30
|
+
|
|
31
|
+
const readJson = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
32
|
+
const writeJson = (p, d) => writeFileSync(p, JSON.stringify(d, null, 2) + "\n");
|
|
33
|
+
const sh = (cmd, opts = {}) => execSync(cmd, { cwd: ROOT, stdio: "inherit", ...opts });
|
|
34
|
+
const shRead = (cmd) => execSync(cmd, { cwd: ROOT, encoding: "utf8" });
|
|
35
|
+
|
|
36
|
+
function bump(version, kind) {
|
|
37
|
+
const [maj, min, pat] = version.split(".").map(Number);
|
|
38
|
+
if (kind === "patch") return `${maj}.${min}.${pat + 1}`;
|
|
39
|
+
if (kind === "minor") return `${maj}.${min + 1}.0`;
|
|
40
|
+
if (kind === "major") return `${maj + 1}.0.0`;
|
|
41
|
+
throw new Error(`Unknown bump kind: ${kind} (use patch, minor, or major)`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function help() {
|
|
45
|
+
console.log(`One-shot release tool. Bumps a plugin (plugin.json + marketplace.json) and
|
|
46
|
+
the root package.json in lockstep, runs prepublish checks, commits, and —
|
|
47
|
+
only with --ship — pushes and publishes to NPM.
|
|
48
|
+
|
|
49
|
+
Usage:
|
|
50
|
+
node scripts/release.mjs <plugin|root>:<patch|minor|major> [flags]
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
node scripts/release.mjs marvin:patch # bump + commit, no push/publish
|
|
54
|
+
node scripts/release.mjs marvin:minor --ship -m "new skill" # bump + commit + push + npm publish
|
|
55
|
+
node scripts/release.mjs root:patch # root only (CLI fix; no plugin)
|
|
56
|
+
node scripts/release.mjs --dry-run marvin:patch # preview, mutate nothing
|
|
57
|
+
|
|
58
|
+
Flags:
|
|
59
|
+
--ship after commit, run \`git push\` and \`npm publish --access public\`
|
|
60
|
+
--dry-run print the plan, change no files
|
|
61
|
+
-m, --message TEXT description after the standard "Release X.Y.Z — " prefix
|
|
62
|
+
-h, --help this text
|
|
63
|
+
|
|
64
|
+
Plugins: ${PLUGINS.join(" | ")} | root`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parse(argv) {
|
|
68
|
+
const out = { target: null, kind: null, dryRun: false, ship: false, message: "" };
|
|
69
|
+
for (let i = 0; i < argv.length; i++) {
|
|
70
|
+
const a = argv[i];
|
|
71
|
+
if (a === "--help" || a === "-h") { help(); process.exit(0); }
|
|
72
|
+
else if (a === "--dry-run") out.dryRun = true;
|
|
73
|
+
else if (a === "--ship") out.ship = true;
|
|
74
|
+
else if (a === "--message" || a === "-m") out.message = argv[++i] ?? "";
|
|
75
|
+
else if (a.includes(":")) [out.target, out.kind] = a.split(":");
|
|
76
|
+
else throw new Error(`Unknown arg: ${a}`);
|
|
77
|
+
}
|
|
78
|
+
if (!out.target || !out.kind) { help(); process.exit(1); }
|
|
79
|
+
if (out.target !== "root" && !PLUGINS.includes(out.target)) {
|
|
80
|
+
throw new Error(`Unknown plugin: ${out.target}. Known: ${PLUGINS.join(", ")}, root`);
|
|
81
|
+
}
|
|
82
|
+
if (!["patch", "minor", "major"].includes(out.kind)) {
|
|
83
|
+
throw new Error(`Unknown bump kind: ${out.kind}. Use patch | minor | major`);
|
|
84
|
+
}
|
|
85
|
+
if (out.target === "root" && out.kind !== "patch") {
|
|
86
|
+
throw new Error("Root bumps are reserved for patch releases (plugin changes drive minor/major).");
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ensureNoDrift(target, marketplace) {
|
|
92
|
+
if (target === "root") return;
|
|
93
|
+
const pluginPath = join(ROOT, `plugins/${target}/.claude-plugin/plugin.json`);
|
|
94
|
+
const pluginVersion = readJson(pluginPath).version;
|
|
95
|
+
const marketplaceVersion = marketplace.plugins.find((p) => p.name === target).version;
|
|
96
|
+
if (pluginVersion !== marketplaceVersion) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Version drift on ${target}: plugin.json=${pluginVersion}, marketplace.json=${marketplaceVersion}. ` +
|
|
99
|
+
`Reconcile both files before releasing.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function ensureGitClean(allowedRelativePaths) {
|
|
105
|
+
const status = shRead("git status --porcelain").split("\n").filter(Boolean);
|
|
106
|
+
const offending = status.filter((line) => {
|
|
107
|
+
const file = line.slice(3);
|
|
108
|
+
return !allowedRelativePaths.some((a) => file === a);
|
|
109
|
+
});
|
|
110
|
+
if (offending.length) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Working tree has changes outside the release scope. Commit or stash them first:\n${offending.join("\n")}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function main() {
|
|
118
|
+
const { target, kind, dryRun, ship, message } = parse(process.argv.slice(2));
|
|
119
|
+
|
|
120
|
+
const rootPkg = readJson(ROOT_PKG);
|
|
121
|
+
const marketplace = readJson(MARKETPLACE);
|
|
122
|
+
ensureNoDrift(target, marketplace);
|
|
123
|
+
|
|
124
|
+
const oldRoot = rootPkg.version;
|
|
125
|
+
const newRoot = bump(oldRoot, "patch");
|
|
126
|
+
let pluginPlan = null;
|
|
127
|
+
if (target !== "root") {
|
|
128
|
+
const pluginPath = join(ROOT, `plugins/${target}/.claude-plugin/plugin.json`);
|
|
129
|
+
const pluginJson = readJson(pluginPath);
|
|
130
|
+
const oldPlugin = pluginJson.version;
|
|
131
|
+
const newPlugin = bump(oldPlugin, kind);
|
|
132
|
+
pluginPlan = { name: target, pluginPath, pluginJson, oldPlugin, newPlugin };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log("\nRelease plan");
|
|
136
|
+
console.log("------------");
|
|
137
|
+
if (pluginPlan) console.log(` plugin ${pluginPlan.name}: ${pluginPlan.oldPlugin} → ${pluginPlan.newPlugin}`);
|
|
138
|
+
console.log(` root @heart-of-gold/toolkit: ${oldRoot} → ${newRoot}`);
|
|
139
|
+
console.log(` prepublish checks: will run`);
|
|
140
|
+
console.log(` commit: yes`);
|
|
141
|
+
console.log(` push: ${ship ? "yes" : "no"}`);
|
|
142
|
+
console.log(` npm publish: ${ship ? "yes" : "no"}`);
|
|
143
|
+
if (dryRun) {
|
|
144
|
+
console.log("\n[dry-run] Nothing changed.");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const allowed = [
|
|
149
|
+
"package.json",
|
|
150
|
+
".claude-plugin/marketplace.json",
|
|
151
|
+
pluginPlan ? `plugins/${pluginPlan.name}/.claude-plugin/plugin.json` : null,
|
|
152
|
+
].filter(Boolean);
|
|
153
|
+
ensureGitClean(allowed);
|
|
154
|
+
|
|
155
|
+
if (pluginPlan) {
|
|
156
|
+
pluginPlan.pluginJson.version = pluginPlan.newPlugin;
|
|
157
|
+
writeJson(pluginPlan.pluginPath, pluginPlan.pluginJson);
|
|
158
|
+
const idx = marketplace.plugins.findIndex((p) => p.name === pluginPlan.name);
|
|
159
|
+
marketplace.plugins[idx].version = pluginPlan.newPlugin;
|
|
160
|
+
writeJson(MARKETPLACE, marketplace);
|
|
161
|
+
}
|
|
162
|
+
rootPkg.version = newRoot;
|
|
163
|
+
writeJson(ROOT_PKG, rootPkg);
|
|
164
|
+
|
|
165
|
+
console.log("\nRunning prepublish checks…");
|
|
166
|
+
sh("npm run check:publish-safety && npm run check:compat");
|
|
167
|
+
|
|
168
|
+
const subject = pluginPlan
|
|
169
|
+
? `Release ${newRoot} — bump ${pluginPlan.name} to ${pluginPlan.newPlugin}${message ? `: ${message}` : ""}`
|
|
170
|
+
: `Release ${newRoot} — root patch${message ? `: ${message}` : ""}`;
|
|
171
|
+
sh(`git add ${allowed.map((p) => `"${p}"`).join(" ")}`);
|
|
172
|
+
sh(`git commit -m ${JSON.stringify(subject)}`);
|
|
173
|
+
console.log(`\nCommit created: ${subject}`);
|
|
174
|
+
|
|
175
|
+
if (!ship) {
|
|
176
|
+
console.log("\nNot shipping. To finish:");
|
|
177
|
+
console.log(" git push && npm publish --access public");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log("\nPushing to origin…");
|
|
182
|
+
sh("git push");
|
|
183
|
+
console.log("\nPublishing to NPM…");
|
|
184
|
+
sh("npm publish --access public");
|
|
185
|
+
console.log(`\nReleased @heart-of-gold/toolkit@${newRoot}.`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
main();
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error(`\nrelease: ${err.message}\n`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|