@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.
- package/CHANGELOG.md +107 -0
- package/README.md +70 -17
- package/dist/check.d.ts +2 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +16 -0
- package/dist/check.js.map +1 -1
- package/dist/choices.d.ts +22 -2
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +62 -0
- package/dist/choices.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/create-prompts.d.ts +6 -2
- package/dist/create-prompts.d.ts.map +1 -1
- package/dist/create-prompts.js +1 -1
- package/dist/create-prompts.js.map +1 -1
- package/dist/db.d.ts +5 -5
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +5 -5
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +386 -27
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +5 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +805 -62
- package/dist/inspect.js.map +1 -1
- package/dist/make/inbox.d.ts.map +1 -1
- package/dist/make/inbox.js +5 -3
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.d.ts.map +1 -1
- package/dist/make/payments.js +58 -29
- package/dist/make/payments.js.map +1 -1
- package/dist/make/shared.d.ts +12 -3
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +52 -11
- package/dist/make/shared.js.map +1 -1
- package/dist/make/tenancy.d.ts.map +1 -1
- package/dist/make/tenancy.js +6 -4
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +136 -65
- package/dist/make.js.map +1 -1
- package/dist/outbox.d.ts +114 -1
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +190 -0
- package/dist/outbox.js.map +1 -1
- package/dist/preflight.d.ts +90 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +423 -0
- package/dist/preflight.js.map +1 -0
- package/dist/provider-add.d.ts +10 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +255 -16
- package/dist/provider-add.js.map +1 -1
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +2 -3
- package/dist/task.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +6 -3
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +5 -11
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +2 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +10 -5
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +5 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +13 -1
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.d.ts +5 -0
- package/dist/templates/testing.d.ts.map +1 -0
- package/dist/templates/testing.js +542 -0
- package/dist/templates/testing.js.map +1 -0
- package/dist/templates/todos.js +2 -2
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +12 -4
- package/src/check.ts +23 -0
- package/src/choices.ts +84 -0
- package/src/config.ts +4 -0
- package/src/create-prompts.ts +8 -2
- package/src/db.ts +12 -12
- package/src/index.ts +592 -37
- package/src/inspect.ts +1253 -156
- package/src/make/inbox.ts +5 -3
- package/src/make/payments.ts +58 -29
- package/src/make/shared.ts +87 -13
- package/src/make/tenancy.ts +6 -4
- package/src/make.ts +179 -65
- package/src/outbox.ts +363 -0
- package/src/preflight.ts +596 -0
- package/src/provider-add.ts +270 -16
- package/src/task.ts +2 -3
- package/src/templates/agents.ts +6 -3
- package/src/templates/base.ts +5 -13
- package/src/templates/index.ts +2 -0
- package/src/templates/server.ts +10 -5
- package/src/templates/shared.ts +15 -1
- package/src/templates/testing.ts +545 -0
- package/src/templates/todos.ts +2 -2
- package/src/test-helpers/generated-app.ts +10 -6
package/src/provider-add.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type IntegrationName,
|
|
5
|
+
type ProviderPresetName,
|
|
6
|
+
providerPresetChoices,
|
|
7
|
+
} from "./choices.js";
|
|
4
8
|
import {
|
|
5
9
|
directoryPath,
|
|
6
10
|
loadBeignetConfig,
|
|
@@ -104,12 +108,105 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
104
108
|
"Run beignet providers audit and beignet doctor --strict.",
|
|
105
109
|
],
|
|
106
110
|
},
|
|
111
|
+
"jobs-bullmq": {
|
|
112
|
+
name: "jobs-bullmq",
|
|
113
|
+
displayName: "BullMQ jobs",
|
|
114
|
+
importName: "bullMQJobsProvider",
|
|
115
|
+
importFrom: "@beignet/provider-jobs-bullmq",
|
|
116
|
+
providerEntry: "bullMQJobsProvider",
|
|
117
|
+
dependencies: (packageJson) => ({
|
|
118
|
+
"@beignet/provider-jobs-bullmq": beignetDependencyVersion(packageJson),
|
|
119
|
+
bullmq: externalVersions.bullmq,
|
|
120
|
+
}),
|
|
121
|
+
appPort: {
|
|
122
|
+
key: "jobs",
|
|
123
|
+
typeName: "JobDispatcherPort",
|
|
124
|
+
importName: "JobDispatcherPort",
|
|
125
|
+
importFrom: "@beignet/core/ports",
|
|
126
|
+
},
|
|
127
|
+
envVars: ["BULLMQ_REDIS_URL"],
|
|
128
|
+
envExample: [
|
|
129
|
+
"# BullMQ jobs provider",
|
|
130
|
+
"BULLMQ_REDIS_URL=redis://localhost:6379",
|
|
131
|
+
"# BULLMQ_QUEUE_NAME=beignet-jobs",
|
|
132
|
+
"# BULLMQ_PREFIX=beignet",
|
|
133
|
+
"# BULLMQ_CONCURRENCY=5",
|
|
134
|
+
"",
|
|
135
|
+
].join("\n"),
|
|
136
|
+
docs: [
|
|
137
|
+
"## BullMQ jobs",
|
|
138
|
+
"",
|
|
139
|
+
"- Package: `@beignet/provider-jobs-bullmq`",
|
|
140
|
+
"- Peer dependency: `bullmq`",
|
|
141
|
+
"- The preset wires `bullMQJobsProvider` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
|
|
142
|
+
"- Set `BULLMQ_REDIS_URL` before booting the app; the default points at local Redis.",
|
|
143
|
+
"- Dispatching enqueues jobs, but nothing executes them until a worker entrypoint runs `createBullMQJobWorker(...)` in a worker process. See the jobs docs page.",
|
|
144
|
+
"- Use `ctx.ports.jobs.dispatch(...)` in app code; `ctx.ports.bullMQJobs` is available as an escape hatch.",
|
|
145
|
+
"",
|
|
146
|
+
].join("\n"),
|
|
147
|
+
nextSteps: [
|
|
148
|
+
"Run your package manager install command.",
|
|
149
|
+
"Set BULLMQ_REDIS_URL in .env.local or your deployment environment.",
|
|
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
|
+
"Run beignet providers audit and beignet doctor --strict.",
|
|
152
|
+
],
|
|
153
|
+
conflictingProviderEntries: [
|
|
154
|
+
"inngestJobsProvider",
|
|
155
|
+
"createInngestJobsProvider",
|
|
156
|
+
"createBullMQJobsProvider",
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
"jobs-inngest": {
|
|
160
|
+
name: "jobs-inngest",
|
|
161
|
+
displayName: "Inngest jobs",
|
|
162
|
+
importName: "inngestJobsProvider",
|
|
163
|
+
importFrom: "@beignet/provider-jobs-inngest",
|
|
164
|
+
providerEntry: "inngestJobsProvider",
|
|
165
|
+
dependencies: (packageJson) => ({
|
|
166
|
+
"@beignet/provider-jobs-inngest": beignetDependencyVersion(packageJson),
|
|
167
|
+
inngest: externalVersions.inngest,
|
|
168
|
+
}),
|
|
169
|
+
appPort: {
|
|
170
|
+
key: "jobs",
|
|
171
|
+
typeName: "JobDispatcherPort",
|
|
172
|
+
importName: "JobDispatcherPort",
|
|
173
|
+
importFrom: "@beignet/core/ports",
|
|
174
|
+
},
|
|
175
|
+
envVars: ["INNGEST_APP_NAME", "INNGEST_EVENT_KEY"],
|
|
176
|
+
envExample: [
|
|
177
|
+
"# Inngest jobs provider",
|
|
178
|
+
"INNGEST_APP_NAME=beignet-app",
|
|
179
|
+
"# INNGEST_EVENT_KEY=",
|
|
180
|
+
"",
|
|
181
|
+
].join("\n"),
|
|
182
|
+
docs: [
|
|
183
|
+
"## Inngest jobs",
|
|
184
|
+
"",
|
|
185
|
+
"- Package: `@beignet/provider-jobs-inngest`",
|
|
186
|
+
"- Peer dependency: `inngest`",
|
|
187
|
+
"- The preset wires `inngestJobsProvider` in `server/providers.ts` and defers `jobs` in `infra/app-ports.ts`.",
|
|
188
|
+
"- Define jobs with `defineJob(...)` and dispatch them through `ctx.ports.jobs.dispatch(...)`.",
|
|
189
|
+
"- Execution needs an Inngest serve route built from `createInngestJobFunction(...)` and `serve(...)`. See the jobs docs page.",
|
|
190
|
+
"- Use `ctx.ports.inngest` only as an escape hatch for Inngest-specific workflow APIs; prefer `ctx.ports.jobs.dispatch(...)` for Beignet jobs.",
|
|
191
|
+
"",
|
|
192
|
+
].join("\n"),
|
|
193
|
+
nextSteps: [
|
|
194
|
+
"Run your package manager install command.",
|
|
195
|
+
"Set INNGEST_APP_NAME and, for Inngest cloud, INNGEST_EVENT_KEY in .env.local or your deployment environment.",
|
|
196
|
+
"Expose an Inngest serve route with createInngestJobFunction(...) so dispatched jobs execute. See the jobs docs page.",
|
|
197
|
+
"Run beignet providers audit and beignet doctor --strict.",
|
|
198
|
+
],
|
|
199
|
+
conflictingProviderEntries: [
|
|
200
|
+
"bullMQJobsProvider",
|
|
201
|
+
"createBullMQJobsProvider",
|
|
202
|
+
],
|
|
203
|
+
},
|
|
107
204
|
"mail-resend": {
|
|
108
205
|
name: "mail-resend",
|
|
109
206
|
displayName: "Resend mail",
|
|
110
|
-
importName: "
|
|
207
|
+
importName: "resendMailProvider",
|
|
111
208
|
importFrom: "@beignet/provider-mail-resend",
|
|
112
|
-
providerEntry: "
|
|
209
|
+
providerEntry: "resendMailProvider",
|
|
113
210
|
dependencies: (packageJson) => ({
|
|
114
211
|
"@beignet/provider-mail-resend": beignetDependencyVersion(packageJson),
|
|
115
212
|
resend: externalVersions.resend,
|
|
@@ -132,7 +229,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
132
229
|
"",
|
|
133
230
|
"- Package: `@beignet/provider-mail-resend`",
|
|
134
231
|
"- Peer dependency: `resend`",
|
|
135
|
-
"- The preset wires `
|
|
232
|
+
"- The preset wires `resendMailProvider` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
|
|
136
233
|
"- `.env.example` ships dev placeholders so the app boots; set real `RESEND_API_KEY` and `RESEND_FROM` values before sending mail.",
|
|
137
234
|
"- Use `ctx.ports.mailer` in app code; `ctx.ports.resend` is available as an escape hatch.",
|
|
138
235
|
"",
|
|
@@ -143,16 +240,16 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
143
240
|
"Run beignet providers audit and beignet doctor --strict.",
|
|
144
241
|
],
|
|
145
242
|
conflictingProviderEntries: [
|
|
146
|
-
"
|
|
243
|
+
"smtpMailProvider",
|
|
147
244
|
"createMemoryMailerProvider",
|
|
148
245
|
],
|
|
149
246
|
},
|
|
150
247
|
"mail-smtp": {
|
|
151
248
|
name: "mail-smtp",
|
|
152
249
|
displayName: "SMTP mail",
|
|
153
|
-
importName: "
|
|
250
|
+
importName: "smtpMailProvider",
|
|
154
251
|
importFrom: "@beignet/provider-mail-smtp",
|
|
155
|
-
providerEntry: "
|
|
252
|
+
providerEntry: "smtpMailProvider",
|
|
156
253
|
dependencies: (packageJson) => ({
|
|
157
254
|
"@beignet/provider-mail-smtp": beignetDependencyVersion(packageJson),
|
|
158
255
|
nodemailer: externalVersions.nodemailer,
|
|
@@ -178,7 +275,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
178
275
|
"",
|
|
179
276
|
"- Package: `@beignet/provider-mail-smtp`",
|
|
180
277
|
"- Peer dependency: `nodemailer`",
|
|
181
|
-
"- The preset wires `
|
|
278
|
+
"- The preset wires `smtpMailProvider` in `server/providers.ts` and defers `mailer` in `infra/app-ports.ts`.",
|
|
182
279
|
"- `.env.example` ships dev placeholders so the app boots; set real `MAIL_*` values before sending mail.",
|
|
183
280
|
"- Use `ctx.ports.mailer` in app code; `ctx.ports.smtp` is available as an escape hatch.",
|
|
184
281
|
"",
|
|
@@ -189,10 +286,61 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
189
286
|
"Run beignet providers audit and beignet doctor --strict.",
|
|
190
287
|
],
|
|
191
288
|
conflictingProviderEntries: [
|
|
192
|
-
"
|
|
289
|
+
"resendMailProvider",
|
|
193
290
|
"createMemoryMailerProvider",
|
|
194
291
|
],
|
|
195
292
|
},
|
|
293
|
+
"payments-stripe": {
|
|
294
|
+
name: "payments-stripe",
|
|
295
|
+
displayName: "Stripe payments",
|
|
296
|
+
importName: "stripePaymentsProvider",
|
|
297
|
+
importFrom: "@beignet/provider-payments-stripe",
|
|
298
|
+
providerEntry: "stripePaymentsProvider",
|
|
299
|
+
dependencies: (packageJson) => ({
|
|
300
|
+
"@beignet/provider-payments-stripe":
|
|
301
|
+
beignetDependencyVersion(packageJson),
|
|
302
|
+
stripe: externalVersions.stripe,
|
|
303
|
+
}),
|
|
304
|
+
appPort: {
|
|
305
|
+
key: "payments",
|
|
306
|
+
typeName: "PaymentsPort",
|
|
307
|
+
importName: "PaymentsPort",
|
|
308
|
+
importFrom: "@beignet/core/payments",
|
|
309
|
+
},
|
|
310
|
+
envVars: [
|
|
311
|
+
"STRIPE_SECRET_KEY",
|
|
312
|
+
"STRIPE_WEBHOOK_SECRET",
|
|
313
|
+
"STRIPE_PUBLISHABLE_KEY",
|
|
314
|
+
],
|
|
315
|
+
envExample: [
|
|
316
|
+
"# Stripe payments provider",
|
|
317
|
+
"STRIPE_SECRET_KEY=sk_test_dev-placeholder-change-me",
|
|
318
|
+
"STRIPE_WEBHOOK_SECRET=whsec_dev-placeholder-change-me",
|
|
319
|
+
"# STRIPE_PUBLISHABLE_KEY=",
|
|
320
|
+
"",
|
|
321
|
+
].join("\n"),
|
|
322
|
+
docs: [
|
|
323
|
+
"## Stripe payments",
|
|
324
|
+
"",
|
|
325
|
+
"- Package: `@beignet/provider-payments-stripe`",
|
|
326
|
+
"- Peer dependency: `stripe`",
|
|
327
|
+
"- The preset wires `stripePaymentsProvider` in `server/providers.ts` and defers `payments` in `infra/app-ports.ts`.",
|
|
328
|
+
"- `.env.example` ships dev placeholders so the app boots; set real `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` values before taking payments.",
|
|
329
|
+
"- Stripe requires a webhook route: run `beignet make payments` for the full billing slice, including a `createPaymentWebhookRoute(...)` route.",
|
|
330
|
+
"- Use `ctx.ports.payments` in app code; `ctx.ports.stripe` is available as an escape hatch.",
|
|
331
|
+
"",
|
|
332
|
+
].join("\n"),
|
|
333
|
+
nextSteps: [
|
|
334
|
+
"Run your package manager install command.",
|
|
335
|
+
"Set STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in .env.local or your deployment environment.",
|
|
336
|
+
"Run beignet make payments for the full billing slice; Stripe needs its webhook route before subscription state can sync.",
|
|
337
|
+
"Run beignet providers audit and beignet doctor --strict.",
|
|
338
|
+
],
|
|
339
|
+
conflictingProviderEntries: [
|
|
340
|
+
"createMemoryPaymentsProvider",
|
|
341
|
+
"createStripePaymentsProvider",
|
|
342
|
+
],
|
|
343
|
+
},
|
|
196
344
|
"search-meilisearch": {
|
|
197
345
|
name: "search-meilisearch",
|
|
198
346
|
displayName: "Meilisearch search",
|
|
@@ -271,11 +419,11 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
271
419
|
"redis-cache": {
|
|
272
420
|
name: "redis-cache",
|
|
273
421
|
displayName: "Redis cache",
|
|
274
|
-
importName: "
|
|
275
|
-
importFrom: "@beignet/provider-redis",
|
|
276
|
-
providerEntry: "
|
|
422
|
+
importName: "redisCacheProvider",
|
|
423
|
+
importFrom: "@beignet/provider-cache-redis",
|
|
424
|
+
providerEntry: "redisCacheProvider",
|
|
277
425
|
dependencies: (packageJson) => ({
|
|
278
|
-
"@beignet/provider-redis": beignetDependencyVersion(packageJson),
|
|
426
|
+
"@beignet/provider-cache-redis": beignetDependencyVersion(packageJson),
|
|
279
427
|
ioredis: externalVersions.ioredis,
|
|
280
428
|
}),
|
|
281
429
|
appPort: {
|
|
@@ -294,9 +442,9 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
294
442
|
docs: [
|
|
295
443
|
"## Redis cache",
|
|
296
444
|
"",
|
|
297
|
-
"- Package: `@beignet/provider-redis`",
|
|
445
|
+
"- Package: `@beignet/provider-cache-redis`",
|
|
298
446
|
"- Peer dependency: `ioredis`",
|
|
299
|
-
"- The preset wires `
|
|
447
|
+
"- The preset wires `redisCacheProvider` in `server/providers.ts` and defers `cache` in `infra/app-ports.ts`.",
|
|
300
448
|
"- Set `REDIS_URL` before booting the app.",
|
|
301
449
|
"- Use `ctx.ports.cache` in app code; `ctx.ports.redis` is available as an escape hatch.",
|
|
302
450
|
"",
|
|
@@ -307,6 +455,53 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
307
455
|
"Run beignet providers audit and beignet doctor --strict.",
|
|
308
456
|
],
|
|
309
457
|
},
|
|
458
|
+
"event-bus-redis": {
|
|
459
|
+
name: "event-bus-redis",
|
|
460
|
+
displayName: "Redis event bus",
|
|
461
|
+
importName: "redisEventBusProvider",
|
|
462
|
+
importFrom: "@beignet/provider-event-bus-redis",
|
|
463
|
+
providerEntry: "redisEventBusProvider",
|
|
464
|
+
dependencies: (packageJson) => ({
|
|
465
|
+
"@beignet/provider-event-bus-redis":
|
|
466
|
+
beignetDependencyVersion(packageJson),
|
|
467
|
+
ioredis: externalVersions.ioredis,
|
|
468
|
+
}),
|
|
469
|
+
appPort: {
|
|
470
|
+
key: "eventBus",
|
|
471
|
+
typeName: "EventBusPort",
|
|
472
|
+
importName: "EventBusPort",
|
|
473
|
+
importFrom: "@beignet/core/ports",
|
|
474
|
+
},
|
|
475
|
+
envVars: ["REDIS_EVENT_BUS_URL"],
|
|
476
|
+
envExample: [
|
|
477
|
+
"# Redis event bus provider",
|
|
478
|
+
"REDIS_EVENT_BUS_URL=redis://localhost:6379",
|
|
479
|
+
"# REDIS_EVENT_BUS_DB=0",
|
|
480
|
+
"# REDIS_EVENT_BUS_CHANNEL_PREFIX=beignet:event-bus:",
|
|
481
|
+
"",
|
|
482
|
+
].join("\n"),
|
|
483
|
+
docs: [
|
|
484
|
+
"## Redis event bus",
|
|
485
|
+
"",
|
|
486
|
+
"- Package: `@beignet/provider-event-bus-redis`",
|
|
487
|
+
"- Peer dependency: `ioredis`",
|
|
488
|
+
"- The preset wires `redisEventBusProvider` in `server/providers.ts` and defers `eventBus` in `infra/app-ports.ts`.",
|
|
489
|
+
"- Set `REDIS_EVENT_BUS_URL` before booting the app.",
|
|
490
|
+
"- 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.",
|
|
491
|
+
"- Use `ctx.ports.eventBus` in app code; `ctx.ports.redisEventBus` is available as an escape hatch.",
|
|
492
|
+
"",
|
|
493
|
+
].join("\n"),
|
|
494
|
+
nextSteps: [
|
|
495
|
+
"Run your package manager install command.",
|
|
496
|
+
"Set REDIS_EVENT_BUS_URL in .env.local or your deployment environment.",
|
|
497
|
+
"Keep durable side effects on the outbox/jobs path; Redis Pub/Sub does not replay missed events.",
|
|
498
|
+
"Run beignet providers audit and beignet doctor --strict.",
|
|
499
|
+
],
|
|
500
|
+
conflictingProviderEntries: [
|
|
501
|
+
"createInMemoryEventBusProvider",
|
|
502
|
+
"createRedisEventBusProvider",
|
|
503
|
+
],
|
|
504
|
+
},
|
|
310
505
|
"redis-locks": {
|
|
311
506
|
name: "redis-locks",
|
|
312
507
|
displayName: "Redis locks",
|
|
@@ -364,7 +559,19 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
364
559
|
importName: "StoragePort",
|
|
365
560
|
importFrom: "@beignet/core/storage",
|
|
366
561
|
},
|
|
367
|
-
envVars: [
|
|
562
|
+
envVars: [
|
|
563
|
+
"STORAGE_S3_BUCKET",
|
|
564
|
+
"STORAGE_S3_REGION",
|
|
565
|
+
"STORAGE_S3_ENDPOINT",
|
|
566
|
+
"STORAGE_S3_PUBLIC_BASE_URL",
|
|
567
|
+
"STORAGE_S3_FORCE_PATH_STYLE",
|
|
568
|
+
"STORAGE_S3_KEY_PREFIX",
|
|
569
|
+
"STORAGE_S3_MAX_ATTEMPTS",
|
|
570
|
+
"STORAGE_S3_RETRY_MODE",
|
|
571
|
+
"STORAGE_S3_ACCESS_KEY_ID",
|
|
572
|
+
"STORAGE_S3_SECRET_ACCESS_KEY",
|
|
573
|
+
"STORAGE_S3_SESSION_TOKEN",
|
|
574
|
+
],
|
|
368
575
|
envExample: [
|
|
369
576
|
"# S3-compatible storage provider",
|
|
370
577
|
"STORAGE_S3_BUCKET=beignet-assets",
|
|
@@ -373,6 +580,8 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
373
580
|
"# STORAGE_S3_PUBLIC_BASE_URL=",
|
|
374
581
|
"# STORAGE_S3_FORCE_PATH_STYLE=false",
|
|
375
582
|
"# STORAGE_S3_KEY_PREFIX=",
|
|
583
|
+
"# STORAGE_S3_MAX_ATTEMPTS=3",
|
|
584
|
+
"# STORAGE_S3_RETRY_MODE=standard",
|
|
376
585
|
"# STORAGE_S3_ACCESS_KEY_ID=",
|
|
377
586
|
"# STORAGE_S3_SECRET_ACCESS_KEY=",
|
|
378
587
|
"# STORAGE_S3_SESSION_TOKEN=",
|
|
@@ -496,6 +705,51 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
496
705
|
|
|
497
706
|
const providerPresetSet = new Set<string>(providerPresetChoices);
|
|
498
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Provider entries the starter template wires for each create-time
|
|
710
|
+
* integration, used to pre-validate preset selections at create time.
|
|
711
|
+
*/
|
|
712
|
+
const integrationProviderEntries: Record<IntegrationName, string> = {
|
|
713
|
+
inngest: "inngestJobsProvider",
|
|
714
|
+
resend: "resendMailProvider",
|
|
715
|
+
"upstash-rate-limit": "upstashRateLimitProvider",
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Whether a preset ships `.env.example` entries the user must fill in.
|
|
720
|
+
*/
|
|
721
|
+
export function providerPresetNeedsEnv(name: ProviderPresetName): boolean {
|
|
722
|
+
return presets[name].envVars.length > 0;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Fail fast when a create-time selection combines presets or template
|
|
727
|
+
* integrations that fill the same app port, before any files are written.
|
|
728
|
+
*/
|
|
729
|
+
export function assertCompatibleCreatePresets(
|
|
730
|
+
presetNames: readonly ProviderPresetName[],
|
|
731
|
+
integrations: readonly IntegrationName[],
|
|
732
|
+
): void {
|
|
733
|
+
const selectedEntries = new Map<string, string>();
|
|
734
|
+
for (const integration of integrations) {
|
|
735
|
+
selectedEntries.set(integrationProviderEntries[integration], integration);
|
|
736
|
+
}
|
|
737
|
+
for (const name of presetNames) {
|
|
738
|
+
selectedEntries.set(presets[name].providerEntry, name);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
for (const name of presetNames) {
|
|
742
|
+
for (const conflict of presets[name].conflictingProviderEntries ?? []) {
|
|
743
|
+
const other = selectedEntries.get(conflict);
|
|
744
|
+
if (other !== undefined && other !== name) {
|
|
745
|
+
throw new Error(
|
|
746
|
+
`--integrations selections "${name}" and "${other}" fill the same app port. Pick one of them.`,
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
499
753
|
export async function addProviderPreset(
|
|
500
754
|
options: AddProviderPresetOptions,
|
|
501
755
|
): Promise<AddProviderPresetResult> {
|
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
|
|
67
|
-
task,
|
|
66
|
+
const output = await runTask(task, {
|
|
68
67
|
input: parsedInput,
|
|
69
68
|
ctx,
|
|
70
69
|
});
|
package/src/templates/agents.ts
CHANGED
|
@@ -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
|
|
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. |
|
package/src/templates/base.ts
CHANGED
|
@@ -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: "
|
|
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": "
|
|
104
|
+
"db:reset": "tsx infra/db/reset.ts",
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
return json({
|
|
@@ -159,18 +159,10 @@ export function readme(ctx: TemplateContext): string {
|
|
|
159
159
|
ctx.packageManager === "npm"
|
|
160
160
|
? "npm run test"
|
|
161
161
|
: `${ctx.packageManager} run test`;
|
|
162
|
-
const lint =
|
|
163
|
-
ctx.packageManager === "npm"
|
|
164
|
-
? "npm run lint"
|
|
165
|
-
: `${ctx.packageManager} run lint`;
|
|
166
162
|
const format =
|
|
167
163
|
ctx.packageManager === "npm"
|
|
168
164
|
? "npm run format"
|
|
169
165
|
: `${ctx.packageManager} run format`;
|
|
170
|
-
const typecheck =
|
|
171
|
-
ctx.packageManager === "npm"
|
|
172
|
-
? "npm run typecheck"
|
|
173
|
-
: `${ctx.packageManager} run typecheck`;
|
|
174
166
|
const cli = packageRunner(ctx);
|
|
175
167
|
|
|
176
168
|
const firstOpen = ctx.api
|
|
@@ -393,7 +385,7 @@ export function integrationsDoc(ctx: TemplateContext): string {
|
|
|
393
385
|
for (const integration of ctx.integrations) {
|
|
394
386
|
if (integration === "inngest") {
|
|
395
387
|
lines.push(
|
|
396
|
-
"## Inngest",
|
|
388
|
+
"## Inngest jobs",
|
|
397
389
|
"",
|
|
398
390
|
"- Package: `@beignet/core` (`@beignet/core/events`, `@beignet/core/jobs`)",
|
|
399
391
|
"- Package: `@beignet/provider-jobs-inngest`",
|
|
@@ -411,7 +403,7 @@ export function integrationsDoc(ctx: TemplateContext): string {
|
|
|
411
403
|
"",
|
|
412
404
|
"- Package: `@beignet/provider-mail-resend`",
|
|
413
405
|
"- Peer dependency: `resend`",
|
|
414
|
-
"- The starter wires `
|
|
406
|
+
"- The starter wires `resendMailProvider` in `server/providers.ts`.",
|
|
415
407
|
"- `.env.example` ships dev placeholders so the app boots; set real `RESEND_API_KEY` and `RESEND_FROM` values before sending mail.",
|
|
416
408
|
"",
|
|
417
409
|
);
|
package/src/templates/index.ts
CHANGED
|
@@ -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 },
|
package/src/templates/server.ts
CHANGED
|
@@ -15,9 +15,9 @@ export function serverProviders(ctx: TemplateContext): string {
|
|
|
15
15
|
hasIntegration(ctx, "inngest")
|
|
16
16
|
? 'import { inngestJobsProvider } from "@beignet/provider-jobs-inngest";'
|
|
17
17
|
: undefined,
|
|
18
|
-
'import {
|
|
18
|
+
'import { pinoLoggerProvider } from "@beignet/provider-logger-pino";',
|
|
19
19
|
hasIntegration(ctx, "resend")
|
|
20
|
-
? 'import {
|
|
20
|
+
? 'import { resendMailProvider } from "@beignet/provider-mail-resend";'
|
|
21
21
|
: undefined,
|
|
22
22
|
hasIntegration(ctx, "upstash-rate-limit")
|
|
23
23
|
? 'import { upstashRateLimitProvider } from "@beignet/provider-rate-limit-upstash";'
|
|
@@ -31,11 +31,11 @@ export function serverProviders(ctx: TemplateContext): string {
|
|
|
31
31
|
const entries = [
|
|
32
32
|
"\tcreateDevtoolsProvider(),",
|
|
33
33
|
"\tcreateAuthBetterAuthProvider<AuthUser, AuthSessionMetadata>(auth),",
|
|
34
|
-
"\
|
|
34
|
+
"\tpinoLoggerProvider,",
|
|
35
35
|
`\t${db.providerInstance},`,
|
|
36
36
|
"\tstarterDatabaseProvider,",
|
|
37
37
|
hasIntegration(ctx, "inngest") ? "\tinngestJobsProvider," : undefined,
|
|
38
|
-
hasIntegration(ctx, "resend") ? "\
|
|
38
|
+
hasIntegration(ctx, "resend") ? "\tresendMailProvider," : undefined,
|
|
39
39
|
hasIntegration(ctx, "upstash-rate-limit")
|
|
40
40
|
? "\tupstashRateLimitProvider,"
|
|
41
41
|
: undefined,
|
|
@@ -297,7 +297,11 @@ export function server(ctx: TemplateContext): string {
|
|
|
297
297
|
|
|
298
298
|
return `import "@beignet/core/server-only";
|
|
299
299
|
import { createNextServer, createNextServerLoader } from "@beignet/next";
|
|
300
|
-
import {
|
|
300
|
+
import {
|
|
301
|
+
createErrorReportingHooks,
|
|
302
|
+
createIdempotencyHooks,
|
|
303
|
+
createSecurityHeadersHooks,
|
|
304
|
+
} from "@beignet/core/server";
|
|
301
305
|
import type { AppContext } from "@/app-context";
|
|
302
306
|
import { appPorts } from "@/infra/app-ports";
|
|
303
307
|
import { env } from "@/lib/env";
|
|
@@ -314,6 +318,7 @@ export const getServer = createNextServerLoader(async () => {
|
|
|
314
318
|
${providerConfigEntries}
|
|
315
319
|
},
|
|
316
320
|
hooks: [
|
|
321
|
+
createSecurityHeadersHooks<AppContext>(),
|
|
317
322
|
createErrorReportingHooks<AppContext>(),
|
|
318
323
|
createIdempotencyHooks<AppContext>(),
|
|
319
324
|
],
|
package/src/templates/shared.ts
CHANGED
|
@@ -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",
|
|
@@ -45,6 +45,7 @@ export const externalVersions = {
|
|
|
45
45
|
hookformResolvers: "^5.0.0",
|
|
46
46
|
reactHookForm: "^7.74.0",
|
|
47
47
|
betterAuth: "1.6.20",
|
|
48
|
+
bullmq: "^5.0.0",
|
|
48
49
|
drizzleKit: "^0.31.10",
|
|
49
50
|
drizzleOrm: "^0.45.2",
|
|
50
51
|
inngest: "^3.0.0",
|
|
@@ -56,6 +57,7 @@ export const externalVersions = {
|
|
|
56
57
|
pino: "^9.7.0",
|
|
57
58
|
resend: "^6.14.0",
|
|
58
59
|
sentryNode: "^10.58.0",
|
|
60
|
+
stripe: "^22.2.1",
|
|
59
61
|
nodemailer: "^8.0.5",
|
|
60
62
|
openFeatureServerSdk: "^1.22.0",
|
|
61
63
|
typesPg: "^8.11.0",
|
|
@@ -65,6 +67,18 @@ export const externalVersions = {
|
|
|
65
67
|
upstashRedis: "^1.0.0",
|
|
66
68
|
};
|
|
67
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
|
+
|
|
68
82
|
/**
|
|
69
83
|
* Dialect-specific names used when rendering database-aware templates.
|
|
70
84
|
*/
|