diff --git a/.changeset/curly-tomatoes-enjoy.md b/.changeset/curly-tomatoes-enjoy.md new file mode 100644 index 0000000..1504887 --- /dev/null +++ b/.changeset/curly-tomatoes-enjoy.md @@ -0,0 +1,5 @@ +--- +"@eslit/cli": minor +--- + +Now the cli can automatically detect the workspace structure on monorepos and single repositories diff --git a/fixtures/library/package.json b/fixtures/library/package.json new file mode 100644 index 0000000..5f3a745 --- /dev/null +++ b/fixtures/library/package.json @@ -0,0 +1,16 @@ +{ + "name": "@eslit-fixtures/library", + "version": "1.0.0", + "description": "", + "main": "index.js", + "private": true, + "scripts": { + "test": "pnpm cli" + }, + "dependencies": { + "@eslit/cli": "workspace:*" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/fixtures/monorepo/.gitignore b/fixtures/monorepo/.gitignore new file mode 100644 index 0000000..d1595af --- /dev/null +++ b/fixtures/monorepo/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# next.js +.next/ +out/ +build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo + +# vercel +.vercel diff --git a/fixtures/monorepo/package.json b/fixtures/monorepo/package.json new file mode 100644 index 0000000..aa0b92a --- /dev/null +++ b/fixtures/monorepo/package.json @@ -0,0 +1,15 @@ +{ + "private": true, + "scripts": { + "test:cli": "pnpm cli" + }, + "devDependencies": { + "@eslit/cli": "workspace:*" + }, + "packageManager": "pnpm@8.6.10", + "name": "monorepo", + "workspaces": [ + "apps/*", + "packages/*" + ] +} diff --git a/fixtures/svelte/package.json b/fixtures/svelte/package.json index 15c07a2..1b96188 100644 --- a/fixtures/svelte/package.json +++ b/fixtures/svelte/package.json @@ -7,9 +7,11 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "test:lint": "eslint ." + "test:lint": "eslint .", + "test:cli": "pnpm cli" }, "devDependencies": { + "@eslit/cli": "workspace:*", "@fontsource/fira-mono": "^4.5.10", "@neoconfetti/svelte": "^1.0.0", "@sveltejs/adapter-auto": "^2.0.0", diff --git a/package.json b/package.json index bbd5ed8..5970f08 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@eslit/config": "workspace:*" }, "devDependencies": { + "@eslit/cli": "workspace:*", "@changesets/cli": "^2.26.2", "@commitlint/config-conventional": "^17.6.6", "@commitlint/types": "^17.4.4", diff --git a/packages/cli/jsconfig.json b/packages/cli/jsconfig.json new file mode 100644 index 0000000..d4e37f4 --- /dev/null +++ b/packages/cli/jsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "exclude": ["./node_modules/**", "./dist/**"], + "include": ["./index.d.ts", "./src/**/*.ts", "./src/**/*.js"], +} diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..b2127fe --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,46 @@ +{ + "name": "@eslit/cli", + "version": "0.0.0", + "description": "", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint ." + }, + "keywords": [], + "author": { + "email": "contact.guz013@gmail.com", + "name": "Gustavo \"Guz\" L. de Mello", + "url": "https://guz.one" + }, + "module": "./src/index.js", + "source": "./src/index.js", + "files": [ + "src", + "index.d.ts" + ], + "homepage": "https://github.com/LoredDev/ESLit", + "type": "module", + "repository": { + "directory": "packages/config", + "type": "git", + "url": "https://github.com/LoredDev/ESLit" + }, + "bin": "./src/index.js", + "license": "MIT", + "dependencies": { + "cardinal": "^2.1.1", + "commander": "^11.0.0", + "nanospinner": "^1.1.0", + "picocolors": "^1.0.0", + "picomatch": "^2.3.1", + "prompts": "^2.4.2", + "recast": "^0.23.3", + "sisteransi": "^1.0.5", + "yaml": "^2.3.1" + }, + "devDependencies": { + "@types/estree": "^1.0.1", + "@types/node": "^20.4.2", + "@types/prompts": "^2.4.4" + } +} diff --git a/packages/cli/src/cli.js b/packages/cli/src/cli.js new file mode 100644 index 0000000..990923b --- /dev/null +++ b/packages/cli/src/cli.js @@ -0,0 +1,155 @@ +import { Command } from 'commander'; +import ConfigsProcessor from './configsProcessor.js'; +import configs from './configs.js'; +import Workspace from './workspace.js'; +import c from 'picocolors'; +import path from 'node:path'; +import { createSpinner } from 'nanospinner'; +import count from './lib/count.js'; +import prompts from 'prompts'; +import ConfigsFile from './configsFile.js'; +import * as cardinal from 'cardinal'; +import ansi from 'sisteransi'; +import PackageInstaller from './packageInstaller.js'; +import notNull from './lib/notNull.js'; + +const stdout = process.stdout; + +export default class Cli { + + #program = new Command(); + + /** @type {import('./types').CliArgs} */ + args = { + dir: process.cwd(), + }; + + /** + * @param {import('./types').CliArgs} [args] Cli arguments object + */ + constructor(args) { + this.#program + .option('--packages ') + .option('--dir ', undefined) + .option('--merge-to-root') + .option('--install-pkgs') + .parse(); + + this.args = { + ...this.args, + ...this.#program.opts(), + ...args, + }; + + this.args.dir = !this.args.dir.startsWith('/') + ? path.join(process.cwd(), this.args.dir) + : this.args.dir; + + } + + async run() { + + process.chdir(this.args.dir); + + const spinner = createSpinner('Detecting workspace configuration'); + + const processor = new ConfigsProcessor({ configs }); + const workspace = new Workspace(this.args.dir, this.args?.packages); + + let packages = (await workspace.getPackages()) + .map(pkg => { + spinner.update({ text: `Detecting configuration for package ${c.bold(c.blue(pkg.name))}` }); + + pkg.config = processor.detectConfig(pkg); + + return pkg; + }); + + spinner.success({ + text: + 'Detecting workspace configuration ' + + c.dim(`${count.packagesWithConfigs(packages)} configs founded\n`), + }); + + const merge = this.args.mergeToRoot ?? packages.length > 1 ? + /** @type {{merge: boolean}} */ + (await prompts({ + name: 'merge', + message: + `Would you like to merge all configuration files into one root ${c.blue('eslint.config.js?')}` + + c.italic(c.dim('\nAll configurations will be applied to the entire workspace and packages')), + initial: true, + type: 'confirm', + })).merge : true; + + console.log(c.dim('\nPlease select which options you prefer\n')); + + packages = await processor.questionConfig( + merge ? workspace.mergePackages(packages) : packages, + configs.filter(c => c.manual), + ); + + const fileHandler = new ConfigsFile(configs, packages.find(c => c.root)?.path); + + for (const pkg of packages) { + + pkg.configFile = fileHandler.generateObj(pkg); + pkg.configFile.content = await fileHandler.generate(pkg.configFile); + + /** @type {boolean} */ + const shouldWrite = + /** @type {{write: boolean}} */ + (await prompts({ + type: 'confirm', + name: 'write', + message: `Do you want to write this config file for ${pkg.root + ? c.blue('the root directory') + : c.blue(pkg.name) + }?\n\n${cardinal.highlight(pkg.configFile.content)}`, + initial: true, + })).write; + + stdout.write(ansi.erase.lines(pkg.configFile.content.split('\n').length + 2)); + + if (shouldWrite) await fileHandler.write(pkg.configFile.path, pkg.configFile.content); + + } + + const packagesMap = new Map(packages.map(p => [p.path, [...notNull(p.configFile).imports.keys()]])); + const installer = new PackageInstaller(packagesMap, packages.find(p => p.root === true)?.path ?? this.args.dir); + + /** @type {boolean | 'changePackage'} */ + let installPkgs = this.args.installPkgs !== undefined ? true : + /** @type {{install: boolean | 'changePackage'}} */ + (await prompts({ + name: 'install', + message: + `Would you like to ESLit to install the npm packages with ${c.green(installer.packageManager.name)}?`, + choices: [ + { title: 'Yes, install all packages', value: true, description: installer.packageManager.description }, + { title: 'No, I will install them manually', value: false }, + { title: 'Change package manager', value: 'changePackage' }, + ], + type: 'select', + })).install; + + if (installPkgs === 'changePackage') { + /** @type {{manager: import('./types').PackageManagerName}} */ + const prompt = await prompts({ + name: 'manager', + message: 'What package manager do you want ESLit to use?', + choices: Object.values(installer.packageManagers).map(m => { + return { title: m.name, description: m.description, value: m.name }; + }), + type: 'select', + }); + installer.packageManager = installer.packageManagers[prompt.manager]; + installPkgs = true; + } + + if (installPkgs) await installer.install(); + + } + +} + diff --git a/packages/cli/src/configs.js b/packages/cli/src/configs.js new file mode 100644 index 0000000..7f40b1b --- /dev/null +++ b/packages/cli/src/configs.js @@ -0,0 +1,33 @@ + +/** @type {import('./types').Config[]} */ +export default [ + { + name: 'framework', + type: 'multiple', + description: 'The UI frameworks being used in the project', + options: [ + { + name: 'svelte', + packages: { 'svelte': 'svelte' }, + configs: ['svelte.recommended'], + detect: ['**/*.svelte', 'svelte.config.{js,ts,cjs,cts}'], + }, + { + name: 'vue', + packages: { 'vue': ['vue', ['hello', 'world']], 'svelte': ['hello'] }, + configs: ['vue.recommended'], + detect: ['nuxt.config.{js,ts,cjs,cts}', '**/*.vue'], + }, + ], + }, + { + name: 'strict', + type: 'confirm', + manual: true, + options: [{ + name: 'yes', + packages: { 'eslint': 'config', 'svelte': ['test1'] }, + configs: ['config.strict'], + }], + }, +]; diff --git a/packages/cli/src/configsFile.js b/packages/cli/src/configsFile.js new file mode 100644 index 0000000..152a05f --- /dev/null +++ b/packages/cli/src/configsFile.js @@ -0,0 +1,344 @@ +import path from 'node:path'; +import notNull from './lib/notNull.js'; +import * as recast from 'recast'; +import fs from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import astUtils from './lib/astUtils.js'; + +/** + * @param {import('./types').ConfigFile['imports']} map1 - The map to has it values merged from map2 + * @param {import('./types').ConfigFile['imports']} map2 - The map to has it values merged to map1 + * @returns {import('./types').ConfigFile['imports']} The resulting map + */ +function mergeImportsMaps(map1, map2) { + for (const [key, value] of map2) { + if (!map1.has(key)) { + map1.set(key, value); + continue; + } + const imports1 = notNull(map1.get(key)); + const imports2 = notNull(map2.get(key)); + + /** + * Because arrays and objects are always different independently from having equal values + * ([] === [] -> false). It is converted to a string so the comparison can be made. + */ + switch ([typeof imports1 === 'string', typeof imports2 === 'string'].join(',')) { + case 'true,true': + if (imports1.toString() === imports2.toString()) + map1.set(key, value); + else + map1.set(key, [['default', imports1.toString()], ['default', imports2.toString()]]); + break; + case 'true,false': + map1.set(key, [['default', imports1.toString()], ...imports2]); + break; + case 'false,true': + map1.set(key, [['default', imports2.toString()], ...imports1]); + break; + case 'false,false': + map1.set(key, [...imports1, ...imports2]); + break; + } + if (typeof map1.get(key) !== 'string') + map1.set(key, [...new Set(map1.get(key))]); + } + return map1; +} + +/** + * @param {string} path1 The path to traverse from + * @param {string} root The root path + * @returns {string} The path to traverse + */ +function getPathDepth(path1, root) { + const pathDepth = path1.replace(root, '').split('/').slice(1); + if (pathDepth.length <= 1) return pathDepth.map(() => '.').join('/'); + return pathDepth.map(() => '..').join('/'); +} + +export default class ConfigsWriter { + + /** @type {string} */ + root = process.cwd(); + + /** + * @param {import('./types').Config[]} configs The array of configs to construct from + * @param {string} [root] The root directory path + */ + constructor(configs, root) { + this.configs = configs; + this.root = root ?? this.root; + } + + /** + * @param {import('./types').Package} pkg The package to generate the config string from + * @returns {import('./types').ConfigFile} The config file object + */ + generateObj(pkg) { + /** @type {import('./types').ConfigFile} */ + const configObj = { + path: path.join(pkg.path, 'eslint.config.js'), + imports: new Map(), + configs: [], + presets: [], + rules: [], + }; + + if (!pkg.root) { + const rootConfig = path.join(getPathDepth(pkg.path, this.root), 'eslint.config.js'); + configObj.imports.set(!rootConfig.startsWith('.') ? `./${rootConfig}` : rootConfig, 'root'); + configObj.presets.push('root'); + } + + for (const [configName, optionsNames] of notNull(pkg.config)) { + + const config = this.configs.find(c => c.name === configName); + if (!config) continue; + + const options = config.options.filter(o => optionsNames.includes(o.name)); + if (!options || options.length === 0) continue; + + const imports = options.reduce((acc, opt) => { + const map1 = new Map(Object.entries(acc.packages ?? {})); + const map2 = new Map(Object.entries(opt.packages ?? {})); + acc.packages = Object.fromEntries(mergeImportsMaps(map1, map2)); + return acc; + }); + + configObj.imports = mergeImportsMaps(configObj.imports, new Map(Object.entries(imports.packages ?? {}))); + + configObj.configs = [...new Set([ + ...configObj.configs, + ...options.map(o => o.configs ?? []).flat(), + ])]; + + configObj.rules = [...new Set([ + ...configObj.rules, + ...options.map(o => o.rules ?? []).flat(), + ])]; + + configObj.presets = [...new Set([ + ...configObj.presets, + ...options.map(o => o.presets ?? []).flat(), + ])]; + + } + + return configObj; + } + + /** + * ! NOTE: + * These functions declared bellow are notably hard to read and have lots of exceptions and + * disabled eslint and typescript checks. Unfortunately this is something that I wasn't able to + * prevent because a lot of the AST typescript types are somewhat wrong or simply hard to work + * with them. + * + * But for somewhat help developing and prevent unwanted errors in the future, the types and eslint + * errors are explicitly disabled and types are explicitly overridden. This is why there are so + * many JSDoc type annotations and comments in general. + * + * Any help to make this code more readable and robust is appreciated + */ + + /** + * @typedef {import('estree').Program} Program + * @typedef {( + * import('./lib/astUtils.js').ExpressionOrIdentifier | + * import('estree').ObjectExpression | + * import('estree').SpreadElement + * )} ConfigArrayElement + */ + + /** + * @param {Program} ast The program ast to be manipulated + * @returns {Promise} The final ast with the recreated default export + * @private + */ + async addDefaultExport(ast) { + + /** @type {{program: Program}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + const { program: exportTemplateAst } = recast.parse([ + '/** @type {import(\'eslint\').Linter.FlatConfig[]} */', + 'export default [', + '', + '];', + ].join('\n'), { parser: (await import('recast/parsers/babel.js')) }); + /** @type {import('estree').ExportDefaultDeclaration} */ + // @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded + const exportTemplateNode = exportTemplateAst.body.find(n => n.type === 'ExportDefaultDeclaration'); + + /** @type {import('estree').ExportDefaultDeclaration | undefined} */ + // @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded + let astExport = ast.body.find(n => n.type === 'ExportDefaultDeclaration'); + if (!astExport) { ast.body.push(exportTemplateNode); return ast; } + + /** @type {import('estree').VariableDeclaration | undefined} */ + const oldExportValue = astExport.declaration.type !== 'ArrayExpression' + // @ts-expect-error astExport.declaration is a expression + ? astUtils.createVariable('oldConfig', 'const', astExport.declaration) + : undefined; + + if (!oldExportValue) return ast; + + // @ts-expect-error declaration is a ArrayExpression + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + exportTemplateNode.declaration.elements.push({ + type: 'SpreadElement', + argument: { type: 'Identifier', name: 'oldConfig' }, + }); + + const astExportIdx = ast.body.indexOf(astExport); + ast.body[astExportIdx] = exportTemplateNode; + ast.body.splice(astExportIdx - 1, 0, oldExportValue); + + return ast; + + } + + /** + * @param {import('./types').ConfigFile['rules']} rules The rules to be used to create the object + * @returns {import('estree').ObjectExpression} The object containing the spread rules + * @private + */ + createRulesObject(rules) { + /** @type {import('estree').SpreadElement[]} */ + // @ts-expect-error The array is filtered to remove undefined's + const expressions = rules + .map(r => { + const e = astUtils.stringToExpression(r); + if (e) return astUtils.toSpreadElement(e); + else undefined; + }).filter(e => e); + + return { + type: 'ObjectExpression', + properties: [{ + // @ts-expect-error because ObjectProperty doesn't exist in estree types + type: 'ObjectProperty', + key: { type: 'Identifier', name: 'rules' }, + value: { + type: 'ObjectExpression', + properties: expressions, + }, + }], + }; + + } + + /** + * Adds elements to the default export node, without adding duplicates + * @typedef {import('estree').ArrayExpression} ArrayExpression + * @param {Program} ast The program ast to be manipulated + * @param {ConfigArrayElement[]} elements The elements to be added to the array + * @returns {Program} The final ast with the recreated default export + * @private + */ + addElementsToExport(ast, elements) { + /** @type {import('estree').ExportDefaultDeclaration} */ + // @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded + const exportNode = ast.body.find(n => n.type === 'ExportDefaultDeclaration'); + const exportNodeIdx = ast.body.indexOf(exportNode); + + /** @type {ArrayExpression} */ + // @ts-expect-error declaration is a ArrayExpression + const array = exportNode.declaration; + + for (const e of elements) { + if (e.type !== 'ObjectExpression' && astUtils.findInArray(array, e)) continue; + array.elements.push(e); + } + + exportNode.declaration = array; + ast.body[exportNodeIdx] = exportNode; + + return ast; + } + + /** + * @param {Program} ast The program ast to be manipulated + * @param {import('./types').ConfigFile['imports']} imports The imports map to be used + * @returns {Program} The final ast with the recreated default export + */ + addPackageImports(ast, imports) { + + /** @type {import('estree').ImportDeclaration[]} */ + const importDeclarations = []; + + for (const [pkgName, specifiers] of imports) { + /** @type {import('estree').ImportDeclaration | undefined} */ + // @ts-expect-error type error, the specifier has to be ImportDeclaration to be founded + const existingDeclaration = ast.body.find(s => s.type === 'ImportDeclaration' && s.source.value === pkgName); + + const importDeclaration = astUtils.createImportDeclaration( + pkgName, typeof specifiers === 'string' ? specifiers : undefined, existingDeclaration, + ); + + if (typeof specifiers !== 'string') { + specifiers.forEach(s => { + if (typeof s === 'string') return importDeclaration.addSpecifier(s); + else return importDeclaration.addSpecifier(s[0], s[1]); + }); + } + + if (existingDeclaration) ast.body[ast.body.indexOf(existingDeclaration)] = importDeclaration.body; + else importDeclarations.push(importDeclaration.body); + + } + + ast.body.unshift(...importDeclarations); + + return ast; + } + + + /** + * @param {import('./types').ConfigFile} config The config file object to be transformed into a eslint.config.js file + * @returns {Promise} The generated config file contents + */ + async generate(config) { + + const existingConfig = existsSync(config.path) ? await fs.readFile(config.path, 'utf-8') : ''; + + /** @type {{program: Program}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { program: ast } = recast.parse(existingConfig, { parser: (await import('recast/parsers/babel.js')) }); + + await this.addDefaultExport(ast); + + /** + * @type {ConfigArrayElement[]} + */ + // @ts-expect-error The array is filtered to remove undefined's + const elements = [ + ...config.configs.map(c => astUtils.stringToExpression(c)), + ...config.presets.map(p => { + const e = astUtils.stringToExpression(p); + if (e) return astUtils.toSpreadElement(e); + else undefined; + }), + config.rules.length > 0 + ? this.createRulesObject(config.rules) + : undefined, + ].filter(e => e); + + this.addElementsToExport(ast, elements); + this.addPackageImports(ast, config.imports); + + const finalCode = recast.prettyPrint(ast, { parser: (await import('recast/parsers/babel.js')) }).code; + return finalCode; + + } + + /** + * @param {string} path The path to the file to be written + * @param {string} content The content of the file + * @returns {Promise} + */ + async write(path, content) { + await fs.writeFile(path, content, 'utf-8'); + } + +} diff --git a/packages/cli/src/configsProcessor.js b/packages/cli/src/configsProcessor.js new file mode 100755 index 0000000..e9d1767 --- /dev/null +++ b/packages/cli/src/configsProcessor.js @@ -0,0 +1,168 @@ +#!node +import path from 'node:path'; +import glob from 'picomatch'; +import prompts from 'prompts'; +import c from 'picocolors'; +import str from './lib/str.js'; + +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 + * @returns {string[]} - The detected options + */ + detectOptions(pkg, options, single) { + + /** @type {string[]} */ + const detectedOptions = []; + + for (const option of options) { + + if (option.detect === true) { + detectedOptions.push(option.name); + 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); + if (single) break; + } + } + + return detectedOptions; + } + + /** + * @param {import('./types.js').Package[] | import('./types.js').Package} pkg - The packages to questions the configs + * @param {import('./types').Config[]} configs - The configs to be used + * @returns {Promise} - The selected options by the user + */ + async questionConfig(pkg, configs) { + + const packages = Array.isArray(pkg) ? [...pkg] : [pkg]; + + const instructions = c.dim(`\n${c.bold('A: Toggle all')} - ↑/↓: Highlight option - ←/→/[space]: Toggle selection - enter/return: Complete answer`); + + for (const config of configs) { + + /** @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 === 'multiple' ? 'multiselect' : 'select', + message: str.capitalize(config.name), + choices: config.type === 'confirm' ? [ + { + title: 'Yes', + value: ['yes'], + }, + { + title: 'No', + value: null, + }, + ] : 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] === null) continue; + + if (selectedOptions[config.name].length === 0) continue; + + if (packages.length <= 1) { + packages[0].config = new Map([ + ...(packages[0].config ?? []), + ...Object.entries(selectedOptions), + ]); + 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: 'autocompleteMultiselect', + 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 = new Map([ + ...(pkg.config ?? []), + ...Object.entries(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) { + + /** @type {import('./types.js').Package['config']} */ + const pkgConfig = new Map(); + + for (const config of this.configs.filter(c => !c.manual)) { + pkgConfig.set(config.name, this.detectOptions( + pkg, + config.options, + config.type !== 'multiple', + )); + } + + return pkgConfig; + } + +} + diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js new file mode 100755 index 0000000..20e2906 --- /dev/null +++ b/packages/cli/src/index.js @@ -0,0 +1,4 @@ +import Cli from './cli.js'; + +const cli = new Cli(); +await cli.run(); diff --git a/packages/cli/src/lib/astUtils.js b/packages/cli/src/lib/astUtils.js new file mode 100644 index 0000000..824d48d --- /dev/null +++ b/packages/cli/src/lib/astUtils.js @@ -0,0 +1,170 @@ +import * as recast from 'recast'; + +/** + * @typedef {( + * import('estree').MemberExpression | + * import('estree').Identifier | + * import('estree').CallExpression | + * import('estree').NewExpression + * )} ExpressionOrIdentifier + * This type only includes the expressions used in the cli's config type + * @typedef {import('estree').VariableDeclaration} VariableDeclaration + * @typedef {import('estree').Identifier['name']} IdentifierName + * @typedef {VariableDeclaration['kind']} VariableKind + * @typedef {import('estree').VariableDeclarator['init']} VariableInit + * @typedef {import('estree').SpreadElement} SpreadElement + * @typedef {import('estree').Expression} Expression + * @typedef {import('estree').ArrayExpression} ArrayExpression + */ + +/** + * @param {IdentifierName} identifier Nave of the variable identifier + * @param {VariableKind} [kind] Type of variable declaration + * @param {VariableInit} [init] Initial value of the variable + * @returns {VariableDeclaration} The variable declaration ast node object + */ +export function createVariable(identifier, kind = 'const', init) { + return { + type: 'VariableDeclaration', + kind, + declarations: [{ + type: 'VariableDeclarator', + id: { type: 'Identifier', name: identifier }, + init, + }], + }; +} + +/** + * @param {string} string The expression in string + * @returns {ExpressionOrIdentifier | undefined} The expression or identifier node of that string (undefined if string is not a expression) + */ +export function stringToExpression(string) { + /** @type {ExpressionOrIdentifier} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const e = recast.parse(string).program.body[0].expression; + if (['MemberExpression', 'Identifier', 'CallExpression', 'NewExpression'].includes(e.type)) return e; + else return undefined; +} + +/** + * @param {ArrayExpression} array The array node to search trough + * @param {ExpressionOrIdentifier | SpreadElement} element The element to be search + * @returns {ExpressionOrIdentifier | undefined} The element of the array founded, undefined if it isn't found + */ +export function findInArray(array, element) { + + /** @type {ExpressionOrIdentifier[]} */ + // @ts-expect-error The array should have just tge type above + element = element.type === 'SpreadElement' ? element.argument : element; + + /** @type {ExpressionOrIdentifier[]} */ + // @ts-expect-error The array is filtered to have the type above + const filteredElements = array.elements + .map(n => { + if (n?.type === 'SpreadElement') return n.argument; + return n; + }).filter(n => n && n.type === element.type); + + const toStringElements = filteredElements.map(n => recast.print(n).code); + const toStringElement = recast.print(element).code; + + const idx = toStringElements.findIndex(e => e === toStringElement); + return filteredElements[idx]; +} + +/** + * @param {ExpressionOrIdentifier} expression The expression to be spread + * @returns {SpreadElement} The spread element node + */ +export function toSpreadElement(expression) { + return { + type: 'SpreadElement', + argument: expression, + }; +} + +/** + * @typedef {{ + * body: import('estree').ImportDeclaration + * addSpecifier: (specifier: string, alias?: string) => ThisType + * convertDefaultSpecifier: () => ThisType + * }} ImportDeclarationHelper + * @param {string} source The package name or source path to be imported + * @param {string} [defaultImported] The default specifier imported + * @param {import('estree').ImportDeclaration} [body] The body of the import declaration to start with + * @returns {ImportDeclarationHelper} A helper object for manipulating the import declaration + */ +export function createImportDeclaration(source, defaultImported, body) { + const helper = { + /** @type {import('estree').ImportDeclaration} */ + body: body ?? { + type: 'ImportDeclaration', + specifiers: defaultImported ? [{ + type: 'ImportDefaultSpecifier', + local: { type: 'Identifier', name: defaultImported }, + }] : [], + source: { + type: 'Literal', + value: source, + }, + }, + /** + * Converts a default specifier to a specifier with a alias. + * @example + * import eslit from 'eslit'; + * // Is converted to + * import { default as eslit } from 'eslit'; + * @returns {ThisType} This helper with the converted default specifier + */ + convertDefaultSpecifier() { + const specifier = this.body.specifiers.find(s => s.type === 'ImportDefaultSpecifier'); + if (!specifier) + return this; + + this.body.specifiers.splice( + this.body.specifiers.indexOf(specifier), + 1, + ); + return this.addSpecifier('default', specifier.local.name); + }, + /** + * @param {string} specifier The value to be imported from the package + * @param {string} [alias] The local alias of the value + * @returns {ThisType} This helper with the added specifiers + */ + addSpecifier(specifier, alias) { + this.convertDefaultSpecifier(); + if (this.body.specifiers.find(s => s.local.name === alias || s.local.name === specifier)) + return this; + + this.body.specifiers.push({ + type: 'ImportSpecifier', + imported: { + type: 'Identifier', + name: specifier, + }, + local: { + type: 'Identifier', + name: alias ?? specifier, + }, + }); + + return this; + }, + }; + + if (defaultImported && body && !body.specifiers.find(s => s.type === 'ImportDefaultSpecifier' && s.local.name === defaultImported)) { + helper.addSpecifier('default', defaultImported); + } + return helper; + +} + +export default { + createVariable, + stringToExpression, + toSpreadElement, + findInArray, + createImportDeclaration, +}; diff --git a/packages/cli/src/lib/count.js b/packages/cli/src/lib/count.js new file mode 100644 index 0000000..d772dcd --- /dev/null +++ b/packages/cli/src/lib/count.js @@ -0,0 +1,12 @@ + +/** + * @param {import('../types').Package[]} packages - Package list + * @returns {number} Number of packages' configs + */ +function packagesWithConfigs(packages) { + return packages.map(p => + [...p.config?.values() ?? []].filter((options) => options.length > 0).length, + ).reduce((partial, sum) => partial + sum, 0); +} + +export default { packagesWithConfigs }; diff --git a/packages/cli/src/lib/notNull.js b/packages/cli/src/lib/notNull.js new file mode 100644 index 0000000..b58aa98 --- /dev/null +++ b/packages/cli/src/lib/notNull.js @@ -0,0 +1,15 @@ +/** + * JSDoc types lack a non-null assertion. + * @template T + * @param {T} value The value which to assert against null or undefined + * @returns {NonNullable} The said value + * @throws {TypeError} If the value is unexpectedly null or undefined + * @author Jimmy Wärting - https://github.com/jimmywarting + * @see https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-873331031 + * @see https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-1249287966 + */ +export default function notNull(value) { + // Use `==` to check for both null and undefined + if (value == null) throw new Error('did not expect value to be null or undefined'); + return value; +} 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/packageInstaller.js b/packages/cli/src/packageInstaller.js new file mode 100644 index 0000000..4d9ca9b --- /dev/null +++ b/packages/cli/src/packageInstaller.js @@ -0,0 +1,210 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { exec } from 'node:child_process'; +import { createSpinner } from 'nanospinner'; +import c from 'picocolors'; +import * as recast from 'recast'; +import { readFile, writeFile } from 'node:fs/promises'; +import { readFileSync } from 'node:fs'; + + +/** + * @type {import('./types').PackageManagerHandler} + */ +class CommandHandler { + + /** @type {string} */ + command; + + /** @type {((path: string, packages: string[]) => string | Promise) | undefined} */ + checker; + + /** + * @param {string} command What command to use to install + * @param {(path: string, packages: string[]) => string | Promise} [checker] Checks if a argument should be passed + */ + constructor(command, checker) { + this.command = command; + this.checker = checker; + } + + /** + * @param {string} path The path to run the command + * @param {string[]} packages The packages to be added on the command + * @returns {Promise} + */ + async install(path, packages) { + + if (this.checker) + this.command += await this.checker(path, packages); + + return new Promise((res) => { + const spinner = createSpinner(`Installing packages with ${c.green(this.command)} ${c.dim(packages.join(' '))}`).start(); + try { + const child = exec(`${this.command} ${packages.join(' ')}`, { cwd: path }); + child.stdout?.on('data', (chunk) => spinner.update({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + text: `Installing packages with ${c.green(this.command)} ${c.dim(packages.join(' '))}\n ${c.dim(chunk)}`, + })); + child.stdout?.on('close', () => { + spinner.success({ + text: `Installed packages with ${c.green(this.command)}`, + }); res(); + }); + } + catch (error) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + res(console.error(`Error while installing the packages with ${this.command} ${c.dim(packages.join(' '))} on ${path}: ${error}`)); + } + }); + } +} + +/** + * @type {import('./types').PackageManagerHandler} + */ +class DenoHandler { + + /** + * @param {string} path The path to run the command + * @param {string[]} packages The packages to be added on the command + * @returns {Promise} + */ + async install(path, packages) { + const configPath = join(path, 'eslint.config.js'); + + if (!existsSync(configPath)) return; + + const configFile = await readFile(configPath, 'utf8'); + /** @type {{program: import('estree').Program}}*/ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { program: ast } = recast.parse(configFile, { parser: (await import('recast/parsers/babel.js')) }); + + ast.body.map((node) => { + if (node.type !== 'ImportDeclaration') return node; + + if (packages.includes(node.source.value?.toString() ?? '')) { + node.source.value = `npm:${node.source.value}`; + } + return node; + }); + + await writeFile(configPath, recast.prettyPrint(ast).code, 'utf-8'); + + console.log(c.green('Added npm: specifier to dependencies')); + + } + +} + +export default class PackageInstaller { + + /** + * @typedef {Map} PackagesMap + * @type {PackagesMap} + */ + packagesMap; + + /** + * @typedef {{ + * name: import('./types').PackageManagerName + * description: string + * handler: import('./types').PackageManagerHandler + * }} PackageManager + * @type {PackageManager} + */ + packageManager; + + /** + * @type {Record} + */ + packageManagers = { + deno: { + name: 'deno', + description: 'Adds npm: specifiers to the eslint.config.js file', + handler: new DenoHandler(), + }, + bun: { + name: 'bun', + description: 'Uses bun install', + handler: new CommandHandler('bun install'), + }, + pnpm: { + name: 'pnpm', + description: 'Uses pnpm install', + handler: new CommandHandler('pnpm install --save-dev', (path) => { + if (existsSync(join(path, 'pnpm-workspace.yaml')) && existsSync(join(path, 'package.json'))) + return ' -w'; + else return ''; + }), + }, + yarn: { + name: 'yarn', + description: 'Uses yarn add', + handler: new CommandHandler('yarn add --dev'), + }, + npm: { + name: 'npm', + description: 'Uses npm install', + handler: new CommandHandler('npm install --save-dev'), + }, + }; + + /** + * @param {PackagesMap} packagesMap The map of directories and packages to be installed + * @param {string} root Root directory path + */ + constructor(packagesMap, root) { + this.packagesMap = packagesMap; + this.packageManager = this.detectPackageManager(root); + } + + /** + * @param {string} root Root directory path + * @returns {PackageManager} The package manager detected; + * @private + */ + detectPackageManager(root) { + /** @type {(...path: string[]) => boolean} */ + const exists = (...path) => existsSync(join(root, ...path)); + + switch (true) { + case exists('deno.json'): + case exists('deno.jsonc'): + return this.packageManagers.deno; + + case exists('bun.lockb'): + return this.packageManagers.bun; + + case exists('pnpm-lock.yaml'): + return this.packageManagers.pnpm; + + case exists('yarn.lock'): + return this.packageManagers.yarn; + + case exists('package-lock.json'): + return this.packageManagers.npm; + + case exists('package.json'): + /** @type {{packageManager?: string}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-case-declarations + const { packageManager } = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')); + if (!packageManager) return this.packageManagers.npm; + + if (packageManager.includes('pnpm')) return this.packageManagers.pnpm; + if (packageManager.includes('yarn')) return this.packageManagers.yarn; + if (packageManager.includes('npm')) return this.packageManagers.npm; + + else return this.packageManagers.npm; + + default: return this.packageManagers.npm; + } + } + + async install() { + for (const [path, packages] of this.packagesMap) { + await this.packageManager.handler.install(path, packages); + } + } + +} diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts new file mode 100644 index 0000000..e784a55 --- /dev/null +++ b/packages/cli/src/types.d.ts @@ -0,0 +1,61 @@ +import type { OptionValues } from 'commander'; + +export type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno'; + +export type CliArgs = { + packages?: string[] + mergeToRoot?: boolean + installPkgs?: boolean | PackageManagerName + dir: string +} & OptionValues; + +export type Config = { + name: string + type: 'single' | 'multiple' + manual?: boolean + description?: string + options: { + name: string + packages?: Record + configs?: string[] + rules?: string[] + presets?: string[] + detect?: string[] | true + }[] +} | { + name: string + type: 'confirm' + manual: true + description?: string + options: [{ + name: 'yes' + packages?: Record + configs?: string[] + rules?: string[] + presets?: string[] + detect?: undefined + }] +}; + +export interface Package { + root?: boolean + name: string + path: string + files: string[] + directories: string[] + config?: Map + configFile?: ConfigFile +} + +export interface ConfigFile { + path: string + imports: Map + configs: string[] + presets: string[] + rules: string[] + content?: string +} + +export interface PackageManagerHandler { + install(path: string, packages: string[]): Promise | void +} diff --git a/packages/cli/src/workspace.js b/packages/cli/src/workspace.js new file mode 100644 index 0000000..f4fd0e0 --- /dev/null +++ b/packages/cli/src/workspace.js @@ -0,0 +1,246 @@ +import fs from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import YAML from 'yaml'; +import path, { join } from 'node:path'; +import glob from 'picomatch'; +import picomatch from 'picomatch'; + + +/** + * @template T + * @param {Promise} promise - The async function to try running + * @returns {Promise} - Returns the result of the async function, or null if it errors + */ +async function tryRun(promise) { + try { + return await promise; + } + catch (err) { + return null; + } +} + +/** + * @param {string} directory - The directory to find .gitignore and .eslintignore + * @returns {Promise} - List of ignore glob patterns + */ +async function getIgnoredFiles(directory) { + const gitIgnore = (await tryRun(fs.readFile(join(directory, '.gitignore'), 'utf8')) ?? '') + .split('\n') + .filter(p => p && !p.startsWith('#')) + .map(p => join(directory, '**', p)); + + const eslintIgnore = (await tryRun(fs.readFile(join(directory, '.eslintignore'), 'utf8')) ?? '') + .split('\n') + .filter(p => p && !p.startsWith('#')) + .map(p => join(directory, '**', p)); + + return [...eslintIgnore, ...gitIgnore]; +} + +/** + * @param {string} directory - The directory to work in. + * @returns {Promise} - The package name founded. + */ +async function getPackageName(directory) { + if (existsSync(join(directory, 'package.json'))) { + const file = await fs.readFile(join(directory, 'package.json'), 'utf8'); + /** @type {{name?: string}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const obj = JSON.parse(file); + + if (obj.name) return obj.name; + } + return path.normalize(directory).split('/').at(-1) ?? directory; +} + +export default class Workspace { + + /** + * @param {string} directory - The directory to get the workspace from + * @param {string[] | false} [packagePatterns] + * List of package patterns (`false` to explicitly tell that this workspace is not a monorepo) + */ + constructor(directory, packagePatterns) { + this.dir = directory; + this.packagePatterns = packagePatterns; + } + + /** + * @param {string} [directory] - The directory to work on + * @param {string[]} [ignores] - Glob patterns to ignore + * @returns {Promise<{files: string[], directories: string[]}>} - List of all files in the directory + */ + async getPaths(directory = this.dir, ignores = []) { + + ignores.push( + ...[ + '.git', + '.dist', + '.DS_Store', + 'node_modules', + ].map((f) => join(directory, f)), + ...await getIgnoredFiles(directory), + ); + + const paths = (await fs.readdir(directory)) + .map((f) => path.normalize(join(directory, f))) + .filter((p) => !glob.isMatch(p, ignores)); + + /** @type {string[]} */ + const files = []; + /** @type {string[]} */ + const directories = []; + + for (const path of paths) { + if ((await fs.lstat(path)).isDirectory()) { + const subPaths = await this.getPaths(path, ignores); + directories.push(path, ...subPaths.directories); + files.push(...subPaths.files); + } + else { + files.push(path); + } + } + + return { + files: files.map(p => path.normalize(p.replace(this.dir, './'))), + directories: directories.map(p => path.normalize(p.replace(this.dir, './'))), + }; + } + + /** + * @returns {Promise} - List of packages on a directory; + */ + async getPackagePatterns() { + + /** @type {string[]} */ + let packagePatterns = []; + + const pnpmWorkspace = + existsSync(join(this.dir, 'pnpm-workspace.yaml')) + ? 'pnpm-workspace.yaml' + : existsSync(join(this.dir, 'pnpm-workspace.yml')) + ? 'pnpm-workspace.yml' + : null; + + if (pnpmWorkspace) { + const pnpmWorkspaceYaml = await fs.readFile(join(this.dir, pnpmWorkspace), 'utf8'); + + /** @type {{packages?: string[]}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const pnpmWorkspaceObj = YAML.parse(pnpmWorkspaceYaml); + + packagePatterns.push(...(pnpmWorkspaceObj?.packages ?? [])); + } + else if (existsSync(join(this.dir, 'package.json'))) { + const packageJson = await fs.readFile(join(this.dir, 'package.json'), 'utf8'); + + /** @type {{workspaces?: string[]}} */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const packageJsonObj = JSON.parse(packageJson); + + packagePatterns.push(...(packageJsonObj?.workspaces ?? [])); + } + + return packagePatterns.map(p => { + p = path.normalize(p); + p = p.startsWith('/') ? p.replace('/', '') : p; + p = p.endsWith('/') ? p.slice(0, p.length - 1) : p; + return p; + }); + } + + /** + * @returns {Promise} - The list of packages that exist in the workspace + */ + async getPackages() { + + const paths = await this.getPaths(); + + /** @type {import('./types').Package} */ + const rootPackage = { + root: true, + name: await getPackageName(this.dir), + path: this.dir, + files: paths.files, + directories: paths.directories, + }; + + if (this.packagePatterns === false) return [rootPackage]; + + const packagePatterns = this.packagePatterns ?? await this.getPackagePatterns(); + const packagePaths = paths.directories.filter(d => picomatch.isMatch(d, packagePatterns)); + + /** @type {import('./types').Package[]} */ + const packages = []; + + for (const packagePath of packagePaths) { + packages.push({ + root: false, + path: join(this.dir, packagePath), + name: await getPackageName(join(this.dir, packagePath)), + files: paths.files + .filter(f => picomatch.isMatch(f, `${packagePath}/**/*`)) + .map(f => f.replace(`${packagePath}/`, '')), + directories: paths.directories + .filter(d => picomatch.isMatch(d, `${packagePath}/**/*`)) + .map(d => d.replace(`${packagePath}/`, '')), + }); + + rootPackage.files = rootPackage.files + .filter(f => picomatch.isMatch(f, `!${packagePath}/**/*`)); + + rootPackage.directories = rootPackage.directories + .filter(d => picomatch.isMatch(d, `!${packagePath}/**/*`)); + } + + return [rootPackage, ...packages]; + + } + + /** + * @param {import('./types').Package[]} packages - Packages to be merged into root + * @returns {[import('./types').Package]} A array containing only the root package + */ + mergePackages(packages) { + + const rootPackage = packages.find(p => p.root) ?? packages[0]; + + const merged = packages.reduce((accumulated, pkg) => { + + const files = [...new Set([ + ...accumulated.files, + ...pkg.files.map(f => join(pkg.path, f)), + ] + .map(p => p.replace(`${rootPackage.path}/`, '')), + )]; + + const directories = [...new Set([ + ...accumulated.directories, + ...pkg.directories.map(d => join(pkg.path, d)), + ] + .map(p => p.replace(`${rootPackage.path}/`, ''))), + ]; + + const mergedConfig = new Map(); + for (const [config, options] of pkg.config ?? []) { + const accumulatedOptions = accumulated.config?.get(config) ?? []; + mergedConfig.set(config, [...new Set([...options, ...accumulatedOptions])]); + } + + return { + root: true, + path: rootPackage.path, + name: rootPackage.name, + files, + directories, + config: mergedConfig, + }; + }, rootPackage); + + return [merged]; + + } + +} diff --git a/packages/config/package.json b/packages/config/package.json index 9684538..a7bee8e 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -5,11 +5,11 @@ "main": "index.js", "module": "./src/index.js", "source": "./src/index.js", - "files": [ - "src", - "index.d.ts" - ], - "homepage": "https://github.com/LoredDev/ESLit", + "files": [ + "src", + "index.d.ts" + ], + "homepage": "https://github.com/LoredDev/ESLit", "exports": { "default": "./src/index.js", "import": "./src/index.js", @@ -21,16 +21,16 @@ "test": "echo \"Error: no test specified\" && exit 1", "lint": "eslint ." }, - "repository": { - "directory": "packages/config", - "type": "git", - "url": "https://github.com/LoredDev/ESLit" - }, + "repository": { + "directory": "packages/config", + "type": "git", + "url": "https://github.com/LoredDev/ESLit" + }, "author": { - "email": "contact.guz013@gmail.com", - "name": "Gustavo \"Guz\" L. de Mello", - "url": "https://guz.one" - }, + "email": "contact.guz013@gmail.com", + "name": "Gustavo \"Guz\" L. de Mello", + "url": "https://guz.one" + }, "license": "MIT", "devDependencies": { "@types/eslint__js": "^8.42.0", @@ -44,14 +44,13 @@ "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", "eslint-plugin-jsdoc": "^46.4.4", - "globals": "^13.20.0", - "yaml": "^2.3.1" + "globals": "^13.20.0" }, "peerDependencies": { "eslint": "^8.45.0", "typescript": "^5.1.6" }, - "publishConfig": { - "access": "public" - } + "publishConfig": { + "access": "public" + } } diff --git a/packages/config/src/configs/jsdoc.js b/packages/config/src/configs/jsdoc.js index 9ffe6e2..f3169f6 100644 --- a/packages/config/src/configs/jsdoc.js +++ b/packages/config/src/configs/jsdoc.js @@ -2,7 +2,6 @@ import jsdoc from 'eslint-plugin-jsdoc'; /** * JSDoc rules overrides - * * @type {Readonly} */ const config = { @@ -11,16 +10,6 @@ const config = { rules: { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access ...jsdoc.configs['recommended-typescript-flavor-error'].rules, - - 'jsdoc/tag-lines': ['error', 'always', { - count: 1, - applyToEndTag: false, - startLines: 1, - endLines: 0, - tags: { - param: { lines: 'never' }, - }, - }], }, }; export default config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60c9ef7..c1969bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,9 @@ importers: '@commitlint/types': specifier: ^17.4.4 version: 17.4.4 + '@eslit/cli': + specifier: workspace:* + version: link:packages/cli '@svitejs/changesets-changelog-github-compact': specifier: ^1.1.0 version: 1.1.0 @@ -34,8 +37,23 @@ importers: specifier: ^1.10.9 version: 1.10.9 + fixtures/library: + dependencies: + '@eslit/cli': + specifier: workspace:* + version: link:../../packages/cli + + fixtures/monorepo: + devDependencies: + '@eslit/cli': + specifier: workspace:* + version: link:../../packages/cli + fixtures/svelte: devDependencies: + '@eslit/cli': + specifier: workspace:* + version: link:../../packages/cli '@fontsource/fira-mono': specifier: ^4.5.10 version: 4.5.10 @@ -79,6 +97,46 @@ importers: specifier: ^4.4.2 version: 4.4.2 + packages/cli: + dependencies: + cardinal: + specifier: ^2.1.1 + version: 2.1.1 + commander: + specifier: ^11.0.0 + version: 11.0.0 + nanospinner: + specifier: ^1.1.0 + version: 1.1.0 + picocolors: + specifier: ^1.0.0 + version: 1.0.0 + picomatch: + specifier: ^2.3.1 + version: 2.3.1 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + recast: + specifier: ^0.23.3 + version: 0.23.3 + sisteransi: + specifier: ^1.0.5 + version: 1.0.5 + yaml: + specifier: ^2.3.1 + version: 2.3.1 + devDependencies: + '@types/estree': + specifier: ^1.0.1 + version: 1.0.1 + '@types/node': + specifier: ^20.4.2 + version: 20.4.2 + '@types/prompts': + specifier: ^2.4.4 + version: 2.4.4 + packages/config: dependencies: '@eslint/eslintrc': @@ -99,9 +157,6 @@ importers: globals: specifier: ^13.20.0 version: 13.20.0 - yaml: - specifier: ^2.3.1 - version: 2.3.1 devDependencies: '@types/eslint__js': specifier: ^8.42.0 @@ -849,6 +904,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 @@ -1159,6 +1221,10 @@ packages: dependencies: color-convert: 2.0.1 + /ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + dev: false + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1217,10 +1283,25 @@ packages: engines: {node: '>=0.10.0'} dev: true + /assert@2.0.0: + resolution: {integrity: sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==} + dependencies: + es6-object-assign: 1.1.0 + is-nan: 1.3.2 + object-is: 1.1.5 + util: 0.12.5 + dev: false + + /ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + dependencies: + tslib: 2.4.1 + dev: false + /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - dev: true /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} @@ -1282,7 +1363,6 @@ packages: dependencies: function-bind: 1.1.1 get-intrinsic: 1.2.1 - dev: true /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1302,6 +1382,14 @@ packages: engines: {node: '>=6'} dev: true + /cardinal@2.1.1: + resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} + hasBin: true + dependencies: + ansicolors: 0.3.2 + redeyed: 2.1.1 + dev: false + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1393,6 +1481,11 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /commander@11.0.0: + resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} + engines: {node: '>=16'} + dev: false + /comment-parser@1.3.1: resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} engines: {node: '>= 12.0.0'} @@ -1516,7 +1609,6 @@ packages: dependencies: has-property-descriptors: 1.0.0 object-keys: 1.1.1 - dev: true /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} @@ -1637,6 +1729,10 @@ packages: is-symbol: 1.0.4 dev: true + /es6-object-assign@1.1.0: + resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} + dev: false + /es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} dev: true @@ -1874,7 +1970,6 @@ packages: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - dev: true /esquery@1.5.0: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} @@ -1992,7 +2087,6 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 - dev: true /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} @@ -2025,7 +2119,6 @@ packages: /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true /function.prototype.name@1.1.5: resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} @@ -2053,7 +2146,6 @@ packages: has: 1.0.3 has-proto: 1.0.1 has-symbols: 1.0.3 - dev: true /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} @@ -2113,7 +2205,6 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.1 - dev: true /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2148,31 +2239,26 @@ packages: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: get-intrinsic: 1.2.1 - dev: true /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} - dev: true /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: true /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - dev: true /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 - dev: true /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -2237,6 +2323,14 @@ packages: side-channel: 1.0.4 dev: true + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: false + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -2280,7 +2374,6 @@ packages: /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - dev: true /is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} @@ -2311,12 +2404,27 @@ packages: engines: {node: '>=8'} dev: true + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: false + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + /is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + dev: false + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -2397,7 +2505,6 @@ packages: for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 - dev: true /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -2457,6 +2564,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + /kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -2649,6 +2760,12 @@ packages: hasBin: true dev: true + /nanospinner@1.1.0: + resolution: {integrity: sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA==} + dependencies: + picocolors: 1.0.0 + dev: false + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -2685,10 +2802,17 @@ packages: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} dev: true + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + dev: false + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - dev: true /object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} @@ -2814,7 +2938,6 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -2887,6 +3010,14 @@ packages: hasBin: true dev: true + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + dev: false + /pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} dev: true @@ -2944,6 +3075,17 @@ packages: picomatch: 2.3.1 dev: true + /recast@0.23.3: + resolution: {integrity: sha512-HbCVFh2ANP6a09nzD4lx7XthsxMOJWKX5pIcUwtLrmeEIl3I0DwjCoVXDE0Aobk+7k/mS3H50FK4iuYArpcT6Q==} + engines: {node: '>= 4'} + dependencies: + assert: 2.0.0 + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tslib: 2.4.1 + dev: false + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -2952,6 +3094,12 @@ packages: strip-indent: 3.0.0 dev: true + /redeyed@2.1.1: + resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + dependencies: + esprima: 4.0.1 + dev: false + /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: true @@ -3118,6 +3266,10 @@ packages: totalist: 3.0.1 dev: true + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: false + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -3150,6 +3302,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + /spawndamnit@2.0.0: resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} dependencies: @@ -3433,7 +3590,6 @@ packages: /tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} - dev: true /tsutils@3.21.0(typescript@5.0.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -3590,6 +3746,16 @@ packages: dependencies: punycode: 2.3.0 + /util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.10 + which-typed-array: 1.1.10 + dev: false + /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -3692,7 +3858,6 @@ packages: gopd: 1.0.1 has-tostringtag: 1.0.0 is-typed-array: 1.1.10 - dev: true /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}