refactor: ♻️ repurpose Cli class to Configs

Renamed Cli to ConfigsProcessor, now the Cli class is purposed to just
parsing the cli arguments and orchestrate other classes
This commit is contained in:
Guz013
2023-08-03 18:33:58 -03:00
parent c201a25e6e
commit e775d83ccf
6 changed files with 193 additions and 144 deletions

169
packages/cli/src/cli.js Executable file → Normal file
View File

@@ -1,152 +1,55 @@
#!node
import path from 'node:path';
import { createSpinner } from 'nanospinner';
import glob from 'picomatch';
import c from 'picocolors';
import { Command } from 'commander';
import ConfigsProcessor from './configsProcessor.js';
import configs from './configs.js';
import Workspace from './workspace.js';
import path from 'node:path';
export default class Cli {
/** @type {string} */
dir = process.cwd();
/** @type {import('./types').Config[]} */
configs;
#program = new Command();
/** @type {string[] | undefined} */
#packagesPatterns;
/** @type {import('./types').CliArgs} */
args = {
dir: process.cwd(),
};
/**
* @param {{
* configs: import('./types').Config[],
* packages?: string[],
* directory?: string,
* }} options - Cli options
*/
constructor(options) {
this.#packagesPatterns = options.packages;
this.configs = options?.configs;
this.dir = path.normalize(options.directory ?? this.dir);
}
setArgs() {
this.#program
.option('--packages <string...>')
.option('--merge-to-root')
.option('--dir <path>', undefined);
/**
* @param {import('./types').Package} pkg - Package to detect from
* @param {import('./types').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) {
this.#program.parse();
/** @type {string[]} */
const detectedOptions = [];
this.args = {
...this.args,
...this.#program.opts(),
};
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;
const match = glob(option.detect);
const files = pkg.files.filter(f => match ? match(f) : false);
const directories = pkg.directories.filter(f => match ? match(f) : false);
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').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').Package['config']} */
const pkgConfig = {};
for (const config of this.configs) {
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').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').PackagesConfigsMap} */
const configMap = new Map();
for (const pkg of packages) {
Object.entries(pkg.config ?? {}).forEach(([key, options]) => {
/** @type {Map<string, string[]>} */
const optionsMap = configMap.get(key) ?? new Map();
options.forEach(option => {
const paths = optionsMap.get(option) ?? [];
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]);
}
});
configMap.set(key, optionsMap);
});
}
return configMap;
this.args.dir = !this.args.dir.startsWith('/')
? path.join(process.cwd(), this.args.dir)
: this.args.dir;
}
async run() {
let packages = await new Workspace(this.dir, this.#packagesPatterns).getPackages();
this.setArgs();
packages = packages.map(
pkg => {
pkg.config = this.detectConfig(pkg); return pkg;
},
);
console.log(this.args.dir);
console.log(packages);
const processor = new ConfigsProcessor({ configs });
const packages = (await new Workspace(this.args.dir, this.args?.packages )
.getPackages())
.map(pkg => {
pkg.config = processor.detectConfig(pkg);
return pkg;
});
const configsMaps = processor.generateConfigMap(packages);
console.log(configsMaps);
}
}

View File

@@ -3,7 +3,7 @@
export default [
{
name: 'framework',
type: 'single',
type: 'multiple',
options: [
{
name: 'svelte',

View File

@@ -0,0 +1,138 @@
#!node
import path from 'node:path';
import { createSpinner } from 'nanospinner';
import glob from 'picomatch';
import c from 'picocolors';
export default class ConfigsProcessor {
/** @type {string} */
dir = process.cwd();
/** @type {import('./types.js').Config[]} */
configs;
/** @type {string[] | undefined} */
#packagesPatterns;
/**
* @param {{
* configs: import('./types.js').Config[],
* packages?: string[],
* directory?: string,
* }} options - Cli options
*/
constructor(options) {
this.#packagesPatterns = options.packages;
this.configs = options?.configs;
this.dir = path.normalize(options.directory ?? this.dir);
}
/**
* @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) {
/** @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;
const match = glob(option.detect);
const files = pkg.files.filter(f => match ? match(f) : false);
const directories = pkg.directories.filter(f => match ? match(f) : false);
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
*/
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) {
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
*/
generateConfigMap(packages) {
/** @type {import('./types.js').PackagesConfigsMap} */
const configMap = new Map();
for (const pkg of packages) {
Object.entries(pkg.config ?? {}).forEach(([key, options]) => {
/** @type {Map<string, string[]>} */
const optionsMap = configMap.get(key) ?? new Map();
options.forEach(option => {
const paths = optionsMap.get(option) ?? [];
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]);
}
});
configMap.set(key, optionsMap);
});
}
return configMap;
}
}

View File

@@ -1,12 +1,4 @@
import Cli from './cli.js';
import configs from './configs.js';
import { program } from 'commander';
program.option('--packages <string...>');
program.parse(process.argv);
/** @type {{ packages?: string[] } & import('commander').OptionValues} */
const options = program.opts();
const cli = new Cli({ configs, packages: options.packages });
const cli = new Cli();
await cli.run();

View File

@@ -1,3 +1,10 @@
import type { OptionValues } from 'commander';
export type CliArgs = {
packages?: string[]
mergeToRoot?: boolean
dir: string
} & OptionValues;
export interface Config {
name: string

11
pnpm-lock.yaml generated
View File

@@ -124,6 +124,9 @@ importers:
'@types/node':
specifier: ^20.4.2
version: 20.4.2
'@types/prompts':
specifier: ^2.4.4
version: 2.4.4
packages/config:
dependencies:
@@ -891,6 +894,13 @@ packages:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
dev: true
/@types/prompts@2.4.4:
resolution: {integrity: sha512-p5N9uoTH76lLvSAaYSZtBCdEXzpOOufsRjnhjVSrZGXikVGHX9+cc9ERtHRV4hvBKHyZb1bg4K+56Bd2TqUn4A==}
dependencies:
'@types/node': 20.4.2
kleur: 3.0.3
dev: true
/@types/pug@2.0.6:
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
dev: true
@@ -2507,7 +2517,6 @@ packages:
/kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
dev: false
/kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}