@beignet/cli 0.0.51 → 0.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +182 -19
- package/dist/analysis/layers.d.ts +2 -0
- package/dist/analysis/layers.d.ts.map +1 -1
- package/dist/analysis/layers.js +9 -2
- package/dist/analysis/layers.js.map +1 -1
- package/dist/analysis/port-wiring.d.ts +4 -2
- package/dist/analysis/port-wiring.d.ts.map +1 -1
- package/dist/analysis/port-wiring.js +41 -11
- 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/source-index.d.ts +2 -1
- package/dist/analysis/source-index.d.ts.map +1 -1
- package/dist/analysis/source-index.js +34 -5
- package/dist/analysis/source-index.js.map +1 -1
- package/dist/analysis/workspace-routes.d.ts +8 -0
- package/dist/analysis/workspace-routes.d.ts.map +1 -0
- package/dist/analysis/workspace-routes.js +291 -0
- package/dist/analysis/workspace-routes.js.map +1 -0
- package/dist/analysis/workspace.d.ts +24 -3
- package/dist/analysis/workspace.d.ts.map +1 -1
- package/dist/analysis/workspace.js +184 -43
- package/dist/analysis/workspace.js.map +1 -1
- package/dist/app-map-changes.d.ts.map +1 -1
- package/dist/app-map-changes.js +24 -14
- package/dist/app-map-changes.js.map +1 -1
- package/dist/app-map.d.ts.map +1 -1
- package/dist/app-map.js +87 -81
- 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/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +19 -0
- package/dist/config.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 +23 -3
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +424 -129
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +108 -43
- package/dist/lint.js.map +1 -1
- package/dist/make/shared.js +1 -1
- package/dist/make.js +18 -1
- package/dist/make.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/provider-add.js +2 -2
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +22 -6
- package/dist/provider-audit.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 +6 -6
- 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 +8 -5
- package/skills/app-structure/SKILL.md +74 -7
- package/src/analysis/layers.ts +13 -2
- package/src/analysis/port-wiring.ts +68 -11
- package/src/analysis/source-files.ts +61 -0
- package/src/analysis/source-index.ts +56 -4
- package/src/analysis/workspace-routes.ts +385 -0
- package/src/analysis/workspace.ts +302 -59
- package/src/app-map-changes.ts +41 -13
- package/src/app-map.ts +114 -103
- package/src/check.ts +115 -25
- package/src/config.ts +33 -0
- package/src/git-changes.ts +218 -86
- package/src/index.ts +33 -5
- package/src/inspect.ts +608 -156
- package/src/lint.ts +152 -48
- package/src/make/shared.ts +1 -1
- package/src/make.ts +31 -1
- package/src/mcp.ts +1 -1
- package/src/provider-add.ts +2 -2
- package/src/provider-audit.ts +30 -10
- 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 +6 -6
- package/src/templates/testing.ts +12 -19
- package/src/test-discovery.ts +53 -0
package/src/inspect.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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 {
|
|
6
8
|
type ContractFactoryBindings,
|
|
@@ -10,7 +12,23 @@ import {
|
|
|
10
12
|
} from "./analysis/contract-factories.js";
|
|
11
13
|
import { canonicalFeatureLayerPath } from "./analysis/layers.js";
|
|
12
14
|
import { analyzePortWiringFiles } from "./analysis/port-wiring.js";
|
|
13
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
analysisSourceFileExtensions,
|
|
17
|
+
isAnalysisSourceFile,
|
|
18
|
+
isAnalysisSourceFileExtension,
|
|
19
|
+
isJavaScriptSourceFile,
|
|
20
|
+
javascriptAnalysisSourceFileExtensions,
|
|
21
|
+
sourceFileScriptKind,
|
|
22
|
+
stripSourceFileExtension,
|
|
23
|
+
typescriptAnalysisSourceFileExtensions,
|
|
24
|
+
} from "./analysis/source-files.js";
|
|
25
|
+
import {
|
|
26
|
+
createSourceIndex,
|
|
27
|
+
exportedVariableDeclarations,
|
|
28
|
+
type SourceIndex,
|
|
29
|
+
} from "./analysis/source-index.js";
|
|
30
|
+
import { createAnalysisWorkspace, projectPath } from "./analysis/workspace.js";
|
|
31
|
+
import { workspaceRouteContracts } from "./analysis/workspace-routes.js";
|
|
14
32
|
import { createPainter } from "./ansi.js";
|
|
15
33
|
import type { DatabaseSchemaDialect, DatabaseSchemaTable } from "./choices.js";
|
|
16
34
|
import {
|
|
@@ -86,6 +104,7 @@ import {
|
|
|
86
104
|
legacyGeneratedPackageScripts,
|
|
87
105
|
} from "./templates/shared.js";
|
|
88
106
|
import { testSupportTemplateFiles } from "./templates/testing.js";
|
|
107
|
+
import { isAppTestFile } from "./test-discovery.js";
|
|
89
108
|
import { getCliVersion } from "./version.js";
|
|
90
109
|
|
|
91
110
|
type InspectAppOptions = {
|
|
@@ -200,6 +219,7 @@ type RouteExport = {
|
|
|
200
219
|
handlerFile: string;
|
|
201
220
|
contractRef?: string;
|
|
202
221
|
contractFile?: string;
|
|
222
|
+
contractUnresolved?: boolean;
|
|
203
223
|
catchAllPrefix?: string;
|
|
204
224
|
source: "next-route" | "route-local";
|
|
205
225
|
};
|
|
@@ -248,19 +268,76 @@ export async function inspectApp(
|
|
|
248
268
|
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
249
269
|
await assertDirectory(targetDir);
|
|
250
270
|
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
271
|
+
const workspace = await createAnalysisWorkspace(targetDir, {
|
|
272
|
+
config: options.config,
|
|
273
|
+
});
|
|
274
|
+
const { config } = workspace;
|
|
275
|
+
const files = workspace.projects[0].files;
|
|
256
276
|
const inspectedContracts = await readContracts(targetDir, files, config);
|
|
277
|
+
for (const project of workspace.projects.slice(1)) {
|
|
278
|
+
const shared = await readContracts(
|
|
279
|
+
project.targetDir,
|
|
280
|
+
project.files,
|
|
281
|
+
project.config,
|
|
282
|
+
);
|
|
283
|
+
inspectedContracts.contracts.push(
|
|
284
|
+
...shared.contracts.map((contract) => ({
|
|
285
|
+
...contract,
|
|
286
|
+
file: projectPath(project, contract.file),
|
|
287
|
+
})),
|
|
288
|
+
);
|
|
289
|
+
inspectedContracts.queryTransportDiagnostics.push(
|
|
290
|
+
...shared.queryTransportDiagnostics.map((diagnostic) => ({
|
|
291
|
+
...diagnostic,
|
|
292
|
+
...(diagnostic.file
|
|
293
|
+
? { file: projectPath(project, diagnostic.file) }
|
|
294
|
+
: {}),
|
|
295
|
+
...(diagnostic.subject
|
|
296
|
+
? {
|
|
297
|
+
subject: {
|
|
298
|
+
...diagnostic.subject,
|
|
299
|
+
file: projectPath(project, diagnostic.subject.file),
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
: {}),
|
|
303
|
+
})),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
257
306
|
const { contracts } = inspectedContracts;
|
|
307
|
+
contracts.sort(compareContracts);
|
|
308
|
+
const convention = inspectConvention(
|
|
309
|
+
files,
|
|
310
|
+
config,
|
|
311
|
+
contracts.length > 0 && workspace.projects.length > 1,
|
|
312
|
+
);
|
|
258
313
|
const matchedRoutes = await inspectRouteSurface(
|
|
259
314
|
targetDir,
|
|
260
315
|
files,
|
|
261
316
|
config,
|
|
262
317
|
contracts,
|
|
263
|
-
|
|
318
|
+
workspace.projects.length > 1 ? createSourceIndex(workspace) : undefined,
|
|
319
|
+
);
|
|
320
|
+
let workspaceRoutesUnresolved = false;
|
|
321
|
+
if (workspace.projects.length > 1) {
|
|
322
|
+
const registration = await workspaceRouteContracts(workspace);
|
|
323
|
+
workspaceRoutesUnresolved = registration.unresolved;
|
|
324
|
+
matchedRoutes.routes = matchedRoutes.routes.map((route) => {
|
|
325
|
+
if (route.handlerSource === "route-local") return route;
|
|
326
|
+
if (!registration.contracts.has(`${route.file}#${route.exportName}`))
|
|
327
|
+
return {
|
|
328
|
+
...route,
|
|
329
|
+
handlerFile: undefined,
|
|
330
|
+
handlerSource: "missing" as const,
|
|
331
|
+
};
|
|
332
|
+
return config.framework === "web"
|
|
333
|
+
? {
|
|
334
|
+
...route,
|
|
335
|
+
handlerFile: config.paths.server,
|
|
336
|
+
handlerSource: "web-server" as const,
|
|
337
|
+
}
|
|
338
|
+
: route;
|
|
339
|
+
});
|
|
340
|
+
}
|
|
264
341
|
const diagnostics = await inspectDiagnostics(
|
|
265
342
|
targetDir,
|
|
266
343
|
files,
|
|
@@ -272,6 +349,15 @@ export async function inspectApp(
|
|
|
272
349
|
inspectedContracts.queryTransportDiagnostics,
|
|
273
350
|
);
|
|
274
351
|
|
|
352
|
+
if (workspaceRoutesUnresolved)
|
|
353
|
+
diagnostics.push({
|
|
354
|
+
severity: "warning",
|
|
355
|
+
code: "BEIGNET_WORKSPACE_ROUTES_UNRESOLVED",
|
|
356
|
+
file: config.paths.server,
|
|
357
|
+
message:
|
|
358
|
+
"Could not fully resolve workspace route composition. Use explicit defineRoutes arrays and defineRouteGroup bindings reachable from the server routes option.",
|
|
359
|
+
});
|
|
360
|
+
|
|
275
361
|
return {
|
|
276
362
|
schemaVersion: 1,
|
|
277
363
|
targetDir,
|
|
@@ -354,11 +440,15 @@ async function buildDoctorFixPlan(
|
|
|
354
440
|
: await loadBeignetConfig(targetDir, files);
|
|
355
441
|
const convention = inspectConvention(files, config);
|
|
356
442
|
const { contracts } = await readContracts(targetDir, files, config);
|
|
443
|
+
const sourceIndex = config.workspace.packages.length
|
|
444
|
+
? createSourceIndex(await createAnalysisWorkspace(targetDir, { config }))
|
|
445
|
+
: undefined;
|
|
357
446
|
const matchedRoutes = await inspectRouteSurface(
|
|
358
447
|
targetDir,
|
|
359
448
|
files,
|
|
360
449
|
config,
|
|
361
450
|
contracts,
|
|
451
|
+
sourceIndex,
|
|
362
452
|
);
|
|
363
453
|
const operations: PlannedDoctorFixOperation[] = [];
|
|
364
454
|
|
|
@@ -883,14 +973,15 @@ async function readContracts(
|
|
|
883
973
|
function inspectConvention(
|
|
884
974
|
files: string[],
|
|
885
975
|
config: ResolvedBeignetConfig,
|
|
976
|
+
sharedContracts = false,
|
|
886
977
|
): InspectConvention {
|
|
887
978
|
const missingNextLayout = missingRequirements(files, [
|
|
888
|
-
`${directoryPath(config.paths.contracts)}
|
|
979
|
+
...(sharedContracts ? [] : [`${directoryPath(config.paths.contracts)}/`]),
|
|
889
980
|
`${directoryPath(config.paths.routes)}/`,
|
|
890
981
|
config.paths.server,
|
|
891
982
|
]);
|
|
892
983
|
const missingWebLayout = missingRequirements(files, [
|
|
893
|
-
`${directoryPath(config.paths.contracts)}
|
|
984
|
+
...(sharedContracts ? [] : [`${directoryPath(config.paths.contracts)}/`]),
|
|
894
985
|
config.paths.server,
|
|
895
986
|
]);
|
|
896
987
|
const missingResourceGenerator = missingRequirements(files, [
|
|
@@ -946,7 +1037,7 @@ function parseContracts(source: string, file: string): ParsedContract[] {
|
|
|
946
1037
|
source,
|
|
947
1038
|
ts.ScriptTarget.Latest,
|
|
948
1039
|
true,
|
|
949
|
-
|
|
1040
|
+
sourceFileScriptKind(file),
|
|
950
1041
|
);
|
|
951
1042
|
const assignments = variableAssignments(sourceFile);
|
|
952
1043
|
const assignmentsByName = new Map(
|
|
@@ -1556,34 +1647,53 @@ function normalizeContractPath(routePath: string): string {
|
|
|
1556
1647
|
async function readRouteExports(
|
|
1557
1648
|
targetDir: string,
|
|
1558
1649
|
routeFiles: string[],
|
|
1650
|
+
files: string[],
|
|
1559
1651
|
config: ResolvedBeignetConfig,
|
|
1652
|
+
sourceIndex?: SourceIndex,
|
|
1560
1653
|
): Promise<Map<string, RouteExport[]>> {
|
|
1561
1654
|
const routeExports = new Map<string, RouteExport[]>();
|
|
1562
1655
|
|
|
1563
1656
|
for (const file of routeFiles) {
|
|
1564
1657
|
const routePath = nextRoutePath(file, config.paths.routes);
|
|
1565
1658
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
1566
|
-
routeExports.set(
|
|
1659
|
+
routeExports.set(
|
|
1660
|
+
file,
|
|
1661
|
+
await parseRouteExports(
|
|
1662
|
+
source,
|
|
1663
|
+
file,
|
|
1664
|
+
routePath,
|
|
1665
|
+
files,
|
|
1666
|
+
config,
|
|
1667
|
+
sourceIndex,
|
|
1668
|
+
),
|
|
1669
|
+
);
|
|
1567
1670
|
}
|
|
1568
1671
|
|
|
1569
1672
|
return routeExports;
|
|
1570
1673
|
}
|
|
1571
1674
|
|
|
1572
|
-
function parseRouteExports(
|
|
1675
|
+
async function parseRouteExports(
|
|
1573
1676
|
source: string,
|
|
1574
1677
|
handlerFile: string,
|
|
1575
1678
|
routePath: string,
|
|
1679
|
+
files: string[],
|
|
1576
1680
|
config: ResolvedBeignetConfig,
|
|
1577
|
-
|
|
1681
|
+
sourceIndex?: SourceIndex,
|
|
1682
|
+
): Promise<RouteExport[]> {
|
|
1578
1683
|
const exports: RouteExport[] = [];
|
|
1579
1684
|
const sourceFile = ts.createSourceFile(
|
|
1580
1685
|
handlerFile,
|
|
1581
1686
|
source,
|
|
1582
1687
|
ts.ScriptTarget.Latest,
|
|
1583
1688
|
true,
|
|
1584
|
-
|
|
1689
|
+
sourceFileScriptKind(handlerFile),
|
|
1690
|
+
);
|
|
1691
|
+
const imports = parseRouteNamedImports(
|
|
1692
|
+
sourceFile,
|
|
1693
|
+
config,
|
|
1694
|
+
handlerFile,
|
|
1695
|
+
files,
|
|
1585
1696
|
);
|
|
1586
|
-
const imports = parseRouteNamedImports(sourceFile, config, handlerFile);
|
|
1587
1697
|
|
|
1588
1698
|
for (const statement of sourceFile.statements) {
|
|
1589
1699
|
if (
|
|
@@ -1628,11 +1738,17 @@ function parseRouteExports(
|
|
|
1628
1738
|
|
|
1629
1739
|
if (localName) {
|
|
1630
1740
|
const imported = imports.get(localName);
|
|
1741
|
+
const resolved = await sourceIndex?.resolveValue(
|
|
1742
|
+
handlerFile,
|
|
1743
|
+
ts.factory.createIdentifier(localName),
|
|
1744
|
+
);
|
|
1631
1745
|
exports.push({
|
|
1632
1746
|
method,
|
|
1633
1747
|
handlerFile,
|
|
1634
|
-
contractRef:
|
|
1635
|
-
|
|
1748
|
+
contractRef:
|
|
1749
|
+
resolved?.exportName ?? imported?.importedName ?? localName,
|
|
1750
|
+
contractFile: resolved?.file ?? imported?.contractFile,
|
|
1751
|
+
...(sourceIndex && !resolved ? { contractUnresolved: true } : {}),
|
|
1636
1752
|
source: "route-local",
|
|
1637
1753
|
});
|
|
1638
1754
|
continue;
|
|
@@ -1656,6 +1772,7 @@ function parseRouteNamedImports(
|
|
|
1656
1772
|
sourceFile: ts.SourceFile,
|
|
1657
1773
|
config: ResolvedBeignetConfig,
|
|
1658
1774
|
handlerFile: string,
|
|
1775
|
+
files: string[],
|
|
1659
1776
|
): Map<string, { importedName: string; contractFile?: string }> {
|
|
1660
1777
|
const imports = new Map<
|
|
1661
1778
|
string,
|
|
@@ -1677,6 +1794,7 @@ function parseRouteNamedImports(
|
|
|
1677
1794
|
statement.moduleSpecifier.text,
|
|
1678
1795
|
config,
|
|
1679
1796
|
handlerFile,
|
|
1797
|
+
files,
|
|
1680
1798
|
);
|
|
1681
1799
|
for (const element of statement.importClause.namedBindings.elements) {
|
|
1682
1800
|
if (element.isTypeOnly) continue;
|
|
@@ -1757,6 +1875,7 @@ function parseNamedImports(
|
|
|
1757
1875
|
source: string,
|
|
1758
1876
|
config: ResolvedBeignetConfig,
|
|
1759
1877
|
importerFile?: string,
|
|
1878
|
+
files?: string[],
|
|
1760
1879
|
): Map<
|
|
1761
1880
|
string,
|
|
1762
1881
|
{
|
|
@@ -1779,6 +1898,7 @@ function parseNamedImports(
|
|
|
1779
1898
|
sourcePath,
|
|
1780
1899
|
config,
|
|
1781
1900
|
importerFile,
|
|
1901
|
+
files,
|
|
1782
1902
|
);
|
|
1783
1903
|
for (const member of match[1].split(",")) {
|
|
1784
1904
|
const parsed = parseImportMember(member);
|
|
@@ -1849,9 +1969,7 @@ function sourceFileFromImport(
|
|
|
1849
1969
|
files?: string[],
|
|
1850
1970
|
): string | undefined {
|
|
1851
1971
|
const resolveCandidate = (candidate: string) => {
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
const candidates = [`${candidate}.ts`, `${candidate}/index.ts`];
|
|
1972
|
+
const candidates = sourceFileSpecifierCandidates(candidate, importerFile);
|
|
1855
1973
|
return files
|
|
1856
1974
|
? candidates.find((file) => files.includes(file))
|
|
1857
1975
|
: candidates[0];
|
|
@@ -1870,33 +1988,76 @@ function sourceFileFromImport(
|
|
|
1870
1988
|
return undefined;
|
|
1871
1989
|
}
|
|
1872
1990
|
|
|
1991
|
+
function sourceFileCandidates(base: string, importerFile?: string): string[] {
|
|
1992
|
+
const extensions =
|
|
1993
|
+
importerFile && isJavaScriptSourceFile(importerFile)
|
|
1994
|
+
? [
|
|
1995
|
+
...javascriptAnalysisSourceFileExtensions,
|
|
1996
|
+
...typescriptAnalysisSourceFileExtensions,
|
|
1997
|
+
]
|
|
1998
|
+
: analysisSourceFileExtensions;
|
|
1999
|
+
return [
|
|
2000
|
+
...extensions.map((extension) => `${base}${extension}`),
|
|
2001
|
+
...extensions.map((extension) => `${base}/index${extension}`),
|
|
2002
|
+
];
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function sourceFileSpecifierCandidates(
|
|
2006
|
+
candidate: string,
|
|
2007
|
+
importerFile?: string,
|
|
2008
|
+
): string[] {
|
|
2009
|
+
const extension = path.posix.extname(candidate).toLowerCase();
|
|
2010
|
+
if (!extension) return sourceFileCandidates(candidate, importerFile);
|
|
2011
|
+
if (!isAnalysisSourceFileExtension(extension)) return [candidate];
|
|
2012
|
+
|
|
2013
|
+
const sourceExtensions =
|
|
2014
|
+
extension === ".js"
|
|
2015
|
+
? [".ts", ".tsx"]
|
|
2016
|
+
: extension === ".jsx"
|
|
2017
|
+
? [".tsx"]
|
|
2018
|
+
: extension === ".mjs"
|
|
2019
|
+
? [".mts"]
|
|
2020
|
+
: extension === ".cjs"
|
|
2021
|
+
? [".cts"]
|
|
2022
|
+
: [];
|
|
2023
|
+
const sourceCandidates = sourceExtensions.map(
|
|
2024
|
+
(sourceExtension) =>
|
|
2025
|
+
`${candidate.slice(0, -extension.length)}${sourceExtension}`,
|
|
2026
|
+
);
|
|
2027
|
+
const directCandidates =
|
|
2028
|
+
importerFile && isJavaScriptSourceFile(importerFile)
|
|
2029
|
+
? [candidate, ...sourceCandidates]
|
|
2030
|
+
: [...sourceCandidates, candidate];
|
|
2031
|
+
return [
|
|
2032
|
+
...new Set([
|
|
2033
|
+
...directCandidates,
|
|
2034
|
+
...directCandidates.map(directoryIndexSourceFile),
|
|
2035
|
+
]),
|
|
2036
|
+
];
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
function isSourceFileNamed(file: string, name: string): boolean {
|
|
2040
|
+
return (
|
|
2041
|
+
isAnalysisSourceFile(file) &&
|
|
2042
|
+
path.posix.basename(stripSourceFileExtension(file)) === name
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
function directoryIndexSourceFile(file: string): string {
|
|
2047
|
+
const extension = path.posix.extname(file);
|
|
2048
|
+
return `${stripSourceFileExtension(file)}/index${extension}`;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
1873
2051
|
function contractFileFromImport(
|
|
1874
2052
|
sourcePath: string,
|
|
1875
2053
|
config: ResolvedBeignetConfig,
|
|
1876
2054
|
importerFile?: string,
|
|
2055
|
+
files?: string[],
|
|
1877
2056
|
): string | undefined {
|
|
1878
2057
|
const resolveCandidate = (candidate: string) => {
|
|
1879
|
-
const
|
|
1880
|
-
if (!
|
|
1881
|
-
|
|
1882
|
-
let sourceExtension: string | undefined;
|
|
1883
|
-
switch (extension) {
|
|
1884
|
-
case ".js":
|
|
1885
|
-
sourceExtension = ".ts";
|
|
1886
|
-
break;
|
|
1887
|
-
case ".jsx":
|
|
1888
|
-
sourceExtension = ".tsx";
|
|
1889
|
-
break;
|
|
1890
|
-
case ".mjs":
|
|
1891
|
-
sourceExtension = ".mts";
|
|
1892
|
-
break;
|
|
1893
|
-
case ".cjs":
|
|
1894
|
-
sourceExtension = ".cts";
|
|
1895
|
-
break;
|
|
1896
|
-
}
|
|
1897
|
-
return sourceExtension
|
|
1898
|
-
? `${candidate.slice(0, -extension.length)}${sourceExtension}`
|
|
1899
|
-
: candidate;
|
|
2058
|
+
const candidates = sourceFileSpecifierCandidates(candidate, importerFile);
|
|
2059
|
+
if (!files) return candidates[0];
|
|
2060
|
+
return candidates.find((file) => files.includes(file)) ?? candidates[0];
|
|
1900
2061
|
};
|
|
1901
2062
|
const contractsPath = directoryPath(config.paths.contracts);
|
|
1902
2063
|
const aliasPrefix = `@/${contractsPath}/`;
|
|
@@ -1905,7 +2066,7 @@ function contractFileFromImport(
|
|
|
1905
2066
|
const relativeExact = contractsPath;
|
|
1906
2067
|
|
|
1907
2068
|
if (sourcePath === aliasExact || sourcePath === relativeExact) {
|
|
1908
|
-
return `${contractsPath}/index
|
|
2069
|
+
return resolveCandidate(`${contractsPath}/index`);
|
|
1909
2070
|
}
|
|
1910
2071
|
if (sourcePath.startsWith(aliasPrefix)) {
|
|
1911
2072
|
return resolveCandidate(sourcePath.slice("@/".length));
|
|
@@ -1997,14 +2158,21 @@ async function inspectRouteSurface(
|
|
|
1997
2158
|
files: string[],
|
|
1998
2159
|
config: ResolvedBeignetConfig,
|
|
1999
2160
|
contracts: InspectedContract[],
|
|
2161
|
+
sourceIndex?: SourceIndex,
|
|
2000
2162
|
): Promise<MatchedRoutesResult> {
|
|
2001
2163
|
if (config.framework === "next") {
|
|
2002
2164
|
const routeFiles = files.filter(
|
|
2003
2165
|
(file) =>
|
|
2004
2166
|
file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
|
|
2005
|
-
file
|
|
2167
|
+
isNextRouteFile(file, config),
|
|
2168
|
+
);
|
|
2169
|
+
const routeExports = await readRouteExports(
|
|
2170
|
+
targetDir,
|
|
2171
|
+
routeFiles,
|
|
2172
|
+
files,
|
|
2173
|
+
config,
|
|
2174
|
+
sourceIndex,
|
|
2006
2175
|
);
|
|
2007
|
-
const routeExports = await readRouteExports(targetDir, routeFiles, config);
|
|
2008
2176
|
return matchRoutes(contracts, routeExports);
|
|
2009
2177
|
}
|
|
2010
2178
|
|
|
@@ -2071,13 +2239,13 @@ function findContracts(
|
|
|
2071
2239
|
routeExport: RouteExport,
|
|
2072
2240
|
): InspectedContract[] {
|
|
2073
2241
|
if (routeExport.source === "route-local") {
|
|
2242
|
+
if (routeExport.contractUnresolved) return [];
|
|
2074
2243
|
const contract = contracts.find(
|
|
2075
2244
|
(contract) =>
|
|
2076
2245
|
contract.exportName === routeExport.contractRef &&
|
|
2077
2246
|
(!routeExport.contractFile ||
|
|
2078
2247
|
contract.file === routeExport.contractFile ||
|
|
2079
|
-
contract.file ===
|
|
2080
|
-
routeExport.contractFile.replace(/\.ts$/, "/index.ts")),
|
|
2248
|
+
contract.file === directoryIndexSourceFile(routeExport.contractFile)),
|
|
2081
2249
|
);
|
|
2082
2250
|
return contract ? [contract] : [];
|
|
2083
2251
|
}
|
|
@@ -2319,6 +2487,7 @@ async function inspectCanonicalConformance(
|
|
|
2319
2487
|
files.some((file) => file.startsWith(`${clientDir}/`)) ||
|
|
2320
2488
|
[
|
|
2321
2489
|
"@beignet/react-query",
|
|
2490
|
+
"@beignet/react-form",
|
|
2322
2491
|
"@beignet/react-hook-form",
|
|
2323
2492
|
"@beignet/react-uploads",
|
|
2324
2493
|
"@beignet/nuqs",
|
|
@@ -2365,12 +2534,13 @@ async function inspectCanonicalConformance(
|
|
|
2365
2534
|
}
|
|
2366
2535
|
|
|
2367
2536
|
if (
|
|
2537
|
+
installedPackages.has("@beignet/react-form") ||
|
|
2368
2538
|
installedPackages.has("@beignet/react-hook-form") ||
|
|
2369
2539
|
files.includes(clientForms)
|
|
2370
2540
|
) {
|
|
2371
2541
|
requiredFiles.push({
|
|
2372
2542
|
file: clientForms,
|
|
2373
|
-
reason: `${clientForms} should own
|
|
2543
|
+
reason: `${clientForms} should own form adapter setup (createReactForm or createReactHookForm) when form helpers are used.`,
|
|
2374
2544
|
});
|
|
2375
2545
|
}
|
|
2376
2546
|
|
|
@@ -2521,7 +2691,12 @@ async function inspectCanonicalConformance(
|
|
|
2521
2691
|
|
|
2522
2692
|
const featureRoutesPath = `${featuresPath}/`;
|
|
2523
2693
|
for (const file of files) {
|
|
2524
|
-
if (
|
|
2694
|
+
if (
|
|
2695
|
+
!file.startsWith(featureRoutesPath) ||
|
|
2696
|
+
!isAnalysisSourceFile(file) ||
|
|
2697
|
+
isAppTestFile(file) ||
|
|
2698
|
+
!stripSourceFileExtension(file).endsWith("/routes")
|
|
2699
|
+
) {
|
|
2525
2700
|
continue;
|
|
2526
2701
|
}
|
|
2527
2702
|
|
|
@@ -2910,7 +3085,7 @@ async function inspectProductionReadiness(
|
|
|
2910
3085
|
let hasSecurityHeadersHook = false;
|
|
2911
3086
|
|
|
2912
3087
|
for (const file of files) {
|
|
2913
|
-
if (!file
|
|
3088
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
2914
3089
|
|
|
2915
3090
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2916
3091
|
const routeServerOrInfra = isRouteServerOrInfraFile(file, config);
|
|
@@ -2937,7 +3112,7 @@ async function inspectProductionReadiness(
|
|
|
2937
3112
|
|
|
2938
3113
|
if (
|
|
2939
3114
|
fileWithinDirectory(file, cronDirectory) &&
|
|
2940
|
-
file
|
|
3115
|
+
isNextRouteFile(file, config) &&
|
|
2941
3116
|
!/\bCRON_SECRET\b/.test(source) &&
|
|
2942
3117
|
!/\bcreateOutboxDrainRoute\s*\(/.test(source) &&
|
|
2943
3118
|
!/\bcreateScheduleRoute\s*\(/.test(source)
|
|
@@ -3239,6 +3414,25 @@ async function inspectProductionReadiness(
|
|
|
3239
3414
|
"@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.",
|
|
3240
3415
|
});
|
|
3241
3416
|
}
|
|
3417
|
+
|
|
3418
|
+
if (
|
|
3419
|
+
(await betterAuthRequiresIssuerSchema(targetDir, packageJson)) &&
|
|
3420
|
+
(await anyFileContains(
|
|
3421
|
+
targetDir,
|
|
3422
|
+
configFiles,
|
|
3423
|
+
"drizzleAdapter",
|
|
3424
|
+
sourceCache,
|
|
3425
|
+
)) &&
|
|
3426
|
+
!(await hasBetterAuthIssuerSchema(targetDir, files, sourceCache))
|
|
3427
|
+
) {
|
|
3428
|
+
diagnostics.push({
|
|
3429
|
+
severity: "warning",
|
|
3430
|
+
code: "BEIGNET_BETTER_AUTH_ISSUER_SCHEMA_MISSING",
|
|
3431
|
+
file: schemaDir,
|
|
3432
|
+
message:
|
|
3433
|
+
"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.",
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3242
3436
|
}
|
|
3243
3437
|
|
|
3244
3438
|
const installedReadinessProviders = [...installedPackages].filter(
|
|
@@ -3284,7 +3478,7 @@ async function uploadHasExplicitSizeLimit(
|
|
|
3284
3478
|
);
|
|
3285
3479
|
if (!importedName) continue;
|
|
3286
3480
|
|
|
3287
|
-
const importedFile =
|
|
3481
|
+
const importedFile = sourceFileFromImport(moduleName, file, files);
|
|
3288
3482
|
if (!importedFile) continue;
|
|
3289
3483
|
|
|
3290
3484
|
const importedSource = await readCachedSource(
|
|
@@ -3339,20 +3533,6 @@ function exportedObjectHasProperty(
|
|
|
3339
3533
|
return new RegExp(`\\b${escapeRegExp(propertyName)}\\s*:`).test(initializer);
|
|
3340
3534
|
}
|
|
3341
3535
|
|
|
3342
|
-
function resolveLocalTypeScriptModule(
|
|
3343
|
-
importer: string,
|
|
3344
|
-
moduleName: string,
|
|
3345
|
-
files: string[],
|
|
3346
|
-
): string | undefined {
|
|
3347
|
-
const base = normalizePath(
|
|
3348
|
-
path.posix.normalize(
|
|
3349
|
-
path.posix.join(path.posix.dirname(importer), moduleName),
|
|
3350
|
-
),
|
|
3351
|
-
);
|
|
3352
|
-
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}/index.ts`];
|
|
3353
|
-
return candidates.find((candidate) => files.includes(candidate));
|
|
3354
|
-
}
|
|
3355
|
-
|
|
3356
3536
|
async function hasReadinessCheck(
|
|
3357
3537
|
targetDir: string,
|
|
3358
3538
|
files: string[],
|
|
@@ -3360,10 +3540,18 @@ async function hasReadinessCheck(
|
|
|
3360
3540
|
sourceCache: Map<string, string>,
|
|
3361
3541
|
): Promise<boolean> {
|
|
3362
3542
|
const routesDir = directoryPath(config.paths.routes);
|
|
3363
|
-
if (
|
|
3543
|
+
if (
|
|
3544
|
+
files.some(
|
|
3545
|
+
(file) =>
|
|
3546
|
+
path.posix.dirname(file) === `${routesDir}/ready` &&
|
|
3547
|
+
isNextRouteFile(file, config),
|
|
3548
|
+
)
|
|
3549
|
+
) {
|
|
3550
|
+
return true;
|
|
3551
|
+
}
|
|
3364
3552
|
|
|
3365
3553
|
for (const file of files) {
|
|
3366
|
-
if (!file
|
|
3554
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
3367
3555
|
if (!isRouteServerOrInfraFile(file, config)) continue;
|
|
3368
3556
|
|
|
3369
3557
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
@@ -3905,10 +4093,11 @@ function isFeatureUploadDefinition(
|
|
|
3905
4093
|
file: string,
|
|
3906
4094
|
config: ResolvedBeignetConfig,
|
|
3907
4095
|
): boolean {
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
4096
|
+
return isFeatureArtifactFile(
|
|
4097
|
+
file,
|
|
4098
|
+
directoryPath(config.paths.features),
|
|
4099
|
+
"uploads",
|
|
4100
|
+
);
|
|
3912
4101
|
}
|
|
3913
4102
|
|
|
3914
4103
|
function operationalConfigFiles(
|
|
@@ -3918,17 +4107,263 @@ function operationalConfigFiles(
|
|
|
3918
4107
|
return providerOperationalConfigFiles(files, config);
|
|
3919
4108
|
}
|
|
3920
4109
|
|
|
3921
|
-
|
|
4110
|
+
type InspectedPackageJson = {
|
|
4111
|
+
dependencies?: Record<string, string>;
|
|
4112
|
+
devDependencies?: Record<string, string>;
|
|
4113
|
+
peerDependencies?: Record<string, string>;
|
|
4114
|
+
};
|
|
4115
|
+
|
|
4116
|
+
function declaredBetterAuthMayRequireIssuerSchema(
|
|
4117
|
+
packageJson: InspectedPackageJson | undefined,
|
|
4118
|
+
): boolean {
|
|
4119
|
+
const version =
|
|
4120
|
+
packageJson?.dependencies?.["better-auth"] ??
|
|
4121
|
+
packageJson?.devDependencies?.["better-auth"] ??
|
|
4122
|
+
packageJson?.peerDependencies?.["better-auth"];
|
|
4123
|
+
if (!version) return false;
|
|
4124
|
+
|
|
4125
|
+
const normalized = version.trim();
|
|
4126
|
+
const workspaceRange = normalized.startsWith("workspace:")
|
|
4127
|
+
? normalized.slice("workspace:".length)
|
|
4128
|
+
: normalized;
|
|
4129
|
+
const npmAlias = /^npm:better-auth@(.+)$/.exec(workspaceRange);
|
|
4130
|
+
const candidate = npmAlias?.[1] ?? workspaceRange;
|
|
4131
|
+
const range = semver.validRange(candidate);
|
|
4132
|
+
|
|
4133
|
+
// Tags, catalogs, local paths, and other non-semver declarations can resolve
|
|
4134
|
+
// to 1.7. Prefer a migration warning until an installed version is readable.
|
|
4135
|
+
if (!range) return true;
|
|
4136
|
+
|
|
4137
|
+
return semver.intersects(range, ">=1.7.0");
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
async function betterAuthRequiresIssuerSchema(
|
|
4141
|
+
targetDir: string,
|
|
4142
|
+
packageJson: InspectedPackageJson | undefined,
|
|
4143
|
+
): Promise<boolean> {
|
|
4144
|
+
const installed = await readInstalledBetterAuthPackageJson(targetDir);
|
|
4145
|
+
if (installed?.version) {
|
|
4146
|
+
const version = semver.valid(installed.version.trim());
|
|
4147
|
+
if (version) return semver.gte(version, "1.7.0");
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
return declaredBetterAuthMayRequireIssuerSchema(packageJson);
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
async function readInstalledBetterAuthPackageJson(
|
|
4154
|
+
targetDir: string,
|
|
4155
|
+
): Promise<{ name?: string; version?: string } | undefined> {
|
|
4156
|
+
const nodeModulesPackage =
|
|
4157
|
+
await readBetterAuthPackageFromNodeModules(targetDir);
|
|
4158
|
+
if (nodeModulesPackage) return nodeModulesPackage;
|
|
4159
|
+
|
|
4160
|
+
const requireFromTarget = createRequire(path.join(targetDir, "package.json"));
|
|
4161
|
+
const packageJsonPath = resolveModuleFromTarget(
|
|
4162
|
+
requireFromTarget,
|
|
4163
|
+
"better-auth/package.json",
|
|
4164
|
+
);
|
|
4165
|
+
if (packageJsonPath) {
|
|
4166
|
+
return readJsonIfExists(packageJsonPath);
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4169
|
+
const entryPath = resolveModuleFromTarget(requireFromTarget, "better-auth");
|
|
4170
|
+
if (!entryPath) return undefined;
|
|
4171
|
+
|
|
4172
|
+
let directory = path.dirname(entryPath);
|
|
4173
|
+
while (true) {
|
|
4174
|
+
const candidate = await readJsonIfExists<{
|
|
4175
|
+
name?: string;
|
|
4176
|
+
version?: string;
|
|
4177
|
+
}>(path.join(directory, "package.json"));
|
|
4178
|
+
if (candidate?.name === "better-auth") return candidate;
|
|
4179
|
+
|
|
4180
|
+
const parent = path.dirname(directory);
|
|
4181
|
+
if (parent === directory) return undefined;
|
|
4182
|
+
directory = parent;
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
async function readBetterAuthPackageFromNodeModules(
|
|
4187
|
+
targetDir: string,
|
|
4188
|
+
): Promise<{ name?: string; version?: string } | undefined> {
|
|
4189
|
+
let directory = targetDir;
|
|
4190
|
+
while (true) {
|
|
4191
|
+
const candidate = await readJsonIfExists<{
|
|
4192
|
+
name?: string;
|
|
4193
|
+
version?: string;
|
|
4194
|
+
}>(path.join(directory, "node_modules", "better-auth", "package.json"));
|
|
4195
|
+
if (candidate?.name === "better-auth") return candidate;
|
|
4196
|
+
|
|
4197
|
+
const parent = path.dirname(directory);
|
|
4198
|
+
if (parent === directory) return undefined;
|
|
4199
|
+
directory = parent;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
function resolveModuleFromTarget(
|
|
4204
|
+
requireFromTarget: ReturnType<typeof createRequire>,
|
|
4205
|
+
request: string,
|
|
4206
|
+
): string | undefined {
|
|
4207
|
+
try {
|
|
4208
|
+
return requireFromTarget.resolve(request);
|
|
4209
|
+
} catch {
|
|
4210
|
+
return undefined;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
async function hasBetterAuthIssuerSchema(
|
|
3922
4215
|
targetDir: string,
|
|
3923
4216
|
files: string[],
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4217
|
+
sourceCache: Map<string, string>,
|
|
4218
|
+
): Promise<boolean> {
|
|
4219
|
+
const schemaFiles = files.filter(
|
|
4220
|
+
(file) =>
|
|
4221
|
+
!isTestSourceFile(file) &&
|
|
4222
|
+
/\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs)$/.test(file),
|
|
4223
|
+
);
|
|
4224
|
+
|
|
4225
|
+
for (const file of schemaFiles) {
|
|
4226
|
+
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
4227
|
+
if (sourceHasBetterAuthIssuerSchema(file, source)) return true;
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
return false;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
function sourceHasBetterAuthIssuerSchema(
|
|
4234
|
+
file: string,
|
|
4235
|
+
source: string,
|
|
4236
|
+
): boolean {
|
|
4237
|
+
const sourceFile = ts.createSourceFile(
|
|
4238
|
+
file,
|
|
4239
|
+
source,
|
|
4240
|
+
ts.ScriptTarget.Latest,
|
|
4241
|
+
true,
|
|
4242
|
+
);
|
|
4243
|
+
let found = false;
|
|
4244
|
+
const visit = (node: ts.Node): void => {
|
|
4245
|
+
if (found) return;
|
|
4246
|
+
if (
|
|
4247
|
+
ts.isCallExpression(node) &&
|
|
4248
|
+
isBetterAuthAccountTableCall(node) &&
|
|
4249
|
+
betterAuthAccountTableHasIssuerIdentity(node)
|
|
4250
|
+
) {
|
|
4251
|
+
found = true;
|
|
4252
|
+
return;
|
|
3929
4253
|
}
|
|
3930
|
-
|
|
3931
|
-
|
|
4254
|
+
ts.forEachChild(node, visit);
|
|
4255
|
+
};
|
|
4256
|
+
visit(sourceFile);
|
|
4257
|
+
return found;
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
function isBetterAuthAccountTableCall(call: ts.CallExpression): boolean {
|
|
4261
|
+
const callee = unwrapContractExpression(call.expression);
|
|
4262
|
+
const factoryName = ts.isIdentifier(callee)
|
|
4263
|
+
? callee.text
|
|
4264
|
+
: ts.isPropertyAccessExpression(callee)
|
|
4265
|
+
? callee.name.text
|
|
4266
|
+
: undefined;
|
|
4267
|
+
return (
|
|
4268
|
+
factoryName !== undefined &&
|
|
4269
|
+
["mysqlTable", "pgTable", "sqliteTable"].includes(factoryName) &&
|
|
4270
|
+
staticString(call.arguments[0]) === "account"
|
|
4271
|
+
);
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
function betterAuthAccountTableHasIssuerIdentity(
|
|
4275
|
+
call: ts.CallExpression,
|
|
4276
|
+
): boolean {
|
|
4277
|
+
const columns = call.arguments[1];
|
|
4278
|
+
if (!columns || !ts.isObjectLiteralExpression(columns)) return false;
|
|
4279
|
+
const issuer = columns.properties.find(
|
|
4280
|
+
(property) =>
|
|
4281
|
+
ts.isPropertyAssignment(property) &&
|
|
4282
|
+
staticPropertyName(property.name) === "issuer",
|
|
4283
|
+
);
|
|
4284
|
+
if (!issuer || !ts.isPropertyAssignment(issuer)) return false;
|
|
4285
|
+
if (!isBetterAuthIssuerColumn(issuer.initializer)) return false;
|
|
4286
|
+
|
|
4287
|
+
const constraints = call.arguments[2];
|
|
4288
|
+
if (
|
|
4289
|
+
!constraints ||
|
|
4290
|
+
(!ts.isArrowFunction(constraints) &&
|
|
4291
|
+
!ts.isFunctionExpression(constraints)) ||
|
|
4292
|
+
constraints.parameters.length === 0 ||
|
|
4293
|
+
!ts.isIdentifier(constraints.parameters[0]?.name)
|
|
4294
|
+
) {
|
|
4295
|
+
return false;
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
const tableName = constraints.parameters[0].name.text;
|
|
4299
|
+
let found = false;
|
|
4300
|
+
const visit = (node: ts.Node): void => {
|
|
4301
|
+
if (found) return;
|
|
4302
|
+
if (
|
|
4303
|
+
ts.isCallExpression(node) &&
|
|
4304
|
+
isBetterAuthIssuerAccountUniqueConstraint(node, tableName)
|
|
4305
|
+
) {
|
|
4306
|
+
found = true;
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
ts.forEachChild(node, visit);
|
|
4310
|
+
};
|
|
4311
|
+
visit(constraints.body);
|
|
4312
|
+
return found;
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
function isBetterAuthIssuerColumn(expression: ts.Expression): boolean {
|
|
4316
|
+
let current = unwrapContractExpression(expression);
|
|
4317
|
+
while (ts.isCallExpression(current)) {
|
|
4318
|
+
const callee = unwrapContractExpression(current.expression);
|
|
4319
|
+
if (
|
|
4320
|
+
ts.isIdentifier(callee) &&
|
|
4321
|
+
["text", "varchar"].includes(callee.text) &&
|
|
4322
|
+
staticString(current.arguments[0]) === "issuer"
|
|
4323
|
+
) {
|
|
4324
|
+
return true;
|
|
4325
|
+
}
|
|
4326
|
+
if (!ts.isPropertyAccessExpression(callee)) return false;
|
|
4327
|
+
current = unwrapContractExpression(callee.expression);
|
|
4328
|
+
}
|
|
4329
|
+
return false;
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
function isBetterAuthIssuerAccountUniqueConstraint(
|
|
4333
|
+
call: ts.CallExpression,
|
|
4334
|
+
tableName: string,
|
|
4335
|
+
): boolean {
|
|
4336
|
+
const callee = unwrapContractExpression(call.expression);
|
|
4337
|
+
if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "on") {
|
|
4338
|
+
return false;
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
const factoryCall = unwrapContractExpression(callee.expression);
|
|
4342
|
+
if (!ts.isCallExpression(factoryCall)) return false;
|
|
4343
|
+
const factory = unwrapContractExpression(factoryCall.expression);
|
|
4344
|
+
if (
|
|
4345
|
+
!ts.isIdentifier(factory) ||
|
|
4346
|
+
!["unique", "uniqueIndex"].includes(factory.text)
|
|
4347
|
+
) {
|
|
4348
|
+
return false;
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
if (call.arguments.length !== 2) return false;
|
|
4352
|
+
const columnNames = call.arguments.map((argument) => {
|
|
4353
|
+
const column = unwrapContractExpression(argument);
|
|
4354
|
+
return ts.isPropertyAccessExpression(column) &&
|
|
4355
|
+
ts.isIdentifier(column.expression) &&
|
|
4356
|
+
column.expression.text === tableName
|
|
4357
|
+
? column.name.text
|
|
4358
|
+
: undefined;
|
|
4359
|
+
});
|
|
4360
|
+
return columnNames.includes("issuer") && columnNames.includes("accountId");
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
async function readPackageJson(
|
|
4364
|
+
targetDir: string,
|
|
4365
|
+
files: string[],
|
|
4366
|
+
): Promise<InspectedPackageJson | undefined> {
|
|
3932
4367
|
if (!files.includes("package.json")) return undefined;
|
|
3933
4368
|
|
|
3934
4369
|
return JSON.parse(
|
|
@@ -4089,7 +4524,7 @@ async function hasBillingEntitlementsProviderWiring(
|
|
|
4089
4524
|
const serverPath = path.dirname(config.paths.server);
|
|
4090
4525
|
const candidateFiles = files.filter(
|
|
4091
4526
|
(file) =>
|
|
4092
|
-
|
|
4527
|
+
isAnalysisSourceFile(file) &&
|
|
4093
4528
|
!isTestSourceFile(file) &&
|
|
4094
4529
|
(file.startsWith(`${infraPath}/`) || file.startsWith(`${serverPath}/`)),
|
|
4095
4530
|
);
|
|
@@ -4105,10 +4540,7 @@ async function hasBillingEntitlementsProviderWiring(
|
|
|
4105
4540
|
}
|
|
4106
4541
|
|
|
4107
4542
|
function isTestSourceFile(file: string): boolean {
|
|
4108
|
-
return (
|
|
4109
|
-
/(?:^|\/)tests\//.test(file) ||
|
|
4110
|
-
/\.(?:test|spec)\.(?:ts|tsx|mts|cts)$/.test(file)
|
|
4111
|
-
);
|
|
4543
|
+
return /(?:^|\/)tests\//.test(file) || isAppTestFile(file);
|
|
4112
4544
|
}
|
|
4113
4545
|
|
|
4114
4546
|
function exportedConstInitializer(
|
|
@@ -4135,8 +4567,8 @@ function featureNameFromContractFile(
|
|
|
4135
4567
|
): string | undefined {
|
|
4136
4568
|
const featuresPath = directoryPath(config.paths.features);
|
|
4137
4569
|
const match = new RegExp(
|
|
4138
|
-
`^${escapeRegExp(featuresPath)}/([^/]+)/contracts
|
|
4139
|
-
).exec(file);
|
|
4570
|
+
`^${escapeRegExp(featuresPath)}/([^/]+)/contracts$`,
|
|
4571
|
+
).exec(stripSourceFileExtension(file));
|
|
4140
4572
|
return match?.[1];
|
|
4141
4573
|
}
|
|
4142
4574
|
|
|
@@ -4153,7 +4585,7 @@ function featureRuntimeFiles(
|
|
|
4153
4585
|
"use-cases",
|
|
4154
4586
|
];
|
|
4155
4587
|
return files.filter((file) => {
|
|
4156
|
-
if (!file
|
|
4588
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
4157
4589
|
return runtimeFolders.some((folder) =>
|
|
4158
4590
|
file.startsWith(`${featuresPath}/${feature}/${folder}/`),
|
|
4159
4591
|
);
|
|
@@ -4169,11 +4601,16 @@ function featureTestFiles(
|
|
|
4169
4601
|
return files.filter((file) => {
|
|
4170
4602
|
if (
|
|
4171
4603
|
!file.startsWith(`${featuresPath}/${feature}/`) ||
|
|
4172
|
-
!file
|
|
4604
|
+
!isAppTestFile(file)
|
|
4173
4605
|
) {
|
|
4174
4606
|
return false;
|
|
4175
4607
|
}
|
|
4176
|
-
return
|
|
4608
|
+
return (
|
|
4609
|
+
!options.policyOnly ||
|
|
4610
|
+
["policy.test", "policy.spec"].includes(
|
|
4611
|
+
path.posix.basename(stripSourceFileExtension(file)),
|
|
4612
|
+
)
|
|
4613
|
+
);
|
|
4177
4614
|
});
|
|
4178
4615
|
}
|
|
4179
4616
|
|
|
@@ -4340,17 +4777,12 @@ async function inspectFeatureArtifactDrift(
|
|
|
4340
4777
|
severity: "warning",
|
|
4341
4778
|
code: "BEIGNET_FEATURE_TEST_PLACEMENT",
|
|
4342
4779
|
file,
|
|
4343
|
-
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
|
|
4780
|
+
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.`,
|
|
4344
4781
|
});
|
|
4345
4782
|
}
|
|
4346
4783
|
|
|
4347
4784
|
for (const file of files) {
|
|
4348
|
-
if (
|
|
4349
|
-
!/\.(?:ts|tsx|mts|cts)$/.test(file) ||
|
|
4350
|
-
/\.d\.(?:ts|mts|cts)$/.test(file)
|
|
4351
|
-
) {
|
|
4352
|
-
continue;
|
|
4353
|
-
}
|
|
4785
|
+
if (!isAnalysisSourceFile(file)) continue;
|
|
4354
4786
|
|
|
4355
4787
|
const misplaced = directFeatureTestMatch(file, featuresPath)
|
|
4356
4788
|
? undefined
|
|
@@ -4365,7 +4797,6 @@ async function inspectFeatureArtifactDrift(
|
|
|
4365
4797
|
}
|
|
4366
4798
|
|
|
4367
4799
|
if (isTestSourceFile(file)) continue;
|
|
4368
|
-
if (!file.endsWith(".ts")) continue;
|
|
4369
4800
|
|
|
4370
4801
|
if (
|
|
4371
4802
|
isNotificationDefinitionFile(file, featuresPath) ||
|
|
@@ -4391,9 +4822,10 @@ function directFeatureTestMatch(
|
|
|
4391
4822
|
file: string,
|
|
4392
4823
|
featuresPath: string,
|
|
4393
4824
|
): RegExpExecArray | null {
|
|
4825
|
+
if (!isAppTestFile(file)) return null;
|
|
4394
4826
|
return new RegExp(
|
|
4395
|
-
`^${escapeRegExp(featuresPath)}/([^/]+)/[^/]+\\.(?:test|spec)
|
|
4396
|
-
).exec(file);
|
|
4827
|
+
`^${escapeRegExp(featuresPath)}/([^/]+)/[^/]+\\.(?:test|spec)$`,
|
|
4828
|
+
).exec(stripSourceFileExtension(file));
|
|
4397
4829
|
}
|
|
4398
4830
|
|
|
4399
4831
|
function isFeatureArtifactFile(
|
|
@@ -4401,9 +4833,10 @@ function isFeatureArtifactFile(
|
|
|
4401
4833
|
featuresPath: string,
|
|
4402
4834
|
folder: "uploads" | "notifications",
|
|
4403
4835
|
): boolean {
|
|
4836
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
4404
4837
|
const match = new RegExp(
|
|
4405
|
-
`^${escapeRegExp(featuresPath)}/[^/]+/${folder}/([^/]+)
|
|
4406
|
-
).exec(file);
|
|
4838
|
+
`^${escapeRegExp(featuresPath)}/[^/]+/${folder}/([^/]+)$`,
|
|
4839
|
+
).exec(stripSourceFileExtension(file));
|
|
4407
4840
|
return Boolean(match && match[1] !== "index");
|
|
4408
4841
|
}
|
|
4409
4842
|
|
|
@@ -4413,7 +4846,7 @@ async function appHasUploadRoute(
|
|
|
4413
4846
|
config: ResolvedBeignetConfig,
|
|
4414
4847
|
): Promise<boolean> {
|
|
4415
4848
|
for (const file of files) {
|
|
4416
|
-
if (!file
|
|
4849
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
4417
4850
|
if (!isRouteServerOrInfraFile(file, config)) continue;
|
|
4418
4851
|
|
|
4419
4852
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
@@ -4967,11 +5400,17 @@ async function readFeatureWorkflowRegistries(
|
|
|
4967
5400
|
for (const kind of kinds) {
|
|
4968
5401
|
const folder = kind === "events" ? "domain/events" : kind;
|
|
4969
5402
|
const pattern = new RegExp(
|
|
4970
|
-
`^${escapeRegExp(featuresPath)}/[^/]+/${escapeRegExp(folder)}/index
|
|
5403
|
+
`^${escapeRegExp(featuresPath)}/[^/]+/${escapeRegExp(folder)}/index$`,
|
|
4971
5404
|
);
|
|
4972
5405
|
|
|
4973
5406
|
for (const file of files) {
|
|
4974
|
-
if (
|
|
5407
|
+
if (
|
|
5408
|
+
!isAnalysisSourceFile(file) ||
|
|
5409
|
+
isWorkflowTestFile(file) ||
|
|
5410
|
+
!pattern.test(stripSourceFileExtension(file))
|
|
5411
|
+
) {
|
|
5412
|
+
continue;
|
|
5413
|
+
}
|
|
4975
5414
|
|
|
4976
5415
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
4977
5416
|
const memberFiles = exportedMemberFiles(source, file, files);
|
|
@@ -5031,8 +5470,7 @@ async function readRuntimeManifestIdentifiers(
|
|
|
5031
5470
|
const manifests: RuntimeManifestIdentifierSets[] = [];
|
|
5032
5471
|
|
|
5033
5472
|
for (const file of files) {
|
|
5034
|
-
if (!file
|
|
5035
|
-
if (file.endsWith(".d.ts") || isWorkflowTestFile(file)) continue;
|
|
5473
|
+
if (!isAnalysisSourceFile(file) || isWorkflowTestFile(file)) continue;
|
|
5036
5474
|
|
|
5037
5475
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5038
5476
|
if (!source.includes("defineRuntimeManifest")) continue;
|
|
@@ -5169,12 +5607,18 @@ async function listenedEventNames(
|
|
|
5169
5607
|
): Promise<Set<string>> {
|
|
5170
5608
|
const featuresPath = directoryPath(config.paths.features);
|
|
5171
5609
|
const listenerPattern = new RegExp(
|
|
5172
|
-
`^${escapeRegExp(featuresPath)}/[^/]+/listeners/[^/]
|
|
5610
|
+
`^${escapeRegExp(featuresPath)}/[^/]+/listeners/[^/]+$`,
|
|
5173
5611
|
);
|
|
5174
5612
|
const listened = new Set<string>();
|
|
5175
5613
|
|
|
5176
5614
|
for (const file of files) {
|
|
5177
|
-
if (
|
|
5615
|
+
if (
|
|
5616
|
+
!isAnalysisSourceFile(file) ||
|
|
5617
|
+
isWorkflowTestFile(file) ||
|
|
5618
|
+
!listenerPattern.test(stripSourceFileExtension(file))
|
|
5619
|
+
) {
|
|
5620
|
+
continue;
|
|
5621
|
+
}
|
|
5178
5622
|
|
|
5179
5623
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5180
5624
|
const callPattern = /\bdefineListener\s*\(/g;
|
|
@@ -5223,8 +5667,7 @@ async function listenerWiringReferences(
|
|
|
5223
5667
|
const listenerRegistryFile = listenersFilePath(config);
|
|
5224
5668
|
|
|
5225
5669
|
for (const file of files) {
|
|
5226
|
-
if (!file
|
|
5227
|
-
if (file.endsWith(".d.ts") || isWorkflowTestFile(file)) continue;
|
|
5670
|
+
if (!isAnalysisSourceFile(file) || isWorkflowTestFile(file)) continue;
|
|
5228
5671
|
|
|
5229
5672
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5230
5673
|
const namedImports = parseNamedImportSources(source);
|
|
@@ -5233,7 +5676,7 @@ async function listenerWiringReferences(
|
|
|
5233
5676
|
source,
|
|
5234
5677
|
ts.ScriptTarget.Latest,
|
|
5235
5678
|
true,
|
|
5236
|
-
file
|
|
5679
|
+
sourceFileScriptKind(file),
|
|
5237
5680
|
);
|
|
5238
5681
|
const registerListenersBindings = importedRegisterListenersBindings(
|
|
5239
5682
|
source,
|
|
@@ -5693,10 +6136,7 @@ function callReferencesCentralListenerRegistry(args: {
|
|
|
5693
6136
|
|
|
5694
6137
|
function isWorkflowTestFile(file: string): boolean {
|
|
5695
6138
|
return (
|
|
5696
|
-
file.
|
|
5697
|
-
file.endsWith(".test.tsx") ||
|
|
5698
|
-
file.startsWith("tests/") ||
|
|
5699
|
-
file.includes("/tests/")
|
|
6139
|
+
isAppTestFile(file) || file.startsWith("tests/") || file.includes("/tests/")
|
|
5700
6140
|
);
|
|
5701
6141
|
}
|
|
5702
6142
|
|
|
@@ -5711,13 +6151,7 @@ async function inspectServerlessFootguns(
|
|
|
5711
6151
|
const cronDirectory = path.posix.join(routeDirectory, "cron");
|
|
5712
6152
|
|
|
5713
6153
|
for (const file of files) {
|
|
5714
|
-
if (
|
|
5715
|
-
!file.endsWith(".ts") ||
|
|
5716
|
-
file.endsWith(".test.ts") ||
|
|
5717
|
-
file.endsWith(".d.ts")
|
|
5718
|
-
) {
|
|
5719
|
-
continue;
|
|
5720
|
-
}
|
|
6154
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
5721
6155
|
|
|
5722
6156
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5723
6157
|
const isInfraOrServerFile = isRuntimeFile(file, config);
|
|
@@ -5863,9 +6297,10 @@ function fileWithinDirectory(file: string, directory: string): boolean {
|
|
|
5863
6297
|
}
|
|
5864
6298
|
|
|
5865
6299
|
function isProviderLifecycleFile(file: string, source?: string): boolean {
|
|
6300
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
5866
6301
|
return (
|
|
5867
|
-
/(^|\/)provider(s)
|
|
5868
|
-
/(^|\/)[^/]*provider[^/]
|
|
6302
|
+
/(^|\/)provider(s)?$/.test(sourcePath) ||
|
|
6303
|
+
/(^|\/)[^/]*provider[^/]*$/.test(sourcePath) ||
|
|
5869
6304
|
/(^|\/)providers?\//.test(file) ||
|
|
5870
6305
|
(source !== undefined && containsCallExpression(source, "createProvider"))
|
|
5871
6306
|
);
|
|
@@ -5876,8 +6311,9 @@ function isNextRouteFile(file: string, config: ResolvedBeignetConfig): boolean {
|
|
|
5876
6311
|
|
|
5877
6312
|
return (
|
|
5878
6313
|
file.startsWith(`${routesPath}/`) &&
|
|
5879
|
-
file
|
|
5880
|
-
|
|
6314
|
+
isSourceFileNamed(file, "route") &&
|
|
6315
|
+
[".js", ".jsx", ".ts", ".tsx"].includes(path.posix.extname(file)) &&
|
|
6316
|
+
!isAppTestFile(file)
|
|
5881
6317
|
);
|
|
5882
6318
|
}
|
|
5883
6319
|
|
|
@@ -6000,6 +6436,10 @@ async function inspectPortProviderDrift(
|
|
|
6000
6436
|
const portsSource = files.includes(config.paths.ports)
|
|
6001
6437
|
? await readFile(path.join(targetDir, config.paths.ports), "utf8")
|
|
6002
6438
|
: "";
|
|
6439
|
+
const sharedPorts =
|
|
6440
|
+
config.workspace.packages.length > 0
|
|
6441
|
+
? await analyzePortWiringFiles({ targetDir, files, config })
|
|
6442
|
+
: undefined;
|
|
6003
6443
|
const packageJson = files.includes("package.json")
|
|
6004
6444
|
? await readPackageJson(targetDir, files)
|
|
6005
6445
|
: undefined;
|
|
@@ -6023,7 +6463,12 @@ async function inspectPortProviderDrift(
|
|
|
6023
6463
|
|
|
6024
6464
|
for (const expected of providerPortRequirements) {
|
|
6025
6465
|
if (!installedPackages.has(expected.packageName)) continue;
|
|
6026
|
-
if (
|
|
6466
|
+
if (
|
|
6467
|
+
sharedPorts
|
|
6468
|
+
? sharedPorts.declared.has(expected.portName)
|
|
6469
|
+
: portsSource.includes(`${expected.portName}:`)
|
|
6470
|
+
)
|
|
6471
|
+
continue;
|
|
6027
6472
|
diagnostics.push({
|
|
6028
6473
|
severity: "warning",
|
|
6029
6474
|
code: "BEIGNET_PROVIDER_PORT_MISSING",
|
|
@@ -7812,7 +8257,13 @@ async function inspectFeatureRouteRegistration(
|
|
|
7812
8257
|
const feature = featureNameFromContractFile(contract.file, config);
|
|
7813
8258
|
if (!feature) continue;
|
|
7814
8259
|
|
|
7815
|
-
const
|
|
8260
|
+
const routeStem = `${featuresPath}/${feature}/routes`;
|
|
8261
|
+
const routeFile =
|
|
8262
|
+
files.find(
|
|
8263
|
+
(file) =>
|
|
8264
|
+
isAnalysisSourceFile(file) &&
|
|
8265
|
+
stripSourceFileExtension(file) === routeStem,
|
|
8266
|
+
) ?? `${routeStem}${path.posix.extname(contract.file) || ".ts"}`;
|
|
7816
8267
|
const featureRouteGroups = routeGroups.filter(
|
|
7817
8268
|
(routeGroup) => routeGroup.file === routeFile,
|
|
7818
8269
|
);
|
|
@@ -7902,13 +8353,14 @@ async function readFeatureRouteGroups(
|
|
|
7902
8353
|
): Promise<FeatureRouteGroup[]> {
|
|
7903
8354
|
const featuresPath = `${directoryPath(config.paths.features)}/`;
|
|
7904
8355
|
const routeFiles = files.filter(
|
|
7905
|
-
(file) =>
|
|
8356
|
+
(file) =>
|
|
8357
|
+
file.startsWith(featuresPath) && isSourceFileNamed(file, "routes"),
|
|
7906
8358
|
);
|
|
7907
8359
|
const routeGroups: FeatureRouteGroup[] = [];
|
|
7908
8360
|
|
|
7909
8361
|
for (const file of routeFiles) {
|
|
7910
8362
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
7911
|
-
const imports = parseNamedImports(source, config, file);
|
|
8363
|
+
const imports = parseNamedImports(source, config, file, files);
|
|
7912
8364
|
routeGroups.push(...parseFeatureRouteGroups(source, file, imports));
|
|
7913
8365
|
}
|
|
7914
8366
|
|
|
@@ -8071,8 +8523,7 @@ function findRouteGroupContract(
|
|
|
8071
8523
|
contract.exportName === routeGroupContract.exportName &&
|
|
8072
8524
|
(!routeGroupContract.file ||
|
|
8073
8525
|
contract.file === routeGroupContract.file ||
|
|
8074
|
-
contract.file ===
|
|
8075
|
-
routeGroupContract.file.replace(/\.ts$/, "/index.ts")),
|
|
8526
|
+
contract.file === directoryIndexSourceFile(routeGroupContract.file)),
|
|
8076
8527
|
);
|
|
8077
8528
|
}
|
|
8078
8529
|
|
|
@@ -9449,7 +9900,7 @@ async function readContractLists(
|
|
|
9449
9900
|
|
|
9450
9901
|
for (const file of contractFiles) {
|
|
9451
9902
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
9452
|
-
const imports = parseNamedImports(source, config, file);
|
|
9903
|
+
const imports = parseNamedImports(source, config, file, files);
|
|
9453
9904
|
for (const list of parseContractLists(source, file, imports)) {
|
|
9454
9905
|
lists.set(contractListKey(list.file, list.name), list);
|
|
9455
9906
|
}
|
|
@@ -9466,18 +9917,18 @@ function contractSourceFiles(
|
|
|
9466
9917
|
if (usesFeatureOwnedContracts(config)) {
|
|
9467
9918
|
return files.filter(
|
|
9468
9919
|
(file) =>
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
).test(
|
|
9920
|
+
isAnalysisSourceFile(file) &&
|
|
9921
|
+
!isAppTestFile(file) &&
|
|
9922
|
+
new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts$`).test(
|
|
9923
|
+
stripSourceFileExtension(file),
|
|
9924
|
+
),
|
|
9472
9925
|
);
|
|
9473
9926
|
}
|
|
9474
9927
|
|
|
9475
|
-
return files.filter(
|
|
9476
|
-
(file)
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
!file.endsWith(".test.ts"),
|
|
9480
|
-
);
|
|
9928
|
+
return files.filter((file) => {
|
|
9929
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
9930
|
+
return stripSourceFileExtension(file).startsWith(`${contractsPath}/`);
|
|
9931
|
+
});
|
|
9481
9932
|
}
|
|
9482
9933
|
|
|
9483
9934
|
function contractListSourceFiles(
|
|
@@ -9487,22 +9938,23 @@ function contractListSourceFiles(
|
|
|
9487
9938
|
const contractsPath = directoryPath(config.paths.contracts);
|
|
9488
9939
|
if (usesFeatureOwnedContracts(config)) {
|
|
9489
9940
|
return files.filter((file) => {
|
|
9490
|
-
if (!file
|
|
9491
|
-
|
|
9492
|
-
if (
|
|
9941
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
9942
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
9943
|
+
if (sourcePath === `${contractsPath}/index`) return true;
|
|
9944
|
+
if (sourcePath === `${contractsPath}/contracts`) return true;
|
|
9493
9945
|
return new RegExp(
|
|
9494
|
-
`^${escapeRegExp(contractsPath)}/[^/]+/contracts
|
|
9495
|
-
).test(
|
|
9946
|
+
`^${escapeRegExp(contractsPath)}/[^/]+/contracts$`,
|
|
9947
|
+
).test(sourcePath);
|
|
9496
9948
|
});
|
|
9497
9949
|
}
|
|
9498
9950
|
|
|
9499
|
-
return files.filter(
|
|
9500
|
-
(file)
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
);
|
|
9951
|
+
return files.filter((file) => {
|
|
9952
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
9953
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
9954
|
+
return (
|
|
9955
|
+
sourcePath === contractsPath || sourcePath.startsWith(`${contractsPath}/`)
|
|
9956
|
+
);
|
|
9957
|
+
});
|
|
9506
9958
|
}
|
|
9507
9959
|
|
|
9508
9960
|
function parseContractLists(
|
|
@@ -9556,7 +10008,7 @@ function resolveContractList(
|
|
|
9556
10008
|
if (seen.has(key)) return new Set();
|
|
9557
10009
|
const list =
|
|
9558
10010
|
lists.get(key) ??
|
|
9559
|
-
lists.get(contractListKey(file
|
|
10011
|
+
lists.get(contractListKey(directoryIndexSourceFile(file), name));
|
|
9560
10012
|
if (!list) return undefined;
|
|
9561
10013
|
|
|
9562
10014
|
seen.add(key);
|
|
@@ -10146,7 +10598,7 @@ function nextRoutePath(file: string, routeRoot: string): string {
|
|
|
10146
10598
|
const normalizedRouteRoot = directoryPath(routeRoot);
|
|
10147
10599
|
const routeSegments = file
|
|
10148
10600
|
.slice(normalizedRouteRoot.length + 1)
|
|
10149
|
-
.replace(/\/route\.ts$/, "")
|
|
10601
|
+
.replace(/\/route\.(?:js|jsx|ts|tsx)$/, "")
|
|
10150
10602
|
.split("/")
|
|
10151
10603
|
.filter(Boolean);
|
|
10152
10604
|
const apiPath = routeSegments.map(nextSegmentToContractSegment).join("/");
|