@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.
- package/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +25 -0
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +265 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/build-plugin.js +30 -0
- package/bin/cli.js +1064 -0
- package/bin/commands/add.js +533 -0
- package/bin/commands/add.test.js +77 -0
- package/bin/commands/build-desktop.js +166 -0
- package/bin/commands/changelog.js +443 -0
- package/bin/commands/diff.js +325 -0
- package/bin/commands/lint.js +419 -0
- package/bin/commands/lint.test.js +103 -0
- package/bin/commands/mcp-setup.js +246 -0
- package/bin/commands/pull.js +287 -0
- package/bin/commands/pull.test.js +36 -0
- package/bin/commands/push.js +231 -0
- package/bin/commands/push.test.js +14 -0
- package/bin/commands/search.js +344 -0
- package/bin/commands/search.test.js +115 -0
- package/bin/commands/setup.js +545 -0
- package/bin/commands/setup.test.js +46 -0
- package/bin/commands/sync-profile.js +405 -0
- package/bin/commands/sync-profile.test.js +14 -0
- package/bin/commands/sync-source.js +418 -0
- package/bin/commands/sync-source.test.js +14 -0
- package/bin/commands/watch.js +206 -0
- package/bin/lib/generators/claude-plugin.js +266 -0
- package/bin/lib/generators/claude-plugin.test.js +110 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +282 -0
- package/bin/lib/licensing/index.js +35 -0
- package/bin/lib/licensing/storage.js +364 -0
- package/bin/lib/licensing/storage.test.js +55 -0
- package/bin/lib/licensing/validator.js +213 -0
- package/bin/lib/licensing/validator.test.js +137 -0
- package/bin/lib/microsoft-mcp.js +176 -0
- package/bin/lib/microsoft-mcp.test.js +106 -0
- package/bin/lib/skills.js +84 -0
- package/bin/mcp/powerbi-modeling-launcher.js +38 -0
- package/bin/postinstall.js +44 -0
- package/bin/utils/errors.js +159 -0
- package/bin/utils/git.js +298 -0
- package/bin/utils/logger.js +142 -0
- package/bin/utils/mcp-detect.js +274 -0
- package/bin/utils/mcp-detect.test.js +105 -0
- package/bin/utils/pbix.js +305 -0
- package/bin/utils/pbix.test.js +37 -0
- package/bin/utils/profiles.js +312 -0
- package/bin/utils/projects.js +168 -0
- package/bin/utils/readline.js +206 -0
- package/bin/utils/readline.test.js +47 -0
- package/bin/utils/tui.js +314 -0
- package/bin/utils/tui.test.js +127 -0
- package/commands/contributions.md +265 -0
- package/commands/data-model-design.md +468 -0
- package/commands/dax-doctor.md +248 -0
- package/commands/fabric-scripts.md +452 -0
- package/commands/migration-assistant.md +290 -0
- package/commands/model-documenter.md +242 -0
- package/commands/pbi-connect.md +239 -0
- package/commands/project-kickoff.md +905 -0
- package/commands/report-layout.md +296 -0
- package/commands/rls-design.md +533 -0
- package/commands/theme-tweaker.md +624 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +37 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +95 -0
- package/docs/openrouter-free-models.md +92 -0
- package/library/examples/README.md +151 -0
- package/library/examples/finance-reporting/README.md +351 -0
- package/library/examples/finance-reporting/data-model.md +267 -0
- package/library/examples/finance-reporting/measures.dax +557 -0
- package/library/examples/hr-analytics/README.md +371 -0
- package/library/examples/hr-analytics/data-model.md +315 -0
- package/library/examples/hr-analytics/measures.dax +460 -0
- package/library/examples/marketing-analytics/README.md +37 -0
- package/library/examples/marketing-analytics/data-model.md +62 -0
- package/library/examples/marketing-analytics/measures.dax +110 -0
- package/library/examples/retail-analytics/README.md +439 -0
- package/library/examples/retail-analytics/data-model.md +288 -0
- package/library/examples/retail-analytics/measures.dax +481 -0
- package/library/examples/supply-chain/README.md +37 -0
- package/library/examples/supply-chain/data-model.md +69 -0
- package/library/examples/supply-chain/measures.dax +77 -0
- package/library/examples/udf-library/README.md +228 -0
- package/library/examples/udf-library/functions.dax +571 -0
- package/library/snippets/dax/README.md +292 -0
- package/library/snippets/dax/business-domains.md +576 -0
- package/library/snippets/dax/calculate-patterns.md +276 -0
- package/library/snippets/dax/calculation-groups.md +489 -0
- package/library/snippets/dax/error-handling.md +495 -0
- package/library/snippets/dax/iterators-and-aggregations.md +474 -0
- package/library/snippets/dax/kpis-and-metrics.md +293 -0
- package/library/snippets/dax/rankings-and-topn.md +235 -0
- package/library/snippets/dax/security-patterns.md +413 -0
- package/library/snippets/dax/text-and-formatting.md +316 -0
- package/library/snippets/dax/time-intelligence.md +196 -0
- package/library/snippets/dax/user-defined-functions.md +477 -0
- package/library/snippets/dax/virtual-tables.md +546 -0
- package/library/snippets/excel-formulas/README.md +84 -0
- package/library/snippets/excel-formulas/aggregations.md +330 -0
- package/library/snippets/excel-formulas/dates-and-times.md +361 -0
- package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
- package/library/snippets/excel-formulas/lookups.md +169 -0
- package/library/snippets/excel-formulas/text-functions.md +363 -0
- package/library/snippets/governance/naming-conventions.md +97 -0
- package/library/snippets/governance/review-checklists.md +107 -0
- package/library/snippets/power-query/README.md +389 -0
- package/library/snippets/power-query/api-integration.md +707 -0
- package/library/snippets/power-query/connections.md +434 -0
- package/library/snippets/power-query/data-cleaning.md +298 -0
- package/library/snippets/power-query/error-handling.md +526 -0
- package/library/snippets/power-query/parameters.md +350 -0
- package/library/snippets/power-query/performance.md +506 -0
- package/library/snippets/power-query/transformations.md +330 -0
- package/library/snippets/report-design/accessibility.md +78 -0
- package/library/snippets/report-design/chart-selection.md +54 -0
- package/library/snippets/report-design/layout-patterns.md +87 -0
- package/library/templates/data-models/README.md +93 -0
- package/library/templates/data-models/finance-model.md +627 -0
- package/library/templates/data-models/retail-star-schema.md +473 -0
- package/library/templates/excel/README.md +83 -0
- package/library/templates/excel/budget-tracker.md +432 -0
- package/library/templates/excel/data-entry-form.md +533 -0
- package/library/templates/power-bi/README.md +72 -0
- package/library/templates/power-bi/finance-report.md +449 -0
- package/library/templates/power-bi/kpi-scorecard.md +461 -0
- package/library/templates/power-bi/sales-dashboard.md +281 -0
- package/library/themes/excel/README.md +436 -0
- package/library/themes/power-bi/README.md +271 -0
- package/library/themes/power-bi/accessible.json +307 -0
- package/library/themes/power-bi/bi-superpowers-default.json +858 -0
- package/library/themes/power-bi/corporate-blue.json +291 -0
- package/library/themes/power-bi/dark-mode.json +291 -0
- package/library/themes/power-bi/minimal.json +292 -0
- package/library/themes/power-bi/print-friendly.json +309 -0
- package/package.json +93 -0
- package/skills/contributions/SKILL.md +267 -0
- package/skills/data-model-design/SKILL.md +470 -0
- package/skills/data-modeling/SKILL.md +254 -0
- package/skills/data-quality/SKILL.md +664 -0
- package/skills/dax/SKILL.md +708 -0
- package/skills/dax-doctor/SKILL.md +250 -0
- package/skills/dax-udf/SKILL.md +489 -0
- package/skills/deployment/SKILL.md +320 -0
- package/skills/excel-formulas/SKILL.md +463 -0
- package/skills/fabric-scripts/SKILL.md +454 -0
- package/skills/fast-standard/SKILL.md +509 -0
- package/skills/governance/SKILL.md +205 -0
- package/skills/migration-assistant/SKILL.md +292 -0
- package/skills/model-documenter/SKILL.md +244 -0
- package/skills/pbi-connect/SKILL.md +241 -0
- package/skills/power-query/SKILL.md +406 -0
- package/skills/project-kickoff/SKILL.md +907 -0
- package/skills/query-performance/SKILL.md +480 -0
- package/skills/report-design/SKILL.md +207 -0
- package/skills/report-layout/SKILL.md +298 -0
- package/skills/rls-design/SKILL.md +535 -0
- package/skills/semantic-model/SKILL.md +237 -0
- package/skills/testing-validation/SKILL.md +643 -0
- package/skills/theme-tweaker/SKILL.md +626 -0
- package/src/content/base.md +237 -0
- package/src/content/mcp-requirements.json +69 -0
- package/src/content/routing.md +203 -0
- package/src/content/skills/contributions.md +259 -0
- package/src/content/skills/data-model-design.md +462 -0
- package/src/content/skills/data-modeling.md +246 -0
- package/src/content/skills/data-quality.md +656 -0
- package/src/content/skills/dax-doctor.md +242 -0
- package/src/content/skills/dax-udf.md +481 -0
- package/src/content/skills/dax.md +700 -0
- package/src/content/skills/deployment.md +312 -0
- package/src/content/skills/excel-formulas.md +455 -0
- package/src/content/skills/fabric-scripts.md +446 -0
- package/src/content/skills/fast-standard.md +501 -0
- package/src/content/skills/governance.md +197 -0
- package/src/content/skills/migration-assistant.md +284 -0
- package/src/content/skills/model-documenter.md +236 -0
- package/src/content/skills/pbi-connect.md +233 -0
- package/src/content/skills/power-query.md +398 -0
- package/src/content/skills/project-kickoff.md +899 -0
- package/src/content/skills/query-performance.md +472 -0
- package/src/content/skills/report-design.md +199 -0
- package/src/content/skills/report-layout.md +290 -0
- package/src/content/skills/rls-design.md +527 -0
- package/src/content/skills/semantic-model.md +229 -0
- package/src/content/skills/testing-validation.md +635 -0
- 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
|
+
});
|