create-claude-workspace 2.3.23 → 2.3.25

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.
@@ -1,6 +1,7 @@
1
1
  // ─── CI pipeline status polling ───
2
2
  // Uses gh/glab CLI to watch pipeline status after push.
3
3
  import { execFileSync } from 'node:child_process';
4
+ import { resolve } from 'node:path';
4
5
  const POLL_INTERVAL = 15_000;
5
6
  const MAX_POLL_TIME = 30 * 60_000; // 30 minutes
6
7
  const CLI_TIMEOUT = 15_000;
@@ -20,38 +21,80 @@ export function detectCIPlatform(projectDir) {
20
21
  }
21
22
  /**
22
23
  * Check if the CLI tool for a given platform is installed and authenticated.
23
- * Returns null if OK, or an error message describing what's missing.
24
+ * Tries the CLI name directly, then known install paths (for freshly installed tools
25
+ * not yet in the current process PATH).
24
26
  */
25
27
  export function checkPlatformCLI(platform) {
26
28
  if (platform === 'none')
27
- return null;
29
+ return { status: 'ok' };
28
30
  const cli = platform === 'github' ? 'gh' : 'glab';
29
- // Check if binary exists
30
- try {
31
- execFileSync(cli, ['--version'], { stdio: 'pipe', timeout: 10_000 });
32
- }
33
- catch (err) {
34
- const code = err.code;
35
- if (code === 'ENOENT') {
36
- return `${cli} CLI is not installed`;
37
- }
38
- return `${cli} CLI error: ${err.message?.split('\n')[0]}`;
39
- }
31
+ const bin = resolveCLIBinary(cli);
32
+ if (!bin)
33
+ return { status: 'not-installed' };
40
34
  // Check if authenticated
41
35
  try {
42
36
  if (platform === 'github') {
43
- execFileSync('gh', ['auth', 'status'], { stdio: 'pipe', timeout: 10_000 });
37
+ execFileSync(bin, ['auth', 'status'], { stdio: 'pipe', timeout: 10_000 });
44
38
  }
45
39
  else {
46
- // glab doesn't have a clean auth status command try a harmless API call
47
- execFileSync('glab', ['api', 'version'], { stdio: 'pipe', timeout: 10_000 });
40
+ execFileSync(bin, ['auth', 'status'], { stdio: 'pipe', timeout: 10_000 });
48
41
  }
49
42
  }
50
43
  catch {
51
- return `${cli} CLI is installed but not authenticated — run: ${cli} auth login`;
44
+ return { status: 'not-authenticated', command: `${cli} auth login` };
45
+ }
46
+ return { status: 'ok' };
47
+ }
48
+ /**
49
+ * Resolve the full path to a CLI binary.
50
+ * First tries the bare name (in PATH), then checks known installation directories
51
+ * for freshly installed tools that aren't in the current process PATH.
52
+ */
53
+ export function resolveCLIBinary(cli) {
54
+ // Try bare name first (already in PATH)
55
+ try {
56
+ execFileSync(cli, ['--version'], { stdio: 'pipe', timeout: 10_000 });
57
+ return cli;
58
+ }
59
+ catch (err) {
60
+ if (err.code !== 'ENOENT')
61
+ return cli; // Found but errored
62
+ }
63
+ // Try known installation paths (winget, scoop, choco, brew, etc.)
64
+ const candidates = getKnownCLIPaths(cli);
65
+ for (const path of candidates) {
66
+ try {
67
+ execFileSync(path, ['--version'], { stdio: 'pipe', timeout: 10_000 });
68
+ return path;
69
+ }
70
+ catch (err) {
71
+ if (err.code !== 'ENOENT')
72
+ return path;
73
+ }
52
74
  }
53
75
  return null;
54
76
  }
77
+ function getKnownCLIPaths(cli) {
78
+ const paths = [];
79
+ const isWindows = process.platform === 'win32';
80
+ const home = process.env.HOME || process.env.USERPROFILE || '';
81
+ if (isWindows) {
82
+ const localApps = process.env.LOCALAPPDATA || resolve(home, 'AppData', 'Local');
83
+ const progFiles = process.env.ProgramFiles || 'C:\\Program Files';
84
+ const progFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
85
+ if (cli === 'gh') {
86
+ paths.push(resolve(progFiles, 'GitHub CLI', 'gh.exe'), resolve(progFilesX86, 'GitHub CLI', 'gh.exe'), resolve(localApps, 'Microsoft', 'WinGet', 'Links', 'gh.exe'), resolve(home, 'scoop', 'shims', 'gh.exe'));
87
+ }
88
+ else if (cli === 'glab') {
89
+ paths.push(resolve(progFiles, 'glab', 'bin', 'glab.exe'), resolve(localApps, 'Microsoft', 'WinGet', 'Links', 'glab.exe'), resolve(home, 'scoop', 'shims', 'glab.exe'));
90
+ }
91
+ }
92
+ else {
93
+ // macOS / Linux
94
+ paths.push(`/usr/local/bin/${cli}`, `/opt/homebrew/bin/${cli}`, resolve(home, '.local', 'bin', cli));
95
+ }
96
+ return paths;
97
+ }
55
98
  /**
56
99
  * Attempt to install the CLI tool for a given platform.
57
100
  * Returns true if installation succeeded, false otherwise.
@@ -259,33 +259,42 @@ export async function runScheduler(opts) {
259
259
  // Ensure platform CLI is available before task mode detection
260
260
  const detectedPlatform = detectCIPlatform(opts.projectDir);
261
261
  if (detectedPlatform !== 'none') {
262
- const cliError = checkPlatformCLI(detectedPlatform);
263
- if (cliError) {
264
- const cli = detectedPlatform === 'github' ? 'gh' : 'glab';
265
- logger.warn(`${cliError} — attempting auto-install of ${cli}`);
262
+ const cli = detectedPlatform === 'github' ? 'gh' : 'glab';
263
+ let cliCheck = checkPlatformCLI(detectedPlatform);
264
+ if (cliCheck.status === 'not-installed') {
265
+ logger.warn(`${cli} CLI is not installed — attempting auto-install`);
266
266
  const installResult = installPlatformCLI(detectedPlatform);
267
267
  if (installResult.success) {
268
268
  logger.info(`Installed ${cli} via ${installResult.method}`);
269
- const authError = checkPlatformCLI(detectedPlatform);
270
- if (authError?.includes('not authenticated')) {
271
- logger.warn(`${cli} installed but needs authentication. Run: ${cli} auth login`);
272
- }
269
+ cliCheck = checkPlatformCLI(detectedPlatform);
273
270
  }
274
271
  else {
275
272
  logger.error(`Failed to install ${cli}: ${installResult.error}`);
273
+ logger.error(`Install ${cli} manually and re-run the scheduler.`);
274
+ process.exit(1);
276
275
  }
277
276
  }
277
+ if (cliCheck.status === 'not-authenticated') {
278
+ logger.error(`${cli} CLI is installed but not authenticated.`);
279
+ logger.error(`Run this command to authenticate, then re-run the scheduler:`);
280
+ logger.error(` ${cliCheck.command}`);
281
+ process.exit(1);
282
+ }
283
+ if (cliCheck.status === 'error') {
284
+ logger.error(`${cli} CLI error: ${cliCheck.message}`);
285
+ process.exit(1);
286
+ }
278
287
  }
279
- // Task mode detection
288
+ // Task mode detection — always re-detect unless user explicitly set --task-mode
289
+ // (stored state may be stale if CLI was installed/authenticated since last run)
280
290
  if (opts.taskMode) {
281
291
  state.taskMode = opts.taskMode;
292
+ logger.info(`Task mode (from flag): ${state.taskMode}`);
282
293
  }
283
- else if (!existing) {
284
- // Auto-detect: platform mode if remote has issues with status:: labels
294
+ else {
285
295
  state.taskMode = detectTaskMode(opts.projectDir);
286
- logger.info(`Task mode auto-detected: ${state.taskMode}`);
296
+ logger.info(`Task mode: ${state.taskMode}`);
287
297
  }
288
- // If resuming existing state, keep the stored taskMode
289
298
  // Platform mode: shorter idle poll interval (30s vs 5min)
290
299
  const mutableOpts = opts;
291
300
  if (state.taskMode === 'platform' && !opts.taskMode && opts.idlePollInterval === SCHEDULER_DEFAULTS.idlePollInterval) {
@@ -785,6 +785,15 @@ export async function recoverOrphanedWorktrees(projectDir, state, logger, _deps)
785
785
  // Phase 1: Orphaned worktrees → re-inject into pipelines
786
786
  for (const worktreePath of orphans) {
787
787
  try {
788
+ // If the worktree directory was manually deleted, prune it from git and skip
789
+ if (!existsSync(worktreePath)) {
790
+ logger.info(`[recovery] Worktree directory missing: ${worktreePath} — pruning from git`);
791
+ try {
792
+ execFileSync('git', ['worktree', 'prune'], { cwd: projectDir, stdio: 'pipe', timeout: 10_000 });
793
+ }
794
+ catch { /* ignore */ }
795
+ continue;
796
+ }
788
797
  const branch = getCurrentBranch(worktreePath);
789
798
  const alreadyMerged = isBranchMerged(projectDir, branch);
790
799
  if (alreadyMerged) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "2.3.23",
3
+ "version": "2.3.25",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",