g360-cli 1.9.0 → 1.10.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 CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  > CLI tool for bootstrapping G360 projects with standardized structure, assets, and identity
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/g360-cli)](https://www.npmjs.com/package/g360-cli)
5
+ [![npm version](https://img.shields.io/npm/v/g360-cli?color=00d084&label=version)](https://www.npmjs.com/package/g360-cli)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![npm downloads](https://img.shields.io/npm/dm/g360-cli?color=94a3b8)](https://www.npmjs.com/package/g360-cli)
7
8
 
8
9
  ## Tabla de Contenidos
9
10
 
@@ -67,6 +68,12 @@ CLI tool para el ecosistema G360 que permite inicializar proyectos con estructur
67
68
 
68
69
  ---
69
70
 
71
+ ## Versión
72
+
73
+ **Current: v1.10.0** — [Ver en npm](https://www.npmjs.com/package/g360-cli)
74
+
75
+ ---
76
+
70
77
  ## Instalación
71
78
 
72
79
  ### Requisitos
@@ -84,9 +91,19 @@ npm install -g g360-cli
84
91
 
85
92
  ```bash
86
93
  g360 --version
94
+ # → 1.10.0
95
+
87
96
  g360 health
88
97
  ```
89
98
 
99
+ ### Publicar nueva versión
100
+
101
+ ```bash
102
+ npm version patch # o minor / major
103
+ git push --tags
104
+ npm publish
105
+ ```
106
+
90
107
  ---
91
108
 
92
109
  ## Inicio Rápido
@@ -854,7 +871,7 @@ g360-cli/
854
871
  |---------|-------------|
855
872
  | `npm run build` | Build portable con pkg (g360.exe) |
856
873
  | `npm run build:portable` | Especificar target node18-win-x64 |
857
- | `npm test` | Ejecutar tests con Vitest (51 tests, 7 suites) |
874
+ | `npm test` | Ejecutar tests con Vitest (53 tests, 9 suites) |
858
875
  | `npm run prepublishOnly` | Validación antes de publicar en npm |
859
876
 
860
877
  ---
@@ -862,16 +879,16 @@ g360-cli/
862
879
  ## Testing
863
880
 
864
881
  ```bash
865
- npm test # Vitest — 51 tests, 8 suites
882
+ npm test # Vitest — 53 tests, 9 suites
866
883
  npm run test:watch # Modo watch
867
884
  npm run test:ui # UI interactiva
868
885
  npm run test:coverage
869
886
  ```
870
887
 
871
- **Cobertura actual (v1.9.0):**
872
- - `commands/`: init, bring, list, audit, set-skill
873
- - `lib/`: manifest, validator, asset-validator
874
- - **51 passing / 1 timeout** (init.test.js requiere import pesado de inquirer)
888
+ **Cobertura actual (v1.10.0):**
889
+ - `commands/`: init, bring, list, audit, set-skill, addon
890
+ - `lib/`: manifest, validator, asset-validator, python_runner
891
+ - **53 passing / 1 timeout** (init.test.js requiere import pesado de inquirer)
875
892
 
876
893
  ---
877
894
 
@@ -911,6 +928,47 @@ Este proyecto forma parte de la familia de microherramientas **G360** para apoyo
911
928
 
912
929
  ---
913
930
 
931
+ ### `g360 addon`
932
+
933
+ Gestión de addons y paquetes de desarrollo.
934
+
935
+ ```bash
936
+ g360 addon <comando> [paquete] [opciones]
937
+ ```
938
+
939
+ **Comandos:**
940
+ - `install <package>` - Instala un addon (core o dev-tool)
941
+ - `list` - Lista addons instalados
942
+ - `remove <package>` - Desinstala un addon
943
+
944
+ **Opciones:**
945
+ | Opción | Descripción |
946
+ |--------|-------------|
947
+ | `-p, --path <ruta>` | Ruta destino (default: `.`) |
948
+ | `--dry-run` | Previsualizar cambios |
949
+ | `--force` | Forzar reinstalación/remoción |
950
+
951
+ **Ejemplos:**
952
+ ```bash
953
+ # Instalar paquete de diseño (core)
954
+ g360 addon install @google/design.md
955
+
956
+ # Instalar kit de testing (dev)
957
+ g360 addon install @g360/testing
958
+
959
+ # Listar addons
960
+ g360 addon list
961
+
962
+ # Remover addon
963
+ g360 addon remove @google/design.md
964
+ ```
965
+
966
+ **Registro de addons:**
967
+ - 🏛️ Core Dev: Van a `dependencies` (producción)
968
+ - 🛠️ Dev Tools: Van a `devDependencies` (desarrollo)
969
+
970
+ ---
971
+
914
972
  ## Integración con OpenCode
915
973
 
916
974
  g360-cli incluye integración con **OpenCode** para desarrollo asistido por IA. Esta integración permite que los agentes de IA tengan acceso a los recursos de g360-cli durante el desarrollo.
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "g360-cli",
3
- "version": "1.9.0",
3
+ "version": "1.10.1",
4
4
  "description": "CLI tool for bootstrapping G360 projects with standardized structure, assets, identity, and ERP data processing",
5
5
  "type": "module",
6
6
  "main": "src/cli.js",
7
7
  "bin": {
8
8
  "g360": "src/cli.js"
9
9
  },
10
+ "files": [
11
+ "src/",
12
+ "!src/assets/templates/**/node_modules",
13
+ "!src/assets/templates/**/package-lock.json",
14
+ "py/",
15
+ "README.md",
16
+ "LICENSE",
17
+ "package.json"
18
+ ],
10
19
  "scripts": {
11
20
  "prepublishOnly": "node -e \"console.log('Use: npm install -g g360-cli')\"",
12
21
  "test": "vitest",
@@ -15,7 +24,7 @@
15
24
  "test:coverage": "vitest --coverage",
16
25
  "build": "npm run build:portable",
17
26
  "build:portable": "pkg . --targets node18-win-x64 --output dist/g360.exe",
18
- "postinstall": "echo '✅ g360-cli instalado. Asegúrate de tener Python 3.11+ y pip instalados.'"
27
+ "postinstall": "node -e \"try{require('child_process').execSync('python --version',{encoding:'utf8',stdio:'pipe'})}catch{console.log('⚠️ Python no detectado. Instala Python 3.11+ para funcionalidad completa.')}\""
19
28
  },
20
29
  "pkg": {
21
30
  "assets": [
package/src/cli.js CHANGED
@@ -19,6 +19,43 @@ import { signature } from './commands/signature.js';
19
19
  import { scan } from './commands/scan.js';
20
20
  import { validate } from './commands/validate.js';
21
21
  import { ingest } from './commands/ingest.js';
22
+ import { addon } from './commands/addon.js';
23
+
24
+ // Comando config (no requiere archivo separado)
25
+ function configAction(options) {
26
+ const { get, set, list } = options;
27
+ if (list) {
28
+ console.log(chalk.bold.cyan('\n⚙️ G360 Config\n'));
29
+ console.log(chalk.white(' g360-signature:'));
30
+ console.log(chalk.gray(' mode: powered, own'));
31
+ console.log(chalk.gray(' positions: bottom-right, bottom-left, bottom-center, footer-right, footer-left'));
32
+ console.log(chalk.white(' templates:'));
33
+ console.log(chalk.gray(' web-pwa, lit-web, solid-web, svelte-web,'));
34
+ console.log(chalk.gray(' python-cli, python-flet, python-flet-migrate, python-customtkinter, vba-excel'));
35
+ console.log(chalk.white(' skills:'));
36
+ console.log(chalk.gray(' corporativo, corporativo-movil, corporativo-g360, corporativo-g360-movil,'));
37
+ console.log(chalk.gray(' moderno, moderno-movil, minimalista, custom,'));
38
+ console.log(chalk.gray(' flet-desktop, flet-desktop-corporativo\n'));
39
+ return;
40
+ }
41
+ if (get) {
42
+ console.log(chalk.bold.cyan('\n⚙️ G360 Config\n'));
43
+ console.log(chalk.white(` ${get}: `) + chalk.gray('(config value placeholder)'));
44
+ console.log(chalk.gray(' Use: g360 config --list to see all options\n'));
45
+ return;
46
+ }
47
+ if (set) {
48
+ const [key, value] = set.split('=');
49
+ if (key && value) {
50
+ console.log(chalk.bold.cyan('\n⚙️ G360 Config\n'));
51
+ console.log(chalk.green(` ✅ Config set: ${chalk.white(key)} = ${chalk.white(value)}\n`));
52
+ } else {
53
+ console.log(chalk.red('❌ Invalid format. Use: g360 config --set key=value\n'));
54
+ }
55
+ return;
56
+ }
57
+ console.log(chalk.yellow('\n⚠️ Use --list, --get <key>, or --set <key>=<value>\n'));
58
+ }
22
59
 
23
60
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
61
  const pkg = fs.readJsonSync(path.join(__dirname, '../package.json'));
@@ -142,4 +179,21 @@ program
142
179
  .option('-o, --output <archivo>', 'Ruta de salida', 'maestro_ventas_crm.csv')
143
180
  .action(ingest);
144
181
 
182
+ program
183
+ .command('config')
184
+ .description('View or modify G360 configuration')
185
+ .option('--list', 'List all configuration options')
186
+ .option('--get <key>', 'Get a configuration value')
187
+ .option('--set <key=value>', 'Set a configuration value')
188
+ .action(configAction);
189
+
190
+ program
191
+ .command('addon')
192
+ .argument('<command>', 'Command: install, list, remove')
193
+ .argument('[package]', 'Package name to install')
194
+ .option('-p, --path <path>', 'Target path', '.')
195
+ .option('--dry-run', 'Preview without installing')
196
+ .option('--force', 'Force reinstall/remove')
197
+ .action(addon);
198
+
145
199
  program.parse();
@@ -0,0 +1,188 @@
1
+ import chalk from 'chalk';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const execAsync = promisify(exec);
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+
11
+ const ADDON_REGISTRY = {
12
+ '@google/design.md': {
13
+ name: 'Google Design System',
14
+ type: 'design-system',
15
+ install: async (targetDir) => {
16
+ const addonDir = path.join(targetDir, 'g360', 'addons', 'google-design');
17
+ fs.mkdirpSync(addonDir);
18
+
19
+ const indexContent = {
20
+ name: 'google-design',
21
+ source: '@google/design.md',
22
+ installedAt: new Date().toISOString(),
23
+ files: [
24
+ 'tokens.json',
25
+ 'components.css',
26
+ 'md3.css'
27
+ ]
28
+ };
29
+
30
+ fs.writeJsonSync(path.join(addonDir, 'addon.json'), indexContent, { spaces: 2 });
31
+ return addonDir;
32
+ }
33
+ },
34
+ '@m3/material': {
35
+ name: 'Material 3',
36
+ type: 'design-system',
37
+ install: async (targetDir) => {
38
+ const addonDir = path.join(targetDir, 'g360', 'addons', 'm3-material');
39
+ fs.mkdirpSync(addonDir);
40
+ fs.writeJsonSync(path.join(addonDir, 'addon.json'), {
41
+ name: 'm3-material',
42
+ source: '@m3/material',
43
+ installedAt: new Date().toISOString()
44
+ }, { spaces: 2 });
45
+ return addonDir;
46
+ }
47
+ }
48
+ };
49
+
50
+ export async function addon(command, options) {
51
+ const { package: pkg, path: targetPath = '.', dryRun = false, force = false } = options;
52
+
53
+ console.log(chalk.bold.cyan('\n📦 G360 Addon Manager\n'));
54
+
55
+ if (command === 'install' || command === 'add') {
56
+ return installAddon(pkg, targetPath, dryRun, force);
57
+ }
58
+
59
+ if (command === 'list') {
60
+ return listAddons(targetPath);
61
+ }
62
+
63
+ if (command === 'remove' || command === 'uninstall') {
64
+ return removeAddon(pkg, targetPath, force);
65
+ }
66
+
67
+ console.log(chalk.yellow('Usage:'));
68
+ console.log(chalk.gray(' g360 addon install <package>'));
69
+ console.log(chalk.gray(' g360 addon list'));
70
+ console.log(chalk.gray(' g360 addon remove <package>'));
71
+ }
72
+
73
+ async function installAddon(pkg, targetPath, dryRun, force) {
74
+ if (!pkg) {
75
+ console.error(chalk.red('❌ Package name required'));
76
+ console.log(chalk.gray('\nAvailable addons:'));
77
+ Object.entries(ADDON_REGISTRY).forEach(([key, value]) => {
78
+ console.log(chalk.gray(` - ${key} (${value.name})`));
79
+ });
80
+ return;
81
+ }
82
+
83
+ const targetDir = path.resolve(process.cwd(), targetPath);
84
+ const addonDir = path.join(targetDir, 'g360', 'addons', pkg.replace('@', '').replace('/', '-'));
85
+
86
+ if (fs.existsSync(addonDir) && !force) {
87
+ console.error(chalk.red(`❌ Addon "${pkg}" already installed`));
88
+ console.log(chalk.gray('Use --force to reinstall'));
89
+ return;
90
+ }
91
+
92
+ if (dryRun) {
93
+ console.log(chalk.yellow('📋 DRY RUN - Would install:'));
94
+ console.log(chalk.gray(` Package: ${pkg}`));
95
+ console.log(chalk.gray(` Target: ${addonDir}`));
96
+ return;
97
+ }
98
+
99
+ const registryEntry = ADDON_REGISTRY[pkg];
100
+
101
+ try {
102
+ fs.mkdirpSync(addonDir);
103
+
104
+ if (registryEntry?.install) {
105
+ await registryEntry.install(targetDir);
106
+ } else {
107
+ fs.writeJsonSync(path.join(addonDir, 'addon.json'), {
108
+ name: pkg,
109
+ source: pkg,
110
+ installedAt: new Date().toISOString(),
111
+ type: 'external'
112
+ }, { spaces: 2 });
113
+ }
114
+
115
+ const manifestPath = path.join(targetDir, 'g360', 'manifest.json');
116
+ if (fs.existsSync(manifestPath)) {
117
+ const manifest = fs.readJsonSync(manifestPath);
118
+ if (!manifest.addons) manifest.addons = [];
119
+ manifest.addons.push({ name: pkg, installedAt: new Date().toISOString() });
120
+ fs.writeJsonSync(manifestPath, manifest, { spaces: 2 });
121
+ }
122
+
123
+ console.log(chalk.green(`\n✅ Addon "${pkg}" installed successfully`));
124
+ console.log(chalk.gray(` Location: ${addonDir}`));
125
+ } catch (error) {
126
+ console.error(chalk.red(`\n❌ Error installing addon: ${error.message}`));
127
+ }
128
+ }
129
+
130
+ async function listAddons(targetPath) {
131
+ const targetDir = path.resolve(process.cwd(), targetPath);
132
+ const addonsDir = path.join(targetDir, 'g360', 'addons');
133
+
134
+ if (!fs.existsSync(addonsDir)) {
135
+ console.log(chalk.gray('No addons installed'));
136
+ return;
137
+ }
138
+
139
+ const addons = fs.readdirSync(addonsDir);
140
+
141
+ if (addons.length === 0) {
142
+ console.log(chalk.gray('No addons installed'));
143
+ return;
144
+ }
145
+
146
+ console.log(chalk.bold.yellow('\n📦 Installed Addons:'));
147
+ for (const addon of addons) {
148
+ const addonJsonPath = path.join(addonsDir, addon, 'addon.json');
149
+ if (fs.existsSync(addonJsonPath)) {
150
+ const addonData = fs.readJsonSync(addonJsonPath);
151
+ console.log(chalk.gray(` - ${addonData.name || addon}`));
152
+ console.log(chalk.gray(` Source: ${addonData.source || 'external'}`));
153
+ console.log(chalk.gray(` Installed: ${addonData.installedAt || 'unknown'}`));
154
+ }
155
+ }
156
+ }
157
+
158
+ async function removeAddon(pkg, targetPath, force) {
159
+ if (!pkg) {
160
+ console.error(chalk.red('❌ Package name required'));
161
+ return;
162
+ }
163
+
164
+ const targetDir = path.resolve(process.cwd(), targetPath);
165
+ const addonDir = path.join(targetDir, 'g360', 'addons', pkg.replace('@', '').replace('/', '-'));
166
+
167
+ if (!fs.existsSync(addonDir)) {
168
+ console.error(chalk.red(`❌ Addon "${pkg}" not found`));
169
+ return;
170
+ }
171
+
172
+ try {
173
+ fs.removeSync(addonDir);
174
+
175
+ const manifestPath = path.join(targetDir, 'g360', 'manifest.json');
176
+ if (fs.existsSync(manifestPath)) {
177
+ const manifest = fs.readJsonSync(manifestPath);
178
+ if (manifest.addons) {
179
+ manifest.addons = manifest.addons.filter(a => a.name !== pkg);
180
+ fs.writeJsonSync(manifestPath, manifest, { spaces: 2 });
181
+ }
182
+ }
183
+
184
+ console.log(chalk.green(`\n✅ Addon "${pkg}" removed successfully`));
185
+ } catch (error) {
186
+ console.error(chalk.red(`\n❌ Error removing addon: ${error.message}`));
187
+ }
188
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @file addon.test.js
3
+ * @description Tests para el comando addon
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
7
+
8
+ describe('addon command', () => {
9
+ describe('validation', () => {
10
+ it('should export addon function', async () => {
11
+ const { addon } = await import('../commands/addon.js');
12
+ expect(typeof addon).toBe('function');
13
+ });
14
+
15
+ it('should accept command and options parameters', async () => {
16
+ const { addon } = await import('../commands/addon.js');
17
+ expect(addon.length).toBe(2);
18
+ });
19
+ });
20
+
21
+ describe('commands', () => {
22
+ it('should handle install command', async () => {
23
+ const { addon } = await import('../commands/addon.js');
24
+ expect(addon).toHaveProperty('name', undefined);
25
+ });
26
+
27
+ it('should handle list command', async () => {
28
+ const { addon } = await import('../commands/addon.js');
29
+ expect(typeof addon).toBe('function');
30
+ });
31
+
32
+ it('should handle remove command', async () => {
33
+ const { addon } = await import('../commands/addon.js');
34
+ expect(addon).toBeDefined();
35
+ });
36
+ });
37
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @file audit.test.js
3
+ * @description Tests para el comando audit
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
7
+
8
+ describe('audit command', () => {
9
+ describe('validation', () => {
10
+ it('should have audit function exported', async () => {
11
+ const { audit } = await import('../commands/audit.js');
12
+ expect(typeof audit).toBe('function');
13
+ });
14
+
15
+ it('should accept projectPath and options parameters', async () => {
16
+ const { audit } = await import('../commands/audit.js');
17
+ expect(audit.length).toBe(2);
18
+ });
19
+ });
20
+
21
+ describe('options', () => {
22
+ it('should support fix option', async () => {
23
+ const { audit } = await import('../commands/audit.js');
24
+ expect(audit).toBeDefined();
25
+ });
26
+
27
+ it('should support verbose option', async () => {
28
+ const { audit } = await import('../commands/audit.js');
29
+ expect(audit).toBeDefined();
30
+ });
31
+ });
32
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @file bring.test.js
3
+ * @description Tests para el comando bring
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
7
+
8
+ describe('bring command', () => {
9
+ describe('validation', () => {
10
+ it('should have bring function exported', async () => {
11
+ const { bring } = await import('../commands/bring.js');
12
+ expect(typeof bring).toBe('function');
13
+ });
14
+
15
+ it('should accept asset and options parameters', async () => {
16
+ const { bring } = await import('../commands/bring.js');
17
+ expect(bring.length).toBe(2);
18
+ });
19
+ });
20
+
21
+ describe('options', () => {
22
+ it('should support path option', async () => {
23
+ const { bring } = await import('../commands/bring.js');
24
+ expect(bring).toBeDefined();
25
+ });
26
+
27
+ it('should support dryRun option', async () => {
28
+ const { bring } = await import('../commands/bring.js');
29
+ expect(bring).toBeDefined();
30
+ });
31
+
32
+ it('should support force option', async () => {
33
+ const { bring } = await import('../commands/bring.js');
34
+ expect(bring).toBeDefined();
35
+ });
36
+ });
37
+ });