Compare commits
5 Commits
renovate/e
...
renovate/h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e0b831592 | ||
|
|
504deabcb6 | ||
|
|
fa9667ef09 | ||
|
|
263e1edb63 | ||
|
|
3aed0f1708 |
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -23,6 +23,11 @@
|
|||||||
"yaml"
|
"yaml"
|
||||||
],
|
],
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"eslegant"
|
"eslegant",
|
||||||
|
"estree",
|
||||||
|
"nanospinner",
|
||||||
|
"picocolors",
|
||||||
|
"picomatch",
|
||||||
|
"sisteransi"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||||
"@typescript-eslint/parser": "^6.4.1",
|
"@typescript-eslint/parser": "^6.4.1",
|
||||||
"eslint-import-resolver-typescript": "^3.6.0",
|
"eslint-import-resolver-typescript": "^3.6.0",
|
||||||
|
"eslint-plugin-compat": "^4.2.0",
|
||||||
"eslint-plugin-i": "2.28.0-2",
|
"eslint-plugin-i": "2.28.0-2",
|
||||||
"eslint-plugin-jsdoc": "^46.5.0",
|
"eslint-plugin-jsdoc": "^46.5.0",
|
||||||
"eslint-plugin-n": "^16.0.2",
|
"eslint-plugin-n": "^16.0.2",
|
||||||
|
|||||||
24
configs/js/src/@types/eslint-plugin-compat.d.ts
vendored
Normal file
24
configs/js/src/@types/eslint-plugin-compat.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Type declaration for the `eslint-plugin-compat` package in a attempt to make it
|
||||||
|
* compatible with the new flat config.
|
||||||
|
* @license MIT
|
||||||
|
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ESLint } from 'eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Check the browser compatibility of your code.
|
||||||
|
*
|
||||||
|
* ---
|
||||||
|
* **Note:** Types in this project where overridden to be compatible with
|
||||||
|
* ESLint new flat config types. ESlint already has backwards compatibility
|
||||||
|
* for plugins not created in the new flat config.
|
||||||
|
* @see {@link https://www.npmjs.com/package/eslint-plugin-compat npm package}
|
||||||
|
*/
|
||||||
|
declare module 'eslint-plugin-compat' {
|
||||||
|
declare const plugin: ESLint.Plugin;
|
||||||
|
export default plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -18,12 +18,12 @@ import importPlugin from 'eslint-plugin-i';
|
|||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-relative-parent-imports
|
// eslint-disable-next-line import/no-relative-parent-imports
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES } from '../constants.js';
|
||||||
|
|
||||||
|
|
||||||
/** @type {import('eslint').Linter.FlatConfig} */
|
/** @type {import('eslint').Linter.FlatConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.builtin,
|
...globals.builtin,
|
||||||
@@ -50,9 +50,9 @@ const config = {
|
|||||||
'unicorn': unicornPlugin,
|
'unicorn': unicornPlugin,
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
'import/extensions': [...tsFiles, ...jsFiles],
|
'import/extensions': FILES,
|
||||||
'import/parsers': {
|
'import/parsers': {
|
||||||
'@typescript-eslint/parser': [...tsFiles, ...jsFiles ],
|
'@typescript-eslint/parser': FILES,
|
||||||
},
|
},
|
||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
node: true,
|
node: true,
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES } from '../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
rules: {
|
rules: {
|
||||||
...{}, // Plugin: eslint-plugin-jsdoc
|
...{}, // Plugin: eslint-plugin-jsdoc
|
||||||
'jsdoc/match-description': 'error',
|
'jsdoc/match-description': 'error',
|
||||||
|
|||||||
@@ -8,11 +8,22 @@
|
|||||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import compatPlugin from 'eslint-plugin-compat';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
import { createVariations } from '../../lib/rule-variations.js';
|
import { createVariations } from '../../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../../constants.js';
|
import { FILES } from '../../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
compat: compatPlugin,
|
||||||
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...{}, // Plugin: eslint-plugin-unicorn
|
...{}, // Plugin: eslint-plugin-unicorn
|
||||||
'unicorn/prefer-add-event-listener': 'error',
|
'unicorn/prefer-add-event-listener': 'error',
|
||||||
@@ -23,6 +34,9 @@ const recommended = createVariations({
|
|||||||
'unicorn/prefer-keyboard-event-key': 'error',
|
'unicorn/prefer-keyboard-event-key': 'error',
|
||||||
'unicorn/prefer-modern-dom-apis': 'error',
|
'unicorn/prefer-modern-dom-apis': 'error',
|
||||||
'unicorn/prefer-query-selector': 'error',
|
'unicorn/prefer-query-selector': 'error',
|
||||||
|
|
||||||
|
...{}, // Plugin: eslint-plugin-compat
|
||||||
|
'compat/compat': 'error',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,5 +47,5 @@ const strict = createVariations({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const node = { recommended, strict };
|
const browser = { recommended, strict };
|
||||||
export default node;
|
export default browser;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import nodePlugin from 'eslint-plugin-n';
|
|||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
|
|
||||||
import { createVariations } from '../../lib/rule-variations.js';
|
import { createVariations } from '../../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../../constants.js';
|
import { FILES } from '../../constants.js';
|
||||||
|
|
||||||
const commonjs = createVariations({
|
const commonjs = createVariations({
|
||||||
files: ['**/*.cts', '**/*.cjs'],
|
files: ['**/*.cts', '**/*.cjs'],
|
||||||
@@ -47,7 +47,7 @@ const commonjs = createVariations({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.nodeBuiltin,
|
...globals.nodeBuiltin,
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
import perfectionistPlugin from 'eslint-plugin-perfectionist';
|
import perfectionistPlugin from 'eslint-plugin-perfectionist';
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES } from '../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
plugins: {
|
plugins: {
|
||||||
// @ts-expect-error because plugin doesn't export correct type
|
// @ts-expect-error because plugin doesn't export correct type
|
||||||
perfectionist: perfectionistPlugin,
|
perfectionist: perfectionistPlugin,
|
||||||
@@ -24,13 +24,13 @@ const recommended = createVariations({
|
|||||||
'arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
|
'arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
|
||||||
'comma-style': 'error',
|
'comma-style': 'error',
|
||||||
'curly': ['error', 'multi-or-nest', 'consistent'],
|
'curly': ['error', 'multi-or-nest', 'consistent'],
|
||||||
'dot-location': 'error',
|
'dot-location': ['error', 'property'],
|
||||||
'eol-last': 'error',
|
'eol-last': 'error',
|
||||||
'generator-star-spacing': ['error', 'before'],
|
'generator-star-spacing': ['error', 'before'],
|
||||||
'no-mixed-spaces-and-tabs': 'error',
|
'no-mixed-spaces-and-tabs': 'error',
|
||||||
'no-multi-spaces': 'error',
|
'no-multi-spaces': 'error',
|
||||||
'no-whitespace-before-property': 'error',
|
'no-whitespace-before-property': 'error',
|
||||||
'padded-blocks': 'error',
|
'padded-blocks': ['error', 'never'],
|
||||||
'rest-spread-spacing': 'error',
|
'rest-spread-spacing': 'error',
|
||||||
'semi-spacing': 'error',
|
'semi-spacing': 'error',
|
||||||
'space-in-parens': 'error',
|
'space-in-parens': 'error',
|
||||||
|
|||||||
6
configs/js/src/configs/index.d.ts
vendored
6
configs/js/src/configs/index.d.ts
vendored
@@ -86,9 +86,13 @@ const configs: Readonly<{
|
|||||||
*/
|
*/
|
||||||
environments: {
|
environments: {
|
||||||
/**
|
/**
|
||||||
* @description
|
* @summary
|
||||||
* Browser environment configuration, use this if you are working
|
* Browser environment configuration, use this if you are working
|
||||||
* on a pure client-side or mixed codebase environment.
|
* on a pure client-side or mixed codebase environment.
|
||||||
|
* @description
|
||||||
|
* Warns about possible incompatible Web APIs on your codebase, you can
|
||||||
|
* configure the target browsers using {@link https://github.com/browserslist/browserslist browserslist}
|
||||||
|
* on `package.json`.
|
||||||
*/
|
*/
|
||||||
browser: {
|
browser: {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable import/max-dependencies */
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Main export files for all the configs objects, merging then in one `configs` object.
|
* Main export files for all the configs objects, merging then in one `configs` object.
|
||||||
|
|||||||
@@ -9,14 +9,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES } from '../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
rules: {
|
rules: {
|
||||||
...{}, // Plugin: eslint-plugin-unicorn
|
...{}, // Plugin: eslint-plugin-unicorn
|
||||||
'unicorn/filename-case': ['error', { case: 'kebabCase' }],
|
'unicorn/filename-case': ['error', { case: 'kebabCase' }],
|
||||||
'unicorn/prevent-abbreviations': 'error',
|
/*
|
||||||
|
* TODO [>=1.0.0]: This will be replaced by a better naming convention.
|
||||||
|
* 'unicorn/prevent-abbreviations': 'error',
|
||||||
|
*/
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES, TS_FILES } from '../constants.js';
|
||||||
|
|
||||||
// TODO [>=1.0.0]: Create a separate config for performance related practices
|
// TODO [>=1.0.0]: Create a separate config for performance related practices
|
||||||
const performance = createVariations({
|
const performance = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
rules: {
|
rules: {
|
||||||
'prefer-object-spread': 'off',
|
'prefer-object-spread': 'off',
|
||||||
'prefer-spread': 'off',
|
'prefer-spread': 'off',
|
||||||
@@ -23,7 +23,7 @@ const performance = createVariations({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const inferrableTypes = createVariations({
|
const inferrableTypes = createVariations({
|
||||||
files: [...tsFiles],
|
files: TS_FILES,
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/no-inferrable-types': 'error',
|
'@typescript-eslint/no-inferrable-types': 'error',
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES } from '../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
rules: {
|
rules: {
|
||||||
...{}, // ESLint rules
|
...{}, // ESLint rules
|
||||||
'constructor-super': 'error',
|
'constructor-super': 'error',
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import noSecretsPluginRegexes from 'eslint-plugin-no-secrets/regexes.js';
|
|||||||
import noSecretsPlugin from 'eslint-plugin-no-secrets';
|
import noSecretsPlugin from 'eslint-plugin-no-secrets';
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES } from '../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
plugins: {
|
plugins: {
|
||||||
'no-secrets': noSecretsPlugin,
|
'no-secrets': noSecretsPlugin,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { tsFiles } from '../constants.js';
|
import { TS_FILES } from '../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles],
|
files: [TS_FILES].flat(),
|
||||||
rules: {
|
rules: {
|
||||||
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
...{}, // Plugin: @typescript-eslint/eslint-plugin
|
||||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||||
|
|||||||
@@ -9,13 +9,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createVariations } from '../lib/rule-variations.js';
|
import { createVariations } from '../lib/rule-variations.js';
|
||||||
import { jsFiles, tsFiles } from '../constants.js';
|
import { FILES } from '../constants.js';
|
||||||
|
|
||||||
const recommended = createVariations({
|
const recommended = createVariations({
|
||||||
files: [...tsFiles, ...jsFiles],
|
files: FILES,
|
||||||
rules: {
|
rules: {
|
||||||
'camelcase': 'error',
|
'camelcase': 'error',
|
||||||
'max-len': ['error', { code: 80, comments: 100, ignoreUrls: true }],
|
'max-len': ['error', {
|
||||||
|
code: 80,
|
||||||
|
comments: 100,
|
||||||
|
ignoreTemplateLiterals: true,
|
||||||
|
ignoreUrls: true,
|
||||||
|
}],
|
||||||
'no-case-declarations': 'error',
|
'no-case-declarations': 'error',
|
||||||
'no-confusing-arrow': 'error',
|
'no-confusing-arrow': 'error',
|
||||||
'no-console': 'error',
|
'no-console': 'error',
|
||||||
@@ -287,7 +292,7 @@ const strict = createVariations({
|
|||||||
}],
|
}],
|
||||||
'max-nested-callbacks': ['error', 10],
|
'max-nested-callbacks': ['error', 10],
|
||||||
'max-params': ['error', 4],
|
'max-params': ['error', 4],
|
||||||
'max-statements': ['error', 10],
|
'max-statements': ['error', 15],
|
||||||
'multiline-comment-style': ['error', 'starred-block'],
|
'multiline-comment-style': ['error', 'starred-block'],
|
||||||
'new-cap': 'error',
|
'new-cap': 'error',
|
||||||
'new-parens': 'error',
|
'new-parens': 'error',
|
||||||
@@ -303,11 +308,8 @@ const strict = createVariations({
|
|||||||
'no-extend-native': 'error',
|
'no-extend-native': 'error',
|
||||||
'no-extra-bind': 'error',
|
'no-extra-bind': 'error',
|
||||||
'no-extra-boolean-cast': 'error',
|
'no-extra-boolean-cast': 'error',
|
||||||
'no-extra-parens': ['error', 'all', {
|
// TODO [>=1.0.0]: Fix no-extra-parens conflict with the unicorn/no-nested-ternary rule.
|
||||||
enforceForArrowConditionals: false,
|
'no-extra-parens': ['error', 'functions'],
|
||||||
nestedBinaryExpressions: false,
|
|
||||||
ternaryOperandBinaryExpressions: false,
|
|
||||||
}],
|
|
||||||
'no-floating-decimal': 'error',
|
'no-floating-decimal': 'error',
|
||||||
'no-implicit-coercion': 'error',
|
'no-implicit-coercion': 'error',
|
||||||
'no-implied-eval': 'error',
|
'no-implied-eval': 'error',
|
||||||
|
|||||||
@@ -5,7 +5,25 @@
|
|||||||
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const jsFiles = ['**/*.js', '**/*.mjs', '**/*.cjs', '**/*.jsx'];
|
const JS_FILES = [
|
||||||
const tsFiles = ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.tsx'];
|
'**/*.js',
|
||||||
|
'**/*.mjs',
|
||||||
|
'**/*.cjs',
|
||||||
|
'**/*.jsx',
|
||||||
|
];
|
||||||
|
const TS_FILES = [
|
||||||
|
'**/*.ts',
|
||||||
|
'**/*.mts',
|
||||||
|
'**/*.cts',
|
||||||
|
'**/*.tsx',
|
||||||
|
];
|
||||||
|
const FILES = [
|
||||||
|
JS_FILES,
|
||||||
|
TS_FILES,
|
||||||
|
].flat();
|
||||||
|
|
||||||
export { jsFiles, tsFiles };
|
export {
|
||||||
|
FILES,
|
||||||
|
JS_FILES,
|
||||||
|
TS_FILES,
|
||||||
|
};
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ function changeLevel(ruleEntry, level) {
|
|||||||
if (typeof level === 'number') {
|
if (typeof level === 'number') {
|
||||||
/** @type {RuleLevel[]} */
|
/** @type {RuleLevel[]} */
|
||||||
const levels = ['error', 'off', 'warn'];
|
const levels = ['error', 'off', 'warn'];
|
||||||
|
// eslint-disable-next-line security/detect-object-injection
|
||||||
level = levels[level];
|
level = levels[level];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +35,6 @@ function changeLevel(ruleEntry, level) {
|
|||||||
return [level, ruleEntry[1]];
|
return [level, ruleEntry[1]];
|
||||||
|
|
||||||
return level;
|
return level;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { configs, defineConfig, presets } from '@eslegant/js';
|
|||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
...presets.strict,
|
...presets.strict,
|
||||||
configs.environments.node.strict.error,
|
configs.environments.node.strict.default,
|
||||||
{
|
{
|
||||||
...configs.documentation.strict.error,
|
...configs.documentation.strict.error,
|
||||||
files: ['configs/**/*.js', 'configs/**/*.ts'],
|
files: ['configs/**/*.js', 'configs/**/*.ts'],
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"eslegant": "workspace:*",
|
"eslegant": "workspace:*",
|
||||||
"@svitejs/changesets-changelog-github-compact": "^1.1.0",
|
"@svitejs/changesets-changelog-github-compact": "^1.1.0",
|
||||||
"eslint": "^8.47.0",
|
"eslint": "^8.47.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^9.0.0",
|
||||||
"turbo": "^1.10.12"
|
"turbo": "^1.10.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
packages/cli/index.d.ts
vendored
10
packages/cli/index.d.ts
vendored
@@ -1,17 +1,17 @@
|
|||||||
import type { CliArgs } from './src/types';
|
import type { CliArgs } from './src/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that handles the creation and running the ESLegant command line interface
|
* Class that handles the creation and running the ESLegant command line interface.
|
||||||
*/
|
*/
|
||||||
export default class Cli {
|
export default class Cli {
|
||||||
/**
|
/**
|
||||||
* @param args Arguments to pass to the cli when its runs
|
* @param args - Arguments to pass to the cli when its runs.
|
||||||
*/
|
*/
|
||||||
constructor(args: CliArgs);
|
public constructor(args: CliArgs);
|
||||||
/**
|
/**
|
||||||
* Runs the cli with the given arguments
|
* Runs the cli with the given arguments.
|
||||||
*/
|
*/
|
||||||
async run(): Promise<void>;
|
public async run(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { CliArgs, Config } from './src/types.d.ts';
|
export type { CliArgs, Config } from './src/types.d.ts';
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { default as default } from './src/cli.js';
|
export { default } from './src/cli.js';
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
import { Command } from 'commander';
|
/* eslint-disable no-console */
|
||||||
import ConfigsProcessor from './configsProcessor.js';
|
/* eslint-disable import/max-dependencies */
|
||||||
import Workspace from './workspace.js';
|
import process from 'node:process';
|
||||||
import c from 'picocolors';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { createSpinner } from 'nanospinner';
|
import { createSpinner } from 'nanospinner';
|
||||||
import count from './lib/count.js';
|
import { Command } from 'commander';
|
||||||
import prompts from 'prompts';
|
|
||||||
import ConfigsFile from './configsFile.js';
|
|
||||||
import cardinal from 'cardinal';
|
|
||||||
import { erase } from 'sisteransi';
|
import { erase } from 'sisteransi';
|
||||||
import PackageInstaller from './packageInstaller.js';
|
import cardinal from 'cardinal';
|
||||||
import notNull from './lib/notNull.js';
|
import prompts from 'prompts';
|
||||||
|
import c from 'picocolors';
|
||||||
|
|
||||||
|
import PackageInstaller from './package-installer.js';
|
||||||
|
import ConfigsProcessor from './configs-processor.js';
|
||||||
|
import ConfigsFile from './configs-file.js';
|
||||||
|
import notNull from './lib/not-null.js';
|
||||||
|
import Workspace from './workspace.js';
|
||||||
|
import count from './lib/count.js';
|
||||||
|
|
||||||
const stdout = process.stdout;
|
const stdout = process.stdout;
|
||||||
|
|
||||||
export default class Cli {
|
export default class Cli {
|
||||||
|
|
||||||
#program = new Command();
|
#program = new Command();
|
||||||
|
|
||||||
/** @type {import('./types').CliArgs} */
|
/** @type {import('./types').CliArgs} */
|
||||||
args = {
|
args = {
|
||||||
dir: process.cwd(),
|
|
||||||
configs: [],
|
configs: [],
|
||||||
|
dir: process.cwd(),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types').CliArgs} [args] Cli arguments object
|
* @param {import('./types').CliArgs} [args] - Cli arguments object.
|
||||||
*/
|
*/
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
this.#program
|
this.#program
|
||||||
@@ -42,24 +46,26 @@ export default class Cli {
|
|||||||
...args,
|
...args,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.args.dir = !this.args.dir.startsWith('/')
|
this.args.dir = this.args.dir.startsWith('/')
|
||||||
? path.join(process.cwd(), this.args.dir)
|
? this.args.dir
|
||||||
: this.args.dir;
|
: path.join(process.cwd(), this.args.dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-lines-per-function, max-statements, complexity
|
||||||
async run() {
|
async run() {
|
||||||
|
|
||||||
process.chdir(this.args.dir);
|
process.chdir(this.args.dir);
|
||||||
|
|
||||||
const configs = this.args.configs;
|
const configs = this.args.configs;
|
||||||
const spinner = createSpinner('Detecting workspace configuration');
|
const spinner = createSpinner('Detecting workspace configuration');
|
||||||
|
|
||||||
const processor = new ConfigsProcessor({ configs });
|
const processor = new ConfigsProcessor({ configs });
|
||||||
const workspace = new Workspace(this.args.dir, this.args?.packages);
|
const workspace = new Workspace(this.args.dir, this.args.packages);
|
||||||
|
|
||||||
let packages = (await workspace.getPackages())
|
let packages = await workspace.getPackages();
|
||||||
.map(pkg => {
|
packages = packages.map((pkg) => {
|
||||||
spinner.update({ text: `Detecting configuration for package ${c.bold(c.blue(pkg.name))}` });
|
spinner.update({
|
||||||
|
text: `Detecting configuration for package ${c.bold(c.blue(pkg.name))}`,
|
||||||
|
});
|
||||||
|
|
||||||
pkg.config = processor.detectConfig(pkg);
|
pkg.config = processor.detectConfig(pkg);
|
||||||
|
|
||||||
@@ -68,92 +74,120 @@ export default class Cli {
|
|||||||
|
|
||||||
spinner.success({
|
spinner.success({
|
||||||
text:
|
text:
|
||||||
'Detecting workspace configuration ' +
|
`Detecting workspace configuration ${
|
||||||
c.dim(`${count.packagesWithConfigs(packages)} configs founded\n`),
|
c.dim(`${count.packagesWithConfigs(packages)} configs founded\n`)}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const merge = this.args.mergeToRoot !== undefined ? this.args.mergeToRoot : packages.length > 1 ?
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const merge = this.args.mergeToRoot ?? (packages.length > 1
|
||||||
/** @type {{merge: boolean}} */
|
/** @type {{merge: boolean}} */
|
||||||
(await prompts({
|
? (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,
|
initial: true,
|
||||||
|
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'))}`,
|
||||||
|
name: 'merge',
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
})).merge : true;
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
|
})).merge : true);
|
||||||
|
|
||||||
console.log(c.dim('\nPlease select which options you prefer\n'));
|
console.log(c.dim('\nPlease select which options you prefer\n'));
|
||||||
|
|
||||||
packages = await processor.questionConfig(
|
packages = await processor.questionConfig(
|
||||||
merge ? workspace.mergePackages(packages) : packages,
|
merge ? Workspace.mergePackages(packages) : packages,
|
||||||
configs.filter(c => c.manual),
|
configs.filter(config => config.manual),
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileHandler = new ConfigsFile(configs, packages.find(c => c.root)?.path);
|
const fileHandler = new ConfigsFile(
|
||||||
|
configs,
|
||||||
|
packages.find(config => config.root)?.path,
|
||||||
|
);
|
||||||
|
|
||||||
for (const pkg of packages) {
|
for (const pkg of packages) {
|
||||||
|
|
||||||
pkg.configFile = fileHandler.generateObj(pkg);
|
pkg.configFile = fileHandler.generateObj(pkg);
|
||||||
pkg.configFile.content = await fileHandler.generate(pkg.configFile);
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
pkg.configFile.content = await ConfigsFile.generate(pkg.configFile);
|
||||||
|
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const shouldWrite =
|
const shouldWrite =
|
||||||
/** @type {{write: boolean}} */
|
/** @type {{write: boolean}} */
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
(await prompts({
|
(await prompts({
|
||||||
type: 'confirm',
|
initial: true,
|
||||||
name: 'write',
|
|
||||||
message: `Do you want to write this config file for ${pkg.root
|
message: `Do you want to write this config file for ${pkg.root
|
||||||
? c.blue('the root directory')
|
? c.blue('the root directory')
|
||||||
: c.blue(pkg.name)
|
: c.blue(pkg.name)
|
||||||
}?\n\n${cardinal.highlight(pkg.configFile.content)}`,
|
}?\n\n${cardinal.highlight(pkg.configFile.content)}`,
|
||||||
initial: true,
|
name: 'write',
|
||||||
|
type: 'confirm',
|
||||||
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
})).write;
|
})).write;
|
||||||
|
|
||||||
stdout.write(erase.lines(pkg.configFile.content.split('\n').length + 2));
|
stdout.write(
|
||||||
|
erase.lines(pkg.configFile.content.split('\n').length + 2),
|
||||||
if (shouldWrite) await fileHandler.write(pkg.configFile.path, pkg.configFile.content);
|
);
|
||||||
|
|
||||||
|
if (shouldWrite) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await ConfigsFile.write(
|
||||||
|
pkg.configFile.path,
|
||||||
|
pkg.configFile.content,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const packagesMap = new Map(packages.map(p => [p.path, [...notNull(p.configFile).imports.keys()]]));
|
const packagesMap = new Map(packages.map(p =>
|
||||||
const installer = new PackageInstaller(packagesMap, packages.find(p => p.root === true)?.path ?? this.args.dir);
|
[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'} */
|
/** @type {boolean | 'changePackage'} */
|
||||||
let installPkgs = this.args.installPkgs !== undefined ? true :
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
/** @type {{install: boolean | 'changePackage'}} */
|
let installPkgs = this.args.installPkgs ?? (await prompts({
|
||||||
(await prompts({
|
|
||||||
name: 'install',
|
|
||||||
message:
|
|
||||||
`Would you like to ESLit to install the npm packages with ${c.green(installer.packageManager.name)}?\n${c.reset(c.dim(` Packages to install: ${[...new Set([...packagesMap.values()])].join(' ')}\n`))}`,
|
|
||||||
choices: [
|
choices: [
|
||||||
{ title: 'Yes, install all packages', value: true, description: installer.packageManager.description },
|
{
|
||||||
|
description: installer.packageManager.description,
|
||||||
|
title: 'Yes, install all packages',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
{ title: 'No, I will install them manually', value: false },
|
{ title: 'No, I will install them manually', value: false },
|
||||||
{ title: 'Change package manager', value: 'changePackage' },
|
{ title: 'Change package manager', value: 'changePackage' },
|
||||||
],
|
],
|
||||||
|
message:
|
||||||
|
`Would you like to ESLit to install the npm packages with ${c.green(installer.packageManager.name)}?\n${c.reset(c.dim(` Packages to install: ${[...new Set(packagesMap.values())].join(' ')}\n`))}`,
|
||||||
|
name: 'install',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
})).install;
|
})).install;
|
||||||
|
|
||||||
if (installPkgs === 'changePackage') {
|
if (installPkgs === 'changePackage') {
|
||||||
/** @type {{manager: import('./types').PackageManagerName}} */
|
/** @type {{manager: import('./types').PackageManagerName}} */
|
||||||
const prompt = await prompts({
|
const prompt = await prompts({
|
||||||
name: 'manager',
|
choices: Object.values(installer.packageManagers).map(m => ({
|
||||||
|
description: m.description,
|
||||||
|
title: m.name,
|
||||||
|
value: m.name,
|
||||||
|
})),
|
||||||
message: 'What package manager do you want ESLit to use?',
|
message: 'What package manager do you want ESLit to use?',
|
||||||
choices: Object.values(installer.packageManagers).map(m => {
|
name: 'manager',
|
||||||
return { title: m.name, description: m.description, value: m.name };
|
|
||||||
}),
|
|
||||||
type: 'select',
|
type: 'select',
|
||||||
});
|
});
|
||||||
installer.packageManager = installer.packageManagers[prompt.manager];
|
installer.packageManager =
|
||||||
|
installer.packageManagers[prompt.manager];
|
||||||
|
|
||||||
if (!installer.packageManager) throw console.log(c.red('You must select a package manager'));
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (!installer.packageManager)
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-throw-literal, @typescript-eslint/no-confusing-void-expression
|
||||||
|
throw console.log(c.red('You must select a package manager'));
|
||||||
|
|
||||||
installPkgs = true;
|
installPkgs = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installPkgs) await installer.install();
|
if (installPkgs) await installer.install();
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
import path from 'node:path';
|
|
||||||
import notNull from './lib/notNull.js';
|
|
||||||
import { parse, prettyPrint } from 'recast';
|
|
||||||
import fs from 'node:fs/promises';
|
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import astUtils from './lib/astUtils.js';
|
import process from 'node:process';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
import { parse, prettyPrint } from 'recast';
|
||||||
|
|
||||||
|
import astUtils from './lib/ast-utils.js';
|
||||||
|
import notNull from './lib/not-null.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types').ConfigFile['imports']} map1 - The map to has it values merged from map2
|
* @param {import('./types.js').ConfigFile['imports']} map1 -
|
||||||
* @param {import('./types').ConfigFile['imports']} map2 - The map to has it values merged to map1
|
* The map to has it values merged from map2.
|
||||||
* @returns {import('./types').ConfigFile['imports']} The resulting map
|
* @param {import('./types.js').ConfigFile['imports']} map2 -
|
||||||
|
* The map to has it values merged to map1.
|
||||||
|
* @returns {import('./types.js').ConfigFile['imports']} The resulting map.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line max-statements
|
||||||
function mergeImportsMaps(map1, map2) {
|
function mergeImportsMaps(map1, map2) {
|
||||||
for (const [key, value] of map2) {
|
for (const [key, value] of map2) {
|
||||||
if (!map1.has(key)) {
|
if (!map1.has(key)) {
|
||||||
map1.set(key, value);
|
map1.set(key, value);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const imports1 = notNull(map1.get(key));
|
const imports1 = notNull(map1.get(key));
|
||||||
@@ -23,23 +30,37 @@ function mergeImportsMaps(map1, map2) {
|
|||||||
* Because arrays and objects are always different independently from having equal values
|
* 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.
|
* ([] === [] -> false). It is converted to a string so the comparison can be made.
|
||||||
*/
|
*/
|
||||||
switch ([typeof imports1 === 'string', typeof imports2 === 'string'].join(',')) {
|
switch ([
|
||||||
case 'true,true':
|
typeof imports1 === 'string',
|
||||||
if (imports1.toString() === imports2.toString())
|
typeof imports2 === 'string',
|
||||||
|
].join(',')) {
|
||||||
|
case 'true,true': {
|
||||||
|
if (imports1.toString() === imports2.toString()) {
|
||||||
map1.set(key, value);
|
map1.set(key, value);
|
||||||
else
|
}
|
||||||
map1.set(key, [['default', imports1.toString()], ['default', imports2.toString()]]);
|
else {
|
||||||
|
map1.set(key, [
|
||||||
|
['default', imports1.toString()],
|
||||||
|
['default', imports2.toString()],
|
||||||
|
]);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'true,false':
|
}
|
||||||
|
case 'true,false': {
|
||||||
map1.set(key, [['default', imports1.toString()], ...imports2]);
|
map1.set(key, [['default', imports1.toString()], ...imports2]);
|
||||||
break;
|
break;
|
||||||
case 'false,true':
|
}
|
||||||
|
case 'false,true': {
|
||||||
map1.set(key, [['default', imports2.toString()], ...imports1]);
|
map1.set(key, [['default', imports2.toString()], ...imports1]);
|
||||||
break;
|
break;
|
||||||
case 'false,false':
|
}
|
||||||
|
case 'false,false': {
|
||||||
map1.set(key, [...imports1, ...imports2]);
|
map1.set(key, [...imports1, ...imports2]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
// No nothing
|
||||||
|
}
|
||||||
if (typeof map1.get(key) !== 'string')
|
if (typeof map1.get(key) !== 'string')
|
||||||
map1.set(key, [...new Set(map1.get(key))]);
|
map1.set(key, [...new Set(map1.get(key))]);
|
||||||
}
|
}
|
||||||
@@ -47,9 +68,9 @@ function mergeImportsMaps(map1, map2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path1 The path to traverse from
|
* @param {string} path1 - The path to traverse from.
|
||||||
* @param {string} root The root path
|
* @param {string} root - The root path to be removed from the path1.
|
||||||
* @returns {string} The path to traverse
|
* @returns {string} The path to traverse.
|
||||||
*/
|
*/
|
||||||
function getPathDepth(path1, root) {
|
function getPathDepth(path1, root) {
|
||||||
const pathDepth = path1.replace(root, '').split('/').slice(1);
|
const pathDepth = path1.replace(root, '').split('/').slice(1);
|
||||||
@@ -58,13 +79,12 @@ function getPathDepth(path1, root) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class ConfigsWriter {
|
export default class ConfigsWriter {
|
||||||
|
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
root = process.cwd();
|
root = process.cwd();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types').Config[]} configs The array of configs to construct from
|
* @param {import('./types.js').Config[]} configs - The array of configs to construct from.
|
||||||
* @param {string} [root] The root directory path
|
* @param {string} [root] - The root directory path.
|
||||||
*/
|
*/
|
||||||
constructor(configs, root) {
|
constructor(configs, root) {
|
||||||
this.configs = configs;
|
this.configs = configs;
|
||||||
@@ -72,93 +92,13 @@ export default class ConfigsWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types').Package} pkg The package to generate the config string from
|
* @param {Program} ast - The program ast to be manipulated.
|
||||||
* @returns {import('./types').ConfigFile} The config file object
|
* @returns {Promise<Program>} The final ast with the recreated default export.
|
||||||
*/
|
|
||||||
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<Program>} The final ast with the recreated default export
|
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async addDefaultExport(ast) {
|
static async addDefaultExport(ast) {
|
||||||
|
|
||||||
/** @type {{program: Program}} */
|
/** @type {{program: Program}} */
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||||
const { program: exportTemplateAst } = parse([
|
const { program: exportTemplateAst } = parse([
|
||||||
'/** @type {import(\'eslint\').Linter.FlatConfig[]} */',
|
'/** @type {import(\'eslint\').Linter.FlatConfig[]} */',
|
||||||
@@ -168,78 +108,81 @@ export default class ConfigsWriter {
|
|||||||
].join('\n'), { parser: (await import('recast/parsers/babel.js')) });
|
].join('\n'), { parser: (await import('recast/parsers/babel.js')) });
|
||||||
/** @type {import('estree').ExportDefaultDeclaration} */
|
/** @type {import('estree').ExportDefaultDeclaration} */
|
||||||
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
||||||
const exportTemplateNode = exportTemplateAst.body.find(n => n.type === 'ExportDefaultDeclaration');
|
const exportTemplateNode = exportTemplateAst.body
|
||||||
|
.find(n => n.type === 'ExportDefaultDeclaration');
|
||||||
|
|
||||||
/** @type {import('estree').ExportDefaultDeclaration | undefined} */
|
/** @type {import('estree').ExportDefaultDeclaration | undefined} */
|
||||||
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
||||||
let astExport = ast.body.find(n => n.type === 'ExportDefaultDeclaration');
|
const astExport = ast.body
|
||||||
|
.find(n => n.type === 'ExportDefaultDeclaration');
|
||||||
|
|
||||||
if (!astExport) { ast.body.push(exportTemplateNode); return ast; }
|
if (!astExport) { ast.body.push(exportTemplateNode); return ast; }
|
||||||
|
|
||||||
/** @type {import('estree').VariableDeclaration | undefined} */
|
/** @type {import('estree').VariableDeclaration | undefined} */
|
||||||
const oldExportValue = astExport.declaration.type !== 'ArrayExpression'
|
const oldExportValue = astExport.declaration.type === 'ArrayExpression'
|
||||||
|
? undefined
|
||||||
|
: astUtils.createVariable(
|
||||||
|
'oldConfig',
|
||||||
// @ts-expect-error astExport.declaration is a expression
|
// @ts-expect-error astExport.declaration is a expression
|
||||||
? astUtils.createVariable('oldConfig', 'const', astExport.declaration)
|
astExport.declaration,
|
||||||
: undefined;
|
'const',
|
||||||
|
);
|
||||||
|
|
||||||
if (!oldExportValue) return ast;
|
if (!oldExportValue) return ast;
|
||||||
|
|
||||||
// @ts-expect-error declaration is a ArrayExpression
|
// @ts-expect-error declaration is a ArrayExpression
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||||
exportTemplateNode.declaration.elements.push({
|
exportTemplateNode.declaration.elements.push({
|
||||||
|
argument: { name: 'oldConfig', type: 'Identifier' },
|
||||||
type: 'SpreadElement',
|
type: 'SpreadElement',
|
||||||
argument: { type: 'Identifier', name: 'oldConfig' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const astExportIdx = ast.body.indexOf(astExport);
|
const astExportIdx = ast.body.indexOf(astExport);
|
||||||
|
// eslint-disable-next-line security/detect-object-injection
|
||||||
ast.body[astExportIdx] = exportTemplateNode;
|
ast.body[astExportIdx] = exportTemplateNode;
|
||||||
ast.body.splice(astExportIdx - 1, 0, oldExportValue);
|
ast.body.splice(astExportIdx - 1, 0, oldExportValue);
|
||||||
|
|
||||||
return ast;
|
return ast;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types').ConfigFile['rules']} rules The rules to be used to create the object
|
* ! NOTE:
|
||||||
* @returns {import('estree').ObjectExpression} The object containing the spread rules
|
* These functions declared bellow are notably hard to read and have lots of exceptions and
|
||||||
* @private
|
* 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.
|
||||||
*/
|
*/
|
||||||
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').Program} Program
|
||||||
|
* @typedef {(
|
||||||
|
* import('./lib/ast-utils.js').ExpressionOrIdentifier |
|
||||||
|
* import('estree').ObjectExpression |
|
||||||
|
* import('estree').SpreadElement
|
||||||
|
* )} ConfigArrayElement
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds elements to the default export node, without adding duplicates.
|
||||||
* @typedef {import('estree').ArrayExpression} ArrayExpression
|
* @typedef {import('estree').ArrayExpression} ArrayExpression
|
||||||
* @param {Program} ast The program ast to be manipulated
|
* @param {Program} ast - The program ast to be manipulated.
|
||||||
* @param {ConfigArrayElement[]} elements The elements to be added to the array
|
* @param {ConfigArrayElement[]} elements - The elements to be added to the array.
|
||||||
* @returns {Program} The final ast with the recreated default export
|
* @returns {Program} The final ast with the recreated default export.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
addElementsToExport(ast, elements) {
|
static addElementsToExport(ast, elements) {
|
||||||
/** @type {import('estree').ExportDefaultDeclaration} */
|
/** @type {import('estree').ExportDefaultDeclaration} */
|
||||||
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
// @ts-expect-error Node type needs to be ExportDefaultDeclaration to be founded
|
||||||
const exportNode = ast.body.find(n => n.type === 'ExportDefaultDeclaration');
|
const exportNode = ast.body.find(n =>
|
||||||
|
n.type === 'ExportDefaultDeclaration',
|
||||||
|
);
|
||||||
const exportNodeIdx = ast.body.indexOf(exportNode);
|
const exportNodeIdx = ast.body.indexOf(exportNode);
|
||||||
|
|
||||||
/** @type {ArrayExpression} */
|
/** @type {ArrayExpression} */
|
||||||
@@ -247,45 +190,61 @@ export default class ConfigsWriter {
|
|||||||
const array = exportNode.declaration;
|
const array = exportNode.declaration;
|
||||||
|
|
||||||
for (const e of elements) {
|
for (const e of elements) {
|
||||||
if (e.type !== 'ObjectExpression' && astUtils.findInArray(array, e)) continue;
|
if (!(
|
||||||
|
e.type !== 'ObjectExpression' &&
|
||||||
|
astUtils.findInArray(array, e)
|
||||||
|
))
|
||||||
array.elements.push(e);
|
array.elements.push(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
exportNode.declaration = array;
|
exportNode.declaration = array;
|
||||||
|
// eslint-disable-next-line security/detect-object-injection
|
||||||
ast.body[exportNodeIdx] = exportNode;
|
ast.body[exportNodeIdx] = exportNode;
|
||||||
|
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Program} ast The program ast to be manipulated
|
* @param {Program} ast - The program ast to be manipulated.
|
||||||
* @param {import('./types').ConfigFile['imports']} imports The imports map to be used
|
* @param {import('./types.js').ConfigFile['imports']} imports - The imports map to be used.
|
||||||
* @returns {Program} The final ast with the recreated default export
|
* @returns {Program} The final ast with the recreated default export.
|
||||||
*/
|
*/
|
||||||
addPackageImports(ast, imports) {
|
static addPackageImports(ast, imports) {
|
||||||
|
|
||||||
/** @type {import('estree').ImportDeclaration[]} */
|
/** @type {import('estree').ImportDeclaration[]} */
|
||||||
const importDeclarations = [];
|
const importDeclarations = [];
|
||||||
|
|
||||||
for (const [pkgName, specifiers] of imports) {
|
for (const [pkgName, specifiers] of imports) {
|
||||||
/** @type {import('estree').ImportDeclaration | undefined} */
|
/** @type {import('estree').ImportDeclaration | undefined} */
|
||||||
// @ts-expect-error type error, the specifier has to be ImportDeclaration to be founded
|
// @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 existingDeclaration = ast.body.find(s =>
|
||||||
|
s.type === 'ImportDeclaration' &&
|
||||||
|
s.source.value === pkgName,
|
||||||
|
);
|
||||||
|
|
||||||
const importDeclaration = astUtils.createImportDeclaration(
|
const importDeclaration = astUtils.createImportDeclaration(
|
||||||
pkgName, typeof specifiers === 'string' ? specifiers : undefined, existingDeclaration,
|
pkgName,
|
||||||
|
typeof specifiers === 'string'
|
||||||
|
? specifiers
|
||||||
|
: undefined,
|
||||||
|
existingDeclaration,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (typeof specifiers !== 'string') {
|
if (typeof specifiers !== 'string') {
|
||||||
specifiers.forEach(s => {
|
for (const s of specifiers) {
|
||||||
if (typeof s === 'string') return importDeclaration.addSpecifier(s);
|
if (typeof s === 'string')
|
||||||
else return importDeclaration.addSpecifier(s[0], s[1]);
|
importDeclaration.addSpecifier(s);
|
||||||
});
|
else
|
||||||
|
importDeclaration.addSpecifier(s[0], s[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingDeclaration) ast.body[ast.body.indexOf(existingDeclaration)] = importDeclaration.body;
|
if (existingDeclaration) {
|
||||||
else importDeclarations.push(importDeclaration.body);
|
ast.body[ast.body.indexOf(existingDeclaration)] =
|
||||||
|
importDeclaration.body;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
importDeclarations.push(importDeclaration.body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.body.unshift(...importDeclarations);
|
ast.body.unshift(...importDeclarations);
|
||||||
@@ -293,20 +252,55 @@ export default class ConfigsWriter {
|
|||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./types.js').ConfigFile['rules']} rules -
|
||||||
|
* The rules to be used to create the object.
|
||||||
|
* @returns {import('estree').ObjectExpression} The object containing the spread rules.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static 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);
|
||||||
|
return e ? astUtils.toSpreadElement(e) : undefined;
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
return {
|
||||||
|
properties: [{
|
||||||
|
key: { name: 'rules', type: 'Identifier' },
|
||||||
|
// @ts-expect-error because ObjectProperty doesn't exist in estree types
|
||||||
|
type: 'ObjectProperty',
|
||||||
|
value: {
|
||||||
|
properties: expressions,
|
||||||
|
type: 'ObjectExpression',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
type: 'ObjectExpression',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types').ConfigFile} config The config file object to be transformed into a eslint.config.js file
|
* @param {import('./types.js').ConfigFile} config -
|
||||||
* @returns {Promise<string>} The generated config file contents
|
* The config file object to be transformed into a eslint.config.js file.
|
||||||
|
* @returns {Promise<string>} The generated config file contents.
|
||||||
*/
|
*/
|
||||||
async generate(config) {
|
static async generate(config) {
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
const existingConfig = existsSync(config.path) ? await fs.readFile(config.path, 'utf-8') : '';
|
const existingConfig = existsSync(config.path)
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
? await fs.readFile(config.path, 'utf8')
|
||||||
|
: '';
|
||||||
|
|
||||||
/** @type {{program: Program}} */
|
/** @type {{program: Program}} */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const { program: ast } = parse(existingConfig, { parser: (await import('recast/parsers/babel.js')) });
|
const { program: ast } = parse(
|
||||||
|
existingConfig,
|
||||||
|
{ parser: (await import('recast/parsers/babel.js')) },
|
||||||
|
);
|
||||||
|
|
||||||
await this.addDefaultExport(ast);
|
await ConfigsWriter.addDefaultExport(ast);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {ConfigArrayElement[]}
|
* @type {ConfigArrayElement[]}
|
||||||
@@ -314,31 +308,100 @@ export default class ConfigsWriter {
|
|||||||
// @ts-expect-error The array is filtered to remove undefined's
|
// @ts-expect-error The array is filtered to remove undefined's
|
||||||
const elements = [
|
const elements = [
|
||||||
...config.configs.map(c => astUtils.stringToExpression(c)),
|
...config.configs.map(c => astUtils.stringToExpression(c)),
|
||||||
...config.presets.map(p => {
|
...config.presets.map((p) => {
|
||||||
const e = astUtils.stringToExpression(p);
|
const e = astUtils.stringToExpression(p);
|
||||||
if (e) return astUtils.toSpreadElement(e);
|
return e ? astUtils.toSpreadElement(e) : undefined;
|
||||||
else undefined;
|
|
||||||
}),
|
}),
|
||||||
config.rules.length > 0
|
config.rules.length > 0
|
||||||
? this.createRulesObject(config.rules)
|
? ConfigsWriter.createRulesObject(config.rules)
|
||||||
: undefined,
|
: undefined,
|
||||||
].filter(e => e);
|
].filter(Boolean);
|
||||||
|
|
||||||
this.addElementsToExport(ast, elements);
|
ConfigsWriter.addElementsToExport(ast, elements);
|
||||||
this.addPackageImports(ast, config.imports);
|
ConfigsWriter.addPackageImports(ast, config.imports);
|
||||||
|
|
||||||
const finalCode = prettyPrint(ast, { parser: (await import('recast/parsers/babel.js')) }).code;
|
const finalCode = prettyPrint(
|
||||||
|
ast,
|
||||||
|
{ parser: (await import('recast/parsers/babel.js')) },
|
||||||
|
).code;
|
||||||
return finalCode;
|
return finalCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./types.js').Package} pkg - The package to generate the config string from.
|
||||||
|
* @returns {import('./types.js').ConfigFile} The config file object.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line max-statements
|
||||||
|
generateObj(pkg) {
|
||||||
|
/** @type {import('./types.js').ConfigFile} */
|
||||||
|
const configObj = {
|
||||||
|
configs: [],
|
||||||
|
imports: new Map(),
|
||||||
|
path: join(pkg.path, 'eslint.config.js'),
|
||||||
|
presets: [],
|
||||||
|
rules: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!pkg.root) {
|
||||||
|
const rootConfig = 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);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
if (!config) continue;
|
||||||
|
|
||||||
|
const options = config.options.filter(o =>
|
||||||
|
optionsNames.includes(o.name),
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-continue
|
||||||
|
if (!options || options.length === 0) continue;
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/no-array-reduce
|
||||||
|
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.flatMap(o => o.configs ?? []),
|
||||||
|
])];
|
||||||
|
|
||||||
|
configObj.rules = [...new Set([
|
||||||
|
...configObj.rules,
|
||||||
|
...options.flatMap(o => o.rules ?? []),
|
||||||
|
])];
|
||||||
|
|
||||||
|
configObj.presets = [...new Set([
|
||||||
|
...configObj.presets,
|
||||||
|
...options.flatMap(o => o.presets ?? []),
|
||||||
|
])];
|
||||||
|
}
|
||||||
|
|
||||||
|
return configObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path The path to the file to be written
|
* @param {string} path - The path to the file to be written.
|
||||||
* @param {string} content The content of the file
|
* @param {string} content - The content of the file.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async write(path, content) {
|
static async write(path, content) {
|
||||||
await fs.writeFile(path, content, 'utf-8');
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
await fs.writeFile(path, content, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,56 +1,75 @@
|
|||||||
#!node
|
import process from 'node:process';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import glob from 'picomatch';
|
|
||||||
import prompts from 'prompts';
|
import prompts from 'prompts';
|
||||||
|
import glob from 'picomatch';
|
||||||
import c from 'picocolors';
|
import c from 'picocolors';
|
||||||
|
|
||||||
import capitalize from './lib/capitalize.js';
|
import capitalize from './lib/capitalize.js';
|
||||||
|
|
||||||
export default class ConfigsProcessor {
|
export default class ConfigsProcessor {
|
||||||
/** @type {string} */
|
|
||||||
dir = process.cwd();
|
|
||||||
|
|
||||||
/** @type {import('./types.js').Config[]} */
|
/** @type {import('./types.js').Config[]} */
|
||||||
configs;
|
configs;
|
||||||
|
|
||||||
/** @type {string[] | undefined} */
|
/** @type {string} */
|
||||||
#packagesPatterns;
|
dir = process.cwd();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @param {{
|
||||||
* configs: import('./types.js').Config[],
|
* configs: import('./types.js').Config[],
|
||||||
* packages?: string[],
|
* packages?: string[],
|
||||||
* directory?: string,
|
* directory?: string,
|
||||||
* }} options - Cli options
|
* }} options - Cli options.
|
||||||
*/
|
*/
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.#packagesPatterns = options.packages;
|
this.configs = options.configs;
|
||||||
this.configs = options?.configs;
|
|
||||||
this.dir = path.normalize(options.directory ?? this.dir);
|
this.dir = path.normalize(options.directory ?? this.dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types.js').Package} pkg - Package to detect from
|
* @param {import('./types.js').Package} pkg - The package to detect configs.
|
||||||
* @param {import('./types.js').Config['options']} options - Options to be passed
|
* @returns {import('./types.js').Package['config']} - Detected configs record.
|
||||||
* @param {boolean} single - Whether to only detect one option
|
|
||||||
* @returns {string[]} - The detected options
|
|
||||||
*/
|
*/
|
||||||
detectOptions(pkg, options, single) {
|
detectConfig(pkg) {
|
||||||
|
/** @type {import('./types.js').Package['config']} */
|
||||||
|
const pkgConfig = new Map();
|
||||||
|
|
||||||
|
for (const config of this.configs.filter(cfg => !cfg.manual)) {
|
||||||
|
pkgConfig.set(config.name, ConfigsProcessor.detectOptions(
|
||||||
|
pkg,
|
||||||
|
config.options,
|
||||||
|
config.type !== 'multiple',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
static detectOptions(pkg, options, single) {
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
const detectedOptions = [];
|
const detectedOptions = [];
|
||||||
|
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
|
|
||||||
if (option.detect === true) {
|
if (option.detect === true) {
|
||||||
detectedOptions.push(option.name);
|
detectedOptions.push(option.name);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (!option.detect) continue;
|
// eslint-disable-next-line no-continue
|
||||||
|
else if (!option.detect) { continue; }
|
||||||
|
|
||||||
const match = glob(option.detect);
|
const match = glob(option.detect);
|
||||||
|
|
||||||
const files = pkg.files.filter(f => match ? match(f) : false);
|
const files = pkg.files.filter(f => (match ? match(f) : false));
|
||||||
const directories = pkg.directories.filter(f => match ? match(f) : false);
|
const directories = pkg.directories.filter(f =>
|
||||||
|
(match ? match(f) : false),
|
||||||
|
);
|
||||||
|
|
||||||
if (files.length > 0 || directories.length > 0) {
|
if (files.length > 0 || directories.length > 0) {
|
||||||
detectedOptions.push(option.name);
|
detectedOptions.push(option.name);
|
||||||
@@ -62,26 +81,24 @@ export default class ConfigsProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types.js').Package[] | import('./types.js').Package} pkg - The packages to questions the configs
|
* @param {import('./types.js').Package[] | import('./types.js').Package} pkg -
|
||||||
* @param {import('./types').Config[]} configs - The configs to be used
|
* The packages to questions the configs.
|
||||||
* @returns {Promise<import('./types.js').Package[]>} - The selected options by the user
|
* @param {import('./types.js').Config[]} configs - The configs to be used.
|
||||||
|
* @returns {Promise<import('./types.js').Package[]>} - The selected options by the user.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line max-statements, complexity, max-lines-per-function
|
||||||
async questionConfig(pkg, configs) {
|
async questionConfig(pkg, configs) {
|
||||||
|
|
||||||
const packages = Array.isArray(pkg) ? [...pkg] : [pkg];
|
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`);
|
const instructions = c.dim(`\n${c.bold('A: Toggle all')} - ↑/↓: Highlight option - ←/→/[space]: Toggle selection - enter/return: Complete answer`);
|
||||||
|
|
||||||
for (const config of configs) {
|
for (const config of configs) {
|
||||||
|
|
||||||
/** @type {import('prompts').Choice[]} */
|
/** @type {import('prompts').Choice[]} */
|
||||||
const configChoices = config.options.map(option => {return { title: `${capitalize(option.name)}`, value: option.name };});
|
const configChoices = config.options.map(option => ({ title: `${capitalize(option.name)}`, value: option.name }));
|
||||||
|
|
||||||
/** @type {Record<string, string[]>} */
|
/** @type {Record<string, string[]>} */
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const selectedOptions = await prompts({
|
const selectedOptions = await prompts({
|
||||||
name: config.name,
|
|
||||||
type: config.type === 'multiple' ? 'multiselect' : 'select',
|
|
||||||
message: capitalize(config.name),
|
|
||||||
choices: config.type === 'confirm' ? [
|
choices: config.type === 'confirm' ? [
|
||||||
{
|
{
|
||||||
title: 'Yes',
|
title: 'Yes',
|
||||||
@@ -93,11 +110,19 @@ export default class ConfigsProcessor {
|
|||||||
},
|
},
|
||||||
] : configChoices,
|
] : configChoices,
|
||||||
hint: config.description,
|
hint: config.description,
|
||||||
instructions: instructions + c.dim(c.italic('\nSelect none if you don\'t want to use this configuration\n')),
|
instructions: instructions + c.dim(c.italic(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
'\nSelect none if you don\'t want to use this configuration\n',
|
||||||
|
)),
|
||||||
|
message: capitalize(config.name),
|
||||||
|
name: config.name,
|
||||||
|
type: config.type === 'multiple' ? 'multiselect' : 'select',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-continue, @typescript-eslint/no-unnecessary-condition
|
||||||
if (!selectedOptions[config.name]) continue;
|
if (!selectedOptions[config.name]) continue;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
if (selectedOptions[config.name].length === 0) continue;
|
if (selectedOptions[config.name].length === 0) continue;
|
||||||
|
|
||||||
if (packages.length <= 1) {
|
if (packages.length <= 1) {
|
||||||
@@ -105,64 +130,47 @@ export default class ConfigsProcessor {
|
|||||||
...(packages[0].config ?? []),
|
...(packages[0].config ?? []),
|
||||||
...Object.entries(selectedOptions),
|
...Object.entries(selectedOptions),
|
||||||
]);
|
]);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {{title: string, value: import('./types').Package}[]} */
|
/** @type {{title: string, value: import('./types.js').Package}[]} */
|
||||||
const packagesOptions = packages
|
const packagesOptions = packages
|
||||||
.map(pkg => {
|
.map(p => (p.root
|
||||||
return !pkg.root
|
? { title: 'root', value: p }
|
||||||
? {
|
: {
|
||||||
title: `${pkg.name} ${c.dim(pkg.path.replace(this.dir, '.'))}`,
|
title: `${p.name} ${c.dim(p.path.replace(this.dir, '.'))}`,
|
||||||
value: pkg,
|
value: p,
|
||||||
}
|
}))
|
||||||
: { title: 'root', value: pkg };
|
|
||||||
})
|
|
||||||
.filter(p => p.title !== 'root');
|
.filter(p => p.title !== 'root');
|
||||||
|
|
||||||
/** @type {Record<'packages', import('./types').Package[]>} */
|
/** @type {Record<'packages', import('./types.js').Package[]>} */
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const selected = await prompts({
|
const selected = await prompts({
|
||||||
|
choices: packagesOptions,
|
||||||
|
instructions: instructions + c.dim(c.italic(
|
||||||
|
'\nToggle all to use in the root configuration\n',
|
||||||
|
)),
|
||||||
|
message: `What packages would you like to apply ${config.type === 'single' ? 'this choice' : 'these choices'}?`,
|
||||||
|
min: 1,
|
||||||
name: 'packages',
|
name: 'packages',
|
||||||
type: 'autocompleteMultiselect',
|
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 ?? [];
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
selected.packages ??= [];
|
||||||
|
|
||||||
selected.packages.map(pkg => {
|
selected.packages.map((p) => {
|
||||||
pkg.config = new Map([
|
p.config = new Map([
|
||||||
...(pkg.config ?? []),
|
...(p.config ?? []),
|
||||||
...Object.entries(selectedOptions),
|
...Object.entries(selectedOptions),
|
||||||
]); return pkg;
|
]); return p;
|
||||||
});
|
});
|
||||||
packages.map(pkg => selected.packages.find(s => s.name === pkg.name) ?? pkg);
|
packages.map(p =>
|
||||||
|
selected.packages.find(s => s.name === p.name) ?? p,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return packages;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,42 +18,49 @@ import { parse, print } from 'recast';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {IdentifierName} identifier Nave of the variable identifier
|
* @param {IdentifierName} identifier - Nave of the variable identifier.
|
||||||
* @param {VariableKind} [kind] Type of variable declaration
|
* @param {VariableInit} [init] - Initial value of the variable.
|
||||||
* @param {VariableInit} [init] Initial value of the variable
|
* @param {VariableKind} [kind] - Type of variable declaration.
|
||||||
* @returns {VariableDeclaration} The variable declaration ast node object
|
* @returns {VariableDeclaration} The variable declaration ast node object.
|
||||||
*/
|
*/
|
||||||
function createVariable(identifier, kind = 'const', init) {
|
function createVariable(identifier, init, kind = 'const') {
|
||||||
return {
|
return {
|
||||||
type: 'VariableDeclaration',
|
|
||||||
kind,
|
|
||||||
declarations: [{
|
declarations: [{
|
||||||
type: 'VariableDeclarator',
|
id: { name: identifier, type: 'Identifier' },
|
||||||
id: { type: 'Identifier', name: identifier },
|
|
||||||
init,
|
init,
|
||||||
|
type: 'VariableDeclarator',
|
||||||
}],
|
}],
|
||||||
|
kind,
|
||||||
|
type: 'VariableDeclaration',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} string The expression in string
|
* @param {string} string - The code/expression in string.
|
||||||
* @returns {ExpressionOrIdentifier | undefined} The expression or identifier node of that string (undefined if string is not a expression)
|
* @returns {ExpressionOrIdentifier | undefined} -
|
||||||
|
* The expression or identifier node of that string (undefined if string is not a expression).
|
||||||
*/
|
*/
|
||||||
function stringToExpression(string) {
|
function stringToExpression(string) {
|
||||||
/** @type {ExpressionOrIdentifier} */
|
/** @type {ExpressionOrIdentifier} */
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||||
const e = parse(string).program.body[0].expression;
|
const e = parse(string).program.body[0].expression;
|
||||||
if (['MemberExpression', 'Identifier', 'CallExpression', 'NewExpression'].includes(e.type)) return e;
|
if ([
|
||||||
else return undefined;
|
'CallExpression',
|
||||||
|
'Identifier',
|
||||||
|
'MemberExpression',
|
||||||
|
'NewExpression',
|
||||||
|
].includes(e.type)) return e;
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ArrayExpression} array The array node to search trough
|
* @param {ArrayExpression} array - The array node to search trough.
|
||||||
* @param {ExpressionOrIdentifier | SpreadElement} element The element to be search
|
* @param {ExpressionOrIdentifier | SpreadElement} element - The element to be search.
|
||||||
* @returns {ExpressionOrIdentifier | undefined} The element of the array founded, undefined if it isn't found
|
* @returns {ExpressionOrIdentifier | undefined}
|
||||||
|
* The element of the array founded, undefined if it isn't found.
|
||||||
*/
|
*/
|
||||||
function findInArray(array, element) {
|
function findInArray(array, element) {
|
||||||
|
|
||||||
/** @type {ExpressionOrIdentifier[]} */
|
/** @type {ExpressionOrIdentifier[]} */
|
||||||
// @ts-expect-error The array should have just tge type above
|
// @ts-expect-error The array should have just tge type above
|
||||||
element = element.type === 'SpreadElement' ? element.argument : element;
|
element = element.type === 'SpreadElement' ? element.argument : element;
|
||||||
@@ -61,7 +68,7 @@ function findInArray(array, element) {
|
|||||||
/** @type {ExpressionOrIdentifier[]} */
|
/** @type {ExpressionOrIdentifier[]} */
|
||||||
// @ts-expect-error The array is filtered to have the type above
|
// @ts-expect-error The array is filtered to have the type above
|
||||||
const filteredElements = array.elements
|
const filteredElements = array.elements
|
||||||
.map(n => {
|
.map((n) => {
|
||||||
if (n?.type === 'SpreadElement') return n.argument;
|
if (n?.type === 'SpreadElement') return n.argument;
|
||||||
return n;
|
return n;
|
||||||
}).filter(n => n && n.type === element.type);
|
}).filter(n => n && n.type === element.type);
|
||||||
@@ -69,56 +76,88 @@ function findInArray(array, element) {
|
|||||||
const toStringElements = filteredElements.map(n => print(n).code);
|
const toStringElements = filteredElements.map(n => print(n).code);
|
||||||
const toStringElement = print(element).code;
|
const toStringElement = print(element).code;
|
||||||
|
|
||||||
const idx = toStringElements.findIndex(e => e === toStringElement);
|
const idx = toStringElements.indexOf(toStringElement);
|
||||||
|
// eslint-disable-next-line security/detect-object-injection
|
||||||
return filteredElements[idx];
|
return filteredElements[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ExpressionOrIdentifier} expression The expression to be spread
|
* @param {ExpressionOrIdentifier} expression - The expression to be spread.
|
||||||
* @returns {SpreadElement} The spread element node
|
* @returns {SpreadElement} The spread element node.
|
||||||
*/
|
*/
|
||||||
function toSpreadElement(expression) {
|
function toSpreadElement(expression) {
|
||||||
return {
|
return {
|
||||||
type: 'SpreadElement',
|
|
||||||
argument: expression,
|
argument: expression,
|
||||||
|
type: 'SpreadElement',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-secrets/no-secrets
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* body: import('estree').ImportDeclaration
|
* body: import('estree').ImportDeclaration
|
||||||
* addSpecifier: (specifier: string, alias?: string) => ThisType<ImportDeclarationHelper>
|
* addSpecifier: (specifier: string, alias?: string) => ThisType<ImportDeclarationHelper>
|
||||||
* convertDefaultSpecifier: () => ThisType<ImportDeclarationHelper>
|
* convertDefaultSpecifier: () => ThisType<ImportDeclarationHelper>
|
||||||
* }} ImportDeclarationHelper
|
* }} ImportDeclarationHelper
|
||||||
* @param {string} source The package name or source path to be imported
|
* @param {string} source - The package name or source path to be imported.
|
||||||
* @param {string} [defaultImported] The default specifier imported
|
* @param {string} [defaultImported] - The default specifier imported.
|
||||||
* @param {import('estree').ImportDeclaration} [body] The body of the import declaration to start with
|
* @param {import('estree').ImportDeclaration} [body] -
|
||||||
* @returns {ImportDeclarationHelper} A helper object for manipulating the import declaration
|
* The body of the import declaration to start with.
|
||||||
|
* @returns {ImportDeclarationHelper} A helper object for manipulating the import declaration.
|
||||||
*/
|
*/
|
||||||
function createImportDeclaration(source, defaultImported, body) {
|
function createImportDeclaration(source, defaultImported, body) {
|
||||||
const helper = {
|
const helper = {
|
||||||
|
/**
|
||||||
|
* @param {string} specifier - The value to be imported from the package.
|
||||||
|
* @param {string} [alias] - The local alias of the value.
|
||||||
|
* @returns {ThisType<ImportDeclarationHelper>} This helper with the added specifiers.
|
||||||
|
*/
|
||||||
|
addSpecifier(specifier, alias) {
|
||||||
|
this.convertDefaultSpecifier();
|
||||||
|
if (this.body.specifiers.some(s =>
|
||||||
|
s.local.name === alias || s.local.name === specifier,
|
||||||
|
))
|
||||||
|
return this;
|
||||||
|
|
||||||
|
this.body.specifiers.push({
|
||||||
|
imported: {
|
||||||
|
name: specifier,
|
||||||
|
type: 'Identifier',
|
||||||
|
},
|
||||||
|
local: {
|
||||||
|
name: alias ?? specifier,
|
||||||
|
type: 'Identifier',
|
||||||
|
},
|
||||||
|
type: 'ImportSpecifier',
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
/** @type {import('estree').ImportDeclaration} */
|
/** @type {import('estree').ImportDeclaration} */
|
||||||
body: body ?? {
|
body: body ?? {
|
||||||
type: 'ImportDeclaration',
|
|
||||||
specifiers: defaultImported ? [{
|
|
||||||
type: 'ImportDefaultSpecifier',
|
|
||||||
local: { type: 'Identifier', name: defaultImported },
|
|
||||||
}] : [],
|
|
||||||
source: {
|
source: {
|
||||||
type: 'Literal',
|
type: 'Literal',
|
||||||
value: source,
|
value: source,
|
||||||
},
|
},
|
||||||
|
specifiers: defaultImported ? [{
|
||||||
|
local: { name: defaultImported, type: 'Identifier' },
|
||||||
|
type: 'ImportDefaultSpecifier',
|
||||||
|
}] : [],
|
||||||
|
type: 'ImportDeclaration',
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Converts a default specifier to a specifier with a alias.
|
* Converts a default specifier to a specifier with a alias.
|
||||||
|
* @returns {ThisType<ImportDeclarationHelper>} -
|
||||||
|
* This helper with the converted default specifier.
|
||||||
* @example
|
* @example
|
||||||
* import eslit from 'eslit';
|
* import eslit from 'eslit';
|
||||||
* // Is converted to
|
* // Is converted to
|
||||||
* import { default as eslit } from 'eslit';
|
* import { default as eslit } from 'eslit';
|
||||||
* @returns {ThisType<ImportDeclarationHelper>} This helper with the converted default specifier
|
|
||||||
*/
|
*/
|
||||||
convertDefaultSpecifier() {
|
convertDefaultSpecifier() {
|
||||||
const specifier = this.body.specifiers.find(s => s.type === 'ImportDefaultSpecifier');
|
const specifier = this.body.specifiers.find(s =>
|
||||||
|
s.type === 'ImportDefaultSpecifier',
|
||||||
|
);
|
||||||
if (!specifier)
|
if (!specifier)
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
@@ -128,44 +167,24 @@ function createImportDeclaration(source, defaultImported, body) {
|
|||||||
);
|
);
|
||||||
return this.addSpecifier('default', specifier.local.name);
|
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<ImportDeclarationHelper>} 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)) {
|
if (defaultImported &&
|
||||||
|
body &&
|
||||||
|
!body.specifiers.some(s =>
|
||||||
|
s.type === 'ImportDefaultSpecifier' &&
|
||||||
|
s.local.name === defaultImported,
|
||||||
|
))
|
||||||
helper.addSpecifier('default', defaultImported);
|
helper.addSpecifier('default', defaultImported);
|
||||||
}
|
|
||||||
return helper;
|
|
||||||
|
|
||||||
|
return helper;
|
||||||
}
|
}
|
||||||
|
|
||||||
const astUtils = {
|
const astUtils = {
|
||||||
|
createImportDeclaration,
|
||||||
createVariable,
|
createVariable,
|
||||||
|
findInArray,
|
||||||
stringToExpression,
|
stringToExpression,
|
||||||
toSpreadElement,
|
toSpreadElement,
|
||||||
findInArray,
|
|
||||||
createImportDeclaration,
|
|
||||||
};
|
};
|
||||||
export default astUtils;
|
export default astUtils;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} str - The string to capitalize
|
* @param {string} str - The string to capitalize.
|
||||||
* @returns {string} The capitalized string
|
* @returns {string} The capitalized string.
|
||||||
*/
|
*/
|
||||||
export default function capitalize(str) {
|
export default function capitalize(str) {
|
||||||
return str[0].toUpperCase() + str.slice(1);
|
return str[0].toUpperCase() + str.slice(1);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('../types').Package[]} packages - Package list
|
* @param {import('../types').Package[]} packages - Package list.
|
||||||
* @returns {number} Number of packages' configs
|
* @returns {number} Number of packages' configs.
|
||||||
*/
|
*/
|
||||||
function packagesWithConfigs(packages) {
|
function packagesWithConfigs(packages) {
|
||||||
return packages.map(p =>
|
return packages.map(p =>
|
||||||
[...p.config?.values() ?? []].filter((options) => options.length > 0).length,
|
[...p.config?.values() ?? []]
|
||||||
|
.filter(options => options.length > 0).length,
|
||||||
).reduce((partial, sum) => partial + sum, 0);
|
).reduce((partial, sum) => partial + sum, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
// eslint-disable-next-line no-secrets/no-secrets
|
||||||
/**
|
/**
|
||||||
* JSDoc types lack a non-null assertion.
|
* JSDoc types lack a non-null assertion.
|
||||||
* @template T
|
* @template T
|
||||||
* @param {T} value The value which to assert against null or undefined
|
* @param {T} value - The value which to assert against null or undefined.
|
||||||
* @returns {NonNullable<T>} The said value
|
* @returns {NonNullable<T>} The said value.
|
||||||
* @throws {TypeError} If the value is unexpectedly null or undefined
|
* @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-873331031
|
||||||
* @see https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-1249287966
|
* @see https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-1249287966
|
||||||
|
* @author Jimmy Wärting - https://github.com/jimmywarting
|
||||||
*/
|
*/
|
||||||
export default function notNull(value) {
|
export default function notNull(value) {
|
||||||
// Use `==` to check for both null and undefined
|
// Use `==` to check for both null and undefined
|
||||||
if (value == null) throw new Error('did not expect value to be null or undefined');
|
if (value === null || value === undefined)
|
||||||
|
throw new Error('did not expect value to be null or undefined');
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
import { readFile, writeFile } from 'node:fs/promises';
|
||||||
import { existsSync, readFileSync } from 'node:fs';
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
|
||||||
import { exec } from 'node:child_process';
|
import { exec } from 'node:child_process';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
import { parse, prettyPrint } from 'recast';
|
||||||
import { createSpinner } from 'nanospinner';
|
import { createSpinner } from 'nanospinner';
|
||||||
import c from 'picocolors';
|
import c from 'picocolors';
|
||||||
import { parse, prettyPrint } from 'recast';
|
|
||||||
import { readFile, writeFile } from 'node:fs/promises';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('./types').PackageManagerHandler}
|
* @type {import('./types').PackageManagerHandler}
|
||||||
*/
|
*/
|
||||||
class CommandHandler {
|
class CommandHandler {
|
||||||
|
/** @type {((path: string, packages: string[]) => string | Promise<string>) | undefined} */
|
||||||
|
checker;
|
||||||
|
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
command;
|
command;
|
||||||
|
|
||||||
/** @type {((path: string, packages: string[]) => string | Promise<string>) | undefined} */
|
|
||||||
checker;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} command What command to use to install
|
* @param {string} command - What command to use to install.
|
||||||
* @param {(path: string, packages: string[]) => string | Promise<string>} [checker] Checks if a argument should be passed
|
* @param {(path: string, packages: string[]) => string | Promise<string>} [checker] -
|
||||||
|
* Checks if a argument should be passed.
|
||||||
*/
|
*/
|
||||||
constructor(command, checker) {
|
constructor(command, checker) {
|
||||||
this.command = command;
|
this.command = command;
|
||||||
@@ -28,20 +31,20 @@ class CommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path The path to run the command
|
* @param {string} path - The path to run the command.
|
||||||
* @param {string[]} packages The packages to be added on the command
|
* @param {string[]} packages - The packages to be added on the command.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async install(path, packages) {
|
async install(path, packages) {
|
||||||
|
|
||||||
if (this.checker)
|
if (this.checker)
|
||||||
this.command += await this.checker(path, packages);
|
this.command += await this.checker(path, packages);
|
||||||
|
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const spinner = createSpinner(`Installing packages with ${c.green(this.command)} ${c.dim(packages.join(' '))}`).start();
|
const spinner = createSpinner(`Installing packages with ${c.green(this.command)} ${c.dim(packages.join(' '))}`).start();
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line security/detect-child-process
|
||||||
const child = exec(`${this.command} ${packages.join(' ')}`, { cwd: path });
|
const child = exec(`${this.command} ${packages.join(' ')}`, { cwd: path });
|
||||||
child.stdout?.on('data', (chunk) => spinner.update({
|
child.stdout?.on('data', chunk => spinner.update({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
// 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)}`,
|
text: `Installing packages with ${c.green(this.command)} ${c.dim(packages.join(' '))}\n ${c.dim(chunk)}`,
|
||||||
}));
|
}));
|
||||||
@@ -52,7 +55,8 @@ class CommandHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line max-len
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-confusing-void-expression
|
||||||
res(console.error(`Error while installing the packages with ${this.command} ${c.dim(packages.join(' '))} on ${path}: ${error}`));
|
res(console.error(`Error while installing the packages with ${this.command} ${c.dim(packages.join(' '))} on ${path}: ${error}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -62,48 +66,44 @@ class CommandHandler {
|
|||||||
/**
|
/**
|
||||||
* @type {import('./types').PackageManagerHandler}
|
* @type {import('./types').PackageManagerHandler}
|
||||||
*/
|
*/
|
||||||
class DenoHandler {
|
const DenoHandler = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path The path to run the command
|
* @param {string} path - The path to run the command.
|
||||||
* @param {string[]} packages The packages to be added on the command
|
* @param {string[]} packages - The packages to be added on the command.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async install(path, packages) {
|
async install(path, packages) {
|
||||||
const configPath = join(path, 'eslint.config.js');
|
const configPath = join(path, 'eslint.config.js');
|
||||||
|
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
if (!existsSync(configPath)) return;
|
if (!existsSync(configPath)) return;
|
||||||
|
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
const configFile = await readFile(configPath, 'utf8');
|
const configFile = await readFile(configPath, 'utf8');
|
||||||
/** @type {{program: import('estree').Program}} */
|
/** @type {{program: import('estree').Program}} */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const { program: ast } = parse(configFile, { parser: (await import('recast/parsers/babel.js')) });
|
const { program: ast } = parse(
|
||||||
|
configFile,
|
||||||
|
{ parser: (await import('recast/parsers/babel.js')) },
|
||||||
|
);
|
||||||
|
|
||||||
ast.body.map((node) => {
|
ast.body.map((node) => {
|
||||||
if (node.type !== 'ImportDeclaration') return node;
|
if (node.type !== 'ImportDeclaration') return node;
|
||||||
|
|
||||||
if (packages.includes(node.source.value?.toString() ?? '')) {
|
if (packages.includes(node.source.value?.toString() ?? ''))
|
||||||
node.source.value = `npm:${node.source.value}`;
|
node.source.value = `npm:${node.source.value}`;
|
||||||
}
|
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
|
|
||||||
await writeFile(configPath, prettyPrint(ast).code, 'utf-8');
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
await writeFile(configPath, prettyPrint(ast).code, 'utf8');
|
||||||
|
|
||||||
console.log(c.green('Added npm: specifier to dependencies'));
|
console.log(c.green('Added npm: specifier to dependencies'));
|
||||||
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PackageInstaller {
|
export default class PackageInstaller {
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Map<string, string[]>} PackagesMap
|
|
||||||
* @type {PackagesMap}
|
|
||||||
*/
|
|
||||||
packagesMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* name: import('./types').PackageManagerName
|
* name: import('./types').PackageManagerName
|
||||||
@@ -118,40 +118,51 @@ export default class PackageInstaller {
|
|||||||
* @type {Record<import('./types').PackageManagerName, PackageManager>}
|
* @type {Record<import('./types').PackageManagerName, PackageManager>}
|
||||||
*/
|
*/
|
||||||
packageManagers = {
|
packageManagers = {
|
||||||
deno: {
|
|
||||||
name: 'deno',
|
|
||||||
description: 'Adds npm: specifiers to the eslint.config.js file',
|
|
||||||
handler: new DenoHandler(),
|
|
||||||
},
|
|
||||||
bun: {
|
bun: {
|
||||||
name: 'bun',
|
|
||||||
description: 'Uses bun install',
|
description: 'Uses bun install',
|
||||||
handler: new CommandHandler('bun install'),
|
handler: new CommandHandler('bun install'),
|
||||||
|
name: 'bun',
|
||||||
},
|
},
|
||||||
pnpm: {
|
deno: {
|
||||||
name: 'pnpm',
|
description: 'Adds npm: specifiers to the eslint.config.js file',
|
||||||
description: 'Uses pnpm install',
|
handler: DenoHandler,
|
||||||
handler: new CommandHandler('pnpm install --save-dev', (path) => {
|
name: 'deno',
|
||||||
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: {
|
npm: {
|
||||||
name: 'npm',
|
|
||||||
description: 'Uses npm install',
|
description: 'Uses npm install',
|
||||||
handler: new CommandHandler('npm install --save-dev'),
|
handler: new CommandHandler('npm install --save-dev'),
|
||||||
|
name: 'npm',
|
||||||
|
},
|
||||||
|
pnpm: {
|
||||||
|
description: 'Uses pnpm install',
|
||||||
|
handler: new CommandHandler('pnpm install --save-dev', (path) => {
|
||||||
|
if (
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
existsSync(join(path, 'pnpm-workspace.yaml')) &&
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
existsSync(join(path, 'package.json'))
|
||||||
|
)
|
||||||
|
return ' -w';
|
||||||
|
return '';
|
||||||
|
}),
|
||||||
|
name: 'pnpm',
|
||||||
|
},
|
||||||
|
yarn: {
|
||||||
|
description: 'Uses yarn add',
|
||||||
|
handler: new CommandHandler('yarn add --dev'),
|
||||||
|
name: 'yarn',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PackagesMap} packagesMap The map of directories and packages to be installed
|
* @typedef {Map<string, string[]>} PackagesMap
|
||||||
* @param {string} root Root directory path
|
* @type {PackagesMap}
|
||||||
|
*/
|
||||||
|
packagesMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PackagesMap} packagesMap - The map of directories and packages to be installed.
|
||||||
|
* @param {string} root - Root directory path.
|
||||||
*/
|
*/
|
||||||
constructor(packagesMap, root) {
|
constructor(packagesMap, root) {
|
||||||
this.packagesMap = packagesMap;
|
this.packagesMap = packagesMap;
|
||||||
@@ -159,51 +170,68 @@ export default class PackageInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} root Root directory path
|
* @param {string} root - Root directory path.
|
||||||
* @returns {PackageManager} The package manager detected;
|
* @returns {PackageManager} The package manager detected;.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
detectPackageManager(root) {
|
detectPackageManager(root) {
|
||||||
/** @type {(...path: string[]) => boolean} */
|
/** @type {(...path: string[]) => boolean} */
|
||||||
const exists = (...path) => existsSync(join(root, ...path));
|
function exists(...path) {
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
return existsSync(join(root, ...path));
|
||||||
|
}
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case exists('deno.json'):
|
case exists('deno.json'):
|
||||||
case exists('deno.jsonc'):
|
case exists('deno.jsonc'): {
|
||||||
return this.packageManagers.deno;
|
return this.packageManagers.deno;
|
||||||
|
}
|
||||||
|
|
||||||
case exists('bun.lockb'):
|
case exists('bun.lockb'): {
|
||||||
return this.packageManagers.bun;
|
return this.packageManagers.bun;
|
||||||
|
}
|
||||||
|
|
||||||
case exists('pnpm-lock.yaml'):
|
case exists('pnpm-lock.yaml'): {
|
||||||
return this.packageManagers.pnpm;
|
return this.packageManagers.pnpm;
|
||||||
|
}
|
||||||
|
|
||||||
case exists('yarn.lock'):
|
case exists('yarn.lock'): {
|
||||||
return this.packageManagers.yarn;
|
return this.packageManagers.yarn;
|
||||||
|
}
|
||||||
|
|
||||||
case exists('package-lock.json'):
|
case exists('package-lock.json'): {
|
||||||
return this.packageManagers.npm;
|
return this.packageManagers.npm;
|
||||||
|
}
|
||||||
|
|
||||||
case exists('package.json'):
|
case exists('package.json'): {
|
||||||
/** @type {{packageManager?: string}} */
|
/** @type {{packageManager?: string}} */
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-case-declarations
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, no-case-declarations
|
||||||
const { packageManager } = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
const { packageManager } = JSON.parse(
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
readFileSync(join(root, 'package.json'), 'utf8'),
|
||||||
|
);
|
||||||
if (!packageManager) return this.packageManagers.npm;
|
if (!packageManager) return this.packageManagers.npm;
|
||||||
|
|
||||||
if (packageManager.includes('pnpm')) return this.packageManagers.pnpm;
|
if (packageManager.includes('pnpm'))
|
||||||
if (packageManager.includes('yarn')) return this.packageManagers.yarn;
|
return this.packageManagers.pnpm;
|
||||||
if (packageManager.includes('npm')) return this.packageManagers.npm;
|
if (packageManager.includes('yarn'))
|
||||||
|
return this.packageManagers.yarn;
|
||||||
|
if (packageManager.includes('npm'))
|
||||||
|
return this.packageManagers.npm;
|
||||||
|
|
||||||
else return this.packageManagers.npm;
|
return this.packageManagers.npm;
|
||||||
|
}
|
||||||
|
|
||||||
default: return this.packageManagers.npm;
|
default: { return this.packageManagers.npm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async install() {
|
async install() {
|
||||||
for (const [path, packages] of this.packagesMap) {
|
for (const [path, packages] of this.packagesMap)
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await this.packageManager.handler.install(path, packages);
|
await this.packageManager.handler.install(path, packages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
108
packages/cli/src/types.d.ts
vendored
108
packages/cli/src/types.d.ts
vendored
@@ -1,64 +1,72 @@
|
|||||||
import type { OptionValues } from 'commander';
|
import type { OptionValues } from 'commander';
|
||||||
|
|
||||||
type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno';
|
type PackageManagerName = 'bun' | 'deno' | 'npm' | 'pnpm' | 'yarn';
|
||||||
|
|
||||||
type CliArgs = {
|
|
||||||
packages?: string[]
|
|
||||||
mergeToRoot?: boolean
|
|
||||||
installPkgs?: boolean | PackageManagerName
|
|
||||||
dir: string
|
|
||||||
configs: Config[]
|
|
||||||
} & OptionValues;
|
|
||||||
|
|
||||||
interface PackageManagerHandler {
|
interface PackageManagerHandler {
|
||||||
install(path: string, packages: string[]): Promise<void> | void
|
install(path: string, packages: string[]): Promise<void> | void,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
name: string
|
description?: string,
|
||||||
type: 'single' | 'multiple'
|
manual: true,
|
||||||
manual?: boolean
|
name: string,
|
||||||
description?: string
|
|
||||||
options: {
|
|
||||||
name: string
|
|
||||||
packages?: Record<string, string | (string | [string, string])[]>
|
|
||||||
configs?: string[]
|
|
||||||
rules?: string[]
|
|
||||||
presets?: string[]
|
|
||||||
detect?: string[] | true
|
|
||||||
}[]
|
|
||||||
} | {
|
|
||||||
name: string
|
|
||||||
type: 'confirm'
|
|
||||||
manual: true
|
|
||||||
description?: string
|
|
||||||
options: [{
|
options: [{
|
||||||
name: 'yes'
|
configs?: string[],
|
||||||
packages?: Record<string, string | (string | [string, string])[]>
|
detect?: undefined,
|
||||||
configs?: string[]
|
name: 'yes',
|
||||||
rules?: string[]
|
packages?: { [key: string]: ([string, string] | string)[] | string, },
|
||||||
presets?: string[]
|
presets?: string[],
|
||||||
detect?: undefined
|
rules?: string[],
|
||||||
}]
|
}],
|
||||||
|
type: 'confirm',
|
||||||
|
} | {
|
||||||
|
description?: string,
|
||||||
|
manual?: boolean,
|
||||||
|
name: string,
|
||||||
|
options: {
|
||||||
|
configs?: string[],
|
||||||
|
detect?: string[] | true,
|
||||||
|
name: string,
|
||||||
|
packages?: { [key: string]: ([string, string] | string)[] | string, },
|
||||||
|
presets?: string[],
|
||||||
|
rules?: string[],
|
||||||
|
}[],
|
||||||
|
type: 'multiple' | 'single',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Package {
|
type CliArgs = {
|
||||||
root?: boolean
|
configs: Config[],
|
||||||
name: string
|
dir: string,
|
||||||
path: string
|
installPkgs?: PackageManagerName | boolean,
|
||||||
files: string[]
|
mergeToRoot?: boolean,
|
||||||
directories: string[]
|
packages?: string[],
|
||||||
config?: Map<string, string[]>
|
} & OptionValues;
|
||||||
configFile?: ConfigFile
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ConfigFile {
|
interface ConfigFile {
|
||||||
path: string
|
configs: string[],
|
||||||
imports: Map<string, string | (string | [string, string])[]>
|
content?: string,
|
||||||
configs: string[]
|
imports: Map<string, ([string, string] | string)[] | string>,
|
||||||
presets: string[]
|
path: string,
|
||||||
rules: string[]
|
presets: string[],
|
||||||
content?: string
|
rules: string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { PackageManagerName, PackageManagerHandler, CliArgs, Config, Package, ConfigFile };
|
interface Package {
|
||||||
|
config?: Map<string, string[]>,
|
||||||
|
configFile?: ConfigFile,
|
||||||
|
directories: string[],
|
||||||
|
files: string[],
|
||||||
|
name: string,
|
||||||
|
path: string,
|
||||||
|
root?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type {
|
||||||
|
CliArgs,
|
||||||
|
Config,
|
||||||
|
ConfigFile,
|
||||||
|
Package,
|
||||||
|
PackageManagerHandler,
|
||||||
|
PackageManagerName,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,35 +1,41 @@
|
|||||||
import fs from 'node:fs/promises';
|
/* eslint-disable security/detect-non-literal-fs-filename */
|
||||||
import { existsSync } from 'node:fs';
|
|
||||||
import YAML from 'yaml';
|
|
||||||
import path, { join } from 'node:path';
|
import path, { join } from 'node:path';
|
||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
|
||||||
import picomatch from 'picomatch';
|
import picomatch from 'picomatch';
|
||||||
|
import YAML from 'yaml';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T
|
* @template T
|
||||||
* @param {Promise<T>} promise - The async function to try running
|
* @param {Promise<T>} promise - The async function to try running.
|
||||||
* @returns {Promise<T | null>} - Returns the result of the async function, or null if it errors
|
* @returns {Promise<T | null>} - Returns the result of the async function, or null if it errors.
|
||||||
*/
|
*/
|
||||||
async function tryRun(promise) {
|
async function tryRun(promise) {
|
||||||
try {
|
try {
|
||||||
return await promise;
|
return await promise;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} directory - The directory to find .gitignore and .eslintignore
|
* @param {string} directory - The directory to find .gitignore and .eslintignore.
|
||||||
* @returns {Promise<string[]>} - List of ignore glob patterns
|
* @returns {Promise<string[]>} - List of ignore glob patterns.
|
||||||
*/
|
*/
|
||||||
async function getIgnoredFiles(directory) {
|
async function getIgnoredFiles(directory) {
|
||||||
const gitIgnore = (await tryRun(fs.readFile(join(directory, '.gitignore'), 'utf8')) ?? '')
|
const gitIgnore = (
|
||||||
|
await tryRun(fs.readFile(join(directory, '.gitignore'), 'utf8')) ??
|
||||||
|
'')
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter(p => p && !p.startsWith('#'))
|
.filter(p => p && !p.startsWith('#'))
|
||||||
.map(p => join(directory, '**', p));
|
.map(p => join(directory, '**', p));
|
||||||
|
|
||||||
const eslintIgnore = (await tryRun(fs.readFile(join(directory, '.eslintignore'), 'utf8')) ?? '')
|
const eslintIgnore = (
|
||||||
|
await tryRun(fs.readFile(join(directory, '.eslintignore'), 'utf8')) ??
|
||||||
|
'')
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter(p => p && !p.startsWith('#'))
|
.filter(p => p && !p.startsWith('#'))
|
||||||
.map(p => join(directory, '**', p));
|
.map(p => join(directory, '**', p));
|
||||||
@@ -46,19 +52,19 @@ async function getPackageName(directory) {
|
|||||||
const file = await fs.readFile(join(directory, 'package.json'), 'utf8');
|
const file = await fs.readFile(join(directory, 'package.json'), 'utf8');
|
||||||
/** @type {{name?: string}} */
|
/** @type {{name?: string}} */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const obj = JSON.parse(file);
|
const object = JSON.parse(file);
|
||||||
|
|
||||||
if (obj.name) return obj.name;
|
if (object.name) return object.name;
|
||||||
}
|
}
|
||||||
return path.normalize(directory).split('/').at(-1) ?? directory;
|
return path.normalize(directory).split('/').at(-1) ?? directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Workspace {
|
export default class Workspace {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} directory - The directory to get the workspace from
|
* @param {string} directory -
|
||||||
* @param {string[] | false} [packagePatterns]
|
* The directory to get the workspace from.
|
||||||
* List of package patterns (`false` to explicitly tell that this workspace is not a monorepo)
|
* @param {string[] | false} [packagePatterns] -
|
||||||
|
* List of package patterns (`false` to explicitly tell that this workspace is not a monorepo).
|
||||||
*/
|
*/
|
||||||
constructor(directory, packagePatterns) {
|
constructor(directory, packagePatterns) {
|
||||||
this.dir = directory;
|
this.dir = directory;
|
||||||
@@ -66,125 +72,93 @@ export default class Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} [directory] - The directory to work on
|
* @returns {Promise<string[]>} - List of packages on a directory;.
|
||||||
* @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) => !picomatch.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<string[]>} - List of packages on a directory;
|
|
||||||
*/
|
*/
|
||||||
async getPackagePatterns() {
|
async getPackagePatterns() {
|
||||||
|
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
let packagePatterns = [];
|
const packagePatterns = [];
|
||||||
|
|
||||||
const pnpmWorkspace =
|
const pnpmWorkspace =
|
||||||
existsSync(join(this.dir, 'pnpm-workspace.yaml'))
|
existsSync(join(this.dir, 'pnpm-workspace.yaml'))
|
||||||
? 'pnpm-workspace.yaml'
|
? 'pnpm-workspace.yaml'
|
||||||
: existsSync(join(this.dir, 'pnpm-workspace.yml'))
|
: (existsSync(join(this.dir, 'pnpm-workspace.yml'))
|
||||||
? 'pnpm-workspace.yml'
|
? 'pnpm-workspace.yml'
|
||||||
: null;
|
: null);
|
||||||
|
|
||||||
if (pnpmWorkspace) {
|
if (pnpmWorkspace) {
|
||||||
const pnpmWorkspaceYaml = await fs.readFile(join(this.dir, pnpmWorkspace), 'utf8');
|
const pnpmWorkspaceYaml = await fs.readFile(
|
||||||
|
join(this.dir, pnpmWorkspace),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
|
||||||
/** @type {{packages?: string[]}} */
|
/** @type {{packages?: string[]}} */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const pnpmWorkspaceObj = YAML.parse(pnpmWorkspaceYaml);
|
const pnpmWorkspaceObject = YAML.parse(pnpmWorkspaceYaml);
|
||||||
|
|
||||||
packagePatterns.push(...(pnpmWorkspaceObj?.packages ?? []));
|
packagePatterns.push(...pnpmWorkspaceObject.packages ?? []);
|
||||||
}
|
}
|
||||||
else if (existsSync(join(this.dir, 'package.json'))) {
|
else if (existsSync(join(this.dir, 'package.json'))) {
|
||||||
const packageJson = await fs.readFile(join(this.dir, 'package.json'), 'utf8');
|
const packageJson = await fs.readFile(
|
||||||
|
join(this.dir, 'package.json'),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
|
||||||
/** @type {{workspaces?: string[]}} */
|
/** @type {{workspaces?: string[]}} */
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const packageJsonObj = JSON.parse(packageJson);
|
const packageJsonObject = JSON.parse(packageJson);
|
||||||
|
|
||||||
packagePatterns.push(...(packageJsonObj?.workspaces ?? []));
|
packagePatterns.push(...packageJsonObject.workspaces ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
return packagePatterns.map(p => {
|
return packagePatterns.map((p) => {
|
||||||
p = path.normalize(p);
|
p = path.normalize(p);
|
||||||
p = p.startsWith('/') ? p.replace('/', '') : p;
|
p = p.startsWith('/') ? p.replace('/', '') : p;
|
||||||
p = p.endsWith('/') ? p.slice(0, p.length - 1) : p;
|
p = p.endsWith('/') ? p.slice(0, -1) : p;
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<import('./types').Package[]>} - The list of packages that exist in the workspace
|
* @returns {Promise<import('./types').Package[]>} -
|
||||||
|
* The list of packages that exist in the workspace.
|
||||||
*/
|
*/
|
||||||
async getPackages() {
|
async getPackages() {
|
||||||
|
|
||||||
const paths = await this.getPaths();
|
const paths = await this.getPaths();
|
||||||
|
|
||||||
/** @type {import('./types').Package} */
|
/** @type {import('./types').Package} */
|
||||||
const rootPackage = {
|
const rootPackage = {
|
||||||
root: true,
|
directories: paths.directories,
|
||||||
|
files: paths.files,
|
||||||
name: await getPackageName(this.dir),
|
name: await getPackageName(this.dir),
|
||||||
path: this.dir,
|
path: this.dir,
|
||||||
files: paths.files,
|
root: true,
|
||||||
directories: paths.directories,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.packagePatterns === false) return [rootPackage];
|
if (this.packagePatterns === false) return [rootPackage];
|
||||||
|
|
||||||
const packagePatterns = this.packagePatterns ?? await this.getPackagePatterns();
|
const packagePatterns =
|
||||||
const packagePaths = paths.directories.filter(d => picomatch.isMatch(d, packagePatterns));
|
this.packagePatterns ??
|
||||||
|
await this.getPackagePatterns();
|
||||||
|
|
||||||
|
const packagePaths = paths.directories.filter(d =>
|
||||||
|
picomatch.isMatch(d, packagePatterns),
|
||||||
|
);
|
||||||
|
|
||||||
/** @type {import('./types').Package[]} */
|
/** @type {import('./types').Package[]} */
|
||||||
const packages = [];
|
const packages = [];
|
||||||
|
|
||||||
for (const packagePath of packagePaths) {
|
for (const packagePath of packagePaths) {
|
||||||
packages.push({
|
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
|
directories: paths.directories
|
||||||
.filter(d => picomatch.isMatch(d, `${packagePath}/**/*`))
|
.filter(d => picomatch.isMatch(d, `${packagePath}/**/*`))
|
||||||
.map(d => d.replace(`${packagePath}/`, '')),
|
.map(d => d.replace(`${packagePath}/`, '')),
|
||||||
|
files: paths.files
|
||||||
|
.filter(f => picomatch.isMatch(f, `${packagePath}/**/*`))
|
||||||
|
.map(f => f.replace(`${packagePath}/`, '')),
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
name: await getPackageName(join(this.dir, packagePath)),
|
||||||
|
path: join(this.dir, packagePath),
|
||||||
|
root: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
rootPackage.files = rootPackage.files
|
rootPackage.files = rootPackage.files
|
||||||
@@ -195,51 +169,101 @@ export default class Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [rootPackage, ...packages];
|
return [rootPackage, ...packages];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('./types').Package[]} packages - Packages to be merged into root
|
* @param {string} [directory] - The directory to work on.
|
||||||
* @returns {[import('./types').Package]} A array containing only the root package
|
* @param {string[]} [ignores] - Glob patterns to ignore.
|
||||||
|
* @returns {Promise<{files: string[], directories: string[]}>} -
|
||||||
|
* List of all files in the directory.
|
||||||
*/
|
*/
|
||||||
mergePackages(packages) {
|
async getPaths(directory = this.dir, ignores = []) {
|
||||||
|
ignores.push(
|
||||||
|
...[
|
||||||
|
'.git',
|
||||||
|
'.dist',
|
||||||
|
'.DS_Store',
|
||||||
|
'node_modules',
|
||||||
|
].map(f => join(directory, f)),
|
||||||
|
...await getIgnoredFiles(directory),
|
||||||
|
);
|
||||||
|
|
||||||
|
const pathsUnfiltered = await fs.readdir(directory);
|
||||||
|
const paths = pathsUnfiltered
|
||||||
|
.map(f => path.normalize(join(directory, f)))
|
||||||
|
.filter(p => !picomatch.isMatch(p, ignores));
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const files = [];
|
||||||
|
/** @type {string[]} */
|
||||||
|
const directories = [];
|
||||||
|
|
||||||
|
for (const p of paths) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member
|
||||||
|
if ((await fs.lstat(p)).isDirectory()) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const subPaths = await this.getPaths(p, ignores);
|
||||||
|
directories.push(p, ...subPaths.directories);
|
||||||
|
files.push(...subPaths.files);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
files.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
directories: directories.map(p =>
|
||||||
|
path.normalize(p.replace(this.dir, './')),
|
||||||
|
),
|
||||||
|
files: files.map(p => path.normalize(p.replace(this.dir, './'))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('./types').Package[]} packages - Packages to be merged into root.
|
||||||
|
* @returns {[import('./types').Package]} A array containing only the root package.
|
||||||
|
*/
|
||||||
|
static mergePackages(packages) {
|
||||||
const rootPackage = packages.find(p => p.root) ?? packages[0];
|
const rootPackage = packages.find(p => p.root) ?? packages[0];
|
||||||
|
|
||||||
const merged = packages.reduce((accumulated, pkg) => {
|
// TODO [>=1.0.0]: Refactor this to remove the use of Array#reduce()
|
||||||
|
// eslint-disable-next-line unicorn/no-array-reduce
|
||||||
|
const merged = packages.reduce((accumulated, package_) => {
|
||||||
const files = [...new Set([
|
const files = [...new Set([
|
||||||
...accumulated.files,
|
...accumulated.files,
|
||||||
...pkg.files.map(f => join(pkg.path, f)),
|
...package_.files.map(f => join(package_.path, f)),
|
||||||
]
|
]
|
||||||
.map(p => p.replace(`${rootPackage.path}/`, '')),
|
.map(p => p.replace(`${rootPackage.path}/`, '')),
|
||||||
)];
|
)];
|
||||||
|
|
||||||
const directories = [...new Set([
|
const directories = [...new Set([
|
||||||
...accumulated.directories,
|
...accumulated.directories,
|
||||||
...pkg.directories.map(d => join(pkg.path, d)),
|
...package_.directories.map(d => join(package_.path, d)),
|
||||||
]
|
]
|
||||||
.map(p => p.replace(`${rootPackage.path}/`, ''))),
|
.map(p => p.replace(`${rootPackage.path}/`, ''))),
|
||||||
];
|
];
|
||||||
|
|
||||||
const mergedConfig = new Map();
|
const mergedConfig = new Map();
|
||||||
for (const [config, options] of pkg.config ?? []) {
|
for (const [config, options] of package_.config ?? []) {
|
||||||
const accumulatedOptions = accumulated.config?.get(config) ?? [];
|
const accumulatedOptions =
|
||||||
mergedConfig.set(config, [...new Set([...options, ...accumulatedOptions])]);
|
accumulated.config?.get(config) ??
|
||||||
|
[];
|
||||||
|
mergedConfig.set(
|
||||||
|
config,
|
||||||
|
[...new Set([...options, ...accumulatedOptions])],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
root: true,
|
|
||||||
path: rootPackage.path,
|
|
||||||
name: rootPackage.name,
|
|
||||||
files,
|
|
||||||
directories,
|
|
||||||
config: mergedConfig,
|
config: mergedConfig,
|
||||||
|
directories,
|
||||||
|
files,
|
||||||
|
name: rootPackage.name,
|
||||||
|
path: rootPackage.path,
|
||||||
|
root: true,
|
||||||
};
|
};
|
||||||
}, rootPackage);
|
}, rootPackage);
|
||||||
|
|
||||||
return [merged];
|
return [merged];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
import Cli from '@eslegant/cli';
|
import Cli from '@eslegant/cli';
|
||||||
|
|
||||||
const cli = new Cli({ configs: (await import('./configs.js')).default, dir: process.cwd() });
|
const cli = new Cli({
|
||||||
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
|
configs: (await import('./configs.js')).default,
|
||||||
|
dir: process.cwd(),
|
||||||
|
});
|
||||||
await cli.run();
|
await cli.run();
|
||||||
|
|||||||
@@ -2,33 +2,36 @@
|
|||||||
/** @type {import('@eslegant/cli').Config[]} */
|
/** @type {import('@eslegant/cli').Config[]} */
|
||||||
const cliConfig = [
|
const cliConfig = [
|
||||||
{
|
{
|
||||||
name: 'framework',
|
|
||||||
type: 'multiple',
|
|
||||||
description: 'The UI frameworks being used in the project',
|
description: 'The UI frameworks being used in the project',
|
||||||
|
name: 'framework',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'svelte',
|
|
||||||
packages: { 'svelte': 'svelte' },
|
|
||||||
configs: ['svelte.recommended'],
|
configs: ['svelte.recommended'],
|
||||||
detect: ['**/*.svelte', 'svelte.config.{js,ts,cjs,cts}'],
|
detect: ['**/*.svelte', 'svelte.config.{js,ts,cjs,cts}'],
|
||||||
|
name: 'svelte',
|
||||||
|
packages: { svelte: 'svelte' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vue',
|
|
||||||
packages: { 'vue': ['vue', ['hello', 'world']], 'svelte': ['hello'] },
|
|
||||||
configs: ['vue.recommended'],
|
configs: ['vue.recommended'],
|
||||||
detect: ['nuxt.config.{js,ts,cjs,cts}', '**/*.vue'],
|
detect: ['nuxt.config.{js,ts,cjs,cts}', '**/*.vue'],
|
||||||
|
name: 'vue',
|
||||||
|
packages: {
|
||||||
|
svelte: ['hello'],
|
||||||
|
vue: ['vue', ['hello', 'world']],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
type: 'multiple',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'strict',
|
|
||||||
type: 'confirm',
|
|
||||||
manual: true,
|
manual: true,
|
||||||
|
name: 'strict',
|
||||||
options: [{
|
options: [{
|
||||||
name: 'yes',
|
|
||||||
packages: { 'eslint': 'config', 'svelte': ['test1'] },
|
|
||||||
configs: ['config.strict'],
|
configs: ['config.strict'],
|
||||||
|
name: 'yes',
|
||||||
|
packages: { eslint: 'config', svelte: ['test1'] },
|
||||||
}],
|
}],
|
||||||
|
type: 'confirm',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export default cliConfig;
|
export default cliConfig;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
"url": "https://guz.one"
|
"url": "https://guz.one"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"./bin.js",
|
"bin.js",
|
||||||
"./configs.js"
|
"configs.js"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslegant/cli": "workspace:*"
|
"@eslegant/cli": "workspace:*"
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
import Cli from '@eslegant/cli';
|
import Cli from '@eslegant/cli';
|
||||||
|
|
||||||
const cli = new Cli({ configs: (await import('./configs.js')).default, dir: process.cwd() });
|
const cli = new Cli({
|
||||||
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
|
configs: (await import('./configs.js')).default,
|
||||||
|
dir: process.cwd(),
|
||||||
|
});
|
||||||
await cli.run();
|
await cli.run();
|
||||||
|
|||||||
@@ -2,33 +2,36 @@
|
|||||||
/** @type {import('@eslegant/cli').Config[]} */
|
/** @type {import('@eslegant/cli').Config[]} */
|
||||||
const cliConfig = [
|
const cliConfig = [
|
||||||
{
|
{
|
||||||
name: 'framework',
|
|
||||||
type: 'multiple',
|
|
||||||
description: 'The UI frameworks being used in the project',
|
description: 'The UI frameworks being used in the project',
|
||||||
|
name: 'framework',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'svelte',
|
|
||||||
packages: { 'svelte': 'svelte' },
|
|
||||||
configs: ['svelte.recommended'],
|
configs: ['svelte.recommended'],
|
||||||
detect: ['**/*.svelte', 'svelte.config.{js,ts,cjs,cts}'],
|
detect: ['**/*.svelte', 'svelte.config.{js,ts,cjs,cts}'],
|
||||||
|
name: 'svelte',
|
||||||
|
packages: { svelte: 'svelte' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vue',
|
|
||||||
packages: { 'vue': ['vue', ['hello', 'world']], 'svelte': ['hello'] },
|
|
||||||
configs: ['vue.recommended'],
|
configs: ['vue.recommended'],
|
||||||
detect: ['nuxt.config.{js,ts,cjs,cts}', '**/*.vue'],
|
detect: ['nuxt.config.{js,ts,cjs,cts}', '**/*.vue'],
|
||||||
|
name: 'vue',
|
||||||
|
packages: {
|
||||||
|
svelte: ['hello'],
|
||||||
|
vue: ['vue', ['hello', 'world']],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
type: 'multiple',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'strict',
|
|
||||||
type: 'confirm',
|
|
||||||
manual: true,
|
manual: true,
|
||||||
|
name: 'strict',
|
||||||
options: [{
|
options: [{
|
||||||
name: 'yes',
|
|
||||||
packages: { 'eslint': 'config', 'svelte': ['test1'] },
|
|
||||||
configs: ['config.strict'],
|
configs: ['config.strict'],
|
||||||
|
name: 'yes',
|
||||||
|
packages: { eslint: 'config', svelte: ['test1'] },
|
||||||
}],
|
}],
|
||||||
|
type: 'confirm',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export default cliConfig;
|
export default cliConfig;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
"url": "https://guz.one"
|
"url": "https://guz.one"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"./bin.js",
|
"bin.js",
|
||||||
"./configs.js"
|
"configs.js"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslegant/cli": "workspace:*"
|
"@eslegant/cli": "workspace:*"
|
||||||
|
|||||||
78
pnpm-lock.yaml
generated
78
pnpm-lock.yaml
generated
@@ -31,8 +31,8 @@ importers:
|
|||||||
specifier: ^8.47.0
|
specifier: ^8.47.0
|
||||||
version: 8.47.0
|
version: 8.47.0
|
||||||
husky:
|
husky:
|
||||||
specifier: ^8.0.3
|
specifier: ^9.0.0
|
||||||
version: 8.0.3
|
version: 9.0.10
|
||||||
turbo:
|
turbo:
|
||||||
specifier: ^1.10.12
|
specifier: ^1.10.12
|
||||||
version: 1.10.12
|
version: 1.10.12
|
||||||
@@ -54,6 +54,9 @@ importers:
|
|||||||
eslint-import-resolver-typescript:
|
eslint-import-resolver-typescript:
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0(@typescript-eslint/parser@6.4.1)(eslint-plugin-import@2.28.1)(eslint@8.47.0)
|
version: 3.6.0(@typescript-eslint/parser@6.4.1)(eslint-plugin-import@2.28.1)(eslint@8.47.0)
|
||||||
|
eslint-plugin-compat:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0(eslint@8.47.0)
|
||||||
eslint-plugin-i:
|
eslint-plugin-i:
|
||||||
specifier: 2.28.0-2
|
specifier: 2.28.0-2
|
||||||
version: 2.28.0-2(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0)
|
version: 2.28.0-2(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0)
|
||||||
@@ -794,6 +797,10 @@ packages:
|
|||||||
read-yaml-file: 1.1.0
|
read-yaml-file: 1.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@mdn/browser-compat-data@5.3.15:
|
||||||
|
resolution: {integrity: sha512-h/luqw9oAmMF1C/GuUY/PAgZlF4wx71q2bdH+ct8vmjcvseCY32au8XmYy7xZ8l5VJiY/3ltFpr5YiO55v0mzg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@neoconfetti/svelte@1.0.0:
|
/@neoconfetti/svelte@1.0.0:
|
||||||
resolution: {integrity: sha512-SmksyaJAdSlMa9cTidVSIqYo1qti+WTsviNDwgjNVm+KQ3DRP2Df9umDIzC4vCcpEYY+chQe0i2IKnLw03AT8Q==}
|
resolution: {integrity: sha512-SmksyaJAdSlMa9cTidVSIqYo1qti+WTsviNDwgjNVm+KQ3DRP2Df9umDIzC4vCcpEYY+chQe0i2IKnLw03AT8Q==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1440,6 +1447,12 @@ packages:
|
|||||||
util: 0.12.5
|
util: 0.12.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/ast-metadata-inferer@0.8.0:
|
||||||
|
resolution: {integrity: sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==}
|
||||||
|
dependencies:
|
||||||
|
'@mdn/browser-compat-data': 5.3.15
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ast-types@0.16.1:
|
/ast-types@0.16.1:
|
||||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -1496,6 +1509,17 @@ packages:
|
|||||||
wcwidth: 1.0.1
|
wcwidth: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/browserslist@4.21.10:
|
||||||
|
resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==}
|
||||||
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
caniuse-lite: 1.0.30001532
|
||||||
|
electron-to-chromium: 1.4.513
|
||||||
|
node-releases: 2.0.13
|
||||||
|
update-browserslist-db: 1.0.11(browserslist@4.21.10)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/buffer-crc32@0.2.13:
|
/buffer-crc32@0.2.13:
|
||||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1542,6 +1566,10 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/caniuse-lite@1.0.30001532:
|
||||||
|
resolution: {integrity: sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cardinal@2.1.1:
|
/cardinal@2.1.1:
|
||||||
resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==}
|
resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1827,6 +1855,10 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/electron-to-chromium@1.4.513:
|
||||||
|
resolution: {integrity: sha512-cOB0xcInjm+E5qIssHeXJ29BaUyWpMyFKT5RB3bsLENDheCja0wMkHJyiPl0NBE/VzDI7JDuNEQWhe6RitEUcw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/emoji-regex@8.0.0:
|
/emoji-regex@8.0.0:
|
||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1958,7 +1990,6 @@ packages:
|
|||||||
/escalade@3.1.1:
|
/escalade@3.1.1:
|
||||||
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
|
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/escape-string-regexp@1.0.5:
|
/escape-string-regexp@1.0.5:
|
||||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||||
@@ -2031,6 +2062,22 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/eslint-plugin-compat@4.2.0(eslint@8.47.0):
|
||||||
|
resolution: {integrity: sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w==}
|
||||||
|
engines: {node: '>=14.x'}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
|
dependencies:
|
||||||
|
'@mdn/browser-compat-data': 5.3.15
|
||||||
|
ast-metadata-inferer: 0.8.0
|
||||||
|
browserslist: 4.21.10
|
||||||
|
caniuse-lite: 1.0.30001532
|
||||||
|
eslint: 8.47.0
|
||||||
|
find-up: 5.0.0
|
||||||
|
lodash.memoize: 4.1.2
|
||||||
|
semver: 7.5.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/eslint-plugin-es-x@7.2.0(eslint@8.47.0):
|
/eslint-plugin-es-x@7.2.0(eslint@8.47.0):
|
||||||
resolution: {integrity: sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==}
|
resolution: {integrity: sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
@@ -2688,9 +2735,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==}
|
resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/husky@8.0.3:
|
/husky@9.0.10:
|
||||||
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
|
resolution: {integrity: sha512-TQGNknoiy6bURzIO77pPRu+XHi6zI7T93rX+QnJsoYFf3xdjKOur+IlfqzJGMHIK/wXrLg+GsvMs8Op7vI2jVA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -3044,6 +3091,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
|
/lodash.memoize@4.1.2:
|
||||||
|
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lodash.merge@4.6.2:
|
/lodash.merge@4.6.2:
|
||||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||||
|
|
||||||
@@ -3213,6 +3264,10 @@ packages:
|
|||||||
whatwg-url: 5.0.0
|
whatwg-url: 5.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/node-releases@2.0.13:
|
||||||
|
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/normalize-package-data@2.5.0:
|
/normalize-package-data@2.5.0:
|
||||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4261,6 +4316,17 @@ packages:
|
|||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/update-browserslist-db@1.0.11(browserslist@4.21.10):
|
||||||
|
resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
browserslist: '>= 4.21.0'
|
||||||
|
dependencies:
|
||||||
|
browserslist: 4.21.10
|
||||||
|
escalade: 3.1.1
|
||||||
|
picocolors: 1.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/uri-js@4.4.1:
|
/uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user