@beignet/cli 0.0.37 → 0.0.39

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 (113) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +138 -101
  3. package/dist/choices.d.ts +17 -21
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +21 -67
  6. package/dist/choices.js.map +1 -1
  7. package/dist/config.d.ts +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/create-prompts.d.ts +5 -6
  12. package/dist/create-prompts.d.ts.map +1 -1
  13. package/dist/create-prompts.js +15 -15
  14. package/dist/create-prompts.js.map +1 -1
  15. package/dist/create.d.ts +11 -2
  16. package/dist/create.d.ts.map +1 -1
  17. package/dist/create.js +38 -28
  18. package/dist/create.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +149 -47
  21. package/dist/index.js.map +1 -1
  22. package/dist/inspect.js +69 -16
  23. package/dist/inspect.js.map +1 -1
  24. package/dist/lib.d.ts +1 -1
  25. package/dist/lib.d.ts.map +1 -1
  26. package/dist/lint.d.ts.map +1 -1
  27. package/dist/lint.js +27 -6
  28. package/dist/lint.js.map +1 -1
  29. package/dist/make/inbox.js +10 -10
  30. package/dist/make/inbox.js.map +1 -1
  31. package/dist/make/payments.d.ts +2 -1
  32. package/dist/make/payments.d.ts.map +1 -1
  33. package/dist/make/payments.js +18 -14
  34. package/dist/make/payments.js.map +1 -1
  35. package/dist/make/shared.d.ts +11 -4
  36. package/dist/make/shared.d.ts.map +1 -1
  37. package/dist/make/shared.js +56 -45
  38. package/dist/make/shared.js.map +1 -1
  39. package/dist/make/tenancy.js +8 -8
  40. package/dist/make/tenancy.js.map +1 -1
  41. package/dist/make.d.ts +3 -2
  42. package/dist/make.d.ts.map +1 -1
  43. package/dist/make.js +481 -200
  44. package/dist/make.js.map +1 -1
  45. package/dist/mcp.d.ts.map +1 -1
  46. package/dist/mcp.js +15 -11
  47. package/dist/mcp.js.map +1 -1
  48. package/dist/operational-error-reporting.d.ts +15 -0
  49. package/dist/operational-error-reporting.d.ts.map +1 -0
  50. package/dist/operational-error-reporting.js +42 -0
  51. package/dist/operational-error-reporting.js.map +1 -0
  52. package/dist/outbox.d.ts.map +1 -1
  53. package/dist/outbox.js +73 -0
  54. package/dist/outbox.js.map +1 -1
  55. package/dist/provider-add.d.ts +21 -3
  56. package/dist/provider-add.d.ts.map +1 -1
  57. package/dist/provider-add.js +117 -81
  58. package/dist/provider-add.js.map +1 -1
  59. package/dist/provider-audit.js +3 -3
  60. package/dist/provider-audit.js.map +1 -1
  61. package/dist/schedule.d.ts.map +1 -1
  62. package/dist/schedule.js +28 -1
  63. package/dist/schedule.js.map +1 -1
  64. package/dist/task.d.ts.map +1 -1
  65. package/dist/task.js +38 -5
  66. package/dist/task.js.map +1 -1
  67. package/dist/templates/base.d.ts +1 -1
  68. package/dist/templates/base.d.ts.map +1 -1
  69. package/dist/templates/base.js +15 -15
  70. package/dist/templates/base.js.map +1 -1
  71. package/dist/templates/index.d.ts +2 -2
  72. package/dist/templates/index.d.ts.map +1 -1
  73. package/dist/templates/index.js +7 -7
  74. package/dist/templates/index.js.map +1 -1
  75. package/dist/templates/server.d.ts +1 -1
  76. package/dist/templates/server.d.ts.map +1 -1
  77. package/dist/templates/server.js +22 -18
  78. package/dist/templates/server.js.map +1 -1
  79. package/dist/templates/shared.d.ts +4 -4
  80. package/dist/templates/shared.d.ts.map +1 -1
  81. package/dist/templates/shared.js +6 -6
  82. package/dist/templates/shared.js.map +1 -1
  83. package/dist/templates/testing.js +1 -1
  84. package/dist/templates/todos.js +6 -6
  85. package/package.json +4 -4
  86. package/skills/app-structure/SKILL.md +13 -8
  87. package/src/choices.ts +38 -88
  88. package/src/config.ts +2 -2
  89. package/src/create-prompts.ts +20 -21
  90. package/src/create.ts +54 -36
  91. package/src/index.ts +193 -65
  92. package/src/inspect.ts +116 -18
  93. package/src/lib.ts +1 -1
  94. package/src/lint.ts +35 -4
  95. package/src/make/inbox.ts +10 -10
  96. package/src/make/payments.ts +26 -18
  97. package/src/make/shared.ts +72 -50
  98. package/src/make/tenancy.ts +8 -8
  99. package/src/make.ts +607 -191
  100. package/src/mcp.ts +20 -13
  101. package/src/operational-error-reporting.ts +60 -0
  102. package/src/outbox.ts +77 -0
  103. package/src/provider-add.ts +179 -95
  104. package/src/provider-audit.ts +3 -3
  105. package/src/schedule.ts +32 -1
  106. package/src/task.ts +42 -5
  107. package/src/templates/base.ts +16 -16
  108. package/src/templates/index.ts +8 -8
  109. package/src/templates/server.ts +22 -18
  110. package/src/templates/shared.ts +10 -10
  111. package/src/templates/testing.ts +1 -1
  112. package/src/templates/todos.ts +6 -6
  113. package/src/test-helpers/generated-app.ts +1 -0
@@ -1,9 +1,9 @@
1
1
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import {
4
- type IntegrationName,
5
4
  type ProviderPresetName,
6
5
  providerPresetChoices,
6
+ type StarterProviderName,
7
7
  } from "./choices.js";
8
8
  import {
9
9
  directoryPath,
@@ -72,6 +72,18 @@ type PlannedFile = {
72
72
  result: "created" | "updated" | "skipped";
73
73
  };
74
74
 
75
+ type ProviderPlanFile = {
76
+ path: string;
77
+ content: string;
78
+ };
79
+
80
+ type ProviderSourceReader = (filePath: string) => Promise<string | undefined>;
81
+
82
+ export type CreateProviderPresetPlan = {
83
+ files: ProviderPlanFile[];
84
+ presets: AddProviderPresetResult[];
85
+ };
86
+
75
87
  const presets: Record<ProviderPresetName, ProviderPreset> = {
76
88
  "flags-openfeature": {
77
89
  name: "flags-openfeature",
@@ -97,7 +109,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
97
109
  "",
98
110
  "- Package: `@beignet/provider-flags-openfeature`",
99
111
  "- Peer dependency: `@openfeature/server-sdk`",
100
- "- The preset wires `createOpenFeatureFlagsProvider()` in `server/providers.ts` and defers `flags` in `infra/app-ports.ts`.",
112
+ "- The preset wires `createOpenFeatureFlagsProvider()` in `server/providers.ts` and defers `flags` in `infra/port-wiring.ts`.",
101
113
  "- Configure the OpenFeature provider globally or replace this preset with `createOpenFeatureFlagsProvider(...)` when the app needs provider-specific setup.",
102
114
  "- Use `ctx.ports.flags` in app code; `ctx.ports.openFeature` is available as an escape hatch.",
103
115
  "",
@@ -105,7 +117,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
105
117
  nextSteps: [
106
118
  "Run your package manager install command.",
107
119
  "Configure an OpenFeature provider before relying on non-default flag values.",
108
- "Run beignet providers audit and beignet doctor --strict.",
120
+ "Run beignet provider audit and beignet doctor --strict.",
109
121
  ],
110
122
  },
111
123
  "jobs-bullmq": {
@@ -138,7 +150,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
138
150
  "",
139
151
  "- Package: `@beignet/provider-jobs-bullmq`",
140
152
  "- Peer dependency: `bullmq`",
141
- "- The preset wires `createBullMQJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
153
+ "- The preset wires `createBullMQJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/port-wiring.ts`.",
142
154
  "- Set `BULLMQ_REDIS_URL` before booting the app; the default points at local Redis.",
143
155
  "- Dispatching enqueues jobs, but nothing executes them until a worker entrypoint runs `createBullMQJobWorker(...)` in a worker process. See the jobs docs page.",
144
156
  "- Use `ctx.ports.jobs.dispatch(...)` in app code; `ctx.ports.bullMQJobs` is available as an escape hatch.",
@@ -148,7 +160,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
148
160
  "Run your package manager install command.",
149
161
  "Set BULLMQ_REDIS_URL in .env.local or your deployment environment.",
150
162
  "Add a worker entrypoint that runs createBullMQJobWorker(...) in a worker process; dispatched jobs do not execute without one. See the jobs docs page.",
151
- "Run beignet providers audit and beignet doctor --strict.",
163
+ "Run beignet provider audit and beignet doctor --strict.",
152
164
  ],
153
165
  conflictingProviderEntries: ["createInngestJobsProvider"],
154
166
  },
@@ -180,7 +192,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
180
192
  "",
181
193
  "- Package: `@beignet/provider-jobs-inngest`",
182
194
  "- Peer dependency: `inngest`",
183
- "- The preset wires `createInngestJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
195
+ "- The preset wires `createInngestJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/port-wiring.ts`.",
184
196
  "- Define jobs with `defineJob(...)` and dispatch them through `ctx.ports.jobs.dispatch(...)`.",
185
197
  "- Execution needs an Inngest serve route built from `createInngestJobFunction(...)` and `serve(...)`. See the jobs docs page.",
186
198
  "- Use `ctx.ports.inngest` only as an escape hatch for Inngest-specific workflow APIs; prefer `ctx.ports.jobs.dispatch(...)` for Beignet jobs.",
@@ -190,7 +202,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
190
202
  "Run your package manager install command.",
191
203
  "Set INNGEST_APP_NAME and, for Inngest cloud, INNGEST_EVENT_KEY in .env.local or your deployment environment.",
192
204
  "Expose an Inngest serve route with createInngestJobFunction(...) so dispatched jobs execute. See the jobs docs page.",
193
- "Run beignet providers audit and beignet doctor --strict.",
205
+ "Run beignet provider audit and beignet doctor --strict.",
194
206
  ],
195
207
  conflictingProviderEntries: ["createBullMQJobsProvider"],
196
208
  },
@@ -222,7 +234,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
222
234
  "",
223
235
  "- Package: `@beignet/provider-mail-resend`",
224
236
  "- Peer dependency: `resend`",
225
- "- The preset wires `createResendMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
237
+ "- The preset wires `createResendMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/port-wiring.ts`.",
226
238
  "- `.env.example` ships dev placeholders so the app boots; set real `RESEND_API_KEY` and `RESEND_FROM` values before sending mail.",
227
239
  "- Use `ctx.ports.mailer` in app code; `ctx.ports.resend` is available as an escape hatch.",
228
240
  "",
@@ -230,7 +242,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
230
242
  nextSteps: [
231
243
  "Run your package manager install command.",
232
244
  "Set RESEND_API_KEY and RESEND_FROM in .env.local or your deployment environment.",
233
- "Run beignet providers audit and beignet doctor --strict.",
245
+ "Run beignet provider audit and beignet doctor --strict.",
234
246
  ],
235
247
  conflictingProviderEntries: [
236
248
  "createSmtpMailProvider",
@@ -268,7 +280,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
268
280
  "",
269
281
  "- Package: `@beignet/provider-mail-smtp`",
270
282
  "- Peer dependency: `nodemailer`",
271
- "- The preset wires `createSmtpMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
283
+ "- The preset wires `createSmtpMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/port-wiring.ts`.",
272
284
  "- `.env.example` ships dev placeholders so the app boots; set real `MAIL_*` values before sending mail.",
273
285
  "- Use `ctx.ports.mailer` in app code; `ctx.ports.smtp` is available as an escape hatch.",
274
286
  "",
@@ -276,7 +288,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
276
288
  nextSteps: [
277
289
  "Run your package manager install command.",
278
290
  "Set MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS, and MAIL_FROM in .env.local or your deployment environment.",
279
- "Run beignet providers audit and beignet doctor --strict.",
291
+ "Run beignet provider audit and beignet doctor --strict.",
280
292
  ],
281
293
  conflictingProviderEntries: [
282
294
  "createResendMailProvider",
@@ -317,7 +329,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
317
329
  "",
318
330
  "- Package: `@beignet/provider-payments-stripe`",
319
331
  "- Peer dependency: `stripe`",
320
- "- The preset wires `createStripePaymentsProvider()` in `server/providers.ts` and defers `payments` in `infra/app-ports.ts`.",
332
+ "- The preset wires `createStripePaymentsProvider()` in `server/providers.ts` and defers `payments` in `infra/port-wiring.ts`.",
321
333
  "- `.env.example` ships dev placeholders so the app boots; set real `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` values before taking payments.",
322
334
  "- Stripe requires a webhook route: run `beignet make payments` for the full billing slice, including a `createPaymentWebhookRoute(...)` route.",
323
335
  "- Use `ctx.ports.payments` in app code; `ctx.ports.stripe` is available as an escape hatch.",
@@ -327,7 +339,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
327
339
  "Run your package manager install command.",
328
340
  "Set STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in .env.local or your deployment environment.",
329
341
  "Run beignet make payments for the full billing slice; Stripe needs its webhook route before subscription state can sync.",
330
- "Run beignet providers audit and beignet doctor --strict.",
342
+ "Run beignet provider audit and beignet doctor --strict.",
331
343
  ],
332
344
  conflictingProviderEntries: ["createMemoryPaymentsProvider"],
333
345
  },
@@ -360,7 +372,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
360
372
  "## Meilisearch search",
361
373
  "",
362
374
  "- Package: `@beignet/provider-search-meilisearch`",
363
- "- The preset wires `createMeilisearchSearchProvider()` in `server/providers.ts` and defers `search` in `infra/app-ports.ts`.",
375
+ "- The preset wires `createMeilisearchSearchProvider()` in `server/providers.ts` and defers `search` in `infra/port-wiring.ts`.",
364
376
  "- Set `MEILISEARCH_HOST` before booting the app; set `MEILISEARCH_API_KEY` when your Meilisearch server requires one.",
365
377
  "- Use `ctx.ports.search` in app code; `ctx.ports.meilisearch` is available as an escape hatch.",
366
378
  "",
@@ -368,11 +380,11 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
368
380
  nextSteps: [
369
381
  "Run your package manager install command.",
370
382
  "Set MEILISEARCH_HOST and any needed MEILISEARCH_* values in .env.local or your deployment environment.",
371
- "Run beignet providers audit and beignet doctor --strict.",
383
+ "Run beignet provider audit and beignet doctor --strict.",
372
384
  ],
373
385
  },
374
- sentry: {
375
- name: "sentry",
386
+ "error-reporting-sentry": {
387
+ name: "error-reporting-sentry",
376
388
  displayName: "Sentry error reporting",
377
389
  importName: "createSentryErrorReportingProvider",
378
390
  importFrom: "@beignet/provider-error-reporting-sentry",
@@ -403,11 +415,11 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
403
415
  nextSteps: [
404
416
  "Run your package manager install command.",
405
417
  "Set SENTRY_DSN in .env.local or your deployment environment.",
406
- "Run beignet providers audit and beignet doctor --strict.",
418
+ "Run beignet provider audit and beignet doctor --strict.",
407
419
  ],
408
420
  },
409
- "redis-cache": {
410
- name: "redis-cache",
421
+ "cache-redis": {
422
+ name: "cache-redis",
411
423
  displayName: "Redis cache",
412
424
  importName: "createRedisCacheProvider",
413
425
  importFrom: "@beignet/provider-cache-redis",
@@ -434,7 +446,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
434
446
  "",
435
447
  "- Package: `@beignet/provider-cache-redis`",
436
448
  "- Peer dependency: `ioredis`",
437
- "- The preset wires `createRedisCacheProvider()` in `server/providers.ts` and defers `cache` in `infra/app-ports.ts`.",
449
+ "- The preset wires `createRedisCacheProvider()` in `server/providers.ts` and defers `cache` in `infra/port-wiring.ts`.",
438
450
  "- Set `REDIS_URL` before booting the app.",
439
451
  "- Use `ctx.ports.cache` in app code; `ctx.ports.redis` is available as an escape hatch.",
440
452
  "",
@@ -442,7 +454,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
442
454
  nextSteps: [
443
455
  "Run your package manager install command.",
444
456
  "Set REDIS_URL in .env.local or your deployment environment.",
445
- "Run beignet providers audit and beignet doctor --strict.",
457
+ "Run beignet provider audit and beignet doctor --strict.",
446
458
  ],
447
459
  },
448
460
  "event-bus-redis": {
@@ -475,7 +487,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
475
487
  "",
476
488
  "- Package: `@beignet/provider-event-bus-redis`",
477
489
  "- Peer dependency: `ioredis`",
478
- "- The preset wires `createRedisEventBusProvider()` in `server/providers.ts` and defers `eventBus` in `infra/app-ports.ts`.",
490
+ "- The preset wires `createRedisEventBusProvider()` in `server/providers.ts` and defers `eventBus` in `infra/port-wiring.ts`.",
479
491
  "- Set `REDIS_EVENT_BUS_URL` before booting the app.",
480
492
  "- Redis Pub/Sub delivery is best-effort and cross-process, but not durable: use outbox/jobs for side effects that need retry, replay, or dead-letter handling.",
481
493
  "- Use `ctx.ports.eventBus` in app code; `ctx.ports.redisEventBus` is available as an escape hatch.",
@@ -485,12 +497,12 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
485
497
  "Run your package manager install command.",
486
498
  "Set REDIS_EVENT_BUS_URL in .env.local or your deployment environment.",
487
499
  "Keep durable side effects on the outbox/jobs path; Redis Pub/Sub does not replay missed events.",
488
- "Run beignet providers audit and beignet doctor --strict.",
500
+ "Run beignet provider audit and beignet doctor --strict.",
489
501
  ],
490
502
  conflictingProviderEntries: ["createMemoryEventBusProvider"],
491
503
  },
492
- "redis-locks": {
493
- name: "redis-locks",
504
+ "locks-redis": {
505
+ name: "locks-redis",
494
506
  displayName: "Redis locks",
495
507
  importName: "createRedisLocksProvider",
496
508
  importFrom: "@beignet/provider-locks-redis",
@@ -518,7 +530,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
518
530
  "",
519
531
  "- Package: `@beignet/provider-locks-redis`",
520
532
  "- Peer dependency: `ioredis`",
521
- "- The preset wires `createRedisLocksProvider()` in `server/providers.ts` and defers `locks` in `infra/app-ports.ts`.",
533
+ "- The preset wires `createRedisLocksProvider()` in `server/providers.ts` and defers `locks` in `infra/port-wiring.ts`.",
522
534
  "- Set `REDIS_LOCKS_URL` before booting the app.",
523
535
  "- Use `ctx.ports.locks` in app code; `ctx.ports.redisLocks` is available as an escape hatch.",
524
536
  "",
@@ -526,11 +538,11 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
526
538
  nextSteps: [
527
539
  "Run your package manager install command.",
528
540
  "Set REDIS_LOCKS_URL in .env.local or your deployment environment.",
529
- "Run beignet providers audit and beignet doctor --strict.",
541
+ "Run beignet provider audit and beignet doctor --strict.",
530
542
  ],
531
543
  },
532
- "s3-storage": {
533
- name: "s3-storage",
544
+ "storage-s3": {
545
+ name: "storage-s3",
534
546
  displayName: "S3-compatible storage",
535
547
  importName: "createS3StorageProvider",
536
548
  importFrom: "@beignet/provider-storage-s3",
@@ -579,7 +591,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
579
591
  "",
580
592
  "- Package: `@beignet/provider-storage-s3`",
581
593
  "- Peer dependencies: `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`",
582
- "- The preset wires `createS3StorageProvider()` in `server/providers.ts` and defers `storage` in `infra/app-ports.ts`.",
594
+ "- The preset wires `createS3StorageProvider()` in `server/providers.ts` and defers `storage` in `infra/port-wiring.ts`.",
583
595
  "- Set `STORAGE_S3_BUCKET` before booting the app; set endpoint and credentials when using an S3-compatible host outside AWS defaults.",
584
596
  "- Use `ctx.ports.storage` in app code; `ctx.ports.s3Storage` is available as an escape hatch.",
585
597
  "",
@@ -587,15 +599,15 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
587
599
  nextSteps: [
588
600
  "Run your package manager install command.",
589
601
  "Set STORAGE_S3_BUCKET and any needed STORAGE_S3_* credentials in .env.local or your deployment environment.",
590
- "Run beignet providers audit and beignet doctor --strict.",
602
+ "Run beignet provider audit and beignet doctor --strict.",
591
603
  ],
592
604
  conflictingProviderEntries: [
593
605
  "createLocalStorageProvider",
594
606
  "createVercelBlobStorageProvider",
595
607
  ],
596
608
  },
597
- "vercel-blob-storage": {
598
- name: "vercel-blob-storage",
609
+ "storage-vercel-blob": {
610
+ name: "storage-vercel-blob",
599
611
  displayName: "Vercel Blob storage",
600
612
  importName: "createVercelBlobStorageProvider",
601
613
  importFrom: "@beignet/provider-storage-vercel-blob",
@@ -625,7 +637,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
625
637
  "",
626
638
  "- Package: `@beignet/provider-storage-vercel-blob`",
627
639
  "- Peer dependency: `@vercel/blob`",
628
- "- The preset wires `createVercelBlobStorageProvider()` in `server/providers.ts` and defers `storage` in `infra/app-ports.ts`.",
640
+ "- The preset wires `createVercelBlobStorageProvider()` in `server/providers.ts` and defers `storage` in `infra/port-wiring.ts`.",
629
641
  "- On Vercel, connect a Blob store to the project and `BLOB_READ_WRITE_TOKEN` is set automatically; set it manually elsewhere.",
630
642
  "- The store is uniform-visibility (`BLOB_ACCESS`, default `private`): Vercel Blob does not report per-object access back, so mixed visibility needs a second store.",
631
643
  "- Use `ctx.ports.storage` in app code; `ctx.ports.vercelBlob` is available as an escape hatch.",
@@ -634,15 +646,15 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
634
646
  nextSteps: [
635
647
  "Run your package manager install command.",
636
648
  "Connect a Vercel Blob store to the project (or set BLOB_READ_WRITE_TOKEN manually).",
637
- "Run beignet providers audit and beignet doctor --strict.",
649
+ "Run beignet provider audit and beignet doctor --strict.",
638
650
  ],
639
651
  conflictingProviderEntries: [
640
652
  "createLocalStorageProvider",
641
653
  "createS3StorageProvider",
642
654
  ],
643
655
  },
644
- "upstash-rate-limit": {
645
- name: "upstash-rate-limit",
656
+ "rate-limit-upstash": {
657
+ name: "rate-limit-upstash",
646
658
  displayName: "Upstash rate limit",
647
659
  importName: "createUpstashRateLimitProvider",
648
660
  importFrom: "@beignet/provider-rate-limit-upstash",
@@ -673,7 +685,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
673
685
  "",
674
686
  "- Package: `@beignet/provider-rate-limit-upstash`",
675
687
  "- Peer dependencies: `@upstash/ratelimit` and `@upstash/redis`",
676
- "- The preset wires `createUpstashRateLimitProvider()` in `server/providers.ts` and defers `rateLimit` in `infra/app-ports.ts`.",
688
+ "- The preset wires `createUpstashRateLimitProvider()` in `server/providers.ts` and defers `rateLimit` in `infra/port-wiring.ts`.",
677
689
  "- `.env.example` ships dev placeholders so the app boots; set real `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` values before rate limiting requests.",
678
690
  "- Use `ctx.ports.rateLimit` in app code; `ctx.ports.upstash` is available as an escape hatch.",
679
691
  "",
@@ -681,7 +693,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
681
693
  nextSteps: [
682
694
  "Run your package manager install command.",
683
695
  "Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in .env.local or your deployment environment.",
684
- "Run beignet providers audit and beignet doctor --strict.",
696
+ "Run beignet provider audit and beignet doctor --strict.",
685
697
  ],
686
698
  },
687
699
  };
@@ -690,12 +702,12 @@ const providerPresetSet = new Set<string>(providerPresetChoices);
690
702
 
691
703
  /**
692
704
  * Provider entries the starter template wires for each create-time
693
- * integration, used to pre-validate preset selections at create time.
705
+ * provider, used to pre-validate preset selections at create time.
694
706
  */
695
- const integrationProviderEntries: Record<IntegrationName, string> = {
696
- inngest: "createInngestJobsProvider",
697
- resend: "createResendMailProvider",
698
- "upstash-rate-limit": "createUpstashRateLimitProvider",
707
+ const starterProviderEntries: Record<StarterProviderName, string> = {
708
+ "jobs-inngest": "createInngestJobsProvider",
709
+ "mail-resend": "createResendMailProvider",
710
+ "rate-limit-upstash": "createUpstashRateLimitProvider",
699
711
  };
700
712
 
701
713
  /**
@@ -707,15 +719,18 @@ export function providerPresetNeedsEnv(name: ProviderPresetName): boolean {
707
719
 
708
720
  /**
709
721
  * Fail fast when a create-time selection combines presets or template
710
- * integrations that fill the same app port, before any files are written.
722
+ * starter providers that fill the same app port, before any files are written.
711
723
  */
712
724
  export function assertCompatibleCreatePresets(
713
725
  presetNames: readonly ProviderPresetName[],
714
- integrations: readonly IntegrationName[],
726
+ starterProviders: readonly StarterProviderName[],
715
727
  ): void {
716
728
  const selectedEntries = new Map<string, string>();
717
- for (const integration of integrations) {
718
- selectedEntries.set(integrationProviderEntries[integration], integration);
729
+ for (const starterProvider of starterProviders) {
730
+ selectedEntries.set(
731
+ starterProviderEntries[starterProvider],
732
+ starterProvider,
733
+ );
719
734
  }
720
735
  for (const name of presetNames) {
721
736
  selectedEntries.set(presets[name].importName, name);
@@ -726,7 +741,7 @@ export function assertCompatibleCreatePresets(
726
741
  const other = selectedEntries.get(conflict);
727
742
  if (other !== undefined && other !== name) {
728
743
  throw new Error(
729
- `--integrations selections "${name}" and "${other}" fill the same app port. Pick one of them.`,
744
+ `--providers selections "${name}" and "${other}" fill the same app port. Pick one of them.`,
730
745
  );
731
746
  }
732
747
  }
@@ -747,18 +762,85 @@ export async function addProviderPreset(
747
762
  const preset = presets[options.name as ProviderPresetName];
748
763
  const targetDir = path.resolve(options.cwd ?? process.cwd());
749
764
  const config = await loadBeignetConfig(targetDir);
750
- const packageJsonPath = "package.json";
751
- const providersPath = serverProvidersPath(config);
752
- const integrationsPath = "docs/integrations.md";
765
+ const readSource: ProviderSourceReader = (filePath) =>
766
+ readOptionalAppFile(targetDir, filePath);
767
+ const filesToPlan = await providerPresetFiles(preset, config, readSource);
768
+ const plannedFiles = await planFiles(filesToPlan, readSource);
753
769
 
754
- const packageJsonSource = await readRequiredAppFile(
770
+ if (!options.dryRun) {
771
+ for (const file of plannedFiles) {
772
+ await writePlannedFile(targetDir, file);
773
+ }
774
+ }
775
+
776
+ return providerPresetResult(
777
+ preset,
755
778
  targetDir,
779
+ Boolean(options.dryRun),
780
+ plannedFiles,
781
+ );
782
+ }
783
+
784
+ /**
785
+ * Apply provider presets to an in-memory fresh scaffold. Each preset reads the
786
+ * previous preset's output so dry runs model the real sequential create path.
787
+ */
788
+ export async function planProviderPresetsForCreate(options: {
789
+ targetDir: string;
790
+ files: readonly ProviderPlanFile[];
791
+ names: readonly ProviderPresetName[];
792
+ }): Promise<CreateProviderPresetPlan> {
793
+ const sources = new Map(
794
+ options.files.map((file) => [file.path, file.content] as const),
795
+ );
796
+ const config = await loadBeignetConfig(options.targetDir);
797
+ const presetResults: AddProviderPresetResult[] = [];
798
+ const readSource: ProviderSourceReader = async (filePath) => {
799
+ const generatedSource = sources.get(filePath);
800
+ return generatedSource ?? readOptionalAppFile(options.targetDir, filePath);
801
+ };
802
+
803
+ for (const name of options.names) {
804
+ const preset = presets[name];
805
+ const filesToPlan = await providerPresetFiles(preset, config, readSource);
806
+ const plannedFiles = await planFiles(filesToPlan, readSource);
807
+
808
+ for (const file of plannedFiles) {
809
+ sources.set(file.path, file.content);
810
+ }
811
+
812
+ presetResults.push(
813
+ providerPresetResult(preset, options.targetDir, true, plannedFiles),
814
+ );
815
+ }
816
+
817
+ return {
818
+ files: [...sources].map(([filePath, content]) => ({
819
+ path: filePath,
820
+ content,
821
+ })),
822
+ presets: presetResults,
823
+ };
824
+ }
825
+
826
+ async function providerPresetFiles(
827
+ preset: ProviderPreset,
828
+ config: ResolvedBeignetConfig,
829
+ readSource: ProviderSourceReader,
830
+ ): Promise<ProviderPlanFile[]> {
831
+ const packageJsonPath = "package.json";
832
+ const providersPath = serverProvidersPath(config);
833
+ const providersDocsPath = "docs/providers.md";
834
+ const packageJsonSource = await readRequiredSource(
835
+ readSource,
756
836
  packageJsonPath,
757
837
  );
758
838
  const packageJson = parsePackageJson(packageJsonSource);
759
- const envExampleSource = await readOptionalAppFile(targetDir, ".env.example");
760
- const envExampleContent = updateEnvExample(envExampleSource, preset);
761
- const filesToPlan = [
839
+ const envExampleContent = updateEnvExample(
840
+ await readSource(".env.example"),
841
+ preset,
842
+ );
843
+ const files = [
762
844
  {
763
845
  path: packageJsonPath,
764
846
  content: updatePackageJson(packageJsonSource, packageJson, preset),
@@ -766,53 +848,51 @@ export async function addProviderPreset(
766
848
  {
767
849
  path: providersPath,
768
850
  content: updateServerProviders(
769
- await readRequiredAppFile(targetDir, providersPath),
851
+ await readRequiredSource(readSource, providersPath),
770
852
  preset,
771
853
  ),
772
854
  },
773
855
  {
774
856
  path: config.paths.ports,
775
857
  content: updatePortsFile(
776
- await readRequiredAppFile(targetDir, config.paths.ports),
858
+ await readRequiredSource(readSource, config.paths.ports),
777
859
  preset.appPort,
778
860
  ),
779
861
  },
780
862
  {
781
- path: config.paths.infrastructurePorts,
863
+ path: config.paths.portWiring,
782
864
  content: updateInfrastructurePortsFile(
783
- await readRequiredAppFile(targetDir, config.paths.infrastructurePorts),
865
+ await readRequiredSource(readSource, config.paths.portWiring),
784
866
  preset.appPort.key,
785
867
  ),
786
868
  },
787
869
  {
788
- path: integrationsPath,
789
- content: updateIntegrationsDoc(
790
- await readOptionalAppFile(targetDir, integrationsPath),
791
- preset,
792
- ),
870
+ path: providersDocsPath,
871
+ content: updateProvidersDoc(await readSource(providersDocsPath), preset),
793
872
  },
794
873
  ];
795
874
 
796
875
  if (envExampleContent !== undefined) {
797
- filesToPlan.splice(filesToPlan.length - 1, 0, {
876
+ files.splice(files.length - 1, 0, {
798
877
  path: ".env.example",
799
878
  content: envExampleContent,
800
879
  });
801
880
  }
802
881
 
803
- const plannedFiles = await planFiles(targetDir, filesToPlan);
804
-
805
- if (!options.dryRun) {
806
- for (const file of plannedFiles) {
807
- await writePlannedFile(targetDir, file);
808
- }
809
- }
882
+ return files;
883
+ }
810
884
 
885
+ function providerPresetResult(
886
+ preset: ProviderPreset,
887
+ targetDir: string,
888
+ dryRun: boolean,
889
+ plannedFiles: readonly PlannedFile[],
890
+ ): AddProviderPresetResult {
811
891
  return {
812
892
  schemaVersion: 1,
813
893
  name: preset.name,
814
894
  targetDir,
815
- dryRun: Boolean(options.dryRun),
895
+ dryRun,
816
896
  files: plannedFiles.map((file) => file.path),
817
897
  createdFiles: plannedFiles
818
898
  .filter((file) => file.result === "created")
@@ -827,6 +907,19 @@ export async function addProviderPreset(
827
907
  };
828
908
  }
829
909
 
910
+ async function readRequiredSource(
911
+ readSource: ProviderSourceReader,
912
+ filePath: string,
913
+ ): Promise<string> {
914
+ const content = await readSource(filePath);
915
+ if (content === undefined) {
916
+ throw new Error(
917
+ `Could not find ${filePath}. Run this command in a Beignet app.`,
918
+ );
919
+ }
920
+ return content;
921
+ }
922
+
830
923
  function beignetDependencyVersion(packageJson: PackageJson): string {
831
924
  return (
832
925
  packageJson.dependencies?.["@beignet/core"] ??
@@ -959,14 +1052,14 @@ function updateEnvExample(
959
1052
  return appendSection(current, preset.envExample);
960
1053
  }
961
1054
 
962
- function updateIntegrationsDoc(
1055
+ function updateProvidersDoc(
963
1056
  source: string | undefined,
964
1057
  preset: ProviderPreset,
965
1058
  ): string {
966
1059
  const current =
967
1060
  source ??
968
1061
  [
969
- "# Integrations",
1062
+ "# Providers",
970
1063
  "",
971
1064
  "This file tracks app-level provider setup added after the initial scaffold.",
972
1065
  "",
@@ -1056,13 +1149,17 @@ function ensureTypeProperty(
1056
1149
  function ensureDeferredPort(source: string, portKey: string): string {
1057
1150
  const match = /\bdeferred\s*:\s*\[/.exec(source);
1058
1151
  if (!match) {
1059
- throw new Error("Could not find `deferred: [...]` in infra/app-ports.ts.");
1152
+ throw new Error(
1153
+ "Could not find `deferred: [...]` in infra/port-wiring.ts.",
1154
+ );
1060
1155
  }
1061
1156
 
1062
1157
  const openBracket = source.indexOf("[", match.index);
1063
1158
  const closeBracket = matchingDelimiterIndex(source, openBracket, "[", "]");
1064
1159
  if (closeBracket === -1) {
1065
- throw new Error("Could not parse `deferred` array in infra/app-ports.ts.");
1160
+ throw new Error(
1161
+ "Could not parse `deferred` array in infra/port-wiring.ts.",
1162
+ );
1066
1163
  }
1067
1164
 
1068
1165
  const arrayText = source.slice(openBracket, closeBracket + 1);
@@ -1140,13 +1237,13 @@ function sortRecord(record: Record<string, string>): Record<string, string> {
1140
1237
  }
1141
1238
 
1142
1239
  async function planFiles(
1143
- targetDir: string,
1144
- files: Array<{ path: string; content: string }>,
1240
+ files: ProviderPlanFile[],
1241
+ readSource: ProviderSourceReader,
1145
1242
  ): Promise<PlannedFile[]> {
1146
1243
  const planned: PlannedFile[] = [];
1147
1244
 
1148
1245
  for (const file of files) {
1149
- const existing = await readOptionalAppFile(targetDir, file.path);
1246
+ const existing = await readSource(file.path);
1150
1247
  planned.push({
1151
1248
  ...file,
1152
1249
  result:
@@ -1172,19 +1269,6 @@ async function writePlannedFile(
1172
1269
  await writeFile(destination, file.content);
1173
1270
  }
1174
1271
 
1175
- async function readRequiredAppFile(
1176
- targetDir: string,
1177
- filePath: string,
1178
- ): Promise<string> {
1179
- const content = await readOptionalAppFile(targetDir, filePath);
1180
- if (content === undefined) {
1181
- throw new Error(
1182
- `Could not find ${filePath}. Run this command in a Beignet app.`,
1183
- );
1184
- }
1185
- return content;
1186
- }
1187
-
1188
1272
  async function readOptionalAppFile(
1189
1273
  targetDir: string,
1190
1274
  filePath: string,
@@ -149,7 +149,7 @@ export async function auditProviders(
149
149
  ? await readCachedSource(targetDir, config.paths.ports, sourceCache)
150
150
  : "";
151
151
  const infrastructurePath = directoryPath(
152
- path.dirname(config.paths.infrastructurePorts),
152
+ path.dirname(config.paths.portWiring),
153
153
  );
154
154
  const schemaDir = `${infrastructurePath}/db/schema`;
155
155
 
@@ -1679,13 +1679,13 @@ function isInfraOrServerFile(
1679
1679
  ): boolean {
1680
1680
  const serverDir = directoryPath(path.dirname(config.paths.server));
1681
1681
  const infrastructureDir = directoryPath(
1682
- path.dirname(config.paths.infrastructurePorts),
1682
+ path.dirname(config.paths.portWiring),
1683
1683
  );
1684
1684
 
1685
1685
  return (
1686
1686
  file === config.paths.server ||
1687
1687
  file.startsWith(`${serverDir}/`) ||
1688
- file === config.paths.infrastructurePorts ||
1688
+ file === config.paths.portWiring ||
1689
1689
  file.startsWith(`${infrastructureDir}/`)
1690
1690
  );
1691
1691
  }