g360-cli 1.0.0 → 1.1.1
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 +626 -28
- package/package.json +7 -2
- package/src/assets/config/g360-skills.json +172 -0
- package/src/assets/templates/lit-web/index.html +20 -0
- package/src/assets/templates/lit-web/package.json +19 -0
- package/src/assets/templates/lit-web/src/components/app-root.js +73 -0
- package/src/assets/templates/lit-web/src/core/skill.json +18 -0
- package/src/assets/templates/lit-web/src/index.js +8 -0
- package/src/assets/templates/lit-web/src/styles/main.css +91 -0
- package/src/assets/templates/lit-web/vite.config.js +10 -0
- package/src/assets/templates/solid-web/index.html +21 -0
- package/src/assets/templates/solid-web/package.json +20 -0
- package/src/assets/templates/solid-web/src/components/App.jsx +21 -0
- package/src/assets/templates/solid-web/src/core/skill.json +18 -0
- package/src/assets/templates/solid-web/src/index.jsx +11 -0
- package/src/assets/templates/solid-web/src/styles/main.css +115 -0
- package/src/assets/templates/solid-web/vite.config.js +12 -0
- package/src/assets/templates/svelte-web/package.json +21 -0
- package/src/assets/templates/svelte-web/src/app.css +77 -0
- package/src/assets/templates/svelte-web/src/app.html +19 -0
- package/src/assets/templates/svelte-web/src/core/skill.json +18 -0
- package/src/assets/templates/svelte-web/src/routes/+page.svelte +38 -0
- package/src/assets/templates/svelte-web/svelte.config.js +16 -0
- package/src/assets/templates/web-pwa/index.html +19 -21
- package/src/assets/templates/web-pwa/package.json +18 -2
- package/src/cli.js +26 -1
- package/src/commands/clean.js +288 -30
- package/src/commands/convert.js +388 -0
- package/src/commands/init.js +2 -1
- package/src/commands/set-skill.js +84 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
* G360 Theme - Estilos principales
|
|
3
|
+
* Sistema de diseño para proyectos G360
|
|
4
|
+
* ============================================ */
|
|
5
|
+
|
|
6
|
+
/* === VARIABLES G360 === */
|
|
7
|
+
:root {
|
|
8
|
+
/* Colores G360 - Oscuro (Default) */
|
|
9
|
+
--g360-bg: #0b1220;
|
|
10
|
+
--g360-surface: #151e2e;
|
|
11
|
+
--g360-accent: #00d084;
|
|
12
|
+
--g360-accent-dark: #00796B;
|
|
13
|
+
--g360-text: #f0f4f8;
|
|
14
|
+
--g360-muted: #94a3b8;
|
|
15
|
+
--g360-border: rgba(255, 255, 255, 0.1);
|
|
16
|
+
|
|
17
|
+
/* Efectos */
|
|
18
|
+
--g360-glass: rgba(21, 30, 46, 0.75);
|
|
19
|
+
--g360-blur: 16px;
|
|
20
|
+
--g360-rounded: 16px;
|
|
21
|
+
|
|
22
|
+
/* Espaciado */
|
|
23
|
+
--g360-space-xs: 4px;
|
|
24
|
+
--g360-space-sm: 8px;
|
|
25
|
+
--g360-space-md: 16px;
|
|
26
|
+
--g360-space-lg: 24px;
|
|
27
|
+
--g360-space-xl: 32px;
|
|
28
|
+
|
|
29
|
+
/* Tipografía */
|
|
30
|
+
--g360-font: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
31
|
+
--g360-font-size: 14px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* === ESTILOS BASE === */
|
|
35
|
+
* {
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
body {
|
|
42
|
+
font-family: var(--g360-font);
|
|
43
|
+
font-size: var(--g360-font-size);
|
|
44
|
+
background: var(--g360-bg);
|
|
45
|
+
color: var(--g360-text);
|
|
46
|
+
line-height: 1.5;
|
|
47
|
+
-webkit-font-smoothing: antialiased;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* === UTILIDADES === */
|
|
51
|
+
.g360-card {
|
|
52
|
+
background: var(--g360-surface);
|
|
53
|
+
border: 1px solid var(--g360-border);
|
|
54
|
+
border-radius: var(--g360-rounded);
|
|
55
|
+
padding: var(--g360-space-md);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.g360-btn {
|
|
59
|
+
background: var(--g360-accent);
|
|
60
|
+
color: var(--g360-bg);
|
|
61
|
+
border: none;
|
|
62
|
+
border-radius: var(--g360-rounded);
|
|
63
|
+
padding: var(--g360-space-sm) var(--g360-space-md);
|
|
64
|
+
font-weight: 700;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* === MODO CLARO === */
|
|
69
|
+
@media (prefers-color-scheme: light) {
|
|
70
|
+
:root {
|
|
71
|
+
--g360-bg: #ffffff;
|
|
72
|
+
--g360-surface: #f5f5f5;
|
|
73
|
+
--g360-text: #1f2937;
|
|
74
|
+
--g360-muted: #6b7280;
|
|
75
|
+
--g360-border: rgba(0, 0, 0, 0.1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="author" content="@carloscus" />
|
|
7
|
+
<meta name="description" content="Proyecto G360 desarrollado con SvelteKit" />
|
|
8
|
+
<meta name="theme-color" content="#00d084" />
|
|
9
|
+
<title>Mi Proyecto G360</title>
|
|
10
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
11
|
+
%sveltekit.head%
|
|
12
|
+
</head>
|
|
13
|
+
<body data-sveltekit-preload-data="hover">
|
|
14
|
+
<div style="display: contents">%sveltekit.body%</div>
|
|
15
|
+
|
|
16
|
+
<!-- Firma G360 -->
|
|
17
|
+
<g360-signature mode="powered" style="position: fixed; bottom: 16px; right: 16px; z-index: 100;"></g360-signature>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill": "corporativo",
|
|
3
|
+
"device": "movil",
|
|
4
|
+
"template": "svelte-web",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"createdAt": "2024-01-01T00:00:00.000Z",
|
|
7
|
+
"colors": {
|
|
8
|
+
"bg": "#0b1220",
|
|
9
|
+
"surface": "#151e2e",
|
|
10
|
+
"accent": "#00d084",
|
|
11
|
+
"text": "#f0f4f8",
|
|
12
|
+
"muted": "#94a3b8"
|
|
13
|
+
},
|
|
14
|
+
"signature": {
|
|
15
|
+
"mode": "powered",
|
|
16
|
+
"text": "powered by G360"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let titulo = 'Mi Proyecto G360';
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="container">
|
|
6
|
+
<header>
|
|
7
|
+
<h1>{titulo}</h1>
|
|
8
|
+
</header>
|
|
9
|
+
<main>
|
|
10
|
+
<slot />
|
|
11
|
+
</main>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
.container {
|
|
16
|
+
max-width: 480px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 16px;
|
|
19
|
+
min-height: 100vh;
|
|
20
|
+
background: var(--g360-bg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
header {
|
|
24
|
+
padding: 16px 0;
|
|
25
|
+
border-bottom: 1px solid var(--g360-surface);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
h1 {
|
|
29
|
+
margin: 0;
|
|
30
|
+
font-size: 24px;
|
|
31
|
+
font-weight: 700;
|
|
32
|
+
color: var(--g360-accent);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
main {
|
|
36
|
+
padding: 24px 0;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import adapter from '@sveltejs/adapter-static';
|
|
2
|
+
|
|
3
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
4
|
+
const config = {
|
|
5
|
+
kit: {
|
|
6
|
+
adapter: adapter({
|
|
7
|
+
pages: 'build',
|
|
8
|
+
assets: 'build',
|
|
9
|
+
fallback: 'index.html',
|
|
10
|
+
precompress: false,
|
|
11
|
+
strict: true
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default config;
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="es">
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
</
|
|
22
|
-
<script src="/app.js"></script>
|
|
23
|
-
</body>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="author" content="@carloscus" />
|
|
7
|
+
<meta name="description" content="Proyecto G360 desarrollado con React + Vite" />
|
|
8
|
+
<meta name="theme-color" content="#00d084" />
|
|
9
|
+
<title>Mi Proyecto G360</title>
|
|
10
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
11
|
+
<link rel="manifest" href="/manifest.json" />
|
|
12
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div id="root"></div>
|
|
16
|
+
|
|
17
|
+
<!-- Firma G360 -->
|
|
18
|
+
<g360-signature mode="powered" style="position: fixed; bottom: 16px; right: 16px; z-index: 100;"></g360-signature>
|
|
19
|
+
|
|
20
|
+
<script type="module" src="/app.js"></script>
|
|
21
|
+
</body>
|
|
24
22
|
</html>
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "mi-react-proyecto",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"
|
|
4
|
+
"description": "Proyecto G360 creado con React + Vite",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^18.2.0",
|
|
13
|
+
"react-dom": "^18.2.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
17
|
+
"vite": "^5.0.0"
|
|
18
|
+
},
|
|
19
|
+
"author": "@carloscus",
|
|
20
|
+
"license": "MIT"
|
|
5
21
|
}
|
package/src/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { init } from './commands/init.js';
|
|
6
|
+
import { setSkill } from './commands/set-skill.js';
|
|
6
7
|
import { bring } from './commands/bring.js';
|
|
7
8
|
import { list } from './commands/list.js';
|
|
8
9
|
import { present } from './commands/present.js';
|
|
@@ -10,23 +11,32 @@ import { audit } from './commands/audit.js';
|
|
|
10
11
|
import { clean } from './commands/clean.js';
|
|
11
12
|
import { health } from './commands/health.js';
|
|
12
13
|
import { update } from './commands/update.js';
|
|
14
|
+
import { convert } from './commands/convert.js';
|
|
13
15
|
|
|
14
16
|
const program = new Command();
|
|
15
17
|
|
|
16
18
|
program
|
|
17
19
|
.name('g360')
|
|
18
20
|
.description('CLI tool for bootstrapping G360 ecosystem projects')
|
|
19
|
-
.version('1.
|
|
21
|
+
.version('1.1.1');
|
|
20
22
|
|
|
21
23
|
program
|
|
22
24
|
.command('init')
|
|
23
25
|
.argument('<name>', 'Project name')
|
|
24
26
|
.option('-t, --template <type>', 'Project template type', 'web-pwa')
|
|
27
|
+
.option('-s, --skill <skill>', 'Skill to use (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom)', 'corporativo-movil')
|
|
25
28
|
.option('-d, --dir <path>', 'Target directory', '.')
|
|
26
29
|
.option('--dry-run', 'Preview without creating files')
|
|
27
30
|
.option('--force', 'Overwrite existing files')
|
|
28
31
|
.action(init);
|
|
29
32
|
|
|
33
|
+
program
|
|
34
|
+
.command('set-skill')
|
|
35
|
+
.argument('<skill>', 'Skill name to set')
|
|
36
|
+
.option('--verbose', 'Show detailed output')
|
|
37
|
+
.option('--force', 'Overwrite existing skill')
|
|
38
|
+
.action(setSkill);
|
|
39
|
+
|
|
30
40
|
program
|
|
31
41
|
.command('bring')
|
|
32
42
|
.argument('[asset]', 'Specific asset to bring (template/component/skill/all)')
|
|
@@ -59,6 +69,11 @@ program
|
|
|
59
69
|
.argument('[path]', 'Path to clean', '.')
|
|
60
70
|
.option('--dry-run', 'Preview files to be removed')
|
|
61
71
|
.option('--force', 'Skip confirmation')
|
|
72
|
+
.option('--dead', 'Remove dead/deprecated files')
|
|
73
|
+
.option('--duplicates', 'Remove duplicate files')
|
|
74
|
+
.option('--orphans', 'Remove orphan files (unreferenced)')
|
|
75
|
+
.option('--organize', 'Show misplaced files suggestions')
|
|
76
|
+
.option('--all', 'Run all cleanup tasks')
|
|
62
77
|
.action(clean);
|
|
63
78
|
|
|
64
79
|
program
|
|
@@ -71,4 +86,14 @@ program
|
|
|
71
86
|
.option('--check', 'Check for updates without installing')
|
|
72
87
|
.action(update);
|
|
73
88
|
|
|
89
|
+
program
|
|
90
|
+
.command('convert')
|
|
91
|
+
.argument('[path]', 'Path to convert', '.')
|
|
92
|
+
.option('-s, --skill <skill>', 'Skill to apply', 'corporativo-movil')
|
|
93
|
+
.option('--dry-run', 'Preview without applying')
|
|
94
|
+
.option('--restructure', 'Restructure project files')
|
|
95
|
+
.option('--force', 'Force dangerous changes')
|
|
96
|
+
.option('--backup', 'Create backup before converting')
|
|
97
|
+
.action(convert);
|
|
98
|
+
|
|
74
99
|
program.parse();
|
package/src/commands/clean.js
CHANGED
|
@@ -1,65 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file clean.js
|
|
3
|
+
* @description Comando para limpiar código muerto, descontinuado y redundante
|
|
4
|
+
* @author @carloscus
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import chalk from 'chalk';
|
|
2
8
|
import fs from 'fs-extra';
|
|
3
9
|
import path from 'path';
|
|
4
|
-
import {
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
const DEAD_PATTERNS = [
|
|
15
|
+
'backup', 'backup-old', 'backups',
|
|
16
|
+
'temp', 'tmp', '.tmp',
|
|
17
|
+
'old', 'deprecated', 'archive',
|
|
18
|
+
'unused', 'trash', 'garbage'
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const DUPLICATE_EXTENSIONS = ['.bak', '.orig', '.swp', '.swo', '~'];
|
|
5
22
|
|
|
6
23
|
export async function clean(projectPath, options) {
|
|
7
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
dryRun = false,
|
|
26
|
+
force = false,
|
|
27
|
+
dead = false,
|
|
28
|
+
duplicates = false,
|
|
29
|
+
orphans = false,
|
|
30
|
+
organize = false,
|
|
31
|
+
all = false
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
8
34
|
const targetDir = path.join(process.cwd(), projectPath);
|
|
9
35
|
|
|
10
|
-
console.log(chalk.bold.cyan('\n🧹 G360 Clean\n'));
|
|
36
|
+
console.log(chalk.bold.cyan('\n🧹 G360 Clean - Limpeza de Proyecto\n'));
|
|
11
37
|
console.log(chalk.gray(`Path: ${targetDir}\n`));
|
|
12
38
|
|
|
13
39
|
if (!fs.existsSync(targetDir)) {
|
|
14
|
-
console.error(chalk.red(`❌ Path
|
|
40
|
+
console.error(chalk.red(`❌ Path no encontrado: ${targetDir}`));
|
|
15
41
|
return;
|
|
16
42
|
}
|
|
17
43
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
44
|
+
const allFiles = await getAllFiles(targetDir);
|
|
45
|
+
const issues = {
|
|
46
|
+
dead: [],
|
|
47
|
+
duplicates: [],
|
|
48
|
+
orphans: [],
|
|
49
|
+
misplaced: [],
|
|
50
|
+
emptyDirs: []
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (dead || all) {
|
|
54
|
+
console.log(chalk.cyan('🔍 Buscando archivos muertos/descontinuados...'));
|
|
55
|
+
issues.dead = findDeadFiles(allFiles);
|
|
56
|
+
console.log(chalk.gray(` Encontrados: ${issues.dead.length}`));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (duplicates || all) {
|
|
60
|
+
console.log(chalk.cyan('🔍 Buscando duplicados...'));
|
|
61
|
+
issues.duplicates = findDuplicateFiles(allFiles);
|
|
62
|
+
console.log(chalk.gray(` Encontrados: ${issues.duplicates.length}`));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (orphans || all) {
|
|
66
|
+
console.log(chalk.cyan('🔍 Buscando archivos huérfanos (sin referencias)...'));
|
|
67
|
+
issues.orphans = await findOrphanFiles(targetDir, allFiles);
|
|
68
|
+
console.log(chalk.gray(` Encontrados: ${issues.orphans.length}`));
|
|
22
69
|
}
|
|
23
70
|
|
|
24
|
-
|
|
71
|
+
if (organize || all) {
|
|
72
|
+
console.log(chalk.cyan('🔍 Analizando organización de componentes...'));
|
|
73
|
+
issues.misplaced = analyzeFileOrganization(allFiles);
|
|
74
|
+
console.log(chalk.gray(` Encontrados: ${issues.misplaced.length}`));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.cyan('🔍 Buscando carpetas vacías...'));
|
|
78
|
+
issues.emptyDirs = findEmptyDirectories(targetDir);
|
|
79
|
+
console.log(chalk.gray(` Encontradas: ${issues.emptyDirs.length}`));
|
|
80
|
+
|
|
81
|
+
const totalIssues = Object.values(issues).reduce((sum, arr) => sum + arr.length, 0);
|
|
25
82
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
83
|
+
if (totalIssues === 0) {
|
|
84
|
+
console.log(chalk.green('\n✅ ¡Proyecto limpio! No se encontraron problemas.\n'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.bold.yellow(`\n⚠️ Problemas encontrados: ${totalIssues}\n`));
|
|
89
|
+
|
|
90
|
+
if (issues.dead.length > 0) {
|
|
91
|
+
console.log(chalk.bold.red('📁 Archivos muertos/descontinuados:'));
|
|
92
|
+
issues.dead.forEach(f => console.log(chalk.gray(` - ${f}`)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (issues.duplicates.length > 0) {
|
|
96
|
+
console.log(chalk.bold.red('📋 Duplicados:'));
|
|
97
|
+
issues.duplicates.forEach(f => console.log(chalk.gray(` - ${f}`)));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (issues.orphans.length > 0) {
|
|
101
|
+
console.log(chalk.bold.yellow('🔗 Archivos huérfanos (sin referencias):'));
|
|
102
|
+
issues.orphans.forEach(f => console.log(chalk.gray(` - ${f}`)));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (issues.misplaced.length > 0) {
|
|
106
|
+
console.log(chalk.bold.cyan('📍 Archivos descolocados (sugerencias):'));
|
|
107
|
+
issues.misplaced.forEach(f => console.log(chalk.gray(` - ${f.file} → mover a ${f.suggested}`)));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (issues.emptyDirs.length > 0) {
|
|
111
|
+
console.log(chalk.bold.magenta('📂 Carpetas vacías:'));
|
|
112
|
+
issues.emptyDirs.forEach(f => console.log(chalk.gray(` - ${f}`)));
|
|
113
|
+
}
|
|
29
114
|
|
|
30
115
|
if (dryRun) {
|
|
31
|
-
console.log(chalk.yellow('📋 DRY RUN -
|
|
116
|
+
console.log(chalk.yellow('\n📋 DRY RUN - Usa --force para aplicar limpieza\n'));
|
|
32
117
|
return;
|
|
33
118
|
}
|
|
34
119
|
|
|
35
120
|
if (!force) {
|
|
36
|
-
console.log(chalk.
|
|
121
|
+
console.log(chalk.yellow('\n⚠️ Usa --force para confirmar limpieza'));
|
|
122
|
+
console.log(chalk.gray('O usa flags específicos: --dead, --duplicates, --orphans, --organize'));
|
|
37
123
|
return;
|
|
38
124
|
}
|
|
39
125
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
126
|
+
console.log(chalk.cyan('\n🚀 Aplicando limpieza...'));
|
|
127
|
+
|
|
128
|
+
let cleaned = 0;
|
|
129
|
+
|
|
130
|
+
for (const file of issues.dead) {
|
|
131
|
+
await fs.remove(path.join(targetDir, file));
|
|
132
|
+
cleaned++;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const file of issues.duplicates) {
|
|
136
|
+
await fs.remove(path.join(targetDir, file));
|
|
137
|
+
cleaned++;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const dir of issues.emptyDirs) {
|
|
141
|
+
await fs.remove(path.join(targetDir, dir));
|
|
142
|
+
cleaned++;
|
|
46
143
|
}
|
|
144
|
+
|
|
145
|
+
console.log(chalk.green(`\n✅ Limpieza completada: ${cleaned} elementos eliminados\n`));
|
|
47
146
|
}
|
|
48
147
|
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
|
|
148
|
+
function findDeadFiles(files) {
|
|
149
|
+
const dead = [];
|
|
150
|
+
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
const lower = file.toLowerCase();
|
|
153
|
+
const basename = path.basename(file).toLowerCase();
|
|
154
|
+
|
|
155
|
+
if (DEAD_PATTERNS.some(p => lower.includes(p))) {
|
|
156
|
+
dead.push(file);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ext = path.extname(file);
|
|
161
|
+
if (DUPLICATE_EXTENSIONS.includes(ext)) {
|
|
162
|
+
dead.push(file);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
52
165
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
166
|
+
return dead;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function findDuplicateFiles(files) {
|
|
170
|
+
const byName = {};
|
|
171
|
+
const duplicates = [];
|
|
172
|
+
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const name = path.basename(file).toLowerCase();
|
|
175
|
+
const ext = path.extname(file);
|
|
176
|
+
const baseName = name.replace(ext, '');
|
|
56
177
|
|
|
57
|
-
|
|
58
|
-
|
|
178
|
+
const key = `${baseName}${ext}`;
|
|
179
|
+
if (!byName[key]) {
|
|
180
|
+
byName[key] = [];
|
|
181
|
+
}
|
|
182
|
+
byName[key].push(file);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const [key, paths] of Object.entries(byName)) {
|
|
186
|
+
if (paths.length > 1) {
|
|
187
|
+
const isCommonFile = ['index.js', 'index.jsx', 'index.ts', 'index.tsx'].includes(key.toLowerCase());
|
|
188
|
+
if (!isCommonFile) {
|
|
189
|
+
paths.slice(1).forEach(p => duplicates.push(p));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return duplicates;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function findOrphanFiles(projectDir, allFiles) {
|
|
198
|
+
const orphans = [];
|
|
199
|
+
const sourceFiles = allFiles.filter(f => {
|
|
200
|
+
const ext = path.extname(f);
|
|
201
|
+
return ['.js', '.jsx', '.ts', '.tsx', '.svelte', '.vue'].includes(ext);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const referencedFiles = new Set();
|
|
205
|
+
|
|
206
|
+
for (const file of sourceFiles) {
|
|
207
|
+
const fullPath = path.join(projectDir, file);
|
|
208
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
212
|
+
|
|
213
|
+
const importRegex = /import\s+.*?from\s+['"](.+?)['"]/g;
|
|
214
|
+
let match;
|
|
215
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
216
|
+
const imported = match[1].replace(/^\.\/|\.\.\//g, '');
|
|
217
|
+
const importedPath = path.join(path.dirname(file), imported);
|
|
218
|
+
|
|
219
|
+
for (const f of allFiles) {
|
|
220
|
+
if (f.includes(imported) || f.includes(imported + '.js') || f.includes(imported + '.jsx')) {
|
|
221
|
+
referencedFiles.add(f);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const components = sourceFiles.filter(f => {
|
|
229
|
+
const name = path.basename(f);
|
|
230
|
+
return /^[A-Z][a-zA-Z]*\.jsx?$/.test(name) ||
|
|
231
|
+
/^[A-Z][a-zA-Z]*\.tsx?$/.test(name) ||
|
|
232
|
+
/^[A-Z][a-zA-Z]*\.svelte$/.test(name) ||
|
|
233
|
+
/^[A-Z][a-zA-Z]*\.vue$/.test(name);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
for (const component of components) {
|
|
237
|
+
if (!referencedFiles.has(component)) {
|
|
238
|
+
orphans.push(component);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return orphans;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function analyzeFileOrganization(files) {
|
|
246
|
+
const misplaced = [];
|
|
247
|
+
const structureRules = {
|
|
248
|
+
'src/components': ['.js', '.jsx', '.ts', '.tsx', '.svelte', '.vue'],
|
|
249
|
+
'src/pages': ['.js', '.jsx', '.ts', '.tsx', '.svelte', '.vue'],
|
|
250
|
+
'src/utils': ['.js', '.ts'],
|
|
251
|
+
'src/hooks': ['.js', '.ts'],
|
|
252
|
+
'src/services': ['.js', '.ts'],
|
|
253
|
+
'src/styles': ['.css', '.scss', '.less'],
|
|
254
|
+
'src/assets': ['.png', '.jpg', '.svg', '.gif', '.webp']
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const components = files.filter(f => {
|
|
258
|
+
const name = path.basename(f);
|
|
259
|
+
return /^[A-Z][a-zA-Z]*\.jsx?$/.test(name) ||
|
|
260
|
+
/^[A-Z][a-zA-Z]*\.tsx?$/.test(name);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
for (const comp of components) {
|
|
264
|
+
const dir = path.dirname(comp);
|
|
265
|
+
const parentDir = path.basename(dir);
|
|
266
|
+
|
|
267
|
+
if (!dir.includes('src/components') && !dir.includes('component')) {
|
|
268
|
+
misplaced.push({
|
|
269
|
+
file: comp,
|
|
270
|
+
suggested: 'src/components'
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return misplaced;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function findEmptyDirectories(dir, baseDir = dir) {
|
|
279
|
+
const empty = [];
|
|
280
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
281
|
+
|
|
282
|
+
for (const item of items) {
|
|
283
|
+
if (!item.isDirectory()) continue;
|
|
284
|
+
if (item.name === 'node_modules' || item.name === '.git') continue;
|
|
285
|
+
|
|
286
|
+
const fullPath = path.join(dir, item.name);
|
|
287
|
+
const subItems = fs.readdirSync(fullPath);
|
|
288
|
+
|
|
289
|
+
if (subItems.length === 0) {
|
|
290
|
+
empty.push(path.relative(baseDir, fullPath));
|
|
59
291
|
} else {
|
|
60
|
-
|
|
292
|
+
empty.push(...findEmptyDirectories(fullPath, baseDir));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return empty;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function getAllFiles(dir, baseDir = dir) {
|
|
300
|
+
const files = [];
|
|
301
|
+
const ignoreDirs = ['node_modules', '.git', 'dist', 'build', '.next', 'out', '.nuxt', 'coverage', '.cache', '.svelte-kit'];
|
|
302
|
+
|
|
303
|
+
if (!fs.existsSync(dir)) return files;
|
|
304
|
+
|
|
305
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
306
|
+
|
|
307
|
+
for (const item of items) {
|
|
308
|
+
if (ignoreDirs.includes(item.name)) continue;
|
|
309
|
+
|
|
310
|
+
const fullPath = path.join(dir, item.name);
|
|
311
|
+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
312
|
+
|
|
313
|
+
if (item.isDirectory()) {
|
|
314
|
+
files.push(...await getAllFiles(fullPath, baseDir));
|
|
315
|
+
} else {
|
|
316
|
+
files.push(relativePath);
|
|
61
317
|
}
|
|
62
318
|
}
|
|
63
319
|
|
|
64
|
-
return
|
|
320
|
+
return files;
|
|
65
321
|
}
|
|
322
|
+
|
|
323
|
+
export default { clean };
|