@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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects Utility
|
|
3
|
+
* =================
|
|
4
|
+
*
|
|
5
|
+
* Shared functions for working with bi-repo projects.
|
|
6
|
+
* Extracted from add.js, pull.js, push.js to reduce duplication.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/projects
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get a specific project by name
|
|
16
|
+
* @param {string} repoPath - Path to bi-repo
|
|
17
|
+
* @param {string} projectName - Name of the project
|
|
18
|
+
* @returns {Object|null} Project config with projectPath added, or null
|
|
19
|
+
*/
|
|
20
|
+
function getProject(repoPath, projectName) {
|
|
21
|
+
const projectPath = path.join(repoPath, 'projects', projectName);
|
|
22
|
+
const configPath = path.join(projectPath, 'project.json');
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(configPath)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
30
|
+
return {
|
|
31
|
+
...config,
|
|
32
|
+
projectPath,
|
|
33
|
+
};
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// Invalid JSON in config file
|
|
36
|
+
if (process.env.DEBUG === 'true') {
|
|
37
|
+
console.error(`[DEBUG] Failed to parse project config: ${configPath}`);
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get list of all projects in the repo
|
|
45
|
+
* @param {string} repoPath - Path to bi-repo
|
|
46
|
+
* @returns {Object[]} Array of project configs with projectPath added
|
|
47
|
+
*/
|
|
48
|
+
function getAllProjects(repoPath) {
|
|
49
|
+
const projectsDir = path.join(repoPath, 'projects');
|
|
50
|
+
|
|
51
|
+
if (!fs.existsSync(projectsDir)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const projects = [];
|
|
56
|
+
const dirs = fs.readdirSync(projectsDir);
|
|
57
|
+
|
|
58
|
+
for (const dir of dirs) {
|
|
59
|
+
const projectPath = path.join(projectsDir, dir);
|
|
60
|
+
|
|
61
|
+
// Skip if not a directory
|
|
62
|
+
try {
|
|
63
|
+
if (!fs.statSync(projectPath).isDirectory()) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const configPath = path.join(projectPath, 'project.json');
|
|
71
|
+
|
|
72
|
+
if (fs.existsSync(configPath)) {
|
|
73
|
+
try {
|
|
74
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
75
|
+
projects.push({
|
|
76
|
+
...config,
|
|
77
|
+
projectPath,
|
|
78
|
+
});
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// Skip invalid configs
|
|
81
|
+
if (process.env.DEBUG === 'true') {
|
|
82
|
+
console.error(`[DEBUG] Skipping invalid config: ${configPath}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return projects;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Detect current project based on working directory
|
|
93
|
+
* @param {string} repoPath - Path to bi-repo
|
|
94
|
+
* @returns {Object|null} Project config or null if not in a project folder
|
|
95
|
+
*/
|
|
96
|
+
function detectCurrentProject(repoPath) {
|
|
97
|
+
const cwd = process.cwd();
|
|
98
|
+
const projectsBase = path.join(repoPath, 'projects');
|
|
99
|
+
|
|
100
|
+
// Check if we're inside a project folder
|
|
101
|
+
if (cwd.startsWith(projectsBase)) {
|
|
102
|
+
const relative = path.relative(projectsBase, cwd);
|
|
103
|
+
const projectName = relative.split(path.sep)[0];
|
|
104
|
+
|
|
105
|
+
if (projectName && projectName !== '..') {
|
|
106
|
+
return getProject(repoPath, projectName);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* List project names
|
|
115
|
+
* @param {string} repoPath - Path to bi-repo
|
|
116
|
+
* @returns {string[]} Array of project names
|
|
117
|
+
*/
|
|
118
|
+
function listProjectNames(repoPath) {
|
|
119
|
+
return getAllProjects(repoPath).map((p) => p.name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if a project exists
|
|
124
|
+
* @param {string} repoPath - Path to bi-repo
|
|
125
|
+
* @param {string} projectName - Name of the project
|
|
126
|
+
* @returns {boolean} True if project exists
|
|
127
|
+
*/
|
|
128
|
+
function projectExists(repoPath, projectName) {
|
|
129
|
+
const projectPath = path.join(repoPath, 'projects', projectName);
|
|
130
|
+
const configPath = path.join(projectPath, 'project.json');
|
|
131
|
+
return fs.existsSync(configPath);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get project path
|
|
136
|
+
* @param {string} repoPath - Path to bi-repo
|
|
137
|
+
* @param {string} projectName - Name of the project
|
|
138
|
+
* @returns {string} Path to project directory
|
|
139
|
+
*/
|
|
140
|
+
function getProjectPath(repoPath, projectName) {
|
|
141
|
+
return path.join(repoPath, 'projects', projectName);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Print available projects to console
|
|
146
|
+
* @param {string} repoPath - Path to bi-repo
|
|
147
|
+
* @param {string} prefix - Prefix for each line (default: ' - ')
|
|
148
|
+
*/
|
|
149
|
+
function printAvailableProjects(repoPath, prefix = ' - ') {
|
|
150
|
+
const projects = getAllProjects(repoPath);
|
|
151
|
+
if (projects.length === 0) {
|
|
152
|
+
console.log(`${prefix}(no projects found)`);
|
|
153
|
+
} else {
|
|
154
|
+
projects.forEach((p) => {
|
|
155
|
+
console.log(`${prefix}${p.name}`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
getProject,
|
|
162
|
+
getAllProjects,
|
|
163
|
+
detectCurrentProject,
|
|
164
|
+
listProjectNames,
|
|
165
|
+
projectExists,
|
|
166
|
+
getProjectPath,
|
|
167
|
+
printAvailableProjects,
|
|
168
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readline Utilities for BI Agent Superpowers
|
|
3
|
+
* ============================================
|
|
4
|
+
*
|
|
5
|
+
* Shared readline functions for interactive CLI commands.
|
|
6
|
+
* Consolidates duplicated code from setup.js, add.js, sync-source.js, sync-profile.js
|
|
7
|
+
*
|
|
8
|
+
* @module utils/readline
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a readline interface for interactive prompts
|
|
15
|
+
* @returns {readline.Interface} Readline interface
|
|
16
|
+
*/
|
|
17
|
+
function createReadline() {
|
|
18
|
+
return readline.createInterface({
|
|
19
|
+
input: process.stdin,
|
|
20
|
+
output: process.stdout,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Prompt the user for input
|
|
26
|
+
* @param {readline.Interface} rl - Readline interface
|
|
27
|
+
* @param {string} question - Question to ask
|
|
28
|
+
* @returns {Promise<string>} User's answer (trimmed)
|
|
29
|
+
*/
|
|
30
|
+
function prompt(rl, question) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
rl.question(question, (answer) => {
|
|
33
|
+
resolve(answer.trim());
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ask for yes/no confirmation
|
|
40
|
+
* @param {readline.Interface} rl - Readline interface
|
|
41
|
+
* @param {string} question - Question to ask (without y/n suffix)
|
|
42
|
+
* @param {boolean} defaultValue - Default value if user just presses Enter
|
|
43
|
+
* @returns {Promise<boolean>} True if confirmed, false otherwise
|
|
44
|
+
*/
|
|
45
|
+
function confirm(rl, question, defaultValue = false) {
|
|
46
|
+
const suffix = defaultValue ? ' (S/n): ' : ' (s/N): ';
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
rl.question(question + suffix, (answer) => {
|
|
49
|
+
const trimmed = answer.trim().toLowerCase();
|
|
50
|
+
if (trimmed === '') {
|
|
51
|
+
resolve(defaultValue);
|
|
52
|
+
} else {
|
|
53
|
+
resolve(trimmed === 's' || trimmed === 'y' || trimmed === 'si' || trimmed === 'yes');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Ask user to select from a list of options
|
|
61
|
+
* @param {readline.Interface} rl - Readline interface
|
|
62
|
+
* @param {string} question - Question to ask
|
|
63
|
+
* @param {string[]} options - Array of option strings
|
|
64
|
+
* @param {number} defaultIndex - Default option index (0-based)
|
|
65
|
+
* @returns {Promise<{index: number, value: string}>} Selected option
|
|
66
|
+
*/
|
|
67
|
+
async function select(rl, question, options, defaultIndex = 0) {
|
|
68
|
+
console.log(`\n${question}\n`);
|
|
69
|
+
|
|
70
|
+
options.forEach((option, i) => {
|
|
71
|
+
const marker = i === defaultIndex ? '>' : ' ';
|
|
72
|
+
console.log(` ${marker} ${i + 1}. ${option}`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
const answer = await prompt(rl, `Elige una opción (1-${options.length}) [${defaultIndex + 1}]: `);
|
|
78
|
+
|
|
79
|
+
if (answer === '') {
|
|
80
|
+
return { index: defaultIndex, value: options[defaultIndex] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const num = parseInt(answer, 10);
|
|
84
|
+
if (isNaN(num) || num < 1 || num > options.length) {
|
|
85
|
+
console.log('Opción no válida, usando la predeterminada.');
|
|
86
|
+
return { index: defaultIndex, value: options[defaultIndex] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { index: num - 1, value: options[num - 1] };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Ask user to select multiple options from a list
|
|
94
|
+
* @param {readline.Interface} rl - Readline interface
|
|
95
|
+
* @param {string} question - Question to ask
|
|
96
|
+
* @param {Array<{label: string, value: any, selected?: boolean}>} options - Options with labels and values
|
|
97
|
+
* @returns {Promise<any[]>} Array of selected values
|
|
98
|
+
*/
|
|
99
|
+
async function multiSelect(rl, question, options) {
|
|
100
|
+
console.log(`\n${question}\n`);
|
|
101
|
+
console.log(' Instrucciones:');
|
|
102
|
+
console.log(' - Escribe números separados por comas (ej: 1,3,5)');
|
|
103
|
+
console.log(' - "a" para seleccionar todos');
|
|
104
|
+
console.log(' - "n" para deseleccionar todos');
|
|
105
|
+
console.log(' - Enter para confirmar selección actual\n');
|
|
106
|
+
|
|
107
|
+
// Show options with current selection
|
|
108
|
+
options.forEach((option, i) => {
|
|
109
|
+
const marker = option.selected ? '[x]' : '[ ]';
|
|
110
|
+
console.log(` ${marker} ${i + 1}. ${option.label}`);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log('');
|
|
114
|
+
|
|
115
|
+
const answer = await prompt(rl, 'Selección: ');
|
|
116
|
+
|
|
117
|
+
if (answer.toLowerCase() === 'a') {
|
|
118
|
+
return options.map((o) => o.value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (answer.toLowerCase() === 'n' || answer === '') {
|
|
122
|
+
return options.filter((o) => o.selected).map((o) => o.value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Parse comma-separated numbers
|
|
126
|
+
const indices = answer
|
|
127
|
+
.split(',')
|
|
128
|
+
.map((s) => parseInt(s.trim(), 10) - 1)
|
|
129
|
+
.filter((n) => !isNaN(n) && n >= 0 && n < options.length);
|
|
130
|
+
|
|
131
|
+
return indices.map((i) => options[i].value);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Show a spinner while executing an async operation
|
|
136
|
+
* @param {string} message - Message to show
|
|
137
|
+
* @param {Promise} promise - Promise to wait for
|
|
138
|
+
* @returns {Promise} Result of the promise
|
|
139
|
+
*/
|
|
140
|
+
async function withSpinner(message, promise) {
|
|
141
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
142
|
+
let i = 0;
|
|
143
|
+
|
|
144
|
+
const interval = setInterval(() => {
|
|
145
|
+
process.stdout.write(`\r${frames[i]} ${message}`);
|
|
146
|
+
i = (i + 1) % frames.length;
|
|
147
|
+
}, 80);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const result = await promise;
|
|
151
|
+
clearInterval(interval);
|
|
152
|
+
process.stdout.write(`\r✓ ${message}\n`);
|
|
153
|
+
return result;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
clearInterval(interval);
|
|
156
|
+
process.stdout.write(`\r✗ ${message}\n`);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Exit with error message
|
|
163
|
+
* @param {string} message - Error message
|
|
164
|
+
* @param {number} code - Exit code (default 1)
|
|
165
|
+
*/
|
|
166
|
+
function exitWithError(message, code = 1) {
|
|
167
|
+
console.error(`\n✗ ${message}\n`);
|
|
168
|
+
process.exit(code);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Show success message
|
|
173
|
+
* @param {string} message - Success message
|
|
174
|
+
*/
|
|
175
|
+
function showSuccess(message) {
|
|
176
|
+
console.log(`✓ ${message}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Show warning message
|
|
181
|
+
* @param {string} message - Warning message
|
|
182
|
+
*/
|
|
183
|
+
function showWarning(message) {
|
|
184
|
+
console.log(`⚠ ${message}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Show info message
|
|
189
|
+
* @param {string} message - Info message
|
|
190
|
+
*/
|
|
191
|
+
function showInfo(message) {
|
|
192
|
+
console.log(`ℹ ${message}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
createReadline,
|
|
197
|
+
prompt,
|
|
198
|
+
confirm,
|
|
199
|
+
select,
|
|
200
|
+
multiSelect,
|
|
201
|
+
withSpinner,
|
|
202
|
+
exitWithError,
|
|
203
|
+
showSuccess,
|
|
204
|
+
showWarning,
|
|
205
|
+
showInfo,
|
|
206
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Readline Utilities
|
|
3
|
+
* @module utils/readline.test
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { test, describe } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
|
|
9
|
+
const readline = require('./readline');
|
|
10
|
+
|
|
11
|
+
describe('Readline Utilities', () => {
|
|
12
|
+
test('exports createReadline function', () => {
|
|
13
|
+
assert.strictEqual(typeof readline.createReadline, 'function');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('exports prompt function', () => {
|
|
17
|
+
assert.strictEqual(typeof readline.prompt, 'function');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('exports confirm function', () => {
|
|
21
|
+
assert.strictEqual(typeof readline.confirm, 'function');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('exports select function', () => {
|
|
25
|
+
assert.strictEqual(typeof readline.select, 'function');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('exports multiSelect function', () => {
|
|
29
|
+
assert.strictEqual(typeof readline.multiSelect, 'function');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('exports exitWithError function', () => {
|
|
33
|
+
assert.strictEqual(typeof readline.exitWithError, 'function');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('exports showSuccess function', () => {
|
|
37
|
+
assert.strictEqual(typeof readline.showSuccess, 'function');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('exports showWarning function', () => {
|
|
41
|
+
assert.strictEqual(typeof readline.showWarning, 'function');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('exports showInfo function', () => {
|
|
45
|
+
assert.strictEqual(typeof readline.showInfo, 'function');
|
|
46
|
+
});
|
|
47
|
+
});
|