@beignet/cli 0.0.22 → 0.0.24

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 (59) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +35 -5
  3. package/dist/choices.d.ts +8 -0
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +4 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +8 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspect.d.ts.map +1 -1
  11. package/dist/inspect.js +184 -0
  12. package/dist/inspect.js.map +1 -1
  13. package/dist/lib.d.ts +2 -2
  14. package/dist/lib.d.ts.map +1 -1
  15. package/dist/lib.js +1 -1
  16. package/dist/lib.js.map +1 -1
  17. package/dist/make.d.ts +4 -3
  18. package/dist/make.d.ts.map +1 -1
  19. package/dist/make.js +349 -45
  20. package/dist/make.js.map +1 -1
  21. package/dist/mcp.d.ts.map +1 -1
  22. package/dist/mcp.js +6 -2
  23. package/dist/mcp.js.map +1 -1
  24. package/dist/templates/agents.d.ts.map +1 -1
  25. package/dist/templates/agents.js +9 -5
  26. package/dist/templates/agents.js.map +1 -1
  27. package/dist/templates/base.d.ts.map +1 -1
  28. package/dist/templates/base.js +4 -2
  29. package/dist/templates/base.js.map +1 -1
  30. package/dist/templates/db/mysql.js +2 -2
  31. package/dist/templates/db/postgres.js +2 -2
  32. package/dist/templates/db/sqlite.js +2 -2
  33. package/dist/templates/index.d.ts.map +1 -1
  34. package/dist/templates/index.js +1 -0
  35. package/dist/templates/index.js.map +1 -1
  36. package/dist/templates/server.d.ts +1 -0
  37. package/dist/templates/server.d.ts.map +1 -1
  38. package/dist/templates/server.js +90 -20
  39. package/dist/templates/server.js.map +1 -1
  40. package/dist/templates/shell.d.ts.map +1 -1
  41. package/dist/templates/shell.js +60 -29
  42. package/dist/templates/shell.js.map +1 -1
  43. package/dist/templates/todos.js +5 -5
  44. package/package.json +2 -2
  45. package/src/choices.ts +10 -0
  46. package/src/index.ts +11 -0
  47. package/src/inspect.ts +319 -0
  48. package/src/lib.ts +2 -1
  49. package/src/make.ts +449 -53
  50. package/src/mcp.ts +7 -1
  51. package/src/templates/agents.ts +9 -5
  52. package/src/templates/base.ts +4 -2
  53. package/src/templates/db/mysql.ts +2 -2
  54. package/src/templates/db/postgres.ts +2 -2
  55. package/src/templates/db/sqlite.ts +2 -2
  56. package/src/templates/index.ts +1 -0
  57. package/src/templates/server.ts +90 -20
  58. package/src/templates/shell.ts +61 -29
  59. package/src/templates/todos.ts +5 -5
@@ -1007,12 +1007,40 @@ export function SettingsNav() {
1007
1007
  );
1008
1008
  }
1009
1009
  `;
1010
+ const todoClientQueries = `import type { QueryClient } from "@tanstack/react-query";
1011
+ import { rq } from "@/client";
1012
+ import {
1013
+ createTodo,
1014
+ deleteTodo,
1015
+ listTodos,
1016
+ updateTodo,
1017
+ } from "@/features/todos/contracts";
1018
+
1019
+ export function listTodosQueryOptions() {
1020
+ return rq(listTodos).queryOptions();
1021
+ }
1022
+
1023
+ export function createTodoMutationOptions() {
1024
+ return rq(createTodo).mutationOptions();
1025
+ }
1026
+
1027
+ export function updateTodoMutationOptions() {
1028
+ return rq(updateTodo).mutationOptions();
1029
+ }
1030
+
1031
+ export function deleteTodoMutationOptions() {
1032
+ return rq(deleteTodo).mutationOptions();
1033
+ }
1034
+
1035
+ export function invalidateTodos(queryClient: QueryClient) {
1036
+ return rq(listTodos).invalidate(queryClient);
1037
+ }
1038
+ `;
1010
1039
  const todoApp = `"use client";
1011
1040
 
1012
1041
  import { rootFormError } from "@beignet/react-hook-form";
1013
1042
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
1014
1043
  import { Trash2Icon } from "lucide-react";
1015
- import { rq } from "@/client";
1016
1044
  import { rhf } from "@/client/forms";
1017
1045
  import { Button } from "@/components/ui/button";
1018
1046
  import { Card, CardContent } from "@/components/ui/card";
@@ -1020,45 +1048,44 @@ import { Checkbox } from "@/components/ui/checkbox";
1020
1048
  import { Input } from "@/components/ui/input";
1021
1049
  import { Label } from "@/components/ui/label";
1022
1050
  import {
1023
- createTodo,
1024
- deleteTodo,
1025
- listTodos,
1026
- updateTodo,
1027
- } from "@/features/todos/contracts";
1051
+ createTodoMutationOptions,
1052
+ deleteTodoMutationOptions,
1053
+ invalidateTodos,
1054
+ listTodosQueryOptions,
1055
+ updateTodoMutationOptions,
1056
+ } from "@/features/todos/client/queries";
1057
+ import { createTodo } from "@/features/todos/contracts";
1028
1058
  import { cn } from "@/lib/utils";
1029
1059
 
1030
1060
  const createTodoForm = rhf(createTodo);
1031
1061
 
1032
1062
  export function TodoApp() {
1033
1063
  const queryClient = useQueryClient();
1034
- const todosQuery = useQuery(rq(listTodos).queryOptions());
1064
+ const todosQuery = useQuery(listTodosQueryOptions());
1035
1065
  const form = createTodoForm.useForm({
1036
1066
  defaultValues: { title: "" },
1037
1067
  });
1038
1068
 
1039
- const invalidateTodos = () => rq(listTodos).invalidate(queryClient);
1069
+ const invalidateTodoQueries = () => invalidateTodos(queryClient);
1040
1070
 
1041
- const createTodoMutation = useMutation(
1042
- rq(createTodo).mutationOptions({
1043
- onSuccess: async () => {
1044
- form.reset();
1045
- await invalidateTodos();
1046
- },
1047
- onError: (error) => {
1048
- form.setError("root", rootFormError(error, "Could not create the todo."));
1049
- },
1050
- }),
1051
- );
1052
- const updateTodoMutation = useMutation(
1053
- rq(updateTodo).mutationOptions({
1054
- onSuccess: invalidateTodos,
1055
- }),
1056
- );
1057
- const deleteTodoMutation = useMutation(
1058
- rq(deleteTodo).mutationOptions({
1059
- onSuccess: invalidateTodos,
1060
- }),
1061
- );
1071
+ const createTodoMutation = useMutation({
1072
+ ...createTodoMutationOptions(),
1073
+ onSuccess: async () => {
1074
+ form.reset();
1075
+ await invalidateTodoQueries();
1076
+ },
1077
+ onError: (error) => {
1078
+ form.setError("root", rootFormError(error, "Could not create the todo."));
1079
+ },
1080
+ });
1081
+ const updateTodoMutation = useMutation({
1082
+ ...updateTodoMutationOptions(),
1083
+ onSuccess: invalidateTodoQueries,
1084
+ });
1085
+ const deleteTodoMutation = useMutation({
1086
+ ...deleteTodoMutationOptions(),
1087
+ onSuccess: invalidateTodoQueries,
1088
+ });
1062
1089
 
1063
1090
  const onSubmit = form.handleSubmit((body) => {
1064
1091
  form.clearErrors("root");
@@ -1186,6 +1213,10 @@ export function shellTemplateFiles(ctx) {
1186
1213
  { path: "components/theme-toggle.tsx", content: themeToggle },
1187
1214
  { path: "components/app-sidebar.tsx", content: appSidebar },
1188
1215
  { path: "components/settings-nav.tsx", content: settingsNav },
1216
+ {
1217
+ path: "features/todos/client/queries.ts",
1218
+ content: todoClientQueries,
1219
+ },
1189
1220
  {
1190
1221
  path: "features/todos/components/todo-app.tsx",
1191
1222
  content: todoApp,
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/templates/shell.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/templates/shell.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,aAAa,GAGd,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AAEH,MAAM,YAAY,GAAG;;;;;;;;;;;;;CAapB,CAAC;AAEF,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmClB,CAAC;AAEF,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DnB,CAAC;AAEF,MAAM,UAAU,GAAG;;;;;;;;;CASlB,CAAC;AAEF,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmGlB,CAAC;AAEF,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmHlB,CAAC;AAEF,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BjB,CAAC;AAEF,SAAS,aAAa,CAAC,GAAoB;IACzC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA0CA,aAAa,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;CAkB1B,CAAC;AACF,CAAC;AAED,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;CAgBjB,CAAC;AAEF,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;CAiBtB,CAAC;AAEF,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgH3B,CAAC;AAEF,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsG5B,CAAC;AAEF,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;CAyB9B,CAAC;AAEF,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoF3B,CAAC;AAEF,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;CAmBnB,CAAC;AAEF,MAAM,cAAc,GAAG;;;CAGtB,CAAC;AAEF,MAAM,WAAW,GAAG;;;CAGnB,CAAC;AAEF,MAAM,aAAa,GAAG;;;;;;;;;;;CAWrB,CAAC;AAEF,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCnB,CAAC;AAEF,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmGlB,CAAC;AAEF,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCnB,CAAC;AAEF,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BzB,CAAC;AAEF,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgJf,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,GAAoB;IACrD,OAAO;QACL,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE;QAC/C,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,YAAY,EAAE;QACpD,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE;QAC9C,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,UAAU,EAAE;QACtD,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,UAAU,EAAE;QAC5D,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,UAAU,EAAE;QAC5D,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,EAAE;QACpD,EAAE,IAAI,EAAE,8BAA8B,EAAE,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE;QACrE,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,SAAS,EAAE;QACxD,EAAE,IAAI,EAAE,+BAA+B,EAAE,OAAO,EAAE,cAAc,EAAE;QAClE,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,mBAAmB,EAAE;QACrE;YACE,IAAI,EAAE,sCAAsC;YAC5C,OAAO,EAAE,oBAAoB;SAC9B;QACD;YACE,IAAI,EAAE,wCAAwC;YAC9C,OAAO,EAAE,sBAAsB;SAChC;QACD;YACE,IAAI,EAAE,qCAAqC;YAC3C,OAAO,EAAE,mBAAmB;SAC7B;QACD,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,WAAW,EAAE;QACjD,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,cAAc,EAAE;QAC1D,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,WAAW,EAAE;QACjD,EAAE,IAAI,EAAE,+BAA+B,EAAE,OAAO,EAAE,aAAa,EAAE;QACjE,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,WAAW,EAAE;QAC7D,EAAE,IAAI,EAAE,4BAA4B,EAAE,OAAO,EAAE,UAAU,EAAE;QAC3D,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,WAAW,EAAE;QAC7D;YACE,IAAI,EAAE,kCAAkC;YACxC,OAAO,EAAE,iBAAiB;SAC3B;QACD;YACE,IAAI,EAAE,wCAAwC;YAC9C,OAAO,EAAE,OAAO;SACjB;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAA2B;IACvD,aAAa,EAAE,gBAAgB,CAAC,UAAU;IAC1C,cAAc,EAAE,SAAS;IACzB,aAAa,EAAE,QAAQ;CACxB,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAA2B,EAAE,CAAC"}
@@ -174,7 +174,7 @@ export const todoPolicy = definePolicy({
174
174
  authorizeOwner(ctx, todo, "delete"),
175
175
  });
176
176
  `,
177
- routes: `import type { beignetServerOnly } from "@beignet/core/server-only";
177
+ routes: `import "@beignet/core/server-only";
178
178
  import { defineRouteGroup } from "@beignet/next";
179
179
  import type { AppContext } from "@/app-context";
180
180
  import {
@@ -200,7 +200,7 @@ export const todoRoutes = defineRouteGroup<AppContext>()({
200
200
  ],
201
201
  });
202
202
  `,
203
- listTodosUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
203
+ listTodosUseCase: `import "@beignet/core/server-only";
204
204
  import { normalizeOffsetPage } from "@beignet/core/pagination";
205
205
  import { requireUser } from "@/lib/auth";
206
206
  import { useCase } from "@/lib/use-case";
@@ -220,7 +220,7 @@ export const listTodosUseCase = useCase
220
220
  return ctx.ports.todos.list(user.id, page);
221
221
  });
222
222
  `,
223
- createTodoUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
223
+ createTodoUseCase: `import "@beignet/core/server-only";
224
224
  import { requireUser } from "@/lib/auth";
225
225
  import { useCase } from "@/lib/use-case";
226
226
  import { CreateTodoInputSchema, TodoSchema } from "../schemas";
@@ -238,7 +238,7 @@ export const createTodoUseCase = useCase
238
238
  );
239
239
  });
240
240
  `,
241
- updateTodoUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
241
+ updateTodoUseCase: `import "@beignet/core/server-only";
242
242
  import { appError } from "@/features/shared/errors";
243
243
  import { requireUser } from "@/lib/auth";
244
244
  import { useCase } from "@/lib/use-case";
@@ -263,7 +263,7 @@ export const updateTodoUseCase = useCase
263
263
  });
264
264
  });
265
265
  `,
266
- deleteTodoUseCase: `import type { beignetServerOnly } from "@beignet/core/server-only";
266
+ deleteTodoUseCase: `import "@beignet/core/server-only";
267
267
  import { appError } from "@/features/shared/errors";
268
268
  import { requireUser } from "@/lib/auth";
269
269
  import { useCase } from "@/lib/use-case";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beignet/cli",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "description": "CLI for creating and maintaining Beignet apps.",
6
6
  "main": "./dist/lib.js",
@@ -68,7 +68,7 @@
68
68
  "typescript": "^5.3.0"
69
69
  },
70
70
  "dependencies": {
71
- "@beignet/core": "^0.0.22",
71
+ "@beignet/core": "^0.0.24",
72
72
  "@clack/prompts": "^1.5.1",
73
73
  "@modelcontextprotocol/sdk": "^1.29.0",
74
74
  "@stricli/core": "^1.2.7",
package/src/choices.ts CHANGED
@@ -156,3 +156,13 @@ export const makeFeatureAddonChoices = [
156
156
  * Optional artifact accepted by `beignet make feature --with`.
157
157
  */
158
158
  export type MakeFeatureAddon = (typeof makeFeatureAddonChoices)[number];
159
+
160
+ /**
161
+ * Named recipes supported by `beignet make feature --recipe`.
162
+ */
163
+ export const makeFeatureRecipeChoices = ["full-slice"] as const;
164
+
165
+ /**
166
+ * Recipe accepted by `beignet make feature --recipe`.
167
+ */
168
+ export type MakeFeatureRecipe = (typeof makeFeatureRecipeChoices)[number];
package/src/index.ts CHANGED
@@ -22,7 +22,9 @@ import {
22
22
  type IntegrationName,
23
23
  integrationChoices,
24
24
  type MakeFeatureAddon,
25
+ type MakeFeatureRecipe,
25
26
  makeFeatureAddonChoices,
27
+ makeFeatureRecipeChoices,
26
28
  type PackageManager,
27
29
  packageManagerChoices,
28
30
  type TemplateName,
@@ -76,6 +78,7 @@ type MakeResourceFlags = MakeFlags & {
76
78
 
77
79
  type MakeFeatureFlags = MakeFlags & {
78
80
  with?: readonly MakeFeatureAddon[];
81
+ recipe?: MakeFeatureRecipe;
79
82
  };
80
83
 
81
84
  type MakeListenerFlags = MakeFlags & {
@@ -883,6 +886,13 @@ const makeFeatureCommand = buildCommand<MakeFeatureFlags, [string], CliContext>(
883
886
  brief:
884
887
  "Add feature-owned artifacts. Supports policy, factory/factories, seed/seeds, task/tasks, event/events, listener/listeners, job/jobs, notification/notifications, schedule/schedules, ui, and upload/uploads.",
885
888
  },
889
+ recipe: {
890
+ kind: "enum",
891
+ values: makeFeatureRecipeChoices,
892
+ optional: true,
893
+ brief:
894
+ "Generate a named feature recipe. Supports full-slice for policy, client UI, workflow artifacts, outbox-ready events/jobs, and listener registration.",
895
+ },
886
896
  } satisfies FlagParametersForType<MakeFeatureFlags, CliContext>,
887
897
  positional: namePositional,
888
898
  },
@@ -897,6 +907,7 @@ const makeFeatureCommand = buildCommand<MakeFeatureFlags, [string], CliContext>(
897
907
  const result = await makeFeature({
898
908
  name,
899
909
  with: flags.with,
910
+ recipe: flags.recipe,
900
911
  force: Boolean(flags.force),
901
912
  dryRun: Boolean(flags.dryRun),
902
913
  });
package/src/inspect.ts CHANGED
@@ -363,10 +363,57 @@ export function formatDoctor(
363
363
  );
364
364
  }
365
365
 
366
+ const checklist = productionHardeningChecklist(result.diagnostics);
367
+ if (checklist.length > 0) {
368
+ lines.push("", paint("Production Hardening Checklist:", "bold"), "");
369
+ lines.push(...checklist.map((item) => `- ${item}`));
370
+ }
371
+
366
372
  lines.push("", formatDoctorFooter(result));
367
373
  return lines.join("\n");
368
374
  }
369
375
 
376
+ const productionHardeningDiagnosticCodes = new Set([
377
+ "BEIGNET_AUTH_ROUTE_MISSING",
378
+ "BEIGNET_AUTH_TRUSTED_ORIGINS_MISSING",
379
+ "BEIGNET_BETTER_AUTH_SECRET_PLACEHOLDER",
380
+ "BEIGNET_BILLING_CHECKOUT_IDEMPOTENCY_MISSING",
381
+ "BEIGNET_BILLING_ENTITLEMENTS_PROVIDER_MISSING",
382
+ "BEIGNET_BILLING_IDEMPOTENCY_PORT_MISSING",
383
+ "BEIGNET_CORS_CREDENTIALS_WILDCARD",
384
+ "BEIGNET_CRON_SECRET_MISSING",
385
+ "BEIGNET_DEVTOOLS_ROUTE_EXPOSED",
386
+ "BEIGNET_ENTITLEMENTS_PORT_MISSING",
387
+ "BEIGNET_PAYMENTS_STRIPE_MEMORY_PROVIDER",
388
+ "BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
389
+ "BEIGNET_PROVIDER_ENV_MISSING",
390
+ "BEIGNET_READINESS_ROUTE_MISSING",
391
+ "BEIGNET_TENANT_HEADER_AUTHORITY",
392
+ "BEIGNET_UPLOAD_AUTHORIZATION_MISSING",
393
+ "BEIGNET_UPLOAD_SIZE_LIMIT_MISSING",
394
+ ]);
395
+
396
+ function productionHardeningChecklist(
397
+ diagnostics: InspectDiagnostic[],
398
+ ): string[] {
399
+ if (
400
+ !diagnostics.some((diagnostic) =>
401
+ productionHardeningDiagnosticCodes.has(diagnostic.code),
402
+ )
403
+ ) {
404
+ return [];
405
+ }
406
+
407
+ return [
408
+ "Secrets and provider credentials are unique per environment, validated at startup, never committed, and never logged.",
409
+ "Auth routes, tenant resolution, authorization policies, and trusted origins derive authority from verified sessions, API keys, or trusted gateway metadata.",
410
+ "Devtools, OpenAPI, cron, webhooks, and operational routes have intentional exposure and app-owned authorization.",
411
+ "CORS origins, proxy IP trust, rate-limit keys, and request body limits match the production host topology.",
412
+ "Uploads and storage define max sizes, authorization or explicit public access, object visibility, safe content headers, and short direct-upload expirations.",
413
+ "Provider-backed dependencies have bounded readiness checks, worker shutdown behavior, webhook secrets, and least-privilege credentials.",
414
+ ];
415
+ }
416
+
370
417
  function formatDoctorFooter(result: InspectAppResult): string {
371
418
  const count = (severity: InspectDiagnostic["severity"]) =>
372
419
  result.diagnostics.filter((diagnostic) => diagnostic.severity === severity)
@@ -1974,6 +2021,32 @@ async function inspectProductionReadiness(
1974
2021
  });
1975
2022
  }
1976
2023
 
2024
+ if (
2025
+ isFeatureUploadDefinition(file, config) &&
2026
+ containsCallExpression(source, "defineUpload") &&
2027
+ !/\bauthorize\s*(?:\(|:)/.test(source) &&
2028
+ !/\baccess\s*:\s*["']public["']/.test(source)
2029
+ ) {
2030
+ diagnostics.push({
2031
+ severity: "warning",
2032
+ code: "BEIGNET_UPLOAD_AUTHORIZATION_MISSING",
2033
+ file,
2034
+ message: `${file} defines a protected upload without authorize(...). Add an authorize hook, or set access: "public" only for intentionally public uploads.`,
2035
+ });
2036
+ }
2037
+
2038
+ if (
2039
+ isRouteServerOrInfraFile(file, config) &&
2040
+ containsRequestTenantHeaderAuthority(source)
2041
+ ) {
2042
+ diagnostics.push({
2043
+ severity: "warning",
2044
+ code: "BEIGNET_TENANT_HEADER_AUTHORITY",
2045
+ file,
2046
+ message: `${file} reads x-tenant-id and turns it into tenant authority. Resolve request tenants from verified auth/session claims or trusted gateway metadata instead of a caller-controlled header.`,
2047
+ });
2048
+ }
2049
+
1977
2050
  if (
1978
2051
  isRouteServerOrInfraFile(file, config) &&
1979
2052
  containsCallExpression(source, "createCorsHooks") &&
@@ -1991,6 +2064,19 @@ async function inspectProductionReadiness(
1991
2064
  }
1992
2065
  }
1993
2066
 
2067
+ for (const file of configFiles) {
2068
+ const source = await readCachedSource(targetDir, file, sourceCache);
2069
+
2070
+ if (containsBetterAuthLocalSecret(source)) {
2071
+ diagnostics.push({
2072
+ severity: "warning",
2073
+ code: "BEIGNET_BETTER_AUTH_SECRET_PLACEHOLDER",
2074
+ file,
2075
+ message: `${file} contains the known local Better Auth placeholder secret. Keep it out of production configuration and require a unique BETTER_AUTH_SECRET before deployment.`,
2076
+ });
2077
+ }
2078
+ }
2079
+
1994
2080
  const providerEntries = await readProviderListEntries(
1995
2081
  targetDir,
1996
2082
  files,
@@ -2138,6 +2224,34 @@ function containsReadinessCheck(source: string): boolean {
2138
2224
  return /\bchecks\s*:/.test(source) || /\.checkHealth\s*\(/.test(source);
2139
2225
  }
2140
2226
 
2227
+ function containsBetterAuthLocalSecret(source: string): boolean {
2228
+ if (!source.includes("local-dev-better-auth-secret-change-me")) return false;
2229
+
2230
+ const hasProductionRuntimeGuard =
2231
+ /\bisProductionRuntime\s*=\s*process\.env\.NODE_ENV\s*===\s*["']production["']\s*&&\s*process\.env\.NEXT_PHASE\s*!==\s*["']phase-production-build["']/.test(
2232
+ source,
2233
+ ) &&
2234
+ /BETTER_AUTH_SECRET\s*:\s*isProductionRuntime\s*\?\s*BetterAuthSecret\s*:\s*BetterAuthSecret\.default\(LOCAL_BETTER_AUTH_SECRET\)/s.test(
2235
+ source,
2236
+ );
2237
+ const hasProductionGuard =
2238
+ /BETTER_AUTH_SECRET\s*:\s*process\.env\.NODE_ENV\s*===\s*["']production["']\s*\?\s*BetterAuthSecret\s*:\s*BetterAuthSecret\.default\(LOCAL_BETTER_AUTH_SECRET\)/s.test(
2239
+ source,
2240
+ );
2241
+
2242
+ return !(hasProductionRuntimeGuard || hasProductionGuard);
2243
+ }
2244
+
2245
+ function containsRequestTenantHeaderAuthority(source: string): boolean {
2246
+ return (
2247
+ /(?:(?:\breq|\brequest)\.headers|(?<!\.)\bheaders)\.get\(\s*["']x-tenant-id["']\s*\)/.test(
2248
+ source,
2249
+ ) &&
2250
+ (/\bcreateTenant\s*\(/.test(source) ||
2251
+ /\btenant\s*:\s*\{?\s*id\s*:/.test(source))
2252
+ );
2253
+ }
2254
+
2141
2255
  async function inspectEntitlementsProductionReadiness(
2142
2256
  targetDir: string,
2143
2257
  files: string[],
@@ -4091,6 +4205,7 @@ async function inspectDatabaseLifecycleDrift(
4091
4205
  if (!convention.resourceGenerator) return [];
4092
4206
 
4093
4207
  const diagnostics: InspectDiagnostic[] = [];
4208
+ const sourceCache = new Map<string, string>();
4094
4209
  const resources = await collectResourceNames(targetDir, files, config);
4095
4210
  const infrastructurePath = directoryPath(
4096
4211
  path.dirname(config.paths.infrastructurePorts),
@@ -4149,6 +4264,17 @@ async function inspectDatabaseLifecycleDrift(
4149
4264
  message: `This app has Drizzle database artifacts, but package.json does not define "${script}". Add an app-owned ${script} script so beignet db ${script.replace("db:", "")} can run the database lifecycle command.`,
4150
4265
  });
4151
4266
  }
4267
+
4268
+ diagnostics.push(
4269
+ ...(await inspectDurableDrizzleTableDrift(
4270
+ targetDir,
4271
+ files,
4272
+ config,
4273
+ infrastructurePath,
4274
+ schemaDir,
4275
+ sourceCache,
4276
+ )),
4277
+ );
4152
4278
  }
4153
4279
 
4154
4280
  const schemaFilePath = `${infrastructurePath}/db/schema.ts`;
@@ -4305,6 +4431,199 @@ async function inspectDatabaseLifecycleDrift(
4305
4431
  return diagnostics;
4306
4432
  }
4307
4433
 
4434
+ type DurableDrizzleTableRequirement = {
4435
+ capability: string;
4436
+ defaultTableName: string;
4437
+ code: string;
4438
+ factoryPattern: string;
4439
+ setupPattern?: string;
4440
+ };
4441
+
4442
+ const durableDrizzleTableRequirements: readonly DurableDrizzleTableRequirement[] =
4443
+ [
4444
+ {
4445
+ capability: "idempotency",
4446
+ defaultTableName: "idempotency_records",
4447
+ code: "BEIGNET_IDEMPOTENCY_TABLE_MISSING",
4448
+ factoryPattern: "createDrizzle[A-Za-z]+IdempotencyPort",
4449
+ setupPattern: "createDrizzle[A-Za-z]+IdempotencySetupStatements",
4450
+ },
4451
+ {
4452
+ capability: "outbox",
4453
+ defaultTableName: "outbox_messages",
4454
+ code: "BEIGNET_OUTBOX_TABLE_MISSING",
4455
+ factoryPattern: "createDrizzle[A-Za-z]+OutboxPort",
4456
+ setupPattern: "createDrizzle[A-Za-z]+OutboxSetupStatements",
4457
+ },
4458
+ {
4459
+ capability: "audit",
4460
+ defaultTableName: "audit_log",
4461
+ code: "BEIGNET_AUDIT_TABLE_MISSING",
4462
+ factoryPattern: "createDrizzle(?:[A-Za-z]+AuditLogPort|AuditLog)",
4463
+ setupPattern: "createDrizzle[A-Za-z]+AuditLogSetupStatements",
4464
+ },
4465
+ ];
4466
+
4467
+ async function inspectDurableDrizzleTableDrift(
4468
+ targetDir: string,
4469
+ files: string[],
4470
+ config: ResolvedBeignetConfig,
4471
+ infrastructurePath: string,
4472
+ schemaDir: string,
4473
+ sourceCache: Map<string, string>,
4474
+ ): Promise<InspectDiagnostic[]> {
4475
+ const diagnostics: InspectDiagnostic[] = [];
4476
+ const usageFiles = durableDrizzleUsageFiles(files, config);
4477
+
4478
+ for (const requirement of durableDrizzleTableRequirements) {
4479
+ const tableNames = await configuredDurableDrizzleTableNames(
4480
+ targetDir,
4481
+ usageFiles,
4482
+ requirement,
4483
+ sourceCache,
4484
+ );
4485
+ if (tableNames.size === 0) continue;
4486
+
4487
+ for (const tableName of tableNames) {
4488
+ if (
4489
+ await durableDrizzleTableExists(
4490
+ targetDir,
4491
+ files,
4492
+ infrastructurePath,
4493
+ schemaDir,
4494
+ requirement,
4495
+ tableName,
4496
+ sourceCache,
4497
+ )
4498
+ ) {
4499
+ continue;
4500
+ }
4501
+
4502
+ diagnostics.push({
4503
+ severity: "warning",
4504
+ code: requirement.code,
4505
+ file: schemaDir,
4506
+ message: `This app wires a Drizzle-backed ${requirement.capability} port, but no ${tableName} table setup was found in ${schemaDir}/, drizzle/, or app database setup files. Add the table to your Drizzle schema/migrations, call the provider setup statements, or remove the stale ${requirement.capability} wiring before deployment.`,
4507
+ });
4508
+ }
4509
+ }
4510
+
4511
+ return diagnostics;
4512
+ }
4513
+
4514
+ function durableDrizzleUsageFiles(
4515
+ files: string[],
4516
+ config: ResolvedBeignetConfig,
4517
+ ): string[] {
4518
+ return files.filter(
4519
+ (file) =>
4520
+ /\.(?:ts|tsx|mts|cts)$/.test(file) &&
4521
+ !isTestSourceFile(file) &&
4522
+ isInfraOrServerFile(file, config),
4523
+ );
4524
+ }
4525
+
4526
+ async function configuredDurableDrizzleTableNames(
4527
+ targetDir: string,
4528
+ files: string[],
4529
+ requirement: DurableDrizzleTableRequirement,
4530
+ sourceCache: Map<string, string>,
4531
+ ): Promise<Set<string>> {
4532
+ const tableNames = new Set<string>();
4533
+
4534
+ for (const file of files) {
4535
+ const source = await readCachedSource(targetDir, file, sourceCache);
4536
+ for (const tableName of tableNamesFromCalls(
4537
+ source,
4538
+ requirement.factoryPattern,
4539
+ requirement.defaultTableName,
4540
+ )) {
4541
+ tableNames.add(tableName);
4542
+ }
4543
+ }
4544
+
4545
+ return tableNames;
4546
+ }
4547
+
4548
+ async function durableDrizzleTableExists(
4549
+ targetDir: string,
4550
+ files: string[],
4551
+ infrastructurePath: string,
4552
+ schemaDir: string,
4553
+ requirement: DurableDrizzleTableRequirement,
4554
+ tableName: string,
4555
+ sourceCache: Map<string, string>,
4556
+ ): Promise<boolean> {
4557
+ const schemaFiles = files.filter(
4558
+ (file) =>
4559
+ file.startsWith(`${schemaDir}/`) &&
4560
+ /\.(?:ts|mts|cts)$/.test(file) &&
4561
+ !isTestSourceFile(file),
4562
+ );
4563
+ for (const file of schemaFiles) {
4564
+ const source = await readCachedSource(targetDir, file, sourceCache);
4565
+ if (source.includes(tableName)) return true;
4566
+ }
4567
+
4568
+ const setupFiles = files.filter(
4569
+ (file) =>
4570
+ !isTestSourceFile(file) &&
4571
+ (file.startsWith("drizzle/") ||
4572
+ file.startsWith(`${infrastructurePath}/db/`)) &&
4573
+ /\.(?:ts|js|mts|mjs|cts|cjs|sql)$/.test(file),
4574
+ );
4575
+ for (const file of setupFiles) {
4576
+ const source = await readCachedSource(targetDir, file, sourceCache);
4577
+ if (sourceCreatesTable(source, tableName)) return true;
4578
+
4579
+ if (!requirement.setupPattern) continue;
4580
+ if (
4581
+ tableNamesFromCalls(
4582
+ source,
4583
+ requirement.setupPattern,
4584
+ requirement.defaultTableName,
4585
+ ).has(tableName)
4586
+ ) {
4587
+ return true;
4588
+ }
4589
+ }
4590
+
4591
+ return false;
4592
+ }
4593
+
4594
+ function sourceCreatesTable(source: string, tableName: string): boolean {
4595
+ return new RegExp(
4596
+ `\\bCREATE\\s+TABLE\\b[\\s\\S]{0,200}${escapeRegExp(tableName)}\\b`,
4597
+ "i",
4598
+ ).test(source);
4599
+ }
4600
+
4601
+ function tableNamesFromCalls(
4602
+ source: string,
4603
+ calleePattern: string,
4604
+ defaultTableName: string,
4605
+ ): Set<string> {
4606
+ const tableNames = new Set<string>();
4607
+ const callPattern = new RegExp(
4608
+ `\\b${calleePattern}\\s*\\(([\\s\\S]{0,800}?)\\)`,
4609
+ "g",
4610
+ );
4611
+
4612
+ for (const match of source.matchAll(callPattern)) {
4613
+ const matchIndex = match.index ?? 0;
4614
+ const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
4615
+ if (/\bfunction\s+$/.test(prefix)) continue;
4616
+
4617
+ const argsSource = match[1] ?? "";
4618
+ const tableName = /\btableName\s*:\s*["'`]([^"'`]+)["'`]/.exec(
4619
+ argsSource,
4620
+ )?.[1];
4621
+ tableNames.add(tableName ?? defaultTableName);
4622
+ }
4623
+
4624
+ return tableNames;
4625
+ }
4626
+
4308
4627
  function hasRepositoryAdapterFile(
4309
4628
  files: string[],
4310
4629
  infrastructureDir: string,
package/src/lib.ts CHANGED
@@ -18,7 +18,7 @@ export {
18
18
  inspectApp,
19
19
  } from "./inspect.js";
20
20
  export { formatLint, formatLintGithub, lintApp } from "./lint.js";
21
- export type { MakeFeatureAddon } from "./make.js";
21
+ export type { MakeFeatureAddon, MakeFeatureRecipe } from "./make.js";
22
22
  export {
23
23
  makeAdapter,
24
24
  makeContract,
@@ -26,6 +26,7 @@ export {
26
26
  makeFactory,
27
27
  makeFeature,
28
28
  makeFeatureAddonChoices,
29
+ makeFeatureRecipeChoices,
29
30
  makeJob,
30
31
  makeListener,
31
32
  makeNotification,