ralphctl 0.1.2 → 0.1.4

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,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ unwrapOrThrow
4
+ } from "./chunk-OEUJDSHY.mjs";
2
5
  import {
3
6
  ConfigSchema,
4
- FileNotFoundError,
5
7
  SprintSchema,
6
8
  TasksSchema,
7
- ValidationError,
8
9
  appendToFile,
9
10
  assertSafeCwd,
10
11
  ensureDir,
@@ -20,7 +21,14 @@ import {
20
21
  readValidatedJson,
21
22
  removeDir,
22
23
  writeValidatedJson
23
- } from "./chunk-6PYTKGB5.mjs";
24
+ } from "./chunk-W3TY22IS.mjs";
25
+ import {
26
+ LockError,
27
+ NoCurrentSprintError,
28
+ SprintNotFoundError,
29
+ SprintStatusError,
30
+ StorageError
31
+ } from "./chunk-EDJX7TT6.mjs";
24
32
  import {
25
33
  log
26
34
  } from "./chunk-QBXHAXHI.mjs";
@@ -36,10 +44,10 @@ async function getConfig() {
36
44
  if (!await fileExists(configPath)) {
37
45
  return DEFAULT_CONFIG;
38
46
  }
39
- return readValidatedJson(configPath, ConfigSchema);
47
+ return unwrapOrThrow(await readValidatedJson(configPath, ConfigSchema));
40
48
  }
41
49
  async function saveConfig(config) {
42
- await writeValidatedJson(getConfigPath(), config, ConfigSchema);
50
+ unwrapOrThrow(await writeValidatedJson(getConfigPath(), config, ConfigSchema));
43
51
  }
44
52
  async function getCurrentSprint() {
45
53
  const config = await getConfig();
@@ -91,14 +99,7 @@ import { execSync } from "child_process";
91
99
  // src/utils/file-lock.ts
92
100
  import { mkdir, readFile, unlink, writeFile } from "fs/promises";
93
101
  import { dirname } from "path";
94
- var LockAcquisitionError = class extends Error {
95
- lockPath;
96
- constructor(message, lockPath) {
97
- super(message);
98
- this.name = "LockAcquisitionError";
99
- this.lockPath = lockPath;
100
- }
101
- };
102
+ import { Result } from "typescript-result";
102
103
  var parsed = parseInt(process.env["RALPHCTL_LOCK_TIMEOUT_MS"] ?? "", 10);
103
104
  var LOCK_TIMEOUT_MS = parsed > 0 && parsed <= 36e5 ? parsed : 3e4;
104
105
  var RETRY_DELAY_MS = 50;
@@ -138,12 +139,12 @@ async function acquireLock(filePath) {
138
139
  try {
139
140
  await mkdir(dirname(lockPath), { recursive: true });
140
141
  await writeFile(lockPath, JSON.stringify(lockInfo), { flag: "wx", mode: 384 });
141
- return async () => {
142
+ return Result.ok(async () => {
142
143
  try {
143
144
  await unlink(lockPath);
144
145
  } catch {
145
146
  }
146
- };
147
+ });
147
148
  } catch (err) {
148
149
  if (err instanceof Error && "code" in err && err.code === "EEXIST") {
149
150
  if (await isLockStale(lockPath)) {
@@ -157,15 +158,26 @@ async function acquireLock(filePath) {
157
158
  await sleep(RETRY_DELAY_MS);
158
159
  continue;
159
160
  }
160
- throw err;
161
+ return Result.error(
162
+ new LockError(
163
+ `Failed to acquire lock: ${err instanceof Error ? err.message : String(err)}`,
164
+ lockPath,
165
+ err instanceof Error ? err : void 0
166
+ )
167
+ );
161
168
  }
162
169
  }
163
- throw new LockAcquisitionError(`Failed to acquire lock after ${String(MAX_RETRIES)} retries`, lockPath);
170
+ return Result.error(new LockError(`Failed to acquire lock after ${String(MAX_RETRIES)} retries`, lockPath));
164
171
  }
165
172
  async function withFileLock(filePath, fn) {
166
- const release = await acquireLock(filePath);
173
+ const lockResult = await acquireLock(filePath);
174
+ if (!lockResult.ok) {
175
+ return lockResult;
176
+ }
177
+ const release = lockResult.value;
167
178
  try {
168
- return await fn();
179
+ const value = await fn();
180
+ return Result.ok(value);
169
181
  } finally {
170
182
  await release();
171
183
  }
@@ -188,9 +200,11 @@ ${projectMarker}${message}
188
200
 
189
201
  `;
190
202
  const progressPath = getProgressFilePath(id);
191
- await withFileLock(progressPath, async () => {
192
- await appendToFile(progressPath, entry);
203
+ const lockResult = await withFileLock(progressPath, async () => {
204
+ const appendResult = await appendToFile(progressPath, entry);
205
+ if (!appendResult.ok) throw appendResult.error;
193
206
  });
207
+ if (!lockResult.ok) throw lockResult.error;
194
208
  }
195
209
  function isExecError(err) {
196
210
  return err instanceof Error && typeof err["status"] === "number";
@@ -251,18 +265,19 @@ async function logBaselines(options) {
251
265
  lines.push("");
252
266
  lines.push("---");
253
267
  lines.push("");
254
- await appendToFile(getProgressFilePath(sprintId), lines.join("\n"));
268
+ const appendResult = await appendToFile(getProgressFilePath(sprintId), lines.join("\n"));
269
+ if (!appendResult.ok) throw appendResult.error;
255
270
  }
256
271
  async function getProgress(sprintId) {
257
272
  const id = await resolveSprintId(sprintId);
258
- try {
259
- return await readTextFile(getProgressFilePath(id));
260
- } catch (err) {
261
- if (err instanceof FileNotFoundError) {
273
+ const result = await readTextFile(getProgressFilePath(id));
274
+ if (!result.ok) {
275
+ if (result.error instanceof StorageError && result.error.cause?.code === "ENOENT") {
262
276
  return "";
263
277
  }
264
- throw err;
278
+ throw result.error;
265
279
  }
280
+ return result.value;
266
281
  }
267
282
  function summarizeProgressForContext(progress, projectPath, maxEntries = 3) {
268
283
  const filtered = filterProgressByProject(progress, projectPath);
@@ -323,30 +338,6 @@ function filterProgressByProject(progress, projectPath) {
323
338
  }
324
339
 
325
340
  // src/store/sprint.ts
326
- var SprintNotFoundError = class extends Error {
327
- sprintId;
328
- constructor(sprintId) {
329
- super(`Sprint not found: ${sprintId}`);
330
- this.name = "SprintNotFoundError";
331
- this.sprintId = sprintId;
332
- }
333
- };
334
- var SprintStatusError = class extends Error {
335
- currentStatus;
336
- operation;
337
- constructor(message, currentStatus, operation) {
338
- super(message);
339
- this.name = "SprintStatusError";
340
- this.currentStatus = currentStatus;
341
- this.operation = operation;
342
- }
343
- };
344
- var NoCurrentSprintError = class extends Error {
345
- constructor() {
346
- super("No sprint specified and no current sprint set.");
347
- this.name = "NoCurrentSprintError";
348
- }
349
- };
350
341
  function assertSprintStatus(sprint, allowedStatuses, operation) {
351
342
  if (!allowedStatuses.includes(sprint.status)) {
352
343
  const statusText = allowedStatuses.join(" or ");
@@ -391,15 +382,21 @@ async function createSprint(name) {
391
382
  };
392
383
  const sprintDir = getSprintDir(id);
393
384
  await ensureDir(sprintDir);
394
- await writeValidatedJson(getSprintFilePath(id), sprint, SprintSchema);
395
- await writeValidatedJson(getTasksFilePath(id), [], TasksSchema);
396
- await appendToFile(getProgressFilePath(id), `# Sprint: ${displayName}
385
+ const writeSprintResult = await writeValidatedJson(getSprintFilePath(id), sprint, SprintSchema);
386
+ if (!writeSprintResult.ok) throw writeSprintResult.error;
387
+ const writeTasksResult = await writeValidatedJson(getTasksFilePath(id), [], TasksSchema);
388
+ if (!writeTasksResult.ok) throw writeTasksResult.error;
389
+ const appendResult = await appendToFile(
390
+ getProgressFilePath(id),
391
+ `# Sprint: ${displayName}
397
392
 
398
393
  Created: ${now}
399
394
 
400
395
  ---
401
396
 
402
- `);
397
+ `
398
+ );
399
+ if (!appendResult.ok) throw appendResult.error;
403
400
  return sprint;
404
401
  }
405
402
  async function findActiveSprint() {
@@ -411,25 +408,24 @@ async function getSprint(sprintId) {
411
408
  if (!await fileExists(sprintPath)) {
412
409
  throw new SprintNotFoundError(sprintId);
413
410
  }
414
- return readValidatedJson(sprintPath, SprintSchema);
411
+ const result = await readValidatedJson(sprintPath, SprintSchema);
412
+ if (!result.ok) throw result.error;
413
+ return result.value;
415
414
  }
416
415
  async function saveSprint(sprint) {
417
- await writeValidatedJson(getSprintFilePath(sprint.id), sprint, SprintSchema);
416
+ const result = await writeValidatedJson(getSprintFilePath(sprint.id), sprint, SprintSchema);
417
+ if (!result.ok) throw result.error;
418
418
  }
419
419
  async function listSprints() {
420
420
  const sprintsDir = getSprintsDir();
421
421
  const dirs = await listDirs(sprintsDir);
422
422
  const sprints = [];
423
423
  for (const dir of dirs) {
424
- try {
425
- const sprint = await getSprint(dir);
426
- sprints.push(sprint);
427
- } catch (err) {
428
- if (err instanceof ValidationError || err instanceof SprintNotFoundError) {
429
- continue;
430
- }
431
- throw err;
432
- }
424
+ const sprintPath = getSprintFilePath(dir);
425
+ if (!await fileExists(sprintPath)) continue;
426
+ const result = await readValidatedJson(sprintPath, SprintSchema);
427
+ if (!result.ok) continue;
428
+ sprints.push(result.value);
433
429
  }
434
430
  return sprints.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
435
431
  }
@@ -439,7 +435,9 @@ async function activateSprint(sprintId) {
439
435
  sprint.status = "active";
440
436
  sprint.activatedAt = (/* @__PURE__ */ new Date()).toISOString();
441
437
  await saveSprint(sprint);
442
- const tasks = await readValidatedJson(getTasksFilePath(sprintId), TasksSchema);
438
+ const tasksResult = await readValidatedJson(getTasksFilePath(sprintId), TasksSchema);
439
+ if (!tasksResult.ok) throw tasksResult.error;
440
+ const tasks = tasksResult.value;
443
441
  const projectPaths = tasks.map((t) => t.projectPath).filter((p) => !!p);
444
442
  if (projectPaths.length > 0) {
445
443
  await logBaselines({
@@ -503,9 +501,6 @@ export {
503
501
  logProgress,
504
502
  getProgress,
505
503
  summarizeProgressForContext,
506
- SprintNotFoundError,
507
- SprintStatusError,
508
- NoCurrentSprintError,
509
504
  assertSprintStatus,
510
505
  createSprint,
511
506
  findActiveSprint,