archsync 1.0.0 → 1.0.1
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/README.md +67 -0
- package/dist/archsync.cjs +2 -0
- package/package.json +8 -4
- package/bin/cli.js +0 -91
- package/src/__tests__/e2e-workflow.test.js +0 -66
- package/src/__tests__/hashEngine.test.js +0 -109
- package/src/__tests__/impact.test.js +0 -137
- package/src/__tests__/parsers.test.js +0 -496
- package/src/__tests__/scan-pipeline.test.js +0 -332
- package/src/__tests__/schemaBuilder.test.js +0 -145
- package/src/__tests__/workspace.test.js +0 -178
- package/src/commands/backup.js +0 -54
- package/src/commands/connect.js +0 -129
- package/src/commands/diff.js +0 -228
- package/src/commands/export.js +0 -125
- package/src/commands/impactReport.js +0 -50
- package/src/commands/import.js +0 -126
- package/src/commands/init.js +0 -80
- package/src/commands/login.js +0 -116
- package/src/commands/plugin.js +0 -28
- package/src/commands/push.js +0 -194
- package/src/commands/register.js +0 -127
- package/src/commands/scan.js +0 -498
- package/src/commands/serve.js +0 -133
- package/src/commands/setup.js +0 -233
- package/src/commands/status.js +0 -56
- package/src/commands/validate.js +0 -245
- package/src/commands/watch.js +0 -70
- package/src/core/credentialStore.js +0 -76
- package/src/core/hashEngine.js +0 -34
- package/src/core/impactEngine.js +0 -192
- package/src/core/monorepoDetector.js +0 -41
- package/src/core/pluginManager.js +0 -40
- package/src/core/relationshipEngine.js +0 -917
- package/src/core/requestSigning.js +0 -16
- package/src/core/schemaBuilder.js +0 -230
- package/src/core/schemaDeduplicator.js +0 -54
- package/src/core/supabaseClient.js +0 -68
- package/src/core/workspaceDetector.js +0 -113
- package/src/parsers/astParser.js +0 -274
- package/src/parsers/configParser.js +0 -49
- package/src/parsers/dependencyGraph.js +0 -31
- package/src/parsers/flutterParser.js +0 -98
- package/src/parsers/goParser.js +0 -99
- package/src/parsers/index.js +0 -211
- package/src/parsers/javaParser.js +0 -89
- package/src/parsers/nodeParser.js +0 -429
- package/src/parsers/pythonParser.js +0 -109
- package/src/parsers/reactParser.js +0 -368
- package/src/parsers/smartComment.js +0 -144
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Node.js / Express / TypeScript parser — production-grade entity extraction.
|
|
6
|
-
* Detects: routes, controllers (exported arrow fns + classes), services (classes),
|
|
7
|
-
* middleware, database collections, models, config entries, triggers.
|
|
8
|
-
* Also detects: Fastify routes, tRPC procedures, GraphQL resolvers,
|
|
9
|
-
* Prisma models, Mongoose models.
|
|
10
|
-
* Also extracts mount-point prefixes from app.use() for route path resolution.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// ─── REGEX LIBRARY ───────────────────────────────────────────
|
|
14
|
-
const ROUTE_REGEX = /(?:app|router)\.(get|post|put|patch|delete|all)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
15
|
-
const CLASS_REGEX = /class\s+(\w+)\s*(?:extends\s+(\w+))?\s*(?:implements\s+[\w,\s]+)?\s*\{/g;
|
|
16
|
-
const FUNC_REGEX = /(?:export\s+)?(?:async\s+)?function\s+(\w+)/g;
|
|
17
|
-
const ARROW_EXPORT_REGEX = /export\s+(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\(?/g;
|
|
18
|
-
const MONGOOSE_MODEL_REGEX = /mongoose\.model\s*\(\s*['"`](\w+)['"`]/g;
|
|
19
|
-
const SEQUELIZE_MODEL_REGEX = /sequelize\.define\s*\(\s*['"`](\w+)['"`]/g;
|
|
20
|
-
const FIRESTORE_COLLECTION_REGEX = /\.collection\s*\(\s*['"`](\w+)['"`]\s*\)/g;
|
|
21
|
-
const APP_USE_REGEX = /(?:app|router)\.use\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(\w+)/g;
|
|
22
|
-
const MIDDLEWARE_USE_REGEX = /(?:app|router)\.use\s*\(\s*(\w+)\s*\)/g;
|
|
23
|
-
|
|
24
|
-
// ─── Fastify ─────────────────────────────────────────────────
|
|
25
|
-
// fastify.get('/path', handler) or fastify.route({ method, url })
|
|
26
|
-
const FASTIFY_ROUTE_REGEX = /(?:fastify|server|app)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
27
|
-
const FASTIFY_ROUTE_OBJ_REGEX = /(?:fastify|server|app)\.route\s*\(\s*\{[^}]*method\s*:\s*['"`]([^'"`]+)['"`][^}]*url\s*:\s*['"`]([^'"`]+)['"`]/gis;
|
|
28
|
-
const FASTIFY_PLUGIN_REGEX = /(?:fastify|server)\.register\s*\(\s*(\w+)\s*(?:,|\))/g;
|
|
29
|
-
|
|
30
|
-
// ─── tRPC ─────────────────────────────────────────────────────
|
|
31
|
-
// t.procedure.query / t.procedure.mutation / router({ ... })
|
|
32
|
-
const TRPC_PROC_NAME_REGEX = /(\w+)\s*:\s*(?:publicProcedure|protectedProcedure|procedure|t\.procedure)[^,}]*/g;
|
|
33
|
-
|
|
34
|
-
// ─── GraphQL ──────────────────────────────────────────────────
|
|
35
|
-
const GQL_TYPE_DEF_REGEX = /typeDefs?\s*=\s*(?:gql|`)/g;
|
|
36
|
-
const GQL_QUERY_MUTATION_REGEX = /(?:Query|Mutation|Subscription)\s*:\s*\{([^}]+)\}/gs;
|
|
37
|
-
|
|
38
|
-
// ─── Prisma ───────────────────────────────────────────────────
|
|
39
|
-
// prisma.modelName.findMany / create / etc.
|
|
40
|
-
const PRISMA_MODEL_ACCESS_REGEX = /prisma\.(\w+)\.(findMany|findUnique|findFirst|create|update|delete|upsert|count|aggregate)/g;
|
|
41
|
-
const PRISMA_MODEL_DEF_REGEX = /^model\s+(\w+)\s*\{/gm; // for .prisma schema files
|
|
42
|
-
|
|
43
|
-
// ─── Mongoose (enhanced) ──────────────────────────────────────
|
|
44
|
-
const MONGOOSE_SCHEMA_VAR_REGEX = /(?:const|let|var)\s+(\w+Schema)\s*=\s*new\s+(?:mongoose\.)?Schema/g;
|
|
45
|
-
|
|
46
|
-
export function parseNodeFile(filePath) {
|
|
47
|
-
const source = fs.readFileSync(filePath, 'utf-8');
|
|
48
|
-
const entities = [];
|
|
49
|
-
const relations = [];
|
|
50
|
-
const fileName = path.basename(filePath, path.extname(filePath));
|
|
51
|
-
const dirName = path.basename(path.dirname(filePath));
|
|
52
|
-
const ext = path.extname(filePath);
|
|
53
|
-
|
|
54
|
-
// Infer system from path
|
|
55
|
-
let system = 'backend';
|
|
56
|
-
if (/\/mobile\/|\/app\/(?!routes)/i.test(filePath)) system = 'app';
|
|
57
|
-
else if (/\/web\/|\/client\/|\/frontend\/|\/pages\//i.test(filePath)) system = 'web';
|
|
58
|
-
else if (/\/admin\//i.test(filePath) && !/\/routes\/admin/i.test(filePath)) system = 'admin';
|
|
59
|
-
|
|
60
|
-
// Infer file role from path
|
|
61
|
-
const isControllerFile = /controller/i.test(filePath);
|
|
62
|
-
const isServiceFile = /service/i.test(filePath);
|
|
63
|
-
const isMiddlewareFile = /middleware/i.test(filePath);
|
|
64
|
-
const isRouteFile = /route/i.test(filePath);
|
|
65
|
-
const isModelFile = /model|schema|entity/i.test(filePath);
|
|
66
|
-
const isTriggerFile = /trigger/i.test(filePath);
|
|
67
|
-
const isConfigFile = /config/i.test(filePath);
|
|
68
|
-
const isScheduledFile = /scheduled|cron|job/i.test(filePath);
|
|
69
|
-
const isPrismaSchema = ext === '.prisma';
|
|
70
|
-
const isResolverFile = /resolver/i.test(filePath);
|
|
71
|
-
const isFastifyFile = /fastify|server/i.test(filePath) || source.includes('fastify');
|
|
72
|
-
|
|
73
|
-
let match;
|
|
74
|
-
|
|
75
|
-
// ═══ 1. ROUTES (Express) ═════════════════════════════════
|
|
76
|
-
ROUTE_REGEX.lastIndex = 0;
|
|
77
|
-
while ((match = ROUTE_REGEX.exec(source)) !== null) {
|
|
78
|
-
const method = match[1].toUpperCase();
|
|
79
|
-
const routePath = match[2];
|
|
80
|
-
const line = source.substring(0, match.index).split('\n').length;
|
|
81
|
-
entities.push({
|
|
82
|
-
entityType: 'route',
|
|
83
|
-
name: `${method} ${routePath}`,
|
|
84
|
-
system: 'backend',
|
|
85
|
-
data: { method, path: routePath, framework: 'express' },
|
|
86
|
-
metadata: { sourceFile: filePath, line },
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ═══ 2. FASTIFY ROUTES ═══════════════════════════════════
|
|
91
|
-
FASTIFY_ROUTE_REGEX.lastIndex = 0;
|
|
92
|
-
while (isFastifyFile && (match = FASTIFY_ROUTE_REGEX.exec(source)) !== null) {
|
|
93
|
-
const method = match[1].toUpperCase();
|
|
94
|
-
const routePath = match[2];
|
|
95
|
-
const line = source.substring(0, match.index).split('\n').length;
|
|
96
|
-
// Avoid duplicating if express regex already caught it
|
|
97
|
-
const key = `${method} ${routePath}`;
|
|
98
|
-
if (entities.some(e => e.name === key && e.entityType === 'route')) continue;
|
|
99
|
-
entities.push({
|
|
100
|
-
entityType: 'route',
|
|
101
|
-
name: key,
|
|
102
|
-
system: 'backend',
|
|
103
|
-
data: { method, path: routePath, framework: 'fastify' },
|
|
104
|
-
metadata: { sourceFile: filePath, line },
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
FASTIFY_ROUTE_OBJ_REGEX.lastIndex = 0;
|
|
109
|
-
while ((match = FASTIFY_ROUTE_OBJ_REGEX.exec(source)) !== null) {
|
|
110
|
-
const method = match[1].toUpperCase();
|
|
111
|
-
const routePath = match[2];
|
|
112
|
-
const key = `${method} ${routePath}`;
|
|
113
|
-
if (entities.some(e => e.name === key && e.entityType === 'route')) continue;
|
|
114
|
-
entities.push({
|
|
115
|
-
entityType: 'route',
|
|
116
|
-
name: key,
|
|
117
|
-
system: 'backend',
|
|
118
|
-
data: { method, path: routePath, framework: 'fastify', style: 'route-object' },
|
|
119
|
-
metadata: { sourceFile: filePath },
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Fastify plugin registrations
|
|
124
|
-
FASTIFY_PLUGIN_REGEX.lastIndex = 0;
|
|
125
|
-
while ((match = FASTIFY_PLUGIN_REGEX.exec(source)) !== null) {
|
|
126
|
-
const pluginName = match[1];
|
|
127
|
-
if (['cors', 'helmet', 'multipart', 'formbody', 'cookie', 'jwt', 'auth'].includes(pluginName)) continue;
|
|
128
|
-
entities.push({
|
|
129
|
-
entityType: 'module',
|
|
130
|
-
name: `plugin:${pluginName}`,
|
|
131
|
-
system: 'backend',
|
|
132
|
-
data: { kind: 'fastify-plugin', plugin: pluginName },
|
|
133
|
-
metadata: { sourceFile: filePath },
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ═══ 3. tRPC PROCEDURES ══════════════════════════════════
|
|
138
|
-
TRPC_PROC_NAME_REGEX.lastIndex = 0;
|
|
139
|
-
while ((match = TRPC_PROC_NAME_REGEX.exec(source)) !== null) {
|
|
140
|
-
const procName = match[1];
|
|
141
|
-
if (['default', 'router', 'middleware', 'use'].includes(procName)) continue;
|
|
142
|
-
// Determine procedure type from context
|
|
143
|
-
const contextAfter = source.slice(match.index, match.index + 200);
|
|
144
|
-
const procType = /\.mutation/.test(contextAfter) ? 'mutation'
|
|
145
|
-
: /\.subscription/.test(contextAfter) ? 'subscription'
|
|
146
|
-
: 'query';
|
|
147
|
-
entities.push({
|
|
148
|
-
entityType: 'route',
|
|
149
|
-
name: `tRPC:${procName}`,
|
|
150
|
-
system: 'backend',
|
|
151
|
-
data: { kind: 'trpc-procedure', procedureType: procType },
|
|
152
|
-
metadata: { sourceFile: filePath },
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ═══ 4. GRAPHQL RESOLVERS ════════════════════════════════
|
|
157
|
-
if (isResolverFile || GQL_TYPE_DEF_REGEX.test(source)) {
|
|
158
|
-
GQL_QUERY_MUTATION_REGEX.lastIndex = 0;
|
|
159
|
-
while ((match = GQL_QUERY_MUTATION_REGEX.exec(source)) !== null) {
|
|
160
|
-
const resolverBlock = match[1];
|
|
161
|
-
const fieldRegex = /(\w+)\s*(?::|:\s*async\s*)?\s*(?:_|resolve|\()/g;
|
|
162
|
-
let fieldMatch;
|
|
163
|
-
while ((fieldMatch = fieldRegex.exec(resolverBlock)) !== null) {
|
|
164
|
-
const resolverName = fieldMatch[1];
|
|
165
|
-
if (['async', 'resolve', '__resolveType'].includes(resolverName)) continue;
|
|
166
|
-
entities.push({
|
|
167
|
-
entityType: 'service',
|
|
168
|
-
name: `resolver:${resolverName}`,
|
|
169
|
-
system: 'backend',
|
|
170
|
-
data: { kind: 'graphql-resolver' },
|
|
171
|
-
metadata: { sourceFile: filePath },
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Also detect standalone resolver functions
|
|
177
|
-
if (isResolverFile) {
|
|
178
|
-
ARROW_EXPORT_REGEX.lastIndex = 0;
|
|
179
|
-
while ((match = ARROW_EXPORT_REGEX.exec(source)) !== null) {
|
|
180
|
-
const name = match[1];
|
|
181
|
-
if (entities.some(e => e.name === `resolver:${name}` || e.name === name)) continue;
|
|
182
|
-
entities.push({
|
|
183
|
-
entityType: 'service',
|
|
184
|
-
name: `resolver:${name}`,
|
|
185
|
-
system: 'backend',
|
|
186
|
-
data: { kind: 'graphql-resolver' },
|
|
187
|
-
metadata: { sourceFile: filePath },
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ═══ 5. PRISMA MODELS ════════════════════════════════════
|
|
194
|
-
// From .prisma schema files
|
|
195
|
-
if (isPrismaSchema) {
|
|
196
|
-
PRISMA_MODEL_DEF_REGEX.lastIndex = 0;
|
|
197
|
-
while ((match = PRISMA_MODEL_DEF_REGEX.exec(source)) !== null) {
|
|
198
|
-
entities.push({
|
|
199
|
-
entityType: 'model',
|
|
200
|
-
name: match[1],
|
|
201
|
-
system: 'backend',
|
|
202
|
-
data: { orm: 'prisma', kind: 'schema-definition' },
|
|
203
|
-
metadata: { sourceFile: filePath },
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// From TS/JS files using prisma client
|
|
209
|
-
PRISMA_MODEL_ACCESS_REGEX.lastIndex = 0;
|
|
210
|
-
const seenPrismaModels = new Set();
|
|
211
|
-
while ((match = PRISMA_MODEL_ACCESS_REGEX.exec(source)) !== null) {
|
|
212
|
-
const modelName = match[1];
|
|
213
|
-
// Skip internal Prisma client methods
|
|
214
|
-
if (['$connect', '$disconnect', '$transaction', '$queryRaw', '$executeRaw'].includes(modelName)) continue;
|
|
215
|
-
if (seenPrismaModels.has(modelName)) continue;
|
|
216
|
-
seenPrismaModels.add(modelName);
|
|
217
|
-
entities.push({
|
|
218
|
-
entityType: 'model',
|
|
219
|
-
name: modelName,
|
|
220
|
-
system: 'backend',
|
|
221
|
-
data: { orm: 'prisma', kind: 'client-usage' },
|
|
222
|
-
metadata: { sourceFile: filePath },
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ═══ 6. MOUNT POINTS ═════════════════════════════════════
|
|
227
|
-
APP_USE_REGEX.lastIndex = 0;
|
|
228
|
-
while ((match = APP_USE_REGEX.exec(source)) !== null) {
|
|
229
|
-
const prefix = match[1];
|
|
230
|
-
const routerVar = match[2];
|
|
231
|
-
entities.push({
|
|
232
|
-
entityType: 'mount',
|
|
233
|
-
name: `MOUNT ${prefix}`,
|
|
234
|
-
system: 'backend',
|
|
235
|
-
data: { prefix, routerVariable: routerVar },
|
|
236
|
-
metadata: { sourceFile: filePath },
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// ═══ 7. MIDDLEWARE USAGE ══════════════════════════════════
|
|
241
|
-
MIDDLEWARE_USE_REGEX.lastIndex = 0;
|
|
242
|
-
while ((match = MIDDLEWARE_USE_REGEX.exec(source)) !== null) {
|
|
243
|
-
const mwName = match[1];
|
|
244
|
-
if (['express', 'cors', 'bodyParser', 'json', 'urlencoded', 'morgan', 'helmet', 'compression', 'cookieParser'].includes(mwName)) continue;
|
|
245
|
-
entities.push({
|
|
246
|
-
entityType: 'middleware',
|
|
247
|
-
name: mwName,
|
|
248
|
-
system: 'backend',
|
|
249
|
-
data: { scope: 'global' },
|
|
250
|
-
metadata: { sourceFile: filePath },
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ═══ 8. CLASSES ══════════════════════════════════════════
|
|
255
|
-
CLASS_REGEX.lastIndex = 0;
|
|
256
|
-
while ((match = CLASS_REGEX.exec(source)) !== null) {
|
|
257
|
-
const className = match[1];
|
|
258
|
-
const parentClass = match[2];
|
|
259
|
-
|
|
260
|
-
let entityType = 'service';
|
|
261
|
-
if (/controller/i.test(className)) entityType = 'controller';
|
|
262
|
-
else if (/middleware/i.test(className)) entityType = 'middleware';
|
|
263
|
-
else if (/model|entity|schema/i.test(className)) entityType = 'model';
|
|
264
|
-
else if (/worker|job|queue/i.test(className)) entityType = 'worker';
|
|
265
|
-
else if (isServiceFile) entityType = 'service';
|
|
266
|
-
else if (isControllerFile) entityType = 'controller';
|
|
267
|
-
|
|
268
|
-
entities.push({
|
|
269
|
-
entityType,
|
|
270
|
-
name: className,
|
|
271
|
-
system,
|
|
272
|
-
data: { parentClass: parentClass || null },
|
|
273
|
-
metadata: { sourceFile: filePath },
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
if (parentClass) {
|
|
277
|
-
relations.push({
|
|
278
|
-
source: className, target: parentClass,
|
|
279
|
-
sourceType: entityType, targetType: 'unknown',
|
|
280
|
-
relation: 'extends',
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ═══ 9. EXPORTED FUNCTIONS (controllers / services / middleware) ═══
|
|
286
|
-
if (isControllerFile || isServiceFile || isMiddlewareFile || isRouteFile) {
|
|
287
|
-
ARROW_EXPORT_REGEX.lastIndex = 0;
|
|
288
|
-
while ((match = ARROW_EXPORT_REGEX.exec(source)) !== null) {
|
|
289
|
-
const funcName = match[1];
|
|
290
|
-
if (['default', 'module'].includes(funcName)) continue;
|
|
291
|
-
if (funcName === 'router' || funcName === 'Router') continue;
|
|
292
|
-
|
|
293
|
-
const entityType = isControllerFile ? 'controller'
|
|
294
|
-
: isServiceFile ? 'service'
|
|
295
|
-
: isMiddlewareFile ? 'middleware'
|
|
296
|
-
: 'controller';
|
|
297
|
-
|
|
298
|
-
if (entities.some(e => e.name === funcName)) continue;
|
|
299
|
-
|
|
300
|
-
entities.push({
|
|
301
|
-
entityType,
|
|
302
|
-
name: funcName,
|
|
303
|
-
system,
|
|
304
|
-
data: { kind: 'arrow-export' },
|
|
305
|
-
metadata: { sourceFile: filePath },
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
FUNC_REGEX.lastIndex = 0;
|
|
310
|
-
while ((match = FUNC_REGEX.exec(source)) !== null) {
|
|
311
|
-
const funcName = match[1];
|
|
312
|
-
if (['module', 'exports', 'require', 'constructor', 'default'].includes(funcName)) continue;
|
|
313
|
-
if (entities.some(e => e.name === funcName)) continue;
|
|
314
|
-
|
|
315
|
-
const entityType = isControllerFile ? 'controller'
|
|
316
|
-
: isServiceFile ? 'service'
|
|
317
|
-
: isMiddlewareFile ? 'middleware'
|
|
318
|
-
: 'service';
|
|
319
|
-
|
|
320
|
-
entities.push({
|
|
321
|
-
entityType,
|
|
322
|
-
name: funcName,
|
|
323
|
-
system,
|
|
324
|
-
data: { kind: 'function-export' },
|
|
325
|
-
metadata: { sourceFile: filePath },
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ═══ 10. MONGOOSE MODELS (enhanced) ═══════════════════════
|
|
331
|
-
MONGOOSE_MODEL_REGEX.lastIndex = 0;
|
|
332
|
-
while ((match = MONGOOSE_MODEL_REGEX.exec(source)) !== null) {
|
|
333
|
-
entities.push({
|
|
334
|
-
entityType: 'model', name: match[1], system: 'backend',
|
|
335
|
-
data: { orm: 'mongoose', kind: 'model-registration' },
|
|
336
|
-
metadata: { sourceFile: filePath },
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Mongoose Schema variable declarations (e.g. const UserSchema = new Schema({...}))
|
|
341
|
-
MONGOOSE_SCHEMA_VAR_REGEX.lastIndex = 0;
|
|
342
|
-
while ((match = MONGOOSE_SCHEMA_VAR_REGEX.exec(source)) !== null) {
|
|
343
|
-
const schemaVarName = match[1];
|
|
344
|
-
// Derive model name from schema variable (UserSchema → User)
|
|
345
|
-
const modelName = schemaVarName.replace(/Schema$/i, '');
|
|
346
|
-
if (entities.some(e => e.name === modelName && e.data?.orm === 'mongoose')) continue;
|
|
347
|
-
entities.push({
|
|
348
|
-
entityType: 'model', name: modelName, system: 'backend',
|
|
349
|
-
data: { orm: 'mongoose', kind: 'schema-definition', schemaVar: schemaVarName },
|
|
350
|
-
metadata: { sourceFile: filePath },
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
SEQUELIZE_MODEL_REGEX.lastIndex = 0;
|
|
355
|
-
while ((match = SEQUELIZE_MODEL_REGEX.exec(source)) !== null) {
|
|
356
|
-
entities.push({
|
|
357
|
-
entityType: 'model', name: match[1], system: 'backend',
|
|
358
|
-
data: { orm: 'sequelize' }, metadata: { sourceFile: filePath },
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ═══ 11. DATABASE COLLECTIONS (Firestore) ════════════════
|
|
363
|
-
FIRESTORE_COLLECTION_REGEX.lastIndex = 0;
|
|
364
|
-
const seenCollections = new Set();
|
|
365
|
-
while ((match = FIRESTORE_COLLECTION_REGEX.exec(source)) !== null) {
|
|
366
|
-
const coll = match[1];
|
|
367
|
-
if (seenCollections.has(coll)) continue;
|
|
368
|
-
seenCollections.add(coll);
|
|
369
|
-
entities.push({
|
|
370
|
-
entityType: 'database',
|
|
371
|
-
name: `collection:${coll}`,
|
|
372
|
-
system: 'backend',
|
|
373
|
-
data: { collection: coll, dbType: 'firestore' },
|
|
374
|
-
metadata: { sourceFile: filePath },
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// ═══ 12. TRIGGERS / SCHEDULED FUNCTIONS ═══════════════════
|
|
379
|
-
if (isTriggerFile || isScheduledFile) {
|
|
380
|
-
const TRIGGER = /(?:functions|onCall|onRequest|onSchedule|onDocument)\s*\(\s*['"`]?([^'"`)\s,]+)?/g;
|
|
381
|
-
TRIGGER.lastIndex = 0;
|
|
382
|
-
while ((match = TRIGGER.exec(source)) !== null) {
|
|
383
|
-
const triggerName = match[1] || fileName;
|
|
384
|
-
if (entities.some(e => e.name === triggerName)) continue;
|
|
385
|
-
entities.push({
|
|
386
|
-
entityType: isTriggerFile ? 'trigger' : 'worker',
|
|
387
|
-
name: triggerName,
|
|
388
|
-
system: 'backend',
|
|
389
|
-
data: { kind: 'firebase-trigger' },
|
|
390
|
-
metadata: { sourceFile: filePath },
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// ═══ 13. CONFIG EXPORTS ═══════════════════════════════════
|
|
396
|
-
if (isConfigFile) {
|
|
397
|
-
const CONFIG_EXPORT = /export\s+(?:const|let|function)\s+(\w+)/g;
|
|
398
|
-
CONFIG_EXPORT.lastIndex = 0;
|
|
399
|
-
while ((match = CONFIG_EXPORT.exec(source)) !== null) {
|
|
400
|
-
const name = match[1];
|
|
401
|
-
if (['default'].includes(name)) continue;
|
|
402
|
-
entities.push({
|
|
403
|
-
entityType: 'config',
|
|
404
|
-
name,
|
|
405
|
-
system: 'backend',
|
|
406
|
-
data: {},
|
|
407
|
-
metadata: { sourceFile: filePath },
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// ═══ 14. MIDDLEWARE DEFINITIONS ═══════════════════════════
|
|
413
|
-
if (isMiddlewareFile && entities.length === 0) {
|
|
414
|
-
const MW_EXPORT = /export\s+(?:const|function|default\s+function)\s+(\w+)/g;
|
|
415
|
-
MW_EXPORT.lastIndex = 0;
|
|
416
|
-
while ((match = MW_EXPORT.exec(source)) !== null) {
|
|
417
|
-
const name = match[1];
|
|
418
|
-
entities.push({
|
|
419
|
-
entityType: 'middleware',
|
|
420
|
-
name,
|
|
421
|
-
system: 'backend',
|
|
422
|
-
data: {},
|
|
423
|
-
metadata: { sourceFile: filePath },
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return { filePath, entities, relations };
|
|
429
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Python parser — extracts backend entities from Python source files.
|
|
3
|
-
* Detects: Django models, FastAPI/Flask routes, FastAPI middleware,
|
|
4
|
-
* SQLAlchemy models, and service/manager classes.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Django model: class ModelName(models.Model):
|
|
8
|
-
const DJANGO_MODEL_REGEX = /^class\s+(\w+)\s*\(\s*models\.Model\s*\)\s*:/gm;
|
|
9
|
-
|
|
10
|
-
// SQLAlchemy model: class User(Base):
|
|
11
|
-
const SQLALCHEMY_MODEL_REGEX = /^class\s+(\w+)\s*\(\s*Base\s*\)\s*:/gm;
|
|
12
|
-
|
|
13
|
-
// FastAPI / Flask route decorators: @app.get("/path"), @router.post("/path"), etc.
|
|
14
|
-
const ROUTE_REGEX = /^@(?:app|router|blueprint)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"]([^'"]+)['"]/gim;
|
|
15
|
-
|
|
16
|
-
// FastAPI middleware: @app.middleware("http")
|
|
17
|
-
const MIDDLEWARE_REGEX = /^@app\.middleware\s*\(\s*['"]http['"]\s*\)/gm;
|
|
18
|
-
|
|
19
|
-
// Service / manager classes: class UserService: or class UserManager:
|
|
20
|
-
const SERVICE_REGEX = /^class\s+(\w+(?:Service|Manager|Repository|Handler))\s*(?:\([^)]*\))?\s*:/gm;
|
|
21
|
-
|
|
22
|
-
function parse(content, filePath) {
|
|
23
|
-
const entities = [];
|
|
24
|
-
const relations = [];
|
|
25
|
-
let m;
|
|
26
|
-
|
|
27
|
-
// ─── Django models ────────────────────────────────────────────
|
|
28
|
-
DJANGO_MODEL_REGEX.lastIndex = 0;
|
|
29
|
-
while ((m = DJANGO_MODEL_REGEX.exec(content)) !== null) {
|
|
30
|
-
const name = m[1];
|
|
31
|
-
entities.push({
|
|
32
|
-
id: `${filePath}::${name}`,
|
|
33
|
-
name,
|
|
34
|
-
type: 'model',
|
|
35
|
-
filePath,
|
|
36
|
-
attributes: { framework: 'django' },
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ─── SQLAlchemy models ────────────────────────────────────────
|
|
41
|
-
SQLALCHEMY_MODEL_REGEX.lastIndex = 0;
|
|
42
|
-
while ((m = SQLALCHEMY_MODEL_REGEX.exec(content)) !== null) {
|
|
43
|
-
const name = m[1];
|
|
44
|
-
// Avoid duplicating if already captured as a Django model
|
|
45
|
-
if (!entities.find(e => e.name === name && e.type === 'model')) {
|
|
46
|
-
entities.push({
|
|
47
|
-
id: `${filePath}::${name}`,
|
|
48
|
-
name,
|
|
49
|
-
type: 'model',
|
|
50
|
-
filePath,
|
|
51
|
-
attributes: { framework: 'sqlalchemy' },
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ─── FastAPI middleware ───────────────────────────────────────
|
|
57
|
-
MIDDLEWARE_REGEX.lastIndex = 0;
|
|
58
|
-
let middlewareIndex = 0;
|
|
59
|
-
while ((m = MIDDLEWARE_REGEX.exec(content)) !== null) {
|
|
60
|
-
// Try to extract the function name on the next non-empty line
|
|
61
|
-
const afterDecorator = content.slice(m.index + m[0].length);
|
|
62
|
-
const funcMatch = afterDecorator.match(/^\s*(?:async\s+)?def\s+(\w+)/);
|
|
63
|
-
const name = funcMatch ? funcMatch[1] : `middleware_${middlewareIndex++}`;
|
|
64
|
-
entities.push({
|
|
65
|
-
id: `${filePath}::${name}`,
|
|
66
|
-
name,
|
|
67
|
-
type: 'middleware',
|
|
68
|
-
filePath,
|
|
69
|
-
attributes: { protocol: 'http' },
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ─── Routes ───────────────────────────────────────────────────
|
|
74
|
-
ROUTE_REGEX.lastIndex = 0;
|
|
75
|
-
while ((m = ROUTE_REGEX.exec(content)) !== null) {
|
|
76
|
-
const method = m[1].toUpperCase();
|
|
77
|
-
const routePath = m[2];
|
|
78
|
-
// Try to extract the function name on the next non-empty line after decorator
|
|
79
|
-
const afterDecorator = content.slice(m.index + m[0].length);
|
|
80
|
-
const funcMatch = afterDecorator.match(/^\s*(?:async\s+)?def\s+(\w+)/);
|
|
81
|
-
const name = funcMatch ? funcMatch[1] : `${method}_${routePath.replace(/\//g, '_').replace(/^_/, '')}`;
|
|
82
|
-
entities.push({
|
|
83
|
-
id: `${filePath}::${name}`,
|
|
84
|
-
name,
|
|
85
|
-
type: 'api',
|
|
86
|
-
filePath,
|
|
87
|
-
attributes: { method, path: routePath },
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ─── Services / Managers ─────────────────────────────────────
|
|
92
|
-
SERVICE_REGEX.lastIndex = 0;
|
|
93
|
-
while ((m = SERVICE_REGEX.exec(content)) !== null) {
|
|
94
|
-
const name = m[1];
|
|
95
|
-
if (!entities.find(e => e.id === `${filePath}::${name}`)) {
|
|
96
|
-
entities.push({
|
|
97
|
-
id: `${filePath}::${name}`,
|
|
98
|
-
name,
|
|
99
|
-
type: 'service',
|
|
100
|
-
filePath,
|
|
101
|
-
attributes: {},
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return { entities, relations };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export { parse };
|