next-arch-map 0.1.23 → 0.1.25

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.
@@ -5,6 +5,7 @@ type AnalyzePagesToEndpointsOptions = {
5
5
  extraScanDirs?: string[];
6
6
  httpClientIdentifiers?: string[];
7
7
  httpClientMethods?: string[];
8
+ sdkClientIdentifiers?: string[];
8
9
  };
9
10
  export declare function analyzePagesToEndpoints(options: AnalyzePagesToEndpointsOptions): Promise<Graph>;
10
11
  export {};
@@ -5,6 +5,7 @@ const DEFAULT_APP_DIRS = ["app", "src/app"];
5
5
  const DEFAULT_EXTRA_SCAN_DIRS = ["src/features", "src/services", "src/lib", "src/hooks"];
6
6
  const DEFAULT_HTTP_CLIENT_IDENTIFIERS = ["fetch", "axios", "apiClient"];
7
7
  const DEFAULT_HTTP_CLIENT_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
8
+ const DEFAULT_SDK_CLIENT_IDENTIFIERS = ["supabase"];
8
9
  export async function analyzePagesToEndpoints(options) {
9
10
  const projectRoot = resolveProjectRoot(options.projectRoot);
10
11
  const appDirs = getExistingDirectories(projectRoot, options.appDirs ?? DEFAULT_APP_DIRS);
@@ -14,6 +15,7 @@ export async function analyzePagesToEndpoints(options) {
14
15
  const extraScanDirs = getExistingDirectories(projectRoot, options.extraScanDirs ?? DEFAULT_EXTRA_SCAN_DIRS);
15
16
  const httpClientIdentifiers = new Set((options.httpClientIdentifiers ?? DEFAULT_HTTP_CLIENT_IDENTIFIERS).map((value) => value.trim()));
16
17
  const httpClientMethods = new Set((options.httpClientMethods ?? DEFAULT_HTTP_CLIENT_METHODS).map((value) => value.toLowerCase()));
18
+ const sdkClientIdentifiers = new Set((options.sdkClientIdentifiers ?? DEFAULT_SDK_CLIENT_IDENTIFIERS).map((value) => value.trim()));
17
19
  const nodes = [];
18
20
  const edges = [];
19
21
  const nodeIds = new Set();
@@ -34,7 +36,7 @@ export async function analyzePagesToEndpoints(options) {
34
36
  // Non-page files (route handlers, layouts, helpers) under app/ only
35
37
  // contribute endpoint nodes without creating fake page flows.
36
38
  if (!isPageFile(filePath)) {
37
- const httpCalls = collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods);
39
+ const httpCalls = collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods, sdkClientIdentifiers);
38
40
  for (const call of httpCalls) {
39
41
  ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
40
42
  }
@@ -42,7 +44,7 @@ export async function analyzePagesToEndpoints(options) {
42
44
  }
43
45
  // For page files, follow imports transitively to find HTTP calls
44
46
  // in hooks, utilities, and other local modules.
45
- const httpCalls = collectHttpCallsTransitively(filePath, projectRoot, httpClientIdentifiers, httpClientMethods, sourceFileCache);
47
+ const httpCalls = collectHttpCallsTransitively(filePath, projectRoot, httpClientIdentifiers, httpClientMethods, sdkClientIdentifiers, sourceFileCache);
46
48
  const route = getPageRouteFromFile(appDir, filePath);
47
49
  ensureNode(nodes, nodeIds, buildPageNode(route, filePath));
48
50
  for (const call of httpCalls) {
@@ -101,7 +103,7 @@ export async function analyzePagesToEndpoints(options) {
101
103
  if (!sourceFile) {
102
104
  continue;
103
105
  }
104
- for (const call of collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods)) {
106
+ for (const call of collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods, sdkClientIdentifiers)) {
105
107
  ensureNode(nodes, nodeIds, buildEndpointNode(call.endpoint, filePath));
106
108
  }
107
109
  }
@@ -135,7 +137,7 @@ function collectImportSpecifiers(sourceFile) {
135
137
  }
136
138
  return specifiers;
137
139
  }
138
- function collectHttpCallsTransitively(filePath, projectRoot, httpClientIdentifiers, httpClientMethods, sourceFileCache, visited) {
140
+ function collectHttpCallsTransitively(filePath, projectRoot, httpClientIdentifiers, httpClientMethods, sdkClientIdentifiers, sourceFileCache, visited) {
139
141
  const resolvedPath = path.resolve(filePath);
140
142
  const seen = visited ?? new Set();
141
143
  if (seen.has(resolvedPath)) {
@@ -146,18 +148,18 @@ function collectHttpCallsTransitively(filePath, projectRoot, httpClientIdentifie
146
148
  if (!sourceFile) {
147
149
  return [];
148
150
  }
149
- const calls = collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods);
151
+ const calls = collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods, sdkClientIdentifiers);
150
152
  for (const specifier of collectImportSpecifiers(sourceFile)) {
151
153
  const resolved = resolveLocalModulePath(filePath, specifier, projectRoot);
152
154
  if (!resolved) {
153
155
  continue;
154
156
  }
155
- const transitiveCalls = collectHttpCallsTransitively(resolved, projectRoot, httpClientIdentifiers, httpClientMethods, sourceFileCache, seen);
157
+ const transitiveCalls = collectHttpCallsTransitively(resolved, projectRoot, httpClientIdentifiers, httpClientMethods, sdkClientIdentifiers, sourceFileCache, seen);
156
158
  calls.push(...transitiveCalls);
157
159
  }
158
160
  return calls;
159
161
  }
160
- function collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods) {
162
+ function collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods, sdkClientIdentifiers) {
161
163
  const calls = [];
162
164
  const constMap = collectStringConstants(sourceFile);
163
165
  const visitNode = (node) => {
@@ -170,6 +172,16 @@ function collectHttpCalls(sourceFile, httpClientIdentifiers, httpClientMethods)
170
172
  node,
171
173
  });
172
174
  }
175
+ else {
176
+ const sdkCall = parseSdkCall(node, sdkClientIdentifiers);
177
+ if (sdkCall) {
178
+ calls.push({
179
+ endpoint: sdkCall.endpoint,
180
+ method: sdkCall.method,
181
+ node,
182
+ });
183
+ }
184
+ }
173
185
  }
174
186
  ts.forEachChild(node, visitNode);
175
187
  };
@@ -198,6 +210,32 @@ function parseHttpCall(node, constMap, httpClientIdentifiers, httpClientMethods)
198
210
  }
199
211
  return null;
200
212
  }
213
+ /**
214
+ * Detect SDK-style calls like `supabase.auth.signInWithPassword(...)`.
215
+ * Walks the property access chain to find a root identifier that matches
216
+ * one of the configured SDK client identifiers, then uses the full
217
+ * method chain as the endpoint label.
218
+ */
219
+ function parseSdkCall(node, sdkClientIdentifiers) {
220
+ if (!ts.isPropertyAccessExpression(node.expression)) {
221
+ return null;
222
+ }
223
+ const segments = [];
224
+ let current = node.expression;
225
+ while (ts.isPropertyAccessExpression(current)) {
226
+ segments.unshift(current.name.text);
227
+ current = current.expression;
228
+ }
229
+ if (!ts.isIdentifier(current) || !sdkClientIdentifiers.has(current.text)) {
230
+ return null;
231
+ }
232
+ // Need at least one segment beyond the client name (e.g. supabase.auth.method)
233
+ if (segments.length < 2) {
234
+ return null;
235
+ }
236
+ const endpoint = `${current.text}.${segments.join(".")}`;
237
+ return { endpoint };
238
+ }
201
239
  function getEndpointArgument(expression, constMap) {
202
240
  if (!expression) {
203
241
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-arch-map",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Static analyzer that builds a multi-layer architecture graph for Next.js-style apps.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,15 +9,12 @@ const ALL_NODE_TYPES: NodeType[] = [
9
9
  "page",
10
10
  "endpoint",
11
11
  "handler",
12
- "action",
13
12
  "db",
14
13
  ];
15
14
  const ALL_EDGE_KINDS: EdgeKind[] = [
16
15
  "page-endpoint",
17
16
  "endpoint-db",
18
17
  "endpoint-handler",
19
- "page-action",
20
- "action-endpoint",
21
18
  ];
22
19
 
23
20
 
@@ -15,7 +15,6 @@ const NODE_TYPE_COLORS: Record<NodeType, string> = {
15
15
  page: "bg-blue-500",
16
16
  endpoint: "bg-emerald-600",
17
17
  handler: "bg-teal-500",
18
- action: "bg-amber-400",
19
18
  db: "bg-red-600",
20
19
  };
21
20
 
@@ -31,7 +31,6 @@ const NODE_COLOR: Record<NodeType, string> = {
31
31
  endpoint: "#059669",
32
32
  db: "#dc2626",
33
33
  handler: "#14b8a6",
34
- action: "#fbbf24",
35
34
  };
36
35
 
37
36
  const NODE_BORDER: Record<NodeType, string> = {
@@ -39,15 +38,12 @@ const NODE_BORDER: Record<NodeType, string> = {
39
38
  endpoint: "#047857",
40
39
  db: "#b91c1c",
41
40
  handler: "#0d9488",
42
- action: "#f59e0b",
43
41
  };
44
42
 
45
43
  const EDGE_COLOR: Record<EdgeKind, string> = {
46
44
  "page-endpoint": "#06b6d4",
47
45
  "endpoint-db": "#f97316",
48
46
  "endpoint-handler": "#22c55e",
49
- "page-action": "#eab308",
50
- "action-endpoint": "#a855f7",
51
47
  };
52
48
 
53
49
  const DIFF_BORDER_COLOR: Record<DiffStatus, string> = {
@@ -241,7 +237,7 @@ export function GraphView(props: GraphViewProps) {
241
237
 
242
238
  // Compute layout without hover state — this is the expensive part
243
239
  const { flowNodes: baseFlowNodes, flowEdges: baseFlowEdges } = useMemo(() => {
244
- const typeOrder: NodeType[] = ["page", "action", "endpoint", "handler", "db"];
240
+ const typeOrder: NodeType[] = ["page", "endpoint", "handler", "db"];
245
241
  const visibleNodes = graph.nodes.filter((node) => visibleNodeTypes.has(node.type));
246
242
  const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));
247
243
  const nodesByType = new Map<NodeType, typeof visibleNodes>(
@@ -278,7 +274,7 @@ export function GraphView(props: GraphViewProps) {
278
274
  const isPage = node.type === "page";
279
275
  const screenshot = isPage ? (node.meta?.screenshot as string | undefined) : undefined;
280
276
  const description = node.meta?.description as string | undefined;
281
- const isDarkText = node.type === "action";
277
+ const isDarkText = false;
282
278
 
283
279
  const nodeType = isPage
284
280
  ? "pageNode"
@@ -308,7 +304,7 @@ export function GraphView(props: GraphViewProps) {
308
304
  border: `2px ${borderStyle} ${borderColor}`,
309
305
  padding: "10px 14px",
310
306
  background: NODE_COLOR[node.type],
311
- color: node.type === "action" ? "#1e293b" : "#ffffff",
307
+ color: "#ffffff",
312
308
  fontSize: 12,
313
309
  fontWeight: 600,
314
310
  fontFamily: "'Inter', -apple-system, sans-serif",
@@ -2,8 +2,7 @@ export type NodeType =
2
2
  | "page"
3
3
  | "endpoint"
4
4
  | "db"
5
- | "handler"
6
- | "action";
5
+ | "handler";
7
6
 
8
7
  export type Node = {
9
8
  id: string;