@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,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add Command - Add Project to Repository
|
|
3
|
+
* =========================================
|
|
4
|
+
*
|
|
5
|
+
* Adds a Power BI or Excel project to the bi-repo.
|
|
6
|
+
* Extracts versionable content (TMDL, queries) and creates project config.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* super add "C:/path/to/file.pbix"
|
|
10
|
+
* super add "C:/path/to/file.pbix" --profile finance
|
|
11
|
+
* super add "C:/path/to/file.xlsx" --name budget-2026
|
|
12
|
+
*
|
|
13
|
+
* @module commands/add
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const git = require('../utils/git');
|
|
20
|
+
const profiles = require('../utils/profiles');
|
|
21
|
+
const pbix = require('../utils/pbix');
|
|
22
|
+
const rl = require('../utils/readline');
|
|
23
|
+
|
|
24
|
+
// Using shared readline utilities
|
|
25
|
+
const { createReadline, prompt } = rl;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse command line arguments
|
|
29
|
+
* @param {string[]} args - CLI arguments
|
|
30
|
+
* @returns {Object} Parsed options
|
|
31
|
+
*/
|
|
32
|
+
function parseArgs(args) {
|
|
33
|
+
const options = {
|
|
34
|
+
filePath: null,
|
|
35
|
+
name: null,
|
|
36
|
+
profile: null,
|
|
37
|
+
help: false,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < args.length; i++) {
|
|
41
|
+
const arg = args[i];
|
|
42
|
+
|
|
43
|
+
if (arg === '--name' || arg === '-n') {
|
|
44
|
+
options.name = args[++i];
|
|
45
|
+
} else if (arg === '--profile' || arg === '-p') {
|
|
46
|
+
options.profile = args[++i];
|
|
47
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
48
|
+
options.help = true;
|
|
49
|
+
} else if (!arg.startsWith('-') && !options.filePath) {
|
|
50
|
+
options.filePath = arg;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return options;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Readline functions imported from ../utils/readline.js
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Show help message
|
|
61
|
+
*/
|
|
62
|
+
function showHelp() {
|
|
63
|
+
console.log(`
|
|
64
|
+
super add - Añadir un proyecto BI al repositorio
|
|
65
|
+
|
|
66
|
+
Uso:
|
|
67
|
+
super add <archivo> Añadir un proyecto
|
|
68
|
+
super add <archivo> --profile <nombre> Añadir con perfil específico
|
|
69
|
+
super add <archivo> --name <nombre> Especificar nombre del proyecto
|
|
70
|
+
|
|
71
|
+
Argumentos:
|
|
72
|
+
<archivo> Ruta al archivo .pbix, .pbip o .xlsx
|
|
73
|
+
|
|
74
|
+
Opciones:
|
|
75
|
+
--name, -n <nombre> Nombre personalizado del proyecto (slug)
|
|
76
|
+
--profile, -p <nombre> Perfil a usar (default, finance, retail, etc.)
|
|
77
|
+
--help, -h Mostrar esta ayuda
|
|
78
|
+
|
|
79
|
+
Ejemplos:
|
|
80
|
+
super add "C:/Users/Juan/Documents/Sales.pbix"
|
|
81
|
+
super add "./Dashboard.pbix" --profile finance
|
|
82
|
+
super add "Budget.xlsx" --name budget-2026 --profile finance
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get or create project name
|
|
88
|
+
*/
|
|
89
|
+
async function getProjectName(rl, fileInfo, customName) {
|
|
90
|
+
if (customName) {
|
|
91
|
+
return pbix.generateSlug(customName);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const suggested = pbix.generateSlug(fileInfo.name);
|
|
95
|
+
console.log(`\nNombre detectado: ${fileInfo.name}`);
|
|
96
|
+
const input = await prompt(rl, `Nombre del proyecto [${suggested}]: `);
|
|
97
|
+
|
|
98
|
+
return input ? pbix.generateSlug(input) : suggested;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Select profile for the project
|
|
103
|
+
*/
|
|
104
|
+
async function selectProfile(rl, customProfile) {
|
|
105
|
+
if (customProfile) {
|
|
106
|
+
// Validate profile exists or create it
|
|
107
|
+
if (!profiles.profileExists(customProfile)) {
|
|
108
|
+
console.log(`\nPerfil "${customProfile}" no existe. Creándolo...`);
|
|
109
|
+
profiles.createProfile(customProfile, { inheritsFrom: 'default' });
|
|
110
|
+
}
|
|
111
|
+
return customProfile;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const availableProfiles = profiles.listProfiles();
|
|
115
|
+
|
|
116
|
+
console.log('\nPerfil a usar:');
|
|
117
|
+
availableProfiles.forEach((p, i) => {
|
|
118
|
+
console.log(` ${i + 1}. ${p}`);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const choice = await prompt(rl, `\nSelecciona (1-${availableProfiles.length}): `);
|
|
122
|
+
const choiceNum = parseInt(choice, 10);
|
|
123
|
+
|
|
124
|
+
if (choiceNum >= 1 && choiceNum <= availableProfiles.length) {
|
|
125
|
+
return availableProfiles[choiceNum - 1];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return 'default';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create project directory in repo
|
|
133
|
+
*/
|
|
134
|
+
function createProjectDirectory(repoPath, projectName) {
|
|
135
|
+
const projectDir = path.join(repoPath, 'projects', projectName);
|
|
136
|
+
|
|
137
|
+
if (fs.existsSync(projectDir)) {
|
|
138
|
+
return { exists: true, path: projectDir };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
142
|
+
fs.mkdirSync(path.join(projectDir, 'definition'), { recursive: true });
|
|
143
|
+
fs.mkdirSync(path.join(projectDir, 'queries'), { recursive: true });
|
|
144
|
+
|
|
145
|
+
return { exists: false, path: projectDir };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Extract content based on file type
|
|
150
|
+
*/
|
|
151
|
+
function extractContent(fileInfo, projectDir) {
|
|
152
|
+
const result = {
|
|
153
|
+
success: false,
|
|
154
|
+
files: [],
|
|
155
|
+
message: '',
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (fileInfo.type === 'power-bi-project') {
|
|
159
|
+
// PBIP - extract TMDL directly
|
|
160
|
+
const extraction = pbix.extractPbipToRepo(fileInfo.path, projectDir);
|
|
161
|
+
if (extraction.success) {
|
|
162
|
+
result.success = true;
|
|
163
|
+
result.files = extraction.files;
|
|
164
|
+
result.message = `Extraídos ${extraction.files.length} archivos TMDL`;
|
|
165
|
+
} else {
|
|
166
|
+
result.message = extraction.error;
|
|
167
|
+
}
|
|
168
|
+
} else if (fileInfo.type === 'power-bi') {
|
|
169
|
+
// PBIX - binary file, cannot extract directly
|
|
170
|
+
// We'll create a placeholder and instruct user to save as PBIP
|
|
171
|
+
result.success = true;
|
|
172
|
+
result.message = `
|
|
173
|
+
NOTA: Los archivos .pbix son binarios y no se pueden versionar directamente.
|
|
174
|
+
|
|
175
|
+
Para versionar tu modelo, tienes dos opciones:
|
|
176
|
+
|
|
177
|
+
1. RECOMENDADO: Guarda como proyecto PBIP
|
|
178
|
+
- En Power BI Desktop: File → Save as → Power BI Project (.pbip)
|
|
179
|
+
- Luego ejecuta: super add "ruta/al/proyecto.pbip"
|
|
180
|
+
|
|
181
|
+
2. Usar el formato actual:
|
|
182
|
+
- El proyecto se añadirá con referencia al .pbix
|
|
183
|
+
- Los cambios se detectarán por fecha de modificación
|
|
184
|
+
- No podrás ver diferencias detalladas en Git`;
|
|
185
|
+
|
|
186
|
+
// Create a note file
|
|
187
|
+
const noteContent = `# ${fileInfo.name}
|
|
188
|
+
|
|
189
|
+
Este proyecto está vinculado a un archivo .pbix binario.
|
|
190
|
+
|
|
191
|
+
## Archivo Original
|
|
192
|
+
- Ruta: ${fileInfo.path}
|
|
193
|
+
- Tipo: Power BI Desktop (.pbix)
|
|
194
|
+
|
|
195
|
+
## Para mejor versionado
|
|
196
|
+
|
|
197
|
+
Considera guardar como Power BI Project (.pbip):
|
|
198
|
+
1. Abre el archivo en Power BI Desktop
|
|
199
|
+
2. File → Save as → Power BI Project (.pbip)
|
|
200
|
+
3. Ejecuta: super add "ruta/al/proyecto.pbip"
|
|
201
|
+
|
|
202
|
+
Los archivos .pbip contienen TMDL (texto) que Git puede versionar correctamente.
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
fs.writeFileSync(path.join(projectDir, 'README.md'), noteContent);
|
|
206
|
+
result.files = ['README.md'];
|
|
207
|
+
} else if (fileInfo.type === 'excel' || fileInfo.type === 'excel-macro') {
|
|
208
|
+
// Excel - create documentation placeholder
|
|
209
|
+
result.success = true;
|
|
210
|
+
|
|
211
|
+
const workbookDir = path.join(projectDir, 'workbook');
|
|
212
|
+
if (!fs.existsSync(workbookDir)) {
|
|
213
|
+
fs.mkdirSync(workbookDir, { recursive: true });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const structureContent = `# ${fileInfo.name}
|
|
217
|
+
|
|
218
|
+
## Estructura del Workbook
|
|
219
|
+
|
|
220
|
+
Documenta aquí la estructura de tu workbook:
|
|
221
|
+
|
|
222
|
+
### Hojas
|
|
223
|
+
-
|
|
224
|
+
|
|
225
|
+
### Tablas
|
|
226
|
+
-
|
|
227
|
+
|
|
228
|
+
### Rangos Nombrados
|
|
229
|
+
-
|
|
230
|
+
|
|
231
|
+
### Conexiones de Datos
|
|
232
|
+
-
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
fs.writeFileSync(path.join(workbookDir, 'structure.md'), structureContent);
|
|
236
|
+
|
|
237
|
+
const formulasContent = `# Fórmulas Importantes
|
|
238
|
+
|
|
239
|
+
Documenta aquí las fórmulas clave de tu workbook:
|
|
240
|
+
|
|
241
|
+
## Cálculos Principales
|
|
242
|
+
|
|
243
|
+
\`\`\`excel
|
|
244
|
+
=EXAMPLE_FORMULA()
|
|
245
|
+
\`\`\`
|
|
246
|
+
|
|
247
|
+
## Lookups
|
|
248
|
+
|
|
249
|
+
## Agregaciones
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
fs.writeFileSync(path.join(workbookDir, 'formulas.md'), formulasContent);
|
|
253
|
+
|
|
254
|
+
result.files = ['workbook/structure.md', 'workbook/formulas.md'];
|
|
255
|
+
result.message = 'Creada estructura para documentar el workbook Excel';
|
|
256
|
+
} else {
|
|
257
|
+
result.message = 'Tipo de archivo no soportado';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Update repo config with new project
|
|
265
|
+
*/
|
|
266
|
+
function updateRepoConfig(repoPath, projectName, projectConfig) {
|
|
267
|
+
const configPath = path.join(repoPath, '.bi-superpowers.json');
|
|
268
|
+
|
|
269
|
+
let config = { version: '3.0', projects: [] };
|
|
270
|
+
if (fs.existsSync(configPath)) {
|
|
271
|
+
try {
|
|
272
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
273
|
+
} catch (e) {
|
|
274
|
+
// Use default
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Add or update project
|
|
279
|
+
const existingIndex = config.projects.findIndex((p) => p.name === projectName);
|
|
280
|
+
if (existingIndex >= 0) {
|
|
281
|
+
config.projects[existingIndex] = projectConfig;
|
|
282
|
+
} else {
|
|
283
|
+
config.projects.push(projectConfig);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Main add command handler
|
|
291
|
+
*/
|
|
292
|
+
async function addCommand(args, _config) {
|
|
293
|
+
const options = parseArgs(args);
|
|
294
|
+
|
|
295
|
+
if (options.help) {
|
|
296
|
+
showHelp();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check if repo exists
|
|
301
|
+
const repoPath = profiles.getRepoPath();
|
|
302
|
+
if (!repoPath || !fs.existsSync(repoPath)) {
|
|
303
|
+
console.log(`
|
|
304
|
+
No se encontró el repositorio de BI.
|
|
305
|
+
|
|
306
|
+
Ejecuta primero:
|
|
307
|
+
super setup
|
|
308
|
+
|
|
309
|
+
Para crear tu repositorio de proyectos.
|
|
310
|
+
`);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check if file path provided
|
|
315
|
+
if (!options.filePath) {
|
|
316
|
+
console.log(`
|
|
317
|
+
Uso: super add <archivo>
|
|
318
|
+
|
|
319
|
+
Ejemplo:
|
|
320
|
+
super add "C:/Users/Juan/Documents/Sales.pbix"
|
|
321
|
+
super add ./Dashboard.pbip --profile finance
|
|
322
|
+
`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Resolve and validate file path
|
|
327
|
+
const filePath = path.resolve(options.filePath);
|
|
328
|
+
|
|
329
|
+
// Security: Validate path to prevent path traversal attacks
|
|
330
|
+
// Ensure the resolved path is within expected locations (user's folders, not system paths)
|
|
331
|
+
const normalizedPath = path.normalize(filePath);
|
|
332
|
+
const systemPaths = [
|
|
333
|
+
path.normalize('C:\\Windows'),
|
|
334
|
+
path.normalize('C:\\Program Files'),
|
|
335
|
+
path.normalize('C:\\Program Files (x86)'),
|
|
336
|
+
'/etc',
|
|
337
|
+
'/usr',
|
|
338
|
+
'/bin',
|
|
339
|
+
'/sbin',
|
|
340
|
+
'/var',
|
|
341
|
+
'/root',
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
const isSystemPath = systemPaths.some((sysPath) =>
|
|
345
|
+
normalizedPath.toLowerCase().startsWith(sysPath.toLowerCase())
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
if (isSystemPath) {
|
|
349
|
+
console.log('\n✗ Access denied: Cannot add files from system directories');
|
|
350
|
+
console.log(' Please use files from your user directories.');
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const fileInfo = pbix.detectFileType(filePath);
|
|
355
|
+
|
|
356
|
+
if (!fileInfo.exists) {
|
|
357
|
+
console.log(`\n✗ Archivo no encontrado: ${filePath}`);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (fileInfo.type === 'unknown') {
|
|
362
|
+
console.log(`\n✗ Tipo de archivo no soportado: ${fileInfo.extension}`);
|
|
363
|
+
console.log(' Formatos soportados: .pbix, .pbip, .xlsx, .xlsm');
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
console.log(`
|
|
368
|
+
════════════════════════════════════════════════════════════════
|
|
369
|
+
Añadir Proyecto
|
|
370
|
+
════════════════════════════════════════════════════════════════
|
|
371
|
+
|
|
372
|
+
Archivo: ${fileInfo.name}${fileInfo.extension}
|
|
373
|
+
Tipo: ${fileInfo.type}
|
|
374
|
+
Ruta: ${filePath}
|
|
375
|
+
`);
|
|
376
|
+
|
|
377
|
+
const rl = createReadline();
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
// Get project name
|
|
381
|
+
const projectName = await getProjectName(rl, fileInfo, options.name);
|
|
382
|
+
|
|
383
|
+
// Select profile
|
|
384
|
+
const profile = await selectProfile(rl, options.profile);
|
|
385
|
+
|
|
386
|
+
// Create project directory
|
|
387
|
+
const projectDir = createProjectDirectory(repoPath, projectName);
|
|
388
|
+
|
|
389
|
+
if (projectDir.exists) {
|
|
390
|
+
const overwrite = await prompt(
|
|
391
|
+
rl,
|
|
392
|
+
`\nEl proyecto "${projectName}" ya existe. ¿Sobrescribir? (s/n): `
|
|
393
|
+
);
|
|
394
|
+
if (overwrite.toLowerCase() !== 's' && overwrite.toLowerCase() !== 'y') {
|
|
395
|
+
console.log('\nCancelado.');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
console.log(`\nCreando proyecto: ${projectName}`);
|
|
401
|
+
console.log(` Directorio: ${projectDir.path}`);
|
|
402
|
+
console.log(` Perfil: ${profile}`);
|
|
403
|
+
|
|
404
|
+
// Extract content
|
|
405
|
+
const extraction = extractContent(fileInfo, projectDir.path);
|
|
406
|
+
|
|
407
|
+
if (extraction.success) {
|
|
408
|
+
console.log(`\n ✓ ${extraction.message}`);
|
|
409
|
+
|
|
410
|
+
if (extraction.files.length > 0) {
|
|
411
|
+
console.log(' Archivos:');
|
|
412
|
+
extraction.files.slice(0, 5).forEach((f) => {
|
|
413
|
+
console.log(` - ${f}`);
|
|
414
|
+
});
|
|
415
|
+
if (extraction.files.length > 5) {
|
|
416
|
+
console.log(` ... y ${extraction.files.length - 5} más`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} else if (extraction.message) {
|
|
420
|
+
console.log(extraction.message);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Create project.json (legacy, for backward compatibility)
|
|
424
|
+
const projectConfig = pbix.createProjectConfig({
|
|
425
|
+
name: projectName,
|
|
426
|
+
displayName: fileInfo.name,
|
|
427
|
+
type: fileInfo.type,
|
|
428
|
+
profile: profile,
|
|
429
|
+
sourcePath: filePath,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
fs.writeFileSync(
|
|
433
|
+
path.join(projectDir.path, 'project.json'),
|
|
434
|
+
JSON.stringify(projectConfig, null, 2)
|
|
435
|
+
);
|
|
436
|
+
console.log(' ✓ Creado: project.json');
|
|
437
|
+
|
|
438
|
+
// Create bi-project.json (versioned project config - NEW in v2.4)
|
|
439
|
+
const biProjectConfig = {
|
|
440
|
+
version: '1.0',
|
|
441
|
+
name: projectName,
|
|
442
|
+
displayName: fileInfo.name,
|
|
443
|
+
type: fileInfo.type,
|
|
444
|
+
profile: profile,
|
|
445
|
+
sourcePath: filePath,
|
|
446
|
+
created: new Date().toISOString(),
|
|
447
|
+
mcpServers: {
|
|
448
|
+
'powerbi-modeling-mcp': {
|
|
449
|
+
enabled: true,
|
|
450
|
+
launcher: 'official-microsoft',
|
|
451
|
+
},
|
|
452
|
+
'powerbi-remote': {
|
|
453
|
+
enabled: false,
|
|
454
|
+
semanticModelId: null,
|
|
455
|
+
},
|
|
456
|
+
'fabric-mcp-server': {
|
|
457
|
+
enabled: true,
|
|
458
|
+
mode: 'all',
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
changelog: {
|
|
462
|
+
enabled: true,
|
|
463
|
+
path: 'CHANGELOG.md',
|
|
464
|
+
includeInAgentContext: true,
|
|
465
|
+
recentChangesCount: 5,
|
|
466
|
+
},
|
|
467
|
+
agentContext: {
|
|
468
|
+
includeRecentChanges: true,
|
|
469
|
+
customInstructions: null,
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
fs.writeFileSync(
|
|
474
|
+
path.join(projectDir.path, 'bi-project.json'),
|
|
475
|
+
JSON.stringify(biProjectConfig, null, 2)
|
|
476
|
+
);
|
|
477
|
+
console.log(' ✓ Creado: bi-project.json (configuración versionada)');
|
|
478
|
+
|
|
479
|
+
// Create initial CHANGELOG.md
|
|
480
|
+
const changelogContent = `# Changelog - ${fileInfo.name}
|
|
481
|
+
|
|
482
|
+
All notable changes to this project will be documented in this file.
|
|
483
|
+
|
|
484
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
485
|
+
|
|
486
|
+
## [Unreleased]
|
|
487
|
+
|
|
488
|
+
### Added
|
|
489
|
+
- Initial project setup with BI Agent Superpowers
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
*Generated by [BI Agent Superpowers](https://github.com/luquimbo/bi-superpowers)*
|
|
493
|
+
`;
|
|
494
|
+
|
|
495
|
+
fs.writeFileSync(path.join(projectDir.path, 'CHANGELOG.md'), changelogContent);
|
|
496
|
+
console.log(' ✓ Creado: CHANGELOG.md');
|
|
497
|
+
|
|
498
|
+
// Update repo config
|
|
499
|
+
updateRepoConfig(repoPath, projectName, {
|
|
500
|
+
name: projectName,
|
|
501
|
+
type: fileInfo.type,
|
|
502
|
+
profile: profile,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Git commit
|
|
506
|
+
if (git.isGitRepo(repoPath)) {
|
|
507
|
+
git.stageFiles(repoPath, '.');
|
|
508
|
+
git.commit(repoPath, `Add project: ${projectName}`);
|
|
509
|
+
console.log(` ✓ Commit: "Add project: ${projectName}"`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
console.log(`
|
|
513
|
+
════════════════════════════════════════════════════════════════
|
|
514
|
+
¡Proyecto añadido!
|
|
515
|
+
════════════════════════════════════════════════════════════════
|
|
516
|
+
|
|
517
|
+
Los archivos originales permanecen en:
|
|
518
|
+
${filePath}
|
|
519
|
+
|
|
520
|
+
Para traer cambios del original al repo:
|
|
521
|
+
super pull ${projectName}
|
|
522
|
+
|
|
523
|
+
Para ver el historial:
|
|
524
|
+
cd "${repoPath}" && git log --oneline
|
|
525
|
+
|
|
526
|
+
════════════════════════════════════════════════════════════════
|
|
527
|
+
`);
|
|
528
|
+
} finally {
|
|
529
|
+
rl.close();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
module.exports = addCommand;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Add Command
|
|
3
|
+
* @module commands/add.test
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { test, describe } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
describe('Add Command', () => {
|
|
11
|
+
test('module exports a function', () => {
|
|
12
|
+
const addCommand = require('./add');
|
|
13
|
+
assert.strictEqual(typeof addCommand, 'function');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Add Command - File Type Detection', () => {
|
|
18
|
+
test('detects .pbix files', () => {
|
|
19
|
+
const pbix = require('../utils/pbix');
|
|
20
|
+
const result = pbix.detectFileType('/path/to/file.pbix');
|
|
21
|
+
assert.strictEqual(result.type, 'power-bi');
|
|
22
|
+
assert.strictEqual(result.extension, '.pbix');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('detects .pbip files', () => {
|
|
26
|
+
const pbix = require('../utils/pbix');
|
|
27
|
+
const result = pbix.detectFileType('/path/to/file.pbip');
|
|
28
|
+
assert.strictEqual(result.type, 'power-bi-project');
|
|
29
|
+
assert.strictEqual(result.extension, '.pbip');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('detects .xlsx files', () => {
|
|
33
|
+
const pbix = require('../utils/pbix');
|
|
34
|
+
const result = pbix.detectFileType('/path/to/file.xlsx');
|
|
35
|
+
assert.strictEqual(result.type, 'excel');
|
|
36
|
+
assert.strictEqual(result.extension, '.xlsx');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('detects .xlsm files', () => {
|
|
40
|
+
const pbix = require('../utils/pbix');
|
|
41
|
+
const result = pbix.detectFileType('/path/to/file.xlsm');
|
|
42
|
+
assert.strictEqual(result.type, 'excel-macro');
|
|
43
|
+
assert.strictEqual(result.extension, '.xlsm');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('handles unknown file types', () => {
|
|
47
|
+
const pbix = require('../utils/pbix');
|
|
48
|
+
const result = pbix.detectFileType('/path/to/file.txt');
|
|
49
|
+
assert.strictEqual(result.type, 'unknown');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('Add Command - Slug Generation', () => {
|
|
54
|
+
test('generates slug from name', () => {
|
|
55
|
+
const pbix = require('../utils/pbix');
|
|
56
|
+
|
|
57
|
+
assert.strictEqual(pbix.generateSlug('Sales Dashboard Q4'), 'sales-dashboard-q4');
|
|
58
|
+
assert.strictEqual(pbix.generateSlug('My Report!'), 'my-report');
|
|
59
|
+
assert.strictEqual(pbix.generateSlug('Budget 2026'), 'budget-2026');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('handles special characters', () => {
|
|
63
|
+
const pbix = require('../utils/pbix');
|
|
64
|
+
|
|
65
|
+
assert.strictEqual(pbix.generateSlug('Report (Final)'), 'report-final');
|
|
66
|
+
assert.strictEqual(pbix.generateSlug('Test & Demo'), 'test-demo');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('limits slug length', () => {
|
|
70
|
+
const pbix = require('../utils/pbix');
|
|
71
|
+
const longName =
|
|
72
|
+
'This is a very long project name that should be truncated to a reasonable length';
|
|
73
|
+
const slug = pbix.generateSlug(longName);
|
|
74
|
+
|
|
75
|
+
assert.ok(slug.length <= 50);
|
|
76
|
+
});
|
|
77
|
+
});
|