@ingcrea/aumic-tailwind-killer 4.1.4 → 4.1.5
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.es.md +175 -165
- package/README.md +127 -119
- package/dist/1_atoms/constants.js +26 -0
- package/dist/1_atoms/hash.js +22 -0
- package/dist/1_atoms/types.js +6 -0
- package/dist/2_molecules/classifier.js +38 -0
- package/dist/2_molecules/concurrency.js +18 -0
- package/dist/3_cells/AiEngine.js +103 -0
- package/dist/3_cells/DuckDbCache.js +24 -0
- package/dist/3_cells/WebCloner.js +61 -0
- package/dist/4_organisms/AstParser.js +116 -0
- package/dist/5_ecosystems/RestoreEcosystem.js +85 -0
- package/dist/5_ecosystems/TransmutationEcosystem.js +552 -0
- package/dist/5_ecosystems/index.js +17 -0
- package/dist/6_galaxies/index.js +131 -0
- package/dist/index.js +5 -1629
- package/dist/worker.js +26 -14
- package/package.json +52 -51
- package/.npmrc.bak +0 -1
- package/bin/aumic-killer.js +0 -100
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AIEngine = void 0;
|
|
4
|
+
class AIEngine {
|
|
5
|
+
provider;
|
|
6
|
+
apiKey;
|
|
7
|
+
model;
|
|
8
|
+
constructor(provider, apiKey, model) {
|
|
9
|
+
this.provider = provider;
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.model = model || this.getDefaultModel(provider);
|
|
12
|
+
}
|
|
13
|
+
/** [EN] Canonical default model for each provider. [ES] Modelo predeterminado canónico por proveedor. */
|
|
14
|
+
getDefaultModel(provider) {
|
|
15
|
+
const map = {
|
|
16
|
+
openai: 'gpt-4o-mini', deepseek: 'deepseek-chat', xai: 'grok-beta',
|
|
17
|
+
alibaba: 'qwen-turbo', claude: 'claude-3-haiku-20240307',
|
|
18
|
+
gemini: 'gemini-1.5-flash', ollama: 'llama3',
|
|
19
|
+
};
|
|
20
|
+
return map[provider] || 'gpt-4o-mini';
|
|
21
|
+
}
|
|
22
|
+
/** [EN] Base endpoint URL per provider. [ES] URL de endpoint base por proveedor. */
|
|
23
|
+
getProviderBaseUrl(provider) {
|
|
24
|
+
const map = {
|
|
25
|
+
openai: 'https://api.openai.com/v1/chat/completions',
|
|
26
|
+
deepseek: 'https://api.deepseek.com/chat/completions',
|
|
27
|
+
xai: 'https://api.x.ai/v1/chat/completions',
|
|
28
|
+
alibaba: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
|
29
|
+
ollama: 'http://127.0.0.1:11434/api/generate',
|
|
30
|
+
};
|
|
31
|
+
return map[provider] || '';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* [EN] Dispatches the naming request to the correct provider and returns a normalized mapping.
|
|
35
|
+
* [ES] Despacha la solicitud de nombrado al proveedor correcto y devuelve un mapeo normalizado.
|
|
36
|
+
*/
|
|
37
|
+
async generateSemanticNames(request) {
|
|
38
|
+
let sys = "Eres un Arquitecto CSS AUM-IC. Te daré un array JSON de cadenas de utilidades de Tailwind. Devuelve un objeto JSON donde la clave es la cadena exacta de Tailwind, y el valor es un nombre de clase semántico BEM corto que empiece con 'aumic-'. Responde ÚNICAMENTE con el objeto JSON válido.";
|
|
39
|
+
if (request.customRules)
|
|
40
|
+
sys += `\n\nREGLAS PERSONALIZADAS:\n${request.customRules}\nDEBES DAR PRIORIDAD ABSOLUTA A ESTAS REGLAS.`;
|
|
41
|
+
const user = JSON.stringify(request.utilities);
|
|
42
|
+
let raw = '';
|
|
43
|
+
if (['openai', 'deepseek', 'xai', 'alibaba'].includes(this.provider))
|
|
44
|
+
raw = await this.callOpenAICompatible(sys, user);
|
|
45
|
+
else if (this.provider === 'gemini')
|
|
46
|
+
raw = await this.callGemini(sys, user);
|
|
47
|
+
else if (this.provider === 'claude')
|
|
48
|
+
raw = await this.callClaude(sys, user);
|
|
49
|
+
else if (this.provider === 'ollama')
|
|
50
|
+
raw = await this.callOllama(sys, user);
|
|
51
|
+
else
|
|
52
|
+
throw new Error(`Proveedor ${this.provider} no implementado.`);
|
|
53
|
+
try {
|
|
54
|
+
// [EN] Strip markdown fences — some LLMs ignore the plain-JSON instruction.
|
|
55
|
+
// [ES] Eliminar bloques markdown — algunos LLMs ignoran la instrucción de JSON plano.
|
|
56
|
+
return { mapping: JSON.parse(raw.replace(/```json/g, '').replace(/```/g, '').trim()) };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { mapping: {} };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** [EN] Adapter for OpenAI-compatible REST APIs. [ES] Adaptador para APIs REST compatibles con OpenAI. */
|
|
63
|
+
async callOpenAICompatible(sys, user) {
|
|
64
|
+
const res = await fetch(this.getProviderBaseUrl(this.provider), {
|
|
65
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` },
|
|
66
|
+
body: JSON.stringify({ model: this.model, messages: [{ role: 'system', content: sys }, { role: 'user', content: user }], temperature: 0.1 })
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`[IA] HTTP ${res.status}: ${await res.text()}`);
|
|
70
|
+
return (await res.json()).choices[0].message.content;
|
|
71
|
+
}
|
|
72
|
+
/** [EN] Adapter for Google Gemini. [ES] Adaptador para Google Gemini. */
|
|
73
|
+
async callGemini(sys, user) {
|
|
74
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`, {
|
|
75
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
76
|
+
body: JSON.stringify({ system_instruction: { parts: { text: sys } }, contents: [{ parts: [{ text: user }] }], generationConfig: { temperature: 0.1 } })
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok)
|
|
79
|
+
throw new Error(`[IA] HTTP ${res.status}: ${await res.text()}`);
|
|
80
|
+
return (await res.json()).candidates[0].content.parts[0].text;
|
|
81
|
+
}
|
|
82
|
+
/** [EN] Adapter for Anthropic Claude. [ES] Adaptador para Anthropic Claude. */
|
|
83
|
+
async callClaude(sys, user) {
|
|
84
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
85
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01' },
|
|
86
|
+
body: JSON.stringify({ model: this.model, system: sys, messages: [{ role: 'user', content: user }], temperature: 0.1, max_tokens: 4096 })
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok)
|
|
89
|
+
throw new Error(`[IA] HTTP ${res.status}: ${await res.text()}`);
|
|
90
|
+
return (await res.json()).content[0].text;
|
|
91
|
+
}
|
|
92
|
+
/** [EN] Adapter for self-hosted Ollama. [ES] Adaptador para Ollama auto-hospedado. */
|
|
93
|
+
async callOllama(sys, user) {
|
|
94
|
+
const res = await fetch(this.getProviderBaseUrl(this.provider), {
|
|
95
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
96
|
+
body: JSON.stringify({ model: this.model, system: sys, prompt: user, stream: false, options: { temperature: 0.1 } })
|
|
97
|
+
});
|
|
98
|
+
if (!res.ok)
|
|
99
|
+
throw new Error(`[IA] Ollama HTTP ${res.status}: ¿Está corriendo Ollama?`);
|
|
100
|
+
return (await res.json()).response;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.AIEngine = AIEngine;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryL1Cache = queryL1Cache;
|
|
4
|
+
/**
|
|
5
|
+
* [EN] Queries the bundled DuckDB lexicon. Unresolved classes are forwarded to the L2 JIT compiler.
|
|
6
|
+
* [ES] Consulta el léxico DuckDB incluido. Las clases no resueltas se reenvían al compilador JIT L2.
|
|
7
|
+
*/
|
|
8
|
+
async function queryL1Cache(classes, version, dbPath) {
|
|
9
|
+
const duckdb = require('duckdb');
|
|
10
|
+
const db = new duckdb.Database(dbPath);
|
|
11
|
+
let css = '';
|
|
12
|
+
const unresolved = new Set();
|
|
13
|
+
await Promise.all(classes.map(cls => new Promise(resolve => {
|
|
14
|
+
db.all('SELECT css FROM tw_lexicon WHERE version = ? AND class = ?', [version, cls], (err, rows) => {
|
|
15
|
+
if (err || rows.length === 0)
|
|
16
|
+
unresolved.add(cls);
|
|
17
|
+
else
|
|
18
|
+
css += `\n/* tw: ${cls} (L1) */\n.${cls} { ${rows[0].css} }\n`;
|
|
19
|
+
resolve();
|
|
20
|
+
});
|
|
21
|
+
})));
|
|
22
|
+
db.close();
|
|
23
|
+
return { css, unresolved };
|
|
24
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WebCloner = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* [EN] Web Cloner cell: network I/O handler for Forensic Mode.
|
|
9
|
+
* [ES] Célula Clonador Web: manejador I/O de red para el Modo Forense.
|
|
10
|
+
*/
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
const website_scraper_1 = __importDefault(require("website-scraper"));
|
|
15
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
16
|
+
class WebCloner {
|
|
17
|
+
/**
|
|
18
|
+
* [EN] Downloads and mirrors a URL. Rewrites all asset paths for offline use.
|
|
19
|
+
* [ES] Descarga y espeja una URL. Reescribe rutas de assets para uso offline.
|
|
20
|
+
*/
|
|
21
|
+
static async cloneWebsite(url, outputDir, cloneDepth) {
|
|
22
|
+
if (await fs_extra_1.default.pathExists(outputDir))
|
|
23
|
+
await fs_extra_1.default.emptyDir(outputDir);
|
|
24
|
+
const options = {
|
|
25
|
+
urls: [url], directory: outputDir,
|
|
26
|
+
sources: [
|
|
27
|
+
{ selector: 'img', attr: 'src' }, { selector: 'link[rel="stylesheet"]', attr: 'href' },
|
|
28
|
+
{ selector: 'script', attr: 'src' }, { selector: 'source', attr: 'src' },
|
|
29
|
+
{ selector: 'source', attr: 'srcset' }, { selector: 'img', attr: 'srcset' }
|
|
30
|
+
],
|
|
31
|
+
recursive: cloneDepth === 'site', maxDepth: cloneDepth === 'site' ? 5 : 1, ignoreErrors: true
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
await (0, website_scraper_1.default)(options);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error(picocolors_1.default.red(`\n[!] Error durante la clonación: ${error}`));
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* [EN] Scans HTML files for Tailwind atomic signatures. Returns true on confirmation.
|
|
43
|
+
* [ES] Escanea archivos HTML en busca de firmas atómicas Tailwind. Devuelve true al confirmar.
|
|
44
|
+
*/
|
|
45
|
+
static async isTailwindPowered(outputDir) {
|
|
46
|
+
const files = (await fs_extra_1.default.readdir(outputDir)).filter(f => f.endsWith('.html'));
|
|
47
|
+
const sigs = ['flex', 'grid', 'w-full', 'h-full', 'absolute', 'relative', 'bg-', 'text-', 'p-4', 'm-'];
|
|
48
|
+
let score = 0;
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const c = await fs_extra_1.default.readFile(path_1.default.join(outputDir, file), 'utf-8');
|
|
51
|
+
for (const sig of sigs) {
|
|
52
|
+
if (c.includes(`"${sig}`))
|
|
53
|
+
score++;
|
|
54
|
+
if (c.includes(` ${sig}`))
|
|
55
|
+
score++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return score >= 5;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.WebCloner = WebCloner;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AstInterceptor = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* [EN] AST Interception Organism — the core transmutation engine.
|
|
9
|
+
* Routes files through Babel AST (JSX/TSX) or Universal Regex (HTML/templates).
|
|
10
|
+
* Supports forward (Tailwind→AUM-IC) and reverse (rollback) passes.
|
|
11
|
+
* [ES] Organismo de Intercepción AST — el motor principal de transmutación.
|
|
12
|
+
* Enruta archivos por Babel AST (JSX/TSX) o Regex Universal (HTML/plantillas).
|
|
13
|
+
* Soporta pases directo (Tailwind→AUM-IC) e inverso (rollback).
|
|
14
|
+
*/
|
|
15
|
+
const parser_1 = require("@babel/parser");
|
|
16
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
17
|
+
const generator_1 = __importDefault(require("@babel/generator"));
|
|
18
|
+
class AstInterceptor {
|
|
19
|
+
/**
|
|
20
|
+
* [EN] Public router: delegates to Babel or Regex based on file extension.
|
|
21
|
+
* `replacerFn` receives a class string and returns the replacement.
|
|
22
|
+
* Set `isRestore = true` to enable reverse (rollback) mode.
|
|
23
|
+
* [ES] Enrutador público: delega a Babel o Regex según la extensión del archivo.
|
|
24
|
+
* `replacerFn` recibe un string de clase y devuelve el reemplazo.
|
|
25
|
+
* Establece `isRestore = true` para el modo inverso (rollback).
|
|
26
|
+
*/
|
|
27
|
+
processFrameworkFile(code, fileName, replacerFn, isRestore = false) {
|
|
28
|
+
const ext = fileName.split('.').pop()?.toLowerCase();
|
|
29
|
+
return ['jsx', 'tsx', 'js', 'ts'].includes(ext || '')
|
|
30
|
+
? this.processWithBabel(code, replacerFn, isRestore)
|
|
31
|
+
: this.processWithUniversalRegex(code, replacerFn, isRestore);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* [EN] Universal Regex engine for non-JS templates.
|
|
35
|
+
* Phase 1 extracts class strings; Phase 2 mutates via replacerFn callback.
|
|
36
|
+
* [ES] Motor Regex Universal para plantillas no-JS.
|
|
37
|
+
* La Fase 1 extrae strings de clase; la Fase 2 muta mediante el callback replacerFn.
|
|
38
|
+
*/
|
|
39
|
+
processWithUniversalRegex(code, replacerFn, isRestore) {
|
|
40
|
+
const extracted = new Set();
|
|
41
|
+
const classRegex = /(class|className|class:list)\s*(=|:)\s*(["'`])(.*?)\3/gs;
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = classRegex.exec(code)) !== null) {
|
|
44
|
+
const clean = match[4].replace(/\s+/g, ' ').trim();
|
|
45
|
+
if (clean && (isRestore || !clean.includes('aumic-')))
|
|
46
|
+
extracted.add(clean);
|
|
47
|
+
}
|
|
48
|
+
const newCode = code.replace(classRegex, (full, attr, sep, q, cls) => {
|
|
49
|
+
const clean = cls.replace(/\s+/g, ' ').trim();
|
|
50
|
+
if (!clean || (!isRestore && clean.includes('aumic-')))
|
|
51
|
+
return full;
|
|
52
|
+
return `${attr}${sep}${q}${replacerFn(clean)}${q}`;
|
|
53
|
+
});
|
|
54
|
+
return { newCode, extractedClasses: Array.from(extracted) };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* [EN] Babel AST engine for JSX/TSX. Visits JSXAttribute nodes (className/class)
|
|
58
|
+
* and CallExpression nodes (clsx, cva, cn, twMerge). Preserves original line numbers.
|
|
59
|
+
* [ES] Motor AST Babel para JSX/TSX. Visita nodos JSXAttribute (className/class)
|
|
60
|
+
* y CallExpression (clsx, cva, cn, twMerge). Preserva números de línea originales.
|
|
61
|
+
*/
|
|
62
|
+
processWithBabel(code, replacerFn, isRestore) {
|
|
63
|
+
const ast = (0, parser_1.parse)(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] });
|
|
64
|
+
const extracted = new Set();
|
|
65
|
+
const traverseAst = traverse_1.default.default || traverse_1.default;
|
|
66
|
+
const fn = replacerFn; // Capture outside Babel visitors to avoid closure loss
|
|
67
|
+
traverseAst(ast, {
|
|
68
|
+
CallExpression(path) {
|
|
69
|
+
if (['clsx', 'cva', 'cn', 'twMerge', 'classNames'].includes(path.node.callee.name)) {
|
|
70
|
+
path.traverse({
|
|
71
|
+
StringLiteral(p) {
|
|
72
|
+
const c = p.node.value.replace(/\s+/g, ' ').trim();
|
|
73
|
+
if (c && (isRestore || !c.includes('aumic-'))) {
|
|
74
|
+
extracted.add(c);
|
|
75
|
+
p.node.value = fn(c);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
TemplateElement(p) {
|
|
79
|
+
const c = p.node.value.raw.replace(/\s+/g, ' ').trim();
|
|
80
|
+
if (c && (isRestore || !c.includes('aumic-'))) {
|
|
81
|
+
extracted.add(c);
|
|
82
|
+
p.node.value.raw = fn(c);
|
|
83
|
+
p.node.value.cooked = p.node.value.raw;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
JSXAttribute(path) {
|
|
90
|
+
if (['className', 'class'].includes(path.node.name.name)) {
|
|
91
|
+
path.traverse({
|
|
92
|
+
StringLiteral(p) {
|
|
93
|
+
const c = p.node.value.replace(/\s+/g, ' ').trim();
|
|
94
|
+
if (c && (isRestore || !c.includes('aumic-'))) {
|
|
95
|
+
extracted.add(c);
|
|
96
|
+
p.node.value = fn(c);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
TemplateElement(p) {
|
|
100
|
+
const c = p.node.value.raw.replace(/\s+/g, ' ').trim();
|
|
101
|
+
if (c && (isRestore || !c.includes('aumic-'))) {
|
|
102
|
+
extracted.add(c);
|
|
103
|
+
p.node.value.raw = fn(c);
|
|
104
|
+
p.node.value.cooked = p.node.value.raw;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const genCode = generator_1.default.default || generator_1.default;
|
|
112
|
+
const out = genCode(ast, { retainLines: true }, code);
|
|
113
|
+
return { newCode: out.code, extractedClasses: Array.from(extracted) };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.AstInterceptor = AstInterceptor;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runRestoreEcosystem = runRestoreEcosystem;
|
|
7
|
+
/**
|
|
8
|
+
* [EN] Restore Ecosystem: orchestrates a full rollback of an AUM-IC transmutation.
|
|
9
|
+
* Reads the aumic-lock.json manifest, restores original class strings via AST,
|
|
10
|
+
* reinstates original package.json and Tailwind config files, and re-runs npm install.
|
|
11
|
+
* [ES] Ecosistema de Restauración: orquesta el rollback completo de una transmutación AUM-IC.
|
|
12
|
+
* Lee el manifiesto aumic-lock.json, restaura los strings de clase originales vía AST,
|
|
13
|
+
* restablece el package.json y los archivos de config de Tailwind originales, y re-ejecuta npm install.
|
|
14
|
+
*/
|
|
15
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const os_1 = __importDefault(require("os"));
|
|
18
|
+
const ora_1 = __importDefault(require("ora"));
|
|
19
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
20
|
+
const glob_1 = require("glob");
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
22
|
+
const AstParser_1 = require("../4_organisms/AstParser");
|
|
23
|
+
const astEngine = new AstParser_1.AstInterceptor();
|
|
24
|
+
/**
|
|
25
|
+
* [EN] Entry point for the restore pipeline. Validates lock file existence,
|
|
26
|
+
* runs the AST reverse pass on all source files, and restores project dependencies.
|
|
27
|
+
* [ES] Punto de entrada del pipeline de restauración. Valida la existencia del lock file,
|
|
28
|
+
* ejecuta el pase inverso AST en todos los archivos fuente, y restaura las dependencias del proyecto.
|
|
29
|
+
*/
|
|
30
|
+
async function runRestoreEcosystem(targetDir) {
|
|
31
|
+
const lockPath = path_1.default.join(targetDir, 'aumic-lock.json');
|
|
32
|
+
if (!fs_extra_1.default.existsSync(lockPath)) {
|
|
33
|
+
console.log(picocolors_1.default.red('\n[X] Error: No se encontró aumic-lock.json. Imposible restaurar.\n'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const spinner = (0, ora_1.default)('Modo Restauración: Leyendo aumic-lock.json...').start();
|
|
37
|
+
const lockData = await fs_extra_1.default.readJson(lockPath);
|
|
38
|
+
spinner.start('Restaurando clases en archivos fuente...');
|
|
39
|
+
const files = await (0, glob_1.glob)('**/*.{astro,tsx,jsx,html,py,rs,php,go,erb,svelte,vue}', {
|
|
40
|
+
cwd: targetDir, absolute: true,
|
|
41
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**', 'target/**', '__pycache__/**', 'venv/**'],
|
|
42
|
+
});
|
|
43
|
+
let restoredCount = 0;
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const content = await fs_extra_1.default.readFile(file, 'utf-8');
|
|
46
|
+
try {
|
|
47
|
+
// [EN] Split multi-hash strings before lookup to handle concatenated hashes.
|
|
48
|
+
// [ES] Dividir strings con múltiples hashes antes de la búsqueda para manejar hashes concatenados.
|
|
49
|
+
const { newCode } = astEngine.processFrameworkFile(content, file, (twClass) => {
|
|
50
|
+
return twClass.split(' ').map(c => lockData[c] || c).join(' ');
|
|
51
|
+
}, true);
|
|
52
|
+
if (newCode !== content) {
|
|
53
|
+
await fs_extra_1.default.writeFile(file, newCode, 'utf-8');
|
|
54
|
+
restoredCount++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch { /* [EN] Skip unreadable or binary files silently. [ES] Ignorar silenciosamente archivos ilegibles o binarios. */ }
|
|
58
|
+
}
|
|
59
|
+
spinner.succeed(`Clases originales restauradas en ${restoredCount} archivos.`);
|
|
60
|
+
spinner.start('Restaurando dependencias y configuraciones...');
|
|
61
|
+
try {
|
|
62
|
+
// [EN] Restore package.json backup if it exists.
|
|
63
|
+
// [ES] Restaurar el respaldo de package.json si existe.
|
|
64
|
+
const pkgBak = path_1.default.join(targetDir, 'package.json.aumic-bak');
|
|
65
|
+
if (fs_extra_1.default.existsSync(pkgBak)) {
|
|
66
|
+
fs_extra_1.default.copyFileSync(pkgBak, path_1.default.join(targetDir, 'package.json'));
|
|
67
|
+
fs_extra_1.default.removeSync(pkgBak);
|
|
68
|
+
}
|
|
69
|
+
// [EN] Restore Tailwind config file backups.
|
|
70
|
+
// [ES] Restaurar los respaldos de archivos de configuración de Tailwind.
|
|
71
|
+
for (const conf of ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.cjs', 'tailwind.config.mjs']) {
|
|
72
|
+
const bak = path_1.default.join(targetDir, `${conf}.aumic-bak`);
|
|
73
|
+
if (fs_extra_1.default.existsSync(bak))
|
|
74
|
+
fs_extra_1.default.renameSync(bak, path_1.default.join(targetDir, conf));
|
|
75
|
+
}
|
|
76
|
+
// [EN] Spawn npm install as a direct binary — no shell interpolation (Zero-Trust execution).
|
|
77
|
+
// [ES] Levantar npm install como binario directo — sin interpolación de shell (ejecución Zero-Trust).
|
|
78
|
+
(0, child_process_1.spawnSync)(os_1.default.platform() === 'win32' ? 'npm.cmd' : 'npm', ['install'], { cwd: targetDir, stdio: 'ignore' });
|
|
79
|
+
spinner.succeed('Tailwind rehidratado. Dependencias y configuraciones restauradas.');
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
spinner.warn('Problema reinstalando dependencias desde el package.json restaurado.');
|
|
83
|
+
}
|
|
84
|
+
console.log(picocolors_1.default.green(picocolors_1.default.bold('\n[✔] PROYECTO RESTAURADO CON ÉXITO A SU ESTADO ORIGINAL.\n')));
|
|
85
|
+
}
|