supipowers 1.2.2 → 1.2.4
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/bin/install.mjs +2 -0
- package/bin/install.ts +58 -14
- package/package.json +10 -1
- package/src/commands/release.ts +26 -8
- package/src/deps/registry.ts +3 -1
- package/src/release/commit-types.ts +2 -0
- package/src/release/executor.ts +20 -10
- package/src/release/version.ts +19 -1
package/bin/install.mjs
CHANGED
|
@@ -3,9 +3,11 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
|
|
6
|
+
const isWindows = process.platform === "win32";
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
const result = spawnSync("bun", [join(__dirname, "install.ts"), ...process.argv.slice(2)], {
|
|
8
9
|
stdio: "inherit",
|
|
9
10
|
env: process.env,
|
|
11
|
+
shell: isWindows,
|
|
10
12
|
});
|
|
11
13
|
process.exit(result.status ?? 1);
|
package/bin/install.ts
CHANGED
|
@@ -26,6 +26,8 @@ import { homedir } from "node:os";
|
|
|
26
26
|
import { scanAll, installDep, formatReport } from "../src/deps/registry.js";
|
|
27
27
|
import type { ExecResult } from "../src/platform/types.js";
|
|
28
28
|
|
|
29
|
+
const isWindows = process.platform === "win32";
|
|
30
|
+
|
|
29
31
|
// ── Helpers ──────────────────────────────────────────────────
|
|
30
32
|
|
|
31
33
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -46,6 +48,7 @@ function run(cmd: string, args: string[], opts: Record<string, unknown> = {}): R
|
|
|
46
48
|
stdio: "pipe",
|
|
47
49
|
encoding: "utf8",
|
|
48
50
|
timeout: 120_000,
|
|
51
|
+
shell: isWindows,
|
|
49
52
|
...opts,
|
|
50
53
|
}) as unknown as RunResult;
|
|
51
54
|
}
|
|
@@ -56,32 +59,54 @@ function bail(msg: string): never {
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
function findOmpBinary(): string | null {
|
|
59
|
-
// Check PATH first
|
|
62
|
+
// Check PATH first (shell: true in run() resolves .cmd shims on Windows)
|
|
60
63
|
const check = run("omp", ["--version"]);
|
|
61
64
|
if (!check.error && check.status === 0) return "omp";
|
|
62
65
|
|
|
63
|
-
// Fallback: check common
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
// Fallback: check common global locations
|
|
67
|
+
const candidates = [
|
|
68
|
+
join(homedir(), ".bun", "bin", "omp"),
|
|
69
|
+
];
|
|
70
|
+
if (isWindows) {
|
|
71
|
+
// Bun on Windows installs .exe binaries
|
|
72
|
+
candidates.push(join(homedir(), ".bun", "bin", "omp.exe"));
|
|
73
|
+
// npm globals on Windows
|
|
74
|
+
const appData = process.env.APPDATA;
|
|
75
|
+
if (appData) candidates.push(join(appData, "npm", "omp.cmd"));
|
|
76
|
+
}
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
if (existsSync(candidate)) {
|
|
79
|
+
const fallback = run(candidate, ["--version"]);
|
|
80
|
+
if (!fallback.error && fallback.status === 0) return candidate;
|
|
81
|
+
}
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
return null;
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
function findPiBinary(): string | null {
|
|
74
|
-
// Check PATH first
|
|
88
|
+
// Check PATH first (shell: true in run() resolves .cmd shims on Windows)
|
|
75
89
|
const check = run("pi", ["--version"]);
|
|
76
90
|
if (!check.error && check.status === 0) return "pi";
|
|
77
91
|
|
|
78
|
-
// Fallback: check common
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
join(homedir(), ".
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
// Fallback: check common global locations
|
|
93
|
+
const candidates: string[] = [];
|
|
94
|
+
if (isWindows) {
|
|
95
|
+
candidates.push(join(homedir(), ".bun", "bin", "pi.exe"));
|
|
96
|
+
const appData = process.env.APPDATA;
|
|
97
|
+
if (appData) {
|
|
98
|
+
candidates.push(join(appData, "npm", "pi.cmd"));
|
|
99
|
+
candidates.push(join(appData, "npm", "pi"));
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
candidates.push(
|
|
103
|
+
join(homedir(), ".bun", "bin", "pi"),
|
|
104
|
+
join(homedir(), ".npm-global", "bin", "pi"),
|
|
105
|
+
"/usr/local/bin/pi",
|
|
106
|
+
"/opt/homebrew/bin/pi",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
for (const candidate of candidates) {
|
|
85
110
|
if (existsSync(candidate)) {
|
|
86
111
|
const fallback = run(candidate, ["--version"]);
|
|
87
112
|
if (!fallback.error && fallback.status === 0) return candidate;
|
|
@@ -171,6 +196,25 @@ function installToPlatform(platformDir: string, packageRoot: string): string {
|
|
|
171
196
|
}
|
|
172
197
|
}
|
|
173
198
|
|
|
199
|
+
// Install dependencies so the extension's runtime imports resolve.
|
|
200
|
+
// Bun installs peer deps by default, which provides @sinclair/typebox
|
|
201
|
+
// and @oh-my-pi/* packages that the extension needs at import time.
|
|
202
|
+
// Without this, the extension fails to load on systems where these
|
|
203
|
+
// packages aren't in Bun's global install (e.g. OMP installed via npm).
|
|
204
|
+
s.message("Installing extension dependencies...");
|
|
205
|
+
const install = run("bun", ["install", "--frozen-lockfile=false"], { cwd: extDir });
|
|
206
|
+
if (install.status !== 0) {
|
|
207
|
+
// Non-fatal: the extension may still work if OMP provides the deps.
|
|
208
|
+
// Log a warning but don't bail — the user might be offline or the
|
|
209
|
+
// registry might be temporarily unreachable.
|
|
210
|
+
note(
|
|
211
|
+
"Could not install extension dependencies.\n" +
|
|
212
|
+
"If /supi commands don't appear in OMP, run:\n" +
|
|
213
|
+
` cd ~/${platformDir}/agent/extensions/supipowers && bun install`,
|
|
214
|
+
"Warning",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
174
218
|
s.stop(
|
|
175
219
|
installedVersion
|
|
176
220
|
? `supipowers updated to v${VERSION} (${platformDir})`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supipowers",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Workflow extension for OMP coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -43,17 +43,26 @@
|
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@oh-my-pi/pi-coding-agent": "*",
|
|
46
|
+
"@oh-my-pi/pi-ai": "*",
|
|
47
|
+
"@oh-my-pi/pi-tui": "*",
|
|
46
48
|
"@sinclair/typebox": "*"
|
|
47
49
|
},
|
|
48
50
|
"peerDependenciesMeta": {
|
|
49
51
|
"@oh-my-pi/pi-coding-agent": {
|
|
50
52
|
"optional": true
|
|
51
53
|
},
|
|
54
|
+
"@oh-my-pi/pi-ai": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
52
57
|
"@oh-my-pi/pi-tui": {
|
|
53
58
|
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"@sinclair/typebox": {
|
|
61
|
+
"optional": true
|
|
54
62
|
}
|
|
55
63
|
},
|
|
56
64
|
"devDependencies": {
|
|
65
|
+
"@oh-my-pi/pi-ai": "latest",
|
|
57
66
|
"@oh-my-pi/pi-coding-agent": "latest",
|
|
58
67
|
"@oh-my-pi/pi-tui": "latest",
|
|
59
68
|
"@sinclair/typebox": "^0.34.48",
|
package/src/commands/release.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ReleaseChannel, BumpType } from "../types.js";
|
|
|
3
3
|
import { loadConfig, updateConfig } from "../config/loader.js";
|
|
4
4
|
import { detectChannels } from "../release/detector.js";
|
|
5
5
|
import { parseConventionalCommits, buildChangelogMarkdown, summarizeChanges } from "../release/changelog.js";
|
|
6
|
-
import { getCurrentVersion, suggestBump, bumpVersion, isVersionReleased } from "../release/version.js";
|
|
6
|
+
import { getCurrentVersion, suggestBump, bumpVersion, isVersionReleased, isTagOnRemote } from "../release/version.js";
|
|
7
7
|
import { executeRelease, type ReleaseProgressFn } from "../release/executor.js";
|
|
8
8
|
import { buildPolishPrompt } from "../release/prompt.js";
|
|
9
9
|
import { notifyInfo, notifySuccess, notifyError } from "../notifications/renderer.js";
|
|
@@ -278,11 +278,16 @@ export function handleRelease(platform: Platform, ctx: any, args?: string): void
|
|
|
278
278
|
progress.activate(2, "Parsing git history");
|
|
279
279
|
const lastTag = await getLastTag(platform, ctx.cwd);
|
|
280
280
|
const currentVersion = getCurrentVersion(ctx.cwd);
|
|
281
|
-
const
|
|
281
|
+
const localTagExists = await isVersionReleased(
|
|
282
282
|
platform.exec.bind(platform),
|
|
283
283
|
ctx.cwd,
|
|
284
284
|
currentVersion,
|
|
285
285
|
);
|
|
286
|
+
// Only check remote when local tag exists — avoids a network call when
|
|
287
|
+
// the version was never tagged at all.
|
|
288
|
+
const remoteTagExists = localTagExists
|
|
289
|
+
? await isTagOnRemote(platform.exec.bind(platform), ctx.cwd, currentVersion)
|
|
290
|
+
: false;
|
|
286
291
|
|
|
287
292
|
// 3. Parse commits since last tag
|
|
288
293
|
const sinceArg = lastTag ? `${lastTag}..HEAD` : "HEAD~50..HEAD";
|
|
@@ -303,18 +308,30 @@ export function handleRelease(platform: Platform, ctx: any, args?: string): void
|
|
|
303
308
|
const commitCount = commits.features.length + commits.fixes.length + commits.breaking.length + commits.improvements.length + commits.maintenance.length + commits.other.length;
|
|
304
309
|
progress.complete(2, `${commitCount} commits since ${lastTag ?? "start"}`);
|
|
305
310
|
|
|
306
|
-
// 4. Version resolution
|
|
311
|
+
// 4. Version resolution
|
|
312
|
+
// Three states:
|
|
313
|
+
// a) No local tag → version is unreleased, skip bump
|
|
314
|
+
// b) Local tag, not on remote → incomplete release, skip bump + skip tag
|
|
315
|
+
// c) Local + remote tag → fully released, ask for bump
|
|
307
316
|
let nextVersion: string;
|
|
308
317
|
let skipBump: boolean;
|
|
318
|
+
let skipTag = false;
|
|
309
319
|
|
|
310
|
-
if (!
|
|
311
|
-
//
|
|
320
|
+
if (!localTagExists && currentVersion !== "0.0.0") {
|
|
321
|
+
// (a) No tag at all — version in package.json is unreleased
|
|
312
322
|
nextVersion = currentVersion;
|
|
313
323
|
skipBump = true;
|
|
314
324
|
progress.skip(3, `v${currentVersion} (already set, not yet released)`);
|
|
315
|
-
notifyInfo(ctx, `Using v${currentVersion}`, "Version not yet released
|
|
325
|
+
notifyInfo(ctx, `Using v${currentVersion}`, "Version not yet released \u2014 skipping bump");
|
|
326
|
+
} else if (localTagExists && !remoteTagExists) {
|
|
327
|
+
// (b) Tag exists locally but never made it to origin — resume
|
|
328
|
+
nextVersion = currentVersion;
|
|
329
|
+
skipBump = true;
|
|
330
|
+
skipTag = true;
|
|
331
|
+
progress.skip(3, `v${currentVersion} (tag exists locally, not pushed)`);
|
|
332
|
+
notifyInfo(ctx, `Resuming v${currentVersion}`, "Tag exists locally but not on remote \u2014 will push");
|
|
316
333
|
} else {
|
|
317
|
-
//
|
|
334
|
+
// (c) Fully released — ask for bump
|
|
318
335
|
progress.activate(3, "Awaiting version selection");
|
|
319
336
|
const suggested = suggestBump(commits);
|
|
320
337
|
const bumpChoice = await ctx.ui.select(
|
|
@@ -330,7 +347,7 @@ export function handleRelease(platform: Platform, ctx: any, args?: string): void
|
|
|
330
347
|
const bump = bumpChoice.split(" \u2014 ")[0] as BumpType;
|
|
331
348
|
nextVersion = bumpVersion(currentVersion, bump);
|
|
332
349
|
skipBump = false;
|
|
333
|
-
progress.complete(3, `${currentVersion}
|
|
350
|
+
progress.complete(3, `${currentVersion} \u2192 ${nextVersion} (${bump})`);
|
|
334
351
|
}
|
|
335
352
|
|
|
336
353
|
// 5. Build changelog
|
|
@@ -416,6 +433,7 @@ export function handleRelease(platform: Platform, ctx: any, args?: string): void
|
|
|
416
433
|
channels,
|
|
417
434
|
dryRun: isDryRun,
|
|
418
435
|
skipBump,
|
|
436
|
+
skipTag,
|
|
419
437
|
onProgress: progress.executorProgress(),
|
|
420
438
|
});
|
|
421
439
|
|
package/src/deps/registry.ts
CHANGED
|
@@ -34,7 +34,9 @@ export async function checkBinary(
|
|
|
34
34
|
exec: ExecFn,
|
|
35
35
|
binary: string,
|
|
36
36
|
): Promise<{ installed: boolean; version?: string }> {
|
|
37
|
-
|
|
37
|
+
// `which` is Unix-only; Windows uses `where` to locate executables
|
|
38
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
39
|
+
const which = await exec(whichCmd, [binary]);
|
|
38
40
|
if (which.code !== 0) return { installed: false };
|
|
39
41
|
|
|
40
42
|
const ver = await exec(binary, ["--version"]);
|
|
@@ -13,6 +13,7 @@ export const VALID_COMMIT_TYPES = [
|
|
|
13
13
|
"test",
|
|
14
14
|
"docs",
|
|
15
15
|
"style",
|
|
16
|
+
"release",
|
|
16
17
|
] as const;
|
|
17
18
|
|
|
18
19
|
export type ConventionalCommitType = (typeof VALID_COMMIT_TYPES)[number];
|
|
@@ -32,4 +33,5 @@ export const MAINTENANCE_TYPES = new Set<ConventionalCommitType>([
|
|
|
32
33
|
"test",
|
|
33
34
|
"docs",
|
|
34
35
|
"style",
|
|
36
|
+
"release",
|
|
35
37
|
]);
|
package/src/release/executor.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface ExecuteReleaseOptions {
|
|
|
22
22
|
dryRun: boolean;
|
|
23
23
|
/** Skip the package.json version write (version was already set locally). */
|
|
24
24
|
skipBump?: boolean;
|
|
25
|
+
/** Skip creating the git tag (it already exists locally). */
|
|
26
|
+
skipTag?: boolean;
|
|
25
27
|
/** Optional callback for step-by-step progress reporting. */
|
|
26
28
|
onProgress?: ReleaseProgressFn;
|
|
27
29
|
}
|
|
@@ -37,7 +39,7 @@ export interface ExecuteReleaseOptions {
|
|
|
37
39
|
* happen (all flags true) so callers can preview without side-effects.
|
|
38
40
|
*/
|
|
39
41
|
export async function executeRelease(opts: ExecuteReleaseOptions): Promise<ReleaseResult> {
|
|
40
|
-
const { exec, cwd, version, changelog, channels, dryRun, skipBump, onProgress } = opts;
|
|
42
|
+
const { exec, cwd, version, changelog, channels, dryRun, skipBump, skipTag, onProgress } = opts;
|
|
41
43
|
const progress = onProgress ?? (() => {});
|
|
42
44
|
|
|
43
45
|
if (dryRun) {
|
|
@@ -45,7 +47,11 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
45
47
|
console.log(`[dry-run] Would bump version to ${version}`);
|
|
46
48
|
console.log(`[dry-run] Would git add -A`);
|
|
47
49
|
console.log(`[dry-run] Would git commit -m "chore(release): v${version}"`);
|
|
48
|
-
|
|
50
|
+
if (skipTag) {
|
|
51
|
+
console.log(`[dry-run] Would skip git tag (already exists locally)`);
|
|
52
|
+
} else {
|
|
53
|
+
console.log(`[dry-run] Would git tag -a v${version}`);
|
|
54
|
+
}
|
|
49
55
|
console.log(`[dry-run] Would git push origin HEAD --follow-tags`);
|
|
50
56
|
for (const ch of channels) {
|
|
51
57
|
console.log(`[dry-run] Would publish to channel: ${ch}`);
|
|
@@ -111,15 +117,19 @@ export async function executeRelease(opts: ExecuteReleaseOptions): Promise<Relea
|
|
|
111
117
|
progress("git-commit", "done");
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
if (skipTag) {
|
|
121
|
+
progress("git-tag", "done", "Already exists");
|
|
122
|
+
} else {
|
|
123
|
+
progress("git-tag", "active", `v${version}`);
|
|
124
|
+
const tagMessage = `Release v${version}\n\n${changelog}`;
|
|
125
|
+
const gitTag = await exec("git", ["tag", "-a", `v${version}`, "-m", tagMessage], { cwd });
|
|
126
|
+
if (gitTag.code !== 0) {
|
|
127
|
+
const detail = gitTag.stderr || gitTag.stdout || `exit code ${gitTag.code}`;
|
|
128
|
+
progress("git-tag", "error", detail);
|
|
129
|
+
return { version, tagCreated: false, pushed: false, channels: [], error: `git tag: ${detail}` };
|
|
130
|
+
}
|
|
131
|
+
progress("git-tag", "done");
|
|
121
132
|
}
|
|
122
|
-
progress("git-tag", "done");
|
|
123
133
|
|
|
124
134
|
progress("git-push", "active", "Pushing to origin");
|
|
125
135
|
const gitPush = await exec("git", ["push", "origin", "HEAD", "--follow-tags"], { cwd });
|
package/src/release/version.ts
CHANGED
|
@@ -78,4 +78,22 @@ export async function isVersionReleased(
|
|
|
78
78
|
} catch {
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
|
-
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check whether a tag for the given version exists on the remote (origin).
|
|
85
|
+
* Returns true when `v{version}` is found via `git ls-remote --tags origin`.
|
|
86
|
+
*/
|
|
87
|
+
export async function isTagOnRemote(
|
|
88
|
+
exec: ExecFn,
|
|
89
|
+
cwd: string,
|
|
90
|
+
version: string,
|
|
91
|
+
): Promise<boolean> {
|
|
92
|
+
try {
|
|
93
|
+
const tag = version.startsWith("v") ? version : `v${version}`;
|
|
94
|
+
const result = await exec("git", ["ls-remote", "--tags", "origin", tag], { cwd });
|
|
95
|
+
return result.code === 0 && result.stdout.trim().length > 0;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|