@beignet/cli 0.0.51 → 0.0.53

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 (117) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +182 -19
  3. package/dist/analysis/layers.d.ts +2 -0
  4. package/dist/analysis/layers.d.ts.map +1 -1
  5. package/dist/analysis/layers.js +9 -2
  6. package/dist/analysis/layers.js.map +1 -1
  7. package/dist/analysis/port-wiring.d.ts +4 -2
  8. package/dist/analysis/port-wiring.d.ts.map +1 -1
  9. package/dist/analysis/port-wiring.js +41 -11
  10. package/dist/analysis/port-wiring.js.map +1 -1
  11. package/dist/analysis/source-files.d.ts +15 -0
  12. package/dist/analysis/source-files.d.ts.map +1 -0
  13. package/dist/analysis/source-files.js +52 -0
  14. package/dist/analysis/source-files.js.map +1 -0
  15. package/dist/analysis/source-index.d.ts +2 -1
  16. package/dist/analysis/source-index.d.ts.map +1 -1
  17. package/dist/analysis/source-index.js +34 -5
  18. package/dist/analysis/source-index.js.map +1 -1
  19. package/dist/analysis/workspace-routes.d.ts +8 -0
  20. package/dist/analysis/workspace-routes.d.ts.map +1 -0
  21. package/dist/analysis/workspace-routes.js +291 -0
  22. package/dist/analysis/workspace-routes.js.map +1 -0
  23. package/dist/analysis/workspace.d.ts +24 -3
  24. package/dist/analysis/workspace.d.ts.map +1 -1
  25. package/dist/analysis/workspace.js +184 -43
  26. package/dist/analysis/workspace.js.map +1 -1
  27. package/dist/app-map-changes.d.ts.map +1 -1
  28. package/dist/app-map-changes.js +24 -14
  29. package/dist/app-map-changes.js.map +1 -1
  30. package/dist/app-map.d.ts.map +1 -1
  31. package/dist/app-map.js +87 -81
  32. package/dist/app-map.js.map +1 -1
  33. package/dist/check.d.ts +12 -0
  34. package/dist/check.d.ts.map +1 -1
  35. package/dist/check.js +92 -25
  36. package/dist/check.js.map +1 -1
  37. package/dist/config.d.ts +7 -0
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +19 -0
  40. package/dist/config.js.map +1 -1
  41. package/dist/git-changes.d.ts +17 -1
  42. package/dist/git-changes.d.ts.map +1 -1
  43. package/dist/git-changes.js +118 -84
  44. package/dist/git-changes.js.map +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +23 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/inspect.d.ts.map +1 -1
  49. package/dist/inspect.js +424 -129
  50. package/dist/inspect.js.map +1 -1
  51. package/dist/lint.d.ts.map +1 -1
  52. package/dist/lint.js +108 -43
  53. package/dist/lint.js.map +1 -1
  54. package/dist/make/shared.js +1 -1
  55. package/dist/make.js +18 -1
  56. package/dist/make.js.map +1 -1
  57. package/dist/mcp.js +1 -1
  58. package/dist/mcp.js.map +1 -1
  59. package/dist/provider-add.js +2 -2
  60. package/dist/provider-add.js.map +1 -1
  61. package/dist/provider-audit.d.ts.map +1 -1
  62. package/dist/provider-audit.js +22 -6
  63. package/dist/provider-audit.js.map +1 -1
  64. package/dist/templates/agents.d.ts.map +1 -1
  65. package/dist/templates/agents.js +36 -13
  66. package/dist/templates/agents.js.map +1 -1
  67. package/dist/templates/base.js +5 -5
  68. package/dist/templates/db/mysql.d.ts.map +1 -1
  69. package/dist/templates/db/mysql.js +51 -22
  70. package/dist/templates/db/mysql.js.map +1 -1
  71. package/dist/templates/db/postgres.d.ts.map +1 -1
  72. package/dist/templates/db/postgres.js +60 -20
  73. package/dist/templates/db/postgres.js.map +1 -1
  74. package/dist/templates/db/sqlite.d.ts.map +1 -1
  75. package/dist/templates/db/sqlite.js +51 -23
  76. package/dist/templates/db/sqlite.js.map +1 -1
  77. package/dist/templates/shadcn.js +1 -1
  78. package/dist/templates/shadcn.js.map +1 -1
  79. package/dist/templates/shared.js +6 -6
  80. package/dist/templates/shared.js.map +1 -1
  81. package/dist/templates/testing.d.ts.map +1 -1
  82. package/dist/templates/testing.js +7 -19
  83. package/dist/templates/testing.js.map +1 -1
  84. package/dist/test-discovery.d.ts +9 -0
  85. package/dist/test-discovery.d.ts.map +1 -0
  86. package/dist/test-discovery.js +40 -0
  87. package/dist/test-discovery.js.map +1 -0
  88. package/package.json +8 -5
  89. package/skills/app-structure/SKILL.md +74 -7
  90. package/src/analysis/layers.ts +13 -2
  91. package/src/analysis/port-wiring.ts +68 -11
  92. package/src/analysis/source-files.ts +61 -0
  93. package/src/analysis/source-index.ts +56 -4
  94. package/src/analysis/workspace-routes.ts +385 -0
  95. package/src/analysis/workspace.ts +302 -59
  96. package/src/app-map-changes.ts +41 -13
  97. package/src/app-map.ts +114 -103
  98. package/src/check.ts +115 -25
  99. package/src/config.ts +33 -0
  100. package/src/git-changes.ts +218 -86
  101. package/src/index.ts +33 -5
  102. package/src/inspect.ts +608 -156
  103. package/src/lint.ts +152 -48
  104. package/src/make/shared.ts +1 -1
  105. package/src/make.ts +31 -1
  106. package/src/mcp.ts +1 -1
  107. package/src/provider-add.ts +2 -2
  108. package/src/provider-audit.ts +30 -10
  109. package/src/templates/agents.ts +36 -13
  110. package/src/templates/base.ts +5 -5
  111. package/src/templates/db/mysql.ts +51 -22
  112. package/src/templates/db/postgres.ts +60 -20
  113. package/src/templates/db/sqlite.ts +51 -23
  114. package/src/templates/shadcn.ts +1 -1
  115. package/src/templates/shared.ts +6 -6
  116. package/src/templates/testing.ts +12 -19
  117. package/src/test-discovery.ts +53 -0
@@ -41,8 +41,8 @@ https://beignetjs.com (agent-friendly index at https://beignetjs.com/llms.txt).
41
41
  README.md covers setup and the app map; this file covers what is not
42
42
  discoverable from the code.
43
43
 
44
- This app requires Node.js 22.12 or newer. Bun 1.3.14 or newer is supported for
45
- Beignet commands.
44
+ This app requires Node.js 22.12 or newer. Bun is optional; npm, pnpm, Yarn, and
45
+ Bun can run Beignet commands.
46
46
 
47
47
  ## Registration is explicit
48
48
 
@@ -71,6 +71,12 @@ is a silent failure — the file exists but never runs:
71
71
  - Apps using runtime integrity must list workflow registries in
72
72
  \`defineRuntimeManifest({...})\`.
73
73
 
74
+ Event schemas must output canonical JSON transport data. Use null, strings,
75
+ finite numbers, booleans, arrays, and plain objects. Keep validation
76
+ deterministic and side-effect-free, keep transforms idempotent, and represent
77
+ timestamps as strings or numbers rather than \`Date\` objects.
78
+ Beignet rejects payloads that would change when decoded and validated again.
79
+
74
80
  The starter ships no workflow registries — generators create them on first
75
81
  use, so their absence is fine. \`${cli} make event\`, \`${cli} make job\`,
76
82
  \`${cli} make listener\`, \`${cli} make schedule\`, \`${cli} make task\`,
@@ -146,20 +152,37 @@ formatting. The individual commands (\`${lint}\`, \`${cli} lint\`,
146
152
  \`${cli} doctor --strict\`, \`${test}\`, \`${typecheck}\`) still work when you
147
153
  need one check alone.
148
154
 
149
- Beignet's architecture lint parses TypeScript imports and re-exports,
150
- \`require()\`, import assignments, and literal dynamic imports, then follows
151
- local value imports through feature-root helpers. When lint cannot verify a
152
- computed module reference inside a constrained layer, use a string-literal
153
- specifier or move runtime loading behind an allowed boundary. Move near-miss
154
- feature layers to the canonical path named in the diagnostic.
155
+ Inside a Git worktree, \`${cli} check\` fingerprints HEAD, the index, the
156
+ worktree, and non-ignored untracked files before and after validation. If the
157
+ ending fingerprint differs from the baseline, the result is stale and
158
+ unsuccessful even when every step passed; rerun the check against the resulting
159
+ source. Outside Git, the same validation runs with source provenance marked
160
+ unavailable; report that limitation instead of describing the result as
161
+ source-current.
162
+
163
+ Beignet's architecture lint parses JavaScript and TypeScript imports and
164
+ re-exports, \`require()\`, import assignments, and literal dynamic imports, then
165
+ follows local value imports through feature-root helpers. This includes JSX
166
+ and the \`.mts\`, \`.cts\`, \`.mjs\`, and \`.cjs\` module variants. When lint
167
+ cannot verify a computed module reference inside a constrained layer, use a
168
+ string-literal specifier or move runtime loading behind an allowed boundary.
169
+ Move near-miss feature layers to the canonical path named in the diagnostic.
155
170
 
156
171
  Before exploring unfamiliar code, run \`${cli} map\` for the human inventory
157
172
  or \`${cli} map --json --feature <name>\` for an evidence-backed graph scoped
158
- to one feature. The graph honors the app's complete TypeScript \`baseUrl\` and
159
- \`paths\` configuration. Fix \`local_import_unresolved\` and
160
- \`contract_declaration_unresolved\` entries rather than treating their missing
161
- edges as proof that no relationship exists. Registration diagnostics expose
162
- the declaration as \`subject.file\` and \`subject.exportName\`.
173
+ to one feature. The graph analyzes JavaScript and TypeScript source and honors
174
+ the app's complete \`baseUrl\` and \`paths\` configuration from
175
+ \`tsconfig.json\`, or \`jsconfig.json\` when no TypeScript config exists. Fix
176
+ \`local_import_unresolved\` and \`contract_declaration_unresolved\` entries
177
+ rather than treating their missing edges as proof that no relationship exists.
178
+ Registration diagnostics expose the declaration as \`subject.file\` and
179
+ \`subject.exportName\`.
180
+
181
+ The graph includes JavaScript and TypeScript test/spec files under canonical
182
+ and configured app test roots. Feature tests retain feature ownership;
183
+ adjacent app, infra, server, and top-level tests remain app-level nodes and can
184
+ appear as reverse-import consumers in changed mode. Treat that as impact
185
+ evidence, not proof of behavioral coverage.
163
186
 
164
187
  After editing in a Git worktree, run \`${cli} map --changed\` before the full
165
188
  check. It reports direct changes, reverse imports, one-hop semantic consumers,
@@ -247,8 +247,8 @@ route groups such as \`app/(app)\` are not part of the URL.
247
247
 
248
248
  Beignet app scaffolded with \`@beignet/cli\`.
249
249
 
250
- Node.js 22.12 or newer is required. Bun 1.3.14 or newer is supported for
251
- Beignet commands.
250
+ Node.js 22.12 or newer is required. Bun is optional; npm, pnpm, Yarn, and Bun
251
+ can run Beignet commands.
252
252
 
253
253
  ## Getting started
254
254
 
@@ -288,9 +288,9 @@ use cases, workflows, ports, providers, and tests connect. \`check\` runs the
288
288
  whole validation loop in one pass: \`${cli} lint\` (dependency direction),
289
289
  \`${cli} doctor --strict\` (route, OpenAPI, and resource drift), and the
290
290
  \`lint\`, \`typecheck\`, and \`test\` package scripts. Use \`${format}\` to
291
- apply Biome formatting. Beignet lint uses the TypeScript import graph, follows
292
- feature-root helpers, and reports unverifiable computed module references in
293
- constrained layers.
291
+ apply Biome formatting. Beignet lint analyzes JavaScript and TypeScript import
292
+ graphs, follows feature-root helpers, and reports unverifiable computed module
293
+ references in constrained layers.
294
294
  ${testingNotes}
295
295
  ## Coding agents
296
296
 
@@ -15,7 +15,7 @@
15
15
  // lives outside the app schema, so `db generate` never diffs against it.
16
16
  //
17
17
  // The Better Auth tables were generated with
18
- // `bunx @better-auth/cli generate` against a scratch app configured with
18
+ // `bunx auth generate` against a scratch app configured with
19
19
  // drizzleAdapter(db, { provider: "mysql" }), then normalized to the starter's
20
20
  // conventions (tabs, no relations, no extra indexes, no column defaults).
21
21
  // MySQL cannot put UNIQUE constraints on bare text columns, so identity
@@ -60,6 +60,7 @@ export function closeDatabaseClient(): Promise<void> {
60
60
  mysqlTable,
61
61
  text,
62
62
  timestamp,
63
+ uniqueIndex,
63
64
  varchar,
64
65
  } from "drizzle-orm/mysql-core";
65
66
 
@@ -86,23 +87,33 @@ export const session = mysqlTable("session", {
86
87
  .references(() => user.id, { onDelete: "cascade" }),
87
88
  });
88
89
 
89
- export const account = mysqlTable("account", {
90
- id: varchar("id", { length: 36 }).primaryKey(),
91
- accountId: text("account_id").notNull(),
92
- providerId: text("provider_id").notNull(),
93
- userId: varchar("user_id", { length: 36 })
94
- .notNull()
95
- .references(() => user.id, { onDelete: "cascade" }),
96
- accessToken: text("access_token"),
97
- refreshToken: text("refresh_token"),
98
- idToken: text("id_token"),
99
- accessTokenExpiresAt: timestamp("access_token_expires_at", { fsp: 3 }),
100
- refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { fsp: 3 }),
101
- scope: text("scope"),
102
- password: text("password"),
103
- createdAt: timestamp("created_at", { fsp: 3 }).notNull(),
104
- updatedAt: timestamp("updated_at", { fsp: 3 }).notNull(),
105
- });
90
+ export const account = mysqlTable(
91
+ "account",
92
+ {
93
+ id: varchar("id", { length: 36 }).primaryKey(),
94
+ issuer: varchar("issuer", { length: 255 }).notNull(),
95
+ accountId: varchar("account_id", { length: 255 }).notNull(),
96
+ providerId: text("provider_id").notNull(),
97
+ userId: varchar("user_id", { length: 36 })
98
+ .notNull()
99
+ .references(() => user.id, { onDelete: "cascade" }),
100
+ accessToken: text("access_token"),
101
+ refreshToken: text("refresh_token"),
102
+ idToken: text("id_token"),
103
+ accessTokenExpiresAt: timestamp("access_token_expires_at", { fsp: 3 }),
104
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { fsp: 3 }),
105
+ scope: text("scope"),
106
+ password: text("password"),
107
+ createdAt: timestamp("created_at", { fsp: 3 }).notNull(),
108
+ updatedAt: timestamp("updated_at", { fsp: 3 }).notNull(),
109
+ },
110
+ (table) => [
111
+ uniqueIndex("account_issuer_accountId_uidx").on(
112
+ table.issuer,
113
+ table.accountId,
114
+ ),
115
+ ],
116
+ );
106
117
 
107
118
  export const verification = mysqlTable("verification", {
108
119
  id: varchar("id", { length: 36 }).primaryKey(),
@@ -476,7 +487,8 @@ const mysqlStarterMigration = {
476
487
  // above, with the provider idempotency setup statements appended.
477
488
  starterMigrationSql: `CREATE TABLE \`account\` (
478
489
  \`id\` varchar(36) NOT NULL,
479
- \`account_id\` text NOT NULL,
490
+ \`issuer\` varchar(255) NOT NULL,
491
+ \`account_id\` varchar(255) NOT NULL,
480
492
  \`provider_id\` text NOT NULL,
481
493
  \`user_id\` varchar(36) NOT NULL,
482
494
  \`access_token\` text,
@@ -488,7 +500,8 @@ const mysqlStarterMigration = {
488
500
  \`password\` text,
489
501
  \`created_at\` timestamp(3) NOT NULL,
490
502
  \`updated_at\` timestamp(3) NOT NULL,
491
- CONSTRAINT \`account_id\` PRIMARY KEY(\`id\`)
503
+ CONSTRAINT \`account_id\` PRIMARY KEY(\`id\`),
504
+ CONSTRAINT \`account_issuer_accountId_uidx\` UNIQUE(\`issuer\`,\`account_id\`)
492
505
  );
493
506
  --> statement-breakpoint
494
507
  CREATE TABLE \`session\` (
@@ -585,9 +598,16 @@ CREATE TABLE IF NOT EXISTS \`idempotency_records\` (
585
598
  "notNull": true,
586
599
  "autoincrement": false
587
600
  },
601
+ "issuer": {
602
+ "name": "issuer",
603
+ "type": "varchar(255)",
604
+ "primaryKey": false,
605
+ "notNull": true,
606
+ "autoincrement": false
607
+ },
588
608
  "account_id": {
589
609
  "name": "account_id",
590
- "type": "text",
610
+ "type": "varchar(255)",
591
611
  "primaryKey": false,
592
612
  "notNull": true,
593
613
  "autoincrement": false
@@ -670,7 +690,16 @@ CREATE TABLE IF NOT EXISTS \`idempotency_records\` (
670
690
  "autoincrement": false
671
691
  }
672
692
  },
673
- "indexes": {},
693
+ "indexes": {
694
+ "account_issuer_accountId_uidx": {
695
+ "name": "account_issuer_accountId_uidx",
696
+ "columns": [
697
+ "issuer",
698
+ "account_id"
699
+ ],
700
+ "isUnique": true
701
+ }
702
+ },
674
703
  "foreignKeys": {
675
704
  "account_user_id_user_id_fk": {
676
705
  "name": "account_user_id_user_id_fk",
@@ -15,7 +15,7 @@
15
15
  // lives outside the app schema, so `db generate` never diffs against it.
16
16
  //
17
17
  // The Better Auth tables were generated with
18
- // `bunx @better-auth/cli generate` against a scratch app configured with
18
+ // `bunx auth generate` against a scratch app configured with
19
19
  // drizzleAdapter(db, { provider: "pg" }), then normalized to the starter's
20
20
  // conventions (tabs, no relations, no extra indexes, no column defaults).
21
21
 
@@ -51,7 +51,7 @@ export function closeDatabaseClient(): Promise<void> {
51
51
  return closePromise;
52
52
  }
53
53
  `,
54
- dbSchema: `import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
54
+ dbSchema: `import { boolean, index, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
55
55
 
56
56
  export const user = pgTable("user", {
57
57
  id: text("id").primaryKey(),
@@ -76,23 +76,33 @@ export const session = pgTable("session", {
76
76
  .references(() => user.id, { onDelete: "cascade" }),
77
77
  });
78
78
 
79
- export const account = pgTable("account", {
80
- id: text("id").primaryKey(),
81
- accountId: text("account_id").notNull(),
82
- providerId: text("provider_id").notNull(),
83
- userId: text("user_id")
84
- .notNull()
85
- .references(() => user.id, { onDelete: "cascade" }),
86
- accessToken: text("access_token"),
87
- refreshToken: text("refresh_token"),
88
- idToken: text("id_token"),
89
- accessTokenExpiresAt: timestamp("access_token_expires_at"),
90
- refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
91
- scope: text("scope"),
92
- password: text("password"),
93
- createdAt: timestamp("created_at").notNull(),
94
- updatedAt: timestamp("updated_at").notNull(),
95
- });
79
+ export const account = pgTable(
80
+ "account",
81
+ {
82
+ id: text("id").primaryKey(),
83
+ issuer: text("issuer").notNull(),
84
+ accountId: text("account_id").notNull(),
85
+ providerId: text("provider_id").notNull(),
86
+ userId: text("user_id")
87
+ .notNull()
88
+ .references(() => user.id, { onDelete: "cascade" }),
89
+ accessToken: text("access_token"),
90
+ refreshToken: text("refresh_token"),
91
+ idToken: text("id_token"),
92
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
93
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
94
+ scope: text("scope"),
95
+ password: text("password"),
96
+ createdAt: timestamp("created_at").notNull(),
97
+ updatedAt: timestamp("updated_at").notNull(),
98
+ },
99
+ (table) => [
100
+ uniqueIndex("account_issuer_accountId_uidx").on(
101
+ table.issuer,
102
+ table.accountId,
103
+ ),
104
+ ],
105
+ );
96
106
 
97
107
  export const verification = pgTable("verification", {
98
108
  id: text("id").primaryKey(),
@@ -413,6 +423,7 @@ const postgresStarterMigration = {
413
423
  // above, with the provider idempotency setup statements appended.
414
424
  starterMigrationSql: `CREATE TABLE "account" (
415
425
  "id" text PRIMARY KEY NOT NULL,
426
+ "issuer" text NOT NULL,
416
427
  "account_id" text NOT NULL,
417
428
  "provider_id" text NOT NULL,
418
429
  "user_id" text NOT NULL,
@@ -470,6 +481,7 @@ CREATE TABLE "verification" (
470
481
  ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
471
482
  ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
472
483
  ALTER TABLE "todos" ADD CONSTRAINT "todos_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
484
+ CREATE UNIQUE INDEX "account_issuer_accountId_uidx" ON "account" USING btree ("issuer","account_id");--> statement-breakpoint
473
485
  CREATE INDEX "todos_user_idx" ON "todos" USING btree ("user_id","created_at");
474
486
  --> statement-breakpoint
475
487
  CREATE TABLE IF NOT EXISTS "idempotency_records" (
@@ -517,6 +529,12 @@ CREATE INDEX IF NOT EXISTS "idempotency_records_expires_idx" ON "idempotency_rec
517
529
  "primaryKey": true,
518
530
  "notNull": true
519
531
  },
532
+ "issuer": {
533
+ "name": "issuer",
534
+ "type": "text",
535
+ "primaryKey": false,
536
+ "notNull": true
537
+ },
520
538
  "account_id": {
521
539
  "name": "account_id",
522
540
  "type": "text",
@@ -590,7 +608,29 @@ CREATE INDEX IF NOT EXISTS "idempotency_records_expires_idx" ON "idempotency_rec
590
608
  "notNull": true
591
609
  }
592
610
  },
593
- "indexes": {},
611
+ "indexes": {
612
+ "account_issuer_accountId_uidx": {
613
+ "name": "account_issuer_accountId_uidx",
614
+ "columns": [
615
+ {
616
+ "expression": "issuer",
617
+ "isExpression": false,
618
+ "asc": true,
619
+ "nulls": "last"
620
+ },
621
+ {
622
+ "expression": "account_id",
623
+ "isExpression": false,
624
+ "asc": true,
625
+ "nulls": "last"
626
+ }
627
+ ],
628
+ "isUnique": true,
629
+ "concurrently": false,
630
+ "method": "btree",
631
+ "with": {}
632
+ }
633
+ },
594
634
  "foreignKeys": {
595
635
  "account_user_id_user_id_fk": {
596
636
  "name": "account_user_id_user_id_fk",
@@ -42,7 +42,7 @@ export function closeDatabaseClient(): Promise<void> {
42
42
  return closePromise;
43
43
  }
44
44
  `,
45
- dbSchema: `import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
45
+ dbSchema: `import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
46
46
 
47
47
  export const user = sqliteTable("user", {
48
48
  id: text("id").primaryKey(),
@@ -69,27 +69,37 @@ export const session = sqliteTable("session", {
69
69
  .references(() => user.id, { onDelete: "cascade" }),
70
70
  });
71
71
 
72
- export const account = sqliteTable("account", {
73
- id: text("id").primaryKey(),
74
- accountId: text("account_id").notNull(),
75
- providerId: text("provider_id").notNull(),
76
- userId: text("user_id")
77
- .notNull()
78
- .references(() => user.id, { onDelete: "cascade" }),
79
- accessToken: text("access_token"),
80
- refreshToken: text("refresh_token"),
81
- idToken: text("id_token"),
82
- accessTokenExpiresAt: integer("access_token_expires_at", {
83
- mode: "timestamp",
84
- }),
85
- refreshTokenExpiresAt: integer("refresh_token_expires_at", {
86
- mode: "timestamp",
87
- }),
88
- scope: text("scope"),
89
- password: text("password"),
90
- createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
91
- updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
92
- });
72
+ export const account = sqliteTable(
73
+ "account",
74
+ {
75
+ id: text("id").primaryKey(),
76
+ issuer: text("issuer").notNull(),
77
+ accountId: text("account_id").notNull(),
78
+ providerId: text("provider_id").notNull(),
79
+ userId: text("user_id")
80
+ .notNull()
81
+ .references(() => user.id, { onDelete: "cascade" }),
82
+ accessToken: text("access_token"),
83
+ refreshToken: text("refresh_token"),
84
+ idToken: text("id_token"),
85
+ accessTokenExpiresAt: integer("access_token_expires_at", {
86
+ mode: "timestamp",
87
+ }),
88
+ refreshTokenExpiresAt: integer("refresh_token_expires_at", {
89
+ mode: "timestamp",
90
+ }),
91
+ scope: text("scope"),
92
+ password: text("password"),
93
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
94
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
95
+ },
96
+ (table) => [
97
+ uniqueIndex("account_issuer_accountId_uidx").on(
98
+ table.issuer,
99
+ table.accountId,
100
+ ),
101
+ ],
102
+ );
93
103
 
94
104
  export const verification = sqliteTable("verification", {
95
105
  id: text("id").primaryKey(),
@@ -452,6 +462,7 @@ export async function createTestDatabase(): Promise<TestDatabase> {
452
462
  // above, with the provider idempotency setup statements appended.
453
463
  starterMigrationSql: `CREATE TABLE \`account\` (
454
464
  \`id\` text PRIMARY KEY NOT NULL,
465
+ \`issuer\` text NOT NULL,
455
466
  \`account_id\` text NOT NULL,
456
467
  \`provider_id\` text NOT NULL,
457
468
  \`user_id\` text NOT NULL,
@@ -467,6 +478,7 @@ export async function createTestDatabase(): Promise<TestDatabase> {
467
478
  FOREIGN KEY (\`user_id\`) REFERENCES \`user\`(\`id\`) ON UPDATE no action ON DELETE cascade
468
479
  );
469
480
  --> statement-breakpoint
481
+ CREATE UNIQUE INDEX \`account_issuer_accountId_uidx\` ON \`account\` (\`issuer\`,\`account_id\`);--> statement-breakpoint
470
482
  CREATE TABLE \`session\` (
471
483
  \`id\` text PRIMARY KEY NOT NULL,
472
484
  \`expires_at\` integer NOT NULL,
@@ -555,6 +567,13 @@ CREATE INDEX IF NOT EXISTS "idempotency_records_expires_idx" ON "idempotency_rec
555
567
  "notNull": true,
556
568
  "autoincrement": false
557
569
  },
570
+ "issuer": {
571
+ "name": "issuer",
572
+ "type": "text",
573
+ "primaryKey": false,
574
+ "notNull": true,
575
+ "autoincrement": false
576
+ },
558
577
  "account_id": {
559
578
  "name": "account_id",
560
579
  "type": "text",
@@ -640,7 +659,16 @@ CREATE INDEX IF NOT EXISTS "idempotency_records_expires_idx" ON "idempotency_rec
640
659
  "autoincrement": false
641
660
  }
642
661
  },
643
- "indexes": {},
662
+ "indexes": {
663
+ "account_issuer_accountId_uidx": {
664
+ "name": "account_issuer_accountId_uidx",
665
+ "columns": [
666
+ "issuer",
667
+ "account_id"
668
+ ],
669
+ "isUnique": true
670
+ }
671
+ },
644
672
  "foreignKeys": {
645
673
  "account_user_id_user_id_fk": {
646
674
  "name": "account_user_id_user_id_fk",
@@ -561,7 +561,7 @@ export const shadcnDependencies: Record<string, string> = {
561
561
  clsx: "^2.1.1",
562
562
  "lucide-react": "^1.16.0",
563
563
  "radix-ui": "^1.4.3",
564
- shadcn: "^4.7.0",
564
+ shadcn: "^4.19.0",
565
565
  "tailwind-merge": "^3.6.0",
566
566
  "tw-animate-css": "^1.4.0",
567
567
  };
@@ -32,7 +32,7 @@ export type TemplateContext = {
32
32
 
33
33
  export const externalVersions = {
34
34
  biome: "^2.4.14",
35
- next: "^16.2.10",
35
+ next: "^16.3.3",
36
36
  react: "^19.2.5",
37
37
  reactDom: "^19.2.5",
38
38
  tsx: "^4.22.4",
@@ -41,16 +41,16 @@ export const externalVersions = {
41
41
  typesReact: "^19.0.0",
42
42
  typesReactDom: "^19.0.0",
43
43
  zod: "^4.0.0",
44
- tanstackReactQuery: "^5.100.7",
44
+ tanstackReactQuery: "^5.102.0",
45
45
  hookformResolvers: "^5.0.0",
46
46
  reactHookForm: "^7.74.0",
47
- betterAuth: "1.6.25",
47
+ betterAuth: "1.7.1",
48
48
  bullmq: "^5.0.0",
49
49
  drizzleKit: "^0.31.10",
50
50
  drizzleOrm: "^0.45.2",
51
- inngest: "^4.12.1",
51
+ inngest: "^4.18.1",
52
52
  libsqlClient: "^0.14.0",
53
- vercelBlob: "^2.5.0",
53
+ vercelBlob: "^2.8.0",
54
54
  mysql2: "^3.12.0",
55
55
  pg: "^8.13.0",
56
56
  pglite: "^0.5.2",
@@ -58,7 +58,7 @@ export const externalVersions = {
58
58
  resend: "^6.14.0",
59
59
  sentryNode: "^10.58.0",
60
60
  stripe: "^22.2.1",
61
- nodemailer: "^9.0.1",
61
+ nodemailer: "^9.1.1",
62
62
  openFeatureServerSdk: "^1.22.0",
63
63
  typesPg: "^8.11.0",
64
64
  ioredis: "^5.0.0",
@@ -1,3 +1,9 @@
1
+ import {
2
+ appTestFilePattern,
3
+ configuredTestDirectoryPathKeys,
4
+ configuredTestFilePathKeys,
5
+ defaultAppTestRoots,
6
+ } from "../test-discovery.js";
1
7
  import type { TemplateFile } from "./shared.js";
2
8
 
3
9
  export const testHelperTemplateFile: TemplateFile = {
@@ -408,16 +414,9 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
408
414
  import path from "node:path";
409
415
  import { pathToFileURL } from "node:url";
410
416
 
411
- const defaultTestRoots = [
412
- "app",
413
- "features",
414
- "infra",
415
- "lib",
416
- "ports",
417
- "server",
418
- "tests",
419
- "src/tests",
420
- ];
417
+ const defaultTestRoots = ${JSON.stringify(defaultAppTestRoots, null, 2)};
418
+ const configuredTestDirectoryPathKeys = ${JSON.stringify(configuredTestDirectoryPathKeys)};
419
+ const configuredTestFilePathKeys = ${JSON.stringify(configuredTestFilePathKeys)};
421
420
  const ignoredDirectories = new Set([
422
421
  ".beignet-local",
423
422
  ".git",
@@ -427,7 +426,7 @@ const ignoredDirectories = new Set([
427
426
  "dist",
428
427
  "node_modules",
429
428
  ]);
430
- const testFilePattern = /\\.(test|spec)\\.[cm]?[tj]sx?$/;
429
+ const testFilePattern = /${appTestFilePattern.source}/;
431
430
 
432
431
  const configFileNames = [
433
432
  "beignet.config.ts",
@@ -532,14 +531,8 @@ async function configuredRoots(cwd: string): Promise<string[]> {
532
531
  }
533
532
  const paths = isObjectRecord(config.paths) ? config.paths : {};
534
533
  return [
535
- paths.tests,
536
- paths.features,
537
- parentPath(paths.routes),
538
- parentPath(paths.server),
539
- parentPath(paths.portWiring),
540
- parentPath(paths.ports),
541
- parentPath(paths.useCaseBuilder),
542
- parentPath(paths.routesBuilder),
534
+ ...configuredTestDirectoryPathKeys.map((key) => paths[key]),
535
+ ...configuredTestFilePathKeys.map((key) => parentPath(paths[key])),
543
536
  ].filter((root): root is string => Boolean(root) && root !== ".");
544
537
  }
545
538
 
@@ -0,0 +1,53 @@
1
+ import path from "node:path";
2
+ import { type BeignetPaths, directoryPath } from "./config.js";
3
+
4
+ export const defaultAppTestRoots = [
5
+ "app",
6
+ "features",
7
+ "infra",
8
+ "lib",
9
+ "ports",
10
+ "server",
11
+ "tests",
12
+ "src/tests",
13
+ ] as const;
14
+
15
+ export const configuredTestDirectoryPathKeys = [
16
+ "tests",
17
+ "features",
18
+ ] as const satisfies readonly (keyof BeignetPaths)[];
19
+
20
+ export const configuredTestFilePathKeys = [
21
+ "routes",
22
+ "server",
23
+ "portWiring",
24
+ "ports",
25
+ "useCaseBuilder",
26
+ "routesBuilder",
27
+ ] as const satisfies readonly (keyof BeignetPaths)[];
28
+
29
+ export const appTestFilePattern = /\.(test|spec)\.[cm]?[jt]sx?$/;
30
+
31
+ export function appTestRoots(paths: BeignetPaths): string[] {
32
+ const roots = [
33
+ ...defaultAppTestRoots,
34
+ ...configuredTestDirectoryPathKeys.map((key) => paths[key]),
35
+ ...configuredTestFilePathKeys.map((key) =>
36
+ directoryPath(path.posix.dirname(paths[key])),
37
+ ),
38
+ ];
39
+ return Array.from(
40
+ new Set(roots.map(directoryPath).filter((root) => root && root !== ".")),
41
+ );
42
+ }
43
+
44
+ export function isAppTestFile(file: string): boolean {
45
+ return appTestFilePattern.test(file);
46
+ }
47
+
48
+ export function isWithinAppTestRoots(
49
+ file: string,
50
+ roots: readonly string[],
51
+ ): boolean {
52
+ return roots.some((root) => file.startsWith(`${root}/`));
53
+ }