@beignet/cli 0.0.41 → 0.0.43

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 (100) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +96 -25
  3. package/dist/analysis/source-index.d.ts +1 -1
  4. package/dist/analysis/source-index.d.ts.map +1 -1
  5. package/dist/analysis/source-index.js +4 -2
  6. package/dist/analysis/source-index.js.map +1 -1
  7. package/dist/analysis/workspace.d.ts +3 -2
  8. package/dist/analysis/workspace.d.ts.map +1 -1
  9. package/dist/analysis/workspace.js +51 -39
  10. package/dist/analysis/workspace.js.map +1 -1
  11. package/dist/app-map.d.ts.map +1 -1
  12. package/dist/app-map.js +65 -4
  13. package/dist/app-map.js.map +1 -1
  14. package/dist/choices.d.ts +18 -0
  15. package/dist/choices.d.ts.map +1 -1
  16. package/dist/choices.js +35 -0
  17. package/dist/choices.js.map +1 -1
  18. package/dist/db.d.ts +18 -7
  19. package/dist/db.d.ts.map +1 -1
  20. package/dist/db.js +20 -7
  21. package/dist/db.js.map +1 -1
  22. package/dist/doctor-fixes.d.ts +64 -0
  23. package/dist/doctor-fixes.d.ts.map +1 -0
  24. package/dist/doctor-fixes.js +142 -0
  25. package/dist/doctor-fixes.js.map +1 -0
  26. package/dist/explain.d.ts +3 -1
  27. package/dist/explain.d.ts.map +1 -1
  28. package/dist/explain.js +136 -42
  29. package/dist/explain.js.map +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +92 -25
  32. package/dist/index.js.map +1 -1
  33. package/dist/inspect.d.ts +38 -9
  34. package/dist/inspect.d.ts.map +1 -1
  35. package/dist/inspect.js +385 -251
  36. package/dist/inspect.js.map +1 -1
  37. package/dist/lib.d.ts +6 -2
  38. package/dist/lib.d.ts.map +1 -1
  39. package/dist/lib.js +3 -2
  40. package/dist/lib.js.map +1 -1
  41. package/dist/make/inbox.js +4 -4
  42. package/dist/make/payments.js +6 -6
  43. package/dist/make/shared.js +3 -3
  44. package/dist/make/shared.js.map +1 -1
  45. package/dist/make/tenancy.d.ts.map +1 -1
  46. package/dist/make/tenancy.js +12 -14
  47. package/dist/make/tenancy.js.map +1 -1
  48. package/dist/make.d.ts.map +1 -1
  49. package/dist/make.js +33 -23
  50. package/dist/make.js.map +1 -1
  51. package/dist/mcp.d.ts.map +1 -1
  52. package/dist/mcp.js +121 -13
  53. package/dist/mcp.js.map +1 -1
  54. package/dist/registry-edits.d.ts +7 -1
  55. package/dist/registry-edits.d.ts.map +1 -1
  56. package/dist/registry-edits.js +61 -43
  57. package/dist/registry-edits.js.map +1 -1
  58. package/dist/templates/agents.d.ts.map +1 -1
  59. package/dist/templates/agents.js +34 -11
  60. package/dist/templates/agents.js.map +1 -1
  61. package/dist/templates/base.d.ts.map +1 -1
  62. package/dist/templates/base.js +5 -5
  63. package/dist/templates/base.js.map +1 -1
  64. package/dist/templates/db/mysql.js +3 -3
  65. package/dist/templates/db/postgres.js +3 -3
  66. package/dist/templates/db/sqlite.js +3 -3
  67. package/dist/templates/server.js +4 -4
  68. package/dist/templates/server.js.map +1 -1
  69. package/dist/templates/shared.d.ts +2 -1
  70. package/dist/templates/shared.d.ts.map +1 -1
  71. package/dist/templates/shared.js +7 -4
  72. package/dist/templates/shared.js.map +1 -1
  73. package/dist/templates/todos.js +2 -2
  74. package/package.json +3 -2
  75. package/skills/app-structure/SKILL.md +43 -10
  76. package/src/analysis/source-index.ts +12 -3
  77. package/src/analysis/workspace.ts +94 -52
  78. package/src/app-map.ts +82 -4
  79. package/src/choices.ts +57 -0
  80. package/src/db.ts +45 -15
  81. package/src/doctor-fixes.ts +252 -0
  82. package/src/explain.ts +151 -43
  83. package/src/index.ts +130 -35
  84. package/src/inspect.ts +537 -295
  85. package/src/lib.ts +28 -1
  86. package/src/make/inbox.ts +4 -4
  87. package/src/make/payments.ts +6 -6
  88. package/src/make/shared.ts +3 -3
  89. package/src/make/tenancy.ts +12 -14
  90. package/src/make.ts +36 -24
  91. package/src/mcp.ts +187 -13
  92. package/src/registry-edits.ts +81 -39
  93. package/src/templates/agents.ts +34 -11
  94. package/src/templates/base.ts +5 -4
  95. package/src/templates/db/mysql.ts +3 -3
  96. package/src/templates/db/postgres.ts +3 -3
  97. package/src/templates/db/sqlite.ts +3 -3
  98. package/src/templates/server.ts +4 -4
  99. package/src/templates/shared.ts +14 -6
  100. package/src/templates/todos.ts +2 -2
@@ -1,3 +1,5 @@
1
+ import ts from "typescript";
2
+
1
3
  /**
2
4
  * Shared string-edit helpers for registry files.
3
5
  *
@@ -28,21 +30,35 @@ export type AppendResult =
28
30
  | { kind: "missing" };
29
31
 
30
32
  /**
31
- * Find the index of the delimiter closing the one at `openIndex`, skipping
32
- * string literals.
33
+ * Yield indexes that belong to executable source, excluding string literals
34
+ * and line/block comments. Registry scanners share this lexer so delimiters
35
+ * in user comments cannot corrupt their depth tracking.
33
36
  */
34
- export function matchingDelimiterIndex(
37
+ export function* codeCharacterIndexes(
35
38
  source: string,
36
- openIndex: number,
37
- open: string,
38
- close: string,
39
- ): number {
40
- let depth = 0;
39
+ start = 0,
40
+ end = source.length,
41
+ ): Generator<number> {
41
42
  let inString: string | undefined;
42
43
  let escaped = false;
44
+ let inLineComment = false;
45
+ let inBlockComment = false;
43
46
 
44
- for (let index = openIndex; index < source.length; index++) {
47
+ for (let index = start; index < end; index++) {
45
48
  const char = source[index];
49
+ const next = source[index + 1];
50
+
51
+ if (inLineComment) {
52
+ if (char === "\n" || char === "\r") inLineComment = false;
53
+ continue;
54
+ }
55
+ if (inBlockComment) {
56
+ if (char === "*" && next === "/") {
57
+ inBlockComment = false;
58
+ index++;
59
+ }
60
+ continue;
61
+ }
46
62
  if (inString) {
47
63
  if (escaped) {
48
64
  escaped = false;
@@ -54,11 +70,40 @@ export function matchingDelimiterIndex(
54
70
  continue;
55
71
  }
56
72
 
73
+ if (char === "/" && next === "/") {
74
+ inLineComment = true;
75
+ index++;
76
+ continue;
77
+ }
78
+ if (char === "/" && next === "*") {
79
+ inBlockComment = true;
80
+ index++;
81
+ continue;
82
+ }
57
83
  if (char === '"' || char === "'" || char === "`") {
58
84
  inString = char;
59
85
  continue;
60
86
  }
61
87
 
88
+ yield index;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Find the index of the delimiter closing the one at `openIndex`, skipping
94
+ * string literals and comments.
95
+ */
96
+ export function matchingDelimiterIndex(
97
+ source: string,
98
+ openIndex: number,
99
+ open: string,
100
+ close: string,
101
+ ): number {
102
+ let depth = 0;
103
+
104
+ for (const index of codeCharacterIndexes(source, openIndex)) {
105
+ const char = source[index];
106
+
62
107
  if (char === open) {
63
108
  depth++;
64
109
  continue;
@@ -77,21 +122,32 @@ export function matchingDelimiterIndex(
77
122
  * Insert a line directly after the last top-level import statement.
78
123
  */
79
124
  export function insertAfterImports(source: string, line: string): string {
80
- const lines = source.split("\n");
81
- let lastImportIndex = -1;
82
-
83
- for (let index = 0; index < lines.length; index++) {
84
- if (lines[index].startsWith("import ")) {
85
- lastImportIndex = index;
86
- }
125
+ const sourceFile = ts.createSourceFile(
126
+ "registry.ts",
127
+ source,
128
+ ts.ScriptTarget.Latest,
129
+ true,
130
+ ts.ScriptKind.TS,
131
+ );
132
+ let lastImport: ts.ImportDeclaration | undefined;
133
+ for (const statement of sourceFile.statements) {
134
+ if (ts.isImportDeclaration(statement)) lastImport = statement;
87
135
  }
88
136
 
89
- if (lastImportIndex === -1) {
137
+ if (!lastImport) {
90
138
  return `${line}\n${source}`;
91
139
  }
92
140
 
93
- lines.splice(lastImportIndex + 1, 0, line);
94
- return lines.join("\n");
141
+ const lineBreak = source.includes("\r\n") ? "\r\n" : "\n";
142
+ const lineEnd = source.indexOf(lineBreak, lastImport.end);
143
+ if (lineEnd === -1) {
144
+ return `${source}${lineBreak}${line}${lineBreak}`;
145
+ }
146
+
147
+ const insertAt = lineEnd + lineBreak.length;
148
+ return `${source.slice(0, insertAt)}${line}${lineBreak}${source.slice(
149
+ insertAt,
150
+ )}`;
95
151
  }
96
152
 
97
153
  /**
@@ -147,11 +203,14 @@ export function addNamedValueImport(
147
203
  export function identifiersFromArrayExpression(
148
204
  expression: string,
149
205
  ): Set<string> {
150
- const withoutBrackets = expression.replace(/^\[/, "").replace(/\]$/, "");
206
+ const code = Array.from({ length: expression.length }, () => " ");
207
+ for (const index of codeCharacterIndexes(expression)) {
208
+ code[index] = expression[index];
209
+ }
151
210
 
152
211
  return new Set(
153
212
  Array.from(
154
- withoutBrackets.matchAll(/\b[A-Za-z_$][\w$]*\b/g),
213
+ code.join("").matchAll(/\b[A-Za-z_$][\w$]*\b/g),
155
214
  ([name]) => name,
156
215
  ),
157
216
  );
@@ -342,26 +401,9 @@ function topLevelValueSlice(
342
401
  if (start >= hardEnd) return undefined;
343
402
 
344
403
  let depth = 0;
345
- let inString: string | undefined;
346
- let escaped = false;
347
404
 
348
- for (let index = start; index <= hardEnd; index++) {
405
+ for (const index of codeCharacterIndexes(source, start, hardEnd + 1)) {
349
406
  const char = source[index];
350
- if (inString) {
351
- if (escaped) {
352
- escaped = false;
353
- } else if (char === "\\") {
354
- escaped = true;
355
- } else if (char === inString) {
356
- inString = undefined;
357
- }
358
- continue;
359
- }
360
-
361
- if (char === '"' || char === "'" || char === "`") {
362
- inString = char;
363
- continue;
364
- }
365
407
 
366
408
  if (char === "[" || char === "(" || char === "{") {
367
409
  depth++;
@@ -62,8 +62,14 @@ use, so their absence is fine. \`${cli} make event\`, \`${cli} make job\`,
62
62
  \`${cli} make listener\`, \`${cli} make schedule\`, \`${cli} make task\`,
63
63
  \`${cli} make seed\`, and \`${cli} make upload\` create or update their
64
64
  required app entrypoints.
65
- \`${cli} doctor\` detects registration drift. \`${cli} doctor --fix\` repairs
66
- route-group, schedule, task, outbox, and listener registration.
65
+ Upload generation validates the central \`uploadRegistry = defineUploads({ ... })\`
66
+ shape before writing; when a custom route cannot be edited safely, follow the
67
+ manual registry instruction in the error.
68
+ \`${cli} doctor\` detects registration drift. Preview exact repair patches
69
+ with \`${cli} doctor --fix --dry-run\`, then apply the returned plan with
70
+ \`${cli} doctor --fix --plan <plan-id>\` and optional
71
+ \`--only <operation-ids>\`. Plain \`${cli} doctor --fix\` remains the
72
+ apply-all shortcut.
67
73
 
68
74
  ## Prefer generators
69
75
 
@@ -73,6 +79,9 @@ artifact list. Use \`${cli} make feature <name> --recipe full-slice\` when you
73
79
  need a richer reference slice with policy, client helpers, workflow artifacts,
74
80
  events, listener registration, jobs, and outbox wiring. After changing the Drizzle schema in
75
81
  \`infra/db/schema/\`, run \`${cli} db generate\` then \`${cli} db migrate\`.
82
+ When MCP is available, use \`db_schema_sync\` for Beignet provider table
83
+ re-exports and \`db\` for \`generate\`, \`migrate\`, \`seed\`, or \`reset\`
84
+ instead of falling back to a shell.
76
85
 
77
86
  ## The framework already solves these
78
87
 
@@ -109,7 +118,11 @@ need one check alone.
109
118
 
110
119
  Before exploring unfamiliar code, run \`${cli} map\` for the human inventory
111
120
  or \`${cli} map --json --feature <name>\` for an evidence-backed graph scoped
112
- to one feature.
121
+ to one feature. The graph honors the app's complete TypeScript \`baseUrl\` and
122
+ \`paths\` configuration. Fix \`local_import_unresolved\` and
123
+ \`contract_declaration_unresolved\` entries rather than treating their missing
124
+ edges as proof that no relationship exists. Registration diagnostics expose
125
+ the declaration as \`subject.file\` and \`subject.exportName\`.
113
126
 
114
127
  ## Package skills
115
128
 
@@ -148,17 +161,27 @@ skill-loading block.
148
161
  \`.mcp.json\` registers the app-local \`@beignet/cli\` bin at
149
162
  \`./node_modules/.bin/beignet mcp\`, which exposes the app map, validation, and
150
163
  generators as structured tools named exactly: \`app_map\`, \`explain\`,
151
- \`check\`, \`routes\`, \`doctor\`, \`doctor_fix\`, \`lint\`, \`make\`,
152
- \`provider_add\`. Use \`app_map\` before broad code search and pass a feature
164
+ \`check\`, \`db\`, \`db_schema_sync\`, \`routes\`, \`doctor\`,
165
+ \`doctor_fix_plan\`, \`doctor_fix\`, \`lint\`, \`make\`, \`provider_add\`.
166
+ Use \`doctor_fix_plan\` to inspect
167
+ stable operation IDs, hashes, exact patches, and current diagnostics before
168
+ passing its \`planId\` and optional \`fixIds\` to \`doctor_fix\`. Guarded
169
+ apply returns the same plan metadata and inspection payload as the CLI. Use
170
+ \`app_map\` before broad code search and pass a feature
153
171
  or node kinds to keep context focused. Use \`explain\` for source-backed
154
- relationships, conventions, findings, and suggested files for one feature,
155
- route, provider, or diagnostic. Use \`check\` after edits to run the complete
156
- validation loop; it returns the same versioned result as
172
+ relationships, conventions, findings, and suggested files for any mapped
173
+ concept or diagnostic; its kinds match the app map. Use \`check\` after edits
174
+ to run the complete validation loop; it returns the same versioned result as
157
175
  \`${cli} check --json\`, bounds failure output, and cancels the active package
158
176
  script when the MCP request is cancelled. It does not apply Beignet fixes, but
159
- app-owned scripts retain their normal side effects. Clients that do not read
160
- \`.mcp.json\` can use the same command from the app root; use \`${cli} mcp\`
161
- only for terminal debugging.
177
+ app-owned scripts retain their normal side effects. Use \`db_schema_sync\` to
178
+ preview or apply idempotent provider-table schema re-exports, then call \`db\`
179
+ with \`generate\` and \`migrate\`; database output is bounded, commands time
180
+ out, and cancellation stops the active process tree. Treat \`seed\` and
181
+ especially \`reset\` as app-owned mutations. Lifecycle \`dryRun\` validates
182
+ and reports the script without executing it; it does not simulate SQL or data
183
+ changes. Clients that do not read \`.mcp.json\` can use the same command from
184
+ the app root; use \`${cli} mcp\` only for terminal debugging.
162
185
  `;
163
186
  }
164
187
 
@@ -1,6 +1,7 @@
1
1
  import { databaseLocalUrl, databaseStartCommand } from "../choices.js";
2
2
  import { shadcnDependencies, shadcnDevDependencies } from "./shadcn.js";
3
3
  import {
4
+ currentGeneratedPackageScripts,
4
5
  externalVersions,
5
6
  hasStarterProvider,
6
7
  json,
@@ -97,11 +98,11 @@ export function packageJson(ctx: TemplateContext): string {
97
98
  start: "next start",
98
99
  lint: "biome lint .",
99
100
  format: "biome format --write .",
100
- test: "tsx lib/beignet-test-runner.ts",
101
+ test: currentGeneratedPackageScripts.test,
101
102
  typecheck: "tsc --noEmit",
102
103
  "db:generate": "drizzle-kit generate",
103
104
  "db:migrate": "drizzle-kit migrate",
104
- "db:reset": "tsx infra/db/reset.ts",
105
+ "db:reset": currentGeneratedPackageScripts["db:reset"],
105
106
  };
106
107
 
107
108
  return json({
@@ -361,7 +362,7 @@ ${optionalAuthRedirects}
361
362
  ? ""
362
363
  : "; it refuses non-local hosts unless `BEIGNET_ALLOW_DATABASE_RESET=true`"
363
364
  }.
364
- - Remove \`DEVTOOLS_ENABLED=true\` in production unless you add authentication and stricter redaction.
365
+ - Remove \`DEVTOOLS_ENABLED=true\` outside development unless you add authentication and stricter redaction.
365
366
  - Set \`APP_URL\`, \`BETTER_AUTH_SECRET\`, \`LOG_LEVEL\`, and provider-specific variables in your hosting environment.
366
367
  - Set \`BETTER_AUTH_TRUSTED_ORIGINS\` before serving auth across multiple origins.
367
368
  - Review the starter authorization policy before exposing user-owned data.
@@ -462,7 +463,7 @@ export function envExample(ctx: TemplateContext): string {
462
463
  "# Copy to .env.local and fill in values for your app.",
463
464
  "APP_URL=http://localhost:3000",
464
465
  "",
465
- "# Dev-only default; remove or protect in production.",
466
+ "# Local-development default; remove or protect outside development.",
466
467
  "DEVTOOLS_ENABLED=true",
467
468
  "",
468
469
  "LOG_LEVEL=info",
@@ -126,9 +126,9 @@ export const todos = mysqlTable(
126
126
  // unmapped, and list ordering compares the strings lexicographically.
127
127
  createdAt: varchar("created_at", { length: 32 }).notNull(),
128
128
  },
129
- (table) => ({
130
- userIdx: index("todos_user_idx").on(table.userId, table.createdAt),
131
- }),
129
+ (table) => [
130
+ index("todos_user_idx").on(table.userId, table.createdAt),
131
+ ],
132
132
  );
133
133
  `,
134
134
  drizzleTodoRepository: `import "@beignet/core/server-only";
@@ -116,9 +116,9 @@ export const todos = pgTable(
116
116
  // unmapped, and list ordering compares the strings lexicographically.
117
117
  createdAt: text("created_at").notNull(),
118
118
  },
119
- (table) => ({
120
- userIdx: index("todos_user_idx").on(table.userId, table.createdAt),
121
- }),
119
+ (table) => [
120
+ index("todos_user_idx").on(table.userId, table.createdAt),
121
+ ],
122
122
  );
123
123
  `,
124
124
  drizzleTodoRepository: `import "@beignet/core/server-only";
@@ -112,9 +112,9 @@ export const todos = sqliteTable(
112
112
  .default(false),
113
113
  createdAt: text("created_at").notNull(),
114
114
  },
115
- (table) => ({
116
- userIdx: index("todos_user_idx").on(table.userId, table.createdAt),
117
- }),
115
+ (table) => [
116
+ index("todos_user_idx").on(table.userId, table.createdAt),
117
+ ],
118
118
  );
119
119
  `,
120
120
  drizzleTodoRepository: `import "@beignet/core/server-only";
@@ -34,7 +34,7 @@ export function serverProviders(ctx: TemplateContext): string {
34
34
 
35
35
  const entries = [
36
36
  "\tcreateDevtoolsProvider(),",
37
- "\tcreateBetterAuthProvider<AuthUser, AuthSessionMetadata>(auth),",
37
+ "\tcreateBetterAuthProvider<AuthUser, AuthSessionMetadata>({ auth }),",
38
38
  "\tcreatePinoLoggerProvider(),",
39
39
  `\t${db.providerInstance},`,
40
40
  "\tstarterDatabaseProvider,",
@@ -196,12 +196,12 @@ export const env = createEnv({
196
196
  NODE_ENV: z
197
197
  .enum(["development", "test", "production"])
198
198
  .default("development"),
199
- APP_URL: z.string().url().default("http://localhost:3000"),
199
+ APP_URL: z.url().default("http://localhost:3000"),
200
200
  DEVTOOLS_ENABLED: BooleanEnv.optional(),
201
201
  BETTER_AUTH_SECRET: isProductionRuntime
202
202
  ? BetterAuthSecret
203
203
  : BetterAuthSecret.default(LOCAL_BETTER_AUTH_SECRET),
204
- BETTER_AUTH_URL: z.string().url().default("http://localhost:3000"),
204
+ BETTER_AUTH_URL: z.url().default("http://localhost:3000"),
205
205
  BETTER_AUTH_TRUSTED_ORIGINS: z.string().optional(),
206
206
  ${databaseEnvLines}
207
207
  LOG_LEVEL: z
@@ -217,7 +217,7 @@ ${inngestEnvLines}
217
217
  export const isProduction = env.NODE_ENV === "production";
218
218
 
219
219
  export const isDevtoolsEnabled =
220
- env.DEVTOOLS_ENABLED ?? process.env.NODE_ENV !== "production";
220
+ env.DEVTOOLS_ENABLED ?? env.NODE_ENV === "development";
221
221
  `;
222
222
  }
223
223
 
@@ -67,18 +67,26 @@ 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
- };
70
+ export const legacyGeneratedPackageScripts: Record<string, readonly string[]> =
71
+ {
72
+ "db:reset": ["bun infra/db/reset.ts"],
73
+ "db:seed": ["bun server/seed.ts"],
74
+ test: ["bun test", "tsx lib/beignet-test-runner.ts"],
75
+ };
75
76
 
76
77
  export const currentGeneratedPackageScripts: Record<string, string> = {
77
78
  "db:reset": "tsx infra/db/reset.ts",
78
79
  "db:seed": "tsx server/seed.ts",
79
- test: "tsx lib/beignet-test-runner.ts",
80
+ test: "node --import tsx lib/beignet-test-runner.ts",
80
81
  };
81
82
 
83
+ export function isLegacyGeneratedPackageScript(
84
+ name: string,
85
+ command: string | undefined,
86
+ ): boolean {
87
+ return legacyGeneratedPackageScripts[name]?.includes(command ?? "") ?? false;
88
+ }
89
+
82
90
  /**
83
91
  * Dialect-specific names used when rendering database-aware templates.
84
92
  */
@@ -2,7 +2,7 @@ const files = {
2
2
  schemas: `import { z } from "zod";
3
3
 
4
4
  export const TodoSchema = z.object({
5
- id: z.string().uuid(),
5
+ id: z.uuid(),
6
6
  userId: z.string(),
7
7
  title: z.string().min(1),
8
8
  completed: z.boolean(),
@@ -26,7 +26,7 @@ export const ListTodosOutputSchema = z.object({
26
26
  });
27
27
 
28
28
  export const TodoIdInputSchema = z.object({
29
- id: z.string().uuid(),
29
+ id: z.uuid(),
30
30
  });
31
31
 
32
32
  export const CreateTodoInputSchema = z.object({