g360-cli 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -12
- package/package.json +1 -1
- package/src/assets/brand/brand.json +1 -3
- package/src/assets/templates/ARCHITECTURE.md +50 -0
- package/src/assets/templates/lit-web/README.md +47 -0
- package/src/assets/templates/python-cli/README.md +47 -0
- package/src/assets/templates/python-customtkinter/README.md +45 -34
- package/src/assets/templates/python-flet/README.md +46 -66
- package/src/assets/templates/python-flet-migrate/README.md +35 -55
- package/src/assets/templates/solid-web/README.md +47 -0
- package/src/assets/templates/svelte-web/README.md +47 -0
- package/src/assets/templates/web-pwa/README.md +47 -0
- package/src/cli.js +16 -0
- package/src/commands/clean.js +2 -23
- package/src/commands/convert.js +1 -23
- package/src/commands/docs.js +815 -0
- package/src/commands/init.js +46 -4
- package/src/commands/lint.js +578 -0
- package/src/lib/file-utils.js +27 -0
- package/src/assets/brand/g360/logotypes/logo_g360_dark_b64.txt +0 -1
- package/src/assets/brand/g360/logotypes/logo_g360_light_b64.txt +0 -1
- package/src/lib/global-config.js +0 -260
- package/src/lib/i18n.js +0 -238
- package/src/lib/python_runner.js +0 -89
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const BRAND_PATH = path.join(__dirname, '..', '..', 'assets', 'brand', 'brand.json');
|
|
8
|
+
|
|
9
|
+
const LEVELS = ['readme', 'architecture', 'business-rules', 'api', 'dependencies', 'classes', 'code-graph', 'all'];
|
|
10
|
+
|
|
11
|
+
export async function docs(level, options) {
|
|
12
|
+
const { project = '.', dryRun = false } = options;
|
|
13
|
+
const targetDir = path.join(process.cwd(), project);
|
|
14
|
+
|
|
15
|
+
if (!LEVELS.includes(level)) {
|
|
16
|
+
console.error(chalk.red(`❌ Nivel invalido: "${level}"`));
|
|
17
|
+
console.log(chalk.gray('Niveles disponibles:'));
|
|
18
|
+
console.log(chalk.gray(' readme - README.md con diagrama y logo'));
|
|
19
|
+
console.log(chalk.gray(' architecture - ARCHITECTURE.md con diagramas'));
|
|
20
|
+
console.log(chalk.gray(' business-rules - BUSINESS_RULES.md (solo Python con commercial_engine)'));
|
|
21
|
+
console.log(chalk.gray(' api - API.md (solo proyectos con endpoints)'));
|
|
22
|
+
console.log(chalk.gray(' dependencies - docs/generated/dependencies.mmd'));
|
|
23
|
+
console.log(chalk.gray(' classes - docs/generated/classes.mmd'));
|
|
24
|
+
console.log(chalk.gray(' code-graph - docs/generated/code_graph.mmd (proyectos grandes)'));
|
|
25
|
+
console.log(chalk.gray(' all - Todos los niveles aplicables'));
|
|
26
|
+
console.log(chalk.gray('\nEjemplo:'));
|
|
27
|
+
console.log(chalk.gray(' g360 docs'));
|
|
28
|
+
console.log(chalk.gray(' g360 docs --level all'));
|
|
29
|
+
console.log(chalk.gray(' g360 docs --level readme --project ./mi-proyecto'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(targetDir)) {
|
|
34
|
+
console.error(chalk.red(`❌ Directorio no encontrado: ${targetDir}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const projectInfo = detectProject(targetDir);
|
|
39
|
+
const brand = loadBrand();
|
|
40
|
+
const manifest = loadManifest(targetDir);
|
|
41
|
+
const skill = loadSkill(targetDir);
|
|
42
|
+
|
|
43
|
+
if (dryRun) {
|
|
44
|
+
console.log(chalk.yellow('\n📋 DRY RUN — Archivos que se generarian:\n'));
|
|
45
|
+
} else {
|
|
46
|
+
console.log(chalk.bold.cyan('\n📝 G360 Documentation Generator\n'));
|
|
47
|
+
console.log(chalk.gray(`Path: ${targetDir}\n`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const levelsToRun = level === 'all'
|
|
51
|
+
? getApplicableLevels(projectInfo)
|
|
52
|
+
: [level];
|
|
53
|
+
|
|
54
|
+
for (const lvl of levelsToRun) {
|
|
55
|
+
const result = await generateLevel(lvl, targetDir, projectInfo, brand, manifest, skill, dryRun);
|
|
56
|
+
if (result) {
|
|
57
|
+
console.log(chalk.green(` ✅ ${result}`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!dryRun) {
|
|
62
|
+
console.log(chalk.gray('\n💡 Usa --dry-run para previsualizar sin escribir archivos.\n'));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function detectProject(dir) {
|
|
67
|
+
const pyproject = path.join(dir, 'pyproject.toml');
|
|
68
|
+
const mainPy = path.join(dir, 'src', 'main.py');
|
|
69
|
+
const indexHtml = path.join(dir, 'index.html');
|
|
70
|
+
const pkgJson = path.join(dir, 'package.json');
|
|
71
|
+
const skillJson = path.join(dir, 'skill.json');
|
|
72
|
+
|
|
73
|
+
let type = 'unknown';
|
|
74
|
+
let framework = null;
|
|
75
|
+
|
|
76
|
+
if (fs.existsSync(pyproject)) {
|
|
77
|
+
const content = fs.readFileSync(pyproject, 'utf8');
|
|
78
|
+
if (content.includes('flet')) {
|
|
79
|
+
type = 'python-flet';
|
|
80
|
+
framework = 'flet';
|
|
81
|
+
} else if (content.includes('customtkinter')) {
|
|
82
|
+
type = 'python-customtkinter';
|
|
83
|
+
framework = 'customtkinter';
|
|
84
|
+
} else if (fs.existsSync(mainPy)) {
|
|
85
|
+
const mainContent = fs.readFileSync(mainPy, 'utf8');
|
|
86
|
+
if (mainContent.includes('flet')) {
|
|
87
|
+
type = 'python-flet';
|
|
88
|
+
framework = 'flet';
|
|
89
|
+
} else {
|
|
90
|
+
type = 'python-cli';
|
|
91
|
+
framework = 'cli';
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
type = 'python-cli';
|
|
95
|
+
framework = 'cli';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (type === 'unknown') {
|
|
100
|
+
if (fs.existsSync(indexHtml)) {
|
|
101
|
+
type = 'web';
|
|
102
|
+
if (fs.existsSync(pkgJson)) {
|
|
103
|
+
try {
|
|
104
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf8'));
|
|
105
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
106
|
+
if (deps.some(d => d.includes('react'))) framework = 'react';
|
|
107
|
+
else if (deps.some(d => d.includes('lit'))) framework = 'lit';
|
|
108
|
+
else if (deps.some(d => d.includes('solid'))) framework = 'solid';
|
|
109
|
+
else if (deps.some(d => d.includes('svelte'))) framework = 'svelte';
|
|
110
|
+
} catch { /* keep framework as detected */ }
|
|
111
|
+
}
|
|
112
|
+
} else if (fs.existsSync(pkgJson)) {
|
|
113
|
+
type = 'web';
|
|
114
|
+
try {
|
|
115
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf8'));
|
|
116
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
117
|
+
if (deps.some(d => d.includes('react'))) framework = 'react';
|
|
118
|
+
else if (deps.some(d => d.includes('lit'))) framework = 'lit';
|
|
119
|
+
else if (deps.some(d => d.includes('solid'))) framework = 'solid';
|
|
120
|
+
else if (deps.some(d => d.includes('svelte'))) framework = 'svelte';
|
|
121
|
+
} catch { /* keep framework as detected */ }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { type, framework, dir };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function loadBrand() {
|
|
129
|
+
if (fs.existsSync(BRAND_PATH)) {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(fs.readFileSync(BRAND_PATH, 'utf8'));
|
|
132
|
+
} catch { /* fallback */ }
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function loadManifest(dir) {
|
|
138
|
+
const manifestPath = path.join(dir, 'g360-manifest.json');
|
|
139
|
+
if (fs.existsSync(manifestPath)) {
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
142
|
+
} catch { /* fallback */ }
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function loadSkill(dir) {
|
|
148
|
+
const candidates = [
|
|
149
|
+
path.join(dir, 'skill.json'),
|
|
150
|
+
path.join(dir, 'src', 'core', 'skill.json'),
|
|
151
|
+
];
|
|
152
|
+
for (const p of candidates) {
|
|
153
|
+
if (fs.existsSync(p)) {
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
156
|
+
} catch { /* fallback */ }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getApplicableLevels(projectInfo) {
|
|
163
|
+
const levels = ['readme', 'architecture'];
|
|
164
|
+
if (projectInfo.type === 'python-flet' || projectInfo.type === 'python-cli') {
|
|
165
|
+
levels.push('business-rules');
|
|
166
|
+
}
|
|
167
|
+
levels.push('dependencies', 'classes');
|
|
168
|
+
return levels;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function generateLevel(lvl, dir, projectInfo, brand, manifest, skill, dryRun) {
|
|
172
|
+
switch (lvl) {
|
|
173
|
+
case 'readme': return await generateReadme(dir, projectInfo, brand, manifest, skill, dryRun);
|
|
174
|
+
case 'architecture': return await generateArchitecture(dir, projectInfo, brand, manifest, skill, dryRun);
|
|
175
|
+
case 'business-rules': return await generateBusinessRules(dir, projectInfo, dryRun);
|
|
176
|
+
case 'dependencies': return await generateDependencies(dir, projectInfo, dryRun);
|
|
177
|
+
case 'classes': return await generateClasses(dir, projectInfo, dryRun);
|
|
178
|
+
case 'code-graph': return await generateCodeGraph(dir, projectInfo, dryRun);
|
|
179
|
+
default: return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function generateReadme(dir, projectInfo, brand, manifest, skill, dryRun) {
|
|
184
|
+
const name = manifest?.name || skill?.name || path.basename(dir);
|
|
185
|
+
const description = manifest?.description || skill?.description || `Proyecto ${name}`;
|
|
186
|
+
const version = manifest?.version || '1.0.0';
|
|
187
|
+
const template = manifest?.template || projectInfo.type;
|
|
188
|
+
const signature = skill?.signature || { mode: 'powered', text: 'powered by G360' };
|
|
189
|
+
const brandName = brand?.brands?.[skill?.brand || 'g360'];
|
|
190
|
+
const logoPath = brandName?.default_logo || 'logotypes/logo-g360-dark.svg';
|
|
191
|
+
const logoDir = path.dirname(logoPath);
|
|
192
|
+
const logoFile = path.basename(logoPath);
|
|
193
|
+
|
|
194
|
+
const diagram = buildReadmeDiagram(dir, projectInfo);
|
|
195
|
+
|
|
196
|
+
const content = `# ${name}
|
|
197
|
+
|
|
198
|
+
<picture>
|
|
199
|
+
<source media="(prefers-color-scheme: dark)" srcset="${logoDir}/${logoFile.replace('-dark', '-light')}">
|
|
200
|
+
<img alt="${name}" height="64" src="${logoDir}/${logoFile}">
|
|
201
|
+
</picture>
|
|
202
|
+
|
|
203
|
+
> ${description}
|
|
204
|
+
|
|
205
|
+
[](https://github.com)
|
|
206
|
+
|
|
207
|
+
## ¿Cómo está organizado el proyecto?
|
|
208
|
+
|
|
209
|
+
\`\`\`mermaid
|
|
210
|
+
${diagram}
|
|
211
|
+
\`\`\`
|
|
212
|
+
|
|
213
|
+
## Quick Start
|
|
214
|
+
|
|
215
|
+
\`\`\`bash
|
|
216
|
+
# 1. Entrar al proyecto
|
|
217
|
+
cd ${name}
|
|
218
|
+
|
|
219
|
+
# 2. Ver estructura
|
|
220
|
+
g360 present
|
|
221
|
+
|
|
222
|
+
# 3. Auditar compliance
|
|
223
|
+
g360 audit
|
|
224
|
+
|
|
225
|
+
# 4. Traer assets de marca
|
|
226
|
+
g360 bring brand
|
|
227
|
+
\`\`\`
|
|
228
|
+
|
|
229
|
+
## Identidad de Marca
|
|
230
|
+
|
|
231
|
+
| Elemento | Valor |
|
|
232
|
+
|---|---|
|
|
233
|
+
| Marca | ${brandName?.name || 'G360'} |
|
|
234
|
+
| Color primario | ${brandName?.primary_color || '#00d084'} |
|
|
235
|
+
| Signature mode | ${signature.mode} |
|
|
236
|
+
| Signature text | "${signature.text}" |
|
|
237
|
+
| Logo | ${logoPath} |
|
|
238
|
+
|
|
239
|
+
## Footer
|
|
240
|
+
|
|
241
|
+
\`\`\`html
|
|
242
|
+
<g360-signature mode="${signature.mode}"></g360-signature>
|
|
243
|
+
\`\`\`
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
**Marca**: ${brandName?.name || 'G360'} · **Isotipo**: 3 puntos + chevron \`>\`
|
|
248
|
+
**Signature**: ${signature.text} · **Powered by**: [g360-signature](https://github.com/carloscus/g360-signature)
|
|
249
|
+
|
|
250
|
+
*Generado por \`g360 docs\` · Fuente: \`brand.json\` + \`skill.json\` + \`g360-manifest.json\`*
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
const outputPath = path.join(dir, 'README.md');
|
|
254
|
+
if (dryRun) {
|
|
255
|
+
console.log(chalk.gray(` [dry-run] README.md`));
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
await fs.writeFile(outputPath, content, 'utf8');
|
|
259
|
+
return 'README.md';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function generateArchitecture(dir, projectInfo, brand, manifest, skill, dryRun) {
|
|
263
|
+
const name = manifest?.name || skill?.name || path.basename(dir);
|
|
264
|
+
const diagram = buildArchitectureDiagram(dir, projectInfo);
|
|
265
|
+
|
|
266
|
+
const content = `# ARCHITECTURE.md — ${name}
|
|
267
|
+
|
|
268
|
+
> Arquitectura general del proyecto. Generado automáticamente por \`g360 docs --level architecture\`.
|
|
269
|
+
|
|
270
|
+
## Arquitectura General
|
|
271
|
+
|
|
272
|
+
\`\`\`mermaid
|
|
273
|
+
${diagram}
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
## Componentes
|
|
277
|
+
|
|
278
|
+
| Componente | Responsabilidad |
|
|
279
|
+
|---|---|
|
|
280
|
+
| Entry point | Punto de inicio de la aplicación |
|
|
281
|
+
| Core | Lógica de negocio y configuración |
|
|
282
|
+
| UI | Presentación y interacción con el usuario |
|
|
283
|
+
| Assets | Recursos estáticos (imágenes, iconos, marca) |
|
|
284
|
+
| Config | Archivos de configuración (skill.json, manifest) |
|
|
285
|
+
|
|
286
|
+
## Flujo de Datos
|
|
287
|
+
|
|
288
|
+
\`\`\`mermaid
|
|
289
|
+
flowchart LR
|
|
290
|
+
INPUT["Entrada del usuario"] --> PROCESS["Procesamiento"]
|
|
291
|
+
PROCESS --> VALIDATE["Validación"]
|
|
292
|
+
VALIDATE --> OUTPUT["Resultado"]
|
|
293
|
+
OUTPUT --> PERSIST["Persistencia"]
|
|
294
|
+
\`\`\`
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
*Generado por \`g360 docs --level architecture\`*
|
|
299
|
+
`;
|
|
300
|
+
|
|
301
|
+
const outputPath = path.join(dir, 'ARCHITECTURE.md');
|
|
302
|
+
if (dryRun) {
|
|
303
|
+
console.log(chalk.gray(` [dry-run] ARCHITECTURE.md`));
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
await fs.writeFile(outputPath, content, 'utf8');
|
|
307
|
+
return 'ARCHITECTURE.md';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function generateBusinessRules(dir, projectInfo, dryRun) {
|
|
311
|
+
const commercialEngine = path.join(dir, 'py', 'src', 'g360_core', 'commercial_engine.py');
|
|
312
|
+
if (!fs.existsSync(commercialEngine)) {
|
|
313
|
+
console.log(chalk.yellow(' ⚠ No se encontró commercial_engine.py. Se omite BUSINESS_RULES.md.\n'));
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const content = `# BUSINESS_RULES.md — Motor de clasificación comercial
|
|
318
|
+
|
|
319
|
+
> Única fuente de verdad para reglas de negocio. Todas las reglas viven en
|
|
320
|
+
> \`commercial_engine.py\`, no en \`processor.py\` ni \`pipeline.py\`.
|
|
321
|
+
|
|
322
|
+
**Módulo**: \`g360_core.commercial_engine\`
|
|
323
|
+
|
|
324
|
+
## Flujo de reglas
|
|
325
|
+
|
|
326
|
+
\`\`\`mermaid
|
|
327
|
+
flowchart TD
|
|
328
|
+
IN["Entrada<br/>DataFrame ERP (.xls/.xlsx/.csv)"]
|
|
329
|
+
P1["parse_referencia<br/>REF_TIPO / REF_SERIE / REF_NUMERO"]
|
|
330
|
+
P2["classify_base<br/>CATEGORIA_OP"]
|
|
331
|
+
P3["resolve_document_relationships<br/>SUBTIPO_AJUSTE"]
|
|
332
|
+
P4["calculate_prices<br/>PRECIO_BASE / RECARGO / EFECTIVO"]
|
|
333
|
+
OUT["Salida<br/>DataFrame enriquecido"]
|
|
334
|
+
CSV["Persistencia<br/>maestro_ventas_crm.csv"]
|
|
335
|
+
|
|
336
|
+
IN --> P1 --> P2 --> P3 --> P4 --> OUT --> CSV
|
|
337
|
+
\`\`\`
|
|
338
|
+
|
|
339
|
+
## Paso 1 — Parseo de REFERENCIA
|
|
340
|
+
|
|
341
|
+
Extrae \`REF_TIPO\`, \`REF_SERIE\` y \`REF_NUMERO\` del campo \`REFERENCIA\`.
|
|
342
|
+
Formato esperado: \`F01/204-56287\`. Regex: \`^([A-Z0-9]+)/(\\d+)-(\\d+)$\`.
|
|
343
|
+
Sin match → \`"S/R"\` en los 3 campos.
|
|
344
|
+
|
|
345
|
+
## Paso 2 — Clasificación primaria
|
|
346
|
+
|
|
347
|
+
Solo mira la fila actual. No cruza con otros documentos.
|
|
348
|
+
|
|
349
|
+
| TPO_DOC | CANTIDAD | → CATEGORIA_OP |
|
|
350
|
+
|---|---|---|
|
|
351
|
+
| F01, BDI, F03, B01, B03, F07, F08, B07, B08 | cualquiera | **VENTA** |
|
|
352
|
+
| NC\* (prefijos en \`NC_PREFIXES\`) | ≠ 0 | **DEVOLUCION** |
|
|
353
|
+
| NC\* | = 0 | **AJUSTE** |
|
|
354
|
+
| ND\* (prefijos en \`ND_PREFIXES\`) | cualquiera | **AJUSTE** |
|
|
355
|
+
|
|
356
|
+
\`SUBTIPO_AJUSTE\` se inicializa vacío aquí; se asigna en el paso 3.
|
|
357
|
+
|
|
358
|
+
## Paso 3 — Resolución de relaciones
|
|
359
|
+
|
|
360
|
+
Cruza \`REFERENCIA\` contra el índice de facturas de \`build_invoice_index\`.
|
|
361
|
+
Clave: \`(TPO_DOC, SERIE_DOC, NRO_DOC, ID_ARTICULO)\`. Solo se indexan registros VENTA.
|
|
362
|
+
|
|
363
|
+
| Condición | CANTIDAD_FAE | → SUBTIPO_AJUSTE |
|
|
364
|
+
|---|---|---|
|
|
365
|
+
| Clave con SKU coincide | = 0 | **CARGO_FIJO** |
|
|
366
|
+
| Clave con SKU coincide | ≈ CANTIDAD factura | **PRECIO_LINEA** |
|
|
367
|
+
| Clave con SKU coincide | < CANTIDAD factura | **PRECIO_PARCIAL** |
|
|
368
|
+
| Clave con SKU coincide | > CANTIDAD factura | **SIN_BASE** |
|
|
369
|
+
| Clave no coincide | = 1 | **CARGO_FIJO** |
|
|
370
|
+
| Clave no coincide | ≠ 1 | **SIN_BASE** |
|
|
371
|
+
|
|
372
|
+
## Paso 4 — Cálculo de precios
|
|
373
|
+
|
|
374
|
+
| Columna | Fórmula | Cuándo |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| \`PRECIO_BASE\` | \`\|SOLES\| / \|CANTIDAD\|\` | CANTIDAD ≠ 0 |
|
|
377
|
+
| \`RECARGO_UNITARIO\` | \`SOLES / \|CANTIDAD_FAE\|\` | AJUSTE con SUBTIPO_LINEA/PARCIAL y FAE ≠ 0 |
|
|
378
|
+
| \`PRECIO_EFECTIVO\` | \`PRECIO_BASE + RECARGO\` | PRECIO_BASE no es NaN |
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
*Generado por \`g360 docs --level business-rules\`*
|
|
383
|
+
`;
|
|
384
|
+
|
|
385
|
+
const outputPath = path.join(dir, 'BUSINESS_RULES.md');
|
|
386
|
+
if (dryRun) {
|
|
387
|
+
console.log(chalk.gray(` [dry-run] BUSINESS_RULES.md`));
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
await fs.writeFile(outputPath, content, 'utf8');
|
|
391
|
+
return 'BUSINESS_RULES.md';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function generateDependencies(dir, projectInfo, dryRun) {
|
|
395
|
+
const scan = scanProject(dir);
|
|
396
|
+
const deps = [];
|
|
397
|
+
|
|
398
|
+
for (const file of scan.pyFiles) {
|
|
399
|
+
if (file.endsWith('__init__.py')) continue;
|
|
400
|
+
const filePath = path.join(dir, file);
|
|
401
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
402
|
+
const imports = extractImports(content);
|
|
403
|
+
for (const imp of imports) {
|
|
404
|
+
if (imp.startsWith('.')) {
|
|
405
|
+
const target = imp.replace(/^\.\.?\//, '').replace(/\.py$/, '');
|
|
406
|
+
deps.push({ from: file.replace(/\.py$/, ''), to: target });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (const file of scan.jsFiles) {
|
|
412
|
+
const filePath = path.join(dir, file);
|
|
413
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
414
|
+
const imports = extractJsImports(content);
|
|
415
|
+
for (const imp of imports) {
|
|
416
|
+
deps.push({ from: file.replace(/\.(js|jsx|ts|tsx)$/, ''), to: imp });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let diagram;
|
|
421
|
+
if (deps.length > 0) {
|
|
422
|
+
const nodes = new Set();
|
|
423
|
+
deps.forEach(d => { nodes.add(d.from); nodes.add(d.to); });
|
|
424
|
+
const nodeList = [...nodes].map(n => ` ${n}`).join('\n');
|
|
425
|
+
const edges = deps.map(d => ` ${d.from} --> ${d.to}`).join('\n');
|
|
426
|
+
diagram = `flowchart TD\n${nodeList}\n\n${edges}`;
|
|
427
|
+
} else {
|
|
428
|
+
diagram = `flowchart TD\n main --> core\n core --> utils\n main --> config`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const outputDir = path.join(dir, 'docs', 'generated');
|
|
432
|
+
const outputPath = path.join(outputDir, 'dependencies.mmd');
|
|
433
|
+
|
|
434
|
+
if (dryRun) {
|
|
435
|
+
console.log(chalk.gray(` [dry-run] docs/generated/dependencies.mmd`));
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
await fs.ensureDir(outputDir);
|
|
439
|
+
await fs.writeFile(outputPath, diagram, 'utf8');
|
|
440
|
+
return 'docs/generated/dependencies.mmd';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function generateClasses(dir, projectInfo, dryRun) {
|
|
444
|
+
const scan = scanProject(dir);
|
|
445
|
+
const classes = [];
|
|
446
|
+
|
|
447
|
+
for (const file of scan.pyFiles) {
|
|
448
|
+
if (file.endsWith('__init__.py')) continue;
|
|
449
|
+
const filePath = path.join(dir, file);
|
|
450
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
451
|
+
const classMatches = content.match(/^class\s+(\w+)/gm);
|
|
452
|
+
if (classMatches) {
|
|
453
|
+
for (const m of classMatches) {
|
|
454
|
+
const name = m.replace(/^class\s+/, '').replace(/\(.*/, '');
|
|
455
|
+
classes.push({ file: file.replace(/\.py$/, ''), name });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
for (const file of scan.jsFiles) {
|
|
461
|
+
const filePath = path.join(dir, file);
|
|
462
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
463
|
+
const classMatches = content.match(/^export\s+class\s+(\w+)/gm);
|
|
464
|
+
if (classMatches) {
|
|
465
|
+
for (const m of classMatches) {
|
|
466
|
+
const name = m.replace(/^export\s+class\s+/, '');
|
|
467
|
+
classes.push({ file: file.replace(/\.(js|jsx|ts|tsx)$/, ''), name });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
let diagram;
|
|
473
|
+
if (classes.length > 0) {
|
|
474
|
+
const nodes = classes.map(c => ` ${c.name}`).join('\n');
|
|
475
|
+
const edges = classes.map(c => ` ${c.file} --> ${c.name}`).join('\n');
|
|
476
|
+
diagram = `flowchart TD\n${nodes}\n\n${edges}`;
|
|
477
|
+
} else {
|
|
478
|
+
diagram = `flowchart TD\n main --> core\n core --> utils`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const outputDir = path.join(dir, 'docs', 'generated');
|
|
482
|
+
const outputPath = path.join(outputDir, 'classes.mmd');
|
|
483
|
+
|
|
484
|
+
if (dryRun) {
|
|
485
|
+
console.log(chalk.gray(` [dry-run] docs/generated/classes.mmd`));
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
await fs.ensureDir(outputDir);
|
|
489
|
+
await fs.writeFile(outputPath, diagram, 'utf8');
|
|
490
|
+
return 'docs/generated/classes.mmd';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function generateCodeGraph(dir, projectInfo, dryRun) {
|
|
494
|
+
const scan = scanProject(dir);
|
|
495
|
+
const totalFiles = scan.files.length;
|
|
496
|
+
if (totalFiles < 5) {
|
|
497
|
+
console.log(chalk.gray(' ⚠ Proyecto demasiado pequeño para code_graph. Se omite.\n'));
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const pyFiles = scan.pyFiles.filter(f => !f.endsWith('__init__.py'));
|
|
502
|
+
const jsFiles = scan.jsFiles;
|
|
503
|
+
|
|
504
|
+
let diagram;
|
|
505
|
+
if (pyFiles.length > 0 || jsFiles.length > 0) {
|
|
506
|
+
diagram = 'flowchart TD\n';
|
|
507
|
+
diagram += ' subgraph Entrada["Entrada"]\n';
|
|
508
|
+
if (scan.hasMainPy) diagram += ' Main["main.py"]\n';
|
|
509
|
+
if (scan.hasIndexHtml) diagram += ' Index["index.html"]\n';
|
|
510
|
+
diagram += ' end\n';
|
|
511
|
+
diagram += ' subgraph Core["Core"]\n';
|
|
512
|
+
for (const f of pyFiles.slice(0, 10)) {
|
|
513
|
+
diagram += ` ${f.replace(/\.py$/, '')}\n`;
|
|
514
|
+
}
|
|
515
|
+
for (const f of jsFiles.slice(0, 10)) {
|
|
516
|
+
diagram += ` ${f.replace(/\.(js|jsx|ts|tsx)$/, '')}\n`;
|
|
517
|
+
}
|
|
518
|
+
diagram += ' end\n';
|
|
519
|
+
diagram += ' subgraph Salida["Salida"]\n';
|
|
520
|
+
diagram += ' Output["Resultado"]\n';
|
|
521
|
+
diagram += ' end\n';
|
|
522
|
+
diagram += ' Entrada --> Core\n';
|
|
523
|
+
diagram += ' Core --> Salida';
|
|
524
|
+
} else {
|
|
525
|
+
diagram = `flowchart TD\n App["Aplicacion"]\n Config["Config"]\n App --> Config`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const outputDir = path.join(dir, 'docs', 'generated');
|
|
529
|
+
const outputPath = path.join(outputDir, 'code_graph.mmd');
|
|
530
|
+
|
|
531
|
+
if (dryRun) {
|
|
532
|
+
console.log(chalk.gray(` [dry-run] docs/generated/code_graph.mmd`));
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
await fs.ensureDir(outputDir);
|
|
536
|
+
await fs.writeFile(outputPath, diagram, 'utf8');
|
|
537
|
+
return 'docs/generated/code_graph.mmd';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function scanProject(dir) {
|
|
541
|
+
const results = {
|
|
542
|
+
files: [],
|
|
543
|
+
dirs: [],
|
|
544
|
+
pyFiles: [],
|
|
545
|
+
jsFiles: [],
|
|
546
|
+
htmlFiles: [],
|
|
547
|
+
configFiles: [],
|
|
548
|
+
uiDirs: [],
|
|
549
|
+
coreDirs: [],
|
|
550
|
+
hasCommercialEngine: false,
|
|
551
|
+
hasMainPy: false,
|
|
552
|
+
hasIndexHtml: false,
|
|
553
|
+
hasPackageJson: false,
|
|
554
|
+
hasPyproject: false,
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const extensions = {
|
|
558
|
+
py: '.py',
|
|
559
|
+
js: '.js',
|
|
560
|
+
jsx: '.jsx',
|
|
561
|
+
ts: '.ts',
|
|
562
|
+
tsx: '.tsx',
|
|
563
|
+
html: '.html',
|
|
564
|
+
svelte: '.svelte',
|
|
565
|
+
vue: '.vue',
|
|
566
|
+
json: '.json',
|
|
567
|
+
toml: '.toml',
|
|
568
|
+
svg: '.svg',
|
|
569
|
+
ico: '.ico',
|
|
570
|
+
png: '.png',
|
|
571
|
+
jpg: '.jpg',
|
|
572
|
+
jpeg: '.jpeg',
|
|
573
|
+
gif: '.gif',
|
|
574
|
+
css: '.css',
|
|
575
|
+
scss: '.scss',
|
|
576
|
+
bat: '.bat',
|
|
577
|
+
sh: '.sh',
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
function walk(d, depth) {
|
|
581
|
+
if (depth > 5) return;
|
|
582
|
+
const items = fs.readdirSync(d, { withFileTypes: true });
|
|
583
|
+
for (const item of items) {
|
|
584
|
+
if (item.name.startsWith('.')) continue;
|
|
585
|
+
if (item.name === 'node_modules') continue;
|
|
586
|
+
if (item.name === '__pycache__') continue;
|
|
587
|
+
if (item.name === '.pytest_cache') continue;
|
|
588
|
+
if (item.name === 'g360') continue;
|
|
589
|
+
|
|
590
|
+
const fullPath = path.join(d, item.name);
|
|
591
|
+
const relPath = path.relative(dir, fullPath);
|
|
592
|
+
|
|
593
|
+
if (item.isDirectory()) {
|
|
594
|
+
results.dirs.push(relPath);
|
|
595
|
+
if (relPath.includes('ui') || relPath.includes('views') || relPath.includes('pages') || relPath.includes('components')) {
|
|
596
|
+
results.uiDirs.push(relPath);
|
|
597
|
+
}
|
|
598
|
+
if (relPath.includes('core') || relPath.includes('business') || relPath.includes('engine')) {
|
|
599
|
+
results.coreDirs.push(relPath);
|
|
600
|
+
}
|
|
601
|
+
walk(fullPath, depth + 1);
|
|
602
|
+
} else if (item.isFile()) {
|
|
603
|
+
const ext = path.extname(item.name).toLowerCase();
|
|
604
|
+
results.files.push({ path: relPath, ext, name: item.name });
|
|
605
|
+
|
|
606
|
+
if (ext === '.py') results.pyFiles.push(relPath);
|
|
607
|
+
if (ext === '.js' || ext === '.jsx' || ext === '.ts' || ext === '.tsx') results.jsFiles.push(relPath);
|
|
608
|
+
if (ext === '.html') results.htmlFiles.push(relPath);
|
|
609
|
+
if (ext === '.json' || ext === '.toml') results.configFiles.push(relPath);
|
|
610
|
+
if (item.name === 'main.py') results.hasMainPy = true;
|
|
611
|
+
if (item.name === 'index.html') results.hasIndexHtml = true;
|
|
612
|
+
if (item.name === 'package.json') results.hasPackageJson = true;
|
|
613
|
+
if (item.name === 'pyproject.toml') results.hasPyproject = true;
|
|
614
|
+
if (item.name === 'commercial_engine.py') results.hasCommercialEngine = true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
walk(dir, 0);
|
|
620
|
+
return results;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function buildReadmeDiagram(dir, projectInfo) {
|
|
624
|
+
const scan = scanProject(dir);
|
|
625
|
+
const type = projectInfo.type;
|
|
626
|
+
|
|
627
|
+
if (type === 'web') {
|
|
628
|
+
const components = scan.jsFiles.filter(f => f.includes('component') || f.includes('Component'));
|
|
629
|
+
const pages = scan.jsFiles.filter(f => f.includes('page') || f.includes('Page') || f.includes('route') || f.includes('Route'));
|
|
630
|
+
const assets = scan.files.filter(f => ['.svg', '.png', '.jpg', '.ico'].includes(f.ext));
|
|
631
|
+
|
|
632
|
+
let diagram = 'flowchart TD\n';
|
|
633
|
+
diagram += ' Frontend["Frontend<br/>' + (projectInfo.framework || 'web') + '"]\n';
|
|
634
|
+
diagram += ' Assets["Assets<br/>' + assets.length + ' files"]\n';
|
|
635
|
+
diagram += ' Config["Config<br/>skill.json"]\n';
|
|
636
|
+
|
|
637
|
+
if (components.length > 0) {
|
|
638
|
+
diagram += ' Frontend --> Components["Components<br/>' + components.length + '"]\n';
|
|
639
|
+
}
|
|
640
|
+
if (pages.length > 0) {
|
|
641
|
+
diagram += ' Frontend --> Pages["Pages<br/>' + pages.length + '"]\n';
|
|
642
|
+
}
|
|
643
|
+
diagram += ' Frontend --> Assets\n';
|
|
644
|
+
diagram += ' Frontend --> Config';
|
|
645
|
+
return diagram;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (type === 'python-flet') {
|
|
649
|
+
const coreFiles = scan.pyFiles.filter(f => f.includes('core') && !f.includes('__init__'));
|
|
650
|
+
const uiFiles = scan.pyFiles.filter(f => f.includes('ui') && !f.includes('__init__'));
|
|
651
|
+
const exportFiles = scan.pyFiles.filter(f => f.includes('export') && !f.includes('__init__'));
|
|
652
|
+
|
|
653
|
+
let diagram = 'flowchart TD\n';
|
|
654
|
+
diagram += ' UI["UI<br/>Flet widgets<br/>' + uiFiles.length + ' files"]\n';
|
|
655
|
+
diagram += ' Core["Core<br/>business logic<br/>' + coreFiles.length + ' files"]\n';
|
|
656
|
+
diagram += ' Theme["Theme<br/>G360Theme"]\n';
|
|
657
|
+
|
|
658
|
+
if (exportFiles.length > 0) {
|
|
659
|
+
diagram += ' Core --> Export["Export<br/>' + exportFiles.length + '"]\n';
|
|
660
|
+
}
|
|
661
|
+
diagram += ' UI --> Core\n';
|
|
662
|
+
diagram += ' UI --> Theme\n';
|
|
663
|
+
if (scan.hasCommercialEngine) {
|
|
664
|
+
diagram += ' Core --> Engine["commercial_engine"]\n';
|
|
665
|
+
}
|
|
666
|
+
return diagram;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (type === 'python-cli') {
|
|
670
|
+
const coreFiles = scan.pyFiles.filter(f => !f.includes('__init__'));
|
|
671
|
+
let diagram = 'flowchart TD\n';
|
|
672
|
+
diagram += ' CLI["CLI<br/>argparse<br/>main.py"]\n';
|
|
673
|
+
diagram += ' Core["Core<br/>' + coreFiles.length + ' modules"]\n';
|
|
674
|
+
diagram += ' Config["Config<br/>skill.json"]\n';
|
|
675
|
+
diagram += ' CLI --> Core\n';
|
|
676
|
+
diagram += ' CLI --> Config';
|
|
677
|
+
return diagram;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (type === 'python-customtkinter') {
|
|
681
|
+
const coreFiles = scan.pyFiles.filter(f => !f.includes('__init__'));
|
|
682
|
+
let diagram = 'flowchart TD\n';
|
|
683
|
+
diagram += ' UI["UI<br/>CustomTkinter<br/>main.py"]\n';
|
|
684
|
+
diagram += ' Core["Core<br/>' + coreFiles.length + ' modules"]\n';
|
|
685
|
+
diagram += ' Theme["Theme<br/>G360Theme"]\n';
|
|
686
|
+
diagram += ' UI --> Core\n';
|
|
687
|
+
diagram += ' UI --> Theme';
|
|
688
|
+
return diagram;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
let diagram = 'flowchart TD\n';
|
|
692
|
+
diagram += ' App["Aplicacion"]\n';
|
|
693
|
+
diagram += ' Config["Configuracion"]\n';
|
|
694
|
+
diagram += ' Assets["Assets"]\n';
|
|
695
|
+
diagram += ' App --> Config\n';
|
|
696
|
+
diagram += ' App --> Assets';
|
|
697
|
+
return diagram;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function buildArchitectureDiagram(dir, projectInfo) {
|
|
701
|
+
const scan = scanProject(dir);
|
|
702
|
+
const type = projectInfo.type;
|
|
703
|
+
|
|
704
|
+
if (type === 'web') {
|
|
705
|
+
const components = scan.jsFiles.filter(f => f.includes('component') || f.includes('Component'));
|
|
706
|
+
const pages = scan.jsFiles.filter(f => f.includes('page') || f.includes('Page') || f.includes('route') || f.includes('Route'));
|
|
707
|
+
const styles = scan.files.filter(f => f.ext === '.css' || f.ext === '.scss');
|
|
708
|
+
|
|
709
|
+
let diagram = 'flowchart TD\n';
|
|
710
|
+
diagram += ' subgraph Frontend["Frontend"]\n';
|
|
711
|
+
diagram += ' Index["index.html"]\n';
|
|
712
|
+
if (pages.length > 0) diagram += ' Pages["Pages (' + pages.length + ')"]\n';
|
|
713
|
+
if (components.length > 0) diagram += ' Components["Components (' + components.length + ')"]\n';
|
|
714
|
+
if (styles.length > 0) diagram += ' Styles["Styles (' + styles.length + ')"]\n';
|
|
715
|
+
diagram += ' end\n';
|
|
716
|
+
diagram += ' subgraph Assets["Assets"]\n';
|
|
717
|
+
diagram += ' Brand["Brand / logo"]\n';
|
|
718
|
+
diagram += ' Signature["Signature"]\n';
|
|
719
|
+
diagram += ' Favicon["Favicon"]\n';
|
|
720
|
+
diagram += ' end\n';
|
|
721
|
+
diagram += ' subgraph Config["Config"]\n';
|
|
722
|
+
diagram += ' Skill["skill.json"]\n';
|
|
723
|
+
diagram += ' Manifest["manifest"]\n';
|
|
724
|
+
diagram += ' end\n';
|
|
725
|
+
diagram += ' Frontend --> Assets\n';
|
|
726
|
+
diagram += ' Frontend --> Config';
|
|
727
|
+
return diagram;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (type === 'python-flet') {
|
|
731
|
+
const coreFiles = scan.pyFiles.filter(f => f.includes('core') && !f.includes('__init__'));
|
|
732
|
+
const uiFiles = scan.pyFiles.filter(f => f.includes('ui') && !f.includes('__init__'));
|
|
733
|
+
const exportFiles = scan.pyFiles.filter(f => f.includes('export') && !f.includes('__init__'));
|
|
734
|
+
|
|
735
|
+
let diagram = 'flowchart TD\n';
|
|
736
|
+
diagram += ' subgraph UI["UI Layer"]\n';
|
|
737
|
+
diagram += ' Main["main.py"]\n';
|
|
738
|
+
if (uiFiles.length > 0) diagram += ' Widgets["Widgets (' + uiFiles.length + ')"]\n';
|
|
739
|
+
diagram += ' end\n';
|
|
740
|
+
diagram += ' subgraph Core["Core Layer"]\n';
|
|
741
|
+
if (coreFiles.length > 0) diagram += ' Logic["Logic (' + coreFiles.length + ')"]\n';
|
|
742
|
+
diagram += ' Theme["G360Theme"]\n';
|
|
743
|
+
diagram += ' Signature["g360_signature"]\n';
|
|
744
|
+
diagram += ' end\n';
|
|
745
|
+
diagram += ' subgraph Data["Data"]\n';
|
|
746
|
+
if (scan.hasPyproject) diagram += ' ERP["ERP files (.xls/.xlsx/.csv)"]\n';
|
|
747
|
+
if (exportFiles.length > 0) diagram += ' Export["Excel export (' + exportFiles.length + ')"]\n';
|
|
748
|
+
diagram += ' end\n';
|
|
749
|
+
diagram += ' UI --> Core\n';
|
|
750
|
+
diagram += ' Core --> Data';
|
|
751
|
+
return diagram;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (type === 'python-cli') {
|
|
755
|
+
let diagram = 'flowchart TD\n';
|
|
756
|
+
diagram += ' subgraph CLI["CLI"]\n';
|
|
757
|
+
diagram += ' Entry["main.py"]\n';
|
|
758
|
+
diagram += ' Commands["Commands"]\n';
|
|
759
|
+
diagram += ' end\n';
|
|
760
|
+
diagram += ' subgraph Core["Core"]\n';
|
|
761
|
+
diagram += ' Logic["Business logic"]\n';
|
|
762
|
+
diagram += ' Config["Config"]\n';
|
|
763
|
+
diagram += ' end\n';
|
|
764
|
+
diagram += ' CLI --> Core';
|
|
765
|
+
return diagram;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
let diagram = 'flowchart TD\n';
|
|
769
|
+
diagram += ' subgraph App["Aplicacion"]\n';
|
|
770
|
+
diagram += ' UI["UI"]\n';
|
|
771
|
+
diagram += ' Core["Core"]\n';
|
|
772
|
+
diagram += ' Data["Data"]\n';
|
|
773
|
+
diagram += ' end\n';
|
|
774
|
+
diagram += ' UI --> Core\n';
|
|
775
|
+
diagram += ' Core --> Data';
|
|
776
|
+
return diagram;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function extractImports(content) {
|
|
780
|
+
const imports = [];
|
|
781
|
+
const fromRegex = /^\s*from\s+(\.[\w.]+)\s+import/gm;
|
|
782
|
+
const match = content.match(fromRegex);
|
|
783
|
+
if (match) {
|
|
784
|
+
for (const m of match) {
|
|
785
|
+
const parts = m.match(/from\s+(\.[\w.]+)/);
|
|
786
|
+
if (parts) imports.push(parts[1]);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return imports;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function extractJsImports(content) {
|
|
793
|
+
const imports = [];
|
|
794
|
+
const fromRegex = /^\s*import\s+.*\s+from\s+['"](\.[^'"]+)['"]/gm;
|
|
795
|
+
const requireRegex = /require\(['"](\.[^'"]+)['"]\)/g;
|
|
796
|
+
|
|
797
|
+
const fromMatches = content.match(fromRegex);
|
|
798
|
+
if (fromMatches) {
|
|
799
|
+
for (const m of fromMatches) {
|
|
800
|
+
const parts = m.match(/from\s+['"](\.[^'"]+)['"]/);
|
|
801
|
+
if (parts) imports.push(parts[1]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const requireMatches = content.match(requireRegex);
|
|
806
|
+
if (requireMatches) {
|
|
807
|
+
for (const m of requireMatches) {
|
|
808
|
+
const parts = m.match(/require\(['"](\.[^'"]+)['"]\)/);
|
|
809
|
+
if (parts) imports.push(parts[1]);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return imports;
|
|
814
|
+
}
|
|
815
|
+
|