From d2adda8aeb7a7bc5b56579032fa3f1af8c3049b9 Mon Sep 17 00:00:00 2001 From: Guz013 <43732358+Guz013@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:07:59 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20manual=20options/configs=20?= =?UTF-8?q?selection=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/package.json | 3 +- packages/cli/src/cli.js | 19 +++--- packages/cli/src/configs.js | 2 + packages/cli/src/configsProcessor.js | 96 +++++++++++++++++++--------- packages/cli/src/lib/str.js | 10 +++ packages/cli/src/types.d.ts | 1 + 6 files changed, 90 insertions(+), 41 deletions(-) create mode 100644 packages/cli/src/lib/str.js diff --git a/packages/cli/package.json b/packages/cli/package.json index 39e2683..0823e1c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,6 +37,7 @@ "yaml": "^2.3.1" }, "devDependencies": { - "@types/node": "^20.4.2" + "@types/node": "^20.4.2", + "@types/prompts": "^2.4.4" } } diff --git a/packages/cli/src/cli.js b/packages/cli/src/cli.js index ef1b2f7..59f8f4d 100644 --- a/packages/cli/src/cli.js +++ b/packages/cli/src/cli.js @@ -13,17 +13,20 @@ export default class Cli { dir: process.cwd(), }; - setArgs() { + /** + * @param {import('./types').CliArgs} [args] Cli arguments object + */ + constructor(args) { this.#program .option('--packages ') .option('--merge-to-root') - .option('--dir ', undefined); - - this.#program.parse(); + .option('--dir ', undefined) + .parse(); this.args = { ...this.args, ...this.#program.opts(), + ...args, }; this.args.dir = !this.args.dir.startsWith('/') @@ -33,17 +36,15 @@ export default class Cli { } async run() { - this.setArgs(); - - console.log(this.args.dir); const processor = new ConfigsProcessor({ configs }); - const packages = (await new Workspace(this.args.dir, this.args?.packages ) - .getPackages()) + const workspace = new Workspace(this.args.dir, this.args?.packages); + let packages = (await workspace.getPackages()) .map(pkg => { pkg.config = processor.detectConfig(pkg); return pkg; }); + packages = await processor.questionConfigs(packages); const configsMaps = processor.generateConfigMap(packages); diff --git a/packages/cli/src/configs.js b/packages/cli/src/configs.js index 896b094..7f7b71e 100644 --- a/packages/cli/src/configs.js +++ b/packages/cli/src/configs.js @@ -4,6 +4,8 @@ export default [ { name: 'framework', type: 'multiple', + description: 'The UI frameworks being used in the project', + manual: true, options: [ { name: 'svelte', diff --git a/packages/cli/src/configsProcessor.js b/packages/cli/src/configsProcessor.js index 065ef08..81bcd51 100755 --- a/packages/cli/src/configsProcessor.js +++ b/packages/cli/src/configsProcessor.js @@ -1,8 +1,9 @@ #!node import path from 'node:path'; -import { createSpinner } from 'nanospinner'; import glob from 'picomatch'; +import prompts from 'prompts'; import c from 'picocolors'; +import str from './lib/str.js'; export default class ConfigsProcessor { /** @type {string} */ @@ -31,25 +32,17 @@ export default class ConfigsProcessor { * @param {import('./types.js').Package} pkg - Package to detect from * @param {import('./types.js').Config['options']} options - Options to be passed * @param {boolean} single - Whether to only detect one option - * @param {import('nanospinner').Spinner} spinner - Spinner to update * @returns {string[]} - The detected options */ - detectOptions(pkg, options, single, spinner) { + detectOptions(pkg, options, single) { /** @type {string[]} */ const detectedOptions = []; for (const option of options) { - spinner.update({ - text: `Configuring ${c.bold(c.blue(pkg.name))}${c.dim(`: option ${c.bold(option.name)}`)}`, - }); - if (option.detect === true) { detectedOptions.push(option.name); - spinner.update({ - text: `Configuring ${c.bold(c.blue(pkg.name))}${c.dim(`: option ${c.bold(option.name)} ${c.green('✓')}`)}`, - }); continue; } else if (!option.detect) continue; @@ -61,54 +54,96 @@ export default class ConfigsProcessor { if (files.length > 0 || directories.length > 0) { detectedOptions.push(option.name); - spinner.update({ - text: `Configuring ${c.bold(c.blue(pkg.name))}${c.dim(`: option ${c.bold(option.name)} ${c.green('✔')}`)}`, - }); if (single) break; } - else { - spinner.update({ - text: `Configuring ${c.bold(c.blue(pkg.name))}${c.dim(`: option ${c.bold(option.name)} ${c.red('✖')}`)}`, - }); - } } return detectedOptions; } /** - * @param {import('./types.js').Package} pkg - The package to detect configs - * @returns {import('./types.js').Package['config']} - Detected configs record + * @param {import('./types.js').Package[]} packages - The package to detect configs + * @returns {Promise} - The selected options by the user + */ + async questionConfigs(packages) { + + const instructions = c.dim(`\n${c.bold('A: Toggle all')} - ↑/↓: Highlight option - ←/→/[space]: Toggle selection - enter/return: Complete answer`); + + for (const config of this.configs.filter(c => c.manual)) { + + /** @type {import('prompts').Choice[]} */ + const configChoices = config.options.map(option => {return { title: `${str.capitalize(option.name)}`, value: option.name };}); + + /** @type {Record} */ + const selectedOptions = await prompts({ + name: config.name, + type: config.type === 'single' ? 'select' : 'multiselect', + message: str.capitalize(config.name), + choices: configChoices, + hint: config.description, + instructions: instructions + c.dim(c.italic('\nSelect none if you don\'t want to use this configuration\n')), + }); + + if (selectedOptions[config.name].length === 0) continue; + + /** @type {{title: string, value: import('./types').Package}[]} */ + const packagesOptions = packages + .map(pkg => { + return !pkg.root + ? { + title: `${pkg.name} ${c.dim(pkg.path.replace(this.dir, '.'))}`, + value: pkg, + } + : { title: 'root', value: pkg }; + }) + .filter(p => p.title !== 'root'); + + /** @type {Record<'packages', import('./types').Package[]>} */ + const selected = await prompts({ + name: 'packages', + type: 'multiselect', + message: `What packages would you like to apply ${config.type === 'single' ? 'this choice' : 'these choices'}?`, + choices: packagesOptions, + min: 1, + instructions: instructions + c.dim(c.italic('\nToggle all to use in the root configuration\n')), + }); + selected.packages = selected.packages ?? []; + + selected.packages.map(pkg => { pkg.config = { ...pkg.config, ...selectedOptions }; return pkg; }); + packages.map(pkg => selected.packages.find(s => s.name === pkg.name) ?? pkg); + } + + return packages; + + } + + /** + * @param {import('./types').Package} pkg - The package to detect configs + * @returns {import('./types').Package['config']} - Detected configs record */ detectConfig(pkg) { - const spinner = createSpinner(`Configuring ${c.bold(c.blue(pkg.name))}`); - spinner.start(); - /** @type {import('./types.js').Package['config']} */ const pkgConfig = {}; - for (const config of this.configs) { + for (const config of this.configs.filter(c => !c.manual)) { pkgConfig[config.name] = this.detectOptions( pkg, config.options, config.type === 'single', - spinner, ); - spinner.update({ text: `Configuring ${c.bold(c.blue(pkg.name))}${c.dim(`: config ${config.name}`)}` }); } - spinner.success({ text: `Configuring ${c.bold(c.blue(pkg.name))}\n${c.dim(JSON.stringify(pkgConfig))}\n` }); return pkgConfig; } /** - * @param {import('./types.js').Package[]} packages Packages to generate the map from - * @returns {import('./types.js').PackagesConfigsMap} A map of what packages has some configuration + * @param {import('./types').Package[]} packages Packages to generate the map from + * @returns {import('./types').PackagesConfigsMap} A map of what packages has some configuration */ generateConfigMap(packages) { - /** @type {import('./types.js').PackagesConfigsMap} */ + /** @type {import('./types').PackagesConfigsMap} */ const configMap = new Map(); for (const pkg of packages) { @@ -122,7 +157,6 @@ export default class ConfigsProcessor { optionsMap.set(option, [pkg.path, ...paths]); if (paths.length >= packages.length - 2 || paths.includes(this.dir)) { - console.log('a', packages.length, paths.length); optionsMap.set(option, [this.dir]); } }); diff --git a/packages/cli/src/lib/str.js b/packages/cli/src/lib/str.js new file mode 100644 index 0000000..257b0d4 --- /dev/null +++ b/packages/cli/src/lib/str.js @@ -0,0 +1,10 @@ + +/** + * @param {string} str - The string to capitalize + * @returns {string} The capitalized string + */ +function capitalize(str) { + return str[0].toUpperCase() + str.slice(1); +} + +export default { capitalize }; diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index 91c2d7f..15b0e48 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -10,6 +10,7 @@ export interface Config { name: string type: 'single' | 'multiple' manual?: boolean + description?: string options: { name: string packages: Record