@tekyzinc/gsd-t 2.50.12 → 2.53.10
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/CHANGELOG.md +24 -0
- package/README.md +379 -372
- package/bin/component-registry.js +250 -0
- package/bin/graph-cgc.js +510 -510
- package/bin/graph-indexer.js +147 -147
- package/bin/graph-overlay.js +195 -195
- package/bin/graph-parsers.js +327 -327
- package/bin/graph-query.js +453 -452
- package/bin/graph-store.js +154 -154
- package/bin/qa-calibrator.js +194 -0
- package/bin/scan-data-collector.js +153 -153
- package/bin/scan-diagrams-generators.js +187 -187
- package/bin/scan-diagrams.js +79 -79
- package/bin/scan-renderer.js +92 -92
- package/bin/scan-report-sections.js +121 -121
- package/bin/scan-report.js +184 -184
- package/bin/scan-schema-parsers.js +199 -199
- package/bin/scan-schema.js +103 -103
- package/bin/token-budget.js +246 -0
- package/commands/Claude-md.md +10 -10
- package/commands/branch.md +15 -15
- package/commands/checkin.md +45 -45
- package/commands/global-change.md +209 -209
- package/commands/gsd-t-audit.md +199 -0
- package/commands/gsd-t-backlog-add.md +94 -94
- package/commands/gsd-t-backlog-edit.md +111 -111
- package/commands/gsd-t-backlog-list.md +63 -63
- package/commands/gsd-t-backlog-move.md +94 -94
- package/commands/gsd-t-backlog-promote.md +123 -123
- package/commands/gsd-t-backlog-remove.md +86 -86
- package/commands/gsd-t-backlog-settings.md +158 -158
- package/commands/gsd-t-complete-milestone.md +528 -515
- package/commands/gsd-t-debug.md +506 -399
- package/commands/gsd-t-discuss.md +174 -174
- package/commands/gsd-t-execute.md +758 -634
- package/commands/gsd-t-feature.md +276 -276
- package/commands/gsd-t-health.md +142 -142
- package/commands/gsd-t-help.md +465 -457
- package/commands/gsd-t-impact.md +302 -302
- package/commands/gsd-t-init.md +320 -280
- package/commands/gsd-t-integrate.md +365 -249
- package/commands/gsd-t-milestone.md +87 -87
- package/commands/gsd-t-partition.md +442 -361
- package/commands/gsd-t-pause.md +82 -82
- package/commands/gsd-t-plan.md +345 -344
- package/commands/gsd-t-populate.md +111 -111
- package/commands/gsd-t-prd.md +326 -326
- package/commands/gsd-t-project.md +211 -211
- package/commands/gsd-t-promote-debt.md +123 -123
- package/commands/gsd-t-prompt.md +137 -137
- package/commands/gsd-t-qa.md +266 -266
- package/commands/gsd-t-quick.md +357 -234
- package/commands/gsd-t-reflect.md +134 -134
- package/commands/gsd-t-resume.md +72 -72
- package/commands/gsd-t-scan.md +615 -615
- package/commands/gsd-t-setup.md +76 -0
- package/commands/gsd-t-status.md +192 -166
- package/commands/gsd-t-test-sync.md +381 -381
- package/commands/gsd-t-triage-and-merge.md +171 -171
- package/commands/gsd-t-verify.md +382 -382
- package/commands/gsd-t-visualize.md +118 -118
- package/commands/gsd-t-wave.md +401 -378
- package/docs/GSD-T-README.md +425 -422
- package/docs/architecture.md +385 -369
- package/docs/harness-design-analysis.md +371 -0
- package/docs/infrastructure.md +205 -205
- package/docs/prd-graph-engine.md +398 -398
- package/docs/prd-gsd2-hybrid.md +559 -559
- package/docs/prd-harness-evolution.md +583 -0
- package/docs/requirements.md +14 -0
- package/docs/workflows.md +226 -226
- package/examples/.gsd-t/domains/example-domain/scope.md +13 -13
- package/package.json +40 -40
- package/scripts/gsd-t-auto-route.js +39 -39
- package/scripts/gsd-t-dashboard-mockup.html +1143 -1143
- package/scripts/gsd-t-dashboard-server.js +171 -171
- package/scripts/gsd-t-dashboard.html +262 -262
- package/scripts/gsd-t-event-writer.js +128 -128
- package/scripts/gsd-t-statusline.js +94 -94
- package/scripts/gsd-t-tools.js +175 -175
- package/templates/CLAUDE-global.md +639 -614
- package/templates/CLAUDE-project.md +24 -0
- package/templates/backlog-settings.md +18 -18
- package/templates/backlog.md +1 -1
- package/templates/progress.md +40 -40
- package/templates/shared-services-contract.md +60 -60
- package/templates/stacks/desktop.ini +2 -2
- package/bin/desktop.ini +0 -2
- package/commands/desktop.ini +0 -2
- package/docs/ci-examples/desktop.ini +0 -2
- package/docs/desktop.ini +0 -2
- package/examples/.gsd-t/contracts/desktop.ini +0 -2
- package/examples/.gsd-t/desktop.ini +0 -2
- package/examples/.gsd-t/domains/desktop.ini +0 -2
- package/examples/.gsd-t/domains/example-domain/desktop.ini +0 -2
- package/examples/desktop.ini +0 -2
- package/examples/rules/desktop.ini +0 -2
- package/scripts/desktop.ini +0 -2
- package/templates/desktop.ini +0 -2
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
function parsePrisma(filePath, warnings) {
|
|
6
|
-
try {
|
|
7
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
8
|
-
const entities = [];
|
|
9
|
-
const modelRe = /model\s+(\w+)\s*\{([^}]+)\}/g;
|
|
10
|
-
let m;
|
|
11
|
-
while ((m = modelRe.exec(content)) !== null) {
|
|
12
|
-
const name = m[1];
|
|
13
|
-
const body = m[2];
|
|
14
|
-
const fields = [];
|
|
15
|
-
let primaryKey = null;
|
|
16
|
-
const relations = [];
|
|
17
|
-
for (const line of body.split('\n')) {
|
|
18
|
-
const trimmed = line.trim();
|
|
19
|
-
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('@@')) continue;
|
|
20
|
-
const parts = trimmed.split(/\s+/);
|
|
21
|
-
if (parts.length < 2) continue;
|
|
22
|
-
const fieldName = parts[0];
|
|
23
|
-
const rawType = parts[1];
|
|
24
|
-
const type = rawType.replace('?', '').replace('[]', '');
|
|
25
|
-
const nullable = rawType.includes('?');
|
|
26
|
-
const unique = trimmed.includes('@unique');
|
|
27
|
-
if (trimmed.includes('@id')) primaryKey = fieldName;
|
|
28
|
-
if (trimmed.includes('@relation')) {
|
|
29
|
-
const relType = rawType.includes('[]') ? 'one-to-many' : 'many-to-one';
|
|
30
|
-
relations.push({ type: relType, fromEntity: name, toEntity: type, throughTable: null });
|
|
31
|
-
} else {
|
|
32
|
-
fields.push({ name: fieldName, type, nullable, unique });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
entities.push({ name, fields, primaryKey, relations });
|
|
36
|
-
}
|
|
37
|
-
return entities;
|
|
38
|
-
} catch (e) { warnings.push('parsePrisma: ' + e.message); return []; }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseTypeOrm(files, warnings) {
|
|
42
|
-
const entities = [];
|
|
43
|
-
for (const filePath of files) {
|
|
44
|
-
try {
|
|
45
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
46
|
-
const classMatch = content.match(/export class (\w+)/);
|
|
47
|
-
if (!classMatch) continue;
|
|
48
|
-
const name = classMatch[1];
|
|
49
|
-
const fields = [];
|
|
50
|
-
let primaryKey = null;
|
|
51
|
-
const relations = [];
|
|
52
|
-
const lines = content.split('\n');
|
|
53
|
-
for (let i = 0; i < lines.length; i++) {
|
|
54
|
-
const line = lines[i].trim();
|
|
55
|
-
if (/@PrimaryGeneratedColumn|@PrimaryColumn/.test(line)) {
|
|
56
|
-
const next = (lines[i + 1] || '').trim();
|
|
57
|
-
const nm = next.match(/(\w+)\s*:/);
|
|
58
|
-
if (nm) primaryKey = nm[1];
|
|
59
|
-
} else if (/@Column/.test(line)) {
|
|
60
|
-
const next = (lines[i + 1] || '').trim();
|
|
61
|
-
const nm = next.match(/(\w+)\s*:\s*(\w+)/);
|
|
62
|
-
if (nm) fields.push({ name: nm[1], type: nm[2], nullable: line.includes('nullable: true'), unique: line.includes('unique: true') });
|
|
63
|
-
} else if (/@(ManyToOne|OneToMany|ManyToMany|OneToOne)\(/.test(line)) {
|
|
64
|
-
const tm = line.match(/\(\s*\(\s*\)\s*=>\s*(\w+)/);
|
|
65
|
-
const relMap = { ManyToOne: 'many-to-one', OneToMany: 'one-to-many', ManyToMany: 'many-to-many', OneToOne: 'one-to-one' };
|
|
66
|
-
const rm = line.match(/@(\w+)\(/);
|
|
67
|
-
if (tm && rm) relations.push({ type: relMap[rm[1]] || 'many-to-one', fromEntity: name, toEntity: tm[1], throughTable: null });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
entities.push({ name, fields, primaryKey, relations });
|
|
71
|
-
} catch (e) { warnings.push('parseTypeOrm: ' + e.message); }
|
|
72
|
-
}
|
|
73
|
-
return entities;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function parseDrizzle(files, warnings) {
|
|
77
|
-
const entities = [];
|
|
78
|
-
for (const filePath of files) {
|
|
79
|
-
try {
|
|
80
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
81
|
-
const tableRe = /(?:pgTable|mysqlTable|sqliteTable)\s*\(\s*['"](\w+)['"]/g;
|
|
82
|
-
let m;
|
|
83
|
-
while ((m = tableRe.exec(content)) !== null) {
|
|
84
|
-
const name = m[1];
|
|
85
|
-
const fields = [];
|
|
86
|
-
const colRe = /(\w+)\s*:\s*\w+\(/g;
|
|
87
|
-
let cm;
|
|
88
|
-
while ((cm = colRe.exec(content)) !== null) {
|
|
89
|
-
fields.push({ name: cm[1], type: 'unknown', nullable: true, unique: false });
|
|
90
|
-
}
|
|
91
|
-
entities.push({ name, fields, primaryKey: null, relations: [] });
|
|
92
|
-
}
|
|
93
|
-
} catch (e) { warnings.push('parseDrizzle: ' + e.message); }
|
|
94
|
-
}
|
|
95
|
-
return entities;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function parseMongoose(files, warnings) {
|
|
99
|
-
const entities = [];
|
|
100
|
-
for (const filePath of files) {
|
|
101
|
-
try {
|
|
102
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
103
|
-
const nameMatch = content.match(/const\s+(\w+)Schema\s*=\s*new\s+(?:mongoose\.)?Schema/);
|
|
104
|
-
const name = nameMatch ? nameMatch[1] : path.basename(filePath, path.extname(filePath));
|
|
105
|
-
const fields = [];
|
|
106
|
-
const relations = [];
|
|
107
|
-
const fieldRe = /(\w+)\s*:\s*\{[^}]*type\s*:\s*(\w+)/g;
|
|
108
|
-
let m;
|
|
109
|
-
while ((m = fieldRe.exec(content)) !== null) {
|
|
110
|
-
fields.push({ name: m[1], type: m[2], nullable: true, unique: false });
|
|
111
|
-
}
|
|
112
|
-
const refRe = /ref\s*:\s*['"](\w+)['"]/g;
|
|
113
|
-
while ((m = refRe.exec(content)) !== null) {
|
|
114
|
-
relations.push({ type: 'many-to-one', fromEntity: name, toEntity: m[1], throughTable: null });
|
|
115
|
-
}
|
|
116
|
-
entities.push({ name, fields, primaryKey: '_id', relations });
|
|
117
|
-
} catch (e) { warnings.push('parseMongoose: ' + e.message); }
|
|
118
|
-
}
|
|
119
|
-
return entities;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function parseSequelize(files, warnings) {
|
|
123
|
-
const entities = [];
|
|
124
|
-
for (const filePath of files) {
|
|
125
|
-
try {
|
|
126
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
127
|
-
const classMatch = content.match(/class (\w+) extends Model/);
|
|
128
|
-
if (!classMatch) continue;
|
|
129
|
-
const name = classMatch[1];
|
|
130
|
-
const fields = [];
|
|
131
|
-
let primaryKey = null;
|
|
132
|
-
const colRe = /(\w+)\s*:\s*\{[^}]*type\s*:\s*DataTypes\.(\w+)([^}]*)\}/g;
|
|
133
|
-
let m;
|
|
134
|
-
while ((m = colRe.exec(content)) !== null) {
|
|
135
|
-
const isPk = m[3].includes('primaryKey: true');
|
|
136
|
-
if (isPk) primaryKey = m[1];
|
|
137
|
-
fields.push({ name: m[1], type: 'DataTypes.' + m[2], nullable: !m[3].includes('allowNull: false'), unique: m[3].includes('unique: true') });
|
|
138
|
-
}
|
|
139
|
-
entities.push({ name, fields, primaryKey, relations: [] });
|
|
140
|
-
} catch (e) { warnings.push('parseSequelize: ' + e.message); }
|
|
141
|
-
}
|
|
142
|
-
return entities;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function parseSqlAlchemy(files, warnings) {
|
|
146
|
-
const entities = [];
|
|
147
|
-
for (const filePath of files) {
|
|
148
|
-
try {
|
|
149
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
150
|
-
const classRe = /class (\w+)\s*\([^)]*Base[^)]*\):/g;
|
|
151
|
-
let m;
|
|
152
|
-
while ((m = classRe.exec(content)) !== null) {
|
|
153
|
-
const name = m[1];
|
|
154
|
-
const fields = [];
|
|
155
|
-
let primaryKey = null;
|
|
156
|
-
const colRe = /(\w+)\s*=\s*Column\((\w+)([^)]*)\)/g;
|
|
157
|
-
let cm;
|
|
158
|
-
while ((cm = colRe.exec(content)) !== null) {
|
|
159
|
-
if (cm[3].includes('primary_key=True')) primaryKey = cm[1];
|
|
160
|
-
fields.push({ name: cm[1], type: cm[2], nullable: !cm[3].includes('nullable=False'), unique: cm[3].includes('unique=True') });
|
|
161
|
-
}
|
|
162
|
-
entities.push({ name, fields, primaryKey, relations: [] });
|
|
163
|
-
}
|
|
164
|
-
} catch (e) { warnings.push('parseSqlAlchemy: ' + e.message); }
|
|
165
|
-
}
|
|
166
|
-
return entities;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function parseRawSql(files, warnings) {
|
|
170
|
-
const entities = [];
|
|
171
|
-
for (const filePath of files) {
|
|
172
|
-
try {
|
|
173
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
174
|
-
const tableRe = /CREATE TABLE\s+[`"]?(\w+)[`"]?\s*\(([^;]+)\)/gi;
|
|
175
|
-
let m;
|
|
176
|
-
while ((m = tableRe.exec(content)) !== null) {
|
|
177
|
-
const name = m[1];
|
|
178
|
-
const body = m[2];
|
|
179
|
-
const fields = [];
|
|
180
|
-
let primaryKey = null;
|
|
181
|
-
for (const line of body.split('\n')) {
|
|
182
|
-
const trimmed = line.trim().replace(/,$/, '');
|
|
183
|
-
if (!trimmed || trimmed.startsWith('--') || /^PRIMARY KEY\s*\(/i.test(trimmed)) continue;
|
|
184
|
-
const parts = trimmed.split(/\s+/);
|
|
185
|
-
if (parts.length < 2) continue;
|
|
186
|
-
const fieldName = parts[0].replace(/[`"]/g, '');
|
|
187
|
-
const type = parts[1];
|
|
188
|
-
const isPk = /PRIMARY KEY/i.test(trimmed);
|
|
189
|
-
if (isPk) primaryKey = fieldName;
|
|
190
|
-
fields.push({ name: fieldName, type, nullable: !/NOT NULL/i.test(trimmed), unique: /\bUNIQUE\b/i.test(trimmed) });
|
|
191
|
-
}
|
|
192
|
-
entities.push({ name, fields, primaryKey, relations: [] });
|
|
193
|
-
}
|
|
194
|
-
} catch (e) { warnings.push('parseRawSql: ' + e.message); }
|
|
195
|
-
}
|
|
196
|
-
return entities;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
module.exports = { parsePrisma, parseTypeOrm, parseDrizzle, parseMongoose, parseSequelize, parseSqlAlchemy, parseRawSql };
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function parsePrisma(filePath, warnings) {
|
|
6
|
+
try {
|
|
7
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
8
|
+
const entities = [];
|
|
9
|
+
const modelRe = /model\s+(\w+)\s*\{([^}]+)\}/g;
|
|
10
|
+
let m;
|
|
11
|
+
while ((m = modelRe.exec(content)) !== null) {
|
|
12
|
+
const name = m[1];
|
|
13
|
+
const body = m[2];
|
|
14
|
+
const fields = [];
|
|
15
|
+
let primaryKey = null;
|
|
16
|
+
const relations = [];
|
|
17
|
+
for (const line of body.split('\n')) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('@@')) continue;
|
|
20
|
+
const parts = trimmed.split(/\s+/);
|
|
21
|
+
if (parts.length < 2) continue;
|
|
22
|
+
const fieldName = parts[0];
|
|
23
|
+
const rawType = parts[1];
|
|
24
|
+
const type = rawType.replace('?', '').replace('[]', '');
|
|
25
|
+
const nullable = rawType.includes('?');
|
|
26
|
+
const unique = trimmed.includes('@unique');
|
|
27
|
+
if (trimmed.includes('@id')) primaryKey = fieldName;
|
|
28
|
+
if (trimmed.includes('@relation')) {
|
|
29
|
+
const relType = rawType.includes('[]') ? 'one-to-many' : 'many-to-one';
|
|
30
|
+
relations.push({ type: relType, fromEntity: name, toEntity: type, throughTable: null });
|
|
31
|
+
} else {
|
|
32
|
+
fields.push({ name: fieldName, type, nullable, unique });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
entities.push({ name, fields, primaryKey, relations });
|
|
36
|
+
}
|
|
37
|
+
return entities;
|
|
38
|
+
} catch (e) { warnings.push('parsePrisma: ' + e.message); return []; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseTypeOrm(files, warnings) {
|
|
42
|
+
const entities = [];
|
|
43
|
+
for (const filePath of files) {
|
|
44
|
+
try {
|
|
45
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
46
|
+
const classMatch = content.match(/export class (\w+)/);
|
|
47
|
+
if (!classMatch) continue;
|
|
48
|
+
const name = classMatch[1];
|
|
49
|
+
const fields = [];
|
|
50
|
+
let primaryKey = null;
|
|
51
|
+
const relations = [];
|
|
52
|
+
const lines = content.split('\n');
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
const line = lines[i].trim();
|
|
55
|
+
if (/@PrimaryGeneratedColumn|@PrimaryColumn/.test(line)) {
|
|
56
|
+
const next = (lines[i + 1] || '').trim();
|
|
57
|
+
const nm = next.match(/(\w+)\s*:/);
|
|
58
|
+
if (nm) primaryKey = nm[1];
|
|
59
|
+
} else if (/@Column/.test(line)) {
|
|
60
|
+
const next = (lines[i + 1] || '').trim();
|
|
61
|
+
const nm = next.match(/(\w+)\s*:\s*(\w+)/);
|
|
62
|
+
if (nm) fields.push({ name: nm[1], type: nm[2], nullable: line.includes('nullable: true'), unique: line.includes('unique: true') });
|
|
63
|
+
} else if (/@(ManyToOne|OneToMany|ManyToMany|OneToOne)\(/.test(line)) {
|
|
64
|
+
const tm = line.match(/\(\s*\(\s*\)\s*=>\s*(\w+)/);
|
|
65
|
+
const relMap = { ManyToOne: 'many-to-one', OneToMany: 'one-to-many', ManyToMany: 'many-to-many', OneToOne: 'one-to-one' };
|
|
66
|
+
const rm = line.match(/@(\w+)\(/);
|
|
67
|
+
if (tm && rm) relations.push({ type: relMap[rm[1]] || 'many-to-one', fromEntity: name, toEntity: tm[1], throughTable: null });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
entities.push({ name, fields, primaryKey, relations });
|
|
71
|
+
} catch (e) { warnings.push('parseTypeOrm: ' + e.message); }
|
|
72
|
+
}
|
|
73
|
+
return entities;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseDrizzle(files, warnings) {
|
|
77
|
+
const entities = [];
|
|
78
|
+
for (const filePath of files) {
|
|
79
|
+
try {
|
|
80
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
81
|
+
const tableRe = /(?:pgTable|mysqlTable|sqliteTable)\s*\(\s*['"](\w+)['"]/g;
|
|
82
|
+
let m;
|
|
83
|
+
while ((m = tableRe.exec(content)) !== null) {
|
|
84
|
+
const name = m[1];
|
|
85
|
+
const fields = [];
|
|
86
|
+
const colRe = /(\w+)\s*:\s*\w+\(/g;
|
|
87
|
+
let cm;
|
|
88
|
+
while ((cm = colRe.exec(content)) !== null) {
|
|
89
|
+
fields.push({ name: cm[1], type: 'unknown', nullable: true, unique: false });
|
|
90
|
+
}
|
|
91
|
+
entities.push({ name, fields, primaryKey: null, relations: [] });
|
|
92
|
+
}
|
|
93
|
+
} catch (e) { warnings.push('parseDrizzle: ' + e.message); }
|
|
94
|
+
}
|
|
95
|
+
return entities;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseMongoose(files, warnings) {
|
|
99
|
+
const entities = [];
|
|
100
|
+
for (const filePath of files) {
|
|
101
|
+
try {
|
|
102
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
103
|
+
const nameMatch = content.match(/const\s+(\w+)Schema\s*=\s*new\s+(?:mongoose\.)?Schema/);
|
|
104
|
+
const name = nameMatch ? nameMatch[1] : path.basename(filePath, path.extname(filePath));
|
|
105
|
+
const fields = [];
|
|
106
|
+
const relations = [];
|
|
107
|
+
const fieldRe = /(\w+)\s*:\s*\{[^}]*type\s*:\s*(\w+)/g;
|
|
108
|
+
let m;
|
|
109
|
+
while ((m = fieldRe.exec(content)) !== null) {
|
|
110
|
+
fields.push({ name: m[1], type: m[2], nullable: true, unique: false });
|
|
111
|
+
}
|
|
112
|
+
const refRe = /ref\s*:\s*['"](\w+)['"]/g;
|
|
113
|
+
while ((m = refRe.exec(content)) !== null) {
|
|
114
|
+
relations.push({ type: 'many-to-one', fromEntity: name, toEntity: m[1], throughTable: null });
|
|
115
|
+
}
|
|
116
|
+
entities.push({ name, fields, primaryKey: '_id', relations });
|
|
117
|
+
} catch (e) { warnings.push('parseMongoose: ' + e.message); }
|
|
118
|
+
}
|
|
119
|
+
return entities;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseSequelize(files, warnings) {
|
|
123
|
+
const entities = [];
|
|
124
|
+
for (const filePath of files) {
|
|
125
|
+
try {
|
|
126
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
127
|
+
const classMatch = content.match(/class (\w+) extends Model/);
|
|
128
|
+
if (!classMatch) continue;
|
|
129
|
+
const name = classMatch[1];
|
|
130
|
+
const fields = [];
|
|
131
|
+
let primaryKey = null;
|
|
132
|
+
const colRe = /(\w+)\s*:\s*\{[^}]*type\s*:\s*DataTypes\.(\w+)([^}]*)\}/g;
|
|
133
|
+
let m;
|
|
134
|
+
while ((m = colRe.exec(content)) !== null) {
|
|
135
|
+
const isPk = m[3].includes('primaryKey: true');
|
|
136
|
+
if (isPk) primaryKey = m[1];
|
|
137
|
+
fields.push({ name: m[1], type: 'DataTypes.' + m[2], nullable: !m[3].includes('allowNull: false'), unique: m[3].includes('unique: true') });
|
|
138
|
+
}
|
|
139
|
+
entities.push({ name, fields, primaryKey, relations: [] });
|
|
140
|
+
} catch (e) { warnings.push('parseSequelize: ' + e.message); }
|
|
141
|
+
}
|
|
142
|
+
return entities;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function parseSqlAlchemy(files, warnings) {
|
|
146
|
+
const entities = [];
|
|
147
|
+
for (const filePath of files) {
|
|
148
|
+
try {
|
|
149
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
150
|
+
const classRe = /class (\w+)\s*\([^)]*Base[^)]*\):/g;
|
|
151
|
+
let m;
|
|
152
|
+
while ((m = classRe.exec(content)) !== null) {
|
|
153
|
+
const name = m[1];
|
|
154
|
+
const fields = [];
|
|
155
|
+
let primaryKey = null;
|
|
156
|
+
const colRe = /(\w+)\s*=\s*Column\((\w+)([^)]*)\)/g;
|
|
157
|
+
let cm;
|
|
158
|
+
while ((cm = colRe.exec(content)) !== null) {
|
|
159
|
+
if (cm[3].includes('primary_key=True')) primaryKey = cm[1];
|
|
160
|
+
fields.push({ name: cm[1], type: cm[2], nullable: !cm[3].includes('nullable=False'), unique: cm[3].includes('unique=True') });
|
|
161
|
+
}
|
|
162
|
+
entities.push({ name, fields, primaryKey, relations: [] });
|
|
163
|
+
}
|
|
164
|
+
} catch (e) { warnings.push('parseSqlAlchemy: ' + e.message); }
|
|
165
|
+
}
|
|
166
|
+
return entities;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseRawSql(files, warnings) {
|
|
170
|
+
const entities = [];
|
|
171
|
+
for (const filePath of files) {
|
|
172
|
+
try {
|
|
173
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
174
|
+
const tableRe = /CREATE TABLE\s+[`"]?(\w+)[`"]?\s*\(([^;]+)\)/gi;
|
|
175
|
+
let m;
|
|
176
|
+
while ((m = tableRe.exec(content)) !== null) {
|
|
177
|
+
const name = m[1];
|
|
178
|
+
const body = m[2];
|
|
179
|
+
const fields = [];
|
|
180
|
+
let primaryKey = null;
|
|
181
|
+
for (const line of body.split('\n')) {
|
|
182
|
+
const trimmed = line.trim().replace(/,$/, '');
|
|
183
|
+
if (!trimmed || trimmed.startsWith('--') || /^PRIMARY KEY\s*\(/i.test(trimmed)) continue;
|
|
184
|
+
const parts = trimmed.split(/\s+/);
|
|
185
|
+
if (parts.length < 2) continue;
|
|
186
|
+
const fieldName = parts[0].replace(/[`"]/g, '');
|
|
187
|
+
const type = parts[1];
|
|
188
|
+
const isPk = /PRIMARY KEY/i.test(trimmed);
|
|
189
|
+
if (isPk) primaryKey = fieldName;
|
|
190
|
+
fields.push({ name: fieldName, type, nullable: !/NOT NULL/i.test(trimmed), unique: /\bUNIQUE\b/i.test(trimmed) });
|
|
191
|
+
}
|
|
192
|
+
entities.push({ name, fields, primaryKey, relations: [] });
|
|
193
|
+
}
|
|
194
|
+
} catch (e) { warnings.push('parseRawSql: ' + e.message); }
|
|
195
|
+
}
|
|
196
|
+
return entities;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = { parsePrisma, parseTypeOrm, parseDrizzle, parseMongoose, parseSequelize, parseSqlAlchemy, parseRawSql };
|
package/bin/scan-schema.js
CHANGED
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const parsers = require('./scan-schema-parsers');
|
|
5
|
-
|
|
6
|
-
function findFiles(dir, suffix) {
|
|
7
|
-
try {
|
|
8
|
-
const results = [];
|
|
9
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
10
|
-
for (const e of entries) {
|
|
11
|
-
const full = path.join(dir, e.name);
|
|
12
|
-
if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules') {
|
|
13
|
-
results.push(...findFiles(full, suffix));
|
|
14
|
-
} else if (e.isFile() && e.name.endsWith(suffix)) {
|
|
15
|
-
results.push(full);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return results;
|
|
19
|
-
} catch { return []; }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function fileContains(filePath, substring) {
|
|
23
|
-
try { return fs.readFileSync(filePath, 'utf8').includes(substring); }
|
|
24
|
-
catch { return false; }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function detectOrm(projectRoot) {
|
|
28
|
-
try {
|
|
29
|
-
const prisma = path.join(projectRoot, 'prisma', 'schema.prisma');
|
|
30
|
-
if (fs.existsSync(prisma)) return { ormType: 'prisma', files: [prisma] };
|
|
31
|
-
|
|
32
|
-
const entityFiles = findFiles(projectRoot, '.entity.ts').filter(f => fileContains(f, '@Entity'));
|
|
33
|
-
if (entityFiles.length) return { ormType: 'typeorm', files: entityFiles };
|
|
34
|
-
|
|
35
|
-
const drizzleFiles = findFiles(projectRoot, '.ts').filter(f => f.endsWith('schema.ts') && fileContains(f, 'drizzle-orm'));
|
|
36
|
-
if (drizzleFiles.length) return { ormType: 'drizzle', files: drizzleFiles };
|
|
37
|
-
|
|
38
|
-
const mongooseFiles = findFiles(projectRoot, '.ts').filter(f => fileContains(f, 'mongoose.Schema'));
|
|
39
|
-
if (mongooseFiles.length) return { ormType: 'mongoose', files: mongooseFiles };
|
|
40
|
-
|
|
41
|
-
const seqFiles = findFiles(projectRoot, '.ts').filter(f => fileContains(f, 'DataTypes') && fileContains(f, 'Model.init'));
|
|
42
|
-
if (seqFiles.length) return { ormType: 'sequelize', files: seqFiles };
|
|
43
|
-
|
|
44
|
-
const pyFiles = findFiles(projectRoot, '.py').filter(f => fileContains(f, 'declarative_base'));
|
|
45
|
-
if (pyFiles.length) return { ormType: 'sqlalchemy', files: pyFiles };
|
|
46
|
-
|
|
47
|
-
const sqlFiles = findFiles(projectRoot, '.sql').filter(f => fileContains(f, 'CREATE TABLE'));
|
|
48
|
-
if (sqlFiles.length) return { ormType: 'raw-sql', files: sqlFiles };
|
|
49
|
-
|
|
50
|
-
// Vector and document stores — check package.json and requirements.txt
|
|
51
|
-
try {
|
|
52
|
-
const pkgPath = path.join(projectRoot, 'package.json');
|
|
53
|
-
if (fs.existsSync(pkgPath)) {
|
|
54
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
55
|
-
const deps = Object.keys({ ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }).join(' ');
|
|
56
|
-
const VECTOR = ['@pinecone-database/pinecone', 'weaviate-client', '@weaviate/client', '@qdrant/js-client-rest', 'chromadb', '@zilliz/milvus2-sdk-node', 'hnswlib-node'];
|
|
57
|
-
const DOCDB = ['mongodb', 'couchdb', 'pouchdb', 'arangojs', 'couchbase', 'cassandra-driver'];
|
|
58
|
-
if (VECTOR.some(p => deps.includes(p))) return { ormType: 'vector-db', files: [pkgPath] };
|
|
59
|
-
if (DOCDB.some(p => deps.includes(p))) return { ormType: 'document-db', files: [pkgPath] };
|
|
60
|
-
}
|
|
61
|
-
const reqPath = path.join(projectRoot, 'requirements.txt');
|
|
62
|
-
if (fs.existsSync(reqPath)) {
|
|
63
|
-
const req = fs.readFileSync(reqPath, 'utf8');
|
|
64
|
-
if (/pinecone|weaviate|qdrant|chromadb|milvus|faiss|annoy/i.test(req)) return { ormType: 'vector-db', files: [reqPath] };
|
|
65
|
-
if (/pymongo|motor|couchdb|cassandra|arangodb/i.test(req)) return { ormType: 'document-db', files: [reqPath] };
|
|
66
|
-
}
|
|
67
|
-
} catch {}
|
|
68
|
-
|
|
69
|
-
return { ormType: null, files: [] };
|
|
70
|
-
} catch { return { ormType: null, files: [] }; }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function extractSchema(projectRoot) {
|
|
74
|
-
try {
|
|
75
|
-
const { ormType, files } = detectOrm(projectRoot);
|
|
76
|
-
if (!ormType) return { detected: false, ormType: null, entities: [], parseWarnings: [] };
|
|
77
|
-
|
|
78
|
-
const warnings = [];
|
|
79
|
-
let entities = [];
|
|
80
|
-
|
|
81
|
-
if (ormType === 'prisma') entities = parsers.parsePrisma(files[0], warnings);
|
|
82
|
-
else if (ormType === 'typeorm') entities = parsers.parseTypeOrm(files, warnings);
|
|
83
|
-
else if (ormType === 'drizzle') entities = parsers.parseDrizzle(files, warnings);
|
|
84
|
-
else if (ormType === 'mongoose') entities = parsers.parseMongoose(files, warnings);
|
|
85
|
-
else if (ormType === 'sequelize') entities = parsers.parseSequelize(files, warnings);
|
|
86
|
-
else if (ormType === 'sqlalchemy') entities = parsers.parseSqlAlchemy(files, warnings);
|
|
87
|
-
else if (ormType === 'raw-sql') entities = parsers.parseRawSql(files, warnings);
|
|
88
|
-
else if (ormType === 'vector-db') entities = [
|
|
89
|
-
{ name: 'VectorIndex', fields: [{ name: 'id', type: 'string' }, { name: 'embedding', type: 'float[]' }, { name: 'metadata', type: 'object' }, { name: 'score', type: 'float' }], relations: [] },
|
|
90
|
-
{ name: 'Namespace', fields: [{ name: 'name', type: 'string' }, { name: 'vectorCount', type: 'int' }], relations: [{ fromEntity: 'Namespace', toEntity: 'VectorIndex', type: 'one-to-many' }] }
|
|
91
|
-
];
|
|
92
|
-
else if (ormType === 'document-db') entities = [
|
|
93
|
-
{ name: 'Collection', fields: [{ name: '_id', type: 'ObjectId' }, { name: 'data', type: 'object' }, { name: 'createdAt', type: 'date' }, { name: 'updatedAt', type: 'date' }], relations: [] }
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
entities = entities.filter(e => e.name && e.name.trim());
|
|
97
|
-
return { detected: true, ormType, entities, parseWarnings: warnings };
|
|
98
|
-
} catch (err) {
|
|
99
|
-
return { detected: false, ormType: null, entities: [], parseWarnings: ['Fatal: ' + err.message] };
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
module.exports = { extractSchema };
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const parsers = require('./scan-schema-parsers');
|
|
5
|
+
|
|
6
|
+
function findFiles(dir, suffix) {
|
|
7
|
+
try {
|
|
8
|
+
const results = [];
|
|
9
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
10
|
+
for (const e of entries) {
|
|
11
|
+
const full = path.join(dir, e.name);
|
|
12
|
+
if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules') {
|
|
13
|
+
results.push(...findFiles(full, suffix));
|
|
14
|
+
} else if (e.isFile() && e.name.endsWith(suffix)) {
|
|
15
|
+
results.push(full);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
19
|
+
} catch { return []; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fileContains(filePath, substring) {
|
|
23
|
+
try { return fs.readFileSync(filePath, 'utf8').includes(substring); }
|
|
24
|
+
catch { return false; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function detectOrm(projectRoot) {
|
|
28
|
+
try {
|
|
29
|
+
const prisma = path.join(projectRoot, 'prisma', 'schema.prisma');
|
|
30
|
+
if (fs.existsSync(prisma)) return { ormType: 'prisma', files: [prisma] };
|
|
31
|
+
|
|
32
|
+
const entityFiles = findFiles(projectRoot, '.entity.ts').filter(f => fileContains(f, '@Entity'));
|
|
33
|
+
if (entityFiles.length) return { ormType: 'typeorm', files: entityFiles };
|
|
34
|
+
|
|
35
|
+
const drizzleFiles = findFiles(projectRoot, '.ts').filter(f => f.endsWith('schema.ts') && fileContains(f, 'drizzle-orm'));
|
|
36
|
+
if (drizzleFiles.length) return { ormType: 'drizzle', files: drizzleFiles };
|
|
37
|
+
|
|
38
|
+
const mongooseFiles = findFiles(projectRoot, '.ts').filter(f => fileContains(f, 'mongoose.Schema'));
|
|
39
|
+
if (mongooseFiles.length) return { ormType: 'mongoose', files: mongooseFiles };
|
|
40
|
+
|
|
41
|
+
const seqFiles = findFiles(projectRoot, '.ts').filter(f => fileContains(f, 'DataTypes') && fileContains(f, 'Model.init'));
|
|
42
|
+
if (seqFiles.length) return { ormType: 'sequelize', files: seqFiles };
|
|
43
|
+
|
|
44
|
+
const pyFiles = findFiles(projectRoot, '.py').filter(f => fileContains(f, 'declarative_base'));
|
|
45
|
+
if (pyFiles.length) return { ormType: 'sqlalchemy', files: pyFiles };
|
|
46
|
+
|
|
47
|
+
const sqlFiles = findFiles(projectRoot, '.sql').filter(f => fileContains(f, 'CREATE TABLE'));
|
|
48
|
+
if (sqlFiles.length) return { ormType: 'raw-sql', files: sqlFiles };
|
|
49
|
+
|
|
50
|
+
// Vector and document stores — check package.json and requirements.txt
|
|
51
|
+
try {
|
|
52
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
53
|
+
if (fs.existsSync(pkgPath)) {
|
|
54
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
55
|
+
const deps = Object.keys({ ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }).join(' ');
|
|
56
|
+
const VECTOR = ['@pinecone-database/pinecone', 'weaviate-client', '@weaviate/client', '@qdrant/js-client-rest', 'chromadb', '@zilliz/milvus2-sdk-node', 'hnswlib-node'];
|
|
57
|
+
const DOCDB = ['mongodb', 'couchdb', 'pouchdb', 'arangojs', 'couchbase', 'cassandra-driver'];
|
|
58
|
+
if (VECTOR.some(p => deps.includes(p))) return { ormType: 'vector-db', files: [pkgPath] };
|
|
59
|
+
if (DOCDB.some(p => deps.includes(p))) return { ormType: 'document-db', files: [pkgPath] };
|
|
60
|
+
}
|
|
61
|
+
const reqPath = path.join(projectRoot, 'requirements.txt');
|
|
62
|
+
if (fs.existsSync(reqPath)) {
|
|
63
|
+
const req = fs.readFileSync(reqPath, 'utf8');
|
|
64
|
+
if (/pinecone|weaviate|qdrant|chromadb|milvus|faiss|annoy/i.test(req)) return { ormType: 'vector-db', files: [reqPath] };
|
|
65
|
+
if (/pymongo|motor|couchdb|cassandra|arangodb/i.test(req)) return { ormType: 'document-db', files: [reqPath] };
|
|
66
|
+
}
|
|
67
|
+
} catch {}
|
|
68
|
+
|
|
69
|
+
return { ormType: null, files: [] };
|
|
70
|
+
} catch { return { ormType: null, files: [] }; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function extractSchema(projectRoot) {
|
|
74
|
+
try {
|
|
75
|
+
const { ormType, files } = detectOrm(projectRoot);
|
|
76
|
+
if (!ormType) return { detected: false, ormType: null, entities: [], parseWarnings: [] };
|
|
77
|
+
|
|
78
|
+
const warnings = [];
|
|
79
|
+
let entities = [];
|
|
80
|
+
|
|
81
|
+
if (ormType === 'prisma') entities = parsers.parsePrisma(files[0], warnings);
|
|
82
|
+
else if (ormType === 'typeorm') entities = parsers.parseTypeOrm(files, warnings);
|
|
83
|
+
else if (ormType === 'drizzle') entities = parsers.parseDrizzle(files, warnings);
|
|
84
|
+
else if (ormType === 'mongoose') entities = parsers.parseMongoose(files, warnings);
|
|
85
|
+
else if (ormType === 'sequelize') entities = parsers.parseSequelize(files, warnings);
|
|
86
|
+
else if (ormType === 'sqlalchemy') entities = parsers.parseSqlAlchemy(files, warnings);
|
|
87
|
+
else if (ormType === 'raw-sql') entities = parsers.parseRawSql(files, warnings);
|
|
88
|
+
else if (ormType === 'vector-db') entities = [
|
|
89
|
+
{ name: 'VectorIndex', fields: [{ name: 'id', type: 'string' }, { name: 'embedding', type: 'float[]' }, { name: 'metadata', type: 'object' }, { name: 'score', type: 'float' }], relations: [] },
|
|
90
|
+
{ name: 'Namespace', fields: [{ name: 'name', type: 'string' }, { name: 'vectorCount', type: 'int' }], relations: [{ fromEntity: 'Namespace', toEntity: 'VectorIndex', type: 'one-to-many' }] }
|
|
91
|
+
];
|
|
92
|
+
else if (ormType === 'document-db') entities = [
|
|
93
|
+
{ name: 'Collection', fields: [{ name: '_id', type: 'ObjectId' }, { name: 'data', type: 'object' }, { name: 'createdAt', type: 'date' }, { name: 'updatedAt', type: 'date' }], relations: [] }
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
entities = entities.filter(e => e.name && e.name.trim());
|
|
97
|
+
return { detected: true, ormType, entities, parseWarnings: warnings };
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return { detected: false, ormType: null, entities: [], parseWarnings: ['Fatal: ' + err.message] };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { extractSchema };
|