@elench/testkit 0.1.63 → 0.1.65
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/lib/coverage/backend-discovery.mjs +6 -30
- package/lib/coverage/evidence.mjs +15 -3
- package/lib/coverage/evidence.test.mjs +13 -4
- package/lib/coverage/fs-walk.mjs +2 -2
- package/lib/coverage/graph-builder.mjs +23 -24
- package/lib/coverage/next-ir-to-graph.mjs +240 -0
- package/node_modules/@elench/next-analysis/package.json +14 -0
- package/node_modules/@elench/next-analysis/src/api-routes.mjs +81 -0
- package/node_modules/@elench/next-analysis/src/api-routes.test.mjs +22 -0
- package/node_modules/@elench/next-analysis/src/app-root.mjs +7 -0
- package/node_modules/@elench/next-analysis/src/backend-links.mjs +31 -0
- package/node_modules/@elench/next-analysis/src/index.mjs +21 -0
- package/node_modules/@elench/next-analysis/src/pages.mjs +68 -0
- package/node_modules/@elench/next-analysis/src/project.mjs +94 -0
- package/node_modules/@elench/next-analysis/src/project.test.mjs +35 -0
- package/node_modules/@elench/next-analysis/src/route-tree.mjs +621 -0
- package/node_modules/@elench/next-analysis/src/routes.mjs +41 -0
- package/node_modules/@elench/next-analysis/src/routes.test.mjs +25 -0
- package/node_modules/@elench/next-analysis/src/server-actions.mjs +53 -0
- package/node_modules/@elench/next-analysis/src/server-actions.test.mjs +37 -0
- package/node_modules/@elench/next-analysis/src/shared.mjs +209 -0
- package/node_modules/@elench/next-analysis/src/swc.mjs +388 -0
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +10 -0
- package/node_modules/@elench/ts-analysis/src/callables.mjs +135 -0
- package/node_modules/@elench/ts-analysis/src/callables.test.mjs +55 -0
- package/node_modules/@elench/ts-analysis/src/exports.mjs +69 -0
- package/node_modules/@elench/ts-analysis/src/exports.test.mjs +50 -0
- package/node_modules/@elench/ts-analysis/src/index.mjs +14 -0
- package/node_modules/@elench/ts-analysis/src/jsx.mjs +69 -0
- package/node_modules/@elench/ts-analysis/src/jsx.test.mjs +43 -0
- package/node_modules/@elench/ts-analysis/src/project.mjs +100 -0
- package/node_modules/@elench/ts-analysis/src/project.test.mjs +54 -0
- package/node_modules/@elench/ts-analysis/src/requests.mjs +141 -0
- package/node_modules/@elench/ts-analysis/src/requests.test.mjs +35 -0
- package/node_modules/@elench/ts-analysis/src/resolution.mjs +53 -0
- package/node_modules/@elench/ts-analysis/src/shared.mjs +32 -0
- package/node_modules/@elench/ts-analysis/src/syntax.mjs +27 -0
- package/node_modules/@next/routing/README.md +91 -0
- package/node_modules/@next/routing/dist/__tests__/captures.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/conditions.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/dynamic-after-rewrites.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/i18n-resolve-routes.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/i18n.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/middleware.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/normalize-next-data.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/redirects.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/resolve-routes.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/__tests__/rewrites.test.d.ts +1 -0
- package/node_modules/@next/routing/dist/destination.d.ts +22 -0
- package/node_modules/@next/routing/dist/i18n.d.ts +48 -0
- package/node_modules/@next/routing/dist/index.d.ts +5 -0
- package/node_modules/@next/routing/dist/index.js +1 -0
- package/node_modules/@next/routing/dist/matchers.d.ts +12 -0
- package/node_modules/@next/routing/dist/middleware.d.ts +12 -0
- package/node_modules/@next/routing/dist/next-data.d.ts +10 -0
- package/node_modules/@next/routing/dist/resolve-routes.d.ts +2 -0
- package/node_modules/@next/routing/dist/types.d.ts +97 -0
- package/node_modules/@next/routing/package.json +39 -0
- package/node_modules/@swc/core/README.md +100 -0
- package/node_modules/@swc/core/Visitor.d.ts +218 -0
- package/node_modules/@swc/core/Visitor.js +1399 -0
- package/node_modules/@swc/core/binding.d.ts +59 -0
- package/node_modules/@swc/core/binding.js +368 -0
- package/node_modules/@swc/core/index.d.ts +120 -0
- package/node_modules/@swc/core/index.js +443 -0
- package/node_modules/@swc/core/package.json +120 -0
- package/node_modules/@swc/core/postinstall.js +148 -0
- package/node_modules/@swc/core/spack.d.ts +51 -0
- package/node_modules/@swc/core/spack.js +87 -0
- package/node_modules/@swc/core/util.d.ts +1 -0
- package/node_modules/@swc/core/util.js +104 -0
- package/node_modules/@swc/core-linux-x64-gnu/README.md +3 -0
- package/node_modules/@swc/core-linux-x64-gnu/package.json +46 -0
- package/node_modules/@swc/core-linux-x64-gnu/swc.linux-x64-gnu.node +0 -0
- package/node_modules/@swc/counter/CHANGELOG.md +7 -0
- package/node_modules/@swc/counter/README.md +7 -0
- package/node_modules/@swc/counter/index.js +1 -0
- package/node_modules/@swc/counter/package.json +27 -0
- package/node_modules/@swc/types/LICENSE +201 -0
- package/node_modules/@swc/types/README.md +4 -0
- package/node_modules/@swc/types/assumptions.d.ts +92 -0
- package/node_modules/@swc/types/assumptions.js +2 -0
- package/node_modules/@swc/types/index.d.ts +2049 -0
- package/node_modules/@swc/types/index.js +2 -0
- package/node_modules/@swc/types/package.json +40 -0
- package/package.json +7 -3
- package/lib/coverage/next-discovery.mjs +0 -205
- package/lib/coverage/next-static-analysis.mjs +0 -1047
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { routeFromAppFile } from "./routes.mjs";
|
|
4
|
+
import { compareByRoute, isPageFile, normalizePath, pageLabelFromRoute } from "./shared.mjs";
|
|
5
|
+
|
|
6
|
+
export function discoverPages({ rootDir, appRoot }) {
|
|
7
|
+
const pageFiles = walkFiles(appRoot).filter(isPageFile);
|
|
8
|
+
return pageFiles
|
|
9
|
+
.map((absolutePath) => {
|
|
10
|
+
const filePath = normalizePath(path.relative(rootDir, absolutePath));
|
|
11
|
+
const route = routeFromAppFile(appRoot, absolutePath);
|
|
12
|
+
const layoutFiles = collectLayoutFiles(rootDir, appRoot, absolutePath);
|
|
13
|
+
const rootFiles = [...layoutFiles, filePath];
|
|
14
|
+
return {
|
|
15
|
+
id: `page:${route}`,
|
|
16
|
+
kind: "page",
|
|
17
|
+
route,
|
|
18
|
+
label: pageLabelFromRoute(route),
|
|
19
|
+
filePath,
|
|
20
|
+
layoutFiles,
|
|
21
|
+
rootFiles,
|
|
22
|
+
};
|
|
23
|
+
})
|
|
24
|
+
.sort(compareByRoute);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function collectLayoutFiles(rootDir, appRoot, pageAbsolutePath) {
|
|
28
|
+
const rootFiles = [];
|
|
29
|
+
let currentDir = path.dirname(pageAbsolutePath);
|
|
30
|
+
const absoluteAppRoot = path.resolve(appRoot);
|
|
31
|
+
|
|
32
|
+
while (currentDir.startsWith(absoluteAppRoot)) {
|
|
33
|
+
const layoutFile = resolveRouteCompanion(currentDir, "layout");
|
|
34
|
+
if (layoutFile) rootFiles.push(normalizePath(path.relative(rootDir, layoutFile)));
|
|
35
|
+
if (currentDir === absoluteAppRoot) break;
|
|
36
|
+
currentDir = path.dirname(currentDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return [...new Set(rootFiles.reverse())];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveRouteCompanion(directoryPath, baseName) {
|
|
43
|
+
const candidates = [
|
|
44
|
+
path.join(directoryPath, `${baseName}.tsx`),
|
|
45
|
+
path.join(directoryPath, `${baseName}.ts`),
|
|
46
|
+
path.join(directoryPath, `${baseName}.jsx`),
|
|
47
|
+
path.join(directoryPath, `${baseName}.js`),
|
|
48
|
+
];
|
|
49
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function walkFiles(rootDir) {
|
|
53
|
+
const results = [];
|
|
54
|
+
const queue = [rootDir];
|
|
55
|
+
while (queue.length > 0) {
|
|
56
|
+
const current = queue.pop();
|
|
57
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
58
|
+
if (entry.isSymbolicLink()) continue;
|
|
59
|
+
const absolutePath = path.join(current, entry.name);
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
queue.push(absolutePath);
|
|
62
|
+
} else if (entry.isFile()) {
|
|
63
|
+
results.push(absolutePath);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return results.sort((left, right) => left.localeCompare(right));
|
|
68
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import nextRouting from "@next/routing";
|
|
4
|
+
import { findNextAppRoot } from "./app-root.mjs";
|
|
5
|
+
import { discoverApiRoutes } from "./api-routes.mjs";
|
|
6
|
+
import { discoverPages } from "./pages.mjs";
|
|
7
|
+
import { analyzeRouteTree } from "./route-tree.mjs";
|
|
8
|
+
import { discoverServerActions } from "./server-actions.mjs";
|
|
9
|
+
import { compareByPath, compareByRoute, createDiagnostic, normalizePath, readFileIfExists } from "./shared.mjs";
|
|
10
|
+
|
|
11
|
+
export function createNextAnalysisProject({ rootDir, buildDir = null, nextConfigPath = null } = {}) {
|
|
12
|
+
const { resolveRoutes } = nextRouting;
|
|
13
|
+
const absoluteRoot = path.resolve(rootDir || process.cwd());
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
const moduleCache = new Map();
|
|
16
|
+
const routeTreeCache = new Map();
|
|
17
|
+
const appRoot = findNextAppRoot(absoluteRoot);
|
|
18
|
+
|
|
19
|
+
const project = {
|
|
20
|
+
rootDir: absoluteRoot,
|
|
21
|
+
buildDir: buildDir ? path.resolve(absoluteRoot, buildDir) : null,
|
|
22
|
+
nextConfigPath: nextConfigPath ? path.resolve(absoluteRoot, nextConfigPath) : null,
|
|
23
|
+
findAppRoot() {
|
|
24
|
+
return appRoot;
|
|
25
|
+
},
|
|
26
|
+
readSourceFile(relativeFilePath) {
|
|
27
|
+
const absolutePath = path.join(absoluteRoot, relativeFilePath);
|
|
28
|
+
return readFileIfExists(absolutePath);
|
|
29
|
+
},
|
|
30
|
+
getDiagnostics() {
|
|
31
|
+
return [...diagnostics];
|
|
32
|
+
},
|
|
33
|
+
loadModule(relativeFilePath) {
|
|
34
|
+
return moduleCache.get(relativeFilePath) || null;
|
|
35
|
+
},
|
|
36
|
+
setModule(relativeFilePath, moduleInfo) {
|
|
37
|
+
moduleCache.set(relativeFilePath, moduleInfo);
|
|
38
|
+
},
|
|
39
|
+
discoverPages() {
|
|
40
|
+
if (!appRoot) {
|
|
41
|
+
diagnostics.push(createDiagnostic({
|
|
42
|
+
level: "warn",
|
|
43
|
+
code: "missing-app-root",
|
|
44
|
+
message: `No Next app root found under "${absoluteRoot}".`,
|
|
45
|
+
}));
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return discoverPages({ rootDir: absoluteRoot, appRoot }).sort(compareByRoute);
|
|
49
|
+
},
|
|
50
|
+
discoverApiRoutes() {
|
|
51
|
+
if (!appRoot) return [];
|
|
52
|
+
return discoverApiRoutes({ rootDir: absoluteRoot, appRoot }).sort((left, right) => {
|
|
53
|
+
return left.requestPath.localeCompare(right.requestPath) || left.method.localeCompare(right.method);
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
discoverServerActions() {
|
|
57
|
+
if (!appRoot) return [];
|
|
58
|
+
return discoverServerActions({ rootDir: absoluteRoot, appRoot }).sort(compareByPath);
|
|
59
|
+
},
|
|
60
|
+
analyzeRouteTree(routeOrPageFile) {
|
|
61
|
+
if (!appRoot) return null;
|
|
62
|
+
const key = normalizePath(routeOrPageFile);
|
|
63
|
+
if (routeTreeCache.has(key)) return routeTreeCache.get(key);
|
|
64
|
+
const result = analyzeRouteTree({ project, routeOrPageFile, diagnostics });
|
|
65
|
+
routeTreeCache.set(key, result);
|
|
66
|
+
return result;
|
|
67
|
+
},
|
|
68
|
+
async loadBuildRouting() {
|
|
69
|
+
if (!project.buildDir || !fs.existsSync(project.buildDir)) return null;
|
|
70
|
+
return {
|
|
71
|
+
async resolve(url, pathnames = []) {
|
|
72
|
+
return resolveRoutes({
|
|
73
|
+
url: new URL(url),
|
|
74
|
+
basePath: "",
|
|
75
|
+
requestBody: null,
|
|
76
|
+
headers: new Headers(),
|
|
77
|
+
pathnames,
|
|
78
|
+
routes: {
|
|
79
|
+
beforeMiddleware: [],
|
|
80
|
+
beforeFiles: [],
|
|
81
|
+
afterFiles: [],
|
|
82
|
+
dynamicRoutes: [],
|
|
83
|
+
onMatch: [],
|
|
84
|
+
fallback: [],
|
|
85
|
+
},
|
|
86
|
+
invokeMiddleware: async () => ({}),
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return project;
|
|
94
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createNextAnalysisProject } from "./index.mjs";
|
|
4
|
+
|
|
5
|
+
const fixtureRoot = path.resolve(
|
|
6
|
+
"/home/georgedlr/workspace/elench/testkit/test/fixtures/integration/next-route-tree-product"
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
describe("@elench/next-analysis project", () => {
|
|
10
|
+
it("discovers pages, api routes, and route trees from a Next fixture", () => {
|
|
11
|
+
const project = createNextAnalysisProject({ rootDir: fixtureRoot });
|
|
12
|
+
|
|
13
|
+
expect(project.findAppRoot()).toMatch(/src\/app$/u);
|
|
14
|
+
expect(project.discoverPages().map((page) => page.route)).toEqual(["/events", "/projects"]);
|
|
15
|
+
expect(project.discoverApiRoutes().map((route) => `${route.method} ${route.requestPath}`)).toEqual([
|
|
16
|
+
"GET /api/projects",
|
|
17
|
+
"POST /api/projects",
|
|
18
|
+
"GET /api/projects/[projectId]/events",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const routeTree = project.analyzeRouteTree("/projects");
|
|
22
|
+
expect(routeTree.reachableModules).toEqual(
|
|
23
|
+
expect.arrayContaining([
|
|
24
|
+
"src/app/(dashboard)/projects/page.tsx",
|
|
25
|
+
"src/components/project-context.tsx",
|
|
26
|
+
])
|
|
27
|
+
);
|
|
28
|
+
expect(routeTree.requests).toEqual(
|
|
29
|
+
expect.arrayContaining([
|
|
30
|
+
expect.objectContaining({ ownerKind: "page", method: "GET", path: "/api/projects" }),
|
|
31
|
+
expect.objectContaining({ ownerKind: "action", method: "POST", path: "/api/projects" }),
|
|
32
|
+
])
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
});
|