archsync 1.0.0 → 1.0.2

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.
Files changed (50) hide show
  1. package/README.md +67 -0
  2. package/dist/archsync.cjs +2 -0
  3. package/package.json +11 -7
  4. package/bin/cli.js +0 -91
  5. package/src/__tests__/e2e-workflow.test.js +0 -66
  6. package/src/__tests__/hashEngine.test.js +0 -109
  7. package/src/__tests__/impact.test.js +0 -137
  8. package/src/__tests__/parsers.test.js +0 -496
  9. package/src/__tests__/scan-pipeline.test.js +0 -332
  10. package/src/__tests__/schemaBuilder.test.js +0 -145
  11. package/src/__tests__/workspace.test.js +0 -178
  12. package/src/commands/backup.js +0 -54
  13. package/src/commands/connect.js +0 -129
  14. package/src/commands/diff.js +0 -228
  15. package/src/commands/export.js +0 -125
  16. package/src/commands/impactReport.js +0 -50
  17. package/src/commands/import.js +0 -126
  18. package/src/commands/init.js +0 -80
  19. package/src/commands/login.js +0 -116
  20. package/src/commands/plugin.js +0 -28
  21. package/src/commands/push.js +0 -194
  22. package/src/commands/register.js +0 -127
  23. package/src/commands/scan.js +0 -498
  24. package/src/commands/serve.js +0 -133
  25. package/src/commands/setup.js +0 -233
  26. package/src/commands/status.js +0 -56
  27. package/src/commands/validate.js +0 -245
  28. package/src/commands/watch.js +0 -70
  29. package/src/core/credentialStore.js +0 -76
  30. package/src/core/hashEngine.js +0 -34
  31. package/src/core/impactEngine.js +0 -192
  32. package/src/core/monorepoDetector.js +0 -41
  33. package/src/core/pluginManager.js +0 -40
  34. package/src/core/relationshipEngine.js +0 -917
  35. package/src/core/requestSigning.js +0 -16
  36. package/src/core/schemaBuilder.js +0 -230
  37. package/src/core/schemaDeduplicator.js +0 -54
  38. package/src/core/supabaseClient.js +0 -68
  39. package/src/core/workspaceDetector.js +0 -113
  40. package/src/parsers/astParser.js +0 -274
  41. package/src/parsers/configParser.js +0 -49
  42. package/src/parsers/dependencyGraph.js +0 -31
  43. package/src/parsers/flutterParser.js +0 -98
  44. package/src/parsers/goParser.js +0 -99
  45. package/src/parsers/index.js +0 -211
  46. package/src/parsers/javaParser.js +0 -89
  47. package/src/parsers/nodeParser.js +0 -429
  48. package/src/parsers/pythonParser.js +0 -109
  49. package/src/parsers/reactParser.js +0 -368
  50. 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 };