@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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
export const typescriptAnalysisSourceFileExtensions = [
|
|
5
|
+
".ts",
|
|
6
|
+
".tsx",
|
|
7
|
+
".mts",
|
|
8
|
+
".cts",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export const javascriptAnalysisSourceFileExtensions = [
|
|
12
|
+
".js",
|
|
13
|
+
".jsx",
|
|
14
|
+
".mjs",
|
|
15
|
+
".cjs",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export const analysisSourceFileExtensions = [
|
|
19
|
+
...typescriptAnalysisSourceFileExtensions,
|
|
20
|
+
...javascriptAnalysisSourceFileExtensions,
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
const sourceFileExtensions = new Set<string>(analysisSourceFileExtensions);
|
|
24
|
+
const javascriptSourceFileExtensions = new Set<string>(
|
|
25
|
+
javascriptAnalysisSourceFileExtensions,
|
|
26
|
+
);
|
|
27
|
+
const declarationFilePattern = /\.d\.(?:ts|mts|cts)$/;
|
|
28
|
+
|
|
29
|
+
/** Whether an extension belongs to the JavaScript/TypeScript source family. */
|
|
30
|
+
export function isAnalysisSourceFileExtension(extension: string): boolean {
|
|
31
|
+
return sourceFileExtensions.has(extension);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Whether a project file can participate in Beignet source analysis. */
|
|
35
|
+
export function isAnalysisSourceFile(file: string): boolean {
|
|
36
|
+
return (
|
|
37
|
+
isAnalysisSourceFileExtension(path.posix.extname(file)) &&
|
|
38
|
+
!declarationFilePattern.test(file)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Remove a JavaScript or TypeScript source extension from a path. */
|
|
43
|
+
export function stripSourceFileExtension(file: string): string {
|
|
44
|
+
const extension = path.posix.extname(file);
|
|
45
|
+
return isAnalysisSourceFileExtension(extension)
|
|
46
|
+
? file.slice(0, -extension.length)
|
|
47
|
+
: file;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Whether a source file uses JavaScript rather than TypeScript syntax. */
|
|
51
|
+
export function isJavaScriptSourceFile(file: string): boolean {
|
|
52
|
+
return javascriptSourceFileExtensions.has(path.posix.extname(file));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Select the TypeScript parser mode for a JavaScript or TypeScript file. */
|
|
56
|
+
export function sourceFileScriptKind(file: string): ts.ScriptKind {
|
|
57
|
+
if (file.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
58
|
+
if (file.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
59
|
+
if (isJavaScriptSourceFile(file)) return ts.ScriptKind.JS;
|
|
60
|
+
return ts.ScriptKind.TS;
|
|
61
|
+
}
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
type ResolvedBeignetConfig,
|
|
9
9
|
resolveConfig,
|
|
10
10
|
} from "../config.js";
|
|
11
|
+
import {
|
|
12
|
+
isAnalysisSourceFileExtension,
|
|
13
|
+
sourceFileScriptKind,
|
|
14
|
+
} from "./source-files.js";
|
|
11
15
|
|
|
12
16
|
const ignoredDirectoryNames = new Set([
|
|
13
17
|
".git",
|
|
@@ -84,7 +88,7 @@ export async function createAnalysisWorkspace(
|
|
|
84
88
|
await readSource(file),
|
|
85
89
|
ts.ScriptTarget.Latest,
|
|
86
90
|
true,
|
|
87
|
-
|
|
91
|
+
sourceFileScriptKind(file),
|
|
88
92
|
);
|
|
89
93
|
sourceFiles.set(file, sourceFile);
|
|
90
94
|
return sourceFile;
|
|
@@ -208,12 +212,7 @@ export function isLocalModuleSpecifier(
|
|
|
208
212
|
importPath: string,
|
|
209
213
|
): boolean {
|
|
210
214
|
const extension = path.posix.extname(importPath);
|
|
211
|
-
if (
|
|
212
|
-
extension &&
|
|
213
|
-
![".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"].includes(
|
|
214
|
-
extension,
|
|
215
|
-
)
|
|
216
|
-
) {
|
|
215
|
+
if (extension && !isAnalysisSourceFileExtension(extension)) {
|
|
217
216
|
return false;
|
|
218
217
|
}
|
|
219
218
|
if (importPath.startsWith(".")) return true;
|
|
@@ -298,28 +297,32 @@ function resolveCompilerConfiguration(targetDir: string): {
|
|
|
298
297
|
compilerOptions: ts.CompilerOptions;
|
|
299
298
|
} {
|
|
300
299
|
const compilerConfigFiles = new Set<string>();
|
|
301
|
-
const configPath =
|
|
302
|
-
|
|
303
|
-
ts.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
300
|
+
const configPath = ["tsconfig.json", "jsconfig.json"]
|
|
301
|
+
.map((file) => path.join(targetDir, file))
|
|
302
|
+
.find(ts.sys.fileExists);
|
|
303
|
+
const compilerOptions = configPath
|
|
304
|
+
? (ts.getParsedCommandLineOfConfigFile(
|
|
305
|
+
configPath,
|
|
306
|
+
{},
|
|
307
|
+
{
|
|
308
|
+
...ts.sys,
|
|
309
|
+
readFile(file) {
|
|
310
|
+
compilerConfigFiles.add(path.resolve(file));
|
|
311
|
+
return ts.sys.readFile(file);
|
|
312
|
+
},
|
|
313
|
+
onUnRecoverableConfigFileDiagnostic: () => {},
|
|
311
314
|
},
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
+
)?.options ?? {})
|
|
316
|
+
: {};
|
|
317
|
+
const analysisOptions = { ...compilerOptions, allowJs: true };
|
|
315
318
|
|
|
316
319
|
return {
|
|
317
320
|
compilerConfigFiles: [...compilerConfigFiles].sort(),
|
|
318
|
-
compilerOptions:
|
|
319
|
-
?
|
|
321
|
+
compilerOptions: analysisOptions.paths
|
|
322
|
+
? analysisOptions
|
|
320
323
|
: {
|
|
321
|
-
...
|
|
322
|
-
baseUrl:
|
|
324
|
+
...analysisOptions,
|
|
325
|
+
baseUrl: analysisOptions.baseUrl ?? targetDir,
|
|
323
326
|
paths: {
|
|
324
327
|
"@/*": ["*"],
|
|
325
328
|
},
|
|
@@ -338,10 +341,3 @@ function compilerPathsBase(
|
|
|
338
341
|
compilerOptions.baseUrl ?? optionsWithPathBase.pathsBasePath ?? targetDir
|
|
339
342
|
);
|
|
340
343
|
}
|
|
341
|
-
|
|
342
|
-
function scriptKind(file: string): ts.ScriptKind {
|
|
343
|
-
if (file.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
344
|
-
if (file.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
345
|
-
if (file.endsWith(".js") || file.endsWith(".mjs")) return ts.ScriptKind.JS;
|
|
346
|
-
return ts.ScriptKind.TS;
|
|
347
|
-
}
|
package/src/app-map-changes.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { readFile, realpath } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
isAnalysisSourceFile,
|
|
5
|
+
stripSourceFileExtension,
|
|
6
|
+
} from "./analysis/source-files.js";
|
|
3
7
|
import {
|
|
4
8
|
type LocalImportReference,
|
|
5
9
|
localImportReferences,
|
|
@@ -794,7 +798,7 @@ async function addImportConsumers(args: {
|
|
|
794
798
|
reference: LocalImportReference;
|
|
795
799
|
}>
|
|
796
800
|
>();
|
|
797
|
-
const sourceFiles = args.workspace.files.filter(
|
|
801
|
+
const sourceFiles = args.workspace.files.filter(isAnalysisSourceFile);
|
|
798
802
|
await Promise.all(
|
|
799
803
|
sourceFiles.map(async (file) => {
|
|
800
804
|
for (const reference of await localImportReferences(
|
|
@@ -826,7 +830,9 @@ async function addImportConsumers(args: {
|
|
|
826
830
|
);
|
|
827
831
|
}
|
|
828
832
|
|
|
829
|
-
for (const change of args.changes.filter((item) =>
|
|
833
|
+
for (const change of args.changes.filter((item) =>
|
|
834
|
+
isAnalysisSourceFile(item.file),
|
|
835
|
+
)) {
|
|
830
836
|
const queue: Array<{
|
|
831
837
|
confidence: "exact" | "partial";
|
|
832
838
|
depth: number;
|
|
@@ -1002,7 +1008,7 @@ function addUnmappedChangeGaps(args: {
|
|
|
1002
1008
|
const represented = reasons.some(
|
|
1003
1009
|
(reason) => reason.changedFile === change.repositoryPath,
|
|
1004
1010
|
);
|
|
1005
|
-
const sourceFile =
|
|
1011
|
+
const sourceFile = isAnalysisSourceFile(change.file);
|
|
1006
1012
|
if (sourceFile && change.status === "deleted") {
|
|
1007
1013
|
args.gaps.push({
|
|
1008
1014
|
code: "deleted_source_unresolved",
|
|
@@ -1289,7 +1295,7 @@ function scopeSort(scope: AppChangeFileScope): number {
|
|
|
1289
1295
|
}
|
|
1290
1296
|
|
|
1291
1297
|
function moduleStem(file: string): string {
|
|
1292
|
-
return normalizePath(file)
|
|
1298
|
+
return stripSourceFileExtension(normalizePath(file));
|
|
1293
1299
|
}
|
|
1294
1300
|
|
|
1295
1301
|
function resolvedModuleKey(file: string): string {
|
|
@@ -1333,10 +1339,6 @@ function highestPriorityScope(
|
|
|
1333
1339
|
);
|
|
1334
1340
|
}
|
|
1335
1341
|
|
|
1336
|
-
function isSourceFile(file: string): boolean {
|
|
1337
|
-
return /\.(?:c|m)?[jt]sx?$/.test(file) && !file.endsWith(".d.ts");
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
1342
|
function isDocumentationPath(file: string, roots: string[]): boolean {
|
|
1341
1343
|
const normalized = normalizePath(file);
|
|
1342
1344
|
const basename = path.posix.basename(normalized);
|
package/src/app-map.ts
CHANGED
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
isImportedContractDeclaration,
|
|
7
7
|
} from "./analysis/contract-factories.js";
|
|
8
8
|
import { analyzePortWiring } from "./analysis/port-wiring.js";
|
|
9
|
+
import {
|
|
10
|
+
isAnalysisSourceFile,
|
|
11
|
+
stripSourceFileExtension,
|
|
12
|
+
} from "./analysis/source-files.js";
|
|
9
13
|
import {
|
|
10
14
|
createSourceIndex,
|
|
11
15
|
exportedVariableDeclarations,
|
|
@@ -29,6 +33,11 @@ import {
|
|
|
29
33
|
} from "./inspect.js";
|
|
30
34
|
import { type LintDiagnostic, lintApp } from "./lint.js";
|
|
31
35
|
import { auditProviders } from "./provider-audit.js";
|
|
36
|
+
import {
|
|
37
|
+
appTestRoots,
|
|
38
|
+
isAppTestFile,
|
|
39
|
+
isWithinAppTestRoots,
|
|
40
|
+
} from "./test-discovery.js";
|
|
32
41
|
|
|
33
42
|
export type { AppMapNodeKind } from "./app-map-schema.js";
|
|
34
43
|
export { appMapNodeKinds } from "./app-map-schema.js";
|
|
@@ -208,6 +217,7 @@ export async function mapApp(
|
|
|
208
217
|
const registries: RegistryRecord[] = [];
|
|
209
218
|
const featuresPath = directoryPath(workspace.config.paths.features);
|
|
210
219
|
const featureNames = discoverFeatureNames(workspace, featuresPath);
|
|
220
|
+
const testRoots = appTestRoots(workspace.config.paths);
|
|
211
221
|
|
|
212
222
|
const addNode = (node: AppMapNode) => {
|
|
213
223
|
const existing = nodes.get(node.id);
|
|
@@ -241,17 +251,23 @@ export async function mapApp(
|
|
|
241
251
|
status: "declared",
|
|
242
252
|
details: roles,
|
|
243
253
|
});
|
|
254
|
+
}
|
|
244
255
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
256
|
+
for (const file of workspace.files.filter(
|
|
257
|
+
(candidate) =>
|
|
258
|
+
isAppTestFile(candidate) && isWithinAppTestRoots(candidate, testRoots),
|
|
259
|
+
)) {
|
|
260
|
+
const id = `test:${file}`;
|
|
261
|
+
const feature = featureForFile(file, featuresPath);
|
|
262
|
+
addNode({
|
|
263
|
+
id,
|
|
264
|
+
kind: "test",
|
|
265
|
+
name: path.posix.basename(file),
|
|
266
|
+
...(feature ? { feature } : {}),
|
|
267
|
+
source: { file },
|
|
268
|
+
status: "declared",
|
|
269
|
+
});
|
|
270
|
+
if (feature) {
|
|
255
271
|
addEdge(exactEdge("contains-test", featureId(feature), id, file));
|
|
256
272
|
}
|
|
257
273
|
}
|
|
@@ -345,7 +361,7 @@ export async function mapApp(
|
|
|
345
361
|
details: { type: "server" },
|
|
346
362
|
});
|
|
347
363
|
}
|
|
348
|
-
const sourceFiles = workspace.files.filter(
|
|
364
|
+
const sourceFiles = workspace.files.filter(isAnalysisSourceFile);
|
|
349
365
|
for (const file of sourceFiles) {
|
|
350
366
|
const sourceFile = await workspace.readSourceFile(file);
|
|
351
367
|
for (const { exportName, declaration } of exportedVariableDeclarations(
|
|
@@ -918,7 +934,8 @@ async function addRouteGroups(args: {
|
|
|
918
934
|
const routeFiles = args.workspace.files.filter(
|
|
919
935
|
(file) =>
|
|
920
936
|
featureForFile(file, args.featuresPath) !== undefined &&
|
|
921
|
-
file
|
|
937
|
+
isAnalysisSourceFile(file) &&
|
|
938
|
+
stripSourceFileExtension(file).endsWith("/routes"),
|
|
922
939
|
);
|
|
923
940
|
|
|
924
941
|
for (const file of routeFiles) {
|
|
@@ -1192,8 +1209,8 @@ async function addOutboxDeliveryGraph(args: {
|
|
|
1192
1209
|
]);
|
|
1193
1210
|
for (const file of args.workspace.files) {
|
|
1194
1211
|
if (
|
|
1195
|
-
!
|
|
1196
|
-
|
|
1212
|
+
!isAnalysisSourceFile(file) ||
|
|
1213
|
+
isAppTestFile(file) ||
|
|
1197
1214
|
![...runtimeDirectories].some(
|
|
1198
1215
|
(directory) =>
|
|
1199
1216
|
directory === "." ||
|
|
@@ -1276,9 +1293,10 @@ function isProviderLifecycleSource(
|
|
|
1276
1293
|
file: string,
|
|
1277
1294
|
sourceFile: ts.SourceFile,
|
|
1278
1295
|
): boolean {
|
|
1296
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
1279
1297
|
if (
|
|
1280
|
-
/(^|\/)provider(s)
|
|
1281
|
-
/(^|\/)[^/]*provider[^/]
|
|
1298
|
+
/(^|\/)provider(s)?$/.test(sourcePath) ||
|
|
1299
|
+
/(^|\/)[^/]*provider[^/]*$/.test(sourcePath) ||
|
|
1282
1300
|
/(^|\/)providers?\//.test(file)
|
|
1283
1301
|
) {
|
|
1284
1302
|
return true;
|
|
@@ -1390,7 +1408,7 @@ async function addTables(args: {
|
|
|
1390
1408
|
const schemaPrefix = `${infraDir}/db/schema/`;
|
|
1391
1409
|
const files = args.workspace.files.filter(
|
|
1392
1410
|
(file) =>
|
|
1393
|
-
|
|
1411
|
+
isAnalysisSourceFile(file) &&
|
|
1394
1412
|
(file.startsWith(schemaPrefix) ||
|
|
1395
1413
|
schemaSources.some((source) =>
|
|
1396
1414
|
file.startsWith(staticPathPrefix(source)),
|
|
@@ -1447,7 +1465,7 @@ async function addFeatureDependencies(args: {
|
|
|
1447
1465
|
}
|
|
1448
1466
|
>();
|
|
1449
1467
|
|
|
1450
|
-
for (const file of args.workspace.files.filter(
|
|
1468
|
+
for (const file of args.workspace.files.filter(isAnalysisSourceFile)) {
|
|
1451
1469
|
const from = featureForFile(file, args.featuresPath);
|
|
1452
1470
|
if (!from || !args.featureNames.has(from)) continue;
|
|
1453
1471
|
for (const reference of await localImportReferences(args.workspace, file)) {
|
|
@@ -1496,7 +1514,7 @@ async function addUnresolvedLocalImports(args: {
|
|
|
1496
1514
|
workspace: AnalysisWorkspace;
|
|
1497
1515
|
unresolved: AppMapUnresolvedReference[];
|
|
1498
1516
|
}): Promise<void> {
|
|
1499
|
-
for (const file of args.workspace.files.filter(
|
|
1517
|
+
for (const file of args.workspace.files.filter(isAnalysisSourceFile)) {
|
|
1500
1518
|
for (const reference of await localImportReferences(args.workspace, file)) {
|
|
1501
1519
|
if (reference.resolvedFile) continue;
|
|
1502
1520
|
args.unresolved.push({
|
|
@@ -1556,15 +1574,16 @@ function appMapContractSourceFiles(workspace: AnalysisWorkspace): string[] {
|
|
|
1556
1574
|
contractsPath === directoryPath(workspace.config.paths.features);
|
|
1557
1575
|
|
|
1558
1576
|
return workspace.files.filter((file) => {
|
|
1559
|
-
if (!file
|
|
1577
|
+
if (!isAnalysisSourceFile(file) || isAppTestFile(file)) return false;
|
|
1578
|
+
const sourcePath = stripSourceFileExtension(file);
|
|
1560
1579
|
if (featureOwned) {
|
|
1561
1580
|
const prefix = `${contractsPath}/`;
|
|
1562
1581
|
if (!file.startsWith(prefix)) return false;
|
|
1563
|
-
const parts =
|
|
1564
|
-
return parts.length === 2 && parts[1] === "contracts
|
|
1582
|
+
const parts = sourcePath.slice(prefix.length).split("/");
|
|
1583
|
+
return parts.length === 2 && parts[1] === "contracts";
|
|
1565
1584
|
}
|
|
1566
1585
|
return (
|
|
1567
|
-
|
|
1586
|
+
sourcePath === contractsPath || sourcePath.startsWith(`${contractsPath}/`)
|
|
1568
1587
|
);
|
|
1569
1588
|
});
|
|
1570
1589
|
}
|
|
@@ -1824,7 +1843,7 @@ function summarizeFeatureFiles(files: string[]): Record<string, number> {
|
|
|
1824
1843
|
client: files.filter((file) => file.includes("/client/")).length,
|
|
1825
1844
|
components: files.filter((file) => file.includes("/components/")).length,
|
|
1826
1845
|
domain: files.filter((file) => file.includes("/domain/")).length,
|
|
1827
|
-
tests: files.filter(
|
|
1846
|
+
tests: files.filter(isAppTestFile).length,
|
|
1828
1847
|
};
|
|
1829
1848
|
}
|
|
1830
1849
|
|
|
@@ -2017,14 +2036,6 @@ function sourceNodeEdge(
|
|
|
2017
2036
|
};
|
|
2018
2037
|
}
|
|
2019
2038
|
|
|
2020
|
-
function isTestFile(file: string): boolean {
|
|
2021
|
-
return /\.(test|spec)\.(ts|tsx|mts|cts)$/.test(file);
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
function isTypeScriptSource(file: string): boolean {
|
|
2025
|
-
return /\.(ts|tsx|mts|cts)$/.test(file) && !file.endsWith(".d.ts");
|
|
2026
|
-
}
|
|
2027
|
-
|
|
2028
2039
|
function staticPathPrefix(pattern: string): string {
|
|
2029
2040
|
const wildcard = pattern.search(/[?*[{]/);
|
|
2030
2041
|
return wildcard === -1 ? pattern : pattern.slice(0, wildcard);
|
package/src/check.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { readdir, readFile, stat } from "node:fs/promises";
|
|
1
|
+
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createPainter } from "./ansi.js";
|
|
4
4
|
import { detectPackageManager, spawnCommand } from "./db.js";
|
|
5
|
+
import {
|
|
6
|
+
type GitSourceSnapshot,
|
|
7
|
+
readGitSourceSnapshot,
|
|
8
|
+
} from "./git-changes.js";
|
|
5
9
|
import {
|
|
6
10
|
applyDoctorFixes,
|
|
7
11
|
formatDoctor,
|
|
@@ -33,6 +37,14 @@ export type CheckAppResult = {
|
|
|
33
37
|
ok: boolean;
|
|
34
38
|
fixes: InspectFix[];
|
|
35
39
|
steps: CheckStep[];
|
|
40
|
+
provenance: CheckProvenance;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Source-state evidence captured around one complete check run. */
|
|
44
|
+
export type CheckProvenance = {
|
|
45
|
+
status: "current" | "stale" | "unavailable";
|
|
46
|
+
start: GitSourceSnapshot;
|
|
47
|
+
end: GitSourceSnapshot;
|
|
36
48
|
};
|
|
37
49
|
|
|
38
50
|
export type CheckAppOptions = {
|
|
@@ -74,19 +86,34 @@ const defaultMaxOutputBytes = 64 * 1024;
|
|
|
74
86
|
export async function checkApp(
|
|
75
87
|
options: CheckAppOptions = {},
|
|
76
88
|
): Promise<CheckAppResult> {
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
89
|
+
options.signal?.throwIfAborted();
|
|
90
|
+
const targetDir = await resolveCheckTargetDir(options.cwd ?? process.cwd());
|
|
91
|
+
options.signal?.throwIfAborted();
|
|
92
|
+
// Validate the app root before fixes may write; reload execution metadata
|
|
93
|
+
// after the provenance baseline so the selected scripts are covered by it.
|
|
94
|
+
await readAppPackageScripts(targetDir);
|
|
95
|
+
options.signal?.throwIfAborted();
|
|
96
|
+
const fixes = options.fix
|
|
97
|
+
? await applyDoctorFixes({ cwd: targetDir, strict: true })
|
|
98
|
+
: [];
|
|
99
|
+
options.signal?.throwIfAborted();
|
|
80
100
|
const steps: CheckStep[] = [];
|
|
81
101
|
const record = (step: CheckStep): void => {
|
|
82
102
|
steps.push(step);
|
|
83
103
|
options.onStep?.(step);
|
|
84
104
|
};
|
|
85
105
|
|
|
106
|
+
const start = await readGitSourceSnapshot({
|
|
107
|
+
cwd: targetDir,
|
|
108
|
+
signal: options.signal,
|
|
109
|
+
});
|
|
110
|
+
options.signal?.throwIfAborted();
|
|
111
|
+
const scripts = await readAppPackageScripts(targetDir);
|
|
112
|
+
const runner = await detectPackageManager(targetDir);
|
|
86
113
|
options.signal?.throwIfAborted();
|
|
87
114
|
record(await lintStep(targetDir, options));
|
|
88
115
|
options.signal?.throwIfAborted();
|
|
89
|
-
const
|
|
116
|
+
const doctorStep = await runDoctorStep(targetDir, options, fixes);
|
|
90
117
|
record(doctorStep);
|
|
91
118
|
|
|
92
119
|
if (options.preflight || options.preflightConnect) {
|
|
@@ -105,16 +132,38 @@ export async function checkApp(
|
|
|
105
132
|
record(await scriptStep(targetDir, runner, script, scripts, options));
|
|
106
133
|
}
|
|
107
134
|
|
|
135
|
+
options.signal?.throwIfAborted();
|
|
136
|
+
const end = await readGitSourceSnapshot({
|
|
137
|
+
cwd: targetDir,
|
|
138
|
+
signal: options.signal,
|
|
139
|
+
});
|
|
140
|
+
options.signal?.throwIfAborted();
|
|
141
|
+
const provenance = checkProvenance(start, end);
|
|
142
|
+
|
|
108
143
|
return {
|
|
109
144
|
schemaVersion: 1,
|
|
110
145
|
targetDir,
|
|
111
146
|
runner,
|
|
112
|
-
ok:
|
|
147
|
+
ok:
|
|
148
|
+
steps.every((step) => step.status !== "failed") &&
|
|
149
|
+
provenance.status !== "stale",
|
|
113
150
|
fixes,
|
|
114
151
|
steps,
|
|
152
|
+
provenance,
|
|
115
153
|
};
|
|
116
154
|
}
|
|
117
155
|
|
|
156
|
+
async function resolveCheckTargetDir(cwd: string): Promise<string> {
|
|
157
|
+
const targetDir = path.resolve(cwd);
|
|
158
|
+
try {
|
|
159
|
+
return await realpath(targetDir);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
162
|
+
if (code === "ENOENT" || code === "ENOTDIR") return targetDir;
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
118
167
|
/**
|
|
119
168
|
* Format a completed step as a single status line, with the failing step's
|
|
120
169
|
* output indented underneath.
|
|
@@ -161,9 +210,35 @@ export function formatCheckSummary(
|
|
|
161
210
|
const parts = [`${counts.passed} passed`];
|
|
162
211
|
if (counts.failed > 0) parts.push(paint(`${counts.failed} failed`, "red"));
|
|
163
212
|
if (counts.skipped > 0) parts.push(`${counts.skipped} skipped`);
|
|
213
|
+
if (result.provenance.status === "stale") {
|
|
214
|
+
parts.push(paint("source state stale", "red"));
|
|
215
|
+
}
|
|
164
216
|
return parts.join(", ");
|
|
165
217
|
}
|
|
166
218
|
|
|
219
|
+
/** Format the source state captured before and after validation. */
|
|
220
|
+
export function formatCheckProvenance(
|
|
221
|
+
result: CheckAppResult,
|
|
222
|
+
options: { color?: boolean } = {},
|
|
223
|
+
): string {
|
|
224
|
+
const paint = createPainter(options.color);
|
|
225
|
+
const { provenance } = result;
|
|
226
|
+
if (provenance.status === "unavailable") {
|
|
227
|
+
return `${paint("-", "cyan")} Source provenance unavailable (not a Git worktree)`;
|
|
228
|
+
}
|
|
229
|
+
if (provenance.status === "stale") {
|
|
230
|
+
return `${paint("✗", "red")} Source state changed while checks were running; rerun beignet check`;
|
|
231
|
+
}
|
|
232
|
+
const snapshot = provenance.end;
|
|
233
|
+
if (snapshot.kind !== "git") {
|
|
234
|
+
throw new Error("Current check provenance must contain a Git snapshot.");
|
|
235
|
+
}
|
|
236
|
+
const head = snapshot.comparison.head
|
|
237
|
+
? `HEAD ${snapshot.comparison.head.slice(0, 8)}`
|
|
238
|
+
: "unborn Git repository";
|
|
239
|
+
return `${paint("✓", "green")} Source state current (${head}, ${shortHash(snapshot.repositoryChangeHash)})`;
|
|
240
|
+
}
|
|
241
|
+
|
|
167
242
|
/**
|
|
168
243
|
* Format a full result at once (JSON-free callers and tests).
|
|
169
244
|
*/
|
|
@@ -172,6 +247,7 @@ export function formatCheck(
|
|
|
172
247
|
options: { color?: boolean } = {},
|
|
173
248
|
): string {
|
|
174
249
|
const lines = result.steps.map((step) => formatCheckStep(step, options));
|
|
250
|
+
lines.push(formatCheckProvenance(result, options));
|
|
175
251
|
return `${lines.join("\n")}\n\n${formatCheckSummary(result, options)}`;
|
|
176
252
|
}
|
|
177
253
|
|
|
@@ -195,11 +271,9 @@ async function lintStep(
|
|
|
195
271
|
async function runDoctorStep(
|
|
196
272
|
targetDir: string,
|
|
197
273
|
options: CheckAppOptions,
|
|
198
|
-
|
|
274
|
+
fixes: InspectFix[],
|
|
275
|
+
): Promise<CheckStep> {
|
|
199
276
|
const startedAt = Date.now();
|
|
200
|
-
const fixes = options.fix
|
|
201
|
-
? await applyDoctorFixes({ cwd: targetDir, strict: true })
|
|
202
|
-
: [];
|
|
203
277
|
const result = await inspectApp({ cwd: targetDir, strict: true });
|
|
204
278
|
result.fixes = fixes;
|
|
205
279
|
const failed = result.diagnostics.some(
|
|
@@ -208,24 +282,40 @@ async function runDoctorStep(
|
|
|
208
282
|
);
|
|
209
283
|
|
|
210
284
|
return {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
durationMs: Date.now() - startedAt,
|
|
224
|
-
},
|
|
225
|
-
fixes,
|
|
285
|
+
name: "beignet doctor --strict",
|
|
286
|
+
command: "beignet doctor --strict",
|
|
287
|
+
status: failed ? "failed" : "passed",
|
|
288
|
+
...(fixes.length > 0
|
|
289
|
+
? {
|
|
290
|
+
detail: `${fixes.length} ${fixes.length === 1 ? "fix" : "fixes"} applied`,
|
|
291
|
+
}
|
|
292
|
+
: {}),
|
|
293
|
+
...(failed
|
|
294
|
+
? { output: formatDoctor(result, { color: options.color }) }
|
|
295
|
+
: {}),
|
|
296
|
+
durationMs: Date.now() - startedAt,
|
|
226
297
|
};
|
|
227
298
|
}
|
|
228
299
|
|
|
300
|
+
function checkProvenance(
|
|
301
|
+
start: GitSourceSnapshot,
|
|
302
|
+
end: GitSourceSnapshot,
|
|
303
|
+
): CheckProvenance {
|
|
304
|
+
if (start.kind === "unavailable" && end.kind === "unavailable") {
|
|
305
|
+
return { status: "unavailable", start, end };
|
|
306
|
+
}
|
|
307
|
+
const current =
|
|
308
|
+
start.kind === "git" &&
|
|
309
|
+
end.kind === "git" &&
|
|
310
|
+
start.repositoryRoot === end.repositoryRoot &&
|
|
311
|
+
start.repositoryChangeHash === end.repositoryChangeHash;
|
|
312
|
+
return { status: current ? "current" : "stale", start, end };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function shortHash(hash: string): string {
|
|
316
|
+
return `${hash.slice(0, 15)}…`;
|
|
317
|
+
}
|
|
318
|
+
|
|
229
319
|
async function scriptStep(
|
|
230
320
|
targetDir: string,
|
|
231
321
|
runner: string,
|