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.
- package/README.md +67 -0
- package/dist/archsync.cjs +2 -0
- package/package.json +11 -7
- 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
package/src/parsers/index.js
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import { parseNodeFile } from './nodeParser.js';
|
|
4
|
-
import { parseReactFile } from './reactParser.js';
|
|
5
|
-
import { parseFlutterFile } from './flutterParser.js';
|
|
6
|
-
import { parseSmartComments } from './smartComment.js';
|
|
7
|
-
import { parseWithAST } from './astParser.js';
|
|
8
|
-
import { parse as parsePython } from './pythonParser.js';
|
|
9
|
-
import { parse as parseGo } from './goParser.js';
|
|
10
|
-
import { parse as parseJava } from './javaParser.js';
|
|
11
|
-
import { parse as parseConfig } from './configParser.js';
|
|
12
|
-
|
|
13
|
-
// Extensions handled by content-based parsers (receive file content, not just path)
|
|
14
|
-
const CONFIG_BASENAMES = new Set(['package.json', 'docker-compose.yml', 'docker-compose.yaml']);
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Unified file parser — selects the right parser based on file extension and framework config.
|
|
18
|
-
*/
|
|
19
|
-
export function parseFile(filePath, options = {}) {
|
|
20
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
21
|
-
const basename = path.basename(filePath);
|
|
22
|
-
const results = [];
|
|
23
|
-
|
|
24
|
-
// Always check for smart comments first
|
|
25
|
-
const smartResult = parseSmartComments(filePath);
|
|
26
|
-
if (smartResult.entities.length > 0) {
|
|
27
|
-
results.push(smartResult);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ── Python (.py) ──────────────────────────────────────────────
|
|
31
|
-
if (ext === '.py') {
|
|
32
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
33
|
-
results.push(parsePython(content, filePath));
|
|
34
|
-
return mergeResults(results, filePath);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ── Go (.go) ──────────────────────────────────────────────────
|
|
38
|
-
if (ext === '.go') {
|
|
39
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
40
|
-
results.push(parseGo(content, filePath));
|
|
41
|
-
return mergeResults(results, filePath);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ── Java (.java) ──────────────────────────────────────────────
|
|
45
|
-
if (ext === '.java') {
|
|
46
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
47
|
-
results.push(parseJava(content, filePath));
|
|
48
|
-
return mergeResults(results, filePath);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ── Config files (package.json, docker-compose, .prisma) ─────
|
|
52
|
-
if (CONFIG_BASENAMES.has(basename) || ext === '.prisma') {
|
|
53
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
54
|
-
results.push(parseConfig(content, filePath));
|
|
55
|
-
return mergeResults(results, filePath);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// If AST mode is enabled, use deep parsing
|
|
59
|
-
if (options.ast && ['.js', '.ts', '.jsx', '.tsx'].includes(ext)) {
|
|
60
|
-
try {
|
|
61
|
-
const astResult = parseWithAST(filePath);
|
|
62
|
-
results.push(astResult);
|
|
63
|
-
return mergeResults(results, filePath);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
// Fall through to regex parsers
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Select regex parser based on extension and framework
|
|
70
|
-
const framework = options.framework || detectFramework(filePath);
|
|
71
|
-
|
|
72
|
-
switch (framework) {
|
|
73
|
-
case 'react':
|
|
74
|
-
case 'nextjs':
|
|
75
|
-
if (['.jsx', '.tsx'].includes(ext)) {
|
|
76
|
-
results.push(parseReactFile(filePath));
|
|
77
|
-
} else {
|
|
78
|
-
results.push(parseNodeFile(filePath));
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
|
|
82
|
-
case 'flutter':
|
|
83
|
-
if (ext === '.dart') {
|
|
84
|
-
results.push(parseFlutterFile(filePath));
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
|
|
88
|
-
case 'express':
|
|
89
|
-
case 'nestjs':
|
|
90
|
-
case 'node':
|
|
91
|
-
default:
|
|
92
|
-
if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) {
|
|
93
|
-
results.push(parseNodeFile(filePath));
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return mergeResults(results, filePath);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Detect framework from file path heuristics
|
|
102
|
-
function detectFramework(filePath) {
|
|
103
|
-
if (/\.dart$/.test(filePath)) return 'flutter';
|
|
104
|
-
if (/\.(jsx|tsx)$/.test(filePath)) return 'react';
|
|
105
|
-
if (/pages\/api\//.test(filePath)) return 'nextjs';
|
|
106
|
-
return 'node';
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ── CodeSee-style fallback: every source file deserves a node ────────
|
|
110
|
-
// When no parser extracted an entity from a source file, synthesise a
|
|
111
|
-
// generic `module` entity for the file itself. Without this, plain
|
|
112
|
-
// utility/config/hook modules are invisible on the map and import arrows
|
|
113
|
-
// pointing at them are silently dropped.
|
|
114
|
-
const MODULE_FALLBACK_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.dart', '.py', '.go', '.java']);
|
|
115
|
-
|
|
116
|
-
export function moduleFallbackEntity(filePath, system) {
|
|
117
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
118
|
-
if (!MODULE_FALLBACK_EXTS.has(ext)) return null;
|
|
119
|
-
const name = path.basename(filePath).replace(/\.[^.]+$/, '');
|
|
120
|
-
if (!name) return null;
|
|
121
|
-
// Module nodes represent the whole file — snippet is its opening lines
|
|
122
|
-
let snippet = '';
|
|
123
|
-
try {
|
|
124
|
-
snippet = fs.readFileSync(filePath, 'utf-8')
|
|
125
|
-
.split('\n').slice(0, SNIPPET_LINES)
|
|
126
|
-
.map(l => (l.length > 160 ? l.slice(0, 160) + '…' : l))
|
|
127
|
-
.join('\n');
|
|
128
|
-
} catch { /* unreadable file — no snippet */ }
|
|
129
|
-
return {
|
|
130
|
-
entityType: 'module',
|
|
131
|
-
name,
|
|
132
|
-
system: system || undefined,
|
|
133
|
-
data: { kind: 'module', ext },
|
|
134
|
-
metadata: { sourceFile: filePath, line: 1, snippet },
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── Line-number annotation ───────────────────────────────────────────
|
|
139
|
-
// Entities anchor to the exact source line where they're declared so the
|
|
140
|
-
// canvas can point arrows at code positions and deep-link into editors.
|
|
141
|
-
// Parsers that already record metadata.line are left untouched; everything
|
|
142
|
-
// else is located by searching the source for the entity's identifying text.
|
|
143
|
-
const SNIPPET_LINES = 14;
|
|
144
|
-
|
|
145
|
-
function annotateLines(filePath, entities) {
|
|
146
|
-
let source;
|
|
147
|
-
try { source = fs.readFileSync(filePath, 'utf-8'); } catch { return; }
|
|
148
|
-
const sourceLines = source.split('\n');
|
|
149
|
-
|
|
150
|
-
const lineOf = (idx) => {
|
|
151
|
-
let line = 1;
|
|
152
|
-
for (let i = 0; i < idx; i++) if (source.charCodeAt(i) === 10) line++;
|
|
153
|
-
return line;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
for (const e of entities) {
|
|
157
|
-
let line = e.metadata?.line;
|
|
158
|
-
if (!line) {
|
|
159
|
-
let needle = e.name || '';
|
|
160
|
-
// "GET /api/users" → search the path; "useQuery:useFoo" → useFoo
|
|
161
|
-
const api = needle.match(/^[A-Z]+\s+(\/\S+)/);
|
|
162
|
-
if (api) needle = api[1];
|
|
163
|
-
else if (needle.includes(':')) needle = needle.split(':').pop();
|
|
164
|
-
|
|
165
|
-
line = 1;
|
|
166
|
-
if (needle && needle.length >= 2) {
|
|
167
|
-
const idx = source.indexOf(needle);
|
|
168
|
-
if (idx >= 0) line = lineOf(idx);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// The actual code at the entity's declaration — what the canvas
|
|
172
|
-
// shows when a member row is expanded.
|
|
173
|
-
const snippet = sourceLines
|
|
174
|
-
.slice(line - 1, line - 1 + SNIPPET_LINES)
|
|
175
|
-
.map(l => (l.length > 160 ? l.slice(0, 160) + '…' : l))
|
|
176
|
-
.join('\n');
|
|
177
|
-
e.metadata = { ...(e.metadata || {}), line, snippet };
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Merge multiple parser results, deduplicating entities by name+type
|
|
182
|
-
function mergeResults(results, filePath) {
|
|
183
|
-
const entityMap = new Map();
|
|
184
|
-
const allRelations = [];
|
|
185
|
-
|
|
186
|
-
for (const result of results) {
|
|
187
|
-
for (const entity of result.entities || []) {
|
|
188
|
-
const key = `${entity.entityType}:${entity.name}`;
|
|
189
|
-
if (!entityMap.has(key)) {
|
|
190
|
-
entityMap.set(key, entity);
|
|
191
|
-
} else {
|
|
192
|
-
// Merge data
|
|
193
|
-
const existing = entityMap.get(key);
|
|
194
|
-
existing.data = { ...existing.data, ...entity.data };
|
|
195
|
-
if (entity.metadata?.tags) {
|
|
196
|
-
existing.metadata.tags = [...new Set([...(existing.metadata?.tags || []), ...entity.metadata.tags])];
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
allRelations.push(...(result.relations || []));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const entities = Array.from(entityMap.values());
|
|
204
|
-
annotateLines(filePath, entities);
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
filePath,
|
|
208
|
-
entities,
|
|
209
|
-
relations: allRelations,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Java parser — extracts backend entities from Java source files.
|
|
3
|
-
* Detects: @RestController, @Service, @Repository, @Entity annotations,
|
|
4
|
-
* and Spring MVC route mappings.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Class-level annotations followed (eventually) by a class declaration
|
|
8
|
-
const CLASS_ANNOTATION_REGEX = /(@(?:RestController|Service|Repository|Entity|Controller)(?:\s*\([^)]*\))?(?:\s*\n\s*@\w+(?:\s*\([^)]*\))?)*)\s*(?:public\s+)?(?:abstract\s+)?class\s+(\w+)/g;
|
|
9
|
-
|
|
10
|
-
// Route mapping annotations: @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping
|
|
11
|
-
const MAPPING_REGEX = /@(RequestMapping|GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping)\s*(?:\(\s*(?:value\s*=\s*)?["']([^"']+)["']\s*\))?/g;
|
|
12
|
-
|
|
13
|
-
function resolveType(annotations) {
|
|
14
|
-
if (/@RestController/.test(annotations) || /@Controller/.test(annotations)) return 'controller';
|
|
15
|
-
if (/@Service/.test(annotations)) return 'service';
|
|
16
|
-
if (/@Repository/.test(annotations)) return 'database';
|
|
17
|
-
if (/@Entity/.test(annotations)) return 'model';
|
|
18
|
-
return 'class';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function resolveHttpMethod(mappingAnnotation) {
|
|
22
|
-
const map = {
|
|
23
|
-
RequestMapping: 'REQUEST',
|
|
24
|
-
GetMapping: 'GET',
|
|
25
|
-
PostMapping: 'POST',
|
|
26
|
-
PutMapping: 'PUT',
|
|
27
|
-
PatchMapping: 'PATCH',
|
|
28
|
-
DeleteMapping: 'DELETE',
|
|
29
|
-
};
|
|
30
|
-
return map[mappingAnnotation] || 'REQUEST';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function parse(content, filePath) {
|
|
34
|
-
const entities = [];
|
|
35
|
-
const relations = [];
|
|
36
|
-
let m;
|
|
37
|
-
|
|
38
|
-
// ─── Annotated classes ────────────────────────────────────────
|
|
39
|
-
CLASS_ANNOTATION_REGEX.lastIndex = 0;
|
|
40
|
-
while ((m = CLASS_ANNOTATION_REGEX.exec(content)) !== null) {
|
|
41
|
-
const annotations = m[1];
|
|
42
|
-
const className = m[2];
|
|
43
|
-
const type = resolveType(annotations);
|
|
44
|
-
|
|
45
|
-
const attributes = {};
|
|
46
|
-
|
|
47
|
-
// Collect any route mappings that appear within the annotation block or on the class
|
|
48
|
-
const classMappingMatch = annotations.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']/);
|
|
49
|
-
if (classMappingMatch) {
|
|
50
|
-
attributes.baseRoute = classMappingMatch[1];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
entities.push({
|
|
54
|
-
id: `${filePath}::${className}`,
|
|
55
|
-
name: className,
|
|
56
|
-
type,
|
|
57
|
-
filePath,
|
|
58
|
-
attributes,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ─── Route mappings (method-level) ───────────────────────────
|
|
63
|
-
MAPPING_REGEX.lastIndex = 0;
|
|
64
|
-
while ((m = MAPPING_REGEX.exec(content)) !== null) {
|
|
65
|
-
const annotationName = m[1];
|
|
66
|
-
const routePath = m[2] || '/';
|
|
67
|
-
const httpMethod = resolveHttpMethod(annotationName);
|
|
68
|
-
|
|
69
|
-
// Try to find the method name immediately after this annotation
|
|
70
|
-
const afterAnnotation = content.slice(m.index + m[0].length);
|
|
71
|
-
const methodMatch = afterAnnotation.match(/^\s*(?:@\w+(?:\s*\([^)]*\))?\s*)*(?:public|protected|private)?\s+\w[\w<>,\s]*\s+(\w+)\s*\(/);
|
|
72
|
-
const methodName = methodMatch ? methodMatch[1] : `${httpMethod}_${routePath.replace(/\//g, '_').replace(/^_/, '')}`;
|
|
73
|
-
|
|
74
|
-
const name = `${methodName}`;
|
|
75
|
-
if (!entities.find(e => e.id === `${filePath}::${name}`)) {
|
|
76
|
-
entities.push({
|
|
77
|
-
id: `${filePath}::${name}`,
|
|
78
|
-
name,
|
|
79
|
-
type: 'api',
|
|
80
|
-
filePath,
|
|
81
|
-
attributes: { method: httpMethod, path: routePath, annotation: annotationName },
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { entities, relations };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export { parse };
|