@linter-spec/cli 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/init/index.js +2 -2
- package/dist/actions/init/install-deps.d.ts +20 -2
- package/dist/actions/init/install-deps.js +59 -4
- package/dist/config/tsconfig.json.ejs +30 -0
- package/dist/utils/conflict-resolve.js +2 -0
- package/dist/utils/generate-template.d.ts +6 -0
- package/dist/utils/generate-template.js +10 -1
- package/dist/utils/npm.d.ts +2 -2
- package/dist/utils/npm.js +5 -4
- package/package.json +3 -3
|
@@ -7,7 +7,7 @@ import generateTemplate from '../../utils/generate-template.js';
|
|
|
7
7
|
import { CLI_NAME, PROJECT_TYPES } from '../../utils/constants.js';
|
|
8
8
|
import { messages } from '../../utils/messages.js';
|
|
9
9
|
import { chooseEnableMarkdownlint, chooseEnablePrettier, chooseEnableStylelint, chooseEslintType, } from './prompts.js';
|
|
10
|
-
import {
|
|
10
|
+
import { installProjectDeps } from './install-deps.js';
|
|
11
11
|
import { setupHusky } from './setup-husky.js';
|
|
12
12
|
export default async function init(options) {
|
|
13
13
|
const cwd = options.cwd || process.cwd();
|
|
@@ -46,7 +46,7 @@ export default async function init(options) {
|
|
|
46
46
|
log.success(messages.stepConflictDone(step));
|
|
47
47
|
if (!disableNpmInstall) {
|
|
48
48
|
log.info(messages.stepInstall(++step));
|
|
49
|
-
await
|
|
49
|
+
await installProjectDeps(cwd, config);
|
|
50
50
|
log.success(messages.stepInstallDone(step));
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
/** True when the project carries its own lint config files. */
|
|
2
2
|
export declare function hasLocalLintConfig(cwd: string): boolean;
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
export interface ProjectDepsConfig {
|
|
4
|
+
enableESLint: boolean;
|
|
5
|
+
eslintType: string;
|
|
6
|
+
enableStylelint: boolean;
|
|
7
|
+
enableMarkdownlint: boolean;
|
|
8
|
+
enablePrettier: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The npm packages to install as devDependencies of the user's project so that
|
|
12
|
+
* the generated `eslint.config.mjs` (and friends) can resolve everything they
|
|
13
|
+
* need — including peers of `@linter-spec/eslint-config` that are marked
|
|
14
|
+
* `optional` in its `peerDependenciesMeta` and therefore would NOT be installed
|
|
15
|
+
* transitively just by adding `@linter-spec/cli`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function projectDepsToInstall(config: ProjectDepsConfig): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Install everything the chosen lint setup needs (CLI + eslint-config +
|
|
20
|
+
* project-type-specific plugins + selected toolchains) as devDeps of `cwd`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function installProjectDeps(cwd: string, config: ProjectDepsConfig): Promise<void>;
|
|
5
23
|
/**
|
|
6
24
|
* When a project relies on the bundled lint configs but has no `node_modules`,
|
|
7
25
|
* install its dependencies first (otherwise config resolution fails).
|
|
@@ -16,10 +16,65 @@ export function hasLocalLintConfig(cwd) {
|
|
|
16
16
|
'.markdownlint?(-cli2).@(jsonc|json|yaml|yml|cjs|mjs)',
|
|
17
17
|
], { cwd, dot: true }).length > 0);
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The npm packages to install as devDependencies of the user's project so that
|
|
21
|
+
* the generated `eslint.config.mjs` (and friends) can resolve everything they
|
|
22
|
+
* need — including peers of `@linter-spec/eslint-config` that are marked
|
|
23
|
+
* `optional` in its `peerDependenciesMeta` and therefore would NOT be installed
|
|
24
|
+
* transitively just by adding `@linter-spec/cli`.
|
|
25
|
+
*/
|
|
26
|
+
export function projectDepsToInstall(config) {
|
|
27
|
+
const deps = new Set([PKG_NAME]);
|
|
28
|
+
if (config.enableESLint) {
|
|
29
|
+
deps.add('eslint');
|
|
30
|
+
deps.add('@linter-spec/eslint-config');
|
|
31
|
+
const t = config.eslintType;
|
|
32
|
+
const isTs = t.startsWith('typescript');
|
|
33
|
+
const isReact = t === 'react' || t === 'typescript/react';
|
|
34
|
+
const isVue = t === 'vue' || t === 'typescript/vue';
|
|
35
|
+
const isNode = t === 'node' || t === 'typescript/node';
|
|
36
|
+
if (isTs) {
|
|
37
|
+
deps.add('typescript');
|
|
38
|
+
deps.add('typescript-eslint');
|
|
39
|
+
}
|
|
40
|
+
if (isReact) {
|
|
41
|
+
deps.add('eslint-plugin-react');
|
|
42
|
+
deps.add('eslint-plugin-react-hooks');
|
|
43
|
+
deps.add('eslint-plugin-jsx-a11y');
|
|
44
|
+
}
|
|
45
|
+
if (isVue) {
|
|
46
|
+
deps.add('eslint-plugin-vue');
|
|
47
|
+
deps.add('vue-eslint-parser');
|
|
48
|
+
}
|
|
49
|
+
if (isNode) {
|
|
50
|
+
deps.add('eslint-plugin-n');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (config.enableStylelint) {
|
|
54
|
+
deps.add('stylelint');
|
|
55
|
+
deps.add('@linter-spec/stylelint-config');
|
|
56
|
+
}
|
|
57
|
+
if (config.enableMarkdownlint) {
|
|
58
|
+
deps.add('markdownlint-cli2');
|
|
59
|
+
deps.add('@linter-spec/markdownlint-config');
|
|
60
|
+
}
|
|
61
|
+
if (config.enablePrettier) {
|
|
62
|
+
deps.add('prettier');
|
|
63
|
+
}
|
|
64
|
+
return [...deps];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Install everything the chosen lint setup needs (CLI + eslint-config +
|
|
68
|
+
* project-type-specific plugins + selected toolchains) as devDeps of `cwd`.
|
|
69
|
+
*/
|
|
70
|
+
export async function installProjectDeps(cwd, config) {
|
|
71
|
+
const pm = detectPackageManager(cwd);
|
|
72
|
+
const pkgs = projectDepsToInstall(config);
|
|
73
|
+
const [command, args] = addDevCommand(pm, pkgs);
|
|
74
|
+
const result = spawn.sync(command, args, { stdio: 'inherit', cwd });
|
|
75
|
+
if (result.status !== 0) {
|
|
76
|
+
throw new Error(`Failed to install project dependencies (\`${command} ${args.join(' ')}\` exited with ${result.status}).`);
|
|
77
|
+
}
|
|
23
78
|
}
|
|
24
79
|
/**
|
|
25
80
|
* When a project relies on the bundled lint configs but has no `node_modules`,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<%_
|
|
2
|
+
// Only emit a tsconfig for TypeScript project types. The init flow skips
|
|
3
|
+
// templates that render to an empty string.
|
|
4
|
+
if (!eslintType.startsWith('typescript')) { return ''; }
|
|
5
|
+
|
|
6
|
+
const isReact = eslintType === 'typescript/react';
|
|
7
|
+
const isVue = eslintType === 'typescript/vue';
|
|
8
|
+
const isNode = eslintType === 'typescript/node';
|
|
9
|
+
// Node-only projects don't need DOM types; everything else (plain TS, React, Vue) does.
|
|
10
|
+
const lib = isNode ? ['ES2022'] : ['ES2022', 'DOM', 'DOM.Iterable'];
|
|
11
|
+
_%>
|
|
12
|
+
{
|
|
13
|
+
"compilerOptions": {
|
|
14
|
+
"target": "ES2022",
|
|
15
|
+
"module": "ESNext",
|
|
16
|
+
"moduleResolution": "Bundler",
|
|
17
|
+
"lib": [<%- lib.map((l) => `"${l}"`).join(', ') %>],<% if (isReact) { %>
|
|
18
|
+
"jsx": "react-jsx",<% } %><% if (isVue) { %>
|
|
19
|
+
"jsx": "preserve",<% } %>
|
|
20
|
+
"strict": true,
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"forceConsistentCasingInFileNames": true,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"resolveJsonModule": true,
|
|
25
|
+
"isolatedModules": true,
|
|
26
|
+
"noEmit": true
|
|
27
|
+
},
|
|
28
|
+
"include": ["src/**/*"<% if (isReact) { %>, "src/**/*.tsx"<% } %><% if (isVue) { %>, "src/**/*.vue"<% } %>],
|
|
29
|
+
"exclude": ["node_modules", "dist", "build", "coverage"]
|
|
30
|
+
}
|
|
@@ -6,6 +6,7 @@ import { confirm } from '@inquirer/prompts';
|
|
|
6
6
|
import log from './log.js';
|
|
7
7
|
import { messages } from './messages.js';
|
|
8
8
|
import { CliAbortError } from './errors.js';
|
|
9
|
+
import { SKIP_IF_EXISTS } from './generate-template.js';
|
|
9
10
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
// Remove these exact dependencies (they conflict or are superseded).
|
|
11
12
|
const packageNamesToRemove = [
|
|
@@ -55,6 +56,7 @@ const checkUselessConfig = (cwd) => []
|
|
|
55
56
|
const checkReWriteConfig = (cwd) => fg
|
|
56
57
|
.sync('**/*.ejs', { cwd: path.resolve(dirname, '../config'), dot: true })
|
|
57
58
|
.map((name) => name.replace(/^_/, '.').replace(/\.ejs$/, ''))
|
|
59
|
+
.filter((filename) => !SKIP_IF_EXISTS.has(path.basename(filename)))
|
|
58
60
|
.filter((filename) => fs.existsSync(path.resolve(cwd, filename)));
|
|
59
61
|
export default async function conflictResolve(cwd, rewriteConfig) {
|
|
60
62
|
const pkgPath = path.resolve(cwd, 'package.json');
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files we will never silently overwrite — user customisations would be lost.
|
|
3
|
+
* The conflict-resolve step already prompts before regenerating known lint
|
|
4
|
+
* configs; these are extra files we only seed on a clean project.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SKIP_IF_EXISTS: Set<string>;
|
|
1
7
|
/**
|
|
2
8
|
* Render the EJS config templates into `cwd`.
|
|
3
9
|
* @param cwd target project root
|
|
@@ -39,6 +39,12 @@ const mergeVSCodeConfig = (filepath, content) => {
|
|
|
39
39
|
return '';
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Files we will never silently overwrite — user customisations would be lost.
|
|
44
|
+
* The conflict-resolve step already prompts before regenerating known lint
|
|
45
|
+
* configs; these are extra files we only seed on a clean project.
|
|
46
|
+
*/
|
|
47
|
+
export const SKIP_IF_EXISTS = new Set(['tsconfig.json']);
|
|
42
48
|
/**
|
|
43
49
|
* Render the EJS config templates into `cwd`.
|
|
44
50
|
* @param cwd target project root
|
|
@@ -49,7 +55,10 @@ export default function generateTemplate(cwd, data, vscode) {
|
|
|
49
55
|
const templatePath = path.resolve(dirname, '../config');
|
|
50
56
|
const templates = fg.sync(`${vscode ? '_vscode' : '**'}/*.ejs`, { cwd: templatePath, dot: true });
|
|
51
57
|
for (const name of templates) {
|
|
52
|
-
const
|
|
58
|
+
const outName = name.replace(/\.ejs$/, '').replace(/^_/, '.');
|
|
59
|
+
const filepath = path.resolve(cwd, outName);
|
|
60
|
+
if (SKIP_IF_EXISTS.has(path.basename(outName)) && fs.existsSync(filepath))
|
|
61
|
+
continue;
|
|
53
62
|
let content = ejs.render(fs.readFileSync(path.resolve(templatePath, name), 'utf8'), {
|
|
54
63
|
eslintIgnores: ESLINT_IGNORE_GLOBS,
|
|
55
64
|
stylelintExt: STYLELINT_FILE_EXT,
|
package/dist/utils/npm.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'cnpm';
|
|
|
12
12
|
* `pnpm` happened to be on PATH and so polluted lockfiles in yarn/bun projects.
|
|
13
13
|
*/
|
|
14
14
|
export declare function detectPackageManager(cwd?: string): PackageManager;
|
|
15
|
-
/** `[command, args]` to add
|
|
16
|
-
export declare function addDevCommand(pm: PackageManager, pkg: string): [string, string[]];
|
|
15
|
+
/** `[command, args]` to add one or more dev dependencies, per package manager. */
|
|
16
|
+
export declare function addDevCommand(pm: PackageManager, pkg: string | string[]): [string, string[]];
|
|
17
17
|
/** `[command, args]` to add a global package, per package manager. */
|
|
18
18
|
export declare function addGlobalCommand(pm: PackageManager, pkg: string): [string, string[]];
|
|
19
19
|
/** `[command, args]` to install all dependencies — `<pm> install` works for all. */
|
package/dist/utils/npm.js
CHANGED
|
@@ -48,15 +48,16 @@ export function detectPackageManager(cwd = process.cwd()) {
|
|
|
48
48
|
// 4. Fallback.
|
|
49
49
|
return 'npm';
|
|
50
50
|
}
|
|
51
|
-
/** `[command, args]` to add
|
|
51
|
+
/** `[command, args]` to add one or more dev dependencies, per package manager. */
|
|
52
52
|
export function addDevCommand(pm, pkg) {
|
|
53
|
+
const list = Array.isArray(pkg) ? pkg : [pkg];
|
|
53
54
|
switch (pm) {
|
|
54
55
|
case 'yarn':
|
|
55
|
-
return ['yarn', ['add', '-D',
|
|
56
|
+
return ['yarn', ['add', '-D', ...list]];
|
|
56
57
|
case 'bun':
|
|
57
|
-
return ['bun', ['add', '-d',
|
|
58
|
+
return ['bun', ['add', '-d', ...list]];
|
|
58
59
|
default: // npm / pnpm / cnpm
|
|
59
|
-
return [pm, ['i', '-D',
|
|
60
|
+
return [pm, ['i', '-D', ...list]];
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
/** `[command, args]` to add a global package, per package manager. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linter-spec/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "One-command lint toolchain (ESLint / Stylelint / markdownlint / Prettier / commitlint) for the linter-spec spec.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "SotherWind",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"text-table": "^0.2.0",
|
|
64
64
|
"@linter-spec/commitlint-config": "^1.0.0",
|
|
65
65
|
"@linter-spec/eslint-config": "^1.0.0",
|
|
66
|
-
"@linter-spec/
|
|
67
|
-
"@linter-spec/
|
|
66
|
+
"@linter-spec/stylelint-config": "^1.0.0",
|
|
67
|
+
"@linter-spec/markdownlint-config": "^1.0.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/cross-spawn": "^6.0.6",
|