@beignet/cli 0.0.32 → 0.0.33

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +26 -16
  3. package/dist/choices.d.ts +3 -3
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +2 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/config.d.ts +1 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +1 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/db.js +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +279 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/inspect.d.ts.map +1 -1
  16. package/dist/inspect.js +797 -58
  17. package/dist/inspect.js.map +1 -1
  18. package/dist/make/inbox.d.ts.map +1 -1
  19. package/dist/make/inbox.js +5 -3
  20. package/dist/make/inbox.js.map +1 -1
  21. package/dist/make/payments.d.ts.map +1 -1
  22. package/dist/make/payments.js +58 -29
  23. package/dist/make/payments.js.map +1 -1
  24. package/dist/make/shared.d.ts +11 -2
  25. package/dist/make/shared.d.ts.map +1 -1
  26. package/dist/make/shared.js +44 -10
  27. package/dist/make/shared.js.map +1 -1
  28. package/dist/make/tenancy.d.ts.map +1 -1
  29. package/dist/make/tenancy.js +6 -4
  30. package/dist/make/tenancy.js.map +1 -1
  31. package/dist/make.d.ts.map +1 -1
  32. package/dist/make.js +129 -55
  33. package/dist/make.js.map +1 -1
  34. package/dist/outbox.d.ts +114 -1
  35. package/dist/outbox.d.ts.map +1 -1
  36. package/dist/outbox.js +190 -0
  37. package/dist/outbox.js.map +1 -1
  38. package/dist/preflight.js +1 -1
  39. package/dist/preflight.js.map +1 -1
  40. package/dist/provider-add.d.ts.map +1 -1
  41. package/dist/provider-add.js +46 -0
  42. package/dist/provider-add.js.map +1 -1
  43. package/dist/task.d.ts.map +1 -1
  44. package/dist/task.js +2 -3
  45. package/dist/task.js.map +1 -1
  46. package/dist/templates/agents.d.ts.map +1 -1
  47. package/dist/templates/agents.js +6 -3
  48. package/dist/templates/agents.js.map +1 -1
  49. package/dist/templates/base.js +3 -3
  50. package/dist/templates/base.js.map +1 -1
  51. package/dist/templates/index.d.ts.map +1 -1
  52. package/dist/templates/index.js +2 -0
  53. package/dist/templates/index.js.map +1 -1
  54. package/dist/templates/server.d.ts.map +1 -1
  55. package/dist/templates/server.js +6 -1
  56. package/dist/templates/server.js.map +1 -1
  57. package/dist/templates/shared.d.ts +3 -1
  58. package/dist/templates/shared.d.ts.map +1 -1
  59. package/dist/templates/shared.js +11 -1
  60. package/dist/templates/shared.js.map +1 -1
  61. package/dist/templates/testing.d.ts +5 -0
  62. package/dist/templates/testing.d.ts.map +1 -0
  63. package/dist/templates/testing.js +542 -0
  64. package/dist/templates/testing.js.map +1 -0
  65. package/dist/templates/todos.js +2 -2
  66. package/package.json +3 -2
  67. package/skills/app-structure/SKILL.md +12 -4
  68. package/src/choices.ts +3 -0
  69. package/src/config.ts +2 -0
  70. package/src/db.ts +1 -1
  71. package/src/index.ts +437 -1
  72. package/src/inspect.ts +1247 -154
  73. package/src/make/inbox.ts +5 -3
  74. package/src/make/payments.ts +58 -29
  75. package/src/make/shared.ts +78 -11
  76. package/src/make/tenancy.ts +6 -4
  77. package/src/make.ts +172 -54
  78. package/src/outbox.ts +363 -0
  79. package/src/preflight.ts +1 -1
  80. package/src/provider-add.ts +47 -0
  81. package/src/task.ts +2 -3
  82. package/src/templates/agents.ts +6 -3
  83. package/src/templates/base.ts +3 -3
  84. package/src/templates/index.ts +2 -0
  85. package/src/templates/server.ts +6 -1
  86. package/src/templates/shared.ts +13 -1
  87. package/src/templates/testing.ts +545 -0
  88. package/src/templates/todos.ts +2 -2
  89. package/src/test-helpers/generated-app.ts +10 -6
package/src/index.ts CHANGED
@@ -162,6 +162,60 @@ type OutboxDrainFlags = {
162
162
  batchSize?: number;
163
163
  };
164
164
 
165
+ type OutboxMessageStatus = "pending" | "claimed" | "delivered" | "deadLettered";
166
+
167
+ type OutboxMessageKind = "event" | "job";
168
+
169
+ const outboxMessageStatusChoices = [
170
+ "pending",
171
+ "claimed",
172
+ "delivered",
173
+ "deadLettered",
174
+ ] as const satisfies readonly OutboxMessageStatus[];
175
+
176
+ const outboxMessageKindChoices = [
177
+ "event",
178
+ "job",
179
+ ] as const satisfies readonly OutboxMessageKind[];
180
+
181
+ type OutboxListFlags = {
182
+ json?: boolean;
183
+ module?: string;
184
+ status?: OutboxMessageStatus;
185
+ kind?: OutboxMessageKind;
186
+ name?: string;
187
+ limit?: number;
188
+ };
189
+
190
+ type OutboxShowFlags = {
191
+ json?: boolean;
192
+ module?: string;
193
+ };
194
+
195
+ type OutboxRequeueFlags = {
196
+ json?: boolean;
197
+ module?: string;
198
+ availableAt?: Date;
199
+ resetAttempts?: boolean;
200
+ };
201
+
202
+ type OutboxPurgeFlags = {
203
+ json?: boolean;
204
+ module?: string;
205
+ before?: Date;
206
+ all?: boolean;
207
+ limit?: number;
208
+ dryRun?: boolean;
209
+ };
210
+
211
+ type OutboxPruneFlags = {
212
+ json?: boolean;
213
+ module?: string;
214
+ before?: Date;
215
+ limit?: number;
216
+ dryRun?: boolean;
217
+ };
218
+
165
219
  type ScheduleRunFlags = {
166
220
  json?: boolean;
167
221
  module?: string;
@@ -215,6 +269,13 @@ const parsePositiveInteger = (input: string): number => {
215
269
  }
216
270
  return value;
217
271
  };
272
+ const parseDate = (input: string): Date => {
273
+ const value = new Date(input);
274
+ if (Number.isNaN(value.getTime())) {
275
+ throw new Error("Expected a valid date.");
276
+ }
277
+ return value;
278
+ };
218
279
 
219
280
  const forceFlag = {
220
281
  kind: "boolean",
@@ -230,6 +291,13 @@ const dryRunFlag = {
230
291
  brief: "Preview generated changes without writing files.",
231
292
  } as const;
232
293
 
294
+ const operationDryRunFlag = {
295
+ kind: "boolean",
296
+ optional: true,
297
+ withNegated: false,
298
+ brief: "Report matching records without deleting them.",
299
+ } as const;
300
+
233
301
  const jsonFlag = {
234
302
  kind: "boolean",
235
303
  optional: true,
@@ -245,6 +313,14 @@ const parsedStringFlag = (brief: string) =>
245
313
  brief,
246
314
  }) as const;
247
315
 
316
+ const parsedDateFlag = (brief: string) =>
317
+ ({
318
+ kind: "parsed",
319
+ parse: parseDate,
320
+ optional: true,
321
+ brief,
322
+ }) as const;
323
+
248
324
  const cwdFlag = parsedStringFlag(
249
325
  "App directory to inspect. Defaults to the current working directory.",
250
326
  );
@@ -336,6 +412,95 @@ const outboxDrainFlagParameters = {
336
412
  },
337
413
  } satisfies FlagParametersForType<OutboxDrainFlags, CliContext>;
338
414
 
415
+ const outboxListFlagParameters = {
416
+ json: jsonFlag,
417
+ module: parsedStringFlag(
418
+ "Outbox registry module. Defaults to server/outbox.ts.",
419
+ ),
420
+ status: {
421
+ kind: "enum",
422
+ values: outboxMessageStatusChoices,
423
+ optional: true,
424
+ brief: "Filter by outbox message status.",
425
+ },
426
+ kind: {
427
+ kind: "enum",
428
+ values: outboxMessageKindChoices,
429
+ optional: true,
430
+ brief: "Filter by outbox message kind.",
431
+ },
432
+ name: parsedStringFlag("Filter by event or job name."),
433
+ limit: {
434
+ kind: "parsed",
435
+ parse: parsePositiveInteger,
436
+ optional: true,
437
+ brief: "Maximum messages to return. Defaults to 50.",
438
+ },
439
+ } satisfies FlagParametersForType<OutboxListFlags, CliContext>;
440
+
441
+ const outboxShowFlagParameters = {
442
+ json: jsonFlag,
443
+ module: parsedStringFlag(
444
+ "Outbox registry module. Defaults to server/outbox.ts.",
445
+ ),
446
+ } satisfies FlagParametersForType<OutboxShowFlags, CliContext>;
447
+
448
+ const outboxRequeueFlagParameters = {
449
+ json: jsonFlag,
450
+ module: parsedStringFlag(
451
+ "Outbox registry module. Defaults to server/outbox.ts.",
452
+ ),
453
+ availableAt: parsedDateFlag(
454
+ "Earliest timestamp the requeued message may be claimed. Defaults to now.",
455
+ ),
456
+ resetAttempts: {
457
+ kind: "boolean",
458
+ optional: true,
459
+ withNegated: false,
460
+ brief: "Reset attempts to zero before requeueing.",
461
+ },
462
+ } satisfies FlagParametersForType<OutboxRequeueFlags, CliContext>;
463
+
464
+ const outboxPurgeFlagParameters = {
465
+ json: jsonFlag,
466
+ module: parsedStringFlag(
467
+ "Outbox registry module. Defaults to server/outbox.ts.",
468
+ ),
469
+ before: parsedDateFlag(
470
+ "Only purge dead-lettered messages last updated before this timestamp.",
471
+ ),
472
+ all: {
473
+ kind: "boolean",
474
+ optional: true,
475
+ withNegated: false,
476
+ brief: "Purge every dead-lettered message when --before is omitted.",
477
+ },
478
+ limit: {
479
+ kind: "parsed",
480
+ parse: parsePositiveInteger,
481
+ optional: true,
482
+ brief: "Maximum messages to purge.",
483
+ },
484
+ dryRun: operationDryRunFlag,
485
+ } satisfies FlagParametersForType<OutboxPurgeFlags, CliContext>;
486
+
487
+ const outboxPruneFlagParameters = {
488
+ json: jsonFlag,
489
+ module: parsedStringFlag(
490
+ "Outbox registry module. Defaults to server/outbox.ts.",
491
+ ),
492
+ before: parsedDateFlag(
493
+ "Prune delivered messages delivered before this timestamp.",
494
+ ),
495
+ limit: {
496
+ kind: "parsed",
497
+ parse: parsePositiveInteger,
498
+ optional: true,
499
+ brief: "Maximum messages to prune.",
500
+ },
501
+ dryRun: operationDryRunFlag,
502
+ } satisfies FlagParametersForType<OutboxPruneFlags, CliContext>;
503
+
339
504
  const scheduleRunFlagParameters = {
340
505
  json: jsonFlag,
341
506
  module: parsedStringFlag(
@@ -365,6 +530,17 @@ const namePositional = {
365
530
  ],
366
531
  } as const;
367
532
 
533
+ const idPositional = {
534
+ kind: "tuple",
535
+ parameters: [
536
+ {
537
+ parse: parseString,
538
+ placeholder: "id",
539
+ brief: "Outbox message ID.",
540
+ },
541
+ ],
542
+ } as const;
543
+
368
544
  const providerPresetPositional = {
369
545
  kind: "tuple",
370
546
  parameters: [
@@ -1048,12 +1224,178 @@ const outboxDrainCommand = buildCommand<OutboxDrainFlags, [], CliContext>({
1048
1224
  },
1049
1225
  });
1050
1226
 
1227
+ const outboxListCommand = buildCommand<OutboxListFlags, [], CliContext>({
1228
+ docs: {
1229
+ brief: "List outbox messages for operational inspection.",
1230
+ },
1231
+ parameters: {
1232
+ flags: outboxListFlagParameters,
1233
+ },
1234
+ loader: async () => {
1235
+ const { runOutboxList } = await import("./outbox.js");
1236
+
1237
+ return async function runList(this: CliContext, flags: OutboxListFlags) {
1238
+ const result = await runOutboxList({
1239
+ modulePath: flags.module,
1240
+ status: flags.status,
1241
+ kind: flags.kind,
1242
+ name: flags.name,
1243
+ limit: flags.limit,
1244
+ });
1245
+
1246
+ writeOutput(
1247
+ this,
1248
+ flags.json
1249
+ ? JSON.stringify(result, null, 2)
1250
+ : outboxListNextSteps(result),
1251
+ );
1252
+ };
1253
+ },
1254
+ });
1255
+
1256
+ const outboxShowCommand = buildCommand<OutboxShowFlags, [string], CliContext>({
1257
+ docs: {
1258
+ brief: "Show one outbox message.",
1259
+ },
1260
+ parameters: {
1261
+ flags: outboxShowFlagParameters,
1262
+ positional: idPositional,
1263
+ },
1264
+ loader: async () => {
1265
+ const { runOutboxShow } = await import("./outbox.js");
1266
+
1267
+ return async function runShow(
1268
+ this: CliContext,
1269
+ flags: OutboxShowFlags,
1270
+ id: string,
1271
+ ) {
1272
+ const result = await runOutboxShow({
1273
+ modulePath: flags.module,
1274
+ id,
1275
+ });
1276
+
1277
+ writeOutput(
1278
+ this,
1279
+ flags.json
1280
+ ? JSON.stringify(result, null, 2)
1281
+ : outboxShowNextSteps(result),
1282
+ );
1283
+ };
1284
+ },
1285
+ });
1286
+
1287
+ const outboxRequeueCommand = buildCommand<
1288
+ OutboxRequeueFlags,
1289
+ [string],
1290
+ CliContext
1291
+ >({
1292
+ docs: {
1293
+ brief: "Return one dead-lettered outbox message to pending state.",
1294
+ },
1295
+ parameters: {
1296
+ flags: outboxRequeueFlagParameters,
1297
+ positional: idPositional,
1298
+ },
1299
+ loader: async () => {
1300
+ const { runOutboxRequeue } = await import("./outbox.js");
1301
+
1302
+ return async function runRequeue(
1303
+ this: CliContext,
1304
+ flags: OutboxRequeueFlags,
1305
+ id: string,
1306
+ ) {
1307
+ const result = await runOutboxRequeue({
1308
+ modulePath: flags.module,
1309
+ id,
1310
+ availableAt: flags.availableAt,
1311
+ resetAttempts: Boolean(flags.resetAttempts),
1312
+ });
1313
+
1314
+ writeOutput(
1315
+ this,
1316
+ flags.json
1317
+ ? JSON.stringify(result, null, 2)
1318
+ : outboxRequeueNextSteps(result),
1319
+ );
1320
+ };
1321
+ },
1322
+ });
1323
+
1324
+ const outboxPurgeCommand = buildCommand<OutboxPurgeFlags, [], CliContext>({
1325
+ docs: {
1326
+ brief: "Delete dead-lettered outbox messages.",
1327
+ },
1328
+ parameters: {
1329
+ flags: outboxPurgeFlagParameters,
1330
+ },
1331
+ loader: async () => {
1332
+ const { runOutboxPurge } = await import("./outbox.js");
1333
+
1334
+ return async function runPurge(this: CliContext, flags: OutboxPurgeFlags) {
1335
+ const result = await runOutboxPurge({
1336
+ modulePath: flags.module,
1337
+ before: flags.before,
1338
+ all: Boolean(flags.all),
1339
+ limit: flags.limit,
1340
+ dryRun: Boolean(flags.dryRun),
1341
+ });
1342
+
1343
+ writeOutput(
1344
+ this,
1345
+ flags.json
1346
+ ? JSON.stringify(result, null, 2)
1347
+ : outboxDeleteNextSteps(
1348
+ "Purged dead-lettered outbox messages",
1349
+ result,
1350
+ ),
1351
+ );
1352
+ };
1353
+ },
1354
+ });
1355
+
1356
+ const outboxPruneCommand = buildCommand<OutboxPruneFlags, [], CliContext>({
1357
+ docs: {
1358
+ brief: "Delete delivered outbox messages older than a retention cutoff.",
1359
+ },
1360
+ parameters: {
1361
+ flags: outboxPruneFlagParameters,
1362
+ },
1363
+ loader: async () => {
1364
+ const { runOutboxPrune } = await import("./outbox.js");
1365
+
1366
+ return async function runPrune(this: CliContext, flags: OutboxPruneFlags) {
1367
+ if (!flags.before) {
1368
+ throw new Error("Pass --before <date> to prune delivered messages.");
1369
+ }
1370
+
1371
+ const result = await runOutboxPrune({
1372
+ modulePath: flags.module,
1373
+ before: flags.before,
1374
+ limit: flags.limit,
1375
+ dryRun: Boolean(flags.dryRun),
1376
+ });
1377
+
1378
+ writeOutput(
1379
+ this,
1380
+ flags.json
1381
+ ? JSON.stringify(result, null, 2)
1382
+ : outboxDeleteNextSteps("Pruned delivered outbox messages", result),
1383
+ );
1384
+ };
1385
+ },
1386
+ });
1387
+
1051
1388
  const outboxRoutes = buildRouteMap({
1052
1389
  docs: {
1053
1390
  brief: "Run Beignet outbox operations.",
1054
1391
  },
1055
1392
  routes: {
1056
1393
  drain: outboxDrainCommand,
1394
+ list: outboxListCommand,
1395
+ show: outboxShowCommand,
1396
+ requeue: outboxRequeueCommand,
1397
+ purge: outboxPurgeCommand,
1398
+ prune: outboxPruneCommand,
1057
1399
  },
1058
1400
  });
1059
1401
 
@@ -1758,6 +2100,37 @@ type OutboxDrainNextStepsResult = {
1758
2100
  };
1759
2101
  };
1760
2102
 
2103
+ type OutboxAdminMessage = {
2104
+ id: string;
2105
+ kind: string;
2106
+ name: string;
2107
+ status: string;
2108
+ attempts: number;
2109
+ maxAttempts: number;
2110
+ availableAt: Date | string;
2111
+ deliveredAt: Date | string | null;
2112
+ updatedAt: Date | string;
2113
+ lastError: { name?: string; message: string } | null;
2114
+ };
2115
+
2116
+ type OutboxListNextStepsResult = {
2117
+ durationMs: number;
2118
+ messages: readonly OutboxAdminMessage[];
2119
+ count: number;
2120
+ };
2121
+
2122
+ type OutboxMessageNextStepsResult = {
2123
+ durationMs: number;
2124
+ message: OutboxAdminMessage;
2125
+ };
2126
+
2127
+ type OutboxDeleteNextStepsResult = {
2128
+ durationMs: number;
2129
+ dryRun: boolean;
2130
+ matched: number;
2131
+ deleted: number;
2132
+ };
2133
+
1761
2134
  type ScheduleRunNextStepsResult = {
1762
2135
  name: string;
1763
2136
  durationMs: number;
@@ -1794,6 +2167,69 @@ Result:
1794
2167
  deadLettered: ${result.result.deadLettered}`;
1795
2168
  }
1796
2169
 
2170
+ function formatOutboxDate(value: Date | string | null | undefined): string {
2171
+ if (!value) return "none";
2172
+ return value instanceof Date ? value.toISOString() : value;
2173
+ }
2174
+
2175
+ function outboxMessageLine(message: OutboxAdminMessage): string {
2176
+ return `${message.id} ${message.status} ${message.kind} ${message.name} attempts ${message.attempts}/${message.maxAttempts} updated ${formatOutboxDate(message.updatedAt)}`;
2177
+ }
2178
+
2179
+ function outboxListNextSteps(result: OutboxListNextStepsResult): string {
2180
+ const messages =
2181
+ result.messages.length === 0
2182
+ ? " none"
2183
+ : result.messages
2184
+ .map((message) => ` ${outboxMessageLine(message)}`)
2185
+ .join("\n");
2186
+
2187
+ return `Listed ${result.messages.length} of ${result.count} matching outbox messages in ${result.durationMs}ms
2188
+
2189
+ Messages:
2190
+ ${messages}`;
2191
+ }
2192
+
2193
+ function outboxShowNextSteps(result: OutboxMessageNextStepsResult): string {
2194
+ const message = result.message;
2195
+ const error = message.lastError
2196
+ ? `${message.lastError.name ?? "Error"}: ${message.lastError.message}`
2197
+ : "none";
2198
+
2199
+ return `Outbox message ${message.id} (${message.status}) in ${result.durationMs}ms
2200
+
2201
+ Message:
2202
+ kind: ${message.kind}
2203
+ name: ${message.name}
2204
+ attempts: ${message.attempts}/${message.maxAttempts}
2205
+ availableAt: ${formatOutboxDate(message.availableAt)}
2206
+ deliveredAt: ${formatOutboxDate(message.deliveredAt)}
2207
+ updatedAt: ${formatOutboxDate(message.updatedAt)}
2208
+ lastError: ${error}`;
2209
+ }
2210
+
2211
+ function outboxRequeueNextSteps(result: OutboxMessageNextStepsResult): string {
2212
+ return `Requeued outbox message ${result.message.id} in ${result.durationMs}ms
2213
+
2214
+ Message:
2215
+ status: ${result.message.status}
2216
+ attempts: ${result.message.attempts}/${result.message.maxAttempts}
2217
+ availableAt: ${formatOutboxDate(result.message.availableAt)}`;
2218
+ }
2219
+
2220
+ function outboxDeleteNextSteps(
2221
+ label: string,
2222
+ result: OutboxDeleteNextStepsResult,
2223
+ ): string {
2224
+ const action = result.dryRun ? "Would delete" : "Deleted";
2225
+
2226
+ return `${label} in ${result.durationMs}ms
2227
+
2228
+ Result:
2229
+ matched: ${result.matched}
2230
+ ${action}: ${result.deleted}`;
2231
+ }
2232
+
1797
2233
  function scheduleRunNextSteps(result: ScheduleRunNextStepsResult): string {
1798
2234
  return `Ran schedule ${result.name} in ${result.durationMs}ms
1799
2235
 
@@ -2103,7 +2539,7 @@ function makeOutboxNextSteps(result: MakeNextStepsResult): string {
2103
2539
  function makeListenerNextSteps(result: MakeNextStepsResult): string {
2104
2540
  return makeNextSteps(result, "listener", [
2105
2541
  "Replace the starter handler with the event reaction.",
2106
- "Register the listener collection from infrastructure startup.",
2542
+ "Register the listener collection from server provider wiring.",
2107
2543
  ]);
2108
2544
  }
2109
2545