next-arch-map 0.1.13 → 0.1.15

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.
@@ -17,41 +17,49 @@ export async function analyzeEndpointsToDb(options) {
17
17
  const seenRouteFiles = new Set();
18
18
  for (const scanRoot of scanRoots) {
19
19
  for (const filePath of walkDirectory(scanRoot)) {
20
- if (seenRouteFiles.has(filePath) || isIgnoredSourceFile(filePath) || !isRouteHandlerFile(filePath)) {
20
+ if (seenRouteFiles.has(filePath) ||
21
+ isIgnoredSourceFile(filePath) ||
22
+ !isRouteHandlerFile(filePath)) {
21
23
  continue;
22
24
  }
23
25
  seenRouteFiles.add(filePath);
24
- const endpointPath = getEndpointRouteFromFile(scanRoot, filePath);
25
- const { availableMethods, dbUsageByModel } = analyzeEndpoint(filePath, projectRoot, moduleCache, sourceFileCache, dbClientIdentifiers);
26
- const endpointNode = ensureNode(nodes, nodeIds, buildEndpointNode(endpointPath, filePath));
27
- const handlerMethods = availableMethods.size > 0 ? [...availableMethods] : [undefined];
28
- for (const methodName of handlerMethods) {
29
- const handlerNode = ensureNode(nodes, nodeIds, buildHandlerNode(endpointPath, filePath, methodName));
30
- const edgeKey = buildEdgeKey(endpointNode.id, handlerNode.id, "endpoint-handler");
31
- if (edgeKeys.has(edgeKey)) {
32
- continue;
26
+ try {
27
+ const endpointPath = getEndpointRouteFromFile(scanRoot, filePath);
28
+ const { availableMethods, dbUsageByModel } = analyzeEndpoint(filePath, projectRoot, moduleCache, sourceFileCache, dbClientIdentifiers);
29
+ const endpointNode = ensureNode(nodes, nodeIds, buildEndpointNode(endpointPath, filePath));
30
+ const handlerMethods = availableMethods.size > 0 ? [...availableMethods] : [undefined];
31
+ for (const methodName of handlerMethods) {
32
+ const handlerNode = ensureNode(nodes, nodeIds, buildHandlerNode(endpointPath, filePath, methodName));
33
+ const edgeKey = buildEdgeKey(endpointNode.id, handlerNode.id, "endpoint-handler");
34
+ if (edgeKeys.has(edgeKey)) {
35
+ continue;
36
+ }
37
+ edgeKeys.add(edgeKey);
38
+ edges.push({
39
+ from: endpointNode.id,
40
+ to: handlerNode.id,
41
+ kind: "endpoint-handler",
42
+ meta: methodName ? { method: methodName } : undefined,
43
+ });
33
44
  }
34
- edgeKeys.add(edgeKey);
35
- edges.push({
36
- from: endpointNode.id,
37
- to: handlerNode.id,
38
- kind: "endpoint-handler",
39
- meta: methodName ? { method: methodName } : undefined,
40
- });
41
- }
42
- for (const [modelName, dbUsage] of dbUsageByModel) {
43
- const dbNode = ensureNode(nodes, nodeIds, buildDbNode(modelName, dbUsage.filePath));
44
- const edgeKey = buildEdgeKey(endpointNode.id, dbNode.id, "endpoint-db");
45
- if (edgeKeys.has(edgeKey)) {
46
- continue;
45
+ for (const [modelName, dbUsage] of dbUsageByModel) {
46
+ const dbNode = ensureNode(nodes, nodeIds, buildDbNode(modelName, dbUsage.filePath));
47
+ const edgeKey = buildEdgeKey(endpointNode.id, dbNode.id, "endpoint-db");
48
+ if (edgeKeys.has(edgeKey)) {
49
+ continue;
50
+ }
51
+ edgeKeys.add(edgeKey);
52
+ edges.push({
53
+ from: endpointNode.id,
54
+ to: dbNode.id,
55
+ kind: "endpoint-db",
56
+ meta: dbUsage.actionName ? { action: dbUsage.actionName } : undefined,
57
+ });
47
58
  }
48
- edgeKeys.add(edgeKey);
49
- edges.push({
50
- from: endpointNode.id,
51
- to: dbNode.id,
52
- kind: "endpoint-db",
53
- meta: dbUsage.actionName ? { action: dbUsage.actionName } : undefined,
54
- });
59
+ }
60
+ catch (error) {
61
+ const relative = path.relative(projectRoot, filePath);
62
+ console.warn(`Warning: skipping ${relative}: ${error instanceof Error ? error.message : error}`);
55
63
  }
56
64
  }
57
65
  }
@@ -219,7 +227,14 @@ function getModuleInfo(filePath, state) {
219
227
  }
220
228
  const sourceFile = getSourceFile(filePath, state.sourceFileCache);
221
229
  if (!sourceFile) {
222
- throw new Error(`Could not parse ${path.relative(state.projectRoot, filePath)}.`);
230
+ const emptyModuleInfo = {
231
+ localDeclarations: new Map(),
232
+ importsByLocalName: new Map(),
233
+ exportsByName: new Map(),
234
+ };
235
+ console.warn(`Warning: could not parse ${path.relative(state.projectRoot, filePath)}, skipping.`);
236
+ state.moduleCache.set(filePath, emptyModuleInfo);
237
+ return emptyModuleInfo;
223
238
  }
224
239
  const localDeclarations = new Map();
225
240
  const importsByLocalName = new Map();
@@ -341,8 +356,10 @@ function getFunctionBodyNode(node) {
341
356
  return node.body;
342
357
  }
343
358
  function hasExportModifier(node) {
344
- return ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
359
+ return (ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ??
360
+ false);
345
361
  }
346
362
  function hasDefaultModifier(node) {
347
- return ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
363
+ return (ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) ??
364
+ false);
348
365
  }
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import ts from "typescript";
2
3
  import { buildActionNode, buildEdgeKey, buildEndpointNode, buildPageNode, collectStringConstants, ensureNode, getExistingDirectories, getPageRouteFromFile, getSourceFile, getStringLiteralValue, isIgnoredSourceFile, isPageFile, resolveProjectRoot, walkDirectory, } from "../utils.js";
3
4
  const DEFAULT_APP_DIRS = ["app", "src/app"];
@@ -25,59 +26,65 @@ export async function analyzePagesToEndpoints(options) {
25
26
  if (isIgnoredSourceFile(filePath)) {
26
27
  continue;
27
28
  }
28
- const sourceFile = getSourceFile(filePath, sourceFileCache);
29
- if (!sourceFile) {
30
- continue;
31
- }
32
- const httpCalls = collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods);
33
- // Non-page files (route handlers, layouts, helpers) under app/ only
34
- // contribute endpoint nodes without creating fake page flows.
35
- if (!isPageFile(filePath)) {
36
- for (const call of httpCalls) {
37
- ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
29
+ try {
30
+ const sourceFile = getSourceFile(filePath, sourceFileCache);
31
+ if (!sourceFile) {
32
+ continue;
38
33
  }
39
- continue;
40
- }
41
- const route = getPageRouteFromFile(appDir, filePath);
42
- ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
43
- for (const call of httpCalls) {
44
- const nextCallIndex = (callIndexByRoute.get(route) ?? 0) + 1;
45
- callIndexByRoute.set(route, nextCallIndex);
46
- const actionContext = inferActionContext(call.node, sourceFile, nextCallIndex);
47
- const actionId = allocateActionId(route, actionContext.id, actionIdCountByRoute);
48
- const pageNode = ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
49
- const actionNode = ensureNode(nodes, nodeIds, buildActionNode(route, actionId, filePath, actionContext.meta));
50
- const endpointNode = ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
51
- const pageActionKey = buildEdgeKey(pageNode.id, actionNode.id, "page-action");
52
- if (!edgeKeys.has(pageActionKey)) {
53
- edgeKeys.add(pageActionKey);
54
- edges.push({
55
- from: pageNode.id,
56
- to: actionNode.id,
57
- kind: "page-action",
58
- });
34
+ const httpCalls = collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods);
35
+ // Non-page files (route handlers, layouts, helpers) under app/ only
36
+ // contribute endpoint nodes without creating fake page flows.
37
+ if (!isPageFile(filePath)) {
38
+ for (const call of httpCalls) {
39
+ ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
40
+ }
41
+ continue;
59
42
  }
60
- const actionEndpointKey = buildEdgeKey(actionNode.id, endpointNode.id, "action-endpoint");
61
- if (!edgeKeys.has(actionEndpointKey)) {
62
- edgeKeys.add(actionEndpointKey);
43
+ const route = getPageRouteFromFile(appDir, filePath);
44
+ ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
45
+ for (const call of httpCalls) {
46
+ const nextCallIndex = (callIndexByRoute.get(route) ?? 0) + 1;
47
+ callIndexByRoute.set(route, nextCallIndex);
48
+ const actionContext = inferActionContext(call.node, sourceFile, nextCallIndex);
49
+ const actionId = allocateActionId(route, actionContext.id, actionIdCountByRoute);
50
+ const pageNode = ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
51
+ const actionNode = ensureNode(nodes, nodeIds, buildActionNode(route, actionId, filePath, actionContext.meta));
52
+ const endpointNode = ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
53
+ const pageActionKey = buildEdgeKey(pageNode.id, actionNode.id, "page-action");
54
+ if (!edgeKeys.has(pageActionKey)) {
55
+ edgeKeys.add(pageActionKey);
56
+ edges.push({
57
+ from: pageNode.id,
58
+ to: actionNode.id,
59
+ kind: "page-action",
60
+ });
61
+ }
62
+ const actionEndpointKey = buildEdgeKey(actionNode.id, endpointNode.id, "action-endpoint");
63
+ if (!edgeKeys.has(actionEndpointKey)) {
64
+ edgeKeys.add(actionEndpointKey);
65
+ edges.push({
66
+ from: actionNode.id,
67
+ to: endpointNode.id,
68
+ kind: "action-endpoint",
69
+ meta: call.method ? { method: call.method } : undefined,
70
+ });
71
+ }
72
+ const edgeKey = buildEdgeKey(pageNode.id, endpointNode.id, "page-endpoint");
73
+ if (edgeKeys.has(edgeKey)) {
74
+ continue;
75
+ }
76
+ edgeKeys.add(edgeKey);
63
77
  edges.push({
64
- from: actionNode.id,
78
+ from: pageNode.id,
65
79
  to: endpointNode.id,
66
- kind: "action-endpoint",
80
+ kind: "page-endpoint",
67
81
  meta: call.method ? { method: call.method } : undefined,
68
82
  });
69
83
  }
70
- const edgeKey = buildEdgeKey(pageNode.id, endpointNode.id, "page-endpoint");
71
- if (edgeKeys.has(edgeKey)) {
72
- continue;
73
- }
74
- edgeKeys.add(edgeKey);
75
- edges.push({
76
- from: pageNode.id,
77
- to: endpointNode.id,
78
- kind: "page-endpoint",
79
- meta: call.method ? { method: call.method } : undefined,
80
- });
84
+ }
85
+ catch (error) {
86
+ const relative = path.relative(projectRoot, filePath);
87
+ console.warn(`Warning: skipping ${relative}: ${error instanceof Error ? error.message : error}`);
81
88
  }
82
89
  }
83
90
  }
@@ -86,12 +93,18 @@ export async function analyzePagesToEndpoints(options) {
86
93
  if (isIgnoredSourceFile(filePath)) {
87
94
  continue;
88
95
  }
89
- const sourceFile = getSourceFile(filePath, sourceFileCache);
90
- if (!sourceFile) {
91
- continue;
96
+ try {
97
+ const sourceFile = getSourceFile(filePath, sourceFileCache);
98
+ if (!sourceFile) {
99
+ continue;
100
+ }
101
+ for (const call of collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods)) {
102
+ ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
103
+ }
92
104
  }
93
- for (const call of collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods)) {
94
- ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
105
+ catch (error) {
106
+ const relative = path.relative(projectRoot, filePath);
107
+ console.warn(`Warning: skipping ${relative}: ${error instanceof Error ? error.message : error}`);
95
108
  }
96
109
  }
97
110
  }
@@ -263,12 +276,15 @@ function isTopLevelNamedFunctionLike(node, sourceFile) {
263
276
  return false;
264
277
  }
265
278
  function getFunctionLikeName(node) {
266
- if ((ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node)) &&
279
+ if ((ts.isFunctionDeclaration(node) ||
280
+ ts.isFunctionExpression(node) ||
281
+ ts.isMethodDeclaration(node)) &&
267
282
  node.name &&
268
283
  ts.isIdentifier(node.name)) {
269
284
  return node.name.text;
270
285
  }
271
- if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && ts.isVariableDeclaration(node.parent)) {
286
+ if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
287
+ ts.isVariableDeclaration(node.parent)) {
272
288
  return ts.isIdentifier(node.parent.name) ? node.parent.name.text : undefined;
273
289
  }
274
290
  return undefined;
@@ -24,21 +24,27 @@ export async function analyzePagesToUi(options) {
24
24
  if (!isPageFile(filePath)) {
25
25
  continue;
26
26
  }
27
- const route = getPageRouteFromFile(appDir, filePath);
28
- const pageNode = ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
29
- const components = collectUiComponentUsages(filePath, projectRoot, uiPathMatchers);
30
- for (const component of components) {
31
- const uiNode = ensureNode(nodes, nodeIds, buildUiNode(component.componentName, component.filePath));
32
- const edgeKey = buildEdgeKey(pageNode.id, uiNode.id, "page-ui");
33
- if (edgeKeys.has(edgeKey)) {
34
- continue;
27
+ try {
28
+ const route = getPageRouteFromFile(appDir, filePath);
29
+ const pageNode = ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
30
+ const components = collectUiComponentUsages(filePath, projectRoot, uiPathMatchers);
31
+ for (const component of components) {
32
+ const uiNode = ensureNode(nodes, nodeIds, buildUiNode(component.componentName, component.filePath));
33
+ const edgeKey = buildEdgeKey(pageNode.id, uiNode.id, "page-ui");
34
+ if (edgeKeys.has(edgeKey)) {
35
+ continue;
36
+ }
37
+ edgeKeys.add(edgeKey);
38
+ edges.push({
39
+ from: pageNode.id,
40
+ to: uiNode.id,
41
+ kind: "page-ui",
42
+ });
35
43
  }
36
- edgeKeys.add(edgeKey);
37
- edges.push({
38
- from: pageNode.id,
39
- to: uiNode.id,
40
- kind: "page-ui",
41
- });
44
+ }
45
+ catch (error) {
46
+ const relative = path.relative(projectRoot, filePath);
47
+ console.warn(`Warning: skipping ${relative}: ${error instanceof Error ? error.message : error}`);
42
48
  }
43
49
  }
44
50
  }
package/dist/diff.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { buildEdgeKey } from "./utils.js";
2
2
  function nodeEqual(a, b) {
3
- return a.type === b.type && a.label === b.label && JSON.stringify(a.meta) === JSON.stringify(b.meta);
3
+ return (a.type === b.type && a.label === b.label && JSON.stringify(a.meta) === JSON.stringify(b.meta));
4
4
  }
5
5
  function edgeEqual(a, b) {
6
6
  return JSON.stringify(a.meta) === JSON.stringify(b.meta);
package/dist/index.d.ts CHANGED
@@ -17,4 +17,4 @@ export { analyzePagesToEndpoints } from "./analyzers/pagesToEndpoints.js";
17
17
  export { analyzeEndpointsToDb } from "./analyzers/endpointsToDb.js";
18
18
  export { analyzePagesToUi } from "./analyzers/pagesToUi.js";
19
19
  export { mergeGraphs, mergePartial } from "./merge.js";
20
- export { getDbModelsForPage, getEndpointsForPage, getPagesForDbModel, } from "./query.js";
20
+ export { getDbModelsForPage, getEndpointsForPage, getPagesForDbModel } from "./query.js";
package/dist/index.js CHANGED
@@ -27,4 +27,4 @@ export { analyzePagesToEndpoints } from "./analyzers/pagesToEndpoints.js";
27
27
  export { analyzeEndpointsToDb } from "./analyzers/endpointsToDb.js";
28
28
  export { analyzePagesToUi } from "./analyzers/pagesToUi.js";
29
29
  export { mergeGraphs, mergePartial } from "./merge.js";
30
- export { getDbModelsForPage, getEndpointsForPage, getPagesForDbModel, } from "./query.js";
30
+ export { getDbModelsForPage, getEndpointsForPage, getPagesForDbModel } from "./query.js";
package/dist/merge.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { buildEdgeKey, mergeEdge, mergeNode } from "./utils.js";
2
2
  export function mergeGraphs(graphs) {
3
- return graphs.reduce((accumulator, graph) => mergePartial(accumulator, graph), { nodes: [], edges: [] });
3
+ return graphs.reduce((accumulator, graph) => mergePartial(accumulator, graph), {
4
+ nodes: [],
5
+ edges: [],
6
+ });
4
7
  }
5
8
  export function mergePartial(base, additions) {
6
9
  const nodesById = new Map();
package/dist/serve.js CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import chokidar from "chokidar";
4
4
  import { analyzeProject } from "./index.js";
5
5
  import { diffGraphs } from "./diff.js";
6
- import { getDbModelsForPage, getEndpointsForPage, getPagesForDbModel, } from "./query.js";
6
+ import { getDbModelsForPage, getEndpointsForPage, getPagesForDbModel } from "./query.js";
7
7
  export async function serve(options) {
8
8
  const projectRoot = path.resolve(options.projectRoot);
9
9
  const port = options.port ?? 4321;
package/dist/utils.js CHANGED
@@ -74,8 +74,7 @@ export function getEndpointRouteFromFile(scanRoot, filePath) {
74
74
  if (!ROUTE_FILE_PATTERN.test(fileName)) {
75
75
  return prefixedSegments.length === 0 ? "/" : `/${prefixedSegments.join("/")}`;
76
76
  }
77
- if (prefixedSegments.length > 1 &&
78
- prefixedSegments[0] === prefixedSegments[1]) {
77
+ if (prefixedSegments.length > 1 && prefixedSegments[0] === prefixedSegments[1]) {
79
78
  prefixedSegments.shift();
80
79
  }
81
80
  return prefixedSegments.length === 0 ? "/" : `/${prefixedSegments.join("/")}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-arch-map",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Static analyzer that builds a multi-layer architecture graph for Next.js-style apps.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,6 +26,9 @@
26
26
  "build": "tsc -p tsconfig.json",
27
27
  "check": "tsc --noEmit -p tsconfig.json",
28
28
  "test": "vitest run",
29
+ "lint": "eslint src/ tests/",
30
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
31
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
29
32
  "dev": "node dist/cli.js dev"
30
33
  },
31
34
  "keywords": [
@@ -45,7 +48,12 @@
45
48
  "typescript": "^5.8.2"
46
49
  },
47
50
  "devDependencies": {
51
+ "@eslint/js": "^10.0.1",
48
52
  "@types/node": "^20.19.0",
53
+ "eslint": "^10.0.3",
54
+ "eslint-config-prettier": "^10.1.8",
55
+ "prettier": "^3.8.1",
56
+ "typescript-eslint": "^8.57.0",
49
57
  "vitest": "^4.1.0"
50
58
  }
51
59
  }
@@ -98,7 +98,7 @@ function buildEdgeKey(from: string, to: string, kind: EdgeKind): string {
98
98
  export function App() {
99
99
  const [graph, setGraph] = useState<Graph | null>(null);
100
100
  const [graphDiff, setGraphDiff] = useState<GraphDiff | null>(null);
101
- const [useServer, setUseServer] = useState(false);
101
+ const [useServer, setUseServer] = useState(true);
102
102
  const [serverUrl, setServerUrl] = useState("http://localhost:4321");
103
103
  const [focusedPageRoute, setFocusedPageRoute] = useState<string | null>(null);
104
104
  const [queryRoute, setQueryRoute] = useState("/dashboard");