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 { Command } from 'commander';
|
||||||
import path from 'node:path';
|
import ConfigsProcessor from './configsProcessor.js';
|
||||||
import { createSpinner } from 'nanospinner';
|
import configs from './configs.js';
|
||||||
import glob from 'picomatch';
|
|
||||||
import c from 'picocolors';
|
|
||||||
import Workspace from './workspace.js';
|
import Workspace from './workspace.js';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
export default class Cli {
|
export default class Cli {
|
||||||
/** @type {string} */
|
|
||||||
dir = process.cwd();
|
|
||||||
|
|
||||||
/** @type {import('./types').Config[]} */
|
#program = new Command();
|
||||||
configs;
|
|
||||||
|
|
||||||
/** @type {string[] | undefined} */
|
/** @type {import('./types').CliArgs} */
|
||||||
#packagesPatterns;
|
args = {
|
||||||
|
dir: process.cwd(),
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
setArgs() {
|
||||||
* @param {{
|
this.#program
|
||||||
* configs: import('./types').Config[],
|
.option('--packages <string...>')
|
||||||
* packages?: string[],
|
.option('--merge-to-root')
|
||||||
* directory?: string,
|
.option('--dir <path>', undefined);
|
||||||
* }} options - Cli options
|
|
||||||
*/
|
|
||||||
constructor(options) {
|
|
||||||
this.#packagesPatterns = options.packages;
|
|
||||||
this.configs = options?.configs;
|
|
||||||
this.dir = path.normalize(options.directory ?? this.dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
this.#program.parse();
|
||||||
* @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) {
|
|
||||||
|
|
||||||
/** @type {string[]} */
|
this.args = {
|
||||||
const detectedOptions = [];
|
...this.args,
|
||||||
|
...this.#program.opts(),
|
||||||
|
};
|
||||||
|
|
||||||
for (const option of options) {
|
this.args.dir = !this.args.dir.startsWith('/')
|
||||||
|
? path.join(process.cwd(), this.args.dir)
|
||||||
spinner.update({
|
: this.args.dir;
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
let packages = await new Workspace(this.dir, this.#packagesPatterns).getPackages();
|
this.setArgs();
|
||||||
|
|
||||||
packages = packages.map(
|
console.log(this.args.dir);
|
||||||
pkg => {
|
|
||||||
pkg.config = this.detectConfig(pkg); return pkg;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
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 [
|
export default [
|
||||||
{
|
{
|
||||||
name: 'framework',
|
name: 'framework',
|
||||||
type: 'single',
|
type: 'multiple',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'svelte',
|
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 Cli from './cli.js';
|
||||||
import configs from './configs.js';
|
|
||||||
import { program } from 'commander';
|
|
||||||
|
|
||||||
program.option('--packages <string...>');
|
const cli = new Cli();
|
||||||
|
|
||||||
program.parse(process.argv);
|
|
||||||
/** @type {{ packages?: string[] } & import('commander').OptionValues} */
|
|
||||||
const options = program.opts();
|
|
||||||
|
|
||||||
const cli = new Cli({ configs, packages: options.packages });
|
|
||||||
await cli.run();
|
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 {
|
export interface Config {
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -124,6 +124,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.4.2
|
specifier: ^20.4.2
|
||||||
version: 20.4.2
|
version: 20.4.2
|
||||||
|
'@types/prompts':
|
||||||
|
specifier: ^2.4.4
|
||||||
|
version: 2.4.4
|
||||||
|
|
||||||
packages/config:
|
packages/config:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -891,6 +894,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
||||||
dev: true
|
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:
|
/@types/pug@2.0.6:
|
||||||
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
|
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -2507,7 +2517,6 @@ packages:
|
|||||||
/kleur@3.0.3:
|
/kleur@3.0.3:
|
||||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/kleur@4.1.5:
|
/kleur@4.1.5:
|
||||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||||
|
|||||||
Reference in New Issue
Block a user