@beignet/cli 0.0.51 → 0.0.52

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 (87) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +51 -18
  3. package/dist/analysis/layers.d.ts.map +1 -1
  4. package/dist/analysis/layers.js +4 -2
  5. package/dist/analysis/layers.js.map +1 -1
  6. package/dist/analysis/port-wiring.d.ts.map +1 -1
  7. package/dist/analysis/port-wiring.js +2 -1
  8. package/dist/analysis/port-wiring.js.map +1 -1
  9. package/dist/analysis/source-files.d.ts +15 -0
  10. package/dist/analysis/source-files.d.ts.map +1 -0
  11. package/dist/analysis/source-files.js +52 -0
  12. package/dist/analysis/source-files.js.map +1 -0
  13. package/dist/analysis/workspace.d.ts.map +1 -1
  14. package/dist/analysis/workspace.js +21 -25
  15. package/dist/analysis/workspace.js.map +1 -1
  16. package/dist/app-map-changes.d.ts.map +1 -1
  17. package/dist/app-map-changes.js +5 -7
  18. package/dist/app-map-changes.js.map +1 -1
  19. package/dist/app-map.d.ts.map +1 -1
  20. package/dist/app-map.js +33 -30
  21. package/dist/app-map.js.map +1 -1
  22. package/dist/check.d.ts +12 -0
  23. package/dist/check.d.ts.map +1 -1
  24. package/dist/check.js +92 -25
  25. package/dist/check.js.map +1 -1
  26. package/dist/git-changes.d.ts +17 -1
  27. package/dist/git-changes.d.ts.map +1 -1
  28. package/dist/git-changes.js +118 -84
  29. package/dist/git-changes.js.map +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/inspect.d.ts.map +1 -1
  34. package/dist/inspect.js +339 -112
  35. package/dist/inspect.js.map +1 -1
  36. package/dist/lint.d.ts.map +1 -1
  37. package/dist/lint.js +3 -6
  38. package/dist/lint.js.map +1 -1
  39. package/dist/mcp.js +1 -1
  40. package/dist/mcp.js.map +1 -1
  41. package/dist/templates/agents.d.ts.map +1 -1
  42. package/dist/templates/agents.js +36 -13
  43. package/dist/templates/agents.js.map +1 -1
  44. package/dist/templates/base.js +5 -5
  45. package/dist/templates/db/mysql.d.ts.map +1 -1
  46. package/dist/templates/db/mysql.js +51 -22
  47. package/dist/templates/db/mysql.js.map +1 -1
  48. package/dist/templates/db/postgres.d.ts.map +1 -1
  49. package/dist/templates/db/postgres.js +60 -20
  50. package/dist/templates/db/postgres.js.map +1 -1
  51. package/dist/templates/db/sqlite.d.ts.map +1 -1
  52. package/dist/templates/db/sqlite.js +51 -23
  53. package/dist/templates/db/sqlite.js.map +1 -1
  54. package/dist/templates/shadcn.js +1 -1
  55. package/dist/templates/shadcn.js.map +1 -1
  56. package/dist/templates/shared.js +4 -4
  57. package/dist/templates/shared.js.map +1 -1
  58. package/dist/templates/testing.d.ts.map +1 -1
  59. package/dist/templates/testing.js +7 -19
  60. package/dist/templates/testing.js.map +1 -1
  61. package/dist/test-discovery.d.ts +9 -0
  62. package/dist/test-discovery.d.ts.map +1 -0
  63. package/dist/test-discovery.js +40 -0
  64. package/dist/test-discovery.js.map +1 -0
  65. package/package.json +7 -5
  66. package/skills/app-structure/SKILL.md +32 -7
  67. package/src/analysis/layers.ts +4 -2
  68. package/src/analysis/port-wiring.ts +2 -1
  69. package/src/analysis/source-files.ts +61 -0
  70. package/src/analysis/workspace.ts +27 -31
  71. package/src/app-map-changes.ts +10 -8
  72. package/src/app-map.ts +43 -32
  73. package/src/check.ts +115 -25
  74. package/src/git-changes.ts +218 -86
  75. package/src/index.ts +8 -5
  76. package/src/inspect.ts +486 -141
  77. package/src/lint.ts +6 -5
  78. package/src/mcp.ts +1 -1
  79. package/src/templates/agents.ts +36 -13
  80. package/src/templates/base.ts +5 -5
  81. package/src/templates/db/mysql.ts +51 -22
  82. package/src/templates/db/postgres.ts +60 -20
  83. package/src/templates/db/sqlite.ts +51 -23
  84. package/src/templates/shadcn.ts +1 -1
  85. package/src/templates/shared.ts +4 -4
  86. package/src/templates/testing.ts +12 -19
  87. package/src/test-discovery.ts +53 -0
package/dist/inspect.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
2
3
  import path from "node:path";
3
4
  import { parseProviderPackageMetadata } from "@beignet/core/providers";
5
+ import semver from "semver";
4
6
  import ts from "typescript";
5
7
  import { contractFactoryBindings, isImportedContractFactoryCall, } from "./analysis/contract-factories.js";
6
8
  import { canonicalFeatureLayerPath } from "./analysis/layers.js";
7
9
  import { analyzePortWiringFiles } from "./analysis/port-wiring.js";
10
+ import { analysisSourceFileExtensions, isAnalysisSourceFile, isAnalysisSourceFileExtension, isJavaScriptSourceFile, javascriptAnalysisSourceFileExtensions, sourceFileScriptKind, stripSourceFileExtension, typescriptAnalysisSourceFileExtensions, } from "./analysis/source-files.js";
8
11
  import { exportedVariableDeclarations } from "./analysis/source-index.js";
9
12
  import { createPainter } from "./ansi.js";
10
13
  import { clientDirPath, clientFormsPath, clientIndexPath, defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
@@ -16,6 +19,7 @@ import { detectedProviderVariants, diagnosticPackageJsonFile, formatProviderEnvA
16
19
  import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, codeCharacterIndexes, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryEntryState, outboxRegistryIdentifiers, } from "./registry-edits.js";
17
20
  import { currentGeneratedPackageScripts, externalVersions, isLegacyGeneratedPackageScript, legacyGeneratedPackageScripts, } from "./templates/shared.js";
18
21
  import { testSupportTemplateFiles } from "./templates/testing.js";
22
+ import { isAppTestFile } from "./test-discovery.js";
19
23
  import { getCliVersion } from "./version.js";
20
24
  export async function inspectApp(options = {}) {
21
25
  const targetDir = path.resolve(options.cwd ?? process.cwd());
@@ -485,7 +489,7 @@ function hasRequirement(files, requirement) {
485
489
  }
486
490
  function parseContracts(source, file) {
487
491
  const contracts = [];
488
- const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
492
+ const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, sourceFileScriptKind(file));
489
493
  const assignments = variableAssignments(sourceFile);
490
494
  const assignmentsByName = new Map(assignments.map((assignment) => [assignment.name, assignment.expression]));
491
495
  const factoryBindings = contractFactoryBindings(sourceFile);
@@ -926,19 +930,19 @@ function normalizeContractPath(routePath) {
926
930
  const segments = routePath.split("/").filter(Boolean);
927
931
  return segments.length === 0 ? "/" : `/${segments.join("/")}`;
928
932
  }
929
- async function readRouteExports(targetDir, routeFiles, config) {
933
+ async function readRouteExports(targetDir, routeFiles, files, config) {
930
934
  const routeExports = new Map();
931
935
  for (const file of routeFiles) {
932
936
  const routePath = nextRoutePath(file, config.paths.routes);
933
937
  const source = await readFile(path.join(targetDir, file), "utf8");
934
- routeExports.set(file, parseRouteExports(source, file, routePath, config));
938
+ routeExports.set(file, parseRouteExports(source, file, routePath, files, config));
935
939
  }
936
940
  return routeExports;
937
941
  }
938
- function parseRouteExports(source, handlerFile, routePath, config) {
942
+ function parseRouteExports(source, handlerFile, routePath, files, config) {
939
943
  const exports = [];
940
- const sourceFile = ts.createSourceFile(handlerFile, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
941
- const imports = parseRouteNamedImports(sourceFile, config, handlerFile);
944
+ const sourceFile = ts.createSourceFile(handlerFile, source, ts.ScriptTarget.Latest, true, sourceFileScriptKind(handlerFile));
945
+ const imports = parseRouteNamedImports(sourceFile, config, handlerFile, files);
942
946
  for (const statement of sourceFile.statements) {
943
947
  if (!ts.isVariableStatement(statement) ||
944
948
  !statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
@@ -994,7 +998,7 @@ function parseRouteExports(source, handlerFile, routePath, config) {
994
998
  }
995
999
  return exports;
996
1000
  }
997
- function parseRouteNamedImports(sourceFile, config, handlerFile) {
1001
+ function parseRouteNamedImports(sourceFile, config, handlerFile, files) {
998
1002
  const imports = new Map();
999
1003
  for (const statement of sourceFile.statements) {
1000
1004
  if (!ts.isImportDeclaration(statement) ||
@@ -1004,7 +1008,7 @@ function parseRouteNamedImports(sourceFile, config, handlerFile) {
1004
1008
  !ts.isNamedImports(statement.importClause.namedBindings)) {
1005
1009
  continue;
1006
1010
  }
1007
- const contractFile = contractFileFromImport(statement.moduleSpecifier.text, config, handlerFile);
1011
+ const contractFile = contractFileFromImport(statement.moduleSpecifier.text, config, handlerFile, files);
1008
1012
  for (const element of statement.importClause.namedBindings.elements) {
1009
1013
  if (element.isTypeOnly)
1010
1014
  continue;
@@ -1065,12 +1069,12 @@ function catchAllRoutePrefix(routePath) {
1065
1069
  const prefixSegments = segments.slice(0, catchAllIndex);
1066
1070
  return prefixSegments.length === 0 ? "/" : `/${prefixSegments.join("/")}`;
1067
1071
  }
1068
- function parseNamedImports(source, config, importerFile) {
1072
+ function parseNamedImports(source, config, importerFile, files) {
1069
1073
  const imports = new Map();
1070
1074
  const importRegex = /import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
1071
1075
  for (const match of source.matchAll(importRegex)) {
1072
1076
  const sourcePath = match[2];
1073
- const contractFile = contractFileFromImport(sourcePath, config, importerFile);
1077
+ const contractFile = contractFileFromImport(sourcePath, config, importerFile, files);
1074
1078
  for (const member of match[1].split(",")) {
1075
1079
  const parsed = parseImportMember(member);
1076
1080
  if (!parsed)
@@ -1120,9 +1124,7 @@ function parseImportMember(member) {
1120
1124
  }
1121
1125
  function sourceFileFromImport(sourcePath, importerFile = "index.ts", files) {
1122
1126
  const resolveCandidate = (candidate) => {
1123
- if (path.extname(candidate))
1124
- return candidate;
1125
- const candidates = [`${candidate}.ts`, `${candidate}/index.ts`];
1127
+ const candidates = sourceFileSpecifierCandidates(candidate, importerFile);
1126
1128
  return files
1127
1129
  ? candidates.find((file) => files.includes(file))
1128
1130
  : candidates[0];
@@ -1135,29 +1137,58 @@ function sourceFileFromImport(sourcePath, importerFile = "index.ts", files) {
1135
1137
  }
1136
1138
  return undefined;
1137
1139
  }
1138
- function contractFileFromImport(sourcePath, config, importerFile) {
1140
+ function sourceFileCandidates(base, importerFile) {
1141
+ const extensions = importerFile && isJavaScriptSourceFile(importerFile)
1142
+ ? [
1143
+ ...javascriptAnalysisSourceFileExtensions,
1144
+ ...typescriptAnalysisSourceFileExtensions,
1145
+ ]
1146
+ : analysisSourceFileExtensions;
1147
+ return [
1148
+ ...extensions.map((extension) => `${base}${extension}`),
1149
+ ...extensions.map((extension) => `${base}/index${extension}`),
1150
+ ];
1151
+ }
1152
+ function sourceFileSpecifierCandidates(candidate, importerFile) {
1153
+ const extension = path.posix.extname(candidate).toLowerCase();
1154
+ if (!extension)
1155
+ return sourceFileCandidates(candidate, importerFile);
1156
+ if (!isAnalysisSourceFileExtension(extension))
1157
+ return [candidate];
1158
+ const sourceExtensions = extension === ".js"
1159
+ ? [".ts", ".tsx"]
1160
+ : extension === ".jsx"
1161
+ ? [".tsx"]
1162
+ : extension === ".mjs"
1163
+ ? [".mts"]
1164
+ : extension === ".cjs"
1165
+ ? [".cts"]
1166
+ : [];
1167
+ const sourceCandidates = sourceExtensions.map((sourceExtension) => `${candidate.slice(0, -extension.length)}${sourceExtension}`);
1168
+ const directCandidates = importerFile && isJavaScriptSourceFile(importerFile)
1169
+ ? [candidate, ...sourceCandidates]
1170
+ : [...sourceCandidates, candidate];
1171
+ return [
1172
+ ...new Set([
1173
+ ...directCandidates,
1174
+ ...directCandidates.map(directoryIndexSourceFile),
1175
+ ]),
1176
+ ];
1177
+ }
1178
+ function isSourceFileNamed(file, name) {
1179
+ return (isAnalysisSourceFile(file) &&
1180
+ path.posix.basename(stripSourceFileExtension(file)) === name);
1181
+ }
1182
+ function directoryIndexSourceFile(file) {
1183
+ const extension = path.posix.extname(file);
1184
+ return `${stripSourceFileExtension(file)}/index${extension}`;
1185
+ }
1186
+ function contractFileFromImport(sourcePath, config, importerFile, files) {
1139
1187
  const resolveCandidate = (candidate) => {
1140
- const extension = path.extname(candidate).toLowerCase();
1141
- if (!extension)
1142
- return `${candidate}.ts`;
1143
- let sourceExtension;
1144
- switch (extension) {
1145
- case ".js":
1146
- sourceExtension = ".ts";
1147
- break;
1148
- case ".jsx":
1149
- sourceExtension = ".tsx";
1150
- break;
1151
- case ".mjs":
1152
- sourceExtension = ".mts";
1153
- break;
1154
- case ".cjs":
1155
- sourceExtension = ".cts";
1156
- break;
1157
- }
1158
- return sourceExtension
1159
- ? `${candidate.slice(0, -extension.length)}${sourceExtension}`
1160
- : candidate;
1188
+ const candidates = sourceFileSpecifierCandidates(candidate, importerFile);
1189
+ if (!files)
1190
+ return candidates[0];
1191
+ return candidates.find((file) => files.includes(file)) ?? candidates[0];
1161
1192
  };
1162
1193
  const contractsPath = directoryPath(config.paths.contracts);
1163
1194
  const aliasPrefix = `@/${contractsPath}/`;
@@ -1165,7 +1196,7 @@ function contractFileFromImport(sourcePath, config, importerFile) {
1165
1196
  const relativePrefix = `${contractsPath}/`;
1166
1197
  const relativeExact = contractsPath;
1167
1198
  if (sourcePath === aliasExact || sourcePath === relativeExact) {
1168
- return `${contractsPath}/index.ts`;
1199
+ return resolveCandidate(`${contractsPath}/index`);
1169
1200
  }
1170
1201
  if (sourcePath.startsWith(aliasPrefix)) {
1171
1202
  return resolveCandidate(sourcePath.slice("@/".length));
@@ -1234,8 +1265,8 @@ function matchRoutes(contracts, routeExports) {
1234
1265
  async function inspectRouteSurface(targetDir, files, config, contracts) {
1235
1266
  if (config.framework === "next") {
1236
1267
  const routeFiles = files.filter((file) => file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
1237
- file.endsWith("/route.ts"));
1238
- const routeExports = await readRouteExports(targetDir, routeFiles, config);
1268
+ isNextRouteFile(file, config));
1269
+ const routeExports = await readRouteExports(targetDir, routeFiles, files, config);
1239
1270
  return matchRoutes(contracts, routeExports);
1240
1271
  }
1241
1272
  if (!files.includes(config.paths.server)) {
@@ -1275,8 +1306,7 @@ function findContracts(contracts, routeExport) {
1275
1306
  const contract = contracts.find((contract) => contract.exportName === routeExport.contractRef &&
1276
1307
  (!routeExport.contractFile ||
1277
1308
  contract.file === routeExport.contractFile ||
1278
- contract.file ===
1279
- routeExport.contractFile.replace(/\.ts$/, "/index.ts")));
1309
+ contract.file === directoryIndexSourceFile(routeExport.contractFile)));
1280
1310
  return contract ? [contract] : [];
1281
1311
  }
1282
1312
  if (routeExport.catchAllPrefix) {
@@ -1513,7 +1543,10 @@ async function inspectCanonicalConformance(targetDir, files, config, convention,
1513
1543
  }
1514
1544
  const featureRoutesPath = `${featuresPath}/`;
1515
1545
  for (const file of files) {
1516
- if (!file.startsWith(featureRoutesPath) || !file.endsWith("/routes.ts")) {
1546
+ if (!file.startsWith(featureRoutesPath) ||
1547
+ !isAnalysisSourceFile(file) ||
1548
+ isAppTestFile(file) ||
1549
+ !stripSourceFileExtension(file).endsWith("/routes")) {
1517
1550
  continue;
1518
1551
  }
1519
1552
  const source = await readCachedSource(targetDir, file, sourceCache);
@@ -1783,7 +1816,7 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
1783
1816
  const schemaDir = `${infrastructurePath}/db/schema`;
1784
1817
  let hasSecurityHeadersHook = false;
1785
1818
  for (const file of files) {
1786
- if (!file.endsWith(".ts") || file.endsWith(".test.ts"))
1819
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
1787
1820
  continue;
1788
1821
  const source = await readCachedSource(targetDir, file, sourceCache);
1789
1822
  const routeServerOrInfra = isRouteServerOrInfraFile(file, config);
@@ -1802,7 +1835,7 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
1802
1835
  });
1803
1836
  }
1804
1837
  if (fileWithinDirectory(file, cronDirectory) &&
1805
- file.endsWith("/route.ts") &&
1838
+ isNextRouteFile(file, config) &&
1806
1839
  !/\bCRON_SECRET\b/.test(source) &&
1807
1840
  !/\bcreateOutboxDrainRoute\s*\(/.test(source) &&
1808
1841
  !/\bcreateScheduleRoute\s*\(/.test(source)) {
@@ -1968,6 +2001,16 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
1968
2001
  message: "@beignet/provider-auth-better-auth is installed, but BETTER_AUTH_TRUSTED_ORIGINS is not mentioned in app env/config files. Add it before deploying across multiple origins or document why the app is single-origin only.",
1969
2002
  });
1970
2003
  }
2004
+ if ((await betterAuthRequiresIssuerSchema(targetDir, packageJson)) &&
2005
+ (await anyFileContains(targetDir, configFiles, "drizzleAdapter", sourceCache)) &&
2006
+ !(await hasBetterAuthIssuerSchema(targetDir, files, sourceCache))) {
2007
+ diagnostics.push({
2008
+ severity: "warning",
2009
+ code: "BEIGNET_BETTER_AUTH_ISSUER_SCHEMA_MISSING",
2010
+ file: schemaDir,
2011
+ message: "This app uses Better Auth 1.7 or a version range that can install it with the Drizzle adapter, but its account schema does not include the required issuer field and unique (issuer, accountId) index. Complete the provider-specific account identity backfill, run bunx auth generate, and apply the reviewed migration before deploying Better Auth 1.7.",
2012
+ });
2013
+ }
1971
2014
  }
1972
2015
  const installedReadinessProviders = [...installedPackages].filter((packageName) => readinessRelevantProviderPackages.has(packageName));
1973
2016
  if (installedReadinessProviders.length > 0 &&
@@ -1995,7 +2038,7 @@ async function uploadHasExplicitSizeLimit(targetDir, file, source, files, source
1995
2038
  const importedName = importedNameForLocalBinding(match[1] ?? "", fileReference);
1996
2039
  if (!importedName)
1997
2040
  continue;
1998
- const importedFile = resolveLocalTypeScriptModule(file, moduleName, files);
2041
+ const importedFile = sourceFileFromImport(moduleName, file, files);
1999
2042
  if (!importedFile)
2000
2043
  continue;
2001
2044
  const importedSource = await readCachedSource(targetDir, importedFile, sourceCache);
@@ -2029,17 +2072,14 @@ function exportedObjectHasProperty(source, exportName, propertyName) {
2029
2072
  const initializer = source.slice(openIndex, closeIndex + 1);
2030
2073
  return new RegExp(`\\b${escapeRegExp(propertyName)}\\s*:`).test(initializer);
2031
2074
  }
2032
- function resolveLocalTypeScriptModule(importer, moduleName, files) {
2033
- const base = normalizePath(path.posix.normalize(path.posix.join(path.posix.dirname(importer), moduleName)));
2034
- const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}/index.ts`];
2035
- return candidates.find((candidate) => files.includes(candidate));
2036
- }
2037
2075
  async function hasReadinessCheck(targetDir, files, config, sourceCache) {
2038
2076
  const routesDir = directoryPath(config.paths.routes);
2039
- if (files.includes(`${routesDir}/ready/route.ts`))
2077
+ if (files.some((file) => path.posix.dirname(file) === `${routesDir}/ready` &&
2078
+ isNextRouteFile(file, config))) {
2040
2079
  return true;
2080
+ }
2041
2081
  for (const file of files) {
2042
- if (!file.endsWith(".ts") || file.endsWith(".test.ts"))
2082
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
2043
2083
  continue;
2044
2084
  if (!isRouteServerOrInfraFile(file, config))
2045
2085
  continue;
@@ -2394,12 +2434,193 @@ async function inspectInngestProductionPath(targetDir, files, config) {
2394
2434
  return diagnostics;
2395
2435
  }
2396
2436
  function isFeatureUploadDefinition(file, config) {
2397
- const featuresPath = directoryPath(config.paths.features);
2398
- return new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/uploads/[^/]+\\.ts$`).test(file);
2437
+ return isFeatureArtifactFile(file, directoryPath(config.paths.features), "uploads");
2399
2438
  }
2400
2439
  function operationalConfigFiles(files, config) {
2401
2440
  return providerOperationalConfigFiles(files, config);
2402
2441
  }
2442
+ function declaredBetterAuthMayRequireIssuerSchema(packageJson) {
2443
+ const version = packageJson?.dependencies?.["better-auth"] ??
2444
+ packageJson?.devDependencies?.["better-auth"] ??
2445
+ packageJson?.peerDependencies?.["better-auth"];
2446
+ if (!version)
2447
+ return false;
2448
+ const normalized = version.trim();
2449
+ const workspaceRange = normalized.startsWith("workspace:")
2450
+ ? normalized.slice("workspace:".length)
2451
+ : normalized;
2452
+ const npmAlias = /^npm:better-auth@(.+)$/.exec(workspaceRange);
2453
+ const candidate = npmAlias?.[1] ?? workspaceRange;
2454
+ const range = semver.validRange(candidate);
2455
+ // Tags, catalogs, local paths, and other non-semver declarations can resolve
2456
+ // to 1.7. Prefer a migration warning until an installed version is readable.
2457
+ if (!range)
2458
+ return true;
2459
+ return semver.intersects(range, ">=1.7.0");
2460
+ }
2461
+ async function betterAuthRequiresIssuerSchema(targetDir, packageJson) {
2462
+ const installed = await readInstalledBetterAuthPackageJson(targetDir);
2463
+ if (installed?.version) {
2464
+ const version = semver.valid(installed.version.trim());
2465
+ if (version)
2466
+ return semver.gte(version, "1.7.0");
2467
+ }
2468
+ return declaredBetterAuthMayRequireIssuerSchema(packageJson);
2469
+ }
2470
+ async function readInstalledBetterAuthPackageJson(targetDir) {
2471
+ const nodeModulesPackage = await readBetterAuthPackageFromNodeModules(targetDir);
2472
+ if (nodeModulesPackage)
2473
+ return nodeModulesPackage;
2474
+ const requireFromTarget = createRequire(path.join(targetDir, "package.json"));
2475
+ const packageJsonPath = resolveModuleFromTarget(requireFromTarget, "better-auth/package.json");
2476
+ if (packageJsonPath) {
2477
+ return readJsonIfExists(packageJsonPath);
2478
+ }
2479
+ const entryPath = resolveModuleFromTarget(requireFromTarget, "better-auth");
2480
+ if (!entryPath)
2481
+ return undefined;
2482
+ let directory = path.dirname(entryPath);
2483
+ while (true) {
2484
+ const candidate = await readJsonIfExists(path.join(directory, "package.json"));
2485
+ if (candidate?.name === "better-auth")
2486
+ return candidate;
2487
+ const parent = path.dirname(directory);
2488
+ if (parent === directory)
2489
+ return undefined;
2490
+ directory = parent;
2491
+ }
2492
+ }
2493
+ async function readBetterAuthPackageFromNodeModules(targetDir) {
2494
+ let directory = targetDir;
2495
+ while (true) {
2496
+ const candidate = await readJsonIfExists(path.join(directory, "node_modules", "better-auth", "package.json"));
2497
+ if (candidate?.name === "better-auth")
2498
+ return candidate;
2499
+ const parent = path.dirname(directory);
2500
+ if (parent === directory)
2501
+ return undefined;
2502
+ directory = parent;
2503
+ }
2504
+ }
2505
+ function resolveModuleFromTarget(requireFromTarget, request) {
2506
+ try {
2507
+ return requireFromTarget.resolve(request);
2508
+ }
2509
+ catch {
2510
+ return undefined;
2511
+ }
2512
+ }
2513
+ async function hasBetterAuthIssuerSchema(targetDir, files, sourceCache) {
2514
+ const schemaFiles = files.filter((file) => !isTestSourceFile(file) &&
2515
+ /\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs)$/.test(file));
2516
+ for (const file of schemaFiles) {
2517
+ const source = await readCachedSource(targetDir, file, sourceCache);
2518
+ if (sourceHasBetterAuthIssuerSchema(file, source))
2519
+ return true;
2520
+ }
2521
+ return false;
2522
+ }
2523
+ function sourceHasBetterAuthIssuerSchema(file, source) {
2524
+ const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
2525
+ let found = false;
2526
+ const visit = (node) => {
2527
+ if (found)
2528
+ return;
2529
+ if (ts.isCallExpression(node) &&
2530
+ isBetterAuthAccountTableCall(node) &&
2531
+ betterAuthAccountTableHasIssuerIdentity(node)) {
2532
+ found = true;
2533
+ return;
2534
+ }
2535
+ ts.forEachChild(node, visit);
2536
+ };
2537
+ visit(sourceFile);
2538
+ return found;
2539
+ }
2540
+ function isBetterAuthAccountTableCall(call) {
2541
+ const callee = unwrapContractExpression(call.expression);
2542
+ const factoryName = ts.isIdentifier(callee)
2543
+ ? callee.text
2544
+ : ts.isPropertyAccessExpression(callee)
2545
+ ? callee.name.text
2546
+ : undefined;
2547
+ return (factoryName !== undefined &&
2548
+ ["mysqlTable", "pgTable", "sqliteTable"].includes(factoryName) &&
2549
+ staticString(call.arguments[0]) === "account");
2550
+ }
2551
+ function betterAuthAccountTableHasIssuerIdentity(call) {
2552
+ const columns = call.arguments[1];
2553
+ if (!columns || !ts.isObjectLiteralExpression(columns))
2554
+ return false;
2555
+ const issuer = columns.properties.find((property) => ts.isPropertyAssignment(property) &&
2556
+ staticPropertyName(property.name) === "issuer");
2557
+ if (!issuer || !ts.isPropertyAssignment(issuer))
2558
+ return false;
2559
+ if (!isBetterAuthIssuerColumn(issuer.initializer))
2560
+ return false;
2561
+ const constraints = call.arguments[2];
2562
+ if (!constraints ||
2563
+ (!ts.isArrowFunction(constraints) &&
2564
+ !ts.isFunctionExpression(constraints)) ||
2565
+ constraints.parameters.length === 0 ||
2566
+ !ts.isIdentifier(constraints.parameters[0]?.name)) {
2567
+ return false;
2568
+ }
2569
+ const tableName = constraints.parameters[0].name.text;
2570
+ let found = false;
2571
+ const visit = (node) => {
2572
+ if (found)
2573
+ return;
2574
+ if (ts.isCallExpression(node) &&
2575
+ isBetterAuthIssuerAccountUniqueConstraint(node, tableName)) {
2576
+ found = true;
2577
+ return;
2578
+ }
2579
+ ts.forEachChild(node, visit);
2580
+ };
2581
+ visit(constraints.body);
2582
+ return found;
2583
+ }
2584
+ function isBetterAuthIssuerColumn(expression) {
2585
+ let current = unwrapContractExpression(expression);
2586
+ while (ts.isCallExpression(current)) {
2587
+ const callee = unwrapContractExpression(current.expression);
2588
+ if (ts.isIdentifier(callee) &&
2589
+ ["text", "varchar"].includes(callee.text) &&
2590
+ staticString(current.arguments[0]) === "issuer") {
2591
+ return true;
2592
+ }
2593
+ if (!ts.isPropertyAccessExpression(callee))
2594
+ return false;
2595
+ current = unwrapContractExpression(callee.expression);
2596
+ }
2597
+ return false;
2598
+ }
2599
+ function isBetterAuthIssuerAccountUniqueConstraint(call, tableName) {
2600
+ const callee = unwrapContractExpression(call.expression);
2601
+ if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "on") {
2602
+ return false;
2603
+ }
2604
+ const factoryCall = unwrapContractExpression(callee.expression);
2605
+ if (!ts.isCallExpression(factoryCall))
2606
+ return false;
2607
+ const factory = unwrapContractExpression(factoryCall.expression);
2608
+ if (!ts.isIdentifier(factory) ||
2609
+ !["unique", "uniqueIndex"].includes(factory.text)) {
2610
+ return false;
2611
+ }
2612
+ if (call.arguments.length !== 2)
2613
+ return false;
2614
+ const columnNames = call.arguments.map((argument) => {
2615
+ const column = unwrapContractExpression(argument);
2616
+ return ts.isPropertyAccessExpression(column) &&
2617
+ ts.isIdentifier(column.expression) &&
2618
+ column.expression.text === tableName
2619
+ ? column.name.text
2620
+ : undefined;
2621
+ });
2622
+ return columnNames.includes("issuer") && columnNames.includes("accountId");
2623
+ }
2403
2624
  async function readPackageJson(targetDir, files) {
2404
2625
  if (!files.includes("package.json"))
2405
2626
  return undefined;
@@ -2484,7 +2705,7 @@ async function usesEntitlementChecks(targetDir, files, sourceCache) {
2484
2705
  async function hasBillingEntitlementsProviderWiring(targetDir, files, config, sourceCache) {
2485
2706
  const infraPath = path.dirname(config.paths.portWiring);
2486
2707
  const serverPath = path.dirname(config.paths.server);
2487
- const candidateFiles = files.filter((file) => /\.(?:ts|tsx|mts|cts)$/.test(file) &&
2708
+ const candidateFiles = files.filter((file) => isAnalysisSourceFile(file) &&
2488
2709
  !isTestSourceFile(file) &&
2489
2710
  (file.startsWith(`${infraPath}/`) || file.startsWith(`${serverPath}/`)));
2490
2711
  for (const file of candidateFiles) {
@@ -2496,8 +2717,7 @@ async function hasBillingEntitlementsProviderWiring(targetDir, files, config, so
2496
2717
  return false;
2497
2718
  }
2498
2719
  function isTestSourceFile(file) {
2499
- return (/(?:^|\/)tests\//.test(file) ||
2500
- /\.(?:test|spec)\.(?:ts|tsx|mts|cts)$/.test(file));
2720
+ return /(?:^|\/)tests\//.test(file) || isAppTestFile(file);
2501
2721
  }
2502
2722
  function exportedConstInitializer(source, exportName) {
2503
2723
  const declaration = new RegExp(`\\bexport\\s+const\\s+${escapeRegExp(exportName)}\\s*=`).exec(source);
@@ -2511,7 +2731,7 @@ function exportedConstInitializer(source, exportName) {
2511
2731
  }
2512
2732
  function featureNameFromContractFile(file, config) {
2513
2733
  const featuresPath = directoryPath(config.paths.features);
2514
- const match = new RegExp(`^${escapeRegExp(featuresPath)}/([^/]+)/contracts\\.ts$`).exec(file);
2734
+ const match = new RegExp(`^${escapeRegExp(featuresPath)}/([^/]+)/contracts$`).exec(stripSourceFileExtension(file));
2515
2735
  return match?.[1];
2516
2736
  }
2517
2737
  function featureRuntimeFiles(files, featuresPath, feature) {
@@ -2523,7 +2743,7 @@ function featureRuntimeFiles(files, featuresPath, feature) {
2523
2743
  "use-cases",
2524
2744
  ];
2525
2745
  return files.filter((file) => {
2526
- if (!file.endsWith(".ts") || file.endsWith(".test.ts"))
2746
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
2527
2747
  return false;
2528
2748
  return runtimeFolders.some((folder) => file.startsWith(`${featuresPath}/${feature}/${folder}/`));
2529
2749
  });
@@ -2531,10 +2751,11 @@ function featureRuntimeFiles(files, featuresPath, feature) {
2531
2751
  function featureTestFiles(files, featuresPath, feature, options = {}) {
2532
2752
  return files.filter((file) => {
2533
2753
  if (!file.startsWith(`${featuresPath}/${feature}/`) ||
2534
- !file.endsWith(".test.ts")) {
2754
+ !isAppTestFile(file)) {
2535
2755
  return false;
2536
2756
  }
2537
- return !options.policyOnly || file.endsWith("/policy.test.ts");
2757
+ return (!options.policyOnly ||
2758
+ ["policy.test", "policy.spec"].includes(path.posix.basename(stripSourceFileExtension(file))));
2538
2759
  });
2539
2760
  }
2540
2761
  function featureNamesFromContracts(contracts, config) {
@@ -2630,14 +2851,12 @@ async function inspectFeatureArtifactDrift(targetDir, files, config, convention)
2630
2851
  severity: "warning",
2631
2852
  code: "BEIGNET_FEATURE_TEST_PLACEMENT",
2632
2853
  file,
2633
- message: `${file} places a test directly under ${featuresPath}/${match[1]}/. Move feature behavior tests into ${featuresPath}/${match[1]}/tests/ so feature suites stay in the canonical folder; adjacent *.test.ts files are for infra and server modules.`,
2854
+ message: `${file} places a test directly under ${featuresPath}/${match[1]}/. Move feature behavior tests into ${featuresPath}/${match[1]}/tests/ so feature suites stay in the canonical folder; adjacent test files are for infra and server modules.`,
2634
2855
  });
2635
2856
  }
2636
2857
  for (const file of files) {
2637
- if (!/\.(?:ts|tsx|mts|cts)$/.test(file) ||
2638
- /\.d\.(?:ts|mts|cts)$/.test(file)) {
2858
+ if (!isAnalysisSourceFile(file))
2639
2859
  continue;
2640
- }
2641
2860
  const misplaced = directFeatureTestMatch(file, featuresPath)
2642
2861
  ? undefined
2643
2862
  : misplacedFeatureLayer(file, config);
@@ -2651,8 +2870,6 @@ async function inspectFeatureArtifactDrift(targetDir, files, config, convention)
2651
2870
  }
2652
2871
  if (isTestSourceFile(file))
2653
2872
  continue;
2654
- if (!file.endsWith(".ts"))
2655
- continue;
2656
2873
  if (isNotificationDefinitionFile(file, featuresPath) ||
2657
2874
  isInfraOrServerFile(file, config)) {
2658
2875
  continue;
@@ -2670,15 +2887,19 @@ async function inspectFeatureArtifactDrift(targetDir, files, config, convention)
2670
2887
  return diagnostics;
2671
2888
  }
2672
2889
  function directFeatureTestMatch(file, featuresPath) {
2673
- return new RegExp(`^${escapeRegExp(featuresPath)}/([^/]+)/[^/]+\\.(?:test|spec)\\.(?:ts|tsx|mts|cts)$`).exec(file);
2890
+ if (!isAppTestFile(file))
2891
+ return null;
2892
+ return new RegExp(`^${escapeRegExp(featuresPath)}/([^/]+)/[^/]+\\.(?:test|spec)$`).exec(stripSourceFileExtension(file));
2674
2893
  }
2675
2894
  function isFeatureArtifactFile(file, featuresPath, folder) {
2676
- const match = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/${folder}/([^/]+)\\.ts$`).exec(file);
2895
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
2896
+ return false;
2897
+ const match = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/${folder}/([^/]+)$`).exec(stripSourceFileExtension(file));
2677
2898
  return Boolean(match && match[1] !== "index");
2678
2899
  }
2679
2900
  async function appHasUploadRoute(targetDir, files, config) {
2680
2901
  for (const file of files) {
2681
- if (!file.endsWith(".ts") || file.endsWith(".test.ts"))
2902
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
2682
2903
  continue;
2683
2904
  if (!isRouteServerOrInfraFile(file, config))
2684
2905
  continue;
@@ -3007,10 +3228,13 @@ async function readFeatureWorkflowRegistries(targetDir, files, config) {
3007
3228
  ];
3008
3229
  for (const kind of kinds) {
3009
3230
  const folder = kind === "events" ? "domain/events" : kind;
3010
- const pattern = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/${escapeRegExp(folder)}/index\\.ts$`);
3231
+ const pattern = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/${escapeRegExp(folder)}/index$`);
3011
3232
  for (const file of files) {
3012
- if (!pattern.test(file))
3233
+ if (!isAnalysisSourceFile(file) ||
3234
+ isWorkflowTestFile(file) ||
3235
+ !pattern.test(stripSourceFileExtension(file))) {
3013
3236
  continue;
3237
+ }
3014
3238
  const source = await readFile(path.join(targetDir, file), "utf8");
3015
3239
  const memberFiles = exportedMemberFiles(source, file, files);
3016
3240
  const exportRegex = new RegExp(`(?:^|\\n)export const\\s+([A-Za-z_$][\\w$]*${workflowRegistrySuffixes[kind]})\\s*=`, "g");
@@ -3052,9 +3276,7 @@ function exportedMemberFiles(source, indexFile, files) {
3052
3276
  async function readRuntimeManifestIdentifiers(targetDir, files) {
3053
3277
  const manifests = [];
3054
3278
  for (const file of files) {
3055
- if (!file.endsWith(".ts") && !file.endsWith(".tsx"))
3056
- continue;
3057
- if (file.endsWith(".d.ts") || isWorkflowTestFile(file))
3279
+ if (!isAnalysisSourceFile(file) || isWorkflowTestFile(file))
3058
3280
  continue;
3059
3281
  const source = await readFile(path.join(targetDir, file), "utf8");
3060
3282
  if (!source.includes("defineRuntimeManifest"))
@@ -3163,11 +3385,14 @@ function registeredWorkflowIdentifiers(source, kind) {
3163
3385
  }
3164
3386
  async function listenedEventNames(targetDir, files, config) {
3165
3387
  const featuresPath = directoryPath(config.paths.features);
3166
- const listenerPattern = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/listeners/[^/]+\\.ts$`);
3388
+ const listenerPattern = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/listeners/[^/]+$`);
3167
3389
  const listened = new Set();
3168
3390
  for (const file of files) {
3169
- if (!listenerPattern.test(file) || isWorkflowTestFile(file))
3391
+ if (!isAnalysisSourceFile(file) ||
3392
+ isWorkflowTestFile(file) ||
3393
+ !listenerPattern.test(stripSourceFileExtension(file))) {
3170
3394
  continue;
3395
+ }
3171
3396
  const source = await readFile(path.join(targetDir, file), "utf8");
3172
3397
  const callPattern = /\bdefineListener\s*\(/g;
3173
3398
  for (const match of source.matchAll(callPattern)) {
@@ -3197,13 +3422,11 @@ async function listenerWiringReferences(targetDir, files, config, listenerRegist
3197
3422
  let centralListenerRegistryReferenced = false;
3198
3423
  const listenerRegistryFile = listenersFilePath(config);
3199
3424
  for (const file of files) {
3200
- if (!file.endsWith(".ts") && !file.endsWith(".tsx"))
3201
- continue;
3202
- if (file.endsWith(".d.ts") || isWorkflowTestFile(file))
3425
+ if (!isAnalysisSourceFile(file) || isWorkflowTestFile(file))
3203
3426
  continue;
3204
3427
  const source = await readFile(path.join(targetDir, file), "utf8");
3205
3428
  const namedImports = parseNamedImportSources(source);
3206
- const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
3429
+ const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, sourceFileScriptKind(file));
3207
3430
  const registerListenersBindings = importedRegisterListenersBindings(source, namedImports);
3208
3431
  const calls = registerListenersCalls(sourceFile, registerListenersBindings);
3209
3432
  let foundRelevantCall = false;
@@ -3523,21 +3746,15 @@ function callReferencesCentralListenerRegistry(args) {
3523
3746
  return false;
3524
3747
  }
3525
3748
  function isWorkflowTestFile(file) {
3526
- return (file.endsWith(".test.ts") ||
3527
- file.endsWith(".test.tsx") ||
3528
- file.startsWith("tests/") ||
3529
- file.includes("/tests/"));
3749
+ return (isAppTestFile(file) || file.startsWith("tests/") || file.includes("/tests/"));
3530
3750
  }
3531
3751
  async function inspectServerlessFootguns(targetDir, files, config, convention) {
3532
3752
  const diagnostics = [];
3533
3753
  const routeDirectory = directoryPath(config.paths.routes);
3534
3754
  const cronDirectory = path.posix.join(routeDirectory, "cron");
3535
3755
  for (const file of files) {
3536
- if (!file.endsWith(".ts") ||
3537
- file.endsWith(".test.ts") ||
3538
- file.endsWith(".d.ts")) {
3756
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
3539
3757
  continue;
3540
- }
3541
3758
  const source = await readFile(path.join(targetDir, file), "utf8");
3542
3759
  const isInfraOrServerFile = isRuntimeFile(file, config);
3543
3760
  const isProviderFile = isProviderLifecycleFile(file, source);
@@ -3642,16 +3859,18 @@ function fileWithinDirectory(file, directory) {
3642
3859
  return directory === "." || file.startsWith(`${directory}/`);
3643
3860
  }
3644
3861
  function isProviderLifecycleFile(file, source) {
3645
- return (/(^|\/)provider(s)?\.ts$/.test(file) ||
3646
- /(^|\/)[^/]*provider[^/]*\.ts$/.test(file) ||
3862
+ const sourcePath = stripSourceFileExtension(file);
3863
+ return (/(^|\/)provider(s)?$/.test(sourcePath) ||
3864
+ /(^|\/)[^/]*provider[^/]*$/.test(sourcePath) ||
3647
3865
  /(^|\/)providers?\//.test(file) ||
3648
3866
  (source !== undefined && containsCallExpression(source, "createProvider")));
3649
3867
  }
3650
3868
  function isNextRouteFile(file, config) {
3651
3869
  const routesPath = directoryPath(config.paths.routes);
3652
3870
  return (file.startsWith(`${routesPath}/`) &&
3653
- file.endsWith("/route.ts") &&
3654
- !file.endsWith(".test.ts"));
3871
+ isSourceFileNamed(file, "route") &&
3872
+ [".js", ".jsx", ".ts", ".tsx"].includes(path.posix.extname(file)) &&
3873
+ !isAppTestFile(file));
3655
3874
  }
3656
3875
  function importsEagerServer(source, importerFile, files, config) {
3657
3876
  const importRegex = /import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
@@ -4882,7 +5101,9 @@ async function inspectFeatureRouteRegistration(targetDir, files, config, convent
4882
5101
  const feature = featureNameFromContractFile(contract.file, config);
4883
5102
  if (!feature)
4884
5103
  continue;
4885
- const routeFile = `${featuresPath}/${feature}/routes.ts`;
5104
+ const routeStem = `${featuresPath}/${feature}/routes`;
5105
+ const routeFile = files.find((file) => isAnalysisSourceFile(file) &&
5106
+ stripSourceFileExtension(file) === routeStem) ?? `${routeStem}${path.posix.extname(contract.file) || ".ts"}`;
4886
5107
  const featureRouteGroups = routeGroups.filter((routeGroup) => routeGroup.file === routeFile);
4887
5108
  if (featureRouteGroups.length === 0) {
4888
5109
  diagnostics.push({
@@ -4948,11 +5169,11 @@ function contractsForRegisteredRouteGroups(contracts, routeGroups, registeredGro
4948
5169
  }
4949
5170
  async function readFeatureRouteGroups(targetDir, files, config) {
4950
5171
  const featuresPath = `${directoryPath(config.paths.features)}/`;
4951
- const routeFiles = files.filter((file) => file.startsWith(featuresPath) && file.endsWith("/routes.ts"));
5172
+ const routeFiles = files.filter((file) => file.startsWith(featuresPath) && isSourceFileNamed(file, "routes"));
4952
5173
  const routeGroups = [];
4953
5174
  for (const file of routeFiles) {
4954
5175
  const source = await readFile(path.join(targetDir, file), "utf8");
4955
- const imports = parseNamedImports(source, config, file);
5176
+ const imports = parseNamedImports(source, config, file, files);
4956
5177
  routeGroups.push(...parseFeatureRouteGroups(source, file, imports));
4957
5178
  }
4958
5179
  return routeGroups;
@@ -5063,8 +5284,7 @@ function findRouteGroupContract(contracts, routeGroupContract) {
5063
5284
  return contracts.find((contract) => contract.exportName === routeGroupContract.exportName &&
5064
5285
  (!routeGroupContract.file ||
5065
5286
  contract.file === routeGroupContract.file ||
5066
- contract.file ===
5067
- routeGroupContract.file.replace(/\.ts$/, "/index.ts")));
5287
+ contract.file === directoryIndexSourceFile(routeGroupContract.file)));
5068
5288
  }
5069
5289
  const managedDatabaseSchemaMarker = "// Managed by beignet db schema sync.";
5070
5290
  const databaseSchemaExports = {
@@ -5996,7 +6216,7 @@ async function readContractLists(targetDir, files, config) {
5996
6216
  const contractFiles = contractListSourceFiles(files, config);
5997
6217
  for (const file of contractFiles) {
5998
6218
  const source = await readFile(path.join(targetDir, file), "utf8");
5999
- const imports = parseNamedImports(source, config, file);
6219
+ const imports = parseNamedImports(source, config, file, files);
6000
6220
  for (const list of parseContractLists(source, file, imports)) {
6001
6221
  lists.set(contractListKey(list.file, list.name), list);
6002
6222
  }
@@ -6006,29 +6226,36 @@ async function readContractLists(targetDir, files, config) {
6006
6226
  function contractSourceFiles(files, config) {
6007
6227
  const contractsPath = directoryPath(config.paths.contracts);
6008
6228
  if (usesFeatureOwnedContracts(config)) {
6009
- return files.filter((file) => new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts\\.ts$`).test(file) && !file.endsWith(".test.ts"));
6229
+ return files.filter((file) => isAnalysisSourceFile(file) &&
6230
+ !isAppTestFile(file) &&
6231
+ new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts$`).test(stripSourceFileExtension(file)));
6010
6232
  }
6011
- return files.filter((file) => file.startsWith(`${contractsPath}/`) &&
6012
- file.endsWith(".ts") &&
6013
- !file.endsWith(".test.ts"));
6233
+ return files.filter((file) => {
6234
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
6235
+ return false;
6236
+ return stripSourceFileExtension(file).startsWith(`${contractsPath}/`);
6237
+ });
6014
6238
  }
6015
6239
  function contractListSourceFiles(files, config) {
6016
6240
  const contractsPath = directoryPath(config.paths.contracts);
6017
6241
  if (usesFeatureOwnedContracts(config)) {
6018
6242
  return files.filter((file) => {
6019
- if (!file.endsWith(".ts") || file.endsWith(".test.ts"))
6243
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
6020
6244
  return false;
6021
- if (file === `${contractsPath}/index.ts`)
6245
+ const sourcePath = stripSourceFileExtension(file);
6246
+ if (sourcePath === `${contractsPath}/index`)
6022
6247
  return true;
6023
- if (file === `${contractsPath}/contracts.ts`)
6248
+ if (sourcePath === `${contractsPath}/contracts`)
6024
6249
  return true;
6025
- return new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts\\.ts$`).test(file);
6250
+ return new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts$`).test(sourcePath);
6026
6251
  });
6027
6252
  }
6028
- return files.filter((file) => (file === `${contractsPath}.ts` ||
6029
- file.startsWith(`${contractsPath}/`)) &&
6030
- file.endsWith(".ts") &&
6031
- !file.endsWith(".test.ts"));
6253
+ return files.filter((file) => {
6254
+ if (!isAnalysisSourceFile(file) || isAppTestFile(file))
6255
+ return false;
6256
+ const sourcePath = stripSourceFileExtension(file);
6257
+ return (sourcePath === contractsPath || sourcePath.startsWith(`${contractsPath}/`));
6258
+ });
6032
6259
  }
6033
6260
  function parseContractLists(source, file, imports) {
6034
6261
  const lists = [];
@@ -6067,7 +6294,7 @@ function resolveContractList(lists, file, name, seen = new Set()) {
6067
6294
  if (seen.has(key))
6068
6295
  return new Set();
6069
6296
  const list = lists.get(key) ??
6070
- lists.get(contractListKey(file.replace(/\.ts$/, "/index.ts"), name));
6297
+ lists.get(contractListKey(directoryIndexSourceFile(file), name));
6071
6298
  if (!list)
6072
6299
  return undefined;
6073
6300
  seen.add(key);
@@ -6439,7 +6666,7 @@ function nextRoutePath(file, routeRoot) {
6439
6666
  const normalizedRouteRoot = directoryPath(routeRoot);
6440
6667
  const routeSegments = file
6441
6668
  .slice(normalizedRouteRoot.length + 1)
6442
- .replace(/\/route\.ts$/, "")
6669
+ .replace(/\/route\.(?:js|jsx|ts|tsx)$/, "")
6443
6670
  .split("/")
6444
6671
  .filter(Boolean);
6445
6672
  const apiPath = routeSegments.map(nextSegmentToContractSegment).join("/");