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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/result-helpers.ts
|
|
4
|
+
import { Result } from "typescript-result";
|
|
5
|
+
async function wrapAsync(fn, mapError) {
|
|
6
|
+
try {
|
|
7
|
+
const value = await fn();
|
|
8
|
+
return Result.ok(value);
|
|
9
|
+
} catch (err) {
|
|
10
|
+
return Result.error(mapError(err));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function ensureError(err) {
|
|
14
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
15
|
+
}
|
|
16
|
+
function unwrapOrThrow(result) {
|
|
17
|
+
if (result.ok) {
|
|
18
|
+
return result.value;
|
|
19
|
+
}
|
|
20
|
+
throw result.error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
wrapAsync,
|
|
25
|
+
ensureError,
|
|
26
|
+
unwrapOrThrow
|
|
27
|
+
};
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ProjectsSchema,
|
|
4
|
+
expandTilde,
|
|
4
5
|
fileExists,
|
|
5
6
|
getProjectsFilePath,
|
|
6
7
|
readValidatedJson,
|
|
7
8
|
validateProjectPath,
|
|
8
9
|
writeValidatedJson
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-W3TY22IS.mjs";
|
|
11
|
+
import {
|
|
12
|
+
ParseError,
|
|
13
|
+
ProjectExistsError,
|
|
14
|
+
ProjectNotFoundError,
|
|
15
|
+
ValidationError
|
|
16
|
+
} from "./chunk-EDJX7TT6.mjs";
|
|
10
17
|
|
|
11
18
|
// src/store/project.ts
|
|
12
19
|
import { basename, resolve } from "path";
|
|
13
|
-
var ProjectNotFoundError = class extends Error {
|
|
14
|
-
projectName;
|
|
15
|
-
constructor(projectName) {
|
|
16
|
-
super(`Project not found: ${projectName}`);
|
|
17
|
-
this.name = "ProjectNotFoundError";
|
|
18
|
-
this.projectName = projectName;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
var ProjectExistsError = class extends Error {
|
|
22
|
-
projectName;
|
|
23
|
-
constructor(projectName) {
|
|
24
|
-
super(`Project already exists: ${projectName}`);
|
|
25
|
-
this.name = "ProjectExistsError";
|
|
26
|
-
this.projectName = projectName;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
20
|
function migrateProjectIfNeeded(project) {
|
|
30
21
|
if (project.repositories) {
|
|
31
22
|
return project;
|
|
@@ -36,12 +27,12 @@ function migrateProjectIfNeeded(project) {
|
|
|
36
27
|
displayName: project.displayName,
|
|
37
28
|
repositories: project.paths.map((p) => ({
|
|
38
29
|
name: basename(p),
|
|
39
|
-
path: p
|
|
30
|
+
path: resolve(expandTilde(p))
|
|
40
31
|
})),
|
|
41
32
|
description: project.description
|
|
42
33
|
};
|
|
43
34
|
}
|
|
44
|
-
throw new
|
|
35
|
+
throw new ParseError(`Invalid project data: no paths or repositories for ${project.name}`);
|
|
45
36
|
}
|
|
46
37
|
async function listProjects() {
|
|
47
38
|
const filePath = getProjectsFilePath();
|
|
@@ -55,10 +46,27 @@ async function listProjects() {
|
|
|
55
46
|
if (needsMigration) {
|
|
56
47
|
const migrated = rawData.map(migrateProjectIfNeeded);
|
|
57
48
|
const validated = ProjectsSchema.parse(migrated);
|
|
58
|
-
await writeValidatedJson(filePath, validated, ProjectsSchema);
|
|
49
|
+
const writeResult = await writeValidatedJson(filePath, validated, ProjectsSchema);
|
|
50
|
+
if (!writeResult.ok) throw writeResult.error;
|
|
51
|
+
return validated;
|
|
52
|
+
}
|
|
53
|
+
const result = await readValidatedJson(filePath, ProjectsSchema);
|
|
54
|
+
if (!result.ok) throw result.error;
|
|
55
|
+
const projects = result.value;
|
|
56
|
+
const hasTildePaths = projects.some((p) => p.repositories.some((r) => r.path.startsWith("~")));
|
|
57
|
+
if (hasTildePaths) {
|
|
58
|
+
const corrected = projects.map((project) => ({
|
|
59
|
+
...project,
|
|
60
|
+
repositories: project.repositories.map(
|
|
61
|
+
(repo) => repo.path.startsWith("~") ? { ...repo, path: resolve(expandTilde(repo.path)) } : repo
|
|
62
|
+
)
|
|
63
|
+
}));
|
|
64
|
+
const validated = ProjectsSchema.parse(corrected);
|
|
65
|
+
const writeResult = await writeValidatedJson(filePath, validated, ProjectsSchema);
|
|
66
|
+
if (!writeResult.ok) throw writeResult.error;
|
|
59
67
|
return validated;
|
|
60
68
|
}
|
|
61
|
-
return
|
|
69
|
+
return projects;
|
|
62
70
|
}
|
|
63
71
|
async function getProject(name) {
|
|
64
72
|
const projects = await listProjects();
|
|
@@ -79,26 +87,27 @@ async function createProject(project) {
|
|
|
79
87
|
}
|
|
80
88
|
const pathErrors = [];
|
|
81
89
|
for (const repo of project.repositories) {
|
|
82
|
-
const resolved = resolve(repo.path);
|
|
90
|
+
const resolved = resolve(expandTilde(repo.path));
|
|
83
91
|
const validation = await validateProjectPath(resolved);
|
|
84
|
-
if (validation
|
|
85
|
-
pathErrors.push(` ${repo.path}: ${validation}`);
|
|
92
|
+
if (!validation.ok) {
|
|
93
|
+
pathErrors.push(` ${repo.path}: ${validation.error.message}`);
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
if (pathErrors.length > 0) {
|
|
89
|
-
throw new
|
|
90
|
-
${pathErrors.join("\n")}
|
|
97
|
+
throw new ValidationError(`Invalid project paths:
|
|
98
|
+
${pathErrors.join("\n")}`, "repositories");
|
|
91
99
|
}
|
|
92
100
|
const normalizedProject = {
|
|
93
101
|
...project,
|
|
94
102
|
repositories: project.repositories.map((repo) => ({
|
|
95
103
|
...repo,
|
|
96
104
|
name: repo.name || basename(repo.path),
|
|
97
|
-
path: resolve(repo.path)
|
|
105
|
+
path: resolve(expandTilde(repo.path))
|
|
98
106
|
}))
|
|
99
107
|
};
|
|
100
108
|
projects.push(normalizedProject);
|
|
101
|
-
await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
109
|
+
const writeResult = await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
110
|
+
if (!writeResult.ok) throw writeResult.error;
|
|
102
111
|
return normalizedProject;
|
|
103
112
|
}
|
|
104
113
|
async function updateProject(name, updates) {
|
|
@@ -110,20 +119,20 @@ async function updateProject(name, updates) {
|
|
|
110
119
|
if (updates.repositories) {
|
|
111
120
|
const pathErrors = [];
|
|
112
121
|
for (const repo of updates.repositories) {
|
|
113
|
-
const resolved = resolve(repo.path);
|
|
122
|
+
const resolved = resolve(expandTilde(repo.path));
|
|
114
123
|
const validation = await validateProjectPath(resolved);
|
|
115
|
-
if (validation
|
|
116
|
-
pathErrors.push(` ${repo.path}: ${validation}`);
|
|
124
|
+
if (!validation.ok) {
|
|
125
|
+
pathErrors.push(` ${repo.path}: ${validation.error.message}`);
|
|
117
126
|
}
|
|
118
127
|
}
|
|
119
128
|
if (pathErrors.length > 0) {
|
|
120
|
-
throw new
|
|
121
|
-
${pathErrors.join("\n")}
|
|
129
|
+
throw new ValidationError(`Invalid project paths:
|
|
130
|
+
${pathErrors.join("\n")}`, "repositories");
|
|
122
131
|
}
|
|
123
132
|
updates.repositories = updates.repositories.map((repo) => ({
|
|
124
133
|
...repo,
|
|
125
134
|
name: repo.name || basename(repo.path),
|
|
126
|
-
path: resolve(repo.path)
|
|
135
|
+
path: resolve(expandTilde(repo.path))
|
|
127
136
|
}));
|
|
128
137
|
}
|
|
129
138
|
const existingProject = projects[index];
|
|
@@ -137,7 +146,8 @@ ${pathErrors.join("\n")}`);
|
|
|
137
146
|
description: updates.description ?? existingProject.description
|
|
138
147
|
};
|
|
139
148
|
projects[index] = updatedProject;
|
|
140
|
-
await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
149
|
+
const writeResult = await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
150
|
+
if (!writeResult.ok) throw writeResult.error;
|
|
141
151
|
return updatedProject;
|
|
142
152
|
}
|
|
143
153
|
async function removeProject(name) {
|
|
@@ -147,7 +157,8 @@ async function removeProject(name) {
|
|
|
147
157
|
throw new ProjectNotFoundError(name);
|
|
148
158
|
}
|
|
149
159
|
projects.splice(index, 1);
|
|
150
|
-
await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
160
|
+
const writeResult = await writeValidatedJson(getProjectsFilePath(), projects, ProjectsSchema);
|
|
161
|
+
if (!writeResult.ok) throw writeResult.error;
|
|
151
162
|
}
|
|
152
163
|
async function getProjectRepos(name) {
|
|
153
164
|
const project = await getProject(name);
|
|
@@ -155,10 +166,10 @@ async function getProjectRepos(name) {
|
|
|
155
166
|
}
|
|
156
167
|
async function addProjectRepo(name, repo) {
|
|
157
168
|
const project = await getProject(name);
|
|
158
|
-
const resolvedPath = resolve(repo.path);
|
|
169
|
+
const resolvedPath = resolve(expandTilde(repo.path));
|
|
159
170
|
const validation = await validateProjectPath(resolvedPath);
|
|
160
|
-
if (validation
|
|
161
|
-
throw new
|
|
171
|
+
if (!validation.ok) {
|
|
172
|
+
throw new ValidationError(`Invalid path ${repo.path}: ${validation.error.message}`, repo.path);
|
|
162
173
|
}
|
|
163
174
|
if (project.repositories.some((r) => r.path === resolvedPath)) {
|
|
164
175
|
return project;
|
|
@@ -174,10 +185,10 @@ async function addProjectRepo(name, repo) {
|
|
|
174
185
|
}
|
|
175
186
|
async function removeProjectRepo(name, path) {
|
|
176
187
|
const project = await getProject(name);
|
|
177
|
-
const resolvedPath = resolve(path);
|
|
188
|
+
const resolvedPath = resolve(expandTilde(path));
|
|
178
189
|
const newRepos = project.repositories.filter((r) => r.path !== resolvedPath);
|
|
179
190
|
if (newRepos.length === 0) {
|
|
180
|
-
throw new
|
|
191
|
+
throw new ValidationError("Cannot remove the last repository from a project", "repositories");
|
|
181
192
|
}
|
|
182
193
|
if (newRepos.length === project.repositories.length) {
|
|
183
194
|
return project;
|
|
@@ -186,8 +197,6 @@ async function removeProjectRepo(name, path) {
|
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
export {
|
|
189
|
-
ProjectNotFoundError,
|
|
190
|
-
ProjectExistsError,
|
|
191
200
|
listProjects,
|
|
192
201
|
getProject,
|
|
193
202
|
projectExists,
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
IOError,
|
|
4
|
+
StorageError,
|
|
5
|
+
ValidationError
|
|
6
|
+
} from "./chunk-EDJX7TT6.mjs";
|
|
2
7
|
|
|
3
8
|
// src/utils/paths.ts
|
|
4
9
|
import { fileURLToPath } from "url";
|
|
@@ -6,6 +11,7 @@ import { dirname, isAbsolute, join, resolve, sep } from "path";
|
|
|
6
11
|
import { existsSync } from "fs";
|
|
7
12
|
import { homedir } from "os";
|
|
8
13
|
import { lstat, realpath, stat } from "fs/promises";
|
|
14
|
+
import { Result } from "typescript-result";
|
|
9
15
|
var __filename = fileURLToPath(import.meta.url);
|
|
10
16
|
var __dirname = dirname(__filename);
|
|
11
17
|
function getRepoRoot() {
|
|
@@ -67,46 +73,38 @@ function assertSafeCwd(path) {
|
|
|
67
73
|
throw new Error(`Unsafe path for cwd: must be absolute, got: ${path}`);
|
|
68
74
|
}
|
|
69
75
|
}
|
|
76
|
+
function expandTilde(path) {
|
|
77
|
+
if (path === "~") return homedir();
|
|
78
|
+
if (path.startsWith("~/")) return homedir() + path.slice(1);
|
|
79
|
+
return path;
|
|
80
|
+
}
|
|
70
81
|
async function validateProjectPath(path) {
|
|
71
82
|
try {
|
|
72
|
-
const resolved = resolve(path);
|
|
83
|
+
const resolved = resolve(expandTilde(path));
|
|
73
84
|
const lstats = await lstat(resolved);
|
|
74
85
|
if (lstats.isSymbolicLink()) {
|
|
75
86
|
const realPath = await realpath(resolved);
|
|
76
87
|
const realStats = await stat(realPath);
|
|
77
88
|
if (!realStats.isDirectory()) {
|
|
78
|
-
return "Symlink target is not a directory";
|
|
89
|
+
return Result.error(new IOError("Symlink target is not a directory"));
|
|
79
90
|
}
|
|
80
|
-
return true;
|
|
91
|
+
return Result.ok(true);
|
|
81
92
|
}
|
|
82
93
|
if (!lstats.isDirectory()) {
|
|
83
|
-
return "Path is not a directory";
|
|
94
|
+
return Result.error(new IOError("Path is not a directory"));
|
|
84
95
|
}
|
|
85
|
-
return true;
|
|
86
|
-
} catch {
|
|
87
|
-
|
|
96
|
+
return Result.ok(true);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const code = err.code;
|
|
99
|
+
const message = code === "EACCES" ? "Permission denied" : "Directory does not exist";
|
|
100
|
+
return Result.error(new IOError(message, err instanceof Error ? err : void 0));
|
|
88
101
|
}
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
// src/utils/storage.ts
|
|
92
105
|
import { access, appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
|
93
106
|
import { dirname as dirname2 } from "path";
|
|
94
|
-
|
|
95
|
-
path;
|
|
96
|
-
constructor(message, path, cause) {
|
|
97
|
-
super(message, { cause });
|
|
98
|
-
this.name = "ValidationError";
|
|
99
|
-
this.path = path;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
var FileNotFoundError = class extends Error {
|
|
103
|
-
path;
|
|
104
|
-
constructor(message, path) {
|
|
105
|
-
super(message);
|
|
106
|
-
this.name = "FileNotFoundError";
|
|
107
|
-
this.path = path;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
107
|
+
import { Result as Result2 } from "typescript-result";
|
|
110
108
|
async function ensureDir(dirPath) {
|
|
111
109
|
await mkdir(dirPath, { recursive: true });
|
|
112
110
|
}
|
|
@@ -135,46 +133,61 @@ async function readValidatedJson(filePath, schema) {
|
|
|
135
133
|
content = await readFile(filePath, "utf-8");
|
|
136
134
|
} catch (err) {
|
|
137
135
|
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
138
|
-
|
|
136
|
+
return Result2.error(new StorageError(`File not found: ${filePath}`, err instanceof Error ? err : void 0));
|
|
139
137
|
}
|
|
140
|
-
|
|
138
|
+
return Result2.error(new StorageError(`Failed to read ${filePath}`, err instanceof Error ? err : void 0));
|
|
141
139
|
}
|
|
142
140
|
let data;
|
|
143
141
|
try {
|
|
144
142
|
data = JSON.parse(content);
|
|
145
143
|
} catch (err) {
|
|
146
|
-
|
|
144
|
+
return Result2.error(
|
|
145
|
+
new ValidationError(`Invalid JSON in ${filePath}`, filePath, err instanceof Error ? err : void 0)
|
|
146
|
+
);
|
|
147
147
|
}
|
|
148
148
|
const result = schema.safeParse(data);
|
|
149
149
|
if (!result.success) {
|
|
150
150
|
const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
151
|
-
|
|
152
|
-
${issues}`, filePath, result.error);
|
|
151
|
+
return Result2.error(new ValidationError(`Validation failed for ${filePath}:
|
|
152
|
+
${issues}`, filePath, result.error));
|
|
153
153
|
}
|
|
154
|
-
return result.data;
|
|
154
|
+
return Result2.ok(result.data);
|
|
155
155
|
}
|
|
156
156
|
async function writeValidatedJson(filePath, data, schema) {
|
|
157
157
|
const result = schema.safeParse(data);
|
|
158
158
|
if (!result.success) {
|
|
159
159
|
const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
160
|
-
|
|
161
|
-
${
|
|
160
|
+
return Result2.error(
|
|
161
|
+
new ValidationError(`Validation failed before writing to ${filePath}:
|
|
162
|
+
${issues}`, filePath, result.error)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await ensureDir(dirname2(filePath));
|
|
167
|
+
await writeFile(filePath, JSON.stringify(result.data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return Result2.error(new StorageError(`Failed to write ${filePath}`, err instanceof Error ? err : void 0));
|
|
162
170
|
}
|
|
163
|
-
|
|
164
|
-
await writeFile(filePath, JSON.stringify(result.data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
171
|
+
return Result2.ok(void 0);
|
|
165
172
|
}
|
|
166
173
|
async function appendToFile(filePath, content) {
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
try {
|
|
175
|
+
await ensureDir(dirname2(filePath));
|
|
176
|
+
await appendFile(filePath, content, { encoding: "utf-8", mode: 384 });
|
|
177
|
+
return Result2.ok(void 0);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
return Result2.error(new StorageError(`Failed to append to ${filePath}`, err instanceof Error ? err : void 0));
|
|
180
|
+
}
|
|
169
181
|
}
|
|
170
182
|
async function readTextFile(filePath) {
|
|
171
183
|
try {
|
|
172
|
-
|
|
184
|
+
const content = await readFile(filePath, "utf-8");
|
|
185
|
+
return Result2.ok(content);
|
|
173
186
|
} catch (err) {
|
|
174
187
|
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
175
|
-
|
|
188
|
+
return Result2.error(new StorageError(`File not found: ${filePath}`, err instanceof Error ? err : void 0));
|
|
176
189
|
}
|
|
177
|
-
|
|
190
|
+
return Result2.error(new StorageError(`Failed to read ${filePath}`, err instanceof Error ? err : void 0));
|
|
178
191
|
}
|
|
179
192
|
}
|
|
180
193
|
|
|
@@ -285,9 +298,8 @@ export {
|
|
|
285
298
|
getIdeateDir,
|
|
286
299
|
getSchemaPath,
|
|
287
300
|
assertSafeCwd,
|
|
301
|
+
expandTilde,
|
|
288
302
|
validateProjectPath,
|
|
289
|
-
ValidationError,
|
|
290
|
-
FileNotFoundError,
|
|
291
303
|
ensureDir,
|
|
292
304
|
removeDir,
|
|
293
305
|
fileExists,
|