@beignet/cli 0.0.31 → 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 (107) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +70 -17
  3. package/dist/check.d.ts +2 -0
  4. package/dist/check.d.ts.map +1 -1
  5. package/dist/check.js +16 -0
  6. package/dist/check.js.map +1 -1
  7. package/dist/choices.d.ts +22 -2
  8. package/dist/choices.d.ts.map +1 -1
  9. package/dist/choices.js +62 -0
  10. package/dist/choices.js.map +1 -1
  11. package/dist/config.d.ts +2 -0
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +2 -0
  14. package/dist/config.js.map +1 -1
  15. package/dist/create-prompts.d.ts +6 -2
  16. package/dist/create-prompts.d.ts.map +1 -1
  17. package/dist/create-prompts.js +1 -1
  18. package/dist/create-prompts.js.map +1 -1
  19. package/dist/db.d.ts +5 -5
  20. package/dist/db.d.ts.map +1 -1
  21. package/dist/db.js +5 -5
  22. package/dist/db.js.map +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +386 -27
  25. package/dist/index.js.map +1 -1
  26. package/dist/inspect.d.ts +5 -0
  27. package/dist/inspect.d.ts.map +1 -1
  28. package/dist/inspect.js +805 -62
  29. package/dist/inspect.js.map +1 -1
  30. package/dist/make/inbox.d.ts.map +1 -1
  31. package/dist/make/inbox.js +5 -3
  32. package/dist/make/inbox.js.map +1 -1
  33. package/dist/make/payments.d.ts.map +1 -1
  34. package/dist/make/payments.js +58 -29
  35. package/dist/make/payments.js.map +1 -1
  36. package/dist/make/shared.d.ts +12 -3
  37. package/dist/make/shared.d.ts.map +1 -1
  38. package/dist/make/shared.js +52 -11
  39. package/dist/make/shared.js.map +1 -1
  40. package/dist/make/tenancy.d.ts.map +1 -1
  41. package/dist/make/tenancy.js +6 -4
  42. package/dist/make/tenancy.js.map +1 -1
  43. package/dist/make.d.ts.map +1 -1
  44. package/dist/make.js +136 -65
  45. package/dist/make.js.map +1 -1
  46. package/dist/outbox.d.ts +114 -1
  47. package/dist/outbox.d.ts.map +1 -1
  48. package/dist/outbox.js +190 -0
  49. package/dist/outbox.js.map +1 -1
  50. package/dist/preflight.d.ts +90 -0
  51. package/dist/preflight.d.ts.map +1 -0
  52. package/dist/preflight.js +423 -0
  53. package/dist/preflight.js.map +1 -0
  54. package/dist/provider-add.d.ts +10 -1
  55. package/dist/provider-add.d.ts.map +1 -1
  56. package/dist/provider-add.js +255 -16
  57. package/dist/provider-add.js.map +1 -1
  58. package/dist/task.d.ts.map +1 -1
  59. package/dist/task.js +2 -3
  60. package/dist/task.js.map +1 -1
  61. package/dist/templates/agents.d.ts.map +1 -1
  62. package/dist/templates/agents.js +6 -3
  63. package/dist/templates/agents.js.map +1 -1
  64. package/dist/templates/base.d.ts.map +1 -1
  65. package/dist/templates/base.js +5 -11
  66. package/dist/templates/base.js.map +1 -1
  67. package/dist/templates/index.d.ts.map +1 -1
  68. package/dist/templates/index.js +2 -0
  69. package/dist/templates/index.js.map +1 -1
  70. package/dist/templates/server.d.ts.map +1 -1
  71. package/dist/templates/server.js +10 -5
  72. package/dist/templates/server.js.map +1 -1
  73. package/dist/templates/shared.d.ts +5 -1
  74. package/dist/templates/shared.d.ts.map +1 -1
  75. package/dist/templates/shared.js +13 -1
  76. package/dist/templates/shared.js.map +1 -1
  77. package/dist/templates/testing.d.ts +5 -0
  78. package/dist/templates/testing.d.ts.map +1 -0
  79. package/dist/templates/testing.js +542 -0
  80. package/dist/templates/testing.js.map +1 -0
  81. package/dist/templates/todos.js +2 -2
  82. package/package.json +3 -2
  83. package/skills/app-structure/SKILL.md +12 -4
  84. package/src/check.ts +23 -0
  85. package/src/choices.ts +84 -0
  86. package/src/config.ts +4 -0
  87. package/src/create-prompts.ts +8 -2
  88. package/src/db.ts +12 -12
  89. package/src/index.ts +592 -37
  90. package/src/inspect.ts +1253 -156
  91. package/src/make/inbox.ts +5 -3
  92. package/src/make/payments.ts +58 -29
  93. package/src/make/shared.ts +87 -13
  94. package/src/make/tenancy.ts +6 -4
  95. package/src/make.ts +179 -65
  96. package/src/outbox.ts +363 -0
  97. package/src/preflight.ts +596 -0
  98. package/src/provider-add.ts +270 -16
  99. package/src/task.ts +2 -3
  100. package/src/templates/agents.ts +6 -3
  101. package/src/templates/base.ts +5 -13
  102. package/src/templates/index.ts +2 -0
  103. package/src/templates/server.ts +10 -5
  104. package/src/templates/shared.ts +15 -1
  105. package/src/templates/testing.ts +545 -0
  106. package/src/templates/todos.ts +2 -2
  107. package/src/test-helpers/generated-app.ts +10 -6
package/src/outbox.ts CHANGED
@@ -3,6 +3,10 @@ import path from "node:path";
3
3
  import type {
4
4
  DrainOutboxOptions,
5
5
  DrainOutboxResult,
6
+ OutboxAdminPort,
7
+ OutboxMessage,
8
+ OutboxMessageKind,
9
+ OutboxMessageStatus,
6
10
  OutboxRegistry,
7
11
  } from "@beignet/core/outbox";
8
12
  import { createJiti } from "jiti";
@@ -17,6 +21,61 @@ export type RunOutboxDrainOptions = {
17
21
  batchSize?: number;
18
22
  };
19
23
 
24
+ /**
25
+ * Options for listing outbox messages.
26
+ */
27
+ export type RunOutboxListOptions = {
28
+ cwd?: string;
29
+ modulePath?: string;
30
+ status?: OutboxMessageStatus;
31
+ kind?: OutboxMessageKind;
32
+ name?: string;
33
+ limit?: number;
34
+ };
35
+
36
+ /**
37
+ * Options for showing one outbox message.
38
+ */
39
+ export type RunOutboxShowOptions = {
40
+ cwd?: string;
41
+ modulePath?: string;
42
+ id: string;
43
+ };
44
+
45
+ /**
46
+ * Options for requeueing one dead-lettered outbox message.
47
+ */
48
+ export type RunOutboxRequeueOptions = {
49
+ cwd?: string;
50
+ modulePath?: string;
51
+ id: string;
52
+ availableAt?: Date;
53
+ resetAttempts?: boolean;
54
+ };
55
+
56
+ /**
57
+ * Options for purging dead-lettered outbox messages.
58
+ */
59
+ export type RunOutboxPurgeOptions = {
60
+ cwd?: string;
61
+ modulePath?: string;
62
+ before?: Date;
63
+ all?: boolean;
64
+ limit?: number;
65
+ dryRun?: boolean;
66
+ };
67
+
68
+ /**
69
+ * Options for pruning delivered outbox messages.
70
+ */
71
+ export type RunOutboxPruneOptions = {
72
+ cwd?: string;
73
+ modulePath?: string;
74
+ before: Date;
75
+ limit?: number;
76
+ dryRun?: boolean;
77
+ };
78
+
20
79
  /**
21
80
  * Result returned by `beignet outbox drain`.
22
81
  */
@@ -28,6 +87,53 @@ export type RunOutboxDrainResult = {
28
87
  durationMs: number;
29
88
  };
30
89
 
90
+ /**
91
+ * Result returned by `beignet outbox list`.
92
+ */
93
+ export type RunOutboxListResult = {
94
+ schemaVersion: 1;
95
+ cwd: string;
96
+ modulePath: string;
97
+ messages: readonly OutboxMessage[];
98
+ count: number;
99
+ durationMs: number;
100
+ };
101
+
102
+ /**
103
+ * Result returned by `beignet outbox show`.
104
+ */
105
+ export type RunOutboxShowResult = {
106
+ schemaVersion: 1;
107
+ cwd: string;
108
+ modulePath: string;
109
+ message: OutboxMessage;
110
+ durationMs: number;
111
+ };
112
+
113
+ /**
114
+ * Result returned by `beignet outbox requeue`.
115
+ */
116
+ export type RunOutboxRequeueResult = {
117
+ schemaVersion: 1;
118
+ cwd: string;
119
+ modulePath: string;
120
+ message: OutboxMessage;
121
+ durationMs: number;
122
+ };
123
+
124
+ /**
125
+ * Result returned by destructive outbox admin commands.
126
+ */
127
+ export type RunOutboxDeleteResult = {
128
+ schemaVersion: 1;
129
+ cwd: string;
130
+ modulePath: string;
131
+ dryRun: boolean;
132
+ matched: number;
133
+ deleted: number;
134
+ durationMs: number;
135
+ };
136
+
31
137
  type OutboxDrainLogger = {
32
138
  error(message: string, meta?: Record<string, unknown>): unknown;
33
139
  };
@@ -47,6 +153,7 @@ type OutboxDrainInstrumentation = {
47
153
 
48
154
  type OutboxDrainPorts = {
49
155
  outbox: DrainOutboxOptions["outbox"];
156
+ outboxAdmin?: OutboxAdminPort;
50
157
  eventBus?: DrainOutboxOptions["eventBus"];
51
158
  jobs?: DrainOutboxOptions["jobs"];
52
159
  logger?: OutboxDrainLogger;
@@ -65,13 +172,24 @@ type OutboxDrainContextArgs = {
65
172
  batchSize?: number;
66
173
  };
67
174
 
175
+ type OutboxAdminOperation = "list" | "show" | "requeue" | "purge" | "prune";
176
+
177
+ type OutboxAdminContextArgs = {
178
+ operation: OutboxAdminOperation;
179
+ };
180
+
68
181
  type OutboxModule = {
69
182
  outboxRegistry?: unknown;
70
183
  createOutboxDrainContext?: (args: OutboxDrainContextArgs) => unknown;
184
+ createOutboxAdminContext?: (args: OutboxAdminContextArgs) => unknown;
71
185
  stopOutboxDrainContext?: (
72
186
  ctx: OutboxDrainContext,
73
187
  args: OutboxDrainContextArgs,
74
188
  ) => unknown;
189
+ stopOutboxAdminContext?: (
190
+ ctx: OutboxDrainContext,
191
+ args: OutboxAdminContextArgs,
192
+ ) => unknown;
75
193
  };
76
194
 
77
195
  /**
@@ -138,6 +256,190 @@ export async function runOutboxDrain(
138
256
  }
139
257
  }
140
258
 
259
+ /**
260
+ * List outbox messages through the app's outbox admin port.
261
+ */
262
+ export async function runOutboxList(
263
+ options: RunOutboxListOptions = {},
264
+ ): Promise<RunOutboxListResult> {
265
+ return withOutboxAdminContext(options, "list", async ({ admin, base }) => {
266
+ const query = {
267
+ status: options.status,
268
+ kind: options.kind,
269
+ name: options.name,
270
+ };
271
+ const messages = await admin.listMessages({
272
+ ...query,
273
+ limit: options.limit,
274
+ });
275
+ const count = await admin.countMessages(query);
276
+
277
+ return {
278
+ ...base,
279
+ messages,
280
+ count,
281
+ };
282
+ });
283
+ }
284
+
285
+ /**
286
+ * Show one outbox message through the app's outbox admin port.
287
+ */
288
+ export async function runOutboxShow(
289
+ options: RunOutboxShowOptions,
290
+ ): Promise<RunOutboxShowResult> {
291
+ return withOutboxAdminContext(options, "show", async ({ admin, base }) => {
292
+ const message = await admin.getMessage(options.id);
293
+ if (!message) {
294
+ throw new Error(`Outbox message "${options.id}" does not exist.`);
295
+ }
296
+
297
+ return {
298
+ ...base,
299
+ message,
300
+ };
301
+ });
302
+ }
303
+
304
+ /**
305
+ * Requeue one dead-lettered outbox message.
306
+ */
307
+ export async function runOutboxRequeue(
308
+ options: RunOutboxRequeueOptions,
309
+ ): Promise<RunOutboxRequeueResult> {
310
+ return withOutboxAdminContext(options, "requeue", async ({ admin, base }) => {
311
+ const message = await admin.requeueMessage({
312
+ id: options.id,
313
+ availableAt: options.availableAt,
314
+ resetAttempts: options.resetAttempts,
315
+ });
316
+
317
+ return {
318
+ ...base,
319
+ message,
320
+ };
321
+ });
322
+ }
323
+
324
+ /**
325
+ * Purge dead-lettered outbox messages.
326
+ */
327
+ export async function runOutboxPurge(
328
+ options: RunOutboxPurgeOptions = {},
329
+ ): Promise<RunOutboxDeleteResult> {
330
+ if (!options.before && !options.all) {
331
+ throw new Error(
332
+ "Pass --before <date> to purge old dead-lettered messages, or pass --all to purge every dead-lettered message.",
333
+ );
334
+ }
335
+
336
+ return withOutboxAdminContext(options, "purge", async ({ admin, base }) => {
337
+ const query = {
338
+ status: "deadLettered" as const,
339
+ updatedBefore: options.before,
340
+ };
341
+ const matched = await admin.countMessages(query);
342
+ const result = options.dryRun
343
+ ? { deleted: dryRunDeleteCount(matched, options.limit) }
344
+ : await admin.purgeDeadLettered({
345
+ before: options.before,
346
+ limit: options.limit,
347
+ });
348
+
349
+ return {
350
+ ...base,
351
+ dryRun: Boolean(options.dryRun),
352
+ matched,
353
+ deleted: result.deleted,
354
+ };
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Prune delivered outbox messages.
360
+ */
361
+ export async function runOutboxPrune(
362
+ options: RunOutboxPruneOptions,
363
+ ): Promise<RunOutboxDeleteResult> {
364
+ return withOutboxAdminContext(options, "prune", async ({ admin, base }) => {
365
+ const query = {
366
+ status: "delivered" as const,
367
+ deliveredBefore: options.before,
368
+ };
369
+ const matched = await admin.countMessages(query);
370
+ const result = options.dryRun
371
+ ? { deleted: dryRunDeleteCount(matched, options.limit) }
372
+ : await admin.pruneDelivered({
373
+ before: options.before,
374
+ limit: options.limit,
375
+ });
376
+
377
+ return {
378
+ ...base,
379
+ dryRun: Boolean(options.dryRun),
380
+ matched,
381
+ deleted: result.deleted,
382
+ };
383
+ });
384
+ }
385
+
386
+ async function withOutboxAdminContext<T extends object>(
387
+ options: { cwd?: string; modulePath?: string },
388
+ operation: OutboxAdminOperation,
389
+ run: (args: {
390
+ admin: OutboxAdminPort;
391
+ base: {
392
+ schemaVersion: 1;
393
+ cwd: string;
394
+ modulePath: string;
395
+ };
396
+ }) => Promise<T>,
397
+ ): Promise<T & { durationMs: number }> {
398
+ const cwd = path.resolve(options.cwd ?? process.cwd());
399
+ const config = await loadBeignetConfig(cwd);
400
+ const modulePath = normalizePath(options.modulePath ?? config.paths.outbox);
401
+ const startedAt = performance.now();
402
+ const outboxModule = await loadOutboxModule(cwd, modulePath);
403
+ const contextArgs = { operation };
404
+ const rawContext = outboxModule.createOutboxAdminContext
405
+ ? await outboxModule.createOutboxAdminContext(contextArgs)
406
+ : await createFallbackOutboxAdminContext(outboxModule);
407
+ const ctx = assertOutboxAdminContext(rawContext, modulePath);
408
+ const admin = resolveOutboxAdminPort(ctx, modulePath);
409
+
410
+ try {
411
+ const result = await run({
412
+ admin,
413
+ base: {
414
+ schemaVersion: 1,
415
+ cwd,
416
+ modulePath,
417
+ },
418
+ });
419
+ return {
420
+ ...result,
421
+ durationMs: Math.round(performance.now() - startedAt),
422
+ };
423
+ } finally {
424
+ if (outboxModule.stopOutboxAdminContext) {
425
+ await outboxModule.stopOutboxAdminContext(ctx, contextArgs);
426
+ } else {
427
+ await outboxModule.stopOutboxDrainContext?.(ctx, {
428
+ registry: maybeOutboxRegistry(outboxModule.outboxRegistry),
429
+ });
430
+ }
431
+ }
432
+ }
433
+
434
+ async function createFallbackOutboxAdminContext(
435
+ outboxModule: OutboxModule,
436
+ ): Promise<unknown> {
437
+ if (!outboxModule.createOutboxDrainContext) return undefined;
438
+ return outboxModule.createOutboxDrainContext({
439
+ registry: maybeOutboxRegistry(outboxModule.outboxRegistry),
440
+ });
441
+ }
442
+
141
443
  async function loadOutboxModule(
142
444
  cwd: string,
143
445
  modulePath: string,
@@ -197,6 +499,19 @@ function assertOutboxDrainContext(
197
499
  return ctx as OutboxDrainContext;
198
500
  }
199
501
 
502
+ function assertOutboxAdminContext(
503
+ ctx: unknown,
504
+ modulePath: string,
505
+ ): OutboxDrainContext {
506
+ if (!isRecord(ctx) || !isRecord(ctx.ports)) {
507
+ throw new Error(
508
+ `${modulePath} must export createOutboxAdminContext or createOutboxDrainContext that returns an app context with ports.outboxAdmin.`,
509
+ );
510
+ }
511
+
512
+ return ctx as OutboxDrainContext;
513
+ }
514
+
200
515
  function isOutboxPort(value: unknown): value is DrainOutboxOptions["outbox"] {
201
516
  return (
202
517
  isRecord(value) &&
@@ -207,6 +522,48 @@ function isOutboxPort(value: unknown): value is DrainOutboxOptions["outbox"] {
207
522
  );
208
523
  }
209
524
 
525
+ function isOutboxAdminPort(value: unknown): value is OutboxAdminPort {
526
+ return (
527
+ isRecord(value) &&
528
+ typeof value.listMessages === "function" &&
529
+ typeof value.countMessages === "function" &&
530
+ typeof value.getMessage === "function" &&
531
+ typeof value.requeueMessage === "function" &&
532
+ typeof value.purgeDeadLettered === "function" &&
533
+ typeof value.pruneDelivered === "function"
534
+ );
535
+ }
536
+
537
+ function resolveOutboxAdminPort(
538
+ ctx: OutboxDrainContext,
539
+ modulePath: string,
540
+ ): OutboxAdminPort {
541
+ if (isOutboxAdminPort(ctx.ports.outboxAdmin)) return ctx.ports.outboxAdmin;
542
+ if (isOutboxAdminPort(ctx.ports.outbox)) return ctx.ports.outbox;
543
+
544
+ throw new Error(
545
+ `${modulePath} createOutboxAdminContext must return ports.outboxAdmin, or ports.outbox must implement OutboxAdminPort.`,
546
+ );
547
+ }
548
+
549
+ function maybeOutboxRegistry(registry: unknown): OutboxRegistry {
550
+ if (
551
+ isRecord(registry) &&
552
+ registry.events instanceof Map &&
553
+ registry.jobs instanceof Map
554
+ ) {
555
+ return {
556
+ events: registry.events,
557
+ jobs: registry.jobs,
558
+ } as unknown as OutboxRegistry;
559
+ }
560
+
561
+ return {
562
+ events: new Map(),
563
+ jobs: new Map(),
564
+ };
565
+ }
566
+
210
567
  async function recordOutboxDrain(
211
568
  ctx: OutboxDrainContext,
212
569
  result: DrainOutboxResult,
@@ -235,6 +592,12 @@ function assertPositiveInteger(name: string, value: number): void {
235
592
  }
236
593
  }
237
594
 
595
+ function dryRunDeleteCount(matched: number, limit: number | undefined): number {
596
+ if (limit === undefined) return matched;
597
+ assertPositiveInteger("limit", limit);
598
+ return Math.min(matched, limit);
599
+ }
600
+
238
601
  function isRecord(value: unknown): value is Record<string, unknown> {
239
602
  return typeof value === "object" && value !== null;
240
603
  }