@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/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,6 +12,16 @@ 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";
|
|
15
|
+
import {
|
|
16
|
+
analysisSourceFileExtensions,
|
|
17
|
+
isAnalysisSourceFile,
|
|
18
|
+
isAnalysisSourceFileExtension,
|
|
19
|
+
isJavaScriptSourceFile,
|
|
20
|
+
javascriptAnalysisSourceFileExtensions,
|
|
21
|
+
sourceFileScriptKind,
|
|
22
|
+
stripSourceFileExtension,
|
|
23
|
+
typescriptAnalysisSourceFileExtensions,
|
|
24
|
+
} from "./analysis/source-files.js";
|
|
13
25
|
import { exportedVariableDeclarations } from "./analysis/source-index.js";
|
|
14
26
|
import { createPainter } from "./ansi.js";
|
|
15
27
|
import type { DatabaseSchemaDialect, DatabaseSchemaTable } from "./choices.js";
|
|
@@ -86,6 +98,7 @@ import {
|
|
|
86
98
|
legacyGeneratedPackageScripts,
|
|
87
99
|
} from "./templates/shared.js";
|
|
88
100
|
import { testSupportTemplateFiles } from "./templates/testing.js";
|
|
101
|
+
import { isAppTestFile } from "./test-discovery.js";
|
|
89
102
|
import { getCliVersion } from "./version.js";
|
|
90
103
|
|
|
91
104
|
type InspectAppOptions = {
|
|
@@ -946,7 +959,7 @@ function parseContracts(source: string, file: string): ParsedContract[] {
|
|
|
946
959
|
source,
|
|
947
960
|
ts.ScriptTarget.Latest,
|
|
948
961
|
true,
|
|
949
|
-
|
|
962
|
+
sourceFileScriptKind(file),
|
|
950
963
|
);
|
|
951
964
|
const assignments = variableAssignments(sourceFile);
|
|
952
965
|
const assignmentsByName = new Map(
|
|
@@ -1556,6 +1569,7 @@ function normalizeContractPath(routePath: string): string {
|
|
|
1556
1569
|
async function readRouteExports(
|
|
1557
1570
|
targetDir: string,
|
|
1558
1571
|
routeFiles: string[],
|
|
1572
|
+
files: string[],
|
|
1559
1573
|
config: ResolvedBeignetConfig,
|
|
1560
1574
|
): Promise<Map<string, RouteExport[]>> {
|
|
1561
1575
|
const routeExports = new Map<string, RouteExport[]>();
|
|
@@ -1563,7 +1577,10 @@ async function readRouteExports(
|
|
|
1563
1577
|
for (const file of routeFiles) {
|
|
1564
1578
|
const routePath = nextRoutePath(file, config.paths.routes);
|
|
1565
1579
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
1566
|
-
routeExports.set(
|
|
1580
|
+
routeExports.set(
|
|
1581
|
+
file,
|
|
1582
|
+
parseRouteExports(source, file, routePath, files, config),
|
|
1583
|
+
);
|
|
1567
1584
|
}
|
|
1568
1585
|
|
|
1569
1586
|
return routeExports;
|
|
@@ -1573,6 +1590,7 @@ function parseRouteExports(
|
|
|
1573
1590
|
source: string,
|
|
1574
1591
|
handlerFile: string,
|
|
1575
1592
|
routePath: string,
|
|
1593
|
+
files: string[],
|
|
1576
1594
|
config: ResolvedBeignetConfig,
|
|
1577
1595
|
): RouteExport[] {
|
|
1578
1596
|
const exports: RouteExport[] = [];
|
|
@@ -1581,9 +1599,14 @@ function parseRouteExports(
|
|
|
1581
1599
|
source,
|
|
1582
1600
|
ts.ScriptTarget.Latest,
|
|
1583
1601
|
true,
|
|
1584
|
-
|
|
1602
|
+
sourceFileScriptKind(handlerFile),
|
|
1603
|
+
);
|
|
1604
|
+
const imports = parseRouteNamedImports(
|
|
1605
|
+
sourceFile,
|
|
1606
|
+
config,
|
|
1607
|
+
handlerFile,
|
|
1608
|
+
files,
|
|
1585
1609
|
);
|
|
1586
|
-
const imports = parseRouteNamedImports(sourceFile, config, handlerFile);
|
|
1587
1610
|
|
|
1588
1611
|
for (const statement of sourceFile.statements) {
|
|
1589
1612
|
if (
|
|
@@ -1656,6 +1679,7 @@ function parseRouteNamedImports(
|
|
|
1656
1679
|
sourceFile: ts.SourceFile,
|
|
1657
1680
|
config: ResolvedBeignetConfig,
|
|
1658
1681
|
handlerFile: string,
|
|
1682
|
+
files: string[],
|
|
1659
1683
|
): Map<string, { importedName: string; contractFile?: string }> {
|
|
1660
1684
|
const imports = new Map<
|
|
1661
1685
|
string,
|
|
@@ -1677,6 +1701,7 @@ function parseRouteNamedImports(
|
|
|
1677
1701
|
statement.moduleSpecifier.text,
|
|
1678
1702
|
config,
|
|
1679
1703
|
handlerFile,
|
|
1704
|
+
files,
|
|
1680
1705
|
);
|
|
1681
1706
|
for (const element of statement.importClause.namedBindings.elements) {
|
|
1682
1707
|
if (element.isTypeOnly) continue;
|
|
@@ -1757,6 +1782,7 @@ function parseNamedImports(
|
|
|
1757
1782
|
source: string,
|
|
1758
1783
|
config: ResolvedBeignetConfig,
|
|
1759
1784
|
importerFile?: string,
|
|
1785
|
+
files?: string[],
|
|
1760
1786
|
): Map<
|
|
1761
1787
|
string,
|
|
1762
1788
|
{
|
|
@@ -1779,6 +1805,7 @@ function parseNamedImports(
|
|
|
1779
1805
|
sourcePath,
|
|
1780
1806
|
config,
|
|
1781
1807
|
importerFile,
|
|
1808
|
+
files,
|
|
1782
1809
|
);
|
|
1783
1810
|
for (const member of match[1].split(",")) {
|
|
1784
1811
|
const parsed = parseImportMember(member);
|
|
@@ -1849,9 +1876,7 @@ function sourceFileFromImport(
|
|
|
1849
1876
|
files?: string[],
|
|
1850
1877
|
): string | undefined {
|
|
1851
1878
|
const resolveCandidate = (candidate: string) => {
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
const candidates = [`${candidate}.ts`, `${candidate}/index.ts`];
|
|
1879
|
+
const candidates = sourceFileSpecifierCandidates(candidate, importerFile);
|
|
1855
1880
|
return files
|
|
1856
1881
|
? candidates.find((file) => files.includes(file))
|
|
1857
1882
|
: candidates[0];
|
|
@@ -1870,33 +1895,76 @@ function sourceFileFromImport(
|
|
|
1870
1895
|
return undefined;
|
|
1871
1896
|
}
|
|
1872
1897
|
|
|
1898
|
+
function sourceFileCandidates(base: string, importerFile?: string): string[] {
|
|
1899
|
+
const extensions =
|
|
1900
|
+
importerFile && isJavaScriptSourceFile(importerFile)
|
|
1901
|
+
? [
|
|
1902
|
+
...javascriptAnalysisSourceFileExtensions,
|
|
1903
|
+
...typescriptAnalysisSourceFileExtensions,
|
|
1904
|
+
]
|
|
1905
|
+
: analysisSourceFileExtensions;
|
|
1906
|
+
return [
|
|
1907
|
+
...extensions.map((extension) => `${base}${extension}`),
|
|
1908
|
+
...extensions.map((extension) => `${base}/index${extension}`),
|
|
1909
|
+
];
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
function sourceFileSpecifierCandidates(
|
|
1913
|
+
candidate: string,
|
|
1914
|
+
importerFile?: string,
|
|
1915
|
+
): string[] {
|
|
1916
|
+
const extension = path.posix.extname(candidate).toLowerCase();
|
|
1917
|
+
if (!extension) return sourceFileCandidates(candidate, importerFile);
|
|
1918
|
+
if (!isAnalysisSourceFileExtension(extension)) return [candidate];
|
|
1919
|
+
|
|
1920
|
+
const sourceExtensions =
|
|
1921
|
+
extension === ".js"
|
|
1922
|
+
? [".ts", ".tsx"]
|
|
1923
|
+
: extension === ".jsx"
|
|
1924
|
+
? [".tsx"]
|
|
1925
|
+
: extension === ".mjs"
|
|
1926
|
+
? [".mts"]
|
|
1927
|
+
: extension === ".cjs"
|
|
1928
|
+
? [".cts"]
|
|
1929
|
+
: [];
|
|
1930
|
+
const sourceCandidates = sourceExtensions.map(
|
|
1931
|
+
(sourceExtension) =>
|
|
1932
|
+
`${candidate.slice(0, -extension.length)}${sourceExtension}`,
|
|
1933
|
+
);
|
|
1934
|
+
const directCandidates =
|
|
1935
|
+
importerFile && isJavaScriptSourceFile(importerFile)
|
|
1936
|
+
? [candidate, ...sourceCandidates]
|
|
1937
|
+
: [...sourceCandidates, candidate];
|
|
1938
|
+
return [
|
|
1939
|
+
...new Set([
|
|
1940
|
+
...directCandidates,
|
|
1941
|
+
...directCandidates.map(directoryIndexSourceFile),
|
|
1942
|
+
]),
|
|
1943
|
+
];
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function isSourceFileNamed(file: string, name: string): boolean {
|
|
1947
|
+
return (
|
|
1948
|
+
isAnalysisSourceFile(file) &&
|
|
1949
|
+
path.posix.basename(stripSourceFileExtension(file)) === name
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
function directoryIndexSourceFile(file: string): string {
|
|
1954
|
+
const extension = path.posix.extname(file);
|
|
1955
|
+
return `${stripSourceFileExtension(file)}/index${extension}`;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1873
1958
|
function contractFileFromImport(
|
|
1874
1959
|
sourcePath: string,
|
|
1875
1960
|
config: ResolvedBeignetConfig,
|
|
1876
1961
|
importerFile?: string,
|
|
1962
|
+
files?: string[],
|
|
1877
1963
|
): string | undefined {
|
|
1878
1964
|
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;
|
|
1965
|
+
const candidates = sourceFileSpecifierCandidates(candidate, importerFile);
|
|
1966
|
+
if (!files) return candidates[0];
|
|
1967
|
+
return candidates.find((file) => files.includes(file)) ?? candidates[0];
|
|
1900
1968
|
};
|
|
1901
1969
|
const contractsPath = directoryPath(config.paths.contracts);
|
|
1902
1970
|
const aliasPrefix = `@/${contractsPath}/`;
|
|
@@ -1905,7 +1973,7 @@ function contractFileFromImport(
|
|
|
1905
1973
|
const relativeExact = contractsPath;
|
|
1906
1974
|
|
|
1907
1975
|
if (sourcePath === aliasExact || sourcePath === relativeExact) {
|
|
1908
|
-
return `${contractsPath}/index
|
|
1976
|
+
return resolveCandidate(`${contractsPath}/index`);
|
|
1909
1977
|
}
|
|
1910
1978
|
if (sourcePath.startsWith(aliasPrefix)) {
|
|
1911
1979
|
return resolveCandidate(sourcePath.slice("@/".length));
|
|
@@ -2002,9 +2070,14 @@ async function inspectRouteSurface(
|
|
|
2002
2070
|
const routeFiles = files.filter(
|
|
2003
2071
|
(file) =>
|
|
2004
2072
|
file.startsWith(`${directoryPath(config.paths.routes)}/`) &&
|
|
2005
|
-
file
|
|
2073
|
+
isNextRouteFile(file, config),
|
|
2074
|
+
);
|
|
2075
|
+
const routeExports = await readRouteExports(
|
|
2076
|
+
targetDir,
|
|
2077
|
+
routeFiles,
|
|
2078
|
+
files,
|
|
2079
|
+
config,
|
|
2006
2080
|
);
|
|
2007
|
-
const routeExports = await readRouteExports(targetDir, routeFiles, config);
|
|
2008
2081
|
return matchRoutes(contracts, routeExports);
|
|
2009
2082
|
}
|
|
2010
2083
|
|
|
@@ -2076,8 +2149,7 @@ function findContracts(
|
|
|
2076
2149
|
contract.exportName === routeExport.contractRef &&
|
|
2077
2150
|
(!routeExport.contractFile ||
|
|
2078
2151
|
contract.file === routeExport.contractFile ||
|
|
2079
|
-
contract.file ===
|
|
2080
|
-
routeExport.contractFile.replace(/\.ts$/, "/index.ts")),
|
|
2152
|
+
contract.file === directoryIndexSourceFile(routeExport.contractFile)),
|
|
2081
2153
|
);
|
|
2082
2154
|
return contract ? [contract] : [];
|
|
2083
2155
|
}
|
|
@@ -2521,7 +2593,12 @@ async function inspectCanonicalConformance(
|
|
|
2521
2593
|
|
|
2522
2594
|
const featureRoutesPath = `${featuresPath}/`;
|
|
2523
2595
|
for (const file of files) {
|
|
2524
|
-
if (
|
|
2596
|
+
if (
|
|
2597
|
+
!file.startsWith(featureRoutesPath) ||
|
|
2598
|
+
!isAnalysisSourceFile(file) ||
|
|
2599
|
+
isAppTestFile(file) ||
|
|
2600
|
+
!stripSourceFileExtension(file).endsWith("/routes")
|
|
2601
|
+
) {
|
|
2525
2602
|
continue;
|
|
2526
2603
|
}
|
|
2527
2604
|
|
|
@@ -2910,7 +2987,7 @@ async function inspectProductionReadiness(
|
|
|
2910
2987
|
let hasSecurityHeadersHook = false;
|
|
2911
2988
|
|
|
2912
2989
|
for (const file of files) {
|
|
2913
|
-
if (!file
|
|
2990
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
2914
2991
|
|
|
2915
2992
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2916
2993
|
const routeServerOrInfra = isRouteServerOrInfraFile(file, config);
|
|
@@ -2937,7 +3014,7 @@ async function inspectProductionReadiness(
|
|
|
2937
3014
|
|
|
2938
3015
|
if (
|
|
2939
3016
|
fileWithinDirectory(file, cronDirectory) &&
|
|
2940
|
-
file
|
|
3017
|
+
isNextRouteFile(file, config) &&
|
|
2941
3018
|
!/\bCRON_SECRET\b/.test(source) &&
|
|
2942
3019
|
!/\bcreateOutboxDrainRoute\s*\(/.test(source) &&
|
|
2943
3020
|
!/\bcreateScheduleRoute\s*\(/.test(source)
|
|
@@ -3239,6 +3316,25 @@ async function inspectProductionReadiness(
|
|
|
3239
3316
|
"@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
3317
|
});
|
|
3241
3318
|
}
|
|
3319
|
+
|
|
3320
|
+
if (
|
|
3321
|
+
(await betterAuthRequiresIssuerSchema(targetDir, packageJson)) &&
|
|
3322
|
+
(await anyFileContains(
|
|
3323
|
+
targetDir,
|
|
3324
|
+
configFiles,
|
|
3325
|
+
"drizzleAdapter",
|
|
3326
|
+
sourceCache,
|
|
3327
|
+
)) &&
|
|
3328
|
+
!(await hasBetterAuthIssuerSchema(targetDir, files, sourceCache))
|
|
3329
|
+
) {
|
|
3330
|
+
diagnostics.push({
|
|
3331
|
+
severity: "warning",
|
|
3332
|
+
code: "BEIGNET_BETTER_AUTH_ISSUER_SCHEMA_MISSING",
|
|
3333
|
+
file: schemaDir,
|
|
3334
|
+
message:
|
|
3335
|
+
"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.",
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3242
3338
|
}
|
|
3243
3339
|
|
|
3244
3340
|
const installedReadinessProviders = [...installedPackages].filter(
|
|
@@ -3284,7 +3380,7 @@ async function uploadHasExplicitSizeLimit(
|
|
|
3284
3380
|
);
|
|
3285
3381
|
if (!importedName) continue;
|
|
3286
3382
|
|
|
3287
|
-
const importedFile =
|
|
3383
|
+
const importedFile = sourceFileFromImport(moduleName, file, files);
|
|
3288
3384
|
if (!importedFile) continue;
|
|
3289
3385
|
|
|
3290
3386
|
const importedSource = await readCachedSource(
|
|
@@ -3339,20 +3435,6 @@ function exportedObjectHasProperty(
|
|
|
3339
3435
|
return new RegExp(`\\b${escapeRegExp(propertyName)}\\s*:`).test(initializer);
|
|
3340
3436
|
}
|
|
3341
3437
|
|
|
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
3438
|
async function hasReadinessCheck(
|
|
3357
3439
|
targetDir: string,
|
|
3358
3440
|
files: string[],
|
|
@@ -3360,10 +3442,18 @@ async function hasReadinessCheck(
|
|
|
3360
3442
|
sourceCache: Map<string, string>,
|
|
3361
3443
|
): Promise<boolean> {
|
|
3362
3444
|
const routesDir = directoryPath(config.paths.routes);
|
|
3363
|
-
if (
|
|
3445
|
+
if (
|
|
3446
|
+
files.some(
|
|
3447
|
+
(file) =>
|
|
3448
|
+
path.posix.dirname(file) === `${routesDir}/ready` &&
|
|
3449
|
+
isNextRouteFile(file, config),
|
|
3450
|
+
)
|
|
3451
|
+
) {
|
|
3452
|
+
return true;
|
|
3453
|
+
}
|
|
3364
3454
|
|
|
3365
3455
|
for (const file of files) {
|
|
3366
|
-
if (!file
|
|
3456
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
3367
3457
|
if (!isRouteServerOrInfraFile(file, config)) continue;
|
|
3368
3458
|
|
|
3369
3459
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
@@ -3905,10 +3995,11 @@ function isFeatureUploadDefinition(
|
|
|
3905
3995
|
file: string,
|
|
3906
3996
|
config: ResolvedBeignetConfig,
|
|
3907
3997
|
): boolean {
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3998
|
+
return isFeatureArtifactFile(
|
|
3999
|
+
file,
|
|
4000
|
+
directoryPath(config.paths.features),
|
|
4001
|
+
"uploads",
|
|
4002
|
+
);
|
|
3912
4003
|
}
|
|
3913
4004
|
|
|
3914
4005
|
function operationalConfigFiles(
|
|
@@ -3918,17 +4009,263 @@ function operationalConfigFiles(
|
|
|
3918
4009
|
return providerOperationalConfigFiles(files, config);
|
|
3919
4010
|
}
|
|
3920
4011
|
|
|
3921
|
-
|
|
4012
|
+
type InspectedPackageJson = {
|
|
4013
|
+
dependencies?: Record<string, string>;
|
|
4014
|
+
devDependencies?: Record<string, string>;
|
|
4015
|
+
peerDependencies?: Record<string, string>;
|
|
4016
|
+
};
|
|
4017
|
+
|
|
4018
|
+
function declaredBetterAuthMayRequireIssuerSchema(
|
|
4019
|
+
packageJson: InspectedPackageJson | undefined,
|
|
4020
|
+
): boolean {
|
|
4021
|
+
const version =
|
|
4022
|
+
packageJson?.dependencies?.["better-auth"] ??
|
|
4023
|
+
packageJson?.devDependencies?.["better-auth"] ??
|
|
4024
|
+
packageJson?.peerDependencies?.["better-auth"];
|
|
4025
|
+
if (!version) return false;
|
|
4026
|
+
|
|
4027
|
+
const normalized = version.trim();
|
|
4028
|
+
const workspaceRange = normalized.startsWith("workspace:")
|
|
4029
|
+
? normalized.slice("workspace:".length)
|
|
4030
|
+
: normalized;
|
|
4031
|
+
const npmAlias = /^npm:better-auth@(.+)$/.exec(workspaceRange);
|
|
4032
|
+
const candidate = npmAlias?.[1] ?? workspaceRange;
|
|
4033
|
+
const range = semver.validRange(candidate);
|
|
4034
|
+
|
|
4035
|
+
// Tags, catalogs, local paths, and other non-semver declarations can resolve
|
|
4036
|
+
// to 1.7. Prefer a migration warning until an installed version is readable.
|
|
4037
|
+
if (!range) return true;
|
|
4038
|
+
|
|
4039
|
+
return semver.intersects(range, ">=1.7.0");
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
async function betterAuthRequiresIssuerSchema(
|
|
4043
|
+
targetDir: string,
|
|
4044
|
+
packageJson: InspectedPackageJson | undefined,
|
|
4045
|
+
): Promise<boolean> {
|
|
4046
|
+
const installed = await readInstalledBetterAuthPackageJson(targetDir);
|
|
4047
|
+
if (installed?.version) {
|
|
4048
|
+
const version = semver.valid(installed.version.trim());
|
|
4049
|
+
if (version) return semver.gte(version, "1.7.0");
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
return declaredBetterAuthMayRequireIssuerSchema(packageJson);
|
|
4053
|
+
}
|
|
4054
|
+
|
|
4055
|
+
async function readInstalledBetterAuthPackageJson(
|
|
4056
|
+
targetDir: string,
|
|
4057
|
+
): Promise<{ name?: string; version?: string } | undefined> {
|
|
4058
|
+
const nodeModulesPackage =
|
|
4059
|
+
await readBetterAuthPackageFromNodeModules(targetDir);
|
|
4060
|
+
if (nodeModulesPackage) return nodeModulesPackage;
|
|
4061
|
+
|
|
4062
|
+
const requireFromTarget = createRequire(path.join(targetDir, "package.json"));
|
|
4063
|
+
const packageJsonPath = resolveModuleFromTarget(
|
|
4064
|
+
requireFromTarget,
|
|
4065
|
+
"better-auth/package.json",
|
|
4066
|
+
);
|
|
4067
|
+
if (packageJsonPath) {
|
|
4068
|
+
return readJsonIfExists(packageJsonPath);
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
const entryPath = resolveModuleFromTarget(requireFromTarget, "better-auth");
|
|
4072
|
+
if (!entryPath) return undefined;
|
|
4073
|
+
|
|
4074
|
+
let directory = path.dirname(entryPath);
|
|
4075
|
+
while (true) {
|
|
4076
|
+
const candidate = await readJsonIfExists<{
|
|
4077
|
+
name?: string;
|
|
4078
|
+
version?: string;
|
|
4079
|
+
}>(path.join(directory, "package.json"));
|
|
4080
|
+
if (candidate?.name === "better-auth") return candidate;
|
|
4081
|
+
|
|
4082
|
+
const parent = path.dirname(directory);
|
|
4083
|
+
if (parent === directory) return undefined;
|
|
4084
|
+
directory = parent;
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
async function readBetterAuthPackageFromNodeModules(
|
|
4089
|
+
targetDir: string,
|
|
4090
|
+
): Promise<{ name?: string; version?: string } | undefined> {
|
|
4091
|
+
let directory = targetDir;
|
|
4092
|
+
while (true) {
|
|
4093
|
+
const candidate = await readJsonIfExists<{
|
|
4094
|
+
name?: string;
|
|
4095
|
+
version?: string;
|
|
4096
|
+
}>(path.join(directory, "node_modules", "better-auth", "package.json"));
|
|
4097
|
+
if (candidate?.name === "better-auth") return candidate;
|
|
4098
|
+
|
|
4099
|
+
const parent = path.dirname(directory);
|
|
4100
|
+
if (parent === directory) return undefined;
|
|
4101
|
+
directory = parent;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
function resolveModuleFromTarget(
|
|
4106
|
+
requireFromTarget: ReturnType<typeof createRequire>,
|
|
4107
|
+
request: string,
|
|
4108
|
+
): string | undefined {
|
|
4109
|
+
try {
|
|
4110
|
+
return requireFromTarget.resolve(request);
|
|
4111
|
+
} catch {
|
|
4112
|
+
return undefined;
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
async function hasBetterAuthIssuerSchema(
|
|
3922
4117
|
targetDir: string,
|
|
3923
4118
|
files: string[],
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4119
|
+
sourceCache: Map<string, string>,
|
|
4120
|
+
): Promise<boolean> {
|
|
4121
|
+
const schemaFiles = files.filter(
|
|
4122
|
+
(file) =>
|
|
4123
|
+
!isTestSourceFile(file) &&
|
|
4124
|
+
/\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs)$/.test(file),
|
|
4125
|
+
);
|
|
4126
|
+
|
|
4127
|
+
for (const file of schemaFiles) {
|
|
4128
|
+
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
4129
|
+
if (sourceHasBetterAuthIssuerSchema(file, source)) return true;
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
return false;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
function sourceHasBetterAuthIssuerSchema(
|
|
4136
|
+
file: string,
|
|
4137
|
+
source: string,
|
|
4138
|
+
): boolean {
|
|
4139
|
+
const sourceFile = ts.createSourceFile(
|
|
4140
|
+
file,
|
|
4141
|
+
source,
|
|
4142
|
+
ts.ScriptTarget.Latest,
|
|
4143
|
+
true,
|
|
4144
|
+
);
|
|
4145
|
+
let found = false;
|
|
4146
|
+
const visit = (node: ts.Node): void => {
|
|
4147
|
+
if (found) return;
|
|
4148
|
+
if (
|
|
4149
|
+
ts.isCallExpression(node) &&
|
|
4150
|
+
isBetterAuthAccountTableCall(node) &&
|
|
4151
|
+
betterAuthAccountTableHasIssuerIdentity(node)
|
|
4152
|
+
) {
|
|
4153
|
+
found = true;
|
|
4154
|
+
return;
|
|
3929
4155
|
}
|
|
3930
|
-
|
|
3931
|
-
|
|
4156
|
+
ts.forEachChild(node, visit);
|
|
4157
|
+
};
|
|
4158
|
+
visit(sourceFile);
|
|
4159
|
+
return found;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
function isBetterAuthAccountTableCall(call: ts.CallExpression): boolean {
|
|
4163
|
+
const callee = unwrapContractExpression(call.expression);
|
|
4164
|
+
const factoryName = ts.isIdentifier(callee)
|
|
4165
|
+
? callee.text
|
|
4166
|
+
: ts.isPropertyAccessExpression(callee)
|
|
4167
|
+
? callee.name.text
|
|
4168
|
+
: undefined;
|
|
4169
|
+
return (
|
|
4170
|
+
factoryName !== undefined &&
|
|
4171
|
+
["mysqlTable", "pgTable", "sqliteTable"].includes(factoryName) &&
|
|
4172
|
+
staticString(call.arguments[0]) === "account"
|
|
4173
|
+
);
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
function betterAuthAccountTableHasIssuerIdentity(
|
|
4177
|
+
call: ts.CallExpression,
|
|
4178
|
+
): boolean {
|
|
4179
|
+
const columns = call.arguments[1];
|
|
4180
|
+
if (!columns || !ts.isObjectLiteralExpression(columns)) return false;
|
|
4181
|
+
const issuer = columns.properties.find(
|
|
4182
|
+
(property) =>
|
|
4183
|
+
ts.isPropertyAssignment(property) &&
|
|
4184
|
+
staticPropertyName(property.name) === "issuer",
|
|
4185
|
+
);
|
|
4186
|
+
if (!issuer || !ts.isPropertyAssignment(issuer)) return false;
|
|
4187
|
+
if (!isBetterAuthIssuerColumn(issuer.initializer)) return false;
|
|
4188
|
+
|
|
4189
|
+
const constraints = call.arguments[2];
|
|
4190
|
+
if (
|
|
4191
|
+
!constraints ||
|
|
4192
|
+
(!ts.isArrowFunction(constraints) &&
|
|
4193
|
+
!ts.isFunctionExpression(constraints)) ||
|
|
4194
|
+
constraints.parameters.length === 0 ||
|
|
4195
|
+
!ts.isIdentifier(constraints.parameters[0]?.name)
|
|
4196
|
+
) {
|
|
4197
|
+
return false;
|
|
4198
|
+
}
|
|
4199
|
+
|
|
4200
|
+
const tableName = constraints.parameters[0].name.text;
|
|
4201
|
+
let found = false;
|
|
4202
|
+
const visit = (node: ts.Node): void => {
|
|
4203
|
+
if (found) return;
|
|
4204
|
+
if (
|
|
4205
|
+
ts.isCallExpression(node) &&
|
|
4206
|
+
isBetterAuthIssuerAccountUniqueConstraint(node, tableName)
|
|
4207
|
+
) {
|
|
4208
|
+
found = true;
|
|
4209
|
+
return;
|
|
4210
|
+
}
|
|
4211
|
+
ts.forEachChild(node, visit);
|
|
4212
|
+
};
|
|
4213
|
+
visit(constraints.body);
|
|
4214
|
+
return found;
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
function isBetterAuthIssuerColumn(expression: ts.Expression): boolean {
|
|
4218
|
+
let current = unwrapContractExpression(expression);
|
|
4219
|
+
while (ts.isCallExpression(current)) {
|
|
4220
|
+
const callee = unwrapContractExpression(current.expression);
|
|
4221
|
+
if (
|
|
4222
|
+
ts.isIdentifier(callee) &&
|
|
4223
|
+
["text", "varchar"].includes(callee.text) &&
|
|
4224
|
+
staticString(current.arguments[0]) === "issuer"
|
|
4225
|
+
) {
|
|
4226
|
+
return true;
|
|
4227
|
+
}
|
|
4228
|
+
if (!ts.isPropertyAccessExpression(callee)) return false;
|
|
4229
|
+
current = unwrapContractExpression(callee.expression);
|
|
4230
|
+
}
|
|
4231
|
+
return false;
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
function isBetterAuthIssuerAccountUniqueConstraint(
|
|
4235
|
+
call: ts.CallExpression,
|
|
4236
|
+
tableName: string,
|
|
4237
|
+
): boolean {
|
|
4238
|
+
const callee = unwrapContractExpression(call.expression);
|
|
4239
|
+
if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "on") {
|
|
4240
|
+
return false;
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
const factoryCall = unwrapContractExpression(callee.expression);
|
|
4244
|
+
if (!ts.isCallExpression(factoryCall)) return false;
|
|
4245
|
+
const factory = unwrapContractExpression(factoryCall.expression);
|
|
4246
|
+
if (
|
|
4247
|
+
!ts.isIdentifier(factory) ||
|
|
4248
|
+
!["unique", "uniqueIndex"].includes(factory.text)
|
|
4249
|
+
) {
|
|
4250
|
+
return false;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
if (call.arguments.length !== 2) return false;
|
|
4254
|
+
const columnNames = call.arguments.map((argument) => {
|
|
4255
|
+
const column = unwrapContractExpression(argument);
|
|
4256
|
+
return ts.isPropertyAccessExpression(column) &&
|
|
4257
|
+
ts.isIdentifier(column.expression) &&
|
|
4258
|
+
column.expression.text === tableName
|
|
4259
|
+
? column.name.text
|
|
4260
|
+
: undefined;
|
|
4261
|
+
});
|
|
4262
|
+
return columnNames.includes("issuer") && columnNames.includes("accountId");
|
|
4263
|
+
}
|
|
4264
|
+
|
|
4265
|
+
async function readPackageJson(
|
|
4266
|
+
targetDir: string,
|
|
4267
|
+
files: string[],
|
|
4268
|
+
): Promise<InspectedPackageJson | undefined> {
|
|
3932
4269
|
if (!files.includes("package.json")) return undefined;
|
|
3933
4270
|
|
|
3934
4271
|
return JSON.parse(
|
|
@@ -4089,7 +4426,7 @@ async function hasBillingEntitlementsProviderWiring(
|
|
|
4089
4426
|
const serverPath = path.dirname(config.paths.server);
|
|
4090
4427
|
const candidateFiles = files.filter(
|
|
4091
4428
|
(file) =>
|
|
4092
|
-
|
|
4429
|
+
isAnalysisSourceFile(file) &&
|
|
4093
4430
|
!isTestSourceFile(file) &&
|
|
4094
4431
|
(file.startsWith(`${infraPath}/`) || file.startsWith(`${serverPath}/`)),
|
|
4095
4432
|
);
|
|
@@ -4105,10 +4442,7 @@ async function hasBillingEntitlementsProviderWiring(
|
|
|
4105
4442
|
}
|
|
4106
4443
|
|
|
4107
4444
|
function isTestSourceFile(file: string): boolean {
|
|
4108
|
-
return (
|
|
4109
|
-
/(?:^|\/)tests\//.test(file) ||
|
|
4110
|
-
/\.(?:test|spec)\.(?:ts|tsx|mts|cts)$/.test(file)
|
|
4111
|
-
);
|
|
4445
|
+
return /(?:^|\/)tests\//.test(file) || isAppTestFile(file);
|
|
4112
4446
|
}
|
|
4113
4447
|
|
|
4114
4448
|
function exportedConstInitializer(
|
|
@@ -4135,8 +4469,8 @@ function featureNameFromContractFile(
|
|
|
4135
4469
|
): string | undefined {
|
|
4136
4470
|
const featuresPath = directoryPath(config.paths.features);
|
|
4137
4471
|
const match = new RegExp(
|
|
4138
|
-
`^${escapeRegExp(featuresPath)}/([^/]+)/contracts
|
|
4139
|
-
).exec(file);
|
|
4472
|
+
`^${escapeRegExp(featuresPath)}/([^/]+)/contracts$`,
|
|
4473
|
+
).exec(stripSourceFileExtension(file));
|
|
4140
4474
|
return match?.[1];
|
|
4141
4475
|
}
|
|
4142
4476
|
|
|
@@ -4153,7 +4487,7 @@ function featureRuntimeFiles(
|
|
|
4153
4487
|
"use-cases",
|
|
4154
4488
|
];
|
|
4155
4489
|
return files.filter((file) => {
|
|
4156
|
-
if (!file
|
|
4490
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
4157
4491
|
return runtimeFolders.some((folder) =>
|
|
4158
4492
|
file.startsWith(`${featuresPath}/${feature}/${folder}/`),
|
|
4159
4493
|
);
|
|
@@ -4169,11 +4503,16 @@ function featureTestFiles(
|
|
|
4169
4503
|
return files.filter((file) => {
|
|
4170
4504
|
if (
|
|
4171
4505
|
!file.startsWith(`${featuresPath}/${feature}/`) ||
|
|
4172
|
-
!file
|
|
4506
|
+
!isAppTestFile(file)
|
|
4173
4507
|
) {
|
|
4174
4508
|
return false;
|
|
4175
4509
|
}
|
|
4176
|
-
return
|
|
4510
|
+
return (
|
|
4511
|
+
!options.policyOnly ||
|
|
4512
|
+
["policy.test", "policy.spec"].includes(
|
|
4513
|
+
path.posix.basename(stripSourceFileExtension(file)),
|
|
4514
|
+
)
|
|
4515
|
+
);
|
|
4177
4516
|
});
|
|
4178
4517
|
}
|
|
4179
4518
|
|
|
@@ -4340,17 +4679,12 @@ async function inspectFeatureArtifactDrift(
|
|
|
4340
4679
|
severity: "warning",
|
|
4341
4680
|
code: "BEIGNET_FEATURE_TEST_PLACEMENT",
|
|
4342
4681
|
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
|
|
4682
|
+
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
4683
|
});
|
|
4345
4684
|
}
|
|
4346
4685
|
|
|
4347
4686
|
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
|
-
}
|
|
4687
|
+
if (!isAnalysisSourceFile(file)) continue;
|
|
4354
4688
|
|
|
4355
4689
|
const misplaced = directFeatureTestMatch(file, featuresPath)
|
|
4356
4690
|
? undefined
|
|
@@ -4365,7 +4699,6 @@ async function inspectFeatureArtifactDrift(
|
|
|
4365
4699
|
}
|
|
4366
4700
|
|
|
4367
4701
|
if (isTestSourceFile(file)) continue;
|
|
4368
|
-
if (!file.endsWith(".ts")) continue;
|
|
4369
4702
|
|
|
4370
4703
|
if (
|
|
4371
4704
|
isNotificationDefinitionFile(file, featuresPath) ||
|
|
@@ -4391,9 +4724,10 @@ function directFeatureTestMatch(
|
|
|
4391
4724
|
file: string,
|
|
4392
4725
|
featuresPath: string,
|
|
4393
4726
|
): RegExpExecArray | null {
|
|
4727
|
+
if (!isAppTestFile(file)) return null;
|
|
4394
4728
|
return new RegExp(
|
|
4395
|
-
`^${escapeRegExp(featuresPath)}/([^/]+)/[^/]+\\.(?:test|spec)
|
|
4396
|
-
).exec(file);
|
|
4729
|
+
`^${escapeRegExp(featuresPath)}/([^/]+)/[^/]+\\.(?:test|spec)$`,
|
|
4730
|
+
).exec(stripSourceFileExtension(file));
|
|
4397
4731
|
}
|
|
4398
4732
|
|
|
4399
4733
|
function isFeatureArtifactFile(
|
|
@@ -4401,9 +4735,10 @@ function isFeatureArtifactFile(
|
|
|
4401
4735
|
featuresPath: string,
|
|
4402
4736
|
folder: "uploads" | "notifications",
|
|
4403
4737
|
): boolean {
|
|
4738
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
4404
4739
|
const match = new RegExp(
|
|
4405
|
-
`^${escapeRegExp(featuresPath)}/[^/]+/${folder}/([^/]+)
|
|
4406
|
-
).exec(file);
|
|
4740
|
+
`^${escapeRegExp(featuresPath)}/[^/]+/${folder}/([^/]+)$`,
|
|
4741
|
+
).exec(stripSourceFileExtension(file));
|
|
4407
4742
|
return Boolean(match && match[1] !== "index");
|
|
4408
4743
|
}
|
|
4409
4744
|
|
|
@@ -4413,7 +4748,7 @@ async function appHasUploadRoute(
|
|
|
4413
4748
|
config: ResolvedBeignetConfig,
|
|
4414
4749
|
): Promise<boolean> {
|
|
4415
4750
|
for (const file of files) {
|
|
4416
|
-
if (!file
|
|
4751
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
4417
4752
|
if (!isRouteServerOrInfraFile(file, config)) continue;
|
|
4418
4753
|
|
|
4419
4754
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
@@ -4967,11 +5302,17 @@ async function readFeatureWorkflowRegistries(
|
|
|
4967
5302
|
for (const kind of kinds) {
|
|
4968
5303
|
const folder = kind === "events" ? "domain/events" : kind;
|
|
4969
5304
|
const pattern = new RegExp(
|
|
4970
|
-
`^${escapeRegExp(featuresPath)}/[^/]+/${escapeRegExp(folder)}/index
|
|
5305
|
+
`^${escapeRegExp(featuresPath)}/[^/]+/${escapeRegExp(folder)}/index$`,
|
|
4971
5306
|
);
|
|
4972
5307
|
|
|
4973
5308
|
for (const file of files) {
|
|
4974
|
-
if (
|
|
5309
|
+
if (
|
|
5310
|
+
!isAnalysisSourceFile(file) ||
|
|
5311
|
+
isWorkflowTestFile(file) ||
|
|
5312
|
+
!pattern.test(stripSourceFileExtension(file))
|
|
5313
|
+
) {
|
|
5314
|
+
continue;
|
|
5315
|
+
}
|
|
4975
5316
|
|
|
4976
5317
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
4977
5318
|
const memberFiles = exportedMemberFiles(source, file, files);
|
|
@@ -5031,8 +5372,7 @@ async function readRuntimeManifestIdentifiers(
|
|
|
5031
5372
|
const manifests: RuntimeManifestIdentifierSets[] = [];
|
|
5032
5373
|
|
|
5033
5374
|
for (const file of files) {
|
|
5034
|
-
if (!file
|
|
5035
|
-
if (file.endsWith(".d.ts") || isWorkflowTestFile(file)) continue;
|
|
5375
|
+
if (!isAnalysisSourceFile(file) || isWorkflowTestFile(file)) continue;
|
|
5036
5376
|
|
|
5037
5377
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5038
5378
|
if (!source.includes("defineRuntimeManifest")) continue;
|
|
@@ -5169,12 +5509,18 @@ async function listenedEventNames(
|
|
|
5169
5509
|
): Promise<Set<string>> {
|
|
5170
5510
|
const featuresPath = directoryPath(config.paths.features);
|
|
5171
5511
|
const listenerPattern = new RegExp(
|
|
5172
|
-
`^${escapeRegExp(featuresPath)}/[^/]+/listeners/[^/]
|
|
5512
|
+
`^${escapeRegExp(featuresPath)}/[^/]+/listeners/[^/]+$`,
|
|
5173
5513
|
);
|
|
5174
5514
|
const listened = new Set<string>();
|
|
5175
5515
|
|
|
5176
5516
|
for (const file of files) {
|
|
5177
|
-
if (
|
|
5517
|
+
if (
|
|
5518
|
+
!isAnalysisSourceFile(file) ||
|
|
5519
|
+
isWorkflowTestFile(file) ||
|
|
5520
|
+
!listenerPattern.test(stripSourceFileExtension(file))
|
|
5521
|
+
) {
|
|
5522
|
+
continue;
|
|
5523
|
+
}
|
|
5178
5524
|
|
|
5179
5525
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5180
5526
|
const callPattern = /\bdefineListener\s*\(/g;
|
|
@@ -5223,8 +5569,7 @@ async function listenerWiringReferences(
|
|
|
5223
5569
|
const listenerRegistryFile = listenersFilePath(config);
|
|
5224
5570
|
|
|
5225
5571
|
for (const file of files) {
|
|
5226
|
-
if (!file
|
|
5227
|
-
if (file.endsWith(".d.ts") || isWorkflowTestFile(file)) continue;
|
|
5572
|
+
if (!isAnalysisSourceFile(file) || isWorkflowTestFile(file)) continue;
|
|
5228
5573
|
|
|
5229
5574
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5230
5575
|
const namedImports = parseNamedImportSources(source);
|
|
@@ -5233,7 +5578,7 @@ async function listenerWiringReferences(
|
|
|
5233
5578
|
source,
|
|
5234
5579
|
ts.ScriptTarget.Latest,
|
|
5235
5580
|
true,
|
|
5236
|
-
file
|
|
5581
|
+
sourceFileScriptKind(file),
|
|
5237
5582
|
);
|
|
5238
5583
|
const registerListenersBindings = importedRegisterListenersBindings(
|
|
5239
5584
|
source,
|
|
@@ -5693,10 +6038,7 @@ function callReferencesCentralListenerRegistry(args: {
|
|
|
5693
6038
|
|
|
5694
6039
|
function isWorkflowTestFile(file: string): boolean {
|
|
5695
6040
|
return (
|
|
5696
|
-
file.
|
|
5697
|
-
file.endsWith(".test.tsx") ||
|
|
5698
|
-
file.startsWith("tests/") ||
|
|
5699
|
-
file.includes("/tests/")
|
|
6041
|
+
isAppTestFile(file) || file.startsWith("tests/") || file.includes("/tests/")
|
|
5700
6042
|
);
|
|
5701
6043
|
}
|
|
5702
6044
|
|
|
@@ -5711,13 +6053,7 @@ async function inspectServerlessFootguns(
|
|
|
5711
6053
|
const cronDirectory = path.posix.join(routeDirectory, "cron");
|
|
5712
6054
|
|
|
5713
6055
|
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
|
-
}
|
|
6056
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) continue;
|
|
5721
6057
|
|
|
5722
6058
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
5723
6059
|
const isInfraOrServerFile = isRuntimeFile(file, config);
|
|
@@ -5863,9 +6199,10 @@ function fileWithinDirectory(file: string, directory: string): boolean {
|
|
|
5863
6199
|
}
|
|
5864
6200
|
|
|
5865
6201
|
function isProviderLifecycleFile(file: string, source?: string): boolean {
|
|
6202
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
5866
6203
|
return (
|
|
5867
|
-
/(^|\/)provider(s)
|
|
5868
|
-
/(^|\/)[^/]*provider[^/]
|
|
6204
|
+
/(^|\/)provider(s)?$/.test(sourcePath) ||
|
|
6205
|
+
/(^|\/)[^/]*provider[^/]*$/.test(sourcePath) ||
|
|
5869
6206
|
/(^|\/)providers?\//.test(file) ||
|
|
5870
6207
|
(source !== undefined && containsCallExpression(source, "createProvider"))
|
|
5871
6208
|
);
|
|
@@ -5876,8 +6213,9 @@ function isNextRouteFile(file: string, config: ResolvedBeignetConfig): boolean {
|
|
|
5876
6213
|
|
|
5877
6214
|
return (
|
|
5878
6215
|
file.startsWith(`${routesPath}/`) &&
|
|
5879
|
-
file
|
|
5880
|
-
|
|
6216
|
+
isSourceFileNamed(file, "route") &&
|
|
6217
|
+
[".js", ".jsx", ".ts", ".tsx"].includes(path.posix.extname(file)) &&
|
|
6218
|
+
!isAppTestFile(file)
|
|
5881
6219
|
);
|
|
5882
6220
|
}
|
|
5883
6221
|
|
|
@@ -7812,7 +8150,13 @@ async function inspectFeatureRouteRegistration(
|
|
|
7812
8150
|
const feature = featureNameFromContractFile(contract.file, config);
|
|
7813
8151
|
if (!feature) continue;
|
|
7814
8152
|
|
|
7815
|
-
const
|
|
8153
|
+
const routeStem = `${featuresPath}/${feature}/routes`;
|
|
8154
|
+
const routeFile =
|
|
8155
|
+
files.find(
|
|
8156
|
+
(file) =>
|
|
8157
|
+
isAnalysisSourceFile(file) &&
|
|
8158
|
+
stripSourceFileExtension(file) === routeStem,
|
|
8159
|
+
) ?? `${routeStem}${path.posix.extname(contract.file) || ".ts"}`;
|
|
7816
8160
|
const featureRouteGroups = routeGroups.filter(
|
|
7817
8161
|
(routeGroup) => routeGroup.file === routeFile,
|
|
7818
8162
|
);
|
|
@@ -7902,13 +8246,14 @@ async function readFeatureRouteGroups(
|
|
|
7902
8246
|
): Promise<FeatureRouteGroup[]> {
|
|
7903
8247
|
const featuresPath = `${directoryPath(config.paths.features)}/`;
|
|
7904
8248
|
const routeFiles = files.filter(
|
|
7905
|
-
(file) =>
|
|
8249
|
+
(file) =>
|
|
8250
|
+
file.startsWith(featuresPath) && isSourceFileNamed(file, "routes"),
|
|
7906
8251
|
);
|
|
7907
8252
|
const routeGroups: FeatureRouteGroup[] = [];
|
|
7908
8253
|
|
|
7909
8254
|
for (const file of routeFiles) {
|
|
7910
8255
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
7911
|
-
const imports = parseNamedImports(source, config, file);
|
|
8256
|
+
const imports = parseNamedImports(source, config, file, files);
|
|
7912
8257
|
routeGroups.push(...parseFeatureRouteGroups(source, file, imports));
|
|
7913
8258
|
}
|
|
7914
8259
|
|
|
@@ -8071,8 +8416,7 @@ function findRouteGroupContract(
|
|
|
8071
8416
|
contract.exportName === routeGroupContract.exportName &&
|
|
8072
8417
|
(!routeGroupContract.file ||
|
|
8073
8418
|
contract.file === routeGroupContract.file ||
|
|
8074
|
-
contract.file ===
|
|
8075
|
-
routeGroupContract.file.replace(/\.ts$/, "/index.ts")),
|
|
8419
|
+
contract.file === directoryIndexSourceFile(routeGroupContract.file)),
|
|
8076
8420
|
);
|
|
8077
8421
|
}
|
|
8078
8422
|
|
|
@@ -9449,7 +9793,7 @@ async function readContractLists(
|
|
|
9449
9793
|
|
|
9450
9794
|
for (const file of contractFiles) {
|
|
9451
9795
|
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
9452
|
-
const imports = parseNamedImports(source, config, file);
|
|
9796
|
+
const imports = parseNamedImports(source, config, file, files);
|
|
9453
9797
|
for (const list of parseContractLists(source, file, imports)) {
|
|
9454
9798
|
lists.set(contractListKey(list.file, list.name), list);
|
|
9455
9799
|
}
|
|
@@ -9466,18 +9810,18 @@ function contractSourceFiles(
|
|
|
9466
9810
|
if (usesFeatureOwnedContracts(config)) {
|
|
9467
9811
|
return files.filter(
|
|
9468
9812
|
(file) =>
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
).test(
|
|
9813
|
+
isAnalysisSourceFile(file) &&
|
|
9814
|
+
!isAppTestFile(file) &&
|
|
9815
|
+
new RegExp(`^${escapeRegExp(contractsPath)}/[^/]+/contracts$`).test(
|
|
9816
|
+
stripSourceFileExtension(file),
|
|
9817
|
+
),
|
|
9472
9818
|
);
|
|
9473
9819
|
}
|
|
9474
9820
|
|
|
9475
|
-
return files.filter(
|
|
9476
|
-
(file)
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
!file.endsWith(".test.ts"),
|
|
9480
|
-
);
|
|
9821
|
+
return files.filter((file) => {
|
|
9822
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
9823
|
+
return stripSourceFileExtension(file).startsWith(`${contractsPath}/`);
|
|
9824
|
+
});
|
|
9481
9825
|
}
|
|
9482
9826
|
|
|
9483
9827
|
function contractListSourceFiles(
|
|
@@ -9487,22 +9831,23 @@ function contractListSourceFiles(
|
|
|
9487
9831
|
const contractsPath = directoryPath(config.paths.contracts);
|
|
9488
9832
|
if (usesFeatureOwnedContracts(config)) {
|
|
9489
9833
|
return files.filter((file) => {
|
|
9490
|
-
if (!file
|
|
9491
|
-
|
|
9492
|
-
if (
|
|
9834
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
9835
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
9836
|
+
if (sourcePath === `${contractsPath}/index`) return true;
|
|
9837
|
+
if (sourcePath === `${contractsPath}/contracts`) return true;
|
|
9493
9838
|
return new RegExp(
|
|
9494
|
-
`^${escapeRegExp(contractsPath)}/[^/]+/contracts
|
|
9495
|
-
).test(
|
|
9839
|
+
`^${escapeRegExp(contractsPath)}/[^/]+/contracts$`,
|
|
9840
|
+
).test(sourcePath);
|
|
9496
9841
|
});
|
|
9497
9842
|
}
|
|
9498
9843
|
|
|
9499
|
-
return files.filter(
|
|
9500
|
-
(file)
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
);
|
|
9844
|
+
return files.filter((file) => {
|
|
9845
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
9846
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
9847
|
+
return (
|
|
9848
|
+
sourcePath === contractsPath || sourcePath.startsWith(`${contractsPath}/`)
|
|
9849
|
+
);
|
|
9850
|
+
});
|
|
9506
9851
|
}
|
|
9507
9852
|
|
|
9508
9853
|
function parseContractLists(
|
|
@@ -9556,7 +9901,7 @@ function resolveContractList(
|
|
|
9556
9901
|
if (seen.has(key)) return new Set();
|
|
9557
9902
|
const list =
|
|
9558
9903
|
lists.get(key) ??
|
|
9559
|
-
lists.get(contractListKey(file
|
|
9904
|
+
lists.get(contractListKey(directoryIndexSourceFile(file), name));
|
|
9560
9905
|
if (!list) return undefined;
|
|
9561
9906
|
|
|
9562
9907
|
seen.add(key);
|
|
@@ -10146,7 +10491,7 @@ function nextRoutePath(file: string, routeRoot: string): string {
|
|
|
10146
10491
|
const normalizedRouteRoot = directoryPath(routeRoot);
|
|
10147
10492
|
const routeSegments = file
|
|
10148
10493
|
.slice(normalizedRouteRoot.length + 1)
|
|
10149
|
-
.replace(/\/route\.ts$/, "")
|
|
10494
|
+
.replace(/\/route\.(?:js|jsx|ts|tsx)$/, "")
|
|
10150
10495
|
.split("/")
|
|
10151
10496
|
.filter(Boolean);
|
|
10152
10497
|
const apiPath = routeSegments.map(nextSegmentToContractSegment).join("/");
|