g360-cli 1.3.0 → 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.
Files changed (29) hide show
  1. package/README.md +87 -5
  2. package/dist/g360.exe +0 -0
  3. package/package.json +6 -6
  4. package/src/assets/config/g360-skills.json +52 -0
  5. package/src/assets/config/project-types.json +16 -0
  6. package/src/assets/config/skills.json +12 -0
  7. package/src/assets/snippets/snippets.json +77 -0
  8. package/src/assets/templates/python-cli/build-portable.bat +33 -0
  9. package/src/assets/templates/python-cli/src/main.py +107 -3
  10. package/src/assets/templates/python-customtkinter/README.md +47 -0
  11. package/src/assets/templates/python-customtkinter/build-portable.bat +28 -0
  12. package/src/assets/templates/python-customtkinter/requirements.txt +1 -0
  13. package/src/assets/templates/python-customtkinter/run.bat +29 -0
  14. package/src/assets/templates/python-customtkinter/src/core/skill.json +22 -0
  15. package/src/assets/templates/python-customtkinter/src/main.py +123 -0
  16. package/src/assets/templates/python-flet/README.md +130 -0
  17. package/src/assets/templates/python-flet/build-portable.bat +42 -0
  18. package/src/assets/templates/python-flet/create_shortcut.vbs +35 -0
  19. package/src/assets/templates/python-flet/requirements.txt +3 -0
  20. package/src/assets/templates/python-flet/run.bat +81 -0
  21. package/src/assets/templates/python-flet/src/core/skill.json +21 -0
  22. package/src/assets/templates/python-flet/src/main.py +101 -0
  23. package/src/assets/templates/python-flet/src/test_app.py +103 -0
  24. package/src/assets/templates/python-flet-migrate/src/main.py +68 -0
  25. package/src/assets/templates/python-flet-migrate/src/migrate_tkinter.py +56 -0
  26. package/src/cli.js +5 -1
  27. package/src/commands/clean.js +51 -4
  28. package/src/commands/init.js +60 -8
  29. package/src/commands/signature.js +12 -8
@@ -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
@@ -31,10 +31,12 @@ program
31
31
  .command('init')
32
32
  .argument('<name>', 'Project name')
33
33
  .option('-t, --template <type>', 'Project template type', 'web-pwa')
34
- .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')
35
35
  .option('-d, --dir <path>', 'Target directory', '.')
36
36
  .option('--dry-run', 'Preview without creating files')
37
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')
38
40
  .action(init);
39
41
 
40
42
  program
@@ -81,6 +83,8 @@ program
81
83
  .option('--orphans', 'Remove orphan files (unreferenced)')
82
84
  .option('--organize', 'Show misplaced files suggestions')
83
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)')
84
88
  .action(clean);
85
89
 
86
90
  program
@@ -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) {
@@ -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 { template = 'web-pwa', skill = 'corporativo-movil', dir = '.', dryRun = false, force = false } = options;
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,21 +91,31 @@ 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
- // Aplicar el skill pasando el directorio destino explícitamente
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
 
61
- // Crear estructura estándar G360 para auditoría y desarrollo guiado
62
111
  await createG360Structure(targetDir, templatesPath);
63
112
 
64
113
  progressBar.stop();
65
-
114
+
66
115
  console.log(chalk.green('\n✅ Project created successfully!\n'));
116
+ if (wantPortable) {
117
+ console.log(chalk.yellow('📦 Versión portable habilitada\n'));
118
+ }
67
119
  console.log(chalk.gray('Next steps:'));
68
120
  console.log(` ${chalk.cyan('cd')} ${name}`);
69
121
  console.log(` ${chalk.cyan('g360 bring')}`);
@@ -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
  }