rook-cli 1.3.2 → 1.3.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.
Files changed (92) hide show
  1. package/package.json +3 -2
  2. package/rook-framework/PRD-INSTALL-COMMAND.md +379 -0
  3. package/rook-framework/PRD.md +1214 -0
  4. package/rook-framework/README.md +143 -0
  5. package/rook-framework/assets/rk-accordion.js +99 -0
  6. package/rook-framework/assets/rk-alert-dialog.js +132 -0
  7. package/rook-framework/assets/rk-bottom-app-bar.js +88 -0
  8. package/rook-framework/assets/rk-carousel.js +145 -0
  9. package/rook-framework/assets/rk-collapsible.js +151 -0
  10. package/rook-framework/assets/rk-dialog.js +161 -0
  11. package/rook-framework/assets/rk-drawer.js +214 -0
  12. package/rook-framework/assets/rk-framework-core.css +2554 -0
  13. package/rook-framework/assets/rk-framework-tokens.css +101 -0
  14. package/rook-framework/assets/rk-modal.js +91 -0
  15. package/rook-framework/assets/rk-popover.js +264 -0
  16. package/rook-framework/assets/rk-progress.js +81 -0
  17. package/rook-framework/assets/rk-quantity.js +91 -0
  18. package/rook-framework/assets/rk-scroll-area.js +286 -0
  19. package/rook-framework/assets/rk-sheet.js +157 -0
  20. package/rook-framework/assets/rk-tabs.js +179 -0
  21. package/rook-framework/assets/rk-toggle.js +153 -0
  22. package/rook-framework/blocks/rk-accordion.liquid +97 -0
  23. package/rook-framework/blocks/rk-badge.liquid +103 -0
  24. package/rook-framework/blocks/rk-button.liquid +166 -0
  25. package/rook-framework/blocks/rk-divider.liquid +100 -0
  26. package/rook-framework/blocks/rk-form-field.liquid +120 -0
  27. package/rook-framework/blocks/rk-icon.liquid +134 -0
  28. package/rook-framework/blocks/rk-image.liquid +198 -0
  29. package/rook-framework/blocks/rk-installments.liquid +99 -0
  30. package/rook-framework/blocks/rk-pix-discount.liquid +99 -0
  31. package/rook-framework/blocks/rk-price.liquid +128 -0
  32. package/rook-framework/blocks/rk-quantity.liquid +108 -0
  33. package/rook-framework/blocks/rk-quick-add.liquid +137 -0
  34. package/rook-framework/blocks/rk-skeleton.liquid +104 -0
  35. package/rook-framework/blocks/rk-typography.liquid +183 -0
  36. package/rook-framework/config/rk-color-scheme-group.json +138 -0
  37. package/rook-framework/config/rk-settings_schema.json +259 -0
  38. package/rook-framework/snippets/rk-accordion.liquid +31 -0
  39. package/rook-framework/snippets/rk-alert-dialog.liquid +83 -0
  40. package/rook-framework/snippets/rk-aspect-ratio.liquid +23 -0
  41. package/rook-framework/snippets/rk-badge.liquid +17 -0
  42. package/rook-framework/snippets/rk-bottom-app-bar.liquid +51 -0
  43. package/rook-framework/snippets/rk-button.liquid +49 -0
  44. package/rook-framework/snippets/rk-card.liquid +64 -0
  45. package/rook-framework/snippets/rk-carousel.liquid +74 -0
  46. package/rook-framework/snippets/rk-checkbox.liquid +34 -0
  47. package/rook-framework/snippets/rk-collapsible.liquid +52 -0
  48. package/rook-framework/snippets/rk-color-schemes-standalone.liquid +61 -0
  49. package/rook-framework/snippets/rk-color-schemes.liquid +43 -0
  50. package/rook-framework/snippets/rk-dialog.liquid +85 -0
  51. package/rook-framework/snippets/rk-divider.liquid +25 -0
  52. package/rook-framework/snippets/rk-drawer.liquid +81 -0
  53. package/rook-framework/snippets/rk-external-assets copy.liquid +33 -0
  54. package/rook-framework/snippets/rk-external-assets.liquid +68 -0
  55. package/rook-framework/snippets/rk-form-field.liquid +83 -0
  56. package/rook-framework/snippets/rk-gap-style.liquid +32 -0
  57. package/rook-framework/snippets/rk-icon.liquid +28 -0
  58. package/rook-framework/snippets/rk-image.liquid +60 -0
  59. package/rook-framework/snippets/rk-input.liquid +35 -0
  60. package/rook-framework/snippets/rk-installments.liquid +54 -0
  61. package/rook-framework/snippets/rk-item.liquid +69 -0
  62. package/rook-framework/snippets/rk-layout-style.liquid +37 -0
  63. package/rook-framework/snippets/rk-modal.liquid +31 -0
  64. package/rook-framework/snippets/rk-pix-discount.liquid +34 -0
  65. package/rook-framework/snippets/rk-popover.liquid +77 -0
  66. package/rook-framework/snippets/rk-price.liquid +48 -0
  67. package/rook-framework/snippets/rk-progress.liquid +38 -0
  68. package/rook-framework/snippets/rk-quantity.liquid +56 -0
  69. package/rook-framework/snippets/rk-quick-add.liquid +67 -0
  70. package/rook-framework/snippets/rk-scripts.liquid +17 -0
  71. package/rook-framework/snippets/rk-scroll-area.liquid +60 -0
  72. package/rook-framework/snippets/rk-sheet.liquid +86 -0
  73. package/rook-framework/snippets/rk-size-style.liquid +48 -0
  74. package/rook-framework/snippets/rk-skeleton.liquid +25 -0
  75. package/rook-framework/snippets/rk-spacing-padding.liquid +18 -0
  76. package/rook-framework/snippets/rk-spacing-style.liquid +54 -0
  77. package/rook-framework/snippets/rk-spinner.liquid +43 -0
  78. package/rook-framework/snippets/rk-swatch.liquid +33 -0
  79. package/rook-framework/snippets/rk-table.liquid +44 -0
  80. package/rook-framework/snippets/rk-tabs.liquid +52 -0
  81. package/rook-framework/snippets/rk-textarea.liquid +42 -0
  82. package/rook-framework/snippets/rk-toggle-group.liquid +27 -0
  83. package/rook-framework/snippets/rk-toggle.liquid +58 -0
  84. package/rook-framework/snippets/rk-typography.liquid +27 -0
  85. package/rook-framework/snippets/rk-variables.liquid +76 -0
  86. package/src/app.js +24 -0
  87. package/src/commands/InstallCommand.js +133 -0
  88. package/src/mcp/server.js +111 -1
  89. package/src/services/FrameworkInstaller.js +485 -0
  90. package/src/templates/block.liquid.txt +0 -15
  91. package/src/ui/PromptUI.js +15 -1
  92. package/src/utils/logger.js +1 -1
@@ -0,0 +1,485 @@
1
+ /**
2
+ * FrameworkInstaller — Serviço de instalação do Rook UI Core Framework.
3
+ *
4
+ * Encapsula toda a lógica de instalação do framework em um tema Shopify:
5
+ * 1. Copiar assets (CSS + JS)
6
+ * 2. Copiar snippets (Liquid)
7
+ * 3. Copiar blocks (Liquid)
8
+ * 4. Injetar settings no settings_schema.json
9
+ * 5. Patch do layout/theme.liquid
10
+ *
11
+ * Detecta automaticamente se o tema já possui um color_scheme_group:
12
+ * - SIM → usa rk-color-schemes.liquid (ponte para os schemes do tema)
13
+ * - NÃO → injeta rk-color-scheme-group.json + usa rk-color-schemes-standalone.liquid
14
+ *
15
+ * Princípio: Responsabilidade Única (SRP) — só instala o framework
16
+ * Princípio: Inversão de Dependência (DIP) — dependências injetadas
17
+ */
18
+
19
+ import path from 'path';
20
+ import fs from 'fs-extra';
21
+ import { fileURLToPath } from 'url';
22
+
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const FRAMEWORK_DIR = path.resolve(__dirname, '../../rook-framework');
25
+
26
+ /** Bloco Liquid injetado no <head> do theme.liquid */
27
+ const HEAD_INJECTION = `
28
+ {%- comment -%} Rook UI Core Framework {%- endcomment -%}
29
+ {%- render 'rk-color-schemes' -%}
30
+ {%- render 'rk-variables' -%}
31
+ {{ 'rk-framework-tokens.css' | asset_url | stylesheet_tag }}
32
+ {{ 'rk-framework-core.css' | asset_url | stylesheet_tag }}
33
+ {%- render 'rk-external-assets', location: 'head' -%}`;
34
+
35
+ /** Bloco Liquid injetado antes do </body> do theme.liquid */
36
+ const BODY_INJECTION = `
37
+ {%- comment -%} Rook UI Core Framework — Scripts {%- endcomment -%}
38
+ {%- render 'rk-external-assets', location: 'body' -%}
39
+ {%- render 'rk-scripts' -%}`;
40
+
41
+ /** Marcador para detectar instalação prévia */
42
+ const RK_MARKER = 'rk-variables';
43
+
44
+ /** Prefixo das seções no settings_schema */
45
+ const RK_SECTION_PREFIX = 'Rook UI';
46
+
47
+ export class FrameworkInstaller {
48
+
49
+ /**
50
+ * @param {import('../utils/logger.js').Logger} logger
51
+ */
52
+ constructor(logger) {
53
+ this.logger = logger;
54
+ }
55
+
56
+ /**
57
+ * Executa a instalação completa do framework.
58
+ *
59
+ * @param {string} themePath - Caminho raiz do tema Shopify
60
+ * @param {Object} [options={}]
61
+ * @param {boolean} [options.force=false] - Sobrescrever sem perguntar
62
+ * @param {boolean} [options.skipLayout=false] - Não modificar theme.liquid
63
+ * @param {boolean} [options.skipSettings=false] - Não modificar settings_schema.json
64
+ * @returns {Promise<{assets: number, snippets: number, blocks: number, settingsSections: number, layoutPatched: boolean, colorSchemeMode: string}>}
65
+ */
66
+ async install(themePath, options = {}) {
67
+ const resultado = {
68
+ assets: 0,
69
+ snippets: 0,
70
+ blocks: 0,
71
+ settingsSections: 0,
72
+ layoutPatched: false,
73
+ colorSchemeMode: 'unknown',
74
+ };
75
+
76
+ // Detectar color_scheme_group do tema antes de copiar snippets
77
+ const themeHasColorSchemes = await this._themeHasColorSchemeGroup(themePath);
78
+ resultado.colorSchemeMode = themeHasColorSchemes ? 'bridge' : 'standalone';
79
+
80
+ if (themeHasColorSchemes) {
81
+ this.logger.info(' Tema possui color_scheme_group → modo ponte (reutiliza esquemas do tema)');
82
+ } else {
83
+ this.logger.info(' Tema sem color_scheme_group → modo standalone (esquemas próprios Rook UI)');
84
+ }
85
+
86
+ // Etapa 1: Assets
87
+ this.logger.destaque('\n [1/5] Copiando assets...');
88
+ resultado.assets = await this._copyDir(
89
+ path.join(FRAMEWORK_DIR, 'assets'),
90
+ path.join(themePath, 'assets'),
91
+ options.force
92
+ );
93
+ this.logger.sucesso(` ${resultado.assets} arquivo(s) copiados para assets/`);
94
+
95
+ // Etapa 2: Snippets (com resolução do color-schemes correto)
96
+ this.logger.destaque('\n [2/5] Copiando snippets...');
97
+ resultado.snippets = await this._copySnippets(themePath, themeHasColorSchemes, options.force);
98
+ this.logger.sucesso(` ${resultado.snippets} arquivo(s) copiados para snippets/`);
99
+
100
+ // Etapa 3: Blocks
101
+ this.logger.destaque('\n [3/5] Copiando blocks...');
102
+ resultado.blocks = await this._copyDir(
103
+ path.join(FRAMEWORK_DIR, 'blocks'),
104
+ path.join(themePath, 'blocks'),
105
+ options.force
106
+ );
107
+ this.logger.sucesso(` ${resultado.blocks} arquivo(s) copiados para blocks/`);
108
+
109
+ // Etapa 4: Settings (com injeção condicional do color_scheme_group)
110
+ if (!options.skipSettings) {
111
+ this.logger.destaque('\n [4/5] Injetando settings no settings_schema.json...');
112
+ resultado.settingsSections = await this._injectSettings(themePath, themeHasColorSchemes);
113
+ this.logger.sucesso(` ${resultado.settingsSections} seção(ões) Rook UI injetadas`);
114
+ } else {
115
+ this.logger.sutil('[4/5] Pulando settings (--skip-settings)');
116
+ }
117
+
118
+ // Etapa 5: Layout
119
+ if (!options.skipLayout) {
120
+ this.logger.destaque('\n [5/5] Configurando layout/theme.liquid...');
121
+ resultado.layoutPatched = await this._patchThemeLayout(themePath);
122
+ if (resultado.layoutPatched) {
123
+ this.logger.sucesso(' theme.liquid atualizado com sucesso');
124
+ } else {
125
+ this.logger.sutil('theme.liquid já contém o Rook UI, pulando');
126
+ }
127
+ } else {
128
+ this.logger.sutil('[5/5] Pulando layout (--skip-layout)');
129
+ }
130
+
131
+ return resultado;
132
+ }
133
+
134
+ /**
135
+ * Valida que o diretório é um tema Shopify.
136
+ *
137
+ * @param {string} themePath
138
+ * @returns {Promise<boolean>}
139
+ */
140
+ async validate(themePath) {
141
+ const indicators = [
142
+ 'config/settings_schema.json',
143
+ 'layout/theme.liquid',
144
+ 'snippets',
145
+ 'sections',
146
+ 'templates',
147
+ ];
148
+
149
+ let found = 0;
150
+ for (const indicator of indicators) {
151
+ if (await fs.pathExists(path.join(themePath, indicator))) {
152
+ found++;
153
+ }
154
+ }
155
+
156
+ return found >= 3;
157
+ }
158
+
159
+ /**
160
+ * Detecta se o Rook UI já está instalado.
161
+ *
162
+ * @param {string} themePath
163
+ * @returns {Promise<boolean>}
164
+ */
165
+ async isAlreadyInstalled(themePath) {
166
+ const checks = [
167
+ path.join(themePath, 'snippets', 'rk-variables.liquid'),
168
+ path.join(themePath, 'assets', 'rk-framework-core.css'),
169
+ ];
170
+
171
+ for (const check of checks) {
172
+ if (await fs.pathExists(check)) {
173
+ return true;
174
+ }
175
+ }
176
+
177
+ // Verificar settings_schema.json
178
+ try {
179
+ const settingsPath = path.join(themePath, 'config', 'settings_schema.json');
180
+ if (await fs.pathExists(settingsPath)) {
181
+ const schema = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
182
+ return schema.some(s => s.name && s.name.startsWith(RK_SECTION_PREFIX));
183
+ }
184
+ } catch {
185
+ // Ignora erro de leitura
186
+ }
187
+
188
+ return false;
189
+ }
190
+
191
+ /**
192
+ * Detecta informações do tema (nome e versão).
193
+ *
194
+ * @param {string} themePath
195
+ * @returns {Promise<{name: string, version: string}|null>}
196
+ */
197
+ async detectThemeInfo(themePath) {
198
+ try {
199
+ const settingsPath = path.join(themePath, 'config', 'settings_schema.json');
200
+ const schema = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
201
+ const themeInfo = schema.find(s => s.name === 'theme_info');
202
+
203
+ if (themeInfo) {
204
+ return {
205
+ name: themeInfo.theme_name || 'Desconhecido',
206
+ version: themeInfo.theme_version || '?',
207
+ };
208
+ }
209
+ } catch {
210
+ // Ignora
211
+ }
212
+ return null;
213
+ }
214
+
215
+ /**
216
+ * Verifica se o tema já possui um color_scheme_group no settings_schema.json.
217
+ *
218
+ * @param {string} themePath
219
+ * @returns {Promise<boolean>}
220
+ * @private
221
+ */
222
+ async _themeHasColorSchemeGroup(themePath) {
223
+ try {
224
+ const settingsPath = path.join(themePath, 'config', 'settings_schema.json');
225
+ if (!await fs.pathExists(settingsPath)) return false;
226
+
227
+ const schema = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
228
+
229
+ for (const section of schema) {
230
+ if (!section.settings) continue;
231
+ for (const setting of section.settings) {
232
+ if (setting.type === 'color_scheme_group') {
233
+ return true;
234
+ }
235
+ }
236
+ }
237
+ } catch {
238
+ // Ignora
239
+ }
240
+ return false;
241
+ }
242
+
243
+ /**
244
+ * Copia snippets com resolução inteligente do rk-color-schemes.
245
+ *
246
+ * - Tema COM color_scheme_group → copia rk-color-schemes.liquid (ponte)
247
+ * - Tema SEM color_scheme_group → copia rk-color-schemes-standalone.liquid como rk-color-schemes.liquid
248
+ *
249
+ * Exclui o arquivo alternativo que não foi usado.
250
+ *
251
+ * @param {string} themePath
252
+ * @param {boolean} themeHasColorSchemes
253
+ * @param {boolean} force
254
+ * @returns {Promise<number>}
255
+ * @private
256
+ */
257
+ async _copySnippets(themePath, themeHasColorSchemes, force) {
258
+ const srcDir = path.join(FRAMEWORK_DIR, 'snippets');
259
+ const destDir = path.join(themePath, 'snippets');
260
+
261
+ // Arquivos a ignorar na cópia genérica
262
+ const skipFiles = new Set([
263
+ 'rk-color-schemes.liquid',
264
+ 'rk-color-schemes-standalone.liquid',
265
+ ]);
266
+
267
+ // Copiar todos os snippets exceto os dois de color-schemes
268
+ const count = await this._copyDir(srcDir, destDir, force, (filename) => {
269
+ if (filename.includes(' copy')) return false;
270
+ if (skipFiles.has(filename)) return false;
271
+ return true;
272
+ });
273
+
274
+ // Copiar a versão correta como rk-color-schemes.liquid
275
+ const sourceFile = themeHasColorSchemes
276
+ ? 'rk-color-schemes.liquid' // Ponte para schemes do tema
277
+ : 'rk-color-schemes-standalone.liquid'; // Schemes próprios do Rook UI
278
+
279
+ const srcPath = path.join(srcDir, sourceFile);
280
+ const destPath = path.join(destDir, 'rk-color-schemes.liquid');
281
+
282
+ if (await fs.pathExists(srcPath)) {
283
+ await fs.copy(srcPath, destPath, { overwrite: true });
284
+ this.logger.sutil(`→ snippets/rk-color-schemes.liquid (${themeHasColorSchemes ? 'ponte' : 'standalone'})`);
285
+ return count + 1;
286
+ }
287
+
288
+ return count;
289
+ }
290
+
291
+ /**
292
+ * Copia todos os arquivos de um diretório para outro.
293
+ *
294
+ * @param {string} srcDir - Diretório de origem
295
+ * @param {string} destDir - Diretório de destino
296
+ * @param {boolean} force - Sobrescrever sem perguntar
297
+ * @param {Function} [filter] - Função de filtro (recebe filename, retorna boolean)
298
+ * @returns {Promise<number>} Quantidade de arquivos copiados
299
+ * @private
300
+ */
301
+ async _copyDir(srcDir, destDir, force = false, filter = null) {
302
+ if (!await fs.pathExists(srcDir)) {
303
+ return 0;
304
+ }
305
+
306
+ await fs.ensureDir(destDir);
307
+
308
+ const files = await fs.readdir(srcDir);
309
+ let count = 0;
310
+
311
+ for (const file of files) {
312
+ if (filter && !filter(file)) {
313
+ continue;
314
+ }
315
+
316
+ const srcFile = path.join(srcDir, file);
317
+ const destFile = path.join(destDir, file);
318
+ const stat = await fs.stat(srcFile);
319
+
320
+ if (!stat.isFile()) {
321
+ continue;
322
+ }
323
+
324
+ // Se o arquivo já existe e não é force, pular
325
+ if (!force && await fs.pathExists(destFile)) {
326
+ // Em modo não-force, sobrescreve mesmo assim para garantir atualização
327
+ // O ConflictResolver é usado no nível do comando, não aqui
328
+ }
329
+
330
+ await fs.copy(srcFile, destFile, { overwrite: true });
331
+ this.logger.sutil(`→ ${path.basename(destDir)}/${file}`);
332
+ count++;
333
+ }
334
+
335
+ return count;
336
+ }
337
+
338
+ /**
339
+ * Injeta as seções do Rook UI no settings_schema.json do tema.
340
+ *
341
+ * Se o tema NÃO possui color_scheme_group, injeta também o
342
+ * rk-color-scheme-group.json com o grupo de cores próprio do Rook UI.
343
+ *
344
+ * @param {string} themePath
345
+ * @param {boolean} themeHasColorSchemes
346
+ * @returns {Promise<number>} Quantidade de seções injetadas
347
+ * @private
348
+ */
349
+ async _injectSettings(themePath, themeHasColorSchemes) {
350
+ const settingsPath = path.join(themePath, 'config', 'settings_schema.json');
351
+ const rkSettingsPath = path.join(FRAMEWORK_DIR, 'config', 'rk-settings_schema.json');
352
+ const rkColorGroupPath = path.join(FRAMEWORK_DIR, 'config', 'rk-color-scheme-group.json');
353
+
354
+ if (!await fs.pathExists(settingsPath)) {
355
+ this.logger.aviso('settings_schema.json não encontrado, pulando injeção de settings');
356
+ return 0;
357
+ }
358
+
359
+ // Backup
360
+ const backupPath = settingsPath + '.rk-backup';
361
+ await fs.copy(settingsPath, backupPath, { overwrite: true });
362
+ this.logger.sutil(`Backup criado: settings_schema.json.rk-backup`);
363
+
364
+ try {
365
+ // Ler arquivos
366
+ let schema = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
367
+ const rkSections = JSON.parse(await fs.readFile(rkSettingsPath, 'utf8'));
368
+
369
+ // Remover seções Rook UI existentes (para atualização limpa)
370
+ schema = schema.filter(s => !s.name || !s.name.startsWith(RK_SECTION_PREFIX));
371
+
372
+ // Se o tema NÃO tem color_scheme_group, injetar o do Rook UI
373
+ if (!themeHasColorSchemes && await fs.pathExists(rkColorGroupPath)) {
374
+ const colorGroup = JSON.parse(await fs.readFile(rkColorGroupPath, 'utf8'));
375
+ schema.push(...colorGroup);
376
+ this.logger.sutil('→ Injetado color_scheme_group próprio do Rook UI');
377
+
378
+ for (const section of colorGroup) {
379
+ this.logger.sutil(`→ ${section.name}`);
380
+ }
381
+ } else if (themeHasColorSchemes) {
382
+ this.logger.sutil('→ Tema já possui color_scheme_group, reaproveitando');
383
+ }
384
+
385
+ // Inserir seções padrão no final
386
+ schema.push(...rkSections);
387
+
388
+ // Salvar
389
+ await fs.writeFile(settingsPath, JSON.stringify(schema, null, 2), 'utf8');
390
+
391
+ // Validar JSON resultante
392
+ JSON.parse(await fs.readFile(settingsPath, 'utf8'));
393
+
394
+ // Log das seções inseridas
395
+ for (const section of rkSections) {
396
+ const settingsCount = section.settings?.filter(s => s.id).length || 0;
397
+ this.logger.sutil(`→ ${section.name} (${settingsCount} settings)`);
398
+ }
399
+
400
+ const totalSections = rkSections.length + (!themeHasColorSchemes ? 1 : 0);
401
+ return totalSections;
402
+ } catch (erro) {
403
+ // Restaurar backup em caso de erro
404
+ this.logger.erro(`Erro ao injetar settings: ${erro.message}`);
405
+ this.logger.info('Restaurando backup...');
406
+ await fs.copy(backupPath, settingsPath, { overwrite: true });
407
+ throw erro;
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Modifica o layout/theme.liquid para carregar o framework.
413
+ *
414
+ * @param {string} themePath
415
+ * @returns {Promise<boolean>} true se modificou, false se já existia
416
+ * @private
417
+ */
418
+ async _patchThemeLayout(themePath) {
419
+ const layoutPath = path.join(themePath, 'layout', 'theme.liquid');
420
+
421
+ if (!await fs.pathExists(layoutPath)) {
422
+ this.logger.aviso('layout/theme.liquid não encontrado, pulando patch');
423
+ return false;
424
+ }
425
+
426
+ // Backup
427
+ const backupPath = layoutPath + '.rk-backup';
428
+ await fs.copy(layoutPath, backupPath, { overwrite: true });
429
+ this.logger.sutil(`Backup criado: theme.liquid.rk-backup`);
430
+
431
+ let content = await fs.readFile(layoutPath, 'utf8');
432
+
433
+ // Detectar instalação prévia
434
+ if (content.includes(RK_MARKER)) {
435
+ // Remover blocos antigos para reinstalar limpo
436
+ content = this._removeExistingRkBlocks(content);
437
+ }
438
+
439
+ // Injetar no <head> — antes de {{ content_for_header }}
440
+ const headAnchor = '{{ content_for_header }}';
441
+ const headFallback = '</head>';
442
+
443
+ if (content.includes(headAnchor)) {
444
+ content = content.replace(headAnchor, HEAD_INJECTION + '\n\n ' + headAnchor);
445
+ this.logger.sutil('→ Injetado no <head>: rk-color-schemes, rk-variables, tokens, core CSS');
446
+ } else if (content.includes(headFallback)) {
447
+ content = content.replace(headFallback, HEAD_INJECTION + '\n ' + headFallback);
448
+ this.logger.sutil('→ Injetado antes de </head> (fallback)');
449
+ } else {
450
+ this.logger.aviso('Não foi possível encontrar ponto de injeção no <head>');
451
+ }
452
+
453
+ // Injetar antes do </body>
454
+ const bodyAnchor = '</body>';
455
+
456
+ if (content.includes(bodyAnchor)) {
457
+ content = content.replace(bodyAnchor, BODY_INJECTION + '\n ' + bodyAnchor);
458
+ this.logger.sutil('→ Injetado antes do </body>: rk-scripts');
459
+ } else {
460
+ this.logger.aviso('Não foi possível encontrar </body> no theme.liquid');
461
+ }
462
+
463
+ await fs.writeFile(layoutPath, content, 'utf8');
464
+ return true;
465
+ }
466
+
467
+ /**
468
+ * Remove blocos Rook UI existentes do theme.liquid para reinstalação limpa.
469
+ *
470
+ * @param {string} content - Conteúdo do theme.liquid
471
+ * @returns {string} Conteúdo limpo
472
+ * @private
473
+ */
474
+ _removeExistingRkBlocks(content) {
475
+ // Remover bloco do <head> (detecta tanto versões com quanto sem rk-color-schemes)
476
+ const headBlockRegex = /\n?\s*\{%-?\s*comment\s*-?%\}\s*Rook UI Core Framework\s*\{%-?\s*endcomment\s*-?%\}[\s\S]*?\{%-?\s*render\s+'rk-external-assets'[^%]*%\}\s*\n?/g;
477
+ content = content.replace(headBlockRegex, '\n');
478
+
479
+ // Remover bloco do </body>
480
+ const bodyBlockRegex = /\n?\s*\{%-?\s*comment\s*-?%\}\s*Rook UI Core Framework — Scripts\s*\{%-?\s*endcomment\s*-?%\}[\s\S]*?\{%-?\s*render\s+'rk-scripts'\s*-?%\}\s*\n?/g;
481
+ content = content.replace(bodyBlockRegex, '\n');
482
+
483
+ return content;
484
+ }
485
+ }
@@ -58,18 +58,3 @@
58
58
  }
59
59
  {% endschema %}
60
60
 
61
- {% javascript %}
62
- class {{PascalName}}Block extends HTMLElement {
63
- connectedCallback() {
64
- this.addEventListener('click', this.handleClick.bind(this));
65
- }
66
-
67
- handleClick() {
68
- console.log('Block clicked:', this.dataset.blockId);
69
- }
70
- }
71
-
72
- if (!customElements.get('{{kebabName}}-block')) {
73
- customElements.define('{{kebabName}}-block', {{PascalName}}Block);
74
- }
75
- {% endjavascript %}
@@ -8,7 +8,7 @@
8
8
  * Princípio: Aberto/Fechado (OCP) — fácil adicionar novos menus
9
9
  */
10
10
 
11
- import { select, checkbox, input } from '@inquirer/prompts';
11
+ import { select, checkbox, input, confirm } from '@inquirer/prompts';
12
12
 
13
13
  export class PromptUI {
14
14
 
@@ -120,6 +120,20 @@ export class PromptUI {
120
120
  * @param {Array<{nome: string, caminho: string}>} componentes - Componentes disponíveis
121
121
  * @returns {Promise<Array<{nome: string, caminho: string}>>} Componentes selecionados
122
122
  */
123
+ /**
124
+ * Exibe uma pergunta de confirmação (sim/não).
125
+ *
126
+ * @param {string} mensagem - Pergunta a ser exibida
127
+ * @param {boolean} [padrao=true] - Valor padrão (true = Sim)
128
+ * @returns {Promise<boolean>}
129
+ */
130
+ async confirmar(mensagem, padrao = true) {
131
+ return confirm({
132
+ message: mensagem,
133
+ default: padrao,
134
+ });
135
+ }
136
+
123
137
  async selecionarComponentes(componentes) {
124
138
  if (componentes.length === 0) {
125
139
  throw new Error('Nenhum componente disponível no repositório.');
@@ -49,7 +49,7 @@ export class Logger {
49
49
  banner() {
50
50
  console.log('');
51
51
  console.log(pc.bold(pc.white(' ╔══════════════════════════════╗')));
52
- console.log(pc.bold(pc.white(' ║ ♟️ ROOK CLI v1.3.2 ║')));
52
+ console.log(pc.bold(pc.white(' ║ ♟️ ROOK CLI v1.3.5 ║')));
53
53
  console.log(pc.bold(pc.white(' ║ Shopify Component Tool ║')));
54
54
  console.log(pc.bold(pc.white(' ╚══════════════════════════════╝')));
55
55
  console.log('');