next-arch-map 0.1.0

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/dist/serve.js ADDED
@@ -0,0 +1,166 @@
1
+ import http from "node:http";
2
+ import path from "node:path";
3
+ import chokidar from "chokidar";
4
+ import { analyzeProject } from "./index.js";
5
+ import { diffGraphs } from "./diff.js";
6
+ import { getDbModelsForPage, getEndpointsForPage, getPagesForDbModel, } from "./query.js";
7
+ export async function serve(options) {
8
+ const projectRoot = path.resolve(options.projectRoot);
9
+ const port = options.port ?? 4321;
10
+ let currentGraph = null;
11
+ let previousGraph = null;
12
+ let isAnalyzing = false;
13
+ let rerunRequested = false;
14
+ async function runAnalysis() {
15
+ if (isAnalyzing) {
16
+ rerunRequested = true;
17
+ return;
18
+ }
19
+ isAnalyzing = true;
20
+ try {
21
+ const nextGraph = await analyzeProject({
22
+ projectRoot,
23
+ appDirs: options.appDirs,
24
+ });
25
+ previousGraph = currentGraph;
26
+ currentGraph = nextGraph;
27
+ const pageCount = currentGraph.nodes.filter((node) => node.type === "page").length;
28
+ const endpointCount = currentGraph.nodes.filter((node) => node.type === "endpoint").length;
29
+ const handlerCount = currentGraph.nodes.filter((node) => node.type === "handler").length;
30
+ const actionCount = currentGraph.nodes.filter((node) => node.type === "action").length;
31
+ const dbCount = currentGraph.nodes.filter((node) => node.type === "db").length;
32
+ const uiCount = currentGraph.nodes.filter((node) => node.type === "ui").length;
33
+ console.log([
34
+ "mode=serve",
35
+ `pages=${pageCount}`,
36
+ `actions=${actionCount}`,
37
+ `endpoints=${endpointCount}`,
38
+ `handlers=${handlerCount}`,
39
+ `db=${dbCount}`,
40
+ `ui=${uiCount}`,
41
+ `nodes=${currentGraph.nodes.length}`,
42
+ `edges=${currentGraph.edges.length}`,
43
+ ].join(" "));
44
+ }
45
+ catch (error) {
46
+ console.error("Analysis error:", error);
47
+ }
48
+ finally {
49
+ isAnalyzing = false;
50
+ if (rerunRequested) {
51
+ rerunRequested = false;
52
+ await runAnalysis();
53
+ }
54
+ }
55
+ }
56
+ await runAnalysis();
57
+ const watcher = chokidar.watch(projectRoot, {
58
+ ignored: ["**/node_modules/**", "**/.git/**", "**/.next/**", "**/arch/**"],
59
+ ignoreInitial: true,
60
+ });
61
+ watcher.on("all", () => {
62
+ void runAnalysis();
63
+ });
64
+ const server = http.createServer((req, res) => {
65
+ res.setHeader("Access-Control-Allow-Origin", "*");
66
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
67
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
68
+ if (req.method === "OPTIONS") {
69
+ res.statusCode = 204;
70
+ res.end();
71
+ return;
72
+ }
73
+ const requestUrl = req.url ? new URL(req.url, `http://localhost:${port}`) : null;
74
+ const pathname = requestUrl?.pathname ?? "";
75
+ if (req.method === "GET" && pathname === "/graph") {
76
+ if (!currentGraph) {
77
+ res.statusCode = 503;
78
+ res.end("Graph not ready");
79
+ return;
80
+ }
81
+ res.setHeader("Content-Type", "application/json");
82
+ res.end(JSON.stringify(currentGraph));
83
+ return;
84
+ }
85
+ if (req.method === "GET" && pathname === "/diff") {
86
+ if (!currentGraph || !previousGraph) {
87
+ res.statusCode = 503;
88
+ res.end("Diff not ready");
89
+ return;
90
+ }
91
+ const diff = diffGraphs(previousGraph, currentGraph);
92
+ res.setHeader("Content-Type", "application/json");
93
+ res.end(JSON.stringify(diff));
94
+ return;
95
+ }
96
+ if (req.method === "GET" && pathname === "/query/page-to-endpoints") {
97
+ if (!currentGraph) {
98
+ res.statusCode = 503;
99
+ res.end("Graph not ready");
100
+ return;
101
+ }
102
+ const route = requestUrl?.searchParams.get("route") ?? "/";
103
+ const endpoints = getEndpointsForPage(currentGraph, route);
104
+ res.setHeader("Content-Type", "application/json");
105
+ res.end(JSON.stringify({ route, endpoints }));
106
+ return;
107
+ }
108
+ if (req.method === "GET" && pathname === "/query/page-to-db") {
109
+ if (!currentGraph) {
110
+ res.statusCode = 503;
111
+ res.end("Graph not ready");
112
+ return;
113
+ }
114
+ const route = requestUrl?.searchParams.get("route") ?? "/";
115
+ const dbModels = getDbModelsForPage(currentGraph, route);
116
+ res.setHeader("Content-Type", "application/json");
117
+ res.end(JSON.stringify({ route, dbModels }));
118
+ return;
119
+ }
120
+ if (req.method === "GET" && pathname === "/query/db-to-pages") {
121
+ if (!currentGraph) {
122
+ res.statusCode = 503;
123
+ res.end("Graph not ready");
124
+ return;
125
+ }
126
+ const model = requestUrl?.searchParams.get("model");
127
+ if (!model) {
128
+ res.statusCode = 400;
129
+ res.end("Missing model query parameter");
130
+ return;
131
+ }
132
+ const pages = getPagesForDbModel(currentGraph, model);
133
+ res.setHeader("Content-Type", "application/json");
134
+ res.end(JSON.stringify({ model, pages }));
135
+ return;
136
+ }
137
+ res.statusCode = 404;
138
+ res.end("Not found");
139
+ });
140
+ await new Promise((resolve, reject) => {
141
+ server.once("error", reject);
142
+ server.listen(port, () => {
143
+ server.off("error", reject);
144
+ console.log(`next-arch-map serve listening on http://localhost:${port}`);
145
+ resolve();
146
+ });
147
+ });
148
+ const close = async () => {
149
+ await watcher.close();
150
+ await new Promise((resolve, reject) => {
151
+ server.close((error) => {
152
+ if (error) {
153
+ reject(error);
154
+ return;
155
+ }
156
+ resolve();
157
+ });
158
+ });
159
+ };
160
+ process.once("SIGINT", () => {
161
+ void close().finally(() => process.exit(0));
162
+ });
163
+ process.once("SIGTERM", () => {
164
+ void close().finally(() => process.exit(0));
165
+ });
166
+ }
@@ -0,0 +1,82 @@
1
+ import ts from "typescript";
2
+ import type { Edge, Node } from "./model.js";
3
+ export declare function resolveProjectRoot(projectRoot: string): string;
4
+ export declare function resolveProjectPath(projectRoot: string, targetPath: string): string;
5
+ export declare function directoryExists(directoryPath: string): boolean;
6
+ export declare function fileExists(filePath: string): boolean;
7
+ export declare function getExistingDirectories(projectRoot: string, candidateDirs: string[]): string[];
8
+ export declare function walkDirectory(directoryPath: string): string[];
9
+ export declare function isIgnoredSourceFile(filePath: string): boolean;
10
+ export declare function isPageFile(filePath: string): boolean;
11
+ export declare function isRouteHandlerFile(filePath: string): boolean;
12
+ export declare function getPageRouteFromFile(appDir: string, filePath: string): string;
13
+ export declare function getEndpointRouteFromFile(scanRoot: string, filePath: string): string;
14
+ export declare function getScriptKind(filePath: string): ts.ScriptKind;
15
+ export declare function getSourceFile(filePath: string, sourceFileCache?: Map<string, ts.SourceFile>): ts.SourceFile | null;
16
+ export declare function resolveLocalModulePath(importerFilePath: string, specifier: string, projectRoot: string): string | null;
17
+ export declare function ensureDirectory(directoryPath: string): void;
18
+ export declare function readJsonFile<T>(filePath: string): T;
19
+ export declare function writeJsonFile(filePath: string, value: unknown): void;
20
+ export declare function buildEdgeKey(from: string, to: string, kind: string): string;
21
+ export declare function ensureNode(nodes: Node[], nodeIds: Set<string>, node: Node): Node;
22
+ export declare function collectStringConstants(sourceFile: ts.SourceFile): Map<string, string>;
23
+ export declare function getStringLiteralValue(expression: ts.Expression): string | null;
24
+ export declare function mergeMeta(baseMeta?: Record<string, any>, nextMeta?: Record<string, any>): Record<string, any> | undefined;
25
+ export declare function mergeNode(existingNode: Node, nextNode: Node): Node;
26
+ export declare function mergeEdge(existingEdge: Edge, nextEdge: Edge): Edge;
27
+ export declare function buildPageNode(route: string, filePath: string): {
28
+ id: string;
29
+ type: "page";
30
+ label: string;
31
+ meta: {
32
+ filePath: string;
33
+ route: string;
34
+ };
35
+ };
36
+ export declare function buildEndpointNode(endpoint: string, filePath: string): {
37
+ id: string;
38
+ type: "endpoint";
39
+ label: string;
40
+ meta: {
41
+ filePath: string;
42
+ route: string;
43
+ };
44
+ };
45
+ export declare function buildHandlerNode(endpoint: string, filePath: string, method?: string): {
46
+ id: string;
47
+ type: "handler";
48
+ label: string;
49
+ meta: {
50
+ method?: string | undefined;
51
+ filePath: string;
52
+ route: string;
53
+ };
54
+ };
55
+ export declare function buildActionNode(pageRoute: string, actionId: string, filePath: string, extraMeta?: Record<string, unknown>): {
56
+ id: string;
57
+ type: "action";
58
+ label: string;
59
+ meta: {
60
+ filePath: string;
61
+ route: string;
62
+ actionId: string;
63
+ };
64
+ };
65
+ export declare function buildDbNode(modelName: string, filePath: string): {
66
+ id: string;
67
+ type: "db";
68
+ label: string;
69
+ meta: {
70
+ filePath: string;
71
+ model: string;
72
+ };
73
+ };
74
+ export declare function buildUiNode(componentName: string, filePath: string): {
75
+ id: string;
76
+ type: "ui";
77
+ label: string;
78
+ meta: {
79
+ filePath: string;
80
+ component: string;
81
+ };
82
+ };
package/dist/utils.js ADDED
@@ -0,0 +1,306 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import ts from "typescript";
4
+ const ROUTE_GROUP_PATTERN = /^\(.*\)$/;
5
+ const SOURCE_FILE_PATTERN = /\.(ts|tsx|js|jsx)$/;
6
+ const PAGE_FILE_PATTERN = /^page\.(ts|tsx|js|jsx)$/;
7
+ const ROUTE_FILE_PATTERN = /^route\.(ts|tsx|js|jsx)$/;
8
+ const SKIPPED_DIRECTORIES = new Set(["node_modules", ".git", ".next", "__tests__"]);
9
+ export function resolveProjectRoot(projectRoot) {
10
+ return path.resolve(projectRoot);
11
+ }
12
+ export function resolveProjectPath(projectRoot, targetPath) {
13
+ return path.isAbsolute(targetPath) ? targetPath : path.resolve(projectRoot, targetPath);
14
+ }
15
+ export function directoryExists(directoryPath) {
16
+ return fs.existsSync(directoryPath) && fs.statSync(directoryPath).isDirectory();
17
+ }
18
+ export function fileExists(filePath) {
19
+ return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
20
+ }
21
+ export function getExistingDirectories(projectRoot, candidateDirs) {
22
+ return candidateDirs
23
+ .map((directoryPath) => resolveProjectPath(projectRoot, directoryPath))
24
+ .filter(directoryExists);
25
+ }
26
+ export function walkDirectory(directoryPath) {
27
+ const files = [];
28
+ for (const entry of fs.readdirSync(directoryPath, { withFileTypes: true })) {
29
+ if (SKIPPED_DIRECTORIES.has(entry.name)) {
30
+ continue;
31
+ }
32
+ const entryPath = path.join(directoryPath, entry.name);
33
+ if (entry.isDirectory()) {
34
+ files.push(...walkDirectory(entryPath));
35
+ continue;
36
+ }
37
+ if (SOURCE_FILE_PATTERN.test(entry.name)) {
38
+ files.push(entryPath);
39
+ }
40
+ }
41
+ return files;
42
+ }
43
+ export function isIgnoredSourceFile(filePath) {
44
+ return (!SOURCE_FILE_PATTERN.test(filePath) ||
45
+ filePath.endsWith(".test.ts") ||
46
+ filePath.endsWith(".test.tsx") ||
47
+ filePath.endsWith(".test.js") ||
48
+ filePath.endsWith(".test.jsx"));
49
+ }
50
+ export function isPageFile(filePath) {
51
+ return PAGE_FILE_PATTERN.test(path.basename(filePath));
52
+ }
53
+ export function isRouteHandlerFile(filePath) {
54
+ return ROUTE_FILE_PATTERN.test(path.basename(filePath));
55
+ }
56
+ export function getPageRouteFromFile(appDir, filePath) {
57
+ const segments = path.relative(appDir, filePath).split(path.sep).filter(Boolean);
58
+ if (segments.length === 0) {
59
+ return "/";
60
+ }
61
+ segments.pop();
62
+ const routeSegments = segments
63
+ .filter((segment) => !ROUTE_GROUP_PATTERN.test(segment))
64
+ .map(decodeRouteSegment);
65
+ return routeSegments.length === 0 ? "/" : `/${routeSegments.join("/")}`;
66
+ }
67
+ export function getEndpointRouteFromFile(scanRoot, filePath) {
68
+ const segments = path.relative(scanRoot, filePath).split(path.sep).filter(Boolean);
69
+ const fileName = segments.pop() ?? "";
70
+ const routeSegments = segments
71
+ .filter((segment) => !ROUTE_GROUP_PATTERN.test(segment))
72
+ .map(decodeRouteSegment);
73
+ const prefixedSegments = [...getEndpointPrefixSegments(scanRoot), ...routeSegments].filter(Boolean);
74
+ if (!ROUTE_FILE_PATTERN.test(fileName)) {
75
+ return prefixedSegments.length === 0 ? "/" : `/${prefixedSegments.join("/")}`;
76
+ }
77
+ if (prefixedSegments.length > 1 &&
78
+ prefixedSegments[0] === prefixedSegments[1]) {
79
+ prefixedSegments.shift();
80
+ }
81
+ return prefixedSegments.length === 0 ? "/" : `/${prefixedSegments.join("/")}`;
82
+ }
83
+ function getEndpointPrefixSegments(scanRoot) {
84
+ const normalized = scanRoot.replace(/\\/g, "/");
85
+ if (normalized.endsWith("/app/api") || normalized.endsWith("/src/app/api")) {
86
+ return ["api"];
87
+ }
88
+ if (normalized.endsWith("/src/api") || path.basename(scanRoot) === "api") {
89
+ return ["api"];
90
+ }
91
+ if (normalized.endsWith("/src/server") || path.basename(scanRoot) === "server") {
92
+ return ["server"];
93
+ }
94
+ return [];
95
+ }
96
+ function decodeRouteSegment(segment) {
97
+ try {
98
+ return decodeURIComponent(segment);
99
+ }
100
+ catch {
101
+ return segment;
102
+ }
103
+ }
104
+ export function getScriptKind(filePath) {
105
+ if (filePath.endsWith(".tsx")) {
106
+ return ts.ScriptKind.TSX;
107
+ }
108
+ if (filePath.endsWith(".jsx")) {
109
+ return ts.ScriptKind.JSX;
110
+ }
111
+ if (filePath.endsWith(".js")) {
112
+ return ts.ScriptKind.JS;
113
+ }
114
+ return ts.ScriptKind.TS;
115
+ }
116
+ export function getSourceFile(filePath, sourceFileCache) {
117
+ const cachedSourceFile = sourceFileCache?.get(filePath);
118
+ if (cachedSourceFile) {
119
+ return cachedSourceFile;
120
+ }
121
+ try {
122
+ const sourceFile = ts.createSourceFile(filePath, fs.readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest, true, getScriptKind(filePath));
123
+ sourceFileCache?.set(filePath, sourceFile);
124
+ return sourceFile;
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ export function resolveLocalModulePath(importerFilePath, specifier, projectRoot) {
131
+ let basePath = null;
132
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
133
+ basePath = path.resolve(path.dirname(importerFilePath), specifier);
134
+ }
135
+ else if (specifier.startsWith("@/")) {
136
+ basePath = path.join(projectRoot, "src", specifier.slice(2));
137
+ }
138
+ if (!basePath) {
139
+ return null;
140
+ }
141
+ const candidates = [
142
+ basePath,
143
+ `${basePath}.ts`,
144
+ `${basePath}.tsx`,
145
+ `${basePath}.js`,
146
+ `${basePath}.jsx`,
147
+ path.join(basePath, "index.ts"),
148
+ path.join(basePath, "index.tsx"),
149
+ path.join(basePath, "index.js"),
150
+ path.join(basePath, "index.jsx"),
151
+ ];
152
+ for (const candidate of candidates) {
153
+ if (fileExists(candidate)) {
154
+ return candidate;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ export function ensureDirectory(directoryPath) {
160
+ fs.mkdirSync(directoryPath, { recursive: true });
161
+ }
162
+ export function readJsonFile(filePath) {
163
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
164
+ }
165
+ export function writeJsonFile(filePath, value) {
166
+ ensureDirectory(path.dirname(filePath));
167
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
168
+ }
169
+ export function buildEdgeKey(from, to, kind) {
170
+ return `${from}::${to}::${kind}`;
171
+ }
172
+ export function ensureNode(nodes, nodeIds, node) {
173
+ if (nodeIds.has(node.id)) {
174
+ const existingNodeIndex = nodes.findIndex((entry) => entry.id === node.id);
175
+ if (existingNodeIndex === -1) {
176
+ return node;
177
+ }
178
+ const mergedNode = mergeNode(nodes[existingNodeIndex], node);
179
+ nodes[existingNodeIndex] = mergedNode;
180
+ return mergedNode;
181
+ }
182
+ nodeIds.add(node.id);
183
+ nodes.push(node);
184
+ return node;
185
+ }
186
+ export function collectStringConstants(sourceFile) {
187
+ const constMap = new Map();
188
+ const visitNode = (node) => {
189
+ if (ts.isVariableDeclaration(node) &&
190
+ ts.isIdentifier(node.name) &&
191
+ node.initializer &&
192
+ ts.isVariableDeclarationList(node.parent) &&
193
+ (node.parent.flags & ts.NodeFlags.Const) !== 0) {
194
+ const stringValue = getStringLiteralValue(node.initializer);
195
+ if (stringValue !== null) {
196
+ constMap.set(node.name.text, stringValue);
197
+ }
198
+ }
199
+ ts.forEachChild(node, visitNode);
200
+ };
201
+ visitNode(sourceFile);
202
+ return constMap;
203
+ }
204
+ export function getStringLiteralValue(expression) {
205
+ if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
206
+ return expression.text;
207
+ }
208
+ return null;
209
+ }
210
+ export function mergeMeta(baseMeta, nextMeta) {
211
+ if (!baseMeta) {
212
+ return nextMeta;
213
+ }
214
+ if (!nextMeta) {
215
+ return baseMeta;
216
+ }
217
+ return {
218
+ ...baseMeta,
219
+ ...nextMeta,
220
+ };
221
+ }
222
+ export function mergeNode(existingNode, nextNode) {
223
+ return {
224
+ ...existingNode,
225
+ ...nextNode,
226
+ meta: mergeMeta(existingNode.meta, nextNode.meta),
227
+ };
228
+ }
229
+ export function mergeEdge(existingEdge, nextEdge) {
230
+ return {
231
+ ...existingEdge,
232
+ ...nextEdge,
233
+ meta: mergeMeta(existingEdge.meta, nextEdge.meta),
234
+ };
235
+ }
236
+ export function buildPageNode(route, filePath) {
237
+ return {
238
+ id: `page:${route}`,
239
+ type: "page",
240
+ label: route,
241
+ meta: {
242
+ filePath,
243
+ route,
244
+ },
245
+ };
246
+ }
247
+ export function buildEndpointNode(endpoint, filePath) {
248
+ return {
249
+ id: `endpoint:${endpoint}`,
250
+ type: "endpoint",
251
+ label: endpoint,
252
+ meta: {
253
+ filePath,
254
+ route: endpoint,
255
+ },
256
+ };
257
+ }
258
+ export function buildHandlerNode(endpoint, filePath, method) {
259
+ const label = method ? `${endpoint}#${method}` : endpoint;
260
+ return {
261
+ id: method ? `handler:${endpoint}:${method}` : `handler:${endpoint}`,
262
+ type: "handler",
263
+ label,
264
+ meta: {
265
+ filePath,
266
+ route: endpoint,
267
+ ...(method ? { method } : {}),
268
+ },
269
+ };
270
+ }
271
+ export function buildActionNode(pageRoute, actionId, filePath, extraMeta) {
272
+ const label = actionId;
273
+ return {
274
+ id: `action:${pageRoute}:${actionId}`,
275
+ type: "action",
276
+ label,
277
+ meta: {
278
+ filePath,
279
+ route: pageRoute,
280
+ actionId,
281
+ ...(extraMeta ?? {}),
282
+ },
283
+ };
284
+ }
285
+ export function buildDbNode(modelName, filePath) {
286
+ return {
287
+ id: `db:${modelName}`,
288
+ type: "db",
289
+ label: modelName,
290
+ meta: {
291
+ filePath,
292
+ model: modelName,
293
+ },
294
+ };
295
+ }
296
+ export function buildUiNode(componentName, filePath) {
297
+ return {
298
+ id: `ui:${componentName}`,
299
+ type: "ui",
300
+ label: componentName,
301
+ meta: {
302
+ filePath,
303
+ component: componentName,
304
+ },
305
+ };
306
+ }
@@ -0,0 +1,25 @@
1
+ # Next.js Dev Route Integration
2
+
3
+ 1. Add a script in your Next.js app:
4
+
5
+ ```json
6
+ {
7
+ "scripts": {
8
+ "arch:graph": "next-arch-map analyze --project-root . --out arch/graph.full.json"
9
+ }
10
+ }
11
+ ```
12
+
13
+ 2. Run the analyzer before visiting your dev-only architecture route:
14
+
15
+ ```bash
16
+ npm run arch:graph
17
+ ```
18
+
19
+ 3. In your Next.js `_dev/routes` page, read `arch/graph.full.json` on the server and pass it into a client-side inspector UI.
20
+
21
+ 4. In the UI:
22
+
23
+ - render the route graph on the left
24
+ - show page, endpoint, DB, and UI summaries in the sidebar
25
+ - when a page is selected, derive its reachable endpoints and DB entities from the graph edges
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "next-arch-map",
3
+ "version": "0.1.0",
4
+ "description": "Static analyzer that builds a multi-layer architecture graph for Next.js-style apps.",
5
+ "type": "module",
6
+ "bin": {
7
+ "next-arch-map": "dist/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./model": {
15
+ "types": "./dist/model.d.ts",
16
+ "import": "./dist/model.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "viewer",
22
+ "README.md",
23
+ "examples"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "check": "tsc --noEmit -p tsconfig.json",
28
+ "dev": "node dist/cli.js dev"
29
+ },
30
+ "keywords": [
31
+ "nextjs",
32
+ "architecture",
33
+ "graph",
34
+ "static-analysis",
35
+ "typescript"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/jesuscasal23/next-arch-map"
40
+ },
41
+ "license": "MIT",
42
+ "dependencies": {
43
+ "chokidar": "^3.6.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^20.19.0",
47
+ "typescript": "^5.8.2"
48
+ }
49
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>next-arch-map viewer</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>