@luquimbo/bi-superpowers 1.0.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 (193) hide show
  1. package/.claude-plugin/plugin.json +8 -0
  2. package/.mcp.json +25 -0
  3. package/AGENTS.md +244 -0
  4. package/CHANGELOG.md +265 -0
  5. package/LICENSE +21 -0
  6. package/README.md +211 -0
  7. package/bin/build-plugin.js +30 -0
  8. package/bin/cli.js +1064 -0
  9. package/bin/commands/add.js +533 -0
  10. package/bin/commands/add.test.js +77 -0
  11. package/bin/commands/build-desktop.js +166 -0
  12. package/bin/commands/changelog.js +443 -0
  13. package/bin/commands/diff.js +325 -0
  14. package/bin/commands/lint.js +419 -0
  15. package/bin/commands/lint.test.js +103 -0
  16. package/bin/commands/mcp-setup.js +246 -0
  17. package/bin/commands/pull.js +287 -0
  18. package/bin/commands/pull.test.js +36 -0
  19. package/bin/commands/push.js +231 -0
  20. package/bin/commands/push.test.js +14 -0
  21. package/bin/commands/search.js +344 -0
  22. package/bin/commands/search.test.js +115 -0
  23. package/bin/commands/setup.js +545 -0
  24. package/bin/commands/setup.test.js +46 -0
  25. package/bin/commands/sync-profile.js +405 -0
  26. package/bin/commands/sync-profile.test.js +14 -0
  27. package/bin/commands/sync-source.js +418 -0
  28. package/bin/commands/sync-source.test.js +14 -0
  29. package/bin/commands/watch.js +206 -0
  30. package/bin/lib/generators/claude-plugin.js +266 -0
  31. package/bin/lib/generators/claude-plugin.test.js +110 -0
  32. package/bin/lib/generators/index.js +116 -0
  33. package/bin/lib/generators/shared.js +282 -0
  34. package/bin/lib/licensing/index.js +35 -0
  35. package/bin/lib/licensing/storage.js +364 -0
  36. package/bin/lib/licensing/storage.test.js +55 -0
  37. package/bin/lib/licensing/validator.js +213 -0
  38. package/bin/lib/licensing/validator.test.js +137 -0
  39. package/bin/lib/microsoft-mcp.js +176 -0
  40. package/bin/lib/microsoft-mcp.test.js +106 -0
  41. package/bin/lib/skills.js +84 -0
  42. package/bin/mcp/powerbi-modeling-launcher.js +38 -0
  43. package/bin/postinstall.js +44 -0
  44. package/bin/utils/errors.js +159 -0
  45. package/bin/utils/git.js +298 -0
  46. package/bin/utils/logger.js +142 -0
  47. package/bin/utils/mcp-detect.js +274 -0
  48. package/bin/utils/mcp-detect.test.js +105 -0
  49. package/bin/utils/pbix.js +305 -0
  50. package/bin/utils/pbix.test.js +37 -0
  51. package/bin/utils/profiles.js +312 -0
  52. package/bin/utils/projects.js +168 -0
  53. package/bin/utils/readline.js +206 -0
  54. package/bin/utils/readline.test.js +47 -0
  55. package/bin/utils/tui.js +314 -0
  56. package/bin/utils/tui.test.js +127 -0
  57. package/commands/contributions.md +265 -0
  58. package/commands/data-model-design.md +468 -0
  59. package/commands/dax-doctor.md +248 -0
  60. package/commands/fabric-scripts.md +452 -0
  61. package/commands/migration-assistant.md +290 -0
  62. package/commands/model-documenter.md +242 -0
  63. package/commands/pbi-connect.md +239 -0
  64. package/commands/project-kickoff.md +905 -0
  65. package/commands/report-layout.md +296 -0
  66. package/commands/rls-design.md +533 -0
  67. package/commands/theme-tweaker.md +624 -0
  68. package/config.example.json +23 -0
  69. package/config.json +23 -0
  70. package/desktop-extension/manifest.json +37 -0
  71. package/desktop-extension/package.json +10 -0
  72. package/desktop-extension/server.js +95 -0
  73. package/docs/openrouter-free-models.md +92 -0
  74. package/library/examples/README.md +151 -0
  75. package/library/examples/finance-reporting/README.md +351 -0
  76. package/library/examples/finance-reporting/data-model.md +267 -0
  77. package/library/examples/finance-reporting/measures.dax +557 -0
  78. package/library/examples/hr-analytics/README.md +371 -0
  79. package/library/examples/hr-analytics/data-model.md +315 -0
  80. package/library/examples/hr-analytics/measures.dax +460 -0
  81. package/library/examples/marketing-analytics/README.md +37 -0
  82. package/library/examples/marketing-analytics/data-model.md +62 -0
  83. package/library/examples/marketing-analytics/measures.dax +110 -0
  84. package/library/examples/retail-analytics/README.md +439 -0
  85. package/library/examples/retail-analytics/data-model.md +288 -0
  86. package/library/examples/retail-analytics/measures.dax +481 -0
  87. package/library/examples/supply-chain/README.md +37 -0
  88. package/library/examples/supply-chain/data-model.md +69 -0
  89. package/library/examples/supply-chain/measures.dax +77 -0
  90. package/library/examples/udf-library/README.md +228 -0
  91. package/library/examples/udf-library/functions.dax +571 -0
  92. package/library/snippets/dax/README.md +292 -0
  93. package/library/snippets/dax/business-domains.md +576 -0
  94. package/library/snippets/dax/calculate-patterns.md +276 -0
  95. package/library/snippets/dax/calculation-groups.md +489 -0
  96. package/library/snippets/dax/error-handling.md +495 -0
  97. package/library/snippets/dax/iterators-and-aggregations.md +474 -0
  98. package/library/snippets/dax/kpis-and-metrics.md +293 -0
  99. package/library/snippets/dax/rankings-and-topn.md +235 -0
  100. package/library/snippets/dax/security-patterns.md +413 -0
  101. package/library/snippets/dax/text-and-formatting.md +316 -0
  102. package/library/snippets/dax/time-intelligence.md +196 -0
  103. package/library/snippets/dax/user-defined-functions.md +477 -0
  104. package/library/snippets/dax/virtual-tables.md +546 -0
  105. package/library/snippets/excel-formulas/README.md +84 -0
  106. package/library/snippets/excel-formulas/aggregations.md +330 -0
  107. package/library/snippets/excel-formulas/dates-and-times.md +361 -0
  108. package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
  109. package/library/snippets/excel-formulas/lookups.md +169 -0
  110. package/library/snippets/excel-formulas/text-functions.md +363 -0
  111. package/library/snippets/governance/naming-conventions.md +97 -0
  112. package/library/snippets/governance/review-checklists.md +107 -0
  113. package/library/snippets/power-query/README.md +389 -0
  114. package/library/snippets/power-query/api-integration.md +707 -0
  115. package/library/snippets/power-query/connections.md +434 -0
  116. package/library/snippets/power-query/data-cleaning.md +298 -0
  117. package/library/snippets/power-query/error-handling.md +526 -0
  118. package/library/snippets/power-query/parameters.md +350 -0
  119. package/library/snippets/power-query/performance.md +506 -0
  120. package/library/snippets/power-query/transformations.md +330 -0
  121. package/library/snippets/report-design/accessibility.md +78 -0
  122. package/library/snippets/report-design/chart-selection.md +54 -0
  123. package/library/snippets/report-design/layout-patterns.md +87 -0
  124. package/library/templates/data-models/README.md +93 -0
  125. package/library/templates/data-models/finance-model.md +627 -0
  126. package/library/templates/data-models/retail-star-schema.md +473 -0
  127. package/library/templates/excel/README.md +83 -0
  128. package/library/templates/excel/budget-tracker.md +432 -0
  129. package/library/templates/excel/data-entry-form.md +533 -0
  130. package/library/templates/power-bi/README.md +72 -0
  131. package/library/templates/power-bi/finance-report.md +449 -0
  132. package/library/templates/power-bi/kpi-scorecard.md +461 -0
  133. package/library/templates/power-bi/sales-dashboard.md +281 -0
  134. package/library/themes/excel/README.md +436 -0
  135. package/library/themes/power-bi/README.md +271 -0
  136. package/library/themes/power-bi/accessible.json +307 -0
  137. package/library/themes/power-bi/bi-superpowers-default.json +858 -0
  138. package/library/themes/power-bi/corporate-blue.json +291 -0
  139. package/library/themes/power-bi/dark-mode.json +291 -0
  140. package/library/themes/power-bi/minimal.json +292 -0
  141. package/library/themes/power-bi/print-friendly.json +309 -0
  142. package/package.json +93 -0
  143. package/skills/contributions/SKILL.md +267 -0
  144. package/skills/data-model-design/SKILL.md +470 -0
  145. package/skills/data-modeling/SKILL.md +254 -0
  146. package/skills/data-quality/SKILL.md +664 -0
  147. package/skills/dax/SKILL.md +708 -0
  148. package/skills/dax-doctor/SKILL.md +250 -0
  149. package/skills/dax-udf/SKILL.md +489 -0
  150. package/skills/deployment/SKILL.md +320 -0
  151. package/skills/excel-formulas/SKILL.md +463 -0
  152. package/skills/fabric-scripts/SKILL.md +454 -0
  153. package/skills/fast-standard/SKILL.md +509 -0
  154. package/skills/governance/SKILL.md +205 -0
  155. package/skills/migration-assistant/SKILL.md +292 -0
  156. package/skills/model-documenter/SKILL.md +244 -0
  157. package/skills/pbi-connect/SKILL.md +241 -0
  158. package/skills/power-query/SKILL.md +406 -0
  159. package/skills/project-kickoff/SKILL.md +907 -0
  160. package/skills/query-performance/SKILL.md +480 -0
  161. package/skills/report-design/SKILL.md +207 -0
  162. package/skills/report-layout/SKILL.md +298 -0
  163. package/skills/rls-design/SKILL.md +535 -0
  164. package/skills/semantic-model/SKILL.md +237 -0
  165. package/skills/testing-validation/SKILL.md +643 -0
  166. package/skills/theme-tweaker/SKILL.md +626 -0
  167. package/src/content/base.md +237 -0
  168. package/src/content/mcp-requirements.json +69 -0
  169. package/src/content/routing.md +203 -0
  170. package/src/content/skills/contributions.md +259 -0
  171. package/src/content/skills/data-model-design.md +462 -0
  172. package/src/content/skills/data-modeling.md +246 -0
  173. package/src/content/skills/data-quality.md +656 -0
  174. package/src/content/skills/dax-doctor.md +242 -0
  175. package/src/content/skills/dax-udf.md +481 -0
  176. package/src/content/skills/dax.md +700 -0
  177. package/src/content/skills/deployment.md +312 -0
  178. package/src/content/skills/excel-formulas.md +455 -0
  179. package/src/content/skills/fabric-scripts.md +446 -0
  180. package/src/content/skills/fast-standard.md +501 -0
  181. package/src/content/skills/governance.md +197 -0
  182. package/src/content/skills/migration-assistant.md +284 -0
  183. package/src/content/skills/model-documenter.md +236 -0
  184. package/src/content/skills/pbi-connect.md +233 -0
  185. package/src/content/skills/power-query.md +398 -0
  186. package/src/content/skills/project-kickoff.md +899 -0
  187. package/src/content/skills/query-performance.md +472 -0
  188. package/src/content/skills/report-design.md +199 -0
  189. package/src/content/skills/report-layout.md +290 -0
  190. package/src/content/skills/rls-design.md +527 -0
  191. package/src/content/skills/semantic-model.md +229 -0
  192. package/src/content/skills/testing-validation.md +635 -0
  193. package/src/content/skills/theme-tweaker.md +618 -0
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Sync Profile Command
3
+ * =====================
4
+ *
5
+ * Syncs snippets/standards from a project to a base profile.
6
+ * Allows users to save their custom patterns for reuse across projects.
7
+ *
8
+ * Usage:
9
+ * super sync-profile Interactive mode
10
+ * super sync-profile --profile finance Sync to specific profile
11
+ * super sync-profile --new healthcare Create new profile and sync
12
+ * super sync-profile --all Sync all snippets
13
+ *
14
+ * @module commands/sync-profile
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const profiles = require('../utils/profiles');
21
+ const rl = require('../utils/readline');
22
+
23
+ // Using shared readline utilities
24
+ const { createReadline, prompt } = rl;
25
+
26
+ /**
27
+ * Parse command line arguments
28
+ * @param {string[]} args - CLI arguments
29
+ * @returns {Object} Parsed options
30
+ */
31
+ function parseArgs(args) {
32
+ const options = {
33
+ profile: null,
34
+ newProfile: null,
35
+ all: false,
36
+ overwrite: false,
37
+ help: false,
38
+ };
39
+
40
+ for (let i = 0; i < args.length; i++) {
41
+ const arg = args[i];
42
+
43
+ if (arg === '--profile' || arg === '-p') {
44
+ options.profile = args[++i];
45
+ } else if (arg === '--new' || arg === '-n') {
46
+ options.newProfile = args[++i];
47
+ } else if (arg === '--all' || arg === '-a') {
48
+ options.all = true;
49
+ } else if (arg === '--overwrite' || arg === '-o') {
50
+ options.overwrite = true;
51
+ } else if (arg === '--help' || arg === '-h') {
52
+ options.help = true;
53
+ }
54
+ }
55
+
56
+ return options;
57
+ }
58
+
59
+ // Readline functions imported from ../utils/readline.js
60
+
61
+ /**
62
+ * Show help message
63
+ */
64
+ function showHelp() {
65
+ console.log(`
66
+ super sync-profile - Sync snippets to a base profile
67
+
68
+ Usage:
69
+ super sync-profile Interactive mode
70
+ super sync-profile --profile <name> Sync to specific profile
71
+ super sync-profile --new <name> Create new profile and sync
72
+
73
+ Options:
74
+ --profile, -p <name> Target profile name
75
+ --new, -n <name> Create a new profile
76
+ --all, -a Sync all snippets without selecting
77
+ --overwrite, -o Overwrite existing files without prompting
78
+ --help, -h Show this help message
79
+
80
+ Examples:
81
+ super sync-profile
82
+ super sync-profile --profile finance
83
+ super sync-profile --new healthcare --all
84
+ `);
85
+ }
86
+
87
+ /**
88
+ * Get snippets from the current repo
89
+ * @param {string} repoPath - Path to bi-repo
90
+ * @returns {Object[]} Array of snippet objects
91
+ */
92
+ function getRepoSnippets(repoPath) {
93
+ const snippetsDir = path.join(repoPath, 'snippets');
94
+ const standardsDir = path.join(repoPath, 'standards');
95
+
96
+ const snippets = [];
97
+
98
+ // Get snippets
99
+ if (fs.existsSync(snippetsDir)) {
100
+ const files = profiles.findMdFiles(snippetsDir);
101
+ for (const file of files) {
102
+ snippets.push({
103
+ path: file,
104
+ relativePath: path.relative(snippetsDir, file),
105
+ type: 'snippet',
106
+ });
107
+ }
108
+ }
109
+
110
+ // Get standards
111
+ if (fs.existsSync(standardsDir)) {
112
+ const files = profiles.findMdFiles(standardsDir);
113
+ for (const file of files) {
114
+ snippets.push({
115
+ path: file,
116
+ relativePath: path.relative(standardsDir, file),
117
+ type: 'standard',
118
+ });
119
+ }
120
+ }
121
+
122
+ return snippets;
123
+ }
124
+
125
+ /**
126
+ * Select target profile interactively
127
+ */
128
+ async function selectProfile(rl, options) {
129
+ // If new profile specified, create it
130
+ if (options.newProfile) {
131
+ const name = options.newProfile;
132
+
133
+ if (profiles.profileExists(name)) {
134
+ console.log(`\n⚠ El perfil "${name}" ya existe.`);
135
+ const useExisting = await prompt(rl, '¿Usar perfil existente? (s/n): ');
136
+ if (useExisting.toLowerCase() !== 's' && useExisting.toLowerCase() !== 'y') {
137
+ return null;
138
+ }
139
+ } else {
140
+ console.log(`\nCreando perfil: ${name}`);
141
+ profiles.createProfile(name, { inheritsFrom: 'default' });
142
+ console.log(' ✓ Perfil creado');
143
+ }
144
+
145
+ return name;
146
+ }
147
+
148
+ // If profile specified, validate it
149
+ if (options.profile) {
150
+ if (!profiles.profileExists(options.profile)) {
151
+ console.log(`\n⚠ Perfil "${options.profile}" no existe.`);
152
+ const create = await prompt(rl, '¿Crear perfil? (s/n): ');
153
+ if (create.toLowerCase() === 's' || create.toLowerCase() === 'y') {
154
+ profiles.createProfile(options.profile, { inheritsFrom: 'default' });
155
+ console.log(' ✓ Perfil creado');
156
+ } else {
157
+ return null;
158
+ }
159
+ }
160
+ return options.profile;
161
+ }
162
+
163
+ // Interactive selection
164
+ const availableProfiles = profiles.listProfiles();
165
+
166
+ console.log('\nPerfiles disponibles:');
167
+ availableProfiles.forEach((p, i) => {
168
+ console.log(` ${i + 1}. ${p}`);
169
+ });
170
+ console.log(` ${availableProfiles.length + 1}. [Crear nuevo perfil]`);
171
+
172
+ const choice = await prompt(rl, `\nSelecciona (1-${availableProfiles.length + 1}): `);
173
+ const choiceNum = parseInt(choice, 10);
174
+
175
+ if (choiceNum === availableProfiles.length + 1) {
176
+ // Create new profile
177
+ const name = await prompt(rl, 'Nombre del nuevo perfil: ');
178
+ if (!name) {
179
+ console.log('Nombre requerido.');
180
+ return null;
181
+ }
182
+
183
+ profiles.createProfile(name, { inheritsFrom: 'default' });
184
+ console.log(' ✓ Perfil creado');
185
+ return name;
186
+ } else if (choiceNum >= 1 && choiceNum <= availableProfiles.length) {
187
+ return availableProfiles[choiceNum - 1];
188
+ }
189
+
190
+ return 'default';
191
+ }
192
+
193
+ /**
194
+ * Select snippets to sync
195
+ */
196
+ async function selectSnippets(rl, snippets, options) {
197
+ if (options.all) {
198
+ return snippets;
199
+ }
200
+
201
+ if (snippets.length === 0) {
202
+ return [];
203
+ }
204
+
205
+ console.log('\nArchivos disponibles para sincronizar:\n');
206
+
207
+ const selected = new Array(snippets.length).fill(true);
208
+
209
+ snippets.forEach((s, i) => {
210
+ const type = s.type === 'snippet' ? '📝' : '📋';
211
+ console.log(` ${i + 1}. [x] ${type} ${s.relativePath}`);
212
+ });
213
+
214
+ console.log(`
215
+ Opciones:
216
+ - Número para toggle (ej: 1)
217
+ - 'a' para seleccionar todos
218
+ - 'n' para deseleccionar todos
219
+ - 'd' para continuar
220
+ `);
221
+
222
+ while (true) {
223
+ const input = await prompt(rl, 'Toggle (1-N), a, n, o d: ');
224
+
225
+ if (input.toLowerCase() === 'd') {
226
+ break;
227
+ } else if (input.toLowerCase() === 'a') {
228
+ selected.fill(true);
229
+ console.log(' ✓ Todos seleccionados');
230
+ } else if (input.toLowerCase() === 'n') {
231
+ selected.fill(false);
232
+ console.log(' ✓ Ninguno seleccionado');
233
+ } else {
234
+ const num = parseInt(input, 10);
235
+ if (num >= 1 && num <= snippets.length) {
236
+ selected[num - 1] = !selected[num - 1];
237
+ const status = selected[num - 1] ? '✓' : '✗';
238
+ console.log(` ${status} ${snippets[num - 1].relativePath}`);
239
+ }
240
+ }
241
+ }
242
+
243
+ return snippets.filter((_, i) => selected[i]);
244
+ }
245
+
246
+ /**
247
+ * Sync snippets to profile
248
+ */
249
+ function syncToProfile(snippets, profileName, overwrite) {
250
+ const results = {
251
+ copied: 0,
252
+ skipped: 0,
253
+ errors: [],
254
+ };
255
+
256
+ const profileSnippetsDir = path.join(profiles.PROFILES_DIR, profileName, 'snippets');
257
+
258
+ for (const snippet of snippets) {
259
+ const targetPath = path.join(profileSnippetsDir, snippet.relativePath);
260
+ const targetDir = path.dirname(targetPath);
261
+
262
+ // Check if file exists
263
+ if (fs.existsSync(targetPath) && !overwrite) {
264
+ results.skipped++;
265
+ continue;
266
+ }
267
+
268
+ try {
269
+ // Create directory if needed
270
+ if (!fs.existsSync(targetDir)) {
271
+ fs.mkdirSync(targetDir, { recursive: true });
272
+ }
273
+
274
+ // Copy file
275
+ fs.copyFileSync(snippet.path, targetPath);
276
+ results.copied++;
277
+ } catch (e) {
278
+ results.errors.push({
279
+ file: snippet.relativePath,
280
+ error: e.message,
281
+ });
282
+ }
283
+ }
284
+
285
+ return results;
286
+ }
287
+
288
+ /**
289
+ * Main sync-profile command handler
290
+ */
291
+ async function syncProfileCommand(args, _config) {
292
+ const options = parseArgs(args);
293
+
294
+ if (options.help) {
295
+ showHelp();
296
+ return;
297
+ }
298
+
299
+ // Check if repo exists
300
+ const repoPath = profiles.getRepoPath();
301
+ if (!repoPath || !fs.existsSync(repoPath)) {
302
+ console.log(`
303
+ No se encontró el repositorio de BI.
304
+
305
+ Ejecuta primero:
306
+ super setup
307
+ `);
308
+ process.exit(1);
309
+ }
310
+
311
+ console.log(`
312
+ ════════════════════════════════════════════════════════════════
313
+ Sync Profile - Guardar snippets al perfil base
314
+ ════════════════════════════════════════════════════════════════
315
+ `);
316
+
317
+ // Get snippets from repo
318
+ const snippets = getRepoSnippets(repoPath);
319
+
320
+ if (snippets.length === 0) {
321
+ console.log('No hay snippets o standards para sincronizar.');
322
+ console.log('\nCrea archivos .md en:');
323
+ console.log(` ${path.join(repoPath, 'snippets')}/`);
324
+ console.log(` ${path.join(repoPath, 'standards')}/`);
325
+ return;
326
+ }
327
+
328
+ console.log(`Encontrados: ${snippets.length} archivos\n`);
329
+
330
+ const rl = createReadline();
331
+
332
+ try {
333
+ // Select profile
334
+ const targetProfile = await selectProfile(rl, options);
335
+
336
+ if (!targetProfile) {
337
+ console.log('\nCancelado.');
338
+ return;
339
+ }
340
+
341
+ console.log(`\nPerfil destino: ${targetProfile}`);
342
+
343
+ // Select snippets
344
+ const selectedSnippets = await selectSnippets(rl, snippets, options);
345
+
346
+ if (selectedSnippets.length === 0) {
347
+ console.log('\nNo hay archivos seleccionados.');
348
+ return;
349
+ }
350
+
351
+ console.log(`\nSincronizando ${selectedSnippets.length} archivos...`);
352
+
353
+ // Check for conflicts if not overwrite mode
354
+ if (!options.overwrite) {
355
+ const profileSnippetsDir = path.join(profiles.PROFILES_DIR, targetProfile, 'snippets');
356
+ const conflicts = selectedSnippets.filter((s) =>
357
+ fs.existsSync(path.join(profileSnippetsDir, s.relativePath))
358
+ );
359
+
360
+ if (conflicts.length > 0) {
361
+ console.log(`\n⚠ ${conflicts.length} archivo(s) ya existen en el perfil:`);
362
+ conflicts.slice(0, 5).forEach((c) => {
363
+ console.log(` - ${c.relativePath}`);
364
+ });
365
+ if (conflicts.length > 5) {
366
+ console.log(` ... y ${conflicts.length - 5} más`);
367
+ }
368
+
369
+ const overwriteChoice = await prompt(rl, '\n¿Sobrescribir existentes? (s/n): ');
370
+ options.overwrite =
371
+ overwriteChoice.toLowerCase() === 's' || overwriteChoice.toLowerCase() === 'y';
372
+ }
373
+ }
374
+
375
+ // Sync
376
+ const results = syncToProfile(selectedSnippets, targetProfile, options.overwrite);
377
+
378
+ console.log(`
379
+ ════════════════════════════════════════════════════════════════
380
+ Resumen
381
+ ════════════════════════════════════════════════════════════════
382
+
383
+ Perfil: ${targetProfile}
384
+ Copiados: ${results.copied}
385
+ Omitidos: ${results.skipped}
386
+ Errores: ${results.errors.length}
387
+
388
+ Los snippets estarán disponibles en proyectos que usen
389
+ el perfil "${targetProfile}".
390
+
391
+ ════════════════════════════════════════════════════════════════
392
+ `);
393
+
394
+ if (results.errors.length > 0) {
395
+ console.log('Errores:');
396
+ results.errors.forEach((e) => {
397
+ console.log(` ✗ ${e.file}: ${e.error}`);
398
+ });
399
+ }
400
+ } finally {
401
+ rl.close();
402
+ }
403
+ }
404
+
405
+ module.exports = syncProfileCommand;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tests for Sync Profile Command
3
+ * @module commands/sync-profile.test
4
+ */
5
+
6
+ const { test, describe } = require('node:test');
7
+ const assert = require('node:assert');
8
+
9
+ describe('Sync Profile Command', () => {
10
+ test('module exports a function', () => {
11
+ const syncProfileCommand = require('./sync-profile');
12
+ assert.strictEqual(typeof syncProfileCommand, 'function');
13
+ });
14
+ });