diff --git a/package-lock.json b/package-lock.json index 30f5482..020f844 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "openapi-clean-arch-generator", + "name": "@blas/openapi-clean-arch-generator", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "openapi-clean-arch-generator", + "name": "@blas/openapi-clean-arch-generator", "version": "1.0.0", "license": "MIT", "dependencies": { @@ -13,10 +13,11 @@ "commander": "^11.1.0", "fs-extra": "^11.2.0", "js-yaml": "^4.1.0", - "mustache": "^4.2.0" + "mustache": "^4.2.0", + "prompts": "^2.4.2" }, "bin": { - "generate-clean-arch": "dist/generate.js" + "generate-clean-arch": "dist/main.js" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -24,6 +25,7 @@ "@types/js-yaml": "^4.0.9", "@types/mustache": "^4.2.6", "@types/node": "^25.5.0", + "@types/prompts": "^2.4.9", "@typescript-eslint/eslint-plugin": "^8.57.1", "@typescript-eslint/parser": "^8.57.1", "eslint": "^10.1.0", @@ -354,6 +356,17 @@ "undici-types": "~7.18.0" } }, + "node_modules/@types/prompts": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz", + "integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "kleur": "^3.0.3" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.57.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", @@ -1293,6 +1306,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -1491,6 +1513,19 @@ "node": ">=6.0.0" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1537,6 +1572,12 @@ "node": ">=8" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 764edff..561b9f4 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "commander": "^11.1.0", "fs-extra": "^11.2.0", "js-yaml": "^4.1.0", - "mustache": "^4.2.0" + "mustache": "^4.2.0", + "prompts": "^2.4.2" }, "engines": { "node": ">=14.0.0" @@ -52,6 +53,7 @@ "@types/js-yaml": "^4.0.9", "@types/mustache": "^4.2.6", "@types/node": "^25.5.0", + "@types/prompts": "^2.4.9", "@typescript-eslint/eslint-plugin": "^8.57.1", "@typescript-eslint/parser": "^8.57.1", "eslint": "^10.1.0", diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index bc86a64..e570ddd 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -1,91 +1,118 @@ -import readline from 'readline'; +import prompts from 'prompts'; import { ApiKeyInfo } from './environment-finder'; import { colors } from './logger'; -function ask(rl: readline.Interface, query: string): Promise { - return new Promise((resolve) => rl.question(query, resolve)); +function clearScreen(): void { + process.stdout.write('\x1Bc'); +} + +function printHeader(current?: number, total?: number): void { + const stepText = + current !== undefined && total !== undefined + ? ` [${colors.cyan}${current}${colors.reset} de ${colors.cyan}${total}${colors.reset}]` + : ''; + console.log(`\n ${colors.bright}🔑 Configuración de URLs base${colors.reset}${stepText}`); + console.log(` ${'─'.repeat(54)}\n`); +} + +function printSummary(tags: string[], result: Record): void { + clearScreen(); + console.log(`\n ${colors.bright}✅ Configuración completada${colors.reset}`); + console.log(` ${'─'.repeat(54)}\n`); + tags.forEach((tag) => { + console.log(` ${colors.bright}${tag}${colors.reset}`); + console.log(` ${colors.cyan}environment.${result[tag]}.url${colors.reset}\n`); + }); + console.log(` ${'─'.repeat(54)}\n`); } /** - * Interactively asks the user which environment API key to use for each tag. + * Interactively asks the user which environment API key to use for each tag, + * using arrow-key selection. The last option always allows typing manually. * Returns a map of tag → environment key (e.g. { "SupplyingMaintenances": "suppliyingMaintenancesApi" }). */ export async function askApiKeysForTags( tags: string[], apiKeys: ApiKeyInfo[] ): Promise> { + if (tags.length === 0) return {}; + + clearScreen(); + printHeader(); + + const modeResponse = await prompts({ + type: 'select', + name: 'mode', + message: 'URL base para los repositorios', + choices: [ + { title: `${colors.bright}La misma para todos${colors.reset}`, value: 'all' }, + { title: `${colors.bright}Configurar individualmente${colors.reset}`, value: 'individual' } + ], + hint: ' ' + }); + + if (modeResponse.mode === undefined) process.exit(0); + const result: Record = {}; - if (tags.length === 0) return result; - - const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - - console.log('\n' + '─'.repeat(60)); - console.log(`${colors.bright}🔑 Configuración de URLs base para repositorios${colors.reset}`); - console.log('─'.repeat(60)); - - for (const tag of tags) { - result[tag] = await askApiKeyForTag(rl, tag, apiKeys); + if (modeResponse.mode === 'all') { + clearScreen(); + printHeader(); + const sharedKey = await askApiKeyForTag('todos los repositorios', apiKeys); + tags.forEach((tag) => (result[tag] = sharedKey)); + } else { + for (let i = 0; i < tags.length; i++) { + clearScreen(); + printHeader(i + 1, tags.length); + result[tags[i]] = await askApiKeyForTag(tags[i], apiKeys); + } } - rl.close(); + printSummary(tags, result); return result; } -async function askApiKeyForTag( - rl: readline.Interface, - tagName: string, - apiKeys: ApiKeyInfo[] -): Promise { - console.log( - `\n Repositorio: ${colors.cyan}${tagName}RepositoryImpl${colors.reset}` - ); +async function askApiKeyForTag(tagName: string, apiKeys: ApiKeyInfo[]): Promise { + const MANUAL_VALUE = '__manual__'; - if (apiKeys.length > 0) { - console.log(` Selecciona la clave de environment para la URL base:\n`); - apiKeys.forEach((k, i) => { - const urlText = k.url ? `\n ${colors.cyan}${k.url}${colors.reset}` : ''; - console.log(` ${colors.bright}${i + 1})${colors.reset} ${k.key}${urlText}`); - }); - console.log(` ${colors.bright}${apiKeys.length + 1})${colors.reset} Escribir manualmente`); - } else { - console.log(` No se encontraron claves de API en environment.ts.`); - console.log(` Escribe la clave manualmente (ej: myApi):\n`); - } - - while (true) { - const answer = (await ask(rl, `\n > `)).trim(); - - if (apiKeys.length > 0) { - const num = parseInt(answer, 10); - if (!isNaN(num) && num >= 1 && num <= apiKeys.length) { - const chosen = apiKeys[num - 1].key; - console.log(` ✅ ${colors.bright}environment.${chosen}.url${colors.reset}`); - return chosen; - } - if (num === apiKeys.length + 1 || answer === '') { - const manual = (await ask(rl, ` Escribe la clave (ej: myApi): `)).trim(); - if (manual) { - console.log(` ✅ ${colors.bright}environment.${manual}.url${colors.reset}`); - return manual; - } - console.log(` ⚠️ La clave no puede estar vacía.`); - continue; - } - - if (answer && isNaN(num)) { - console.log(` ✅ ${colors.bright}environment.${answer}.url${colors.reset}`); - return answer; - } - console.log( - ` ⚠️ Opción inválida. Elige un número del 1 al ${apiKeys.length + 1} o escribe la clave directamente.` - ); - } else { - if (answer) { - console.log(` ✅ ${colors.bright}environment.${answer}.url${colors.reset}`); - return answer; - } - console.log(` ⚠️ La clave no puede estar vacía.`); + const choices = [ + ...apiKeys.map((k) => ({ + title: k.url + ? `${colors.bright}${k.key}${colors.reset}\n ${colors.cyan}↳ ${k.url}${colors.reset}` + : `${colors.bright}${k.key}${colors.reset}`, + value: k.key + })), + { + title: `${colors.bright}Escribir manualmente${colors.reset}`, + value: MANUAL_VALUE } + ]; + + const selectResponse = await prompts({ + type: 'select', + name: 'key', + message: `Repositorio ${colors.bright}${tagName}${colors.reset}`, + choices, + hint: ' ' + }); + + if (selectResponse.key === undefined) process.exit(0); + + if (selectResponse.key !== MANUAL_VALUE) { + return selectResponse.key as string; } + + console.log(); + + const textResponse = await prompts({ + type: 'text', + name: 'key', + message: `Clave de environment`, + hint: 'ej: aprovalmApi', + validate: (v: string) => v.trim().length > 0 || 'La clave no puede estar vacía' + }); + + if (textResponse.key === undefined) process.exit(0); + + return (textResponse.key as string).trim(); }