@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/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { realpathSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { buildApplication, buildCommand, buildRouteMap, proposeCompletions, run, text_en, } from "@stricli/core";
5
- import { completionShellChoices, databaseChoices, databaseStartCommand, integrationChoices, makeFeatureAddonChoices, makeFeatureRecipeChoices, packageManagerChoices, providerPresetChoices, templateChoices, } from "./choices.js";
5
+ import { completionShellChoices, createIntegrationChoices, databaseChoices, databaseStartCommand, makeFeatureAddonChoices, makeFeatureRecipeChoices, packageManagerChoices, providerPresetChoices, splitCreateIntegrations, templateChoices, } from "./choices.js";
6
6
  import { getCliVersion } from "./version.js";
7
7
  const outputFormatChoices = [
8
8
  "human",
@@ -19,6 +19,16 @@ const databaseSchemaTableChoices = [
19
19
  "idempotency",
20
20
  "outbox",
21
21
  ];
22
+ const outboxMessageStatusChoices = [
23
+ "pending",
24
+ "claimed",
25
+ "delivered",
26
+ "deadLettered",
27
+ ];
28
+ const outboxMessageKindChoices = [
29
+ "event",
30
+ "job",
31
+ ];
22
32
  const parseString = (input) => input;
23
33
  const parseProviderPreset = (input) => {
24
34
  if (providerPresetChoices.includes(input)) {
@@ -33,6 +43,13 @@ const parsePositiveInteger = (input) => {
33
43
  }
34
44
  return value;
35
45
  };
46
+ const parseDate = (input) => {
47
+ const value = new Date(input);
48
+ if (Number.isNaN(value.getTime())) {
49
+ throw new Error("Expected a valid date.");
50
+ }
51
+ return value;
52
+ };
36
53
  const forceFlag = {
37
54
  kind: "boolean",
38
55
  optional: true,
@@ -45,6 +62,12 @@ const dryRunFlag = {
45
62
  withNegated: false,
46
63
  brief: "Preview generated changes without writing files.",
47
64
  };
65
+ const operationDryRunFlag = {
66
+ kind: "boolean",
67
+ optional: true,
68
+ withNegated: false,
69
+ brief: "Report matching records without deleting them.",
70
+ };
48
71
  const jsonFlag = {
49
72
  kind: "boolean",
50
73
  optional: true,
@@ -57,6 +80,12 @@ const parsedStringFlag = (brief) => ({
57
80
  optional: true,
58
81
  brief,
59
82
  });
83
+ const parsedDateFlag = (brief) => ({
84
+ kind: "parsed",
85
+ parse: parseDate,
86
+ optional: true,
87
+ brief,
88
+ });
60
89
  const cwdFlag = parsedStringFlag("App directory to inspect. Defaults to the current working directory.");
61
90
  const formatFlag = {
62
91
  kind: "enum",
@@ -91,22 +120,22 @@ const dbFlagParameters = {
91
120
  json: jsonFlag,
92
121
  dryRun: dryRunFlag,
93
122
  };
94
- const dbSchemaGenerateFlagParameters = {
123
+ const dbSchemaSyncFlagParameters = {
95
124
  ...dbFlagParameters,
96
125
  dialect: {
97
126
  kind: "enum",
98
127
  values: databaseSchemaDialectChoices,
99
128
  optional: true,
100
- brief: "Drizzle dialect to generate for. Inferred from server/providers.ts when omitted.",
129
+ brief: "Drizzle dialect to sync. Inferred from server/providers.ts when omitted.",
101
130
  },
102
131
  tables: {
103
132
  kind: "enum",
104
133
  values: databaseSchemaTableChoices,
105
134
  optional: true,
106
135
  variadic: ",",
107
- brief: "Provider tables to generate as a comma-separated list. Defaults to audit,idempotency,outbox.",
136
+ brief: "Provider tables to sync as a comma-separated list. Defaults to audit,idempotency,outbox.",
108
137
  },
109
- output: parsedStringFlag("Generated schema file. Defaults to infra/db/schema/beignet.ts."),
138
+ output: parsedStringFlag("Synced schema file. Defaults to infra/db/schema/beignet.ts."),
110
139
  };
111
140
  const taskRunFlagParameters = {
112
141
  json: jsonFlag,
@@ -124,6 +153,74 @@ const outboxDrainFlagParameters = {
124
153
  brief: "Maximum messages to claim in one drain pass.",
125
154
  },
126
155
  };
156
+ const outboxListFlagParameters = {
157
+ json: jsonFlag,
158
+ module: parsedStringFlag("Outbox registry module. Defaults to server/outbox.ts."),
159
+ status: {
160
+ kind: "enum",
161
+ values: outboxMessageStatusChoices,
162
+ optional: true,
163
+ brief: "Filter by outbox message status.",
164
+ },
165
+ kind: {
166
+ kind: "enum",
167
+ values: outboxMessageKindChoices,
168
+ optional: true,
169
+ brief: "Filter by outbox message kind.",
170
+ },
171
+ name: parsedStringFlag("Filter by event or job name."),
172
+ limit: {
173
+ kind: "parsed",
174
+ parse: parsePositiveInteger,
175
+ optional: true,
176
+ brief: "Maximum messages to return. Defaults to 50.",
177
+ },
178
+ };
179
+ const outboxShowFlagParameters = {
180
+ json: jsonFlag,
181
+ module: parsedStringFlag("Outbox registry module. Defaults to server/outbox.ts."),
182
+ };
183
+ const outboxRequeueFlagParameters = {
184
+ json: jsonFlag,
185
+ module: parsedStringFlag("Outbox registry module. Defaults to server/outbox.ts."),
186
+ availableAt: parsedDateFlag("Earliest timestamp the requeued message may be claimed. Defaults to now."),
187
+ resetAttempts: {
188
+ kind: "boolean",
189
+ optional: true,
190
+ withNegated: false,
191
+ brief: "Reset attempts to zero before requeueing.",
192
+ },
193
+ };
194
+ const outboxPurgeFlagParameters = {
195
+ json: jsonFlag,
196
+ module: parsedStringFlag("Outbox registry module. Defaults to server/outbox.ts."),
197
+ before: parsedDateFlag("Only purge dead-lettered messages last updated before this timestamp."),
198
+ all: {
199
+ kind: "boolean",
200
+ optional: true,
201
+ withNegated: false,
202
+ brief: "Purge every dead-lettered message when --before is omitted.",
203
+ },
204
+ limit: {
205
+ kind: "parsed",
206
+ parse: parsePositiveInteger,
207
+ optional: true,
208
+ brief: "Maximum messages to purge.",
209
+ },
210
+ dryRun: operationDryRunFlag,
211
+ };
212
+ const outboxPruneFlagParameters = {
213
+ json: jsonFlag,
214
+ module: parsedStringFlag("Outbox registry module. Defaults to server/outbox.ts."),
215
+ before: parsedDateFlag("Prune delivered messages delivered before this timestamp."),
216
+ limit: {
217
+ kind: "parsed",
218
+ parse: parsePositiveInteger,
219
+ optional: true,
220
+ brief: "Maximum messages to prune.",
221
+ },
222
+ dryRun: operationDryRunFlag,
223
+ };
127
224
  const scheduleRunFlagParameters = {
128
225
  json: jsonFlag,
129
226
  module: parsedStringFlag("Schedule registry module. Defaults to server/schedules.ts."),
@@ -149,6 +246,16 @@ const namePositional = {
149
246
  },
150
247
  ],
151
248
  };
249
+ const idPositional = {
250
+ kind: "tuple",
251
+ parameters: [
252
+ {
253
+ parse: parseString,
254
+ placeholder: "id",
255
+ brief: "Outbox message ID.",
256
+ },
257
+ ],
258
+ };
152
259
  const providerPresetPositional = {
153
260
  kind: "tuple",
154
261
  parameters: [
@@ -182,7 +289,11 @@ and a shadcn UI shell. Pass --api for an API-only scaffold without the
182
289
  UI shell, and --db to choose the database.
183
290
 
184
291
  Available databases: ${databaseChoices.join(", ")}
185
- Available integrations: ${integrationChoices.join(", ")}
292
+ Available integrations: ${createIntegrationChoices.join(", ")}
293
+
294
+ --integrations accepts the starter template integrations plus the full
295
+ beignet providers add preset catalog; presets are applied to the scaffold
296
+ with the same machinery as beignet providers add.
186
297
 
187
298
  Running create on an interactive terminal without selection flags opens a
188
299
  prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
@@ -215,10 +326,10 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
215
326
  },
216
327
  integrations: {
217
328
  kind: "enum",
218
- values: integrationChoices,
329
+ values: createIntegrationChoices,
219
330
  optional: true,
220
331
  variadic: ",",
221
- brief: "Add first-party integrations as a comma-separated list.",
332
+ brief: "Add first-party integrations or provider presets as a comma-separated list.",
222
333
  },
223
334
  yes: {
224
335
  kind: "boolean",
@@ -271,7 +382,13 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
271
382
  }
272
383
  const api = selections.api ?? false;
273
384
  const database = selections.db ?? "sqlite";
274
- const integrations = uniqueValues([...(selections.integrations ?? [])]);
385
+ const { integrations, presets } = splitCreateIntegrations(uniqueValues([...(selections.integrations ?? [])]));
386
+ let presetsNeedEnv = false;
387
+ if (presets.length > 0) {
388
+ const { assertCompatibleCreatePresets, providerPresetNeedsEnv } = await import("./provider-add.js");
389
+ assertCompatibleCreatePresets(presets, integrations);
390
+ presetsNeedEnv = presets.some(providerPresetNeedsEnv);
391
+ }
275
392
  const result = await createProject({
276
393
  name: selections.directory,
277
394
  template: flags.template,
@@ -282,9 +399,21 @@ prompt-based setup. Pass --yes or any selection flag to skip the prompts.`,
282
399
  force: Boolean(flags.force),
283
400
  dryRun: Boolean(flags.dryRun),
284
401
  });
402
+ if (!flags.dryRun && presets.length > 0) {
403
+ const { addProviderPreset } = await import("./provider-add.js");
404
+ for (const preset of presets) {
405
+ await addProviderPreset({ name: preset, cwd: result.targetDir });
406
+ }
407
+ }
285
408
  writeOutput(this, flags.json
286
- ? JSON.stringify(result, null, 2)
287
- : nextSteps(result, { api, integrations, database }));
409
+ ? JSON.stringify({ ...result, providerPresets: presets }, null, 2)
410
+ : nextSteps(result, {
411
+ api,
412
+ integrations,
413
+ database,
414
+ presets,
415
+ presetsNeedEnv,
416
+ }));
288
417
  };
289
418
  },
290
419
  });
@@ -367,6 +496,12 @@ const checkCommand = buildCommand({
367
496
  withNegated: false,
368
497
  brief: "Apply low-risk doctor fixes before checking.",
369
498
  },
499
+ preflight: {
500
+ kind: "boolean",
501
+ optional: true,
502
+ withNegated: false,
503
+ brief: "Append a beignet preflight step after doctor.",
504
+ },
370
505
  cwd: cwdFlag,
371
506
  },
372
507
  },
@@ -377,6 +512,7 @@ const checkCommand = buildCommand({
377
512
  const result = await checkApp({
378
513
  cwd: flags.cwd,
379
514
  fix: flags.fix,
515
+ preflight: flags.preflight,
380
516
  color,
381
517
  ...(flags.json
382
518
  ? {}
@@ -393,6 +529,47 @@ const checkCommand = buildCommand({
393
529
  };
394
530
  },
395
531
  });
532
+ const preflightCommand = buildCommand({
533
+ docs: {
534
+ brief: "Validate the deploy environment before shipping.",
535
+ fullDescription: "Runtime production gate, unlike the static doctor checks: reads the actual environment the process runs with, verifies every env var installed provider manifests mark as required, flags values still matching .env.example or common placeholders on secret-like keys, and validates the app env schema by importing lib/env.ts. Run it in the deploy pipeline where production configuration is present. Exits 1 on any error finding.",
536
+ },
537
+ parameters: {
538
+ flags: {
539
+ json: jsonFlag,
540
+ envFile: parsedStringFlag("Dotenv-style file merged under the environment (existing env keys win) for local rehearsal."),
541
+ envModule: parsedStringFlag("App env module validated by importing it. Defaults to lib/env.ts."),
542
+ connect: {
543
+ kind: "boolean",
544
+ optional: true,
545
+ withNegated: false,
546
+ brief: "Boot the app server and run every port's checkHealth(). Needs network and real credentials.",
547
+ },
548
+ serverModule: parsedStringFlag("Module exporting getServer, used by --connect. Defaults to server/index.ts."),
549
+ cwd: cwdFlag,
550
+ },
551
+ },
552
+ loader: async () => {
553
+ const { formatPreflight, runPreflight } = await import("./preflight.js");
554
+ const path = await import("node:path");
555
+ return async function runPreflightCommand(flags) {
556
+ const cwd = path.resolve(flags.cwd ?? process.cwd());
557
+ const result = await runPreflight({
558
+ cwd,
559
+ envFile: flags.envFile,
560
+ envModule: flags.envModule,
561
+ connect: flags.connect,
562
+ serverModule: flags.serverModule,
563
+ });
564
+ writeOutput(this, flags.json
565
+ ? JSON.stringify({ schemaVersion: 1, cwd, ...result }, null, 2)
566
+ : formatPreflight(result, cwd));
567
+ if (result.findings.some((finding) => finding.severity === "error")) {
568
+ this.process.exitCode = 1;
569
+ }
570
+ };
571
+ },
572
+ });
396
573
  const lintCommand = buildCommand({
397
574
  docs: {
398
575
  brief: "Check Beignet dependency direction conventions.",
@@ -515,18 +692,18 @@ function databaseCommand(command) {
515
692
  },
516
693
  });
517
694
  }
518
- const dbSchemaGenerateCommand = buildCommand({
695
+ const dbSchemaSyncCommand = buildCommand({
519
696
  docs: {
520
- brief: "Generate provider-owned Drizzle schema exports.",
521
- fullDescription: "Writes an app-owned schema file that re-exports Beignet provider tables, then updates the schema index. Run beignet db generate afterward to let the app's Drizzle Kit script create migrations.",
697
+ brief: "Sync app-owned re-exports of Beignet provider tables.",
698
+ fullDescription: "Idempotently brings the app-owned schema file that re-exports Beignet provider tables in sync with the installed providers, then updates the schema index. Run beignet db generate afterward to let the app's Drizzle Kit script create migrations.",
522
699
  },
523
700
  parameters: {
524
- flags: dbSchemaGenerateFlagParameters,
701
+ flags: dbSchemaSyncFlagParameters,
525
702
  },
526
703
  loader: async () => {
527
- const { generateDatabaseSchema } = await import("./db.js");
528
- return async function runDbSchemaGenerate(flags) {
529
- const result = await generateDatabaseSchema({
704
+ const { syncDatabaseSchema } = await import("./db.js");
705
+ return async function runDbSchemaSync(flags) {
706
+ const result = await syncDatabaseSchema({
530
707
  dialect: flags.dialect,
531
708
  tables: flags.tables,
532
709
  output: flags.output,
@@ -534,16 +711,16 @@ const dbSchemaGenerateCommand = buildCommand({
534
711
  });
535
712
  writeOutput(this, flags.json
536
713
  ? JSON.stringify(result, null, 2)
537
- : databaseSchemaGenerateNextSteps(result));
714
+ : databaseSchemaSyncNextSteps(result));
538
715
  };
539
716
  },
540
717
  });
541
718
  const dbSchemaRoutes = buildRouteMap({
542
719
  docs: {
543
- brief: "Generate Beignet database schema helpers.",
720
+ brief: "Sync app-owned Beignet provider schema exports.",
544
721
  },
545
722
  routes: {
546
- generate: dbSchemaGenerateCommand,
723
+ sync: dbSchemaSyncCommand,
547
724
  },
548
725
  });
549
726
  const dbRoutes = buildRouteMap({
@@ -609,12 +786,132 @@ const outboxDrainCommand = buildCommand({
609
786
  };
610
787
  },
611
788
  });
789
+ const outboxListCommand = buildCommand({
790
+ docs: {
791
+ brief: "List outbox messages for operational inspection.",
792
+ },
793
+ parameters: {
794
+ flags: outboxListFlagParameters,
795
+ },
796
+ loader: async () => {
797
+ const { runOutboxList } = await import("./outbox.js");
798
+ return async function runList(flags) {
799
+ const result = await runOutboxList({
800
+ modulePath: flags.module,
801
+ status: flags.status,
802
+ kind: flags.kind,
803
+ name: flags.name,
804
+ limit: flags.limit,
805
+ });
806
+ writeOutput(this, flags.json
807
+ ? JSON.stringify(result, null, 2)
808
+ : outboxListNextSteps(result));
809
+ };
810
+ },
811
+ });
812
+ const outboxShowCommand = buildCommand({
813
+ docs: {
814
+ brief: "Show one outbox message.",
815
+ },
816
+ parameters: {
817
+ flags: outboxShowFlagParameters,
818
+ positional: idPositional,
819
+ },
820
+ loader: async () => {
821
+ const { runOutboxShow } = await import("./outbox.js");
822
+ return async function runShow(flags, id) {
823
+ const result = await runOutboxShow({
824
+ modulePath: flags.module,
825
+ id,
826
+ });
827
+ writeOutput(this, flags.json
828
+ ? JSON.stringify(result, null, 2)
829
+ : outboxShowNextSteps(result));
830
+ };
831
+ },
832
+ });
833
+ const outboxRequeueCommand = buildCommand({
834
+ docs: {
835
+ brief: "Return one dead-lettered outbox message to pending state.",
836
+ },
837
+ parameters: {
838
+ flags: outboxRequeueFlagParameters,
839
+ positional: idPositional,
840
+ },
841
+ loader: async () => {
842
+ const { runOutboxRequeue } = await import("./outbox.js");
843
+ return async function runRequeue(flags, id) {
844
+ const result = await runOutboxRequeue({
845
+ modulePath: flags.module,
846
+ id,
847
+ availableAt: flags.availableAt,
848
+ resetAttempts: Boolean(flags.resetAttempts),
849
+ });
850
+ writeOutput(this, flags.json
851
+ ? JSON.stringify(result, null, 2)
852
+ : outboxRequeueNextSteps(result));
853
+ };
854
+ },
855
+ });
856
+ const outboxPurgeCommand = buildCommand({
857
+ docs: {
858
+ brief: "Delete dead-lettered outbox messages.",
859
+ },
860
+ parameters: {
861
+ flags: outboxPurgeFlagParameters,
862
+ },
863
+ loader: async () => {
864
+ const { runOutboxPurge } = await import("./outbox.js");
865
+ return async function runPurge(flags) {
866
+ const result = await runOutboxPurge({
867
+ modulePath: flags.module,
868
+ before: flags.before,
869
+ all: Boolean(flags.all),
870
+ limit: flags.limit,
871
+ dryRun: Boolean(flags.dryRun),
872
+ });
873
+ writeOutput(this, flags.json
874
+ ? JSON.stringify(result, null, 2)
875
+ : outboxDeleteNextSteps("Purged dead-lettered outbox messages", result));
876
+ };
877
+ },
878
+ });
879
+ const outboxPruneCommand = buildCommand({
880
+ docs: {
881
+ brief: "Delete delivered outbox messages older than a retention cutoff.",
882
+ },
883
+ parameters: {
884
+ flags: outboxPruneFlagParameters,
885
+ },
886
+ loader: async () => {
887
+ const { runOutboxPrune } = await import("./outbox.js");
888
+ return async function runPrune(flags) {
889
+ if (!flags.before) {
890
+ throw new Error("Pass --before <date> to prune delivered messages.");
891
+ }
892
+ const result = await runOutboxPrune({
893
+ modulePath: flags.module,
894
+ before: flags.before,
895
+ limit: flags.limit,
896
+ dryRun: Boolean(flags.dryRun),
897
+ });
898
+ writeOutput(this, flags.json
899
+ ? JSON.stringify(result, null, 2)
900
+ : outboxDeleteNextSteps("Pruned delivered outbox messages", result));
901
+ };
902
+ },
903
+ });
612
904
  const outboxRoutes = buildRouteMap({
613
905
  docs: {
614
906
  brief: "Run Beignet outbox operations.",
615
907
  },
616
908
  routes: {
617
909
  drain: outboxDrainCommand,
910
+ list: outboxListCommand,
911
+ show: outboxShowCommand,
912
+ requeue: outboxRequeueCommand,
913
+ purge: outboxPurgeCommand,
914
+ prune: outboxPruneCommand,
618
915
  },
619
916
  });
620
917
  const scheduleRunCommand = buildCommand({
@@ -1012,6 +1309,7 @@ Run npm create beignet@latest (or bun create beignet) to scaffold a new app.`,
1012
1309
  make: makeRoutes,
1013
1310
  mcp: mcpCommand,
1014
1311
  outbox: outboxRoutes,
1312
+ preflight: preflightCommand,
1015
1313
  providers: providerRoutes,
1016
1314
  routes: routesCommand,
1017
1315
  schedule: scheduleRoutes,
@@ -1070,6 +1368,57 @@ Result:
1070
1368
  retried: ${result.result.retried}
1071
1369
  deadLettered: ${result.result.deadLettered}`;
1072
1370
  }
1371
+ function formatOutboxDate(value) {
1372
+ if (!value)
1373
+ return "none";
1374
+ return value instanceof Date ? value.toISOString() : value;
1375
+ }
1376
+ function outboxMessageLine(message) {
1377
+ return `${message.id} ${message.status} ${message.kind} ${message.name} attempts ${message.attempts}/${message.maxAttempts} updated ${formatOutboxDate(message.updatedAt)}`;
1378
+ }
1379
+ function outboxListNextSteps(result) {
1380
+ const messages = result.messages.length === 0
1381
+ ? " none"
1382
+ : result.messages
1383
+ .map((message) => ` ${outboxMessageLine(message)}`)
1384
+ .join("\n");
1385
+ return `Listed ${result.messages.length} of ${result.count} matching outbox messages in ${result.durationMs}ms
1386
+
1387
+ Messages:
1388
+ ${messages}`;
1389
+ }
1390
+ function outboxShowNextSteps(result) {
1391
+ const message = result.message;
1392
+ const error = message.lastError
1393
+ ? `${message.lastError.name ?? "Error"}: ${message.lastError.message}`
1394
+ : "none";
1395
+ return `Outbox message ${message.id} (${message.status}) in ${result.durationMs}ms
1396
+
1397
+ Message:
1398
+ kind: ${message.kind}
1399
+ name: ${message.name}
1400
+ attempts: ${message.attempts}/${message.maxAttempts}
1401
+ availableAt: ${formatOutboxDate(message.availableAt)}
1402
+ deliveredAt: ${formatOutboxDate(message.deliveredAt)}
1403
+ updatedAt: ${formatOutboxDate(message.updatedAt)}
1404
+ lastError: ${error}`;
1405
+ }
1406
+ function outboxRequeueNextSteps(result) {
1407
+ return `Requeued outbox message ${result.message.id} in ${result.durationMs}ms
1408
+
1409
+ Message:
1410
+ status: ${result.message.status}
1411
+ attempts: ${result.message.attempts}/${result.message.maxAttempts}
1412
+ availableAt: ${formatOutboxDate(result.message.availableAt)}`;
1413
+ }
1414
+ function outboxDeleteNextSteps(label, result) {
1415
+ const action = result.dryRun ? "Would delete" : "Deleted";
1416
+ return `${label} in ${result.durationMs}ms
1417
+
1418
+ Result:
1419
+ matched: ${result.matched}
1420
+ ${action}: ${result.deleted}`;
1421
+ }
1073
1422
  function scheduleRunNextSteps(result) {
1074
1423
  return `Ran schedule ${result.name} in ${result.durationMs}ms
1075
1424
 
@@ -1083,18 +1432,28 @@ Run:
1083
1432
  function nextSteps(result, options = {}) {
1084
1433
  if (result.dryRun) {
1085
1434
  const plannedFiles = result.files.map((file) => ` ${file}`).join("\n");
1435
+ const plannedPresets = options.presets && options.presets.length > 0
1436
+ ? `\n\nProvider presets applied after scaffold (skipped in dry run):\n${options.presets
1437
+ .map((preset) => ` ${preset}`)
1438
+ .join("\n")}`
1439
+ : "";
1086
1440
  return `Would create ${result.name} at ${result.targetDir}
1087
1441
 
1088
1442
  Planned files:
1089
- ${plannedFiles}`;
1443
+ ${plannedFiles}${plannedPresets}`;
1090
1444
  }
1091
1445
  const pm = result.packageManager;
1092
1446
  const run = pm === "npm" ? "npm run" : `${pm} run`;
1093
1447
  const cli = packageRunnerFromPackageManager(pm);
1094
1448
  const envRequiredIntegrations = options.integrations?.filter(isEnvRequiredProviderIntegration) ?? [];
1095
- const envStep = envRequiredIntegrations.length > 0
1449
+ const envStep = envRequiredIntegrations.length > 0 || options.presetsNeedEnv
1096
1450
  ? " Fill .env.local for selected provider integrations before starting the app.\n"
1097
1451
  : "";
1452
+ const presetSection = options.presets && options.presets.length > 0
1453
+ ? `\nProvider presets applied:\n${options.presets
1454
+ .map((preset) => ` ${preset}`)
1455
+ .join("\n")}\n See docs/integrations.md for setup notes and follow-up steps.\n`
1456
+ : "";
1098
1457
  const openStep = options.api
1099
1458
  ? " open http://localhost:3000 for the API landing page"
1100
1459
  : " open http://localhost:3000/sign-up";
@@ -1108,7 +1467,7 @@ ${plannedFiles}`;
1108
1467
  `
1109
1468
  : "";
1110
1469
  return `Created ${result.name} at ${result.targetDir}
1111
-
1470
+ ${presetSection}
1112
1471
  Next steps:
1113
1472
  cd ${result.targetDir}
1114
1473
  ${pm} install
@@ -1195,13 +1554,13 @@ function databaseCommandNextSteps(result) {
1195
1554
  Command:
1196
1555
  ${command}`;
1197
1556
  }
1198
- function databaseSchemaGenerateNextSteps(result) {
1557
+ function databaseSchemaSyncNextSteps(result) {
1199
1558
  const { changedFiles, skippedFiles } = changedFileLines({
1200
1559
  createdFiles: result.createdFiles,
1201
1560
  updatedFiles: result.updatedFiles,
1202
1561
  skippedFiles: result.skippedFiles,
1203
1562
  });
1204
- const prefix = result.dryRun ? "Would generate" : "Generated";
1563
+ const prefix = result.dryRun ? "Would sync" : "Synced";
1205
1564
  const tables = result.tables.join(", ");
1206
1565
  return `${prefix} Beignet ${result.dialect} database schema (${tables}) in ${result.cwd}
1207
1566
 
@@ -1303,7 +1662,7 @@ function makeOutboxNextSteps(result) {
1303
1662
  function makeListenerNextSteps(result) {
1304
1663
  return makeNextSteps(result, "listener", [
1305
1664
  "Replace the starter handler with the event reaction.",
1306
- "Register the listener collection from infrastructure startup.",
1665
+ "Register the listener collection from server provider wiring.",
1307
1666
  ]);
1308
1667
  }
1309
1668
  function makeScheduleNextSteps(result, options = {}) {