@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.
- package/CHANGELOG.md +20 -0
- package/README.md +51 -18
- package/dist/analysis/layers.d.ts.map +1 -1
- package/dist/analysis/layers.js +4 -2
- package/dist/analysis/layers.js.map +1 -1
- package/dist/analysis/port-wiring.d.ts.map +1 -1
- package/dist/analysis/port-wiring.js +2 -1
- package/dist/analysis/port-wiring.js.map +1 -1
- package/dist/analysis/source-files.d.ts +15 -0
- package/dist/analysis/source-files.d.ts.map +1 -0
- package/dist/analysis/source-files.js +52 -0
- package/dist/analysis/source-files.js.map +1 -0
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +21 -25
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map-changes.d.ts.map +1 -1
- package/dist/app-map-changes.js +5 -7
- package/dist/app-map-changes.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +33 -30
- package/dist/app-map.js.map +1 -1
- package/dist/check.d.ts +12 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +92 -25
- package/dist/check.js.map +1 -1
- package/dist/git-changes.d.ts +17 -1
- package/dist/git-changes.d.ts.map +1 -1
- package/dist/git-changes.js +118 -84
- package/dist/git-changes.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +339 -112
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +3 -6
- package/dist/lint.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +36 -13
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.js +5 -5
- package/dist/templates/db/mysql.d.ts.map +1 -1
- package/dist/templates/db/mysql.js +51 -22
- package/dist/templates/db/mysql.js.map +1 -1
- package/dist/templates/db/postgres.d.ts.map +1 -1
- package/dist/templates/db/postgres.js +60 -20
- package/dist/templates/db/postgres.js.map +1 -1
- package/dist/templates/db/sqlite.d.ts.map +1 -1
- package/dist/templates/db/sqlite.js +51 -23
- package/dist/templates/db/sqlite.js.map +1 -1
- package/dist/templates/shadcn.js +1 -1
- package/dist/templates/shadcn.js.map +1 -1
- package/dist/templates/shared.js +4 -4
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.d.ts.map +1 -1
- package/dist/templates/testing.js +7 -19
- package/dist/templates/testing.js.map +1 -1
- package/dist/test-discovery.d.ts +9 -0
- package/dist/test-discovery.d.ts.map +1 -0
- package/dist/test-discovery.js +40 -0
- package/dist/test-discovery.js.map +1 -0
- package/package.json +7 -5
- package/skills/app-structure/SKILL.md +32 -7
- package/src/analysis/layers.ts +4 -2
- package/src/analysis/port-wiring.ts +2 -1
- package/src/analysis/source-files.ts +61 -0
- package/src/analysis/workspace.ts +27 -31
- package/src/app-map-changes.ts +10 -8
- package/src/app-map.ts +43 -32
- package/src/check.ts +115 -25
- package/src/git-changes.ts +218 -86
- package/src/index.ts +8 -5
- package/src/inspect.ts +486 -141
- package/src/lint.ts +6 -5
- package/src/mcp.ts +1 -1
- package/src/templates/agents.ts +36 -13
- package/src/templates/base.ts +5 -5
- package/src/templates/db/mysql.ts +51 -22
- package/src/templates/db/postgres.ts +60 -20
- package/src/templates/db/sqlite.ts +51 -23
- package/src/templates/shadcn.ts +1 -1
- package/src/templates/shared.ts +4 -4
- package/src/templates/testing.ts +12 -19
- 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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
1141
|
-
if (!
|
|
1142
|
-
return
|
|
1143
|
-
|
|
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
|
|
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
|
|
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) ||
|
|
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
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2754
|
+
!isAppTestFile(file)) {
|
|
2535
2755
|
return false;
|
|
2536
2756
|
}
|
|
2537
|
-
return !options.policyOnly ||
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3231
|
+
const pattern = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/${escapeRegExp(folder)}/index$`);
|
|
3011
3232
|
for (const file of files) {
|
|
3012
|
-
if (!
|
|
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
|
|
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/[^/]
|
|
3388
|
+
const listenerPattern = new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/listeners/[^/]+$`);
|
|
3167
3389
|
const listened = new Set();
|
|
3168
3390
|
for (const file of files) {
|
|
3169
|
-
if (!
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
3646
|
-
|
|
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
|
|
3654
|
-
|
|
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
|
|
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
|
|
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) =>
|
|
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) =>
|
|
6012
|
-
file
|
|
6013
|
-
|
|
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
|
|
6243
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file))
|
|
6020
6244
|
return false;
|
|
6021
|
-
|
|
6245
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
6246
|
+
if (sourcePath === `${contractsPath}/index`)
|
|
6022
6247
|
return true;
|
|
6023
|
-
if (
|
|
6248
|
+
if (sourcePath === `${contractsPath}/contracts`)
|
|
6024
6249
|
return true;
|
|
6025
|
-
return new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts
|
|
6250
|
+
return new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts$`).test(sourcePath);
|
|
6026
6251
|
});
|
|
6027
6252
|
}
|
|
6028
|
-
return files.filter((file) =>
|
|
6029
|
-
file
|
|
6030
|
-
|
|
6031
|
-
|
|
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
|
|
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("/");
|