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:
169
packages/cli/src/cli.js
Executable file → Normal file
169
packages/cli/src/cli.js
Executable file → Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
export default [
|
||||
{
|
||||
name: 'framework',
|
||||
type: 'single',
|
||||
type: 'multiple',
|
||||
options: [
|
||||
{
|
||||
name: 'svelte',
|
||||
|
||||
138
packages/cli/src/configsProcessor.js
Executable file
138
packages/cli/src/configsProcessor.js
Executable 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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
7
packages/cli/src/types.d.ts
vendored
7
packages/cli/src/types.d.ts
vendored
@@ -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
11
pnpm-lock.yaml
generated
@@ -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==}
|
||||
|
||||
Reference in New Issue
Block a user