@linter-spec/cli 1.0.0

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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/actions/init/index.d.ts +2 -0
  4. package/dist/actions/init/index.js +67 -0
  5. package/dist/actions/init/install-deps.d.ts +9 -0
  6. package/dist/actions/init/install-deps.js +36 -0
  7. package/dist/actions/init/prompts.d.ts +4 -0
  8. package/dist/actions/init/prompts.js +10 -0
  9. package/dist/actions/init/setup-husky.d.ts +7 -0
  10. package/dist/actions/init/setup-husky.js +18 -0
  11. package/dist/actions/init/write-vscode.d.ts +4 -0
  12. package/dist/actions/init/write-vscode.js +7 -0
  13. package/dist/actions/scan/index.d.ts +5 -0
  14. package/dist/actions/scan/index.js +20 -0
  15. package/dist/actions/scan/orchestrate.d.ts +6 -0
  16. package/dist/actions/scan/orchestrate.js +49 -0
  17. package/dist/actions/update.d.ts +5 -0
  18. package/dist/actions/update.js +61 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +9 -0
  21. package/dist/commands/commit-file-scan.d.ts +2 -0
  22. package/dist/commands/commit-file-scan.js +35 -0
  23. package/dist/commands/commit-msg-scan.d.ts +2 -0
  24. package/dist/commands/commit-msg-scan.js +24 -0
  25. package/dist/commands/fix.d.ts +2 -0
  26. package/dist/commands/fix.js +26 -0
  27. package/dist/commands/index.d.ts +3 -0
  28. package/dist/commands/index.js +15 -0
  29. package/dist/commands/init.d.ts +2 -0
  30. package/dist/commands/init.js +31 -0
  31. package/dist/commands/scan.d.ts +2 -0
  32. package/dist/commands/scan.js +36 -0
  33. package/dist/commands/update.d.ts +2 -0
  34. package/dist/commands/update.js +8 -0
  35. package/dist/config/_editorconfig.ejs +13 -0
  36. package/dist/config/_markdownlint-cli2.cjs.ejs +13 -0
  37. package/dist/config/_stylelintignore.ejs +5 -0
  38. package/dist/config/_vscode/extensions.json.ejs +9 -0
  39. package/dist/config/_vscode/settings.json.ejs +26 -0
  40. package/dist/config/commitlint.config.mjs.ejs +3 -0
  41. package/dist/config/eslint.config.mjs.ejs +16 -0
  42. package/dist/config/linter-spec.config.mjs.ejs +6 -0
  43. package/dist/config/prettier.config.mjs.ejs +10 -0
  44. package/dist/config/stylelint.config.mjs.ejs +5 -0
  45. package/dist/index.d.ts +6 -0
  46. package/dist/index.js +23 -0
  47. package/dist/lints/eslint/do-eslint.d.ts +7 -0
  48. package/dist/lints/eslint/do-eslint.js +31 -0
  49. package/dist/lints/eslint/format-results.d.ts +6 -0
  50. package/dist/lints/eslint/format-results.js +25 -0
  51. package/dist/lints/eslint/get-config-type.d.ts +12 -0
  52. package/dist/lints/eslint/get-config-type.js +30 -0
  53. package/dist/lints/eslint/get-config.d.ts +10 -0
  54. package/dist/lints/eslint/get-config.js +39 -0
  55. package/dist/lints/eslint/index.d.ts +4 -0
  56. package/dist/lints/eslint/index.js +4 -0
  57. package/dist/lints/index.d.ts +4 -0
  58. package/dist/lints/index.js +4 -0
  59. package/dist/lints/markdownlint/do-markdownlint.d.ts +7 -0
  60. package/dist/lints/markdownlint/do-markdownlint.js +33 -0
  61. package/dist/lints/markdownlint/format-results.d.ts +7 -0
  62. package/dist/lints/markdownlint/format-results.js +38 -0
  63. package/dist/lints/markdownlint/get-config.d.ts +10 -0
  64. package/dist/lints/markdownlint/get-config.js +31 -0
  65. package/dist/lints/markdownlint/index.d.ts +3 -0
  66. package/dist/lints/markdownlint/index.js +3 -0
  67. package/dist/lints/prettier/do-prettier.d.ts +2 -0
  68. package/dist/lints/prettier/do-prettier.js +31 -0
  69. package/dist/lints/prettier/index.d.ts +1 -0
  70. package/dist/lints/prettier/index.js +1 -0
  71. package/dist/lints/resolve-files.d.ts +10 -0
  72. package/dist/lints/resolve-files.js +20 -0
  73. package/dist/lints/stylelint/do-stylelint.d.ts +7 -0
  74. package/dist/lints/stylelint/do-stylelint.js +15 -0
  75. package/dist/lints/stylelint/format-results.d.ts +6 -0
  76. package/dist/lints/stylelint/format-results.js +39 -0
  77. package/dist/lints/stylelint/get-config.d.ts +7 -0
  78. package/dist/lints/stylelint/get-config.js +33 -0
  79. package/dist/lints/stylelint/get-doc-url.d.ts +4 -0
  80. package/dist/lints/stylelint/get-doc-url.js +18 -0
  81. package/dist/lints/stylelint/index.d.ts +4 -0
  82. package/dist/lints/stylelint/index.js +4 -0
  83. package/dist/types.d.ts +85 -0
  84. package/dist/types.js +1 -0
  85. package/dist/utils/conflict-resolve.d.ts +2 -0
  86. package/dist/utils/conflict-resolve.js +103 -0
  87. package/dist/utils/constants.d.ts +43 -0
  88. package/dist/utils/constants.js +91 -0
  89. package/dist/utils/errors.d.ts +9 -0
  90. package/dist/utils/errors.js +12 -0
  91. package/dist/utils/generate-template.d.ts +7 -0
  92. package/dist/utils/generate-template.js +67 -0
  93. package/dist/utils/git.d.ts +9 -0
  94. package/dist/utils/git.js +28 -0
  95. package/dist/utils/log.d.ts +8 -0
  96. package/dist/utils/log.js +23 -0
  97. package/dist/utils/messages.d.ts +47 -0
  98. package/dist/utils/messages.js +54 -0
  99. package/dist/utils/npm.d.ts +20 -0
  100. package/dist/utils/npm.js +76 -0
  101. package/dist/utils/print-report.d.ts +5 -0
  102. package/dist/utils/print-report.js +63 -0
  103. package/dist/utils/read-config.d.ts +3 -0
  104. package/dist/utils/read-config.js +15 -0
  105. package/package.json +88 -0
@@ -0,0 +1,15 @@
1
+ import stylelint from 'stylelint';
2
+ import { STYLELINT_FILE_EXT, STYLELINT_IGNORE_PATTERN } from '../../utils/constants.js';
3
+ import { resolveScanFiles } from '../resolve-files.js';
4
+ import { formatStylelintResults } from './format-results.js';
5
+ import { getStylelintConfig } from './get-config.js';
6
+ export async function doStylelint(options) {
7
+ const files = await resolveScanFiles(options, STYLELINT_FILE_EXT, STYLELINT_IGNORE_PATTERN);
8
+ if (files.length === 0)
9
+ return [];
10
+ const data = await stylelint.lint({
11
+ ...getStylelintConfig(options, options.pkg, options.config),
12
+ files,
13
+ });
14
+ return formatStylelintResults(data.results, options.quiet);
15
+ }
@@ -0,0 +1,6 @@
1
+ import type { LintResult } from 'stylelint';
2
+ import type { ScanResult } from '../../types.js';
3
+ /**
4
+ * Normalise Stylelint results into the shared `ScanResult` shape.
5
+ */
6
+ export declare function formatStylelintResults(results: LintResult[], quiet: boolean | undefined): ScanResult[];
@@ -0,0 +1,39 @@
1
+ import { getStylelintRuleDocUrl } from './get-doc-url.js';
2
+ /**
3
+ * Normalise Stylelint results into the shared `ScanResult` shape.
4
+ */
5
+ export function formatStylelintResults(results, quiet) {
6
+ return results.map(({ source, warnings }) => {
7
+ let errorCount = 0;
8
+ let warningCount = 0;
9
+ const messages = warnings
10
+ .filter((item) => !quiet || item.severity === 'error')
11
+ .map((item) => {
12
+ const { line = 0, column = 0, rule, severity, text } = item;
13
+ if (severity === 'error') {
14
+ errorCount++;
15
+ }
16
+ else {
17
+ warningCount++;
18
+ }
19
+ return {
20
+ line,
21
+ column,
22
+ rule: rule ?? null,
23
+ url: getStylelintRuleDocUrl(rule),
24
+ message: text
25
+ .replace(/([^ ])\.$/u, '$1')
26
+ .replace(rule ? new RegExp(`\\(${rule}\\)`) : '', ''),
27
+ errored: severity === 'error',
28
+ };
29
+ });
30
+ return {
31
+ filePath: source ?? '',
32
+ messages,
33
+ errorCount,
34
+ warningCount,
35
+ fixableErrorCount: 0,
36
+ fixableWarningCount: 0,
37
+ };
38
+ });
39
+ }
@@ -0,0 +1,7 @@
1
+ import type { LinterOptions } from 'stylelint';
2
+ import type { Config, PKG, ScanOptions } from '../../types.js';
3
+ /**
4
+ * Build the Stylelint options. Uses the shared config unless the project
5
+ * provides its own.
6
+ */
7
+ export declare function getStylelintConfig(opts: ScanOptions, pkg: PKG, config: Config): Partial<LinterOptions>;
@@ -0,0 +1,33 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import fs from 'fs-extra';
4
+ import fg from 'fast-glob';
5
+ import { STYLELINT_IGNORE_PATTERN } from '../../utils/constants.js';
6
+ // Absolute path so Stylelint can resolve `extends` regardless of the project cwd.
7
+ const stylelintConfigPath = fileURLToPath(import.meta.resolve('@linter-spec/stylelint-config'));
8
+ /**
9
+ * Build the Stylelint options. Uses the shared config unless the project
10
+ * provides its own.
11
+ */
12
+ export function getStylelintConfig(opts, pkg, config) {
13
+ const { cwd, fix } = opts;
14
+ const lintConfig = {
15
+ fix: Boolean(fix),
16
+ allowEmptyInput: true,
17
+ };
18
+ if (config.stylelintOptions) {
19
+ Object.assign(lintConfig, config.stylelintOptions);
20
+ return lintConfig;
21
+ }
22
+ const userConfig = fg.sync('.stylelintrc?(.@(js|cjs|mjs|yaml|yml|json))', { cwd, dot: true }).length > 0 ||
23
+ fg.sync('stylelint.config.@(js|mjs|cjs)', { cwd, dot: true }).length > 0 ||
24
+ Boolean(pkg.stylelint);
25
+ if (!userConfig) {
26
+ lintConfig.config = { extends: [stylelintConfigPath] };
27
+ }
28
+ const ignoreFilePath = path.resolve(cwd, '.stylelintignore');
29
+ if (!fs.existsSync(ignoreFilePath)) {
30
+ lintConfig.ignorePattern = STYLELINT_IGNORE_PATTERN;
31
+ }
32
+ return lintConfig;
33
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Resolve the documentation URL for a Stylelint rule.
3
+ */
4
+ export declare function getStylelintRuleDocUrl(rule: string | undefined): string;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Resolve the documentation URL for a Stylelint rule.
3
+ */
4
+ export function getStylelintRuleDocUrl(rule) {
5
+ if (!rule)
6
+ return '';
7
+ const scss = rule.match(/^scss\/(\S+)$/);
8
+ if (scss) {
9
+ return `https://github.com/stylelint-scss/stylelint-scss/tree/master/src/rules/${scss[1]}`;
10
+ }
11
+ const stylistic = rule.match(/^@stylistic\/(\S+)$/);
12
+ if (stylistic) {
13
+ return `https://github.com/stylelint-stylistic/stylelint-stylistic/blob/main/lib/rules/${stylistic[1]}/README.md`;
14
+ }
15
+ if (rule !== 'CssSyntaxError')
16
+ return `https://stylelint.io/user-guide/rules/${rule}`;
17
+ return '';
18
+ }
@@ -0,0 +1,4 @@
1
+ export * from './get-config.js';
2
+ export * from './get-doc-url.js';
3
+ export * from './format-results.js';
4
+ export * from './do-stylelint.js';
@@ -0,0 +1,4 @@
1
+ export * from './get-config.js';
2
+ export * from './get-doc-url.js';
3
+ export * from './format-results.js';
4
+ export * from './do-stylelint.js';
@@ -0,0 +1,85 @@
1
+ import type { ESLint } from 'eslint';
2
+ import type stylelint from 'stylelint';
3
+ import type markdownlint from 'markdownlint';
4
+ export interface PKG {
5
+ eslintConfig?: unknown;
6
+ eslintIgnore?: string[];
7
+ stylelint?: unknown;
8
+ husky?: Record<string, unknown>;
9
+ scripts?: Record<string, string>;
10
+ peerDependencies?: Record<string, string>;
11
+ devDependencies?: Record<string, string>;
12
+ dependencies?: Record<string, string>;
13
+ [key: string]: unknown;
14
+ }
15
+ export interface Config {
16
+ /** Enable ESLint (default true). */
17
+ enableESLint?: boolean;
18
+ /** Enable Stylelint (default true). */
19
+ enableStylelint?: boolean;
20
+ /** Enable markdownlint (default true). */
21
+ enableMarkdownlint?: boolean;
22
+ /** Enable Prettier (default true). */
23
+ enablePrettier?: boolean;
24
+ /** Override ESLint options. */
25
+ eslintOptions?: ESLint.Options;
26
+ /** Override Stylelint options. */
27
+ stylelintOptions?: Partial<stylelint.LinterOptions>;
28
+ /** Override markdownlint options. */
29
+ markdownlintOptions?: markdownlint.Options;
30
+ }
31
+ export interface ScanOptions {
32
+ /** Project root the lint runs in. */
33
+ cwd: string;
34
+ /** Directory to scan. */
35
+ include: string;
36
+ /** Explicit file list to scan (overrides `include`). */
37
+ files?: string[];
38
+ /** Only report errors. */
39
+ quiet?: boolean;
40
+ /** Honour ESLint ignore files / rules (maps to `--no-ignore`). */
41
+ ignore?: boolean;
42
+ /** Auto-fix. */
43
+ fix?: boolean;
44
+ /** Write a JSON report file. */
45
+ outputReport?: boolean;
46
+ /** Inline config, takes precedence over `linter-spec.config.*`. */
47
+ config?: Config;
48
+ }
49
+ export interface ScanMessage {
50
+ line: number;
51
+ column: number;
52
+ rule: string | null;
53
+ url: string;
54
+ message: string;
55
+ errored: boolean;
56
+ }
57
+ export interface ScanResult {
58
+ filePath: string;
59
+ errorCount: number;
60
+ warningCount: number;
61
+ fixableErrorCount: number;
62
+ fixableWarningCount: number;
63
+ messages: ScanMessage[];
64
+ }
65
+ export interface ScanReport {
66
+ results: ScanResult[];
67
+ errorCount: number;
68
+ warningCount: number;
69
+ runErrors: Error[];
70
+ }
71
+ export interface InitOptions {
72
+ cwd: string;
73
+ /** Check for and self-update the CLI before initialising. */
74
+ checkVersionUpdate: boolean;
75
+ /** Auto-rewrite conflicting lint config without prompting. */
76
+ rewriteConfig?: boolean;
77
+ /** ESLint project type (see `PROJECT_TYPES`). */
78
+ eslintType?: string;
79
+ enableESLint?: boolean;
80
+ enableStylelint?: boolean;
81
+ enableMarkdownlint?: boolean;
82
+ enablePrettier?: boolean;
83
+ /** Skip the post-init dependency install. */
84
+ disableNpmInstall?: boolean;
85
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { PKG } from '../types.js';
2
+ export default function conflictResolve(cwd: string, rewriteConfig?: boolean): Promise<PKG>;
@@ -0,0 +1,103 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import fs from 'fs-extra';
4
+ import fg from 'fast-glob';
5
+ import { confirm } from '@inquirer/prompts';
6
+ import log from './log.js';
7
+ import { messages } from './messages.js';
8
+ import { CliAbortError } from './errors.js';
9
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ // Remove these exact dependencies (they conflict or are superseded).
11
+ const packageNamesToRemove = [
12
+ '@babel/eslint-parser',
13
+ '@commitlint/cli',
14
+ '@iceworks/spec',
15
+ 'babel-eslint',
16
+ 'eslint',
17
+ 'husky',
18
+ 'markdownlint',
19
+ 'prettier',
20
+ 'stylelint',
21
+ 'tslint',
22
+ ];
23
+ // Remove dependencies starting with these prefixes.
24
+ const packagePrefixesToRemove = [
25
+ '@commitlint/',
26
+ '@typescript-eslint/',
27
+ 'eslint-',
28
+ 'stylelint-',
29
+ 'markdownlint-',
30
+ 'commitlint-',
31
+ ];
32
+ /** Old, now-unused lint config files to delete. */
33
+ const checkUselessConfig = (cwd) => []
34
+ .concat(fg.sync('.eslintrc?(.@(yaml|yml|json))', { cwd, dot: true }))
35
+ .concat(fg.sync('.stylelintrc?(.@(yaml|yml|json))', { cwd, dot: true }))
36
+ // Old markdownlint config slots we no longer write — all superseded by the
37
+ // single `.markdownlint-cli2.cjs` we now emit:
38
+ // - `.markdownlint.{json,jsonc,yaml,yml}` / `.markdownlintrc`: legacy
39
+ // ruleset slots.
40
+ // - `.markdownlint.cjs`: the ruleset slot an earlier version of this CLI
41
+ // wrote; cli2 reads it but at lower precedence than `.markdownlint-cli2.cjs`,
42
+ // so leaving it behind would only confuse readers.
43
+ // - `.markdownlintignore`: never read by cli2 or the VS Code extension
44
+ // (only by the legacy `markdownlint-cli`); ignores now live in the cli2
45
+ // config's `ignores` field.
46
+ .concat(fg.sync('.markdownlint@(rc|.@(json|jsonc|yaml|yml|cjs))', { cwd, dot: true }))
47
+ .concat(fg.sync('.markdownlintignore', { cwd, dot: true }))
48
+ .concat(fg.sync('.prettierrc?(.@(cjs|config.js|config.cjs|yaml|yml|json|json5|toml))', {
49
+ cwd,
50
+ dot: true,
51
+ }))
52
+ .concat(fg.sync('tslint.@(yaml|yml|json)', { cwd, dot: true }))
53
+ .concat(fg.sync('.kylerc?(.@(yaml|yml|json))', { cwd, dot: true }));
54
+ /** Config files we are about to (over)write that already exist. */
55
+ const checkReWriteConfig = (cwd) => fg
56
+ .sync('**/*.ejs', { cwd: path.resolve(dirname, '../config'), dot: true })
57
+ .map((name) => name.replace(/^_/, '.').replace(/\.ejs$/, ''))
58
+ .filter((filename) => fs.existsSync(path.resolve(cwd, filename)));
59
+ export default async function conflictResolve(cwd, rewriteConfig) {
60
+ const pkgPath = path.resolve(cwd, 'package.json');
61
+ const pkg = fs.readJSONSync(pkgPath);
62
+ const dependencies = [].concat(Object.keys(pkg.dependencies || {}), Object.keys(pkg.devDependencies || {}));
63
+ const willRemovePackage = dependencies.filter((name) => packageNamesToRemove.includes(name) ||
64
+ packagePrefixesToRemove.some((prefix) => name.startsWith(prefix)));
65
+ const uselessConfig = checkUselessConfig(cwd);
66
+ const reWriteConfig = checkReWriteConfig(cwd);
67
+ const willChangeCount = willRemovePackage.length + uselessConfig.length + reWriteConfig.length;
68
+ if (willChangeCount > 0) {
69
+ log.warn(messages.conflictDetected);
70
+ if (willRemovePackage.length > 0) {
71
+ log.warn(messages.conflictRemoveDeps);
72
+ log.warn(JSON.stringify(willRemovePackage, null, 2));
73
+ }
74
+ if (uselessConfig.length > 0) {
75
+ log.warn(messages.conflictRemoveConfig);
76
+ log.warn(JSON.stringify(uselessConfig, null, 2));
77
+ }
78
+ if (reWriteConfig.length > 0) {
79
+ log.warn(messages.conflictRewriteConfig);
80
+ log.warn(JSON.stringify(reWriteConfig, null, 2));
81
+ }
82
+ if (typeof rewriteConfig === 'undefined') {
83
+ const isOverWrite = await confirm({ message: messages.conflictConfirm });
84
+ if (!isOverWrite)
85
+ throw new CliAbortError();
86
+ }
87
+ else if (!rewriteConfig) {
88
+ throw new CliAbortError();
89
+ }
90
+ }
91
+ for (const name of uselessConfig) {
92
+ fs.removeSync(path.resolve(cwd, name));
93
+ }
94
+ delete pkg.eslintConfig;
95
+ delete pkg.eslintIgnore;
96
+ delete pkg.stylelint;
97
+ for (const name of willRemovePackage) {
98
+ delete (pkg.dependencies || {})[name];
99
+ delete (pkg.devDependencies || {})[name];
100
+ }
101
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
102
+ return pkg;
103
+ }
@@ -0,0 +1,43 @@
1
+ export declare const UNICODE: {
2
+ readonly success: "✔";
3
+ readonly failure: "✖";
4
+ };
5
+ /** npm package name, e.g. `@linter-spec/cli` (used for install / update). */
6
+ export declare const PKG_NAME: string;
7
+ /** Package version. */
8
+ export declare const PKG_VERSION: string;
9
+ /** The user-facing command name / config-file prefix, e.g. `linter-spec`. */
10
+ export declare const CLI_NAME = "linter-spec";
11
+ /**
12
+ * Project types — values map 1:1 to `@linter-spec/eslint-config` export
13
+ * subpaths (`index` → the package root export).
14
+ */
15
+ export declare const PROJECT_TYPES: Array<{
16
+ name: string;
17
+ value: string;
18
+ }>;
19
+ /** ESLint scan file extensions. */
20
+ export declare const ESLINT_FILE_EXT: string[];
21
+ /**
22
+ * ESLint flat-config global ignore globs.
23
+ * (ESLint 9 dropped `.eslintignore`; ignores now live in the flat config.)
24
+ */
25
+ export declare const ESLINT_IGNORE_GLOBS: string[];
26
+ /** Stylelint scan file extensions. */
27
+ export declare const STYLELINT_FILE_EXT: string[];
28
+ /** Stylelint ignore patterns. */
29
+ export declare const STYLELINT_IGNORE_PATTERN: string[];
30
+ /** markdownlint scan file extensions. */
31
+ export declare const MARKDOWN_LINT_FILE_EXT: string[];
32
+ /**
33
+ * markdownlint ignore patterns — micromatch globs.
34
+ *
35
+ * Used as fast-glob's `ignore` in `linter-spec scan/fix`, AND emitted into the
36
+ * generated `.markdownlint-cli2.cjs`'s `ignores` field which markdownlint-cli2
37
+ * matches with micromatch. Globs that work in both: `**\/<dir>/**`.
38
+ */
39
+ export declare const MARKDOWN_LINT_IGNORE_PATTERN: string[];
40
+ /** Prettier scan file extensions (union of the lint extensions). */
41
+ export declare const PRETTIER_FILE_EXT: string[];
42
+ /** Prettier ignore patterns. */
43
+ export declare const PRETTIER_IGNORE_PATTERN: string[];
@@ -0,0 +1,91 @@
1
+ // dist/utils/constants.js (and src/utils/constants.ts) are both two levels deep,
2
+ // so the relative path to the package manifest is identical at build & runtime.
3
+ import pkg from '../../package.json' with { type: 'json' };
4
+ export const UNICODE = {
5
+ success: '✔', // ✔
6
+ failure: '✖', // ✖
7
+ };
8
+ /** npm package name, e.g. `@linter-spec/cli` (used for install / update). */
9
+ export const PKG_NAME = pkg.name;
10
+ /** Package version. */
11
+ export const PKG_VERSION = pkg.version;
12
+ /** The user-facing command name / config-file prefix, e.g. `linter-spec`. */
13
+ export const CLI_NAME = 'linter-spec';
14
+ /**
15
+ * Project types — values map 1:1 to `@linter-spec/eslint-config` export
16
+ * subpaths (`index` → the package root export).
17
+ */
18
+ export const PROJECT_TYPES = [
19
+ { name: '未使用 React、Vue、Node.js 的项目(JavaScript)', value: 'index' },
20
+ { name: '未使用 React、Vue、Node.js 的项目(TypeScript)', value: 'typescript' },
21
+ { name: 'React 项目(JavaScript)', value: 'react' },
22
+ { name: 'React 项目(TypeScript)', value: 'typescript/react' },
23
+ { name: 'Vue 项目(JavaScript)', value: 'vue' },
24
+ { name: 'Vue 项目(TypeScript)', value: 'typescript/vue' },
25
+ { name: 'Node.js 项目(JavaScript)', value: 'node' },
26
+ { name: 'Node.js 项目(TypeScript)', value: 'typescript/node' },
27
+ { name: '使用 ES5 及之前版本 JavaScript 的老项目', value: 'es5' },
28
+ ];
29
+ /** ESLint scan file extensions. */
30
+ export const ESLINT_FILE_EXT = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.mjs', '.cjs'];
31
+ /**
32
+ * ESLint flat-config global ignore globs.
33
+ * (ESLint 9 dropped `.eslintignore`; ignores now live in the flat config.)
34
+ */
35
+ export const ESLINT_IGNORE_GLOBS = [
36
+ '**/node_modules/**',
37
+ '**/build/**',
38
+ '**/dist/**',
39
+ '**/coverage/**',
40
+ '**/es/**',
41
+ '**/lib/**',
42
+ '**/*.min.js',
43
+ '**/*-min.js',
44
+ '**/*.bundle.js',
45
+ ];
46
+ /** Stylelint scan file extensions. */
47
+ export const STYLELINT_FILE_EXT = ['.css', '.scss', '.less', '.acss'];
48
+ /** Stylelint ignore patterns. */
49
+ export const STYLELINT_IGNORE_PATTERN = [
50
+ '**/node_modules/**',
51
+ '**/build/**',
52
+ '**/dist/**',
53
+ '**/coverage/**',
54
+ '**/es/**',
55
+ '**/lib/**',
56
+ '**/*.min.css',
57
+ '**/*-min.css',
58
+ '**/*.bundle.css',
59
+ ];
60
+ /** markdownlint scan file extensions. */
61
+ export const MARKDOWN_LINT_FILE_EXT = ['.md'];
62
+ /**
63
+ * markdownlint ignore patterns — micromatch globs.
64
+ *
65
+ * Used as fast-glob's `ignore` in `linter-spec scan/fix`, AND emitted into the
66
+ * generated `.markdownlint-cli2.cjs`'s `ignores` field which markdownlint-cli2
67
+ * matches with micromatch. Globs that work in both: `**\/<dir>/**`.
68
+ */
69
+ export const MARKDOWN_LINT_IGNORE_PATTERN = [
70
+ '**/node_modules/**',
71
+ '**/build/**',
72
+ '**/dist/**',
73
+ '**/coverage/**',
74
+ '**/es/**',
75
+ '**/lib/**',
76
+ ];
77
+ /** Prettier scan file extensions (union of the lint extensions). */
78
+ export const PRETTIER_FILE_EXT = [
79
+ ...STYLELINT_FILE_EXT,
80
+ ...ESLINT_FILE_EXT,
81
+ ...MARKDOWN_LINT_FILE_EXT,
82
+ ];
83
+ /** Prettier ignore patterns. */
84
+ export const PRETTIER_IGNORE_PATTERN = [
85
+ 'node_modules/**/*',
86
+ 'build/**/*',
87
+ 'dist/**/*',
88
+ 'lib/**/*',
89
+ 'es/**/*',
90
+ 'coverage/**/*',
91
+ ];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Thrown by library-level code (e.g. conflict resolution) when the user declines
3
+ * to continue. The CLI command layer catches it and exits cleanly, instead of the
4
+ * library calling `process.exit` directly — which would silently kill any program
5
+ * that consumes the Node API.
6
+ */
7
+ export declare class CliAbortError extends Error {
8
+ constructor(message?: string);
9
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Thrown by library-level code (e.g. conflict resolution) when the user declines
3
+ * to continue. The CLI command layer catches it and exits cleanly, instead of the
4
+ * library calling `process.exit` directly — which would silently kill any program
5
+ * that consumes the Node API.
6
+ */
7
+ export class CliAbortError extends Error {
8
+ constructor(message = 'Operation aborted by user') {
9
+ super(message);
10
+ this.name = 'CliAbortError';
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Render the EJS config templates into `cwd`.
3
+ * @param cwd target project root
4
+ * @param data template variables (the resolved `Config` plus `eslintType`)
5
+ * @param vscode only emit the `.vscode/*` templates
6
+ */
7
+ export default function generateTemplate(cwd: string, data: Record<string, unknown>, vscode?: boolean): void;
@@ -0,0 +1,67 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import fs from 'fs-extra';
4
+ import fg from 'fast-glob';
5
+ import ejs from 'ejs';
6
+ import { ESLINT_IGNORE_GLOBS, STYLELINT_FILE_EXT, STYLELINT_IGNORE_PATTERN, MARKDOWN_LINT_IGNORE_PATTERN, } from './constants.js';
7
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
9
+ /**
10
+ * Deep-merge two parsed-JSON values: objects merge recursively, arrays are
11
+ * unioned (source first, deduped), and primitives take the source value.
12
+ * Replaces the single former `lodash.mergeWith` call site.
13
+ */
14
+ const deepMerge = (target, source) => {
15
+ if (Array.isArray(target) && Array.isArray(source)) {
16
+ return [...new Set([...source, ...target])];
17
+ }
18
+ if (isObject(target) && isObject(source)) {
19
+ const out = { ...target };
20
+ for (const key of Object.keys(source)) {
21
+ out[key] = key in target ? deepMerge(target[key], source[key]) : source[key];
22
+ }
23
+ return out;
24
+ }
25
+ return source;
26
+ };
27
+ /**
28
+ * Merge a generated VS Code config into an existing one (arrays are unioned).
29
+ */
30
+ const mergeVSCodeConfig = (filepath, content) => {
31
+ if (!fs.existsSync(filepath))
32
+ return content;
33
+ try {
34
+ const targetData = fs.readJSONSync(filepath);
35
+ const sourceData = JSON.parse(content);
36
+ return JSON.stringify(deepMerge(targetData, sourceData), null, 2);
37
+ }
38
+ catch {
39
+ return '';
40
+ }
41
+ };
42
+ /**
43
+ * Render the EJS config templates into `cwd`.
44
+ * @param cwd target project root
45
+ * @param data template variables (the resolved `Config` plus `eslintType`)
46
+ * @param vscode only emit the `.vscode/*` templates
47
+ */
48
+ export default function generateTemplate(cwd, data, vscode) {
49
+ const templatePath = path.resolve(dirname, '../config');
50
+ const templates = fg.sync(`${vscode ? '_vscode' : '**'}/*.ejs`, { cwd: templatePath, dot: true });
51
+ for (const name of templates) {
52
+ const filepath = path.resolve(cwd, name.replace(/\.ejs$/, '').replace(/^_/, '.'));
53
+ let content = ejs.render(fs.readFileSync(path.resolve(templatePath, name), 'utf8'), {
54
+ eslintIgnores: ESLINT_IGNORE_GLOBS,
55
+ stylelintExt: STYLELINT_FILE_EXT,
56
+ stylelintIgnores: STYLELINT_IGNORE_PATTERN,
57
+ markdownLintIgnores: MARKDOWN_LINT_IGNORE_PATTERN,
58
+ ...data,
59
+ });
60
+ if (/^_vscode/.test(name)) {
61
+ content = mergeVSCodeConfig(filepath, content);
62
+ }
63
+ if (!content.trim())
64
+ continue;
65
+ fs.outputFileSync(filepath, content, 'utf8');
66
+ }
67
+ }
@@ -0,0 +1,9 @@
1
+ import { type Options } from 'execa';
2
+ /**
3
+ * Files changed in this commit (staged, added/copied/modified/renamed).
4
+ */
5
+ export declare const getCommitFiles: (options?: Options) => Promise<string[]>;
6
+ /**
7
+ * Files modified in the working tree but not yet staged.
8
+ */
9
+ export declare const getAmendFiles: (options?: Options) => Promise<string>;
@@ -0,0 +1,28 @@
1
+ import { execa } from 'execa';
2
+ /**
3
+ * Files changed in this commit (staged, added/copied/modified/renamed).
4
+ */
5
+ export const getCommitFiles = async (options = {}) => {
6
+ try {
7
+ const { stdout } = await execa('git', ['diff', '--staged', '--diff-filter=ACMR', '--name-only', '--ignore-submodules'], { ...options, cwd: options.cwd || process.cwd() });
8
+ return typeof stdout === 'string' && stdout ? stdout.split(/\s/).filter(Boolean) : [];
9
+ }
10
+ catch {
11
+ return [];
12
+ }
13
+ };
14
+ /**
15
+ * Files modified in the working tree but not yet staged.
16
+ */
17
+ export const getAmendFiles = async (options = {}) => {
18
+ try {
19
+ const { stdout } = await execa('git', ['diff', '--name-only'], {
20
+ ...options,
21
+ cwd: options.cwd || process.cwd(),
22
+ });
23
+ return typeof stdout === 'string' ? stdout : '';
24
+ }
25
+ catch {
26
+ return '';
27
+ }
28
+ };
@@ -0,0 +1,8 @@
1
+ declare const log: {
2
+ success(text: string): void;
3
+ info(text: string): void;
4
+ warn(text: string): void;
5
+ error(text: string | Error): void;
6
+ result(text: string, pass: boolean): void;
7
+ };
8
+ export default log;
@@ -0,0 +1,23 @@
1
+ import chalk from 'chalk';
2
+ import { CLI_NAME, UNICODE } from './constants.js';
3
+ const { green, blue, yellow, red } = chalk;
4
+ const log = {
5
+ success(text) {
6
+ console.log(green(text));
7
+ },
8
+ info(text) {
9
+ console.info(blue(text));
10
+ },
11
+ warn(text) {
12
+ console.info(yellow(text));
13
+ },
14
+ error(text) {
15
+ // Print the full stack when given an Error (the stack string already
16
+ // includes the message); fall back to the message / raw string otherwise.
17
+ console.error(red(text instanceof Error ? (text.stack ?? text.message) : text));
18
+ },
19
+ result(text, pass) {
20
+ console.info(blue(`[${CLI_NAME}] ${text}`), pass ? green(UNICODE.success) : red(UNICODE.failure));
21
+ },
22
+ };
23
+ export default log;