supipowers 1.2.2 → 1.2.3

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 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 bun global location
64
- const bunPath = join(homedir(), ".bun", "bin", "omp");
65
- if (existsSync(bunPath)) {
66
- const fallback = run(bunPath, ["--version"]);
67
- if (!fallback.error && fallback.status === 0) return bunPath;
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 npm/bun global locations
79
- for (const candidate of [
80
- join(homedir(), ".bun", "bin", "pi"),
81
- join(homedir(), ".npm-global", "bin", "pi"),
82
- "/usr/local/bin/pi",
83
- "/opt/homebrew/bin/pi",
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Workflow extension for OMP coding agents.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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 alreadyReleased = await isVersionReleased(
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 — skip bump when local version is unreleased
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 (!alreadyReleased && currentVersion !== "0.0.0") {
311
- // Local version hasn't been tagged yet use it as-is
320
+ if (!localTagExists && currentVersion !== "0.0.0") {
321
+ // (a) No tag at allversion 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 skipping bump");
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
- // Version already released — ask for bump
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} ${nextVersion} (${bump})`);
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
 
@@ -34,7 +34,9 @@ export async function checkBinary(
34
34
  exec: ExecFn,
35
35
  binary: string,
36
36
  ): Promise<{ installed: boolean; version?: string }> {
37
- const which = await exec("which", [binary]);
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
  ]);
@@ -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
- console.log(`[dry-run] Would git tag -a v${version}`);
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
- progress("git-tag", "active", `v${version}`);
115
- const tagMessage = `Release v${version}\n\n${changelog}`;
116
- const gitTag = await exec("git", ["tag", "-a", `v${version}`, "-m", tagMessage], { cwd });
117
- if (gitTag.code !== 0) {
118
- const detail = gitTag.stderr || gitTag.stdout || `exit code ${gitTag.code}`;
119
- progress("git-tag", "error", detail);
120
- return { version, tagCreated: false, pushed: false, channels: [], error: `git tag: ${detail}` };
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 });
@@ -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
+ }