ralphctl 0.1.1 → 0.1.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.
@@ -1,18 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  escapableSelect
4
- } from "./chunk-NTWO2LXB.mjs";
4
+ } from "./chunk-7LZ6GOGN.mjs";
5
5
  import {
6
6
  EXIT_ERROR,
7
7
  exitWithCode
8
8
  } from "./chunk-7TG3EAQ2.mjs";
9
9
  import {
10
- ProjectExistsError,
11
10
  createProject
12
- } from "./chunk-35KGXPBU.mjs";
11
+ } from "./chunk-PDI6HBZ7.mjs";
13
12
  import {
13
+ ensureError,
14
+ wrapAsync
15
+ } from "./chunk-OEUJDSHY.mjs";
16
+ import {
17
+ expandTilde,
14
18
  validateProjectPath
15
- } from "./chunk-4C24UQ3X.mjs";
19
+ } from "./chunk-W3TY22IS.mjs";
20
+ import {
21
+ IOError,
22
+ ProjectExistsError
23
+ } from "./chunk-EDJX7TT6.mjs";
16
24
  import {
17
25
  createSpinner,
18
26
  emoji,
@@ -31,34 +39,27 @@ import {
31
39
  import { existsSync as existsSync2, statSync as statSync2 } from "fs";
32
40
  import { basename, join as join3, resolve as resolve2 } from "path";
33
41
  import { input, select } from "@inquirer/prompts";
42
+ import { Result as Result3 } from "typescript-result";
34
43
 
35
44
  // src/interactive/file-browser.ts
36
45
  import { readdirSync, statSync } from "fs";
37
46
  import { homedir } from "os";
38
47
  import { dirname, join, resolve } from "path";
48
+ import { Result } from "typescript-result";
39
49
  function listDirectories(dirPath) {
40
- try {
41
- const entries = readdirSync(dirPath, { withFileTypes: true });
42
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
43
- } catch {
44
- return [];
45
- }
50
+ const r = Result.try(() => readdirSync(dirPath, { withFileTypes: true }));
51
+ if (!r.ok) return [];
52
+ return r.value.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
46
53
  }
47
54
  function hasSubdirectories(dirPath) {
48
- try {
49
- const entries = readdirSync(dirPath, { withFileTypes: true });
50
- return entries.some((e) => e.isDirectory() && !e.name.startsWith("."));
51
- } catch {
52
- return false;
53
- }
55
+ const r = Result.try(() => readdirSync(dirPath, { withFileTypes: true }));
56
+ if (!r.ok) return false;
57
+ return r.value.some((e) => e.isDirectory() && !e.name.startsWith("."));
54
58
  }
55
59
  function isGitRepo(dirPath) {
56
- try {
57
- const gitDir = join(dirPath, ".git");
58
- return statSync(gitDir).isDirectory();
59
- } catch {
60
- return false;
61
- }
60
+ const r = Result.try(() => statSync(join(dirPath, ".git")));
61
+ if (!r.ok) return false;
62
+ return r.value.isDirectory();
62
63
  }
63
64
  async function browseDirectory(message = "Browse to directory:", startPath) {
64
65
  let currentPath = startPath ? resolve(startPath) : homedir();
@@ -105,36 +106,37 @@ async function browseDirectory(message = "Browse to directory:", startPath) {
105
106
  name: muted("Cancel"),
106
107
  value: "__CANCEL__"
107
108
  });
108
- try {
109
- const selected = await escapableSelect({
109
+ const selectResult = await wrapAsync(
110
+ () => escapableSelect({
110
111
  message: `${emoji.donut} ${message}
111
112
  ${muted(currentPath)}`,
112
113
  choices,
113
114
  pageSize: 15,
114
115
  loop: false
115
- });
116
- if (selected === null) {
117
- return null;
118
- }
119
- switch (selected) {
120
- case "__SELECT__":
121
- return currentPath;
122
- case "__PARENT__":
123
- currentPath = parentDir;
124
- break;
125
- case "__HOME__":
126
- currentPath = homedir();
127
- break;
128
- case "__CANCEL__":
129
- return null;
130
- default:
131
- currentPath = selected;
132
- }
133
- } catch (err) {
134
- if (err.name === "ExitPromptError") {
116
+ }),
117
+ ensureError
118
+ );
119
+ if (!selectResult.ok) {
120
+ if (selectResult.error.name === "ExitPromptError") return null;
121
+ throw selectResult.error;
122
+ }
123
+ const selected = selectResult.value;
124
+ if (selected === null) {
125
+ return null;
126
+ }
127
+ switch (selected) {
128
+ case "__SELECT__":
129
+ return currentPath;
130
+ case "__PARENT__":
131
+ currentPath = parentDir;
132
+ break;
133
+ case "__HOME__":
134
+ currentPath = homedir();
135
+ break;
136
+ case "__CANCEL__":
135
137
  return null;
136
- }
137
- throw err;
138
+ default:
139
+ currentPath = selected;
138
140
  }
139
141
  }
140
142
  }
@@ -142,6 +144,7 @@ async function browseDirectory(message = "Browse to directory:", startPath) {
142
144
  // src/utils/detect-scripts.ts
143
145
  import { existsSync, readFileSync } from "fs";
144
146
  import { join as join2 } from "path";
147
+ import { Result as Result2 } from "typescript-result";
145
148
  function detectNodePackageManager(projectPath) {
146
149
  if (existsSync(join2(projectPath, "pnpm-lock.yaml"))) return "pnpm";
147
150
  if (existsSync(join2(projectPath, "yarn.lock"))) return "yarn";
@@ -155,15 +158,20 @@ var NODE_PRIMARY_GROUPS = [
155
158
  var NODE_FALLBACK_GROUPS = [
156
159
  { label: "build", aliases: ["build", "compile"] }
157
160
  ];
158
- function readPackageJsonScripts(projectPath) {
161
+ function safeReadPackageJsonScripts(projectPath) {
159
162
  try {
160
163
  const raw = readFileSync(join2(projectPath, "package.json"), "utf-8");
161
164
  const pkg = JSON.parse(raw);
162
- return pkg.scripts ?? {};
165
+ return Result2.ok(pkg.scripts ?? {});
163
166
  } catch {
164
- return {};
167
+ return Result2.error(new IOError("Failed to read package.json"));
165
168
  }
166
169
  }
170
+ function readPackageJsonScripts(projectPath) {
171
+ const result = safeReadPackageJsonScripts(projectPath);
172
+ if (!result.ok) return {};
173
+ return result.value;
174
+ }
167
175
  var nodeDetector = {
168
176
  type: "node",
169
177
  label: "Node.js",
@@ -284,25 +292,19 @@ function validateSlug(slug) {
284
292
  return /^[a-z0-9-]+$/.test(slug);
285
293
  }
286
294
  function isGitRepo2(path) {
287
- try {
288
- const gitDir = join3(path, ".git");
289
- return existsSync2(gitDir) && statSync2(gitDir).isDirectory();
290
- } catch {
291
- return false;
292
- }
295
+ const gitDir = join3(path, ".git");
296
+ const r = Result3.try(() => existsSync2(gitDir) && statSync2(gitDir).isDirectory());
297
+ return r.ok ? r.value : false;
293
298
  }
294
299
  function hasAiInstructions(repoPath) {
295
300
  return existsSync2(join3(repoPath, "CLAUDE.md")) || existsSync2(join3(repoPath, ".github", "copilot-instructions.md"));
296
301
  }
297
302
  async function addCheckScriptToRepository(repo) {
298
303
  let suggested = null;
299
- try {
300
- const detection = detectCheckScriptCandidates(repo.path);
301
- if (detection) {
302
- log.success(` Detected: ${detection.typeLabel}`);
303
- suggested = suggestCheckScript(repo.path);
304
- }
305
- } catch {
304
+ const detectR = Result3.try(() => detectCheckScriptCandidates(repo.path));
305
+ if (detectR.ok && detectR.value) {
306
+ log.success(` Detected: ${detectR.value.typeLabel}`);
307
+ suggested = suggestCheckScript(repo.path);
306
308
  }
307
309
  const checkInput = await input({
308
310
  message: " Check script (optional):",
@@ -337,10 +339,10 @@ async function projectAddCommand(options = {}) {
337
339
  if (options.paths) {
338
340
  const spinner = options.paths.length > 1 ? createSpinner("Validating repository paths...").start() : null;
339
341
  for (const path of options.paths) {
340
- const resolved = resolve2(path.trim());
342
+ const resolved = resolve2(expandTilde(path.trim()));
341
343
  const validation = await validateProjectPath(resolved);
342
- if (validation !== true) {
343
- errors.push(`--path ${path}: ${validation}`);
344
+ if (!validation.ok) {
345
+ errors.push(`--path ${path}: ${validation.error.message}`);
344
346
  }
345
347
  }
346
348
  spinner?.succeed("Paths validated");
@@ -356,7 +358,7 @@ async function projectAddCommand(options = {}) {
356
358
  name = trimmedName;
357
359
  displayName = trimmedDisplayName;
358
360
  repositories = options.paths.map((p) => {
359
- const resolved = resolve2(p.trim());
361
+ const resolved = resolve2(expandTilde(p.trim()));
360
362
  const repo = { name: basename(resolved), path: resolved };
361
363
  if (options.checkScript) repo.checkScript = options.checkScript;
362
364
  return repo;
@@ -384,9 +386,9 @@ async function projectAddCommand(options = {}) {
384
386
  repositories = [];
385
387
  if (options.paths) {
386
388
  for (const p of options.paths) {
387
- const resolved = resolve2(p.trim());
389
+ const resolved = resolve2(expandTilde(p.trim()));
388
390
  const validation = await validateProjectPath(resolved);
389
- if (validation === true) {
391
+ if (validation.ok) {
390
392
  repositories.push({ name: basename(resolved), path: resolved });
391
393
  }
392
394
  }
@@ -416,15 +418,15 @@ async function projectAddCommand(options = {}) {
416
418
  default: process.cwd(),
417
419
  validate: async (v) => {
418
420
  const result = await validateProjectPath(v.trim());
419
- return result;
421
+ return result.ok ? true : result.error.message;
420
422
  }
421
423
  });
422
424
  firstPath = firstPath.trim();
423
425
  }
424
- const resolved = resolve2(firstPath);
426
+ const resolved = resolve2(expandTilde(firstPath));
425
427
  const validation = await validateProjectPath(resolved);
426
- if (validation !== true) {
427
- showError(`Invalid path: ${validation}`);
428
+ if (!validation.ok) {
429
+ showError(`Invalid path: ${validation.error.message}`);
428
430
  exitWithCode(EXIT_ERROR);
429
431
  }
430
432
  repositories.push({ name: basename(resolved), path: resolved });
@@ -456,15 +458,15 @@ Configuring: ${firstRepo.name}`);
456
458
  } else if (addAction === "browse") {
457
459
  const browsed = await browseDirectory("Select repository directory:");
458
460
  if (browsed) {
459
- const resolved = resolve2(browsed);
461
+ const resolved = resolve2(expandTilde(browsed));
460
462
  const validation = await validateProjectPath(resolved);
461
- if (validation === true) {
463
+ if (validation.ok) {
462
464
  const newRepo = { name: basename(resolved), path: resolved };
463
465
  log.success(`Added: ${newRepo.name}`);
464
466
  const repoWithScripts = await addCheckScriptToRepository(newRepo);
465
467
  repositories.push(repoWithScripts);
466
468
  } else {
467
- log.error(`Invalid path: ${validation}`);
469
+ log.error(`Invalid path: ${validation.error.message}`);
468
470
  }
469
471
  }
470
472
  } else {
@@ -474,15 +476,15 @@ Configuring: ${firstRepo.name}`);
474
476
  if (additionalPath.trim() === "") {
475
477
  addMore = false;
476
478
  } else {
477
- const resolved = resolve2(additionalPath.trim());
479
+ const resolved = resolve2(expandTilde(additionalPath.trim()));
478
480
  const validation = await validateProjectPath(resolved);
479
- if (validation === true) {
481
+ if (validation.ok) {
480
482
  const newRepo = { name: basename(resolved), path: resolved };
481
483
  log.success(`Added: ${newRepo.name}`);
482
484
  const repoWithScripts = await addCheckScriptToRepository(newRepo);
483
485
  repositories.push(repoWithScripts);
484
486
  } else {
485
- log.error(`Invalid path: ${validation}`);
487
+ log.error(`Invalid path: ${validation.error.message}`);
486
488
  }
487
489
  }
488
490
  }
@@ -494,40 +496,41 @@ Configuring: ${firstRepo.name}`);
494
496
  const trimmedDescInteractive = description.trim();
495
497
  description = trimmedDescInteractive === "" ? void 0 : trimmedDescInteractive;
496
498
  }
497
- try {
498
- const project = {
499
- name,
500
- displayName,
501
- repositories,
502
- description
503
- };
504
- const created = await createProject(project);
505
- showSuccess("Project added!", [
506
- ["Name", created.name],
507
- ["Display Name", created.displayName]
508
- ]);
509
- if (created.description) {
510
- console.log(field("Description", created.description));
511
- }
512
- console.log(field("Repositories", ""));
513
- for (const repo of created.repositories) {
514
- log.item(`${repo.name} \u2192 ${repo.path}`);
515
- if (repo.checkScript) {
516
- console.log(` Check: ${repo.checkScript}`);
517
- } else {
518
- console.log(` Check: ${muted("(not configured)")}`);
519
- }
520
- }
521
- console.log("");
522
- } catch (err) {
523
- if (err instanceof ProjectExistsError) {
499
+ const project = {
500
+ name,
501
+ displayName,
502
+ repositories,
503
+ description
504
+ };
505
+ const createR = await wrapAsync(() => createProject(project), ensureError);
506
+ if (!createR.ok) {
507
+ if (createR.error instanceof ProjectExistsError) {
524
508
  showError(`Project "${name}" already exists.`);
525
509
  showNextStep(`ralphctl project remove ${name}`, "remove existing project first");
526
510
  log.newline();
527
511
  } else {
528
- throw err;
512
+ throw createR.error;
513
+ }
514
+ return;
515
+ }
516
+ const created = createR.value;
517
+ showSuccess("Project added!", [
518
+ ["Name", created.name],
519
+ ["Display Name", created.displayName]
520
+ ]);
521
+ if (created.description) {
522
+ console.log(field("Description", created.description));
523
+ }
524
+ console.log(field("Repositories", ""));
525
+ for (const repo of created.repositories) {
526
+ log.item(`${repo.name} \u2192 ${repo.path}`);
527
+ if (repo.checkScript) {
528
+ console.log(` Check: ${repo.checkScript}`);
529
+ } else {
530
+ console.log(` Check: ${muted("(not configured)")}`);
529
531
  }
530
532
  }
533
+ console.log("");
531
534
  }
532
535
 
533
536
  export {