@beignet/cli 0.0.32 → 0.0.34

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 (92) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +29 -19
  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 +800 -60
  17. package/dist/inspect.js.map +1 -1
  18. package/dist/make/inbox.d.ts.map +1 -1
  19. package/dist/make/inbox.js +8 -6
  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 +61 -32
  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 +45 -11
  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 +9 -7
  30. package/dist/make/tenancy.js.map +1 -1
  31. package/dist/make.d.ts.map +1 -1
  32. package/dist/make.js +134 -60
  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 +91 -62
  42. package/dist/provider-add.js.map +1 -1
  43. package/dist/registry-edits.js +2 -1
  44. package/dist/registry-edits.js.map +1 -1
  45. package/dist/task.d.ts.map +1 -1
  46. package/dist/task.js +2 -3
  47. package/dist/task.js.map +1 -1
  48. package/dist/templates/agents.d.ts.map +1 -1
  49. package/dist/templates/agents.js +6 -3
  50. package/dist/templates/agents.js.map +1 -1
  51. package/dist/templates/base.js +6 -6
  52. package/dist/templates/base.js.map +1 -1
  53. package/dist/templates/index.d.ts.map +1 -1
  54. package/dist/templates/index.js +2 -0
  55. package/dist/templates/index.js.map +1 -1
  56. package/dist/templates/server.d.ts.map +1 -1
  57. package/dist/templates/server.js +17 -10
  58. package/dist/templates/server.js.map +1 -1
  59. package/dist/templates/shared.d.ts +3 -1
  60. package/dist/templates/shared.d.ts.map +1 -1
  61. package/dist/templates/shared.js +11 -1
  62. package/dist/templates/shared.js.map +1 -1
  63. package/dist/templates/testing.d.ts +5 -0
  64. package/dist/templates/testing.d.ts.map +1 -0
  65. package/dist/templates/testing.js +542 -0
  66. package/dist/templates/testing.js.map +1 -0
  67. package/dist/templates/todos.js +4 -4
  68. package/package.json +3 -2
  69. package/skills/app-structure/SKILL.md +14 -4
  70. package/src/choices.ts +3 -0
  71. package/src/config.ts +2 -0
  72. package/src/db.ts +1 -1
  73. package/src/index.ts +437 -1
  74. package/src/inspect.ts +1248 -156
  75. package/src/make/inbox.ts +8 -6
  76. package/src/make/payments.ts +61 -32
  77. package/src/make/shared.ts +79 -12
  78. package/src/make/tenancy.ts +9 -7
  79. package/src/make.ts +177 -59
  80. package/src/outbox.ts +363 -0
  81. package/src/preflight.ts +1 -1
  82. package/src/provider-add.ts +92 -62
  83. package/src/registry-edits.ts +2 -1
  84. package/src/task.ts +2 -3
  85. package/src/templates/agents.ts +6 -3
  86. package/src/templates/base.ts +6 -6
  87. package/src/templates/index.ts +2 -0
  88. package/src/templates/server.ts +17 -10
  89. package/src/templates/shared.ts +13 -1
  90. package/src/templates/testing.ts +545 -0
  91. package/src/templates/todos.ts +4 -4
  92. package/src/test-helpers/generated-app.ts +10 -6
@@ -76,9 +76,9 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
76
76
  "flags-openfeature": {
77
77
  name: "flags-openfeature",
78
78
  displayName: "OpenFeature flags",
79
- importName: "openFeatureFlagsProvider",
79
+ importName: "createOpenFeatureFlagsProvider",
80
80
  importFrom: "@beignet/provider-flags-openfeature",
81
- providerEntry: "openFeatureFlagsProvider",
81
+ providerEntry: "createOpenFeatureFlagsProvider()",
82
82
  dependencies: (packageJson) => ({
83
83
  "@beignet/provider-flags-openfeature":
84
84
  beignetDependencyVersion(packageJson),
@@ -97,7 +97,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
97
97
  "",
98
98
  "- Package: `@beignet/provider-flags-openfeature`",
99
99
  "- Peer dependency: `@openfeature/server-sdk`",
100
- "- The preset wires `openFeatureFlagsProvider` in `server/providers.ts` and defers `flags` in `infra/app-ports.ts`.",
100
+ "- The preset wires `createOpenFeatureFlagsProvider()` in `server/providers.ts` and defers `flags` in `infra/app-ports.ts`.",
101
101
  "- Configure the OpenFeature provider globally or replace this preset with `createOpenFeatureFlagsProvider(...)` when the app needs provider-specific setup.",
102
102
  "- Use `ctx.ports.flags` in app code; `ctx.ports.openFeature` is available as an escape hatch.",
103
103
  "",
@@ -111,9 +111,9 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
111
111
  "jobs-bullmq": {
112
112
  name: "jobs-bullmq",
113
113
  displayName: "BullMQ jobs",
114
- importName: "bullMQJobsProvider",
114
+ importName: "createBullMQJobsProvider",
115
115
  importFrom: "@beignet/provider-jobs-bullmq",
116
- providerEntry: "bullMQJobsProvider",
116
+ providerEntry: "createBullMQJobsProvider()",
117
117
  dependencies: (packageJson) => ({
118
118
  "@beignet/provider-jobs-bullmq": beignetDependencyVersion(packageJson),
119
119
  bullmq: externalVersions.bullmq,
@@ -138,7 +138,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
138
138
  "",
139
139
  "- Package: `@beignet/provider-jobs-bullmq`",
140
140
  "- Peer dependency: `bullmq`",
141
- "- The preset wires `bullMQJobsProvider` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
141
+ "- The preset wires `createBullMQJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
142
142
  "- Set `BULLMQ_REDIS_URL` before booting the app; the default points at local Redis.",
143
143
  "- Dispatching enqueues jobs, but nothing executes them until a worker entrypoint runs `createBullMQJobWorker(...)` in a worker process. See the jobs docs page.",
144
144
  "- Use `ctx.ports.jobs.dispatch(...)` in app code; `ctx.ports.bullMQJobs` is available as an escape hatch.",
@@ -150,18 +150,14 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
150
150
  "Add a worker entrypoint that runs createBullMQJobWorker(...) in a worker process; dispatched jobs do not execute without one. See the jobs docs page.",
151
151
  "Run beignet providers audit and beignet doctor --strict.",
152
152
  ],
153
- conflictingProviderEntries: [
154
- "inngestJobsProvider",
155
- "createInngestJobsProvider",
156
- "createBullMQJobsProvider",
157
- ],
153
+ conflictingProviderEntries: ["createInngestJobsProvider"],
158
154
  },
159
155
  "jobs-inngest": {
160
156
  name: "jobs-inngest",
161
157
  displayName: "Inngest jobs",
162
- importName: "inngestJobsProvider",
158
+ importName: "createInngestJobsProvider",
163
159
  importFrom: "@beignet/provider-jobs-inngest",
164
- providerEntry: "inngestJobsProvider",
160
+ providerEntry: "createInngestJobsProvider()",
165
161
  dependencies: (packageJson) => ({
166
162
  "@beignet/provider-jobs-inngest": beignetDependencyVersion(packageJson),
167
163
  inngest: externalVersions.inngest,
@@ -184,7 +180,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
184
180
  "",
185
181
  "- Package: `@beignet/provider-jobs-inngest`",
186
182
  "- Peer dependency: `inngest`",
187
- "- The preset wires `inngestJobsProvider` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
183
+ "- The preset wires `createInngestJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
188
184
  "- Define jobs with `defineJob(...)` and dispatch them through `ctx.ports.jobs.dispatch(...)`.",
189
185
  "- Execution needs an Inngest serve route built from `createInngestJobFunction(...)` and `serve(...)`. See the jobs docs page.",
190
186
  "- Use `ctx.ports.inngest` only as an escape hatch for Inngest-specific workflow APIs; prefer `ctx.ports.jobs.dispatch(...)` for Beignet jobs.",
@@ -196,17 +192,14 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
196
192
  "Expose an Inngest serve route with createInngestJobFunction(...) so dispatched jobs execute. See the jobs docs page.",
197
193
  "Run beignet providers audit and beignet doctor --strict.",
198
194
  ],
199
- conflictingProviderEntries: [
200
- "bullMQJobsProvider",
201
- "createBullMQJobsProvider",
202
- ],
195
+ conflictingProviderEntries: ["createBullMQJobsProvider"],
203
196
  },
204
197
  "mail-resend": {
205
198
  name: "mail-resend",
206
199
  displayName: "Resend mail",
207
- importName: "resendMailProvider",
200
+ importName: "createResendMailProvider",
208
201
  importFrom: "@beignet/provider-mail-resend",
209
- providerEntry: "resendMailProvider",
202
+ providerEntry: "createResendMailProvider()",
210
203
  dependencies: (packageJson) => ({
211
204
  "@beignet/provider-mail-resend": beignetDependencyVersion(packageJson),
212
205
  resend: externalVersions.resend,
@@ -229,7 +222,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
229
222
  "",
230
223
  "- Package: `@beignet/provider-mail-resend`",
231
224
  "- Peer dependency: `resend`",
232
- "- The preset wires `resendMailProvider` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
225
+ "- The preset wires `createResendMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
233
226
  "- `.env.example` ships dev placeholders so the app boots; set real `RESEND_API_KEY` and `RESEND_FROM` values before sending mail.",
234
227
  "- Use `ctx.ports.mailer` in app code; `ctx.ports.resend` is available as an escape hatch.",
235
228
  "",
@@ -240,16 +233,16 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
240
233
  "Run beignet providers audit and beignet doctor --strict.",
241
234
  ],
242
235
  conflictingProviderEntries: [
243
- "smtpMailProvider",
236
+ "createSmtpMailProvider",
244
237
  "createMemoryMailerProvider",
245
238
  ],
246
239
  },
247
240
  "mail-smtp": {
248
241
  name: "mail-smtp",
249
242
  displayName: "SMTP mail",
250
- importName: "smtpMailProvider",
243
+ importName: "createSmtpMailProvider",
251
244
  importFrom: "@beignet/provider-mail-smtp",
252
- providerEntry: "smtpMailProvider",
245
+ providerEntry: "createSmtpMailProvider()",
253
246
  dependencies: (packageJson) => ({
254
247
  "@beignet/provider-mail-smtp": beignetDependencyVersion(packageJson),
255
248
  nodemailer: externalVersions.nodemailer,
@@ -275,7 +268,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
275
268
  "",
276
269
  "- Package: `@beignet/provider-mail-smtp`",
277
270
  "- Peer dependency: `nodemailer`",
278
- "- The preset wires `smtpMailProvider` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
271
+ "- The preset wires `createSmtpMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
279
272
  "- `.env.example` ships dev placeholders so the app boots; set real `MAIL_*` values before sending mail.",
280
273
  "- Use `ctx.ports.mailer` in app code; `ctx.ports.smtp` is available as an escape hatch.",
281
274
  "",
@@ -286,16 +279,16 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
286
279
  "Run beignet providers audit and beignet doctor --strict.",
287
280
  ],
288
281
  conflictingProviderEntries: [
289
- "resendMailProvider",
282
+ "createResendMailProvider",
290
283
  "createMemoryMailerProvider",
291
284
  ],
292
285
  },
293
286
  "payments-stripe": {
294
287
  name: "payments-stripe",
295
288
  displayName: "Stripe payments",
296
- importName: "stripePaymentsProvider",
289
+ importName: "createStripePaymentsProvider",
297
290
  importFrom: "@beignet/provider-payments-stripe",
298
- providerEntry: "stripePaymentsProvider",
291
+ providerEntry: "createStripePaymentsProvider()",
299
292
  dependencies: (packageJson) => ({
300
293
  "@beignet/provider-payments-stripe":
301
294
  beignetDependencyVersion(packageJson),
@@ -324,7 +317,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
324
317
  "",
325
318
  "- Package: `@beignet/provider-payments-stripe`",
326
319
  "- Peer dependency: `stripe`",
327
- "- The preset wires `stripePaymentsProvider` in `server/providers.ts` and defers `payments` in `infra/app-ports.ts`.",
320
+ "- The preset wires `createStripePaymentsProvider()` in `server/providers.ts` and defers `payments` in `infra/app-ports.ts`.",
328
321
  "- `.env.example` ships dev placeholders so the app boots; set real `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` values before taking payments.",
329
322
  "- Stripe requires a webhook route: run `beignet make payments` for the full billing slice, including a `createPaymentWebhookRoute(...)` route.",
330
323
  "- Use `ctx.ports.payments` in app code; `ctx.ports.stripe` is available as an escape hatch.",
@@ -336,17 +329,14 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
336
329
  "Run beignet make payments for the full billing slice; Stripe needs its webhook route before subscription state can sync.",
337
330
  "Run beignet providers audit and beignet doctor --strict.",
338
331
  ],
339
- conflictingProviderEntries: [
340
- "createMemoryPaymentsProvider",
341
- "createStripePaymentsProvider",
342
- ],
332
+ conflictingProviderEntries: ["createMemoryPaymentsProvider"],
343
333
  },
344
334
  "search-meilisearch": {
345
335
  name: "search-meilisearch",
346
336
  displayName: "Meilisearch search",
347
- importName: "meilisearchSearchProvider",
337
+ importName: "createMeilisearchSearchProvider",
348
338
  importFrom: "@beignet/provider-search-meilisearch",
349
- providerEntry: "meilisearchSearchProvider",
339
+ providerEntry: "createMeilisearchSearchProvider()",
350
340
  dependencies: (packageJson) => ({
351
341
  "@beignet/provider-search-meilisearch":
352
342
  beignetDependencyVersion(packageJson),
@@ -370,7 +360,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
370
360
  "## Meilisearch search",
371
361
  "",
372
362
  "- Package: `@beignet/provider-search-meilisearch`",
373
- "- The preset wires `meilisearchSearchProvider` in `server/providers.ts` and defers `search` in `infra/app-ports.ts`.",
363
+ "- The preset wires `createMeilisearchSearchProvider()` in `server/providers.ts` and defers `search` in `infra/app-ports.ts`.",
374
364
  "- Set `MEILISEARCH_HOST` before booting the app; set `MEILISEARCH_API_KEY` when your Meilisearch server requires one.",
375
365
  "- Use `ctx.ports.search` in app code; `ctx.ports.meilisearch` is available as an escape hatch.",
376
366
  "",
@@ -384,9 +374,9 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
384
374
  sentry: {
385
375
  name: "sentry",
386
376
  displayName: "Sentry error reporting",
387
- importName: "sentryErrorReportingProvider",
377
+ importName: "createSentryErrorReportingProvider",
388
378
  importFrom: "@beignet/provider-error-reporting-sentry",
389
- providerEntry: "sentryErrorReportingProvider",
379
+ providerEntry: "createSentryErrorReportingProvider()",
390
380
  dependencies: (packageJson) => ({
391
381
  "@beignet/provider-error-reporting-sentry":
392
382
  beignetDependencyVersion(packageJson),
@@ -405,7 +395,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
405
395
  "",
406
396
  "- Package: `@beignet/provider-error-reporting-sentry`",
407
397
  "- Peer dependency: `@sentry/node`",
408
- "- The preset wires `sentryErrorReportingProvider` in `server/providers.ts`.",
398
+ "- The preset wires `createSentryErrorReportingProvider()` in `server/providers.ts`.",
409
399
  "- Set `SENTRY_DSN` before deploying when captures should be sent to Sentry.",
410
400
  "- Use `ctx.ports.errorReporter` in app code; `ctx.ports.sentry` is available as an escape hatch.",
411
401
  "",
@@ -419,9 +409,9 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
419
409
  "redis-cache": {
420
410
  name: "redis-cache",
421
411
  displayName: "Redis cache",
422
- importName: "redisCacheProvider",
412
+ importName: "createRedisCacheProvider",
423
413
  importFrom: "@beignet/provider-cache-redis",
424
- providerEntry: "redisCacheProvider",
414
+ providerEntry: "createRedisCacheProvider()",
425
415
  dependencies: (packageJson) => ({
426
416
  "@beignet/provider-cache-redis": beignetDependencyVersion(packageJson),
427
417
  ioredis: externalVersions.ioredis,
@@ -444,7 +434,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
444
434
  "",
445
435
  "- Package: `@beignet/provider-cache-redis`",
446
436
  "- Peer dependency: `ioredis`",
447
- "- The preset wires `redisCacheProvider` in `server/providers.ts` and defers `cache` in `infra/app-ports.ts`.",
437
+ "- The preset wires `createRedisCacheProvider()` in `server/providers.ts` and defers `cache` in `infra/app-ports.ts`.",
448
438
  "- Set `REDIS_URL` before booting the app.",
449
439
  "- Use `ctx.ports.cache` in app code; `ctx.ports.redis` is available as an escape hatch.",
450
440
  "",
@@ -455,12 +445,56 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
455
445
  "Run beignet providers audit and beignet doctor --strict.",
456
446
  ],
457
447
  },
448
+ "event-bus-redis": {
449
+ name: "event-bus-redis",
450
+ displayName: "Redis event bus",
451
+ importName: "createRedisEventBusProvider",
452
+ importFrom: "@beignet/provider-event-bus-redis",
453
+ providerEntry: "createRedisEventBusProvider()",
454
+ dependencies: (packageJson) => ({
455
+ "@beignet/provider-event-bus-redis":
456
+ beignetDependencyVersion(packageJson),
457
+ ioredis: externalVersions.ioredis,
458
+ }),
459
+ appPort: {
460
+ key: "eventBus",
461
+ typeName: "EventBusPort",
462
+ importName: "EventBusPort",
463
+ importFrom: "@beignet/core/ports",
464
+ },
465
+ envVars: ["REDIS_EVENT_BUS_URL"],
466
+ envExample: [
467
+ "# Redis event bus provider",
468
+ "REDIS_EVENT_BUS_URL=redis://localhost:6379",
469
+ "# REDIS_EVENT_BUS_DB=0",
470
+ "# REDIS_EVENT_BUS_CHANNEL_PREFIX=beignet:event-bus:",
471
+ "",
472
+ ].join("\n"),
473
+ docs: [
474
+ "## Redis event bus",
475
+ "",
476
+ "- Package: `@beignet/provider-event-bus-redis`",
477
+ "- Peer dependency: `ioredis`",
478
+ "- The preset wires `createRedisEventBusProvider()` in `server/providers.ts` and defers `eventBus` in `infra/app-ports.ts`.",
479
+ "- Set `REDIS_EVENT_BUS_URL` before booting the app.",
480
+ "- 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
+ "- Use `ctx.ports.eventBus` in app code; `ctx.ports.redisEventBus` is available as an escape hatch.",
482
+ "",
483
+ ].join("\n"),
484
+ nextSteps: [
485
+ "Run your package manager install command.",
486
+ "Set REDIS_EVENT_BUS_URL in .env.local or your deployment environment.",
487
+ "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.",
489
+ ],
490
+ conflictingProviderEntries: ["createMemoryEventBusProvider"],
491
+ },
458
492
  "redis-locks": {
459
493
  name: "redis-locks",
460
494
  displayName: "Redis locks",
461
- importName: "redisLocksProvider",
495
+ importName: "createRedisLocksProvider",
462
496
  importFrom: "@beignet/provider-locks-redis",
463
- providerEntry: "redisLocksProvider",
497
+ providerEntry: "createRedisLocksProvider()",
464
498
  dependencies: (packageJson) => ({
465
499
  "@beignet/provider-locks-redis": beignetDependencyVersion(packageJson),
466
500
  ioredis: externalVersions.ioredis,
@@ -484,7 +518,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
484
518
  "",
485
519
  "- Package: `@beignet/provider-locks-redis`",
486
520
  "- Peer dependency: `ioredis`",
487
- "- The preset wires `redisLocksProvider` in `server/providers.ts` and defers `locks` in `infra/app-ports.ts`.",
521
+ "- The preset wires `createRedisLocksProvider()` in `server/providers.ts` and defers `locks` in `infra/app-ports.ts`.",
488
522
  "- Set `REDIS_LOCKS_URL` before booting the app.",
489
523
  "- Use `ctx.ports.locks` in app code; `ctx.ports.redisLocks` is available as an escape hatch.",
490
524
  "",
@@ -498,9 +532,9 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
498
532
  "s3-storage": {
499
533
  name: "s3-storage",
500
534
  displayName: "S3-compatible storage",
501
- importName: "s3StorageProvider",
535
+ importName: "createS3StorageProvider",
502
536
  importFrom: "@beignet/provider-storage-s3",
503
- providerEntry: "s3StorageProvider",
537
+ providerEntry: "createS3StorageProvider()",
504
538
  dependencies: (packageJson) => ({
505
539
  "@beignet/provider-storage-s3": beignetDependencyVersion(packageJson),
506
540
  "@aws-sdk/client-s3": externalVersions.awsSdkS3,
@@ -545,7 +579,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
545
579
  "",
546
580
  "- Package: `@beignet/provider-storage-s3`",
547
581
  "- Peer dependencies: `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`",
548
- "- The preset wires `s3StorageProvider` in `server/providers.ts` and defers `storage` in `infra/app-ports.ts`.",
582
+ "- The preset wires `createS3StorageProvider()` in `server/providers.ts` and defers `storage` in `infra/app-ports.ts`.",
549
583
  "- Set `STORAGE_S3_BUCKET` before booting the app; set endpoint and credentials when using an S3-compatible host outside AWS defaults.",
550
584
  "- Use `ctx.ports.storage` in app code; `ctx.ports.s3Storage` is available as an escape hatch.",
551
585
  "",
@@ -556,18 +590,16 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
556
590
  "Run beignet providers audit and beignet doctor --strict.",
557
591
  ],
558
592
  conflictingProviderEntries: [
559
- "localStorageProvider",
560
593
  "createLocalStorageProvider",
561
- "vercelBlobStorageProvider",
562
594
  "createVercelBlobStorageProvider",
563
595
  ],
564
596
  },
565
597
  "vercel-blob-storage": {
566
598
  name: "vercel-blob-storage",
567
599
  displayName: "Vercel Blob storage",
568
- importName: "vercelBlobStorageProvider",
600
+ importName: "createVercelBlobStorageProvider",
569
601
  importFrom: "@beignet/provider-storage-vercel-blob",
570
- providerEntry: "vercelBlobStorageProvider",
602
+ providerEntry: "createVercelBlobStorageProvider()",
571
603
  dependencies: (packageJson) => ({
572
604
  "@beignet/provider-storage-vercel-blob":
573
605
  beignetDependencyVersion(packageJson),
@@ -593,7 +625,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
593
625
  "",
594
626
  "- Package: `@beignet/provider-storage-vercel-blob`",
595
627
  "- Peer dependency: `@vercel/blob`",
596
- "- The preset wires `vercelBlobStorageProvider` in `server/providers.ts` and defers `storage` in `infra/app-ports.ts`.",
628
+ "- The preset wires `createVercelBlobStorageProvider()` in `server/providers.ts` and defers `storage` in `infra/app-ports.ts`.",
597
629
  "- On Vercel, connect a Blob store to the project and `BLOB_READ_WRITE_TOKEN` is set automatically; set it manually elsewhere.",
598
630
  "- 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.",
599
631
  "- Use `ctx.ports.storage` in app code; `ctx.ports.vercelBlob` is available as an escape hatch.",
@@ -605,18 +637,16 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
605
637
  "Run beignet providers audit and beignet doctor --strict.",
606
638
  ],
607
639
  conflictingProviderEntries: [
608
- "localStorageProvider",
609
640
  "createLocalStorageProvider",
610
- "s3StorageProvider",
611
641
  "createS3StorageProvider",
612
642
  ],
613
643
  },
614
644
  "upstash-rate-limit": {
615
645
  name: "upstash-rate-limit",
616
646
  displayName: "Upstash rate limit",
617
- importName: "upstashRateLimitProvider",
647
+ importName: "createUpstashRateLimitProvider",
618
648
  importFrom: "@beignet/provider-rate-limit-upstash",
619
- providerEntry: "upstashRateLimitProvider",
649
+ providerEntry: "createUpstashRateLimitProvider()",
620
650
  dependencies: (packageJson) => ({
621
651
  "@beignet/provider-rate-limit-upstash":
622
652
  beignetDependencyVersion(packageJson),
@@ -643,7 +673,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
643
673
  "",
644
674
  "- Package: `@beignet/provider-rate-limit-upstash`",
645
675
  "- Peer dependencies: `@upstash/ratelimit` and `@upstash/redis`",
646
- "- The preset wires `upstashRateLimitProvider` in `server/providers.ts` and defers `rateLimit` in `infra/app-ports.ts`.",
676
+ "- The preset wires `createUpstashRateLimitProvider()` in `server/providers.ts` and defers `rateLimit` in `infra/app-ports.ts`.",
647
677
  "- `.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.",
648
678
  "- Use `ctx.ports.rateLimit` in app code; `ctx.ports.upstash` is available as an escape hatch.",
649
679
  "",
@@ -663,9 +693,9 @@ const providerPresetSet = new Set<string>(providerPresetChoices);
663
693
  * integration, used to pre-validate preset selections at create time.
664
694
  */
665
695
  const integrationProviderEntries: Record<IntegrationName, string> = {
666
- inngest: "inngestJobsProvider",
667
- resend: "resendMailProvider",
668
- "upstash-rate-limit": "upstashRateLimitProvider",
696
+ inngest: "createInngestJobsProvider",
697
+ resend: "createResendMailProvider",
698
+ "upstash-rate-limit": "createUpstashRateLimitProvider",
669
699
  };
670
700
 
671
701
  /**
@@ -688,7 +718,7 @@ export function assertCompatibleCreatePresets(
688
718
  selectedEntries.set(integrationProviderEntries[integration], integration);
689
719
  }
690
720
  for (const name of presetNames) {
691
- selectedEntries.set(presets[name].providerEntry, name);
721
+ selectedEntries.set(presets[name].importName, name);
692
722
  }
693
723
 
694
724
  for (const name of presetNames) {
@@ -300,7 +300,8 @@ export function appendToOutboxRegistryArray(
300
300
  }
301
301
 
302
302
  function entryIdentifier(entry: string): string {
303
- return entry.replace(/^\.{3}/, "").trim();
303
+ const normalized = entry.replace(/^\.{3}/, "").trim();
304
+ return /^[A-Za-z_$][\w$]*/.exec(normalized)?.[0] ?? normalized;
304
305
  }
305
306
 
306
307
  function appendEntry(
package/src/task.ts CHANGED
@@ -50,7 +50,7 @@ export async function runAppTask(
50
50
  const task = findTask(taskModule.tasks, options.name, modulePath);
51
51
  // Loaded lazily so CLI commands that never touch the app runtime, such as
52
52
  // `beignet lint`, work without a built @beignet/core.
53
- const { parseTaskInput } = await import("@beignet/core/tasks");
53
+ const { parseTaskInput, runTask } = await import("@beignet/core/tasks");
54
54
  const parsedInput = await parseTaskInput(task, rawInput);
55
55
  const contextArgs: TaskRunContextArgs = {
56
56
  task,
@@ -63,8 +63,7 @@ export async function runAppTask(
63
63
  : {};
64
64
 
65
65
  try {
66
- const output = await task.handle({
67
- task,
66
+ const output = await runTask(task, {
68
67
  input: parsedInput,
69
68
  ctx,
70
69
  });
@@ -27,7 +27,9 @@ export function agentsMd(ctx: TemplateContext): string {
27
27
  const intent = intentRunner(ctx);
28
28
  const capabilityClientRow = ctx.api
29
29
  ? ""
30
- : "\n| Query cache keys and invalidation | `rq(contract).invalidate(queryClient, params?)`, `.key(...)`, `.filter(...)` from `client/` (`@beignet/react-query`). Thin named wrappers in `features/<feature>/client/` are the convention — hand-built key arrays are not. |";
30
+ : "\n| Query cache keys and invalidation | `rq(contract).invalidate(queryClient, params?)`, `.key(...)`, `.filter(...)` from `client/` (`@beignet/react-query`). Thin named wrappers in `features/<feature>/client/` are the convention — hand-built key arrays are not. |" +
31
+ "\n| Server Component request context | Use an app-owned server-only helper such as `lib/server-context.ts` that wraps `server.createContextFromNext()` in React `cache(...)`. Layouts may read `ctx.auth`, `ctx.tenant`, and request metadata for redirects and shell state; feature data still belongs behind use cases. |" +
32
+ "\n| Server Component React Query prefetch | Use an app-owned helper such as `lib/server-react-query.ts` on top of the request context: keep the `rq(contract).queryOptions(...)` key, replace only the server `queryFn` with the direct use case call, and do not create a parallel `rqServer` abstraction. |";
31
33
  const specificSkills = ctx.api
32
34
  ? "`@beignet/next#routes-server`, `@beignet/provider-db-drizzle#database-provider`, `@beignet/provider-auth-better-auth#auth-provider`, or `@beignet/cli#app-structure`"
33
35
  : "`@beignet/next#routes-server`, `@beignet/react-query#client`, `@beignet/react-hook-form#forms`, `@beignet/provider-db-drizzle#database-provider`, `@beignet/provider-auth-better-auth#auth-provider`, or `@beignet/cli#app-structure`";
@@ -50,8 +52,8 @@ is a silent failure — the file exists but never runs:
50
52
  - Tasks must be added to \`defineTasks([...])\` in \`server/tasks.ts\`.
51
53
  - Outbox events and jobs must be registered in \`defineOutboxRegistry({...})\`
52
54
  in \`server/outbox.ts\`.
53
- - Listeners must be wired through a \`registerListeners(...)\` call in infra
54
- wiring.
55
+ - Listeners must be added to the \`listeners\` array in \`server/listeners.ts\`
56
+ and wired through a \`registerListeners(...)\` call in server provider wiring.
55
57
 
56
58
  The starter ships no workflow registries — generators create them on first
57
59
  use, so their absence is fine. \`${cli} make event\`, \`${cli} make job\`,
@@ -81,6 +83,7 @@ apps have reimplemented by accident:
81
83
  | Ports outside a request — auth callbacks, module-level helpers | \`const { ports } = await getServer()\` (dynamic \`import("@/server")\` breaks module cycles). Do not construct parallel provider clients or fall back to \`console.*\` when \`ports.logger\` exists. |
82
84
  | Routes that cannot be contracts — webhooks, third-party callbacks, streaming | \`createWebhookRoute\`, \`createPaymentWebhookRoute\`, \`createScheduleRoute\`, \`createOutboxDrainRoute\` from \`@beignet/next\`; \`server.rawRoute(...)\` for anything else. All run the hooks pipeline — never hand-enforce rate limits in a route body. |
83
85
  | Rate limiting or idempotency on a route | Declare \`metadata.rateLimit\` / \`metadata.idempotency\` on the contract (or the \`pipeline\` option on raw routes); hooks enforce it. |
86
+ | Tenant-owned repository access | Pass \`TenantScope\` through app-facing repository methods and unwrap it with \`tenantScopeId(scope)\` in adapters. Raw provider-correlation lookups such as webhook customer IDs are not tenant authorization. |
84
87
  | Route-level tests that exercise real hooks | \`createTestApp\` / \`createTestRequester\` from \`@beignet/web/testing\`. Bind the rate-limit and idempotency ports in the test app when asserting 429s or replay. |
85
88
  | Environment configuration | \`lib/env.ts\` (\`createEnv\`), never ad-hoc \`process.env\` reads in app code. |
86
89
  | The same lookup runs several times in one request — context, policy, use case | Wrap the repository read in \`createMemo(...)\` from \`@beignet/core/memo\` where the adapter is wired in infra. The cache lives for exactly one request; pair mutations with \`.invalidate(...)\`. Never hand-roll per-request caches. |
@@ -67,11 +67,11 @@ export function packageJson(ctx: TemplateContext): string {
67
67
  "@beignet/cli": ctx.beignetVersion,
68
68
  "@beignet/web": ctx.beignetVersion,
69
69
  "@biomejs/biome": externalVersions.biome,
70
- "@types/bun": externalVersions.typesBun,
71
70
  "@types/node": externalVersions.typesNode,
72
71
  "@types/react": externalVersions.typesReact,
73
72
  "@types/react-dom": externalVersions.typesReactDom,
74
73
  "drizzle-kit": externalVersions.drizzleKit,
74
+ tsx: externalVersions.tsx,
75
75
  typescript: externalVersions.typescript,
76
76
  };
77
77
 
@@ -97,11 +97,11 @@ export function packageJson(ctx: TemplateContext): string {
97
97
  start: "next start",
98
98
  lint: "biome lint .",
99
99
  format: "biome format --write .",
100
- test: "bun test",
100
+ test: "tsx lib/beignet-test-runner.ts",
101
101
  typecheck: "tsc --noEmit",
102
102
  "db:generate": "drizzle-kit generate",
103
103
  "db:migrate": "drizzle-kit migrate",
104
- "db:reset": "bun infra/db/reset.ts",
104
+ "db:reset": "tsx infra/db/reset.ts",
105
105
  };
106
106
 
107
107
  return json({
@@ -390,7 +390,7 @@ export function integrationsDoc(ctx: TemplateContext): string {
390
390
  "- Package: `@beignet/core` (`@beignet/core/events`, `@beignet/core/jobs`)",
391
391
  "- Package: `@beignet/provider-jobs-inngest`",
392
392
  "- Peer dependency: `inngest`",
393
- "- The starter wires `inngestJobsProvider` in `server/providers.ts`.",
393
+ "- The starter wires `createInngestJobsProvider()` in `server/providers.ts`.",
394
394
  "- Define jobs with `defineJob(...)` and dispatch them through `ctx.ports.jobs.dispatch(...)`.",
395
395
  "- Use `ctx.ports.inngest` only as an escape hatch for Inngest-specific workflow APIs; prefer `ctx.ports.jobs.dispatch(...)` for Beignet jobs.",
396
396
  "",
@@ -403,7 +403,7 @@ export function integrationsDoc(ctx: TemplateContext): string {
403
403
  "",
404
404
  "- Package: `@beignet/provider-mail-resend`",
405
405
  "- Peer dependency: `resend`",
406
- "- The starter wires `resendMailProvider` in `server/providers.ts`.",
406
+ "- The starter wires `createResendMailProvider()` in `server/providers.ts`.",
407
407
  "- `.env.example` ships dev placeholders so the app boots; set real `RESEND_API_KEY` and `RESEND_FROM` values before sending mail.",
408
408
  "",
409
409
  );
@@ -415,7 +415,7 @@ export function integrationsDoc(ctx: TemplateContext): string {
415
415
  "",
416
416
  "- Package: `@beignet/provider-rate-limit-upstash`",
417
417
  "- Peer dependencies: `@upstash/ratelimit` and `@upstash/redis`",
418
- "- The starter wires `upstashRateLimitProvider` in `server/providers.ts` and adds `rateLimit: RateLimitPort` to `AppPorts`.",
418
+ "- The starter wires `createUpstashRateLimitProvider()` in `server/providers.ts` and adds `rateLimit: RateLimitPort` to `AppPorts`.",
419
419
  "- `.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.",
420
420
  "",
421
421
  );
@@ -28,6 +28,7 @@ import {
28
28
  import { shadcnTemplateFiles } from "./shadcn.js";
29
29
  import type { TemplateContext, TemplateFile } from "./shared.js";
30
30
  import { shellTemplateFiles } from "./shell.js";
31
+ import { testSupportTemplateFiles } from "./testing.js";
31
32
  import { todosFiles } from "./todos.js";
32
33
 
33
34
  /**
@@ -133,6 +134,7 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
133
134
  { path: "lib/auth.ts", content: serverFiles.authHelpers },
134
135
  { path: "lib/better-auth.ts", content: betterAuth(ctx) },
135
136
  { path: "lib/tenant.ts", content: serverFiles.tenantHelper },
137
+ ...testSupportTemplateFiles,
136
138
  { path: "lib/use-case.ts", content: serverFiles.useCaseBuilder },
137
139
  { path: "server/index.ts", content: server(ctx) },
138
140
  { path: "server/context.ts", content: serverFiles.serverContext },
@@ -13,14 +13,14 @@ export function serverProviders(ctx: TemplateContext): string {
13
13
  'import { createAuthBetterAuthProvider } from "@beignet/provider-auth-better-auth";',
14
14
  `import { ${db.providerFactory} } from "@beignet/provider-db-drizzle/${db.subpath}";`,
15
15
  hasIntegration(ctx, "inngest")
16
- ? 'import { inngestJobsProvider } from "@beignet/provider-jobs-inngest";'
16
+ ? 'import { createInngestJobsProvider } from "@beignet/provider-jobs-inngest";'
17
17
  : undefined,
18
- 'import { pinoLoggerProvider } from "@beignet/provider-logger-pino";',
18
+ 'import { createPinoLoggerProvider } from "@beignet/provider-logger-pino";',
19
19
  hasIntegration(ctx, "resend")
20
- ? 'import { resendMailProvider } from "@beignet/provider-mail-resend";'
20
+ ? 'import { createResendMailProvider } from "@beignet/provider-mail-resend";'
21
21
  : undefined,
22
22
  hasIntegration(ctx, "upstash-rate-limit")
23
- ? 'import { upstashRateLimitProvider } from "@beignet/provider-rate-limit-upstash";'
23
+ ? 'import { createUpstashRateLimitProvider } from "@beignet/provider-rate-limit-upstash";'
24
24
  : undefined,
25
25
  'import * as schema from "@/infra/db/schema";',
26
26
  'import { starterDatabaseProvider } from "@/infra/db/provider";',
@@ -31,13 +31,15 @@ export function serverProviders(ctx: TemplateContext): string {
31
31
  const entries = [
32
32
  "\tcreateDevtoolsProvider(),",
33
33
  "\tcreateAuthBetterAuthProvider<AuthUser, AuthSessionMetadata>(auth),",
34
- "\tpinoLoggerProvider,",
34
+ "\tcreatePinoLoggerProvider(),",
35
35
  `\t${db.providerInstance},`,
36
36
  "\tstarterDatabaseProvider,",
37
- hasIntegration(ctx, "inngest") ? "\tinngestJobsProvider," : undefined,
38
- hasIntegration(ctx, "resend") ? "\tresendMailProvider," : undefined,
37
+ hasIntegration(ctx, "inngest")
38
+ ? "\tcreateInngestJobsProvider(),"
39
+ : undefined,
40
+ hasIntegration(ctx, "resend") ? "\tcreateResendMailProvider()," : undefined,
39
41
  hasIntegration(ctx, "upstash-rate-limit")
40
- ? "\tupstashRateLimitProvider,"
42
+ ? "\tcreateUpstashRateLimitProvider(),"
41
43
  : undefined,
42
44
  ].filter((entry): entry is string => Boolean(entry));
43
45
 
@@ -297,7 +299,11 @@ export function server(ctx: TemplateContext): string {
297
299
 
298
300
  return `import "@beignet/core/server-only";
299
301
  import { createNextServer, createNextServerLoader } from "@beignet/next";
300
- import { createErrorReportingHooks, createIdempotencyHooks } from "@beignet/core/server";
302
+ import {
303
+ createErrorReportingHooks,
304
+ createIdempotencyHooks,
305
+ createSecurityHeadersHooks,
306
+ } from "@beignet/core/server";
301
307
  import type { AppContext } from "@/app-context";
302
308
  import { appPorts } from "@/infra/app-ports";
303
309
  import { env } from "@/lib/env";
@@ -314,6 +320,7 @@ export const getServer = createNextServerLoader(async () => {
314
320
  ${providerConfigEntries}
315
321
  },
316
322
  hooks: [
323
+ createSecurityHeadersHooks<AppContext>(),
317
324
  createErrorReportingHooks<AppContext>(),
318
325
  createIdempotencyHooks<AppContext>(),
319
326
  ],
@@ -440,7 +447,7 @@ export type AuthPort = BeignetAuthPort<
440
447
  >;
441
448
  `,
442
449
  serverRoutes: `import "@beignet/core/server-only";
443
- import { contractsFromRoutes, defineRoutes } from "@beignet/next";
450
+ import { contractsFromRoutes, defineRoutes } from "@beignet/core/server";
444
451
  import type { AppContext } from "@/app-context";
445
452
  import { todoRoutes } from "@/features/todos/routes";
446
453
 
@@ -35,8 +35,8 @@ export const externalVersions = {
35
35
  next: "^16.2.4",
36
36
  react: "^19.2.5",
37
37
  reactDom: "^19.2.5",
38
+ tsx: "^4.22.4",
38
39
  typescript: "^5.3.0",
39
- typesBun: "^1.3.13",
40
40
  typesNode: "^20.10.0",
41
41
  typesReact: "^19.0.0",
42
42
  typesReactDom: "^19.0.0",
@@ -67,6 +67,18 @@ export const externalVersions = {
67
67
  upstashRedis: "^1.0.0",
68
68
  };
69
69
 
70
+ export const legacyGeneratedPackageScripts: Record<string, string> = {
71
+ "db:reset": "bun infra/db/reset.ts",
72
+ "db:seed": "bun server/seed.ts",
73
+ test: "bun test",
74
+ };
75
+
76
+ export const currentGeneratedPackageScripts: Record<string, string> = {
77
+ "db:reset": "tsx infra/db/reset.ts",
78
+ "db:seed": "tsx server/seed.ts",
79
+ test: "tsx lib/beignet-test-runner.ts",
80
+ };
81
+
70
82
  /**
71
83
  * Dialect-specific names used when rendering database-aware templates.
72
84
  */