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.
- package/dist/{add-T2SU4O3H.mjs → add-7LBVENXM.mjs} +6 -4
- package/dist/{add-RRRB63YS.mjs → add-DVEYDCTR.mjs} +6 -4
- package/dist/{chunk-NTWO2LXB.mjs → chunk-7LZ6GOGN.mjs} +13 -12
- package/dist/{chunk-NC3A3T2Z.mjs → chunk-DZ6HHTM5.mjs} +1 -1
- package/dist/chunk-EDJX7TT6.mjs +148 -0
- package/dist/{chunk-IDLFGCG7.mjs → chunk-F2MMCTB5.mjs} +71 -77
- package/dist/{chunk-VCZGS3UD.mjs → chunk-LFDW6MWF.mjs} +65 -70
- package/dist/{chunk-OFKKZ6SW.mjs → chunk-M7JV6MKD.mjs} +270 -349
- package/dist/chunk-OEUJDSHY.mjs +27 -0
- package/dist/{chunk-35KGXPBU.mjs → chunk-PDI6HBZ7.mjs} +52 -43
- package/dist/{chunk-4C24UQ3X.mjs → chunk-W3TY22IS.mjs} +52 -40
- package/dist/{chunk-MTBWEN43.mjs → chunk-YIB7QYU4.mjs} +109 -106
- package/dist/cli.mjs +764 -741
- package/dist/create-MQ4OHZAX.mjs +12 -0
- package/dist/{handle-XDMH5DE4.mjs → handle-K2AZLTKU.mjs} +1 -1
- package/dist/{project-BG7VHACC.mjs → project-Q4LKML42.mjs} +6 -4
- package/dist/{resolver-HHMON7RK.mjs → resolver-NH34HTB6.mjs} +27 -17
- package/dist/{sprint-FJVU7HRC.mjs → sprint-UHYXSEBJ.mjs} +8 -5
- package/dist/{wizard-EK22QYUY.mjs → wizard-MCDDXLGE.mjs} +45 -48
- package/package.json +2 -1
- package/dist/create-POR4WRTU.mjs +0 -10
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
escapableSelect
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
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 {
|