g360-cli 1.2.2 → 1.4.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 +182 -3
- package/dist/g360.exe +0 -0
- package/package.json +8 -8
- package/src/assets/config/g360-skills.json +52 -0
- package/src/assets/config/project-types.json +16 -0
- package/src/assets/config/skills.json +12 -0
- package/src/assets/snippets/snippets.json +77 -0
- package/src/assets/templates/python-cli/build-portable.bat +33 -0
- package/src/assets/templates/python-cli/src/main.py +107 -3
- package/src/assets/templates/python-customtkinter/README.md +47 -0
- package/src/assets/templates/python-customtkinter/build-portable.bat +28 -0
- package/src/assets/templates/python-customtkinter/requirements.txt +1 -0
- package/src/assets/templates/python-customtkinter/run.bat +29 -0
- package/src/assets/templates/python-customtkinter/src/core/skill.json +22 -0
- package/src/assets/templates/python-customtkinter/src/main.py +123 -0
- package/src/assets/templates/python-flet/README.md +130 -0
- package/src/assets/templates/python-flet/build-portable.bat +42 -0
- package/src/assets/templates/python-flet/create_shortcut.vbs +35 -0
- package/src/assets/templates/python-flet/requirements.txt +3 -0
- package/src/assets/templates/python-flet/run.bat +81 -0
- package/src/assets/templates/python-flet/src/core/skill.json +21 -0
- package/src/assets/templates/python-flet/src/main.py +101 -0
- package/src/assets/templates/python-flet/src/test_app.py +103 -0
- package/src/assets/templates/python-flet-migrate/src/main.py +68 -0
- package/src/assets/templates/python-flet-migrate/src/migrate_tkinter.py +56 -0
- package/src/cli.js +12 -2
- package/src/commands/clean.js +51 -4
- package/src/commands/init.js +92 -8
- package/src/commands/signature.js +12 -8
- package/src/lib/auditor.js +39 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests para la aplicación G360 Flet
|
|
3
|
+
Usa: pytest test_app.py -v
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import unittest
|
|
7
|
+
from unittest.mock import Mock, patch
|
|
8
|
+
import flet as ft
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestG360App(unittest.TestCase):
|
|
12
|
+
"""Tests básicos para la aplicación Flet"""
|
|
13
|
+
|
|
14
|
+
def setUp(self):
|
|
15
|
+
self.mock_page = Mock(spec=ft.Page)
|
|
16
|
+
|
|
17
|
+
def test_page_theme_setup(self):
|
|
18
|
+
"""Verifica que la página se configura con el tema correcto"""
|
|
19
|
+
self.mock_page.title = "G360 Desktop App"
|
|
20
|
+
self.mock_page.theme_mode = ft.ThemeMode.DARK
|
|
21
|
+
self.mock_page.bgcolor = "#0b1220"
|
|
22
|
+
|
|
23
|
+
self.assertEqual(self.mock_page.title, "G360 Desktop App")
|
|
24
|
+
self.assertEqual(self.mock_page.theme_mode, ft.ThemeMode.DARK)
|
|
25
|
+
self.assertEqual(self.mock_page.bgcolor, "#0b1220")
|
|
26
|
+
|
|
27
|
+
def test_colors_g360(self):
|
|
28
|
+
"""Verifica los colores del tema G360"""
|
|
29
|
+
colors = {
|
|
30
|
+
"bg": "#0b1220",
|
|
31
|
+
"surface": "#151e2e",
|
|
32
|
+
"accent": "#00d084",
|
|
33
|
+
"text": "#f0f4f8",
|
|
34
|
+
"muted": "#94a3b8"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for key, value in colors.items():
|
|
38
|
+
self.assertIsNotNone(value)
|
|
39
|
+
|
|
40
|
+
def test_button_styles(self):
|
|
41
|
+
"""Verifica estilos de botones G360"""
|
|
42
|
+
button = ft.ElevatedButton(
|
|
43
|
+
text="Test",
|
|
44
|
+
bgcolor="#00d084",
|
|
45
|
+
color="#0b1220"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
self.assertIsNotNone(button)
|
|
49
|
+
|
|
50
|
+
def test_container_styles(self):
|
|
51
|
+
"""Verifica estilos de contenedores G360"""
|
|
52
|
+
container = ft.Container(
|
|
53
|
+
bgcolor="#151e2e",
|
|
54
|
+
border_radius=12,
|
|
55
|
+
padding=20
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self.assertIsNotNone(container)
|
|
59
|
+
self.assertEqual(container.bgcolor, "#151e2e")
|
|
60
|
+
self.assertEqual(container.border_radius, 12)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestMigration(unittest.TestCase):
|
|
64
|
+
"""Tests para migración desde tkinter/ctkinter"""
|
|
65
|
+
|
|
66
|
+
def test_convert_tkinter_widgets(self):
|
|
67
|
+
"""Verifica mapeo de widgets tkinter a Flet"""
|
|
68
|
+
mapping = {
|
|
69
|
+
'tk.Label': 'ft.Text',
|
|
70
|
+
'tk.Button': 'ft.ElevatedButton',
|
|
71
|
+
'tk.Entry': 'ft.TextField',
|
|
72
|
+
'tk.Frame': 'ft.Container',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for tk, flet in mapping.items():
|
|
76
|
+
self.assertIsNotNone(tk)
|
|
77
|
+
self.assertIsNotNone(flet)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestCalculations(unittest.TestCase):
|
|
81
|
+
"""Tests para cálculos y procesamiento de datos"""
|
|
82
|
+
|
|
83
|
+
def test_sum_basic(self):
|
|
84
|
+
"""Test básico de suma"""
|
|
85
|
+
result = 2 + 2
|
|
86
|
+
self.assertEqual(result, 4)
|
|
87
|
+
|
|
88
|
+
def test_list_operations(self):
|
|
89
|
+
"""Test operaciones con listas"""
|
|
90
|
+
items = [1, 2, 3, 4, 5]
|
|
91
|
+
self.assertEqual(sum(items), 15)
|
|
92
|
+
self.assertEqual(len(items), 5)
|
|
93
|
+
self.assertEqual(max(items), 5)
|
|
94
|
+
|
|
95
|
+
def test_dict_operations(self):
|
|
96
|
+
"""Test operaciones con diccionarios"""
|
|
97
|
+
data = {"sku": "ABC001", "cantidad": 10, "precio": 50.0}
|
|
98
|
+
self.assertIn("sku", data)
|
|
99
|
+
self.assertEqual(data["cantidad"] * data["precio"], 500.0)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == '__main__':
|
|
103
|
+
unittest.main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MigratedApp:
|
|
5
|
+
def __init__(self, page: ft.Page):
|
|
6
|
+
self.page = page
|
|
7
|
+
self.setup_theme()
|
|
8
|
+
self.create_ui()
|
|
9
|
+
|
|
10
|
+
def setup_theme(self):
|
|
11
|
+
self.page.title = "G360 - Migrated App"
|
|
12
|
+
self.page.theme_mode = ft.ThemeMode.DARK
|
|
13
|
+
self.page.padding = 20
|
|
14
|
+
self.page.bgcolor = "#0b1220"
|
|
15
|
+
|
|
16
|
+
def create_ui(self):
|
|
17
|
+
self.page.add(
|
|
18
|
+
ft.Column([
|
|
19
|
+
self.create_header(),
|
|
20
|
+
self.create_main_content(),
|
|
21
|
+
], spacing=20, expand=True)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def create_header(self):
|
|
25
|
+
return ft.Container(
|
|
26
|
+
content=ft.Row([
|
|
27
|
+
ft.Text("G360 Migrated App", size=28, weight=ft.FontWeight.BOLD, color="#00d084"),
|
|
28
|
+
ft.Container(expand=True),
|
|
29
|
+
ft.Text("from tkinter/ctkinter", size=14, color="#94a3b8"),
|
|
30
|
+
]),
|
|
31
|
+
padding=20,
|
|
32
|
+
bgcolor="#151e2e",
|
|
33
|
+
border_radius=12
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def create_main_content(self):
|
|
37
|
+
return ft.Container(
|
|
38
|
+
content=ft.Column([
|
|
39
|
+
ft.Text("Panel de Controles (antes tk.Button)", size=20, color="#f0f4f8"),
|
|
40
|
+
ft.Row([
|
|
41
|
+
ft.ElevatedButton("Aceptar", bgcolor="#00d084", color="#0b1220"),
|
|
42
|
+
ft.ElevatedButton("Cancelar", bgcolor="#ef4444", color="#ffffff"),
|
|
43
|
+
ft.ElevatedButton("Guardar", bgcolor="#00796B", color="#ffffff"),
|
|
44
|
+
], spacing=15),
|
|
45
|
+
ft.Container(height=30),
|
|
46
|
+
ft.Text("Campos de Entrada (antes tk.Entry)", size=20, color="#f0f4f8"),
|
|
47
|
+
ft.Column([
|
|
48
|
+
ft.TextField(label="Usuario", hint_text="Ingrese usuario", width=300),
|
|
49
|
+
ft.TextField(label="Contraseña", hint_text="Ingrese contraseña", password=True, width=300),
|
|
50
|
+
], spacing=15),
|
|
51
|
+
ft.Container(height=30),
|
|
52
|
+
ft.Text("Casillas y Opciones (antes tk.Checkbutton/tk.Radiobutton)", size=20, color="#f0f4f8"),
|
|
53
|
+
ft.Column([
|
|
54
|
+
ft.Checkbox(label="Recordarme"),
|
|
55
|
+
ft.Checkbox(label="Aceptar términos"),
|
|
56
|
+
], spacing=10),
|
|
57
|
+
], scroll=ft.ScrollMode.AUTO),
|
|
58
|
+
expand=True,
|
|
59
|
+
padding=20
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main(page: ft.Page):
|
|
64
|
+
MigratedApp(page)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
ft.app(target=main)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migración de tkinter/ctkinter a Flet
|
|
3
|
+
Script de ayuda para convertir código existente
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
MAPPING = {
|
|
7
|
+
'tk.Label': 'ft.Text',
|
|
8
|
+
'tk.Button': 'ft.ElevatedButton',
|
|
9
|
+
'tk.Entry': 'ft.TextField',
|
|
10
|
+
'tk.Checkbutton': 'ft.Checkbox',
|
|
11
|
+
'tk.Radiobutton': 'ft.Radio',
|
|
12
|
+
'tk.Listbox': 'ft.ListView',
|
|
13
|
+
'tk.Frame': 'ft.Container',
|
|
14
|
+
'tk.LabelFrame': 'ft.Container with border',
|
|
15
|
+
'tk.Canvas': 'ft.Canvas',
|
|
16
|
+
'tk.Menu': 'ft.AppBar with actions',
|
|
17
|
+
'ctk.CTkButton': 'ft.ElevatedButton',
|
|
18
|
+
'ctk.CTkLabel': 'ft.Text',
|
|
19
|
+
'ctk.CTkEntry': 'ft.TextField',
|
|
20
|
+
'ctk.CTkFrame': 'ft.Container',
|
|
21
|
+
'ctk.CTkCheckBox': 'ft.Checkbox',
|
|
22
|
+
'ctk.CTkRadioButton': 'ft.Radio',
|
|
23
|
+
'ctk.CTkProgressBar': 'ft.ProgressBar',
|
|
24
|
+
'ctk.CTkSlider': 'ft.Slider',
|
|
25
|
+
'ctk.CTkSwitch': 'ft.Switch',
|
|
26
|
+
'ctk.CTkComboBox': 'ft.Dropdown',
|
|
27
|
+
'ctk.CTkTextbox': 'ft.TextField multiline',
|
|
28
|
+
'ctk.CTkTabview': 'ft.Tabs',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
LAYOUT_MAPPING = {
|
|
32
|
+
'pack()': 'page.add()',
|
|
33
|
+
'pack(fill=..., expand=...)': 'expand=True, alignment=...',
|
|
34
|
+
'grid(row=..., column=...)': 'use Column with Row for grid-like',
|
|
35
|
+
'place(x=..., y=...)': 'position in Container',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
THEME_MAPPING = {
|
|
39
|
+
'bg': 'bgcolor',
|
|
40
|
+
'fg': 'color',
|
|
41
|
+
'font': 'size + weight',
|
|
42
|
+
'relief': 'border_style',
|
|
43
|
+
'padx/pady': 'padding',
|
|
44
|
+
'width': 'width',
|
|
45
|
+
'height': 'height',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def convert_tkinter_to_flet(code: str) -> str:
|
|
49
|
+
result = code
|
|
50
|
+
for tk_widget, flet_widget in MAPPING.items():
|
|
51
|
+
result = result.replace(tk_widget, flet_widget)
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
print("Mapeo de tkinter/ctkinter a Flet cargado")
|
|
55
|
+
print("Usa: from migrate_tkinter import convert_tkinter_to_flet")
|
|
56
|
+
print("Para convertir código existente a Flet")
|
package/src/cli.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
5
8
|
import { init } from './commands/init.js';
|
|
6
9
|
import { setSkill } from './commands/set-skill.js';
|
|
7
10
|
import { bring } from './commands/bring.js';
|
|
@@ -14,21 +17,26 @@ import { update } from './commands/update.js';
|
|
|
14
17
|
import { convert } from './commands/convert.js';
|
|
15
18
|
import { signature } from './commands/signature.js';
|
|
16
19
|
|
|
20
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const pkg = fs.readJsonSync(path.join(__dirname, '../package.json'));
|
|
22
|
+
|
|
17
23
|
const program = new Command();
|
|
18
24
|
|
|
19
25
|
program
|
|
20
26
|
.name('g360')
|
|
21
27
|
.description('CLI tool for bootstrapping G360 ecosystem projects')
|
|
22
|
-
.version(
|
|
28
|
+
.version(pkg.version);
|
|
23
29
|
|
|
24
30
|
program
|
|
25
31
|
.command('init')
|
|
26
32
|
.argument('<name>', 'Project name')
|
|
27
33
|
.option('-t, --template <type>', 'Project template type', 'web-pwa')
|
|
28
|
-
.option('-s, --skill <skill>', 'Skill to use (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom)', 'corporativo-movil')
|
|
34
|
+
.option('-s, --skill <skill>', 'Skill to use (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop)', 'corporativo-movil')
|
|
29
35
|
.option('-d, --dir <path>', 'Target directory', '.')
|
|
30
36
|
.option('--dry-run', 'Preview without creating files')
|
|
31
37
|
.option('--force', 'Overwrite existing files')
|
|
38
|
+
.option('--portable', 'Create portable version (for python-flet, python-cli)')
|
|
39
|
+
.option('--no-portable', 'Skip portable version creation')
|
|
32
40
|
.action(init);
|
|
33
41
|
|
|
34
42
|
program
|
|
@@ -75,6 +83,8 @@ program
|
|
|
75
83
|
.option('--orphans', 'Remove orphan files (unreferenced)')
|
|
76
84
|
.option('--organize', 'Show misplaced files suggestions')
|
|
77
85
|
.option('--all', 'Run all cleanup tasks')
|
|
86
|
+
.option('--github', 'Prepare repo for GitHub (update .gitignore)')
|
|
87
|
+
.option('--pre-push', 'Alias for --github (prepare before push)')
|
|
78
88
|
.action(clean);
|
|
79
89
|
|
|
80
90
|
program
|
package/src/commands/clean.js
CHANGED
|
@@ -21,14 +21,16 @@ const DEAD_PATTERNS = [
|
|
|
21
21
|
const DUPLICATE_EXTENSIONS = ['.bak', '.orig', '.swp', '.swo', '~'];
|
|
22
22
|
|
|
23
23
|
export async function clean(projectPath, options) {
|
|
24
|
-
const {
|
|
25
|
-
dryRun = false,
|
|
24
|
+
const {
|
|
25
|
+
dryRun = false,
|
|
26
26
|
force = false,
|
|
27
27
|
dead = false,
|
|
28
28
|
duplicates = false,
|
|
29
29
|
orphans = false,
|
|
30
30
|
organize = false,
|
|
31
|
-
all = false
|
|
31
|
+
all = false,
|
|
32
|
+
github = false,
|
|
33
|
+
prePush = false
|
|
32
34
|
} = options;
|
|
33
35
|
|
|
34
36
|
const targetDir = path.join(process.cwd(), projectPath);
|
|
@@ -123,8 +125,53 @@ export async function clean(projectPath, options) {
|
|
|
123
125
|
return;
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
if (github || prePush) {
|
|
129
|
+
console.log(chalk.bold.cyan('\n📦 Modo Pre-Push GitHub\n'));
|
|
130
|
+
console.log(chalk.gray('Limpiando para subir a repositorio remoto...\n'));
|
|
131
|
+
|
|
132
|
+
const gitIgnore = path.join(targetDir, '.gitignore');
|
|
133
|
+
const requiredIgnores = [
|
|
134
|
+
'node_modules/',
|
|
135
|
+
'__pycache__/',
|
|
136
|
+
'*.pyc',
|
|
137
|
+
'.env',
|
|
138
|
+
'dist/',
|
|
139
|
+
'build/',
|
|
140
|
+
'*.log',
|
|
141
|
+
'.cache/',
|
|
142
|
+
'coverage/',
|
|
143
|
+
'*.egg-info/',
|
|
144
|
+
'.pytest_cache/'
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
if (fs.existsSync(gitIgnore)) {
|
|
148
|
+
const currentIgnore = await fs.readFile(gitIgnore, 'utf-8');
|
|
149
|
+
let updatedIgnore = currentIgnore;
|
|
150
|
+
|
|
151
|
+
for (const pattern of requiredIgnores) {
|
|
152
|
+
if (!currentIgnore.includes(pattern)) {
|
|
153
|
+
updatedIgnore += `\n${pattern}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await fs.writeFile(gitIgnore, updatedIgnore);
|
|
158
|
+
console.log(chalk.green(' ✅ .gitignore actualizado'));
|
|
159
|
+
} else {
|
|
160
|
+
await fs.writeFile(gitIgnore, requiredIgnores.join('\n') + '\n');
|
|
161
|
+
console.log(chalk.green(' ✅ .gitignore creado'));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(chalk.green('\n✅ Repo listo para GitHub!\n'));
|
|
165
|
+
console.log(chalk.gray('Para subir:'));
|
|
166
|
+
console.log(` ${chalk.cyan('git add .')}`);
|
|
167
|
+
console.log(` ${chalk.cyan('git commit -m "clean: preparing for push"')}`);
|
|
168
|
+
console.log(` ${chalk.cyan('git push')}\n`);
|
|
169
|
+
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
126
173
|
console.log(chalk.cyan('\n🚀 Aplicando limpieza...'));
|
|
127
|
-
|
|
174
|
+
|
|
128
175
|
let cleaned = 0;
|
|
129
176
|
|
|
130
177
|
for (const file of issues.dead) {
|
package/src/commands/init.js
CHANGED
|
@@ -5,19 +5,61 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import { manifest } from '../lib/manifest.js';
|
|
6
6
|
import { progress } from '../lib/progress.js';
|
|
7
7
|
import { setSkill } from './set-skill.js';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
8
9
|
|
|
9
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
|
|
12
|
+
const PORTABLE_TEMPLATES = ['python-flet', 'python-flet-migrate', 'python-cli', 'python-customtkinter'];
|
|
13
|
+
|
|
14
|
+
async function askPortableOption(template) {
|
|
15
|
+
if (!PORTABLE_TEMPLATES.includes(template)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const answers = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'confirm',
|
|
22
|
+
name: 'portable',
|
|
23
|
+
message: '¿Deseas crear una versión portable del proyecto? (ejecutable standalone)',
|
|
24
|
+
default: false
|
|
25
|
+
}
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
return answers.portable;
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
export async function init(name, options) {
|
|
12
|
-
const {
|
|
32
|
+
const {
|
|
33
|
+
template = 'web-pwa',
|
|
34
|
+
skill = 'corporativo-movil',
|
|
35
|
+
dir = '.',
|
|
36
|
+
dryRun = false,
|
|
37
|
+
force = false,
|
|
38
|
+
portable = null
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
13
41
|
const targetDir = path.join(process.cwd(), dir, name);
|
|
14
|
-
|
|
42
|
+
|
|
15
43
|
console.log(chalk.bold.cyan('\n🚀 G360 Project Initialization\n'));
|
|
16
44
|
console.log(`Project: ${chalk.yellow(name)}`);
|
|
17
45
|
console.log(`Template: ${chalk.blue(template)}`);
|
|
18
46
|
console.log(`Skill: ${chalk.magenta(skill)}`);
|
|
19
47
|
console.log(`Target: ${chalk.gray(targetDir)}\n`);
|
|
20
48
|
|
|
49
|
+
let wantPortable = false;
|
|
50
|
+
|
|
51
|
+
if (portable === true) {
|
|
52
|
+
wantPortable = true;
|
|
53
|
+
console.log(chalk.yellow('📦 Versión portable: SÍ (flag)\n'));
|
|
54
|
+
} else if (portable === false) {
|
|
55
|
+
console.log(chalk.gray('📦 Versión portable: NO\n'));
|
|
56
|
+
} else {
|
|
57
|
+
wantPortable = await askPortableOption(template);
|
|
58
|
+
if (wantPortable) {
|
|
59
|
+
console.log(chalk.yellow('📦 Versión portable: SÍ\n'));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
21
63
|
const templatesPath = path.join(__dirname, '../assets/templates');
|
|
22
64
|
|
|
23
65
|
if (!fs.existsSync(templatesPath)) {
|
|
@@ -49,24 +91,66 @@ export async function init(name, options) {
|
|
|
49
91
|
const progressBar = progress('Creating project...');
|
|
50
92
|
|
|
51
93
|
try {
|
|
52
|
-
// Asegurar que el directorio base existe
|
|
53
94
|
await fs.ensureDir(path.dirname(targetDir));
|
|
54
95
|
|
|
55
96
|
await fs.copy(templateDir, targetDir);
|
|
56
|
-
await manifest.init(targetDir, { name, template, version: '1.0.0' });
|
|
57
|
-
|
|
58
|
-
|
|
97
|
+
await manifest.init(targetDir, { name, template, version: '1.0.0', portable: wantPortable });
|
|
98
|
+
|
|
99
|
+
if (wantPortable) {
|
|
100
|
+
const portableDir = path.join(targetDir, 'portable');
|
|
101
|
+
await fs.ensureDir(portableDir);
|
|
102
|
+
const buildScript = path.join(targetDir, 'build-portable.bat');
|
|
103
|
+
if (fs.existsSync(buildScript)) {
|
|
104
|
+
await fs.copy(buildScript, path.join(portableDir, 'build.bat'));
|
|
105
|
+
}
|
|
106
|
+
console.log(chalk.gray(' 📦 Carpeta portable/ creada para builds\n'));
|
|
107
|
+
}
|
|
108
|
+
|
|
59
109
|
await setSkill(skill, { force: true, verbose: options.verbose, cwd: targetDir });
|
|
60
110
|
|
|
111
|
+
await createG360Structure(targetDir, templatesPath);
|
|
112
|
+
|
|
61
113
|
progressBar.stop();
|
|
62
|
-
|
|
114
|
+
|
|
63
115
|
console.log(chalk.green('\n✅ Project created successfully!\n'));
|
|
116
|
+
if (wantPortable) {
|
|
117
|
+
console.log(chalk.yellow('📦 Versión portable habilitada\n'));
|
|
118
|
+
}
|
|
64
119
|
console.log(chalk.gray('Next steps:'));
|
|
65
120
|
console.log(` ${chalk.cyan('cd')} ${name}`);
|
|
66
121
|
console.log(` ${chalk.cyan('g360 bring')}`);
|
|
67
122
|
console.log(` ${chalk.cyan('g360 present')}\n`);
|
|
68
|
-
|
|
123
|
+
} catch (error) {
|
|
69
124
|
progressBar.stop();
|
|
70
125
|
console.error(chalk.red(`\n❌ Error: ${error.message}`));
|
|
71
126
|
}
|
|
72
127
|
}
|
|
128
|
+
|
|
129
|
+
async function createG360Structure(projectPath, assetsDir) {
|
|
130
|
+
try {
|
|
131
|
+
const g360Dir = path.join(projectPath, 'g360');
|
|
132
|
+
await fs.ensureDir(g360Dir);
|
|
133
|
+
|
|
134
|
+
const subdirs = ['skills', 'snippets', 'samples', 'config', 'engine'];
|
|
135
|
+
for (const dir of subdirs) {
|
|
136
|
+
await fs.ensureDir(path.join(g360Dir, dir));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const skillJsonPath = path.join(g360Dir, 'skill.json');
|
|
140
|
+
if (!fs.existsSync(skillJsonPath)) {
|
|
141
|
+
const exampleSkillPath = path.join(assetsDir, 'config/skills.json');
|
|
142
|
+
if (fs.existsSync(exampleSkillPath)) {
|
|
143
|
+
await fs.copy(exampleSkillPath, skillJsonPath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const snippetsDir = path.join(g360Dir, 'snippets');
|
|
148
|
+
const exampleSnippetsPath = path.join(assetsDir, 'snippets/snippets.json');
|
|
149
|
+
if (fs.existsSync(exampleSnippetsPath) && !fs.existsSync(path.join(snippetsDir, 'snippets.json'))) {
|
|
150
|
+
await fs.copy(exampleSnippetsPath, path.join(snippetsDir, 'snippets.json'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.log(chalk.yellow(`Warning: Could not create G360 structure: ${error.message}`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -21,20 +21,24 @@ export async function signature(command, options) {
|
|
|
21
21
|
|
|
22
22
|
let htmlContent = fs.readFileSync(indexHtmlPath, 'utf8');
|
|
23
23
|
|
|
24
|
-
// Verificar si ya esta instalado
|
|
25
|
-
if (htmlContent.includes('g360-signature') && !force) {
|
|
26
|
-
console.log(chalk.yellow('⚠️ g360-signature ya se encuentra instalado'));
|
|
27
|
-
console.log(chalk.gray('Usa --force para reinstalar'));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Agregar script CDN antes del cierre del body
|
|
32
24
|
const scriptTag = ' <script type="module" src="https://unpkg.com/g360-signature@latest/index.js"></script>';
|
|
33
25
|
const signatureComponent = `
|
|
34
26
|
<!-- Firma Oficial G360 -->
|
|
35
27
|
<g360-signature mode="powered" style="position: fixed; bottom: 16px; right: 16px; z-index: 99999;"></g360-signature>
|
|
36
28
|
`;
|
|
37
29
|
|
|
30
|
+
// Si --force es true, eliminamos cualquier instancia existente para una reinstalación limpia.
|
|
31
|
+
// De lo contrario, si ya está instalado y no se fuerza, salimos.
|
|
32
|
+
if (force) {
|
|
33
|
+
htmlContent = htmlContent.replace(new RegExp(scriptTag.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '');
|
|
34
|
+
htmlContent = htmlContent.replace(new RegExp(signatureComponent.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '');
|
|
35
|
+
htmlContent = htmlContent.replace(/\n\s*\n/g, '\n'); // Limpiar posibles líneas vacías extra
|
|
36
|
+
} else if (htmlContent.includes('g360-signature')) {
|
|
37
|
+
console.log(chalk.yellow('⚠️ g360-signature ya se encuentra instalado'));
|
|
38
|
+
console.log(chalk.gray('Usa --force para reinstalar'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
if (!htmlContent.includes(scriptTag)) {
|
|
39
43
|
htmlContent = htmlContent.replace('</body>', `${scriptTag}\n </body>`);
|
|
40
44
|
}
|
package/src/lib/auditor.js
CHANGED
|
@@ -46,7 +46,25 @@ export const auditor = {
|
|
|
46
46
|
if (fs.existsSync(manifestPath)) {
|
|
47
47
|
return { status: 'pass', details: 'Manifest found' };
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
51
|
+
if (fs.existsSync(pkgPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
54
|
+
const isG360Tool =
|
|
55
|
+
pkg.name?.startsWith('g360-') ||
|
|
56
|
+
(pkg.keywords && pkg.keywords.some(k => k.includes('g360'))) ||
|
|
57
|
+
pkg.name === 'g360-cli';
|
|
58
|
+
|
|
59
|
+
if (isG360Tool) {
|
|
60
|
+
return { status: 'pass', details: 'G360 tool/package - manifest optional' };
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// Continue to warning
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { status: 'warn', issue: { file: 'g360-manifest.json', message: 'No manifest found - run "g360 bring" to initialize' } };
|
|
50
68
|
},
|
|
51
69
|
|
|
52
70
|
checkStructure(projectDir) {
|
|
@@ -54,7 +72,26 @@ export const auditor = {
|
|
|
54
72
|
if (fs.existsSync(g360Dir)) {
|
|
55
73
|
return { status: 'pass', details: 'G360 assets directory found' };
|
|
56
74
|
}
|
|
57
|
-
|
|
75
|
+
|
|
76
|
+
// Check if this is a G360 tool/package - structure optional
|
|
77
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
78
|
+
if (fs.existsSync(pkgPath)) {
|
|
79
|
+
try {
|
|
80
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
81
|
+
const isG360Tool =
|
|
82
|
+
pkg.name?.startsWith('g360-') ||
|
|
83
|
+
(pkg.keywords && pkg.keywords.some(k => k.includes('g360'))) ||
|
|
84
|
+
pkg.name === 'g360-cli';
|
|
85
|
+
|
|
86
|
+
if (isG360Tool) {
|
|
87
|
+
return { status: 'pass', details: 'G360 tool/package - G360 structure optional' };
|
|
88
|
+
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// If package.json is invalid, continue to warning
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { status: 'warn', issue: { file: 'g360/', message: 'No G360 assets found - run "g360 bring" to initialize assets' } };
|
|
58
95
|
},
|
|
59
96
|
|
|
60
97
|
checkConfig(projectDir) {
|