@beignet/cli 0.0.24 → 0.0.26

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/src/inspect.ts CHANGED
@@ -1,16 +1,14 @@
1
1
  import { readdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import {
5
- type ProviderPackageMetadata,
6
- parseProviderPackageMetadata,
7
- } from "@beignet/core/providers";
3
+ import { parseProviderPackageMetadata } from "@beignet/core/providers";
8
4
  import { createPainter } from "./ansi.js";
9
5
  import {
10
6
  type BeignetConfig,
7
+ type BeignetDatabaseTables,
11
8
  clientDirPath,
12
9
  clientFormsPath,
13
10
  clientIndexPath,
11
+ defaultBeignetConfig,
14
12
  directoryPath,
15
13
  loadBeignetConfig,
16
14
  normalizePath,
@@ -21,6 +19,21 @@ import {
21
19
  formatGithubAnnotation,
22
20
  type GithubAnnotationSeverity,
23
21
  } from "./github-annotations.js";
22
+ import {
23
+ detectedProviderVariants,
24
+ diagnosticPackageJsonFile,
25
+ formatProviderMetadataIssues,
26
+ installedPackageNames,
27
+ providerDoctorRulesForInstalledPackages,
28
+ providerOperationalConfigFiles,
29
+ providerRequiredEnvExists,
30
+ providerRequiredTableExists,
31
+ providerVariantRegistrationSeverity,
32
+ readProviderListEntries,
33
+ readProviderPackageMetadataSource,
34
+ registrationTokenHint,
35
+ serverProviderFiles,
36
+ } from "./provider-audit.js";
24
37
  import {
25
38
  type AppendResult,
26
39
  appendToArrayExpression,
@@ -387,6 +400,7 @@ const productionHardeningDiagnosticCodes = new Set([
387
400
  "BEIGNET_PAYMENTS_STRIPE_MEMORY_PROVIDER",
388
401
  "BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
389
402
  "BEIGNET_PROVIDER_ENV_MISSING",
403
+ "BEIGNET_PROVIDER_TABLE_MISSING",
390
404
  "BEIGNET_READINESS_ROUTE_MISSING",
391
405
  "BEIGNET_TENANT_HEADER_AUTHORITY",
392
406
  "BEIGNET_UPLOAD_AUTHORIZATION_MISSING",
@@ -1917,30 +1931,6 @@ async function inspectRouteErrorCatalogDrift(
1917
1931
  return diagnostics;
1918
1932
  }
1919
1933
 
1920
- type ProviderDoctorVariantRule = {
1921
- name: string;
1922
- displayName: string;
1923
- tokens: readonly string[];
1924
- requiredEnv: readonly string[];
1925
- registrationSeverity?: "warning" | "hint";
1926
- };
1927
-
1928
- type ProviderDoctorRule = {
1929
- packageName: string;
1930
- displayName: string;
1931
- tokens: readonly string[];
1932
- appPorts?: readonly { name: string; type: string }[];
1933
- requiredEnv?: readonly string[];
1934
- registrationSeverity?: "warning" | "hint";
1935
- variants?: readonly ProviderDoctorVariantRule[];
1936
- };
1937
-
1938
- type ProviderPackageMetadataSource = {
1939
- packageName: string;
1940
- file: string;
1941
- metadata: unknown;
1942
- };
1943
-
1944
1934
  const readinessRelevantProviderPackages = new Set([
1945
1935
  "@beignet/provider-auth-better-auth",
1946
1936
  "@beignet/provider-db-drizzle",
@@ -1975,6 +1965,10 @@ async function inspectProductionReadiness(
1975
1965
  packageJson,
1976
1966
  );
1977
1967
  const configFiles = operationalConfigFiles(files, config);
1968
+ const infrastructurePath = directoryPath(
1969
+ path.dirname(config.paths.infrastructurePorts),
1970
+ );
1971
+ const schemaDir = `${infrastructurePath}/db/schema`;
1978
1972
 
1979
1973
  for (const file of files) {
1980
1974
  if (!file.endsWith(".ts") || file.endsWith(".test.ts")) continue;
@@ -2120,7 +2114,12 @@ async function inspectProductionReadiness(
2120
2114
  )) {
2121
2115
  for (const envVar of variant.requiredEnv) {
2122
2116
  if (
2123
- await anyFileContains(targetDir, configFiles, envVar, sourceCache)
2117
+ await providerRequiredEnvExists(
2118
+ targetDir,
2119
+ configFiles,
2120
+ envVar,
2121
+ sourceCache,
2122
+ )
2124
2123
  ) {
2125
2124
  continue;
2126
2125
  }
@@ -2131,12 +2130,44 @@ async function inspectProductionReadiness(
2131
2130
  message: `${provider.packageName} is installed and ${variant.displayName} is registered, but ${envVar} is not mentioned in app env/config files. ${variant.displayName} requires ${envVar}; add the environment variable or remove the provider registration.`,
2132
2131
  });
2133
2132
  }
2133
+
2134
+ for (const tableName of configuredProviderRequiredTables(
2135
+ variant.requiredTables,
2136
+ config,
2137
+ )) {
2138
+ if (
2139
+ await providerRequiredTableExists(
2140
+ targetDir,
2141
+ files,
2142
+ config,
2143
+ infrastructurePath,
2144
+ schemaDir,
2145
+ tableName,
2146
+ sourceCache,
2147
+ )
2148
+ ) {
2149
+ continue;
2150
+ }
2151
+ diagnostics.push({
2152
+ severity: "warning",
2153
+ code: "BEIGNET_PROVIDER_TABLE_MISSING",
2154
+ file: schemaDir,
2155
+ message: `${provider.packageName} is installed and ${variant.displayName} is registered, but required table ${tableName} was not found in ${schemaDir}/, drizzle/, or app database setup files. Add it with beignet db schema generate, include it in your Drizzle schema/migrations, or remove the provider registration.`,
2156
+ });
2157
+ }
2134
2158
  }
2135
2159
  continue;
2136
2160
  }
2137
2161
 
2138
2162
  for (const envVar of provider.requiredEnv ?? []) {
2139
- if (await anyFileContains(targetDir, configFiles, envVar, sourceCache)) {
2163
+ if (
2164
+ await providerRequiredEnvExists(
2165
+ targetDir,
2166
+ configFiles,
2167
+ envVar,
2168
+ sourceCache,
2169
+ )
2170
+ ) {
2140
2171
  continue;
2141
2172
  }
2142
2173
  diagnostics.push({
@@ -2146,6 +2177,31 @@ async function inspectProductionReadiness(
2146
2177
  message: `${provider.packageName} is installed, but ${envVar} is not mentioned in app env/config files. Add the expected environment variable or remove the unused provider package.`,
2147
2178
  });
2148
2179
  }
2180
+
2181
+ for (const tableName of configuredProviderRequiredTables(
2182
+ provider.requiredTables ?? [],
2183
+ config,
2184
+ )) {
2185
+ if (
2186
+ await providerRequiredTableExists(
2187
+ targetDir,
2188
+ files,
2189
+ config,
2190
+ infrastructurePath,
2191
+ schemaDir,
2192
+ tableName,
2193
+ sourceCache,
2194
+ )
2195
+ ) {
2196
+ continue;
2197
+ }
2198
+ diagnostics.push({
2199
+ severity: "warning",
2200
+ code: "BEIGNET_PROVIDER_TABLE_MISSING",
2201
+ file: schemaDir,
2202
+ message: `${provider.packageName} is installed, but required table ${tableName} was not found in ${schemaDir}/, drizzle/, or app database setup files. Add it with beignet db schema generate, include it in your Drizzle schema/migrations, or remove the unused provider package.`,
2203
+ });
2204
+ }
2149
2205
  }
2150
2206
 
2151
2207
  if (installedPackages.has("@beignet/provider-auth-better-auth")) {
@@ -2685,13 +2741,7 @@ function operationalConfigFiles(
2685
2741
  files: string[],
2686
2742
  config: ResolvedBeignetConfig,
2687
2743
  ): string[] {
2688
- return files.filter((file) => {
2689
- if (file === ".env.example") return true;
2690
- if (file === "drizzle.config.ts") return true;
2691
- if (file === "lib/env.ts" || file === "lib/better-auth.ts") return true;
2692
- if (isInfraOrServerFile(file, config) && file.endsWith(".ts")) return true;
2693
- return false;
2694
- });
2744
+ return providerOperationalConfigFiles(files, config);
2695
2745
  }
2696
2746
 
2697
2747
  async function readPackageJson(
@@ -2712,83 +2762,6 @@ async function readPackageJson(
2712
2762
  );
2713
2763
  }
2714
2764
 
2715
- async function providerDoctorRulesForInstalledPackages(
2716
- targetDir: string,
2717
- packageJson:
2718
- | {
2719
- dependencies?: Record<string, string>;
2720
- devDependencies?: Record<string, string>;
2721
- peerDependencies?: Record<string, string>;
2722
- }
2723
- | undefined,
2724
- ): Promise<ProviderDoctorRule[]> {
2725
- const packageNames = [...installedPackageNames(packageJson)];
2726
- const rules = await Promise.all(
2727
- packageNames.map(async (packageName) => {
2728
- const source = await readProviderPackageMetadataSource(
2729
- targetDir,
2730
- packageName,
2731
- );
2732
- if (!source) return undefined;
2733
- const result = parseProviderPackageMetadata(source.metadata);
2734
- if (!result.success) return undefined;
2735
- return providerDoctorRuleFromMetadata(packageName, result.metadata);
2736
- }),
2737
- );
2738
-
2739
- return rules.filter((rule): rule is ProviderDoctorRule => Boolean(rule));
2740
- }
2741
-
2742
- async function readProviderPackageMetadataSource(
2743
- targetDir: string,
2744
- packageName: string,
2745
- ): Promise<ProviderPackageMetadataSource | undefined> {
2746
- for (const candidate of providerPackageJsonCandidates(
2747
- targetDir,
2748
- packageName,
2749
- )) {
2750
- const packageJson = await readJsonIfExists<{
2751
- beignet?: { provider?: unknown };
2752
- }>(candidate);
2753
- const metadata = packageJson?.beignet?.provider;
2754
- if (metadata !== undefined) {
2755
- return { packageName, file: candidate, metadata };
2756
- }
2757
- }
2758
-
2759
- return undefined;
2760
- }
2761
-
2762
- function providerPackageJsonCandidates(
2763
- targetDir: string,
2764
- packageName: string,
2765
- ): string[] {
2766
- const candidates = [
2767
- path.join(
2768
- targetDir,
2769
- "node_modules",
2770
- ...packageName.split("/"),
2771
- "package.json",
2772
- ),
2773
- ];
2774
-
2775
- if (packageName.startsWith("@beignet/")) {
2776
- candidates.push(
2777
- path.resolve(
2778
- // import.meta.dir is Bun-only; the published bin also runs under
2779
- // plain Node, where only import.meta.url is available.
2780
- path.dirname(fileURLToPath(import.meta.url)),
2781
- "../../..",
2782
- "packages",
2783
- packageName.slice("@beignet/".length),
2784
- "package.json",
2785
- ),
2786
- );
2787
- }
2788
-
2789
- return candidates;
2790
- }
2791
-
2792
2765
  async function readJsonIfExists<T>(file: string): Promise<T | undefined> {
2793
2766
  try {
2794
2767
  return JSON.parse(await readFile(file, "utf8")) as T;
@@ -2804,218 +2777,6 @@ async function readJsonIfExists<T>(file: string): Promise<T | undefined> {
2804
2777
  }
2805
2778
  }
2806
2779
 
2807
- function providerDoctorRuleFromMetadata(
2808
- packageName: string,
2809
- metadata: ProviderPackageMetadata,
2810
- ): ProviderDoctorRule {
2811
- const registration = metadata.registration;
2812
-
2813
- return {
2814
- packageName,
2815
- displayName: metadata.displayName ?? packageName,
2816
- tokens: registration?.tokens ?? [],
2817
- appPorts: metadata.appPorts ?? [],
2818
- requiredEnv: metadata.requiredEnv ?? [],
2819
- registrationSeverity: registrationSeverityFromMetadata(registration),
2820
- variants: metadata.variants?.map((variant) => ({
2821
- name: variant.name,
2822
- displayName: variant.displayName ?? variant.name,
2823
- tokens: variant.registration?.tokens ?? [],
2824
- requiredEnv: variant.requiredEnv ?? [],
2825
- registrationSeverity: registrationSeverityFromMetadata(
2826
- variant.registration,
2827
- ),
2828
- })),
2829
- };
2830
- }
2831
-
2832
- function registrationSeverityFromMetadata(
2833
- registration:
2834
- | { required?: boolean; severity?: "warning" | "hint" }
2835
- | undefined,
2836
- ): "warning" | "hint" | undefined {
2837
- return (
2838
- registration?.severity ??
2839
- (registration?.required === true ? "warning" : undefined)
2840
- );
2841
- }
2842
-
2843
- function detectedProviderVariants(
2844
- variants: readonly ProviderDoctorVariantRule[],
2845
- providerEntries: readonly string[],
2846
- ): ProviderDoctorVariantRule[] {
2847
- return variants.filter((variant) =>
2848
- variant.tokens.some((token) =>
2849
- providerEntries.some((entry) => entry.includes(token)),
2850
- ),
2851
- );
2852
- }
2853
-
2854
- function providerVariantRegistrationSeverity(
2855
- variants: readonly ProviderDoctorVariantRule[],
2856
- ): "warning" | "hint" | undefined {
2857
- const severities = variants.map((variant) => variant.registrationSeverity);
2858
- if (severities.includes("warning")) return "warning";
2859
- if (severities.includes("hint")) return "hint";
2860
- return undefined;
2861
- }
2862
-
2863
- async function readProviderListEntries(
2864
- targetDir: string,
2865
- files: string[],
2866
- config: ResolvedBeignetConfig,
2867
- sourceCache: Map<string, string>,
2868
- ): Promise<string[]> {
2869
- const providerFiles = serverProviderFiles(files, config);
2870
- const providerSource = (
2871
- await Promise.all(
2872
- providerFiles.map((file) =>
2873
- readCachedSource(targetDir, file, sourceCache),
2874
- ),
2875
- )
2876
- ).join("\n");
2877
- const providerListSource = extractProviderListSource(providerSource);
2878
- return providerListSource === undefined
2879
- ? []
2880
- : providerListEntries(providerListSource);
2881
- }
2882
-
2883
- function registrationTokenHint(tokens: readonly string[]): string {
2884
- const token = tokens[0];
2885
- if (!token) return "the provider";
2886
- return token.startsWith("create") ? `${token}()` : token;
2887
- }
2888
-
2889
- function diagnosticPackageJsonFile(targetDir: string, file: string): string {
2890
- const relative = normalizePath(path.relative(targetDir, file));
2891
- if (relative.startsWith("../")) return "package.json";
2892
- return relative;
2893
- }
2894
-
2895
- function formatProviderMetadataIssues(
2896
- issues: readonly { path: string; message: string }[],
2897
- ): string {
2898
- return issues.map((issue) => `${issue.path} ${issue.message}`).join("; ");
2899
- }
2900
-
2901
- function installedPackageNames(
2902
- packageJson:
2903
- | {
2904
- dependencies?: Record<string, string>;
2905
- devDependencies?: Record<string, string>;
2906
- peerDependencies?: Record<string, string>;
2907
- }
2908
- | undefined,
2909
- ): Set<string> {
2910
- return new Set([
2911
- ...Object.keys(packageJson?.dependencies ?? {}),
2912
- ...Object.keys(packageJson?.devDependencies ?? {}),
2913
- ...Object.keys(packageJson?.peerDependencies ?? {}),
2914
- ]);
2915
- }
2916
-
2917
- function serverProviderFiles(
2918
- files: string[],
2919
- config: ResolvedBeignetConfig,
2920
- ): string[] {
2921
- const serverDir = directoryPath(path.dirname(config.paths.server));
2922
- const candidates = [`${serverDir}/providers.ts`, config.paths.server];
2923
- return candidates.filter((file) => files.includes(file));
2924
- }
2925
-
2926
- function extractProviderListSource(source: string): string | undefined {
2927
- const listMatches = [
2928
- ...source.matchAll(/\bproviders\s*=\s*\[([\s\S]*?)\]\s*(?:;|as const)/g),
2929
- ...source.matchAll(/\bproviders\s*:\s*\[([\s\S]*?)\]/g),
2930
- ];
2931
- if (listMatches.length === 0) return undefined;
2932
- return listMatches.map((match) => match[1]).join("\n");
2933
- }
2934
-
2935
- function providerListEntries(source: string): string[] {
2936
- const entries: string[] = [];
2937
- let current = "";
2938
- let depth = 0;
2939
- let quote: '"' | "'" | "`" | undefined;
2940
- let escaped = false;
2941
- let lineComment = false;
2942
- let blockComment = false;
2943
-
2944
- for (let index = 0; index < source.length; index += 1) {
2945
- const char = source[index];
2946
- const next = source[index + 1];
2947
-
2948
- if (lineComment) {
2949
- if (char === "\n") {
2950
- lineComment = false;
2951
- current += char;
2952
- }
2953
- continue;
2954
- }
2955
-
2956
- if (blockComment) {
2957
- if (char === "*" && next === "/") {
2958
- blockComment = false;
2959
- index += 1;
2960
- }
2961
- continue;
2962
- }
2963
-
2964
- if (quote) {
2965
- current += char;
2966
- if (escaped) {
2967
- escaped = false;
2968
- continue;
2969
- }
2970
- if (char === "\\") {
2971
- escaped = true;
2972
- continue;
2973
- }
2974
- if (char === quote) {
2975
- quote = undefined;
2976
- }
2977
- continue;
2978
- }
2979
-
2980
- if (char === "/" && next === "/") {
2981
- lineComment = true;
2982
- index += 1;
2983
- continue;
2984
- }
2985
-
2986
- if (char === "/" && next === "*") {
2987
- blockComment = true;
2988
- index += 1;
2989
- continue;
2990
- }
2991
-
2992
- if (char === '"' || char === "'" || char === "`") {
2993
- quote = char;
2994
- current += char;
2995
- continue;
2996
- }
2997
-
2998
- if (char === "(" || char === "[" || char === "{") {
2999
- depth += 1;
3000
- } else if (char === ")" || char === "]" || char === "}") {
3001
- depth = Math.max(0, depth - 1);
3002
- }
3003
-
3004
- if (char === "," && depth === 0) {
3005
- const entry = current.trim();
3006
- if (entry) entries.push(entry);
3007
- current = "";
3008
- continue;
3009
- }
3010
-
3011
- current += char;
3012
- }
3013
-
3014
- const entry = current.trim();
3015
- if (entry) entries.push(entry);
3016
- return entries;
3017
- }
3018
-
3019
2780
  function findProviderEntryIndex(
3020
2781
  entries: string[],
3021
2782
  tokens: readonly string[],
@@ -4433,34 +4194,66 @@ async function inspectDatabaseLifecycleDrift(
4433
4194
 
4434
4195
  type DurableDrizzleTableRequirement = {
4435
4196
  capability: string;
4436
- defaultTableName: string;
4197
+ table: keyof BeignetDatabaseTables;
4437
4198
  code: string;
4438
- factoryPattern: string;
4439
- setupPattern?: string;
4199
+ factoryNames: readonly string[];
4200
+ setupNames: readonly string[];
4201
+ explicitFactoryPattern: string;
4202
+ explicitSetupPattern: string;
4440
4203
  };
4441
4204
 
4442
4205
  const durableDrizzleTableRequirements: readonly DurableDrizzleTableRequirement[] =
4443
4206
  [
4444
4207
  {
4445
4208
  capability: "idempotency",
4446
- defaultTableName: "idempotency_records",
4209
+ table: "idempotency",
4447
4210
  code: "BEIGNET_IDEMPOTENCY_TABLE_MISSING",
4448
- factoryPattern: "createDrizzle[A-Za-z]+IdempotencyPort",
4449
- setupPattern: "createDrizzle[A-Za-z]+IdempotencySetupStatements",
4211
+ factoryNames: [
4212
+ "createDrizzleSqliteIdempotencyPort",
4213
+ "createDrizzlePostgresIdempotencyPort",
4214
+ "createDrizzleMysqlIdempotencyPort",
4215
+ ],
4216
+ setupNames: [
4217
+ "createDrizzleSqliteIdempotencySetupStatements",
4218
+ "createDrizzlePostgresIdempotencySetupStatements",
4219
+ "createDrizzleMysqlIdempotencySetupStatements",
4220
+ ],
4221
+ explicitFactoryPattern: "createDrizzle[A-Za-z]*Idempotency(?:Port)?",
4222
+ explicitSetupPattern: "createDrizzle[A-Za-z]*IdempotencySetupStatements",
4450
4223
  },
4451
4224
  {
4452
4225
  capability: "outbox",
4453
- defaultTableName: "outbox_messages",
4226
+ table: "outbox",
4454
4227
  code: "BEIGNET_OUTBOX_TABLE_MISSING",
4455
- factoryPattern: "createDrizzle[A-Za-z]+OutboxPort",
4456
- setupPattern: "createDrizzle[A-Za-z]+OutboxSetupStatements",
4228
+ factoryNames: [
4229
+ "createDrizzleSqliteOutboxPort",
4230
+ "createDrizzlePostgresOutboxPort",
4231
+ "createDrizzleMysqlOutboxPort",
4232
+ ],
4233
+ setupNames: [
4234
+ "createDrizzleSqliteOutboxSetupStatements",
4235
+ "createDrizzlePostgresOutboxSetupStatements",
4236
+ "createDrizzleMysqlOutboxSetupStatements",
4237
+ ],
4238
+ explicitFactoryPattern: "createDrizzle[A-Za-z]*Outbox(?:Port)?",
4239
+ explicitSetupPattern: "createDrizzle[A-Za-z]*OutboxSetupStatements",
4457
4240
  },
4458
4241
  {
4459
4242
  capability: "audit",
4460
- defaultTableName: "audit_log",
4243
+ table: "audit",
4461
4244
  code: "BEIGNET_AUDIT_TABLE_MISSING",
4462
- factoryPattern: "createDrizzle(?:[A-Za-z]+AuditLogPort|AuditLog)",
4463
- setupPattern: "createDrizzle[A-Za-z]+AuditLogSetupStatements",
4245
+ factoryNames: [
4246
+ "createDrizzleSqliteAuditLogPort",
4247
+ "createDrizzlePostgresAuditLogPort",
4248
+ "createDrizzleMysqlAuditLogPort",
4249
+ ],
4250
+ setupNames: [
4251
+ "createDrizzleSqliteAuditLogSetupStatements",
4252
+ "createDrizzlePostgresAuditLogSetupStatements",
4253
+ "createDrizzleMysqlAuditLogSetupStatements",
4254
+ ],
4255
+ explicitFactoryPattern: "createDrizzle[A-Za-z]*AuditLog(?:Port)?",
4256
+ explicitSetupPattern: "createDrizzle[A-Za-z]*AuditLogSetupStatements",
4464
4257
  },
4465
4258
  ];
4466
4259
 
@@ -4479,6 +4272,7 @@ async function inspectDurableDrizzleTableDrift(
4479
4272
  const tableNames = await configuredDurableDrizzleTableNames(
4480
4273
  targetDir,
4481
4274
  usageFiles,
4275
+ config,
4482
4276
  requirement,
4483
4277
  sourceCache,
4484
4278
  );
@@ -4489,6 +4283,7 @@ async function inspectDurableDrizzleTableDrift(
4489
4283
  await durableDrizzleTableExists(
4490
4284
  targetDir,
4491
4285
  files,
4286
+ config,
4492
4287
  infrastructurePath,
4493
4288
  schemaDir,
4494
4289
  requirement,
@@ -4526,17 +4321,29 @@ function durableDrizzleUsageFiles(
4526
4321
  async function configuredDurableDrizzleTableNames(
4527
4322
  targetDir: string,
4528
4323
  files: string[],
4324
+ config: ResolvedBeignetConfig,
4529
4325
  requirement: DurableDrizzleTableRequirement,
4530
4326
  sourceCache: Map<string, string>,
4531
4327
  ): Promise<Set<string>> {
4532
4328
  const tableNames = new Set<string>();
4329
+ const providerDefaultTableName =
4330
+ defaultBeignetConfig.database.tables[requirement.table];
4331
+ const configuredTableName = config.database.tables[requirement.table];
4533
4332
 
4534
4333
  for (const file of files) {
4535
4334
  const source = await readCachedSource(targetDir, file, sourceCache);
4536
- for (const tableName of tableNamesFromCalls(
4335
+ for (const tableName of tableNamesFromBeignetProviderCalls(
4537
4336
  source,
4538
- requirement.factoryPattern,
4539
- requirement.defaultTableName,
4337
+ requirement.factoryNames,
4338
+ providerDefaultTableName,
4339
+ configuredTableName,
4340
+ )) {
4341
+ tableNames.add(tableName);
4342
+ }
4343
+ for (const tableName of tableNamesFromExplicitCalls(
4344
+ source,
4345
+ requirement.explicitFactoryPattern,
4346
+ configuredTableName,
4540
4347
  )) {
4541
4348
  tableNames.add(tableName);
4542
4349
  }
@@ -4548,21 +4355,25 @@ async function configuredDurableDrizzleTableNames(
4548
4355
  async function durableDrizzleTableExists(
4549
4356
  targetDir: string,
4550
4357
  files: string[],
4358
+ config: ResolvedBeignetConfig,
4551
4359
  infrastructurePath: string,
4552
4360
  schemaDir: string,
4553
4361
  requirement: DurableDrizzleTableRequirement,
4554
4362
  tableName: string,
4555
4363
  sourceCache: Map<string, string>,
4556
4364
  ): 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;
4365
+ if (
4366
+ await providerRequiredTableExists(
4367
+ targetDir,
4368
+ files,
4369
+ config,
4370
+ infrastructurePath,
4371
+ schemaDir,
4372
+ tableName,
4373
+ sourceCache,
4374
+ )
4375
+ ) {
4376
+ return true;
4566
4377
  }
4567
4378
 
4568
4379
  const setupFiles = files.filter(
@@ -4572,16 +4383,22 @@ async function durableDrizzleTableExists(
4572
4383
  file.startsWith(`${infrastructurePath}/db/`)) &&
4573
4384
  /\.(?:ts|js|mts|mjs|cts|cjs|sql)$/.test(file),
4574
4385
  );
4386
+ const providerDefaultTableName =
4387
+ defaultBeignetConfig.database.tables[requirement.table];
4388
+ const configuredTableName = config.database.tables[requirement.table];
4575
4389
  for (const file of setupFiles) {
4576
4390
  const source = await readCachedSource(targetDir, file, sourceCache);
4577
- if (sourceCreatesTable(source, tableName)) return true;
4578
-
4579
- if (!requirement.setupPattern) continue;
4580
4391
  if (
4581
- tableNamesFromCalls(
4392
+ tableNamesFromBeignetProviderCalls(
4393
+ source,
4394
+ requirement.setupNames,
4395
+ providerDefaultTableName,
4396
+ configuredTableName,
4397
+ ).has(tableName) ||
4398
+ tableNamesFromExplicitCalls(
4582
4399
  source,
4583
- requirement.setupPattern,
4584
- requirement.defaultTableName,
4400
+ requirement.explicitSetupPattern,
4401
+ configuredTableName,
4585
4402
  ).has(tableName)
4586
4403
  ) {
4587
4404
  return true;
@@ -4591,17 +4408,30 @@ async function durableDrizzleTableExists(
4591
4408
  return false;
4592
4409
  }
4593
4410
 
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);
4411
+ function tableNamesFromBeignetProviderCalls(
4412
+ source: string,
4413
+ exportNames: readonly string[],
4414
+ defaultTableName: string,
4415
+ configuredTableName: string,
4416
+ ): Set<string> {
4417
+ const tableNames = new Set<string>();
4418
+ for (const callee of beignetProviderCallees(source, exportNames)) {
4419
+ for (const tableName of tableNamesFromCallee(
4420
+ source,
4421
+ callee,
4422
+ defaultTableName,
4423
+ configuredTableName,
4424
+ )) {
4425
+ tableNames.add(tableName);
4426
+ }
4427
+ }
4428
+ return tableNames;
4599
4429
  }
4600
4430
 
4601
- function tableNamesFromCalls(
4431
+ function tableNamesFromExplicitCalls(
4602
4432
  source: string,
4603
4433
  calleePattern: string,
4604
- defaultTableName: string,
4434
+ configuredTableName: string,
4605
4435
  ): Set<string> {
4606
4436
  const tableNames = new Set<string>();
4607
4437
  const callPattern = new RegExp(
@@ -4614,16 +4444,115 @@ function tableNamesFromCalls(
4614
4444
  const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
4615
4445
  if (/\bfunction\s+$/.test(prefix)) continue;
4616
4446
 
4617
- const argsSource = match[1] ?? "";
4618
- const tableName = /\btableName\s*:\s*["'`]([^"'`]+)["'`]/.exec(
4619
- argsSource,
4620
- )?.[1];
4621
- tableNames.add(tableName ?? defaultTableName);
4447
+ const tableName = tableNameFromArgs(match[1] ?? "", configuredTableName);
4448
+ if (tableName) tableNames.add(tableName);
4622
4449
  }
4623
4450
 
4624
4451
  return tableNames;
4625
4452
  }
4626
4453
 
4454
+ type BeignetProviderCallee =
4455
+ | { kind: "identifier"; name: string }
4456
+ | { kind: "namespace"; namespace: string; name: string };
4457
+
4458
+ function beignetProviderCallees(
4459
+ source: string,
4460
+ exportNames: readonly string[],
4461
+ ): BeignetProviderCallee[] {
4462
+ const exportNameSet = new Set(exportNames);
4463
+ const callees: BeignetProviderCallee[] = [];
4464
+
4465
+ for (const match of source.matchAll(
4466
+ /\bimport\s+(?:type\s+)?\{([^}]*)\}\s+from\s+["']@beignet\/provider-db-drizzle\/(?:sqlite|postgres|mysql)["']/g,
4467
+ )) {
4468
+ for (const specifier of match[1].split(",")) {
4469
+ const cleaned = specifier.trim().replace(/^type\s+/, "");
4470
+ if (!cleaned) continue;
4471
+ const [imported, local] = cleaned.split(/\s+as\s+/);
4472
+ if (!exportNameSet.has(imported)) continue;
4473
+ callees.push({ kind: "identifier", name: local?.trim() || imported });
4474
+ }
4475
+ }
4476
+
4477
+ for (const match of source.matchAll(
4478
+ /\bimport\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+["']@beignet\/provider-db-drizzle\/(?:sqlite|postgres|mysql)["']/g,
4479
+ )) {
4480
+ for (const exportName of exportNames) {
4481
+ callees.push({
4482
+ kind: "namespace",
4483
+ namespace: match[1],
4484
+ name: exportName,
4485
+ });
4486
+ }
4487
+ }
4488
+
4489
+ return callees;
4490
+ }
4491
+
4492
+ function tableNamesFromCallee(
4493
+ source: string,
4494
+ callee: BeignetProviderCallee,
4495
+ defaultTableName: string,
4496
+ configuredTableName: string,
4497
+ ): Set<string> {
4498
+ const tableNames = new Set<string>();
4499
+ const calleePattern =
4500
+ callee.kind === "identifier"
4501
+ ? `\\b${escapeRegExp(callee.name)}`
4502
+ : `\\b${escapeRegExp(callee.namespace)}\\s*\\.\\s*${escapeRegExp(
4503
+ callee.name,
4504
+ )}`;
4505
+ const callPattern = new RegExp(
4506
+ `${calleePattern}\\s*\\(([\\s\\S]{0,800}?)\\)`,
4507
+ "g",
4508
+ );
4509
+
4510
+ for (const match of source.matchAll(callPattern)) {
4511
+ const matchIndex = match.index ?? 0;
4512
+ const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
4513
+ if (/\bfunction\s+$/.test(prefix)) continue;
4514
+
4515
+ tableNames.add(
4516
+ tableNameFromArgs(match[1] ?? "", configuredTableName) ??
4517
+ defaultTableName,
4518
+ );
4519
+ }
4520
+
4521
+ return tableNames;
4522
+ }
4523
+
4524
+ function tableNameFromArgs(
4525
+ argsSource: string,
4526
+ configuredTableName: string,
4527
+ ): string | undefined {
4528
+ const literal = /\btableName\s*:\s*["'`]([^"'`]+)["'`]/.exec(argsSource)?.[1];
4529
+ if (literal) return literal;
4530
+ if (/\btableName\s*:/.test(argsSource)) return configuredTableName;
4531
+ return undefined;
4532
+ }
4533
+
4534
+ function configuredProviderRequiredTables(
4535
+ tableNames: readonly string[],
4536
+ config: ResolvedBeignetConfig,
4537
+ ): string[] {
4538
+ return tableNames.map((tableName) =>
4539
+ configuredProviderRequiredTable(tableName, config),
4540
+ );
4541
+ }
4542
+
4543
+ function configuredProviderRequiredTable(
4544
+ tableName: string,
4545
+ config: ResolvedBeignetConfig,
4546
+ ): string {
4547
+ for (const [key, defaultTableName] of Object.entries(
4548
+ defaultBeignetConfig.database.tables,
4549
+ ) as Array<[keyof BeignetDatabaseTables, string]>) {
4550
+ if (tableName === defaultTableName) return config.database.tables[key];
4551
+ }
4552
+
4553
+ return tableName;
4554
+ }
4555
+
4627
4556
  function hasRepositoryAdapterFile(
4628
4557
  files: string[],
4629
4558
  infrastructureDir: string,