rhine-lint 1.3.6 → 1.4.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.
package/README.md CHANGED
@@ -105,6 +105,10 @@ export default {
105
105
  // 默认为 './tsconfig.json'
106
106
  tsconfig: './tsconfig.app.json',
107
107
 
108
+ // 额外的忽略模式 (可选)
109
+ // 这些模式会与 .gitignore 和默认忽略合并
110
+ ignore: ['temp', 'generated', '*.test.ts'],
111
+
108
112
  // ESLint 专项配置
109
113
  eslint: {
110
114
  // 启用/禁用特定范围的规则
@@ -189,6 +193,47 @@ export default {
189
193
 
190
194
  默认使用 `./tsconfig.json`。如果你的项目使用不同的 tsconfig 文件(如 `tsconfig.app.json`、`tsconfig.node.json` 等),可以通过此选项指定。
191
195
 
196
+ ### 忽略模式 Ignore Patterns
197
+
198
+ Rhine Lint 提供了灵活的文件忽略机制,支持多种配置方式。
199
+
200
+ #### 默认忽略
201
+
202
+ 以下目录始终被忽略(无需配置):
203
+ - `node_modules`, `dist`, `.next`, `.git`, `.output`, `.nuxt`, `coverage`, `.cache`
204
+
205
+ #### 自动读取 .gitignore
206
+
207
+ Rhine Lint 会自动解析项目根目录的 `.gitignore` 文件,将其中的模式转换为 ESLint 忽略规则。
208
+
209
+ #### CLI 忽略选项
210
+
211
+ ```bash
212
+ # 添加额外的忽略模式 (支持多次使用)
213
+ rl --ignore temp --ignore generated --ignore "*.test.ts"
214
+
215
+ # 禁用所有忽略规则 (包括 .gitignore 和默认忽略)
216
+ rl --no-ignore
217
+ ```
218
+
219
+ #### 配置文件忽略
220
+
221
+ ```typescript
222
+ // rhine-lint.config.ts
223
+ export default {
224
+ ignore: ['temp', 'generated', '*.test.ts']
225
+ }
226
+ ```
227
+
228
+ #### 忽略模式优先级
229
+
230
+ 1. `--no-ignore` 会禁用所有忽略处理
231
+ 2. 否则,按以下顺序合并(后面的追加到前面):
232
+ - 默认忽略目录
233
+ - `.gitignore` 解析结果
234
+ - 配置文件 `ignore` 数组
235
+ - CLI `--ignore` 参数
236
+
192
237
  ### 缓存目录 Cache Directory
193
238
 
194
239
  Rhine Lint 需要一个目录来存放运行时动态生成的 "Virtual Config" 文件。这些文件是临时的,通常不需要用户关心。
@@ -247,6 +292,38 @@ graph TD
247
292
  #### CLI 入口 (`src/cli.ts`)
248
293
  - **职责**: 程序的入口点。
249
294
  - **实现**: 使用 `cac` 库处理命令行参数(如 `--fix`, `--level`)。
295
+
296
+ ##### CLI 选项定义
297
+
298
+ ```typescript
299
+ cli
300
+ .command("[...files]", "Lint files")
301
+ .option("--fix", "Fix lint errors")
302
+ .option("--config <path>", "Path to config file")
303
+ .option("--level <level>", "Project level (js, ts, frontend, nextjs)")
304
+ .option("--no-project-type-check", "Disable project-based type checking")
305
+ .option("--tsconfig <path>", "Path to tsconfig file")
306
+ .option("--ignore [pattern]", "Add ignore pattern (can be used multiple times)")
307
+ .option("--no-ignore", "Disable all ignore rules")
308
+ .option("--cache-dir <dir>", "Custom cache directory")
309
+ .option("--debug", "Enable debug mode")
310
+ ```
311
+
312
+ ##### 关键逻辑
313
+
314
+ ```typescript
315
+ // --ignore 参数处理 (支持多次调用)
316
+ // cac 会自动将多个 --ignore 收集为数组
317
+ // --no-ignore 会设置 options.ignore = false
318
+ const noIgnore = options.ignore === false;
319
+ let ignorePatterns: string[] = [];
320
+ if (!noIgnore && options.ignore && options.ignore !== true) {
321
+ ignorePatterns = Array.isArray(options.ignore)
322
+ ? options.ignore.filter((p: unknown) => typeof p === 'string')
323
+ : [options.ignore];
324
+ }
325
+ ```
326
+
250
327
  - **逻辑**:
251
328
  1. 它不会直接调用 ESLint API,而是准备好环境路径。
252
329
  2. 调用 `generateTempConfig` 准备配置文件。
@@ -256,6 +333,52 @@ graph TD
256
333
  #### 配置生成器 (`src/core/config.ts`) 🔥核心
257
334
  这是项目最复杂的部分。为了实现「零配置」且不污染用户目录,我们采用 **虚拟配置 (Virtual Configuration)** 策略。
258
335
 
336
+ ##### 函数签名
337
+
338
+ ```typescript
339
+ export async function generateTempConfig(
340
+ cwd: string, // 项目根目录
341
+ userConfigResult: { config: Config, path?: string }, // 用户配置
342
+ cliLevel?: string, // --level 参数
343
+ cliCacheDir?: string, // --cache-dir 参数
344
+ debug?: boolean, // --debug 参数
345
+ cliProjectTypeCheck?: boolean, // --no-project-type-check
346
+ cliTsconfig?: string, // --tsconfig 参数
347
+ cliIgnorePatterns: string[] = [], // --ignore 参数 (数组)
348
+ noIgnore: boolean = false // --no-ignore 参数
349
+ ): Promise<{ eslintPath: string; prettierPath: string; cachePath: string }>
350
+ ```
351
+
352
+ ##### 核心流程
353
+
354
+ 1. **参数优先级处理**: CLI 参数 > 配置文件 > 默认值
355
+ ```typescript
356
+ const projectTypeCheck = cliProjectTypeCheck ?? userConfigResult.config.projectTypeCheck ?? true;
357
+ const tsconfigPath = cliTsconfig ?? userConfigResult.config.tsconfig;
358
+ ```
359
+
360
+ 2. **智能缓存 (SHA-256 指纹)**:
361
+ ```typescript
362
+ const hash = createHash("sha256");
363
+ hash.update(pkg.version || "0.0.0");
364
+ hash.update(cliLevel || "default");
365
+ hash.update(projectTypeCheck ? "ptc-on" : "ptc-off");
366
+ hash.update(tsconfigPath || "default-tsconfig");
367
+ hash.update(cliIgnorePatterns.join(",") || "no-cli-ignore");
368
+ hash.update(noIgnore ? "no-ignore" : "with-ignore");
369
+ // + 用户配置文件内容 + .gitignore 内容
370
+ ```
371
+
372
+ 3. **忽略模式处理**:
373
+ - 若 `--no-ignore`,跳过所有忽略处理
374
+ - 否则:解析 `.gitignore` → 合并默认忽略 → 合并 CLI/Config 忽略
375
+ - 模式规范化:自动添加 `**/` 前缀和 `/**` 后缀
376
+
377
+ 4. **生成虚拟配置**: 动态生成 `eslint.config.mjs` 内容,包含:
378
+ - 忽略模式数组
379
+ - 用户配置加载逻辑
380
+ - level 对应的规则开关
381
+
259
382
  - **动态生成**: 我们不依赖用户项目里的 `.eslintrc`。相反,我们在运行时,在 `node_modules/.cache/rhine-lint/` 下生成一个真实的 `eslint.config.mjs`。
260
383
  - **TypeScript 配置编译 (TS Compilation)**: 如果检测到用户的配置文件是 `.ts` 格式:
261
384
  - 会自动调用内置的 TypeScript 编译器将其转译为 `.mjs` 模块。
@@ -267,22 +390,113 @@ graph TD
267
390
  - **JIT 加载**: 除了上述静态编译,对于部分模块加载我们使用 `jiti` 确保兼容性。
268
391
  - **关键点**: 这种设计使得 `rhine-lint` 内部的依赖(如 `eslint-plugin-react`)可以被正确解析,而不需要用户显式安装它们。
269
392
 
270
- #### 规则资产 (`src/assets/`)
271
- 这里存放了 Lint 规则的「源头」。
393
+ #### 规则资产 (`src/assets/eslint.config.js`)
394
+ 这里存放了 Lint 规则的「源头」。这是一个 **Factory Function**,导出 `createConfig(options)` 函数。
395
+
396
+ ##### OPTIONS 配置项
397
+
398
+ ```javascript
399
+ const OPTIONS = {
400
+ ENABLE_SCRIPT: true, // 启用 TS/JS 文件处理
401
+ ENABLE_TYPE_CHECKED: true, // 启用类型检查规则
402
+ ENABLE_PROJECT_BASE_TYPE_CHECKED: true, // 启用项目级类型检查 (projectService)
403
+ ENABLE_FRONTEND: true, // 启用 React/JSX 规则
404
+ ENABLE_NEXT: false, // 启用 Next.js 规则
405
+ ENABLE_MARKDOWN: true, // 启用 Markdown 规则
406
+ ENABLE_JSON: true, // 启用 JSON 规则
407
+ ENABLE_STYLESHEET: true, // 启用 CSS 规则
408
+ IGNORE_PRETTIER: true, // 禁用与 Prettier 冲突的规则
409
+ TSCONFIG_PATH: './tsconfig.json', // tsconfig 文件路径
410
+ ...overrides // 运行时覆盖
411
+ }
412
+ ```
413
+
414
+ ##### 配置块组装
415
+
416
+ ```javascript
417
+ return [
418
+ ...globalConfig, // 全局忽略配置
419
+ ...scriptConfig, // TS/JS 基础规则 + import-x + unused-imports
420
+ ...frontendConfig, // React/Next.js 规则 (按 level 条件加载)
421
+ ...cssConfig, // CSS 规则
422
+ ...markdownConfig, // Markdown 规则
423
+ ...jsonConfig, // JSON/JSONC 规则
424
+ ...prettierConfig, // eslint-config-prettier (禁用冲突规则)
425
+ ...customConfig, // 自定义规则覆盖
426
+ ]
427
+ ```
272
428
 
273
- - **`eslint.config.js`**: 这是一个 **Factory Function**。它导出一个 `createConfig(options)` 函数。
274
- - **Flat Config**: 采用了 ESLint v9 Flat Config 数组格式。
275
- - **按需加载**: 根据传入的 `options.level` (如 `frontend` 或 `nextjs`),它会动态 `push` 不同的配置块(Block)到数组中。例如,只有在 `nextjs` 模式下,才会加载 `@next/eslint-plugin-next` 相关规则。
276
- - **插件集成**: 所有插件(`react`, `import-x`, `unused-imports` 等)都在这里被引入并配置。
429
+ - **Flat Config**: 采用了 ESLint v9 的 Flat Config 数组格式。
430
+ - **按需加载**: 根据传入的 `options.level` (如 `frontend` `nextjs`),它会动态 `push` 不同的配置块(Block)到数组中。例如,只有在 `nextjs` 模式下,才会加载 `@next/eslint-plugin-next` 相关规则。
431
+ - **插件集成**: 所有插件(`react`, `import-x`, `unused-imports` 等)都在这里被引入并配置。
277
432
 
278
433
  #### 执行引擎 (`src/core/runner.ts`)
434
+
435
+ ##### 核心函数
436
+
437
+ ```typescript
438
+ // 通用命令执行
439
+ async function runCommandWithOutput(
440
+ command: string,
441
+ args: string[],
442
+ cwd: string
443
+ ): Promise<{ output: string, code: number }>
444
+
445
+ // ESLint 执行
446
+ async function runEslint(
447
+ cwd: string,
448
+ configPath: string, // 生成的虚拟配置路径
449
+ fix: boolean,
450
+ files: string[]
451
+ ): Promise<string | null> // 返回错误摘要或 null
452
+
453
+ // Prettier 执行
454
+ async function runPrettier(
455
+ cwd: string,
456
+ configPath: string,
457
+ fix: boolean,
458
+ files: string[]
459
+ ): Promise<string | null>
460
+ ```
461
+
462
+ ##### 二进制解析策略
463
+
464
+ ```typescript
465
+ function resolveBin(pkgName: string, binPathRelative: string): string {
466
+ // 1. 尝试 require.resolve (最快)
467
+ // 2. 回退:遍历目录找 package.json
468
+ // 3. 回退:使用系统 PATH
469
+ }
470
+ ```
471
+
279
472
  - **进程隔离**: 我们使用 Node.js 的 `child_process.spawn` 来调用 `eslint` 和 `prettier` 的可执行文件。
280
473
  - **为什么不使用 API?**:
281
474
  - 使用 API (如 `new ESLint()`) 可能会导致单例冲突,或者在某些边缘情况下与 CLI 行为不一致。
282
475
  - 通过 spawn 调用 CLI 能够最大程度保证兼容性,并且利用多核 CPU 并行运行 Lint 和 Prettier。
283
476
  - **输出清洗**: 原生的 ESLint 输出对于普通用户来说可能太过冗长。我们在这一层捕获 stdout/stderr,移除了 ANSI 乱码,并提取出关键的 "X problems found" 摘要信息,给用户最直观的反馈。
284
477
 
285
- ### 3. 开发指引 Development Guide
478
+ ### 3. 类型定义 (`src/config.ts`)
479
+
480
+ ```typescript
481
+ export type Config = {
482
+ type?: 'js' | 'ts' | 'frontend' | 'react' | 'nextjs',
483
+ cacheDir?: string,
484
+ fix?: boolean,
485
+ projectTypeCheck?: boolean, // 启用项目级类型检查 (default: true)
486
+ tsconfig?: string, // tsconfig 文件路径 (default: './tsconfig.json')
487
+ ignore?: string[], // 额外的忽略模式
488
+ eslint?: {
489
+ config?: [...], // ESLint Flat Config 数组
490
+ overlay?: boolean, // 覆盖模式
491
+ },
492
+ prettier?: {
493
+ config?: {...}, // Prettier 配置对象
494
+ overlay?: boolean,
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### 4. 开发指引 Development Guide
286
500
 
287
501
  如果你想为 Rhine Lint 添加新功能,请遵循以下路径:
288
502
 
@@ -302,7 +516,7 @@ graph TD
302
516
  - **Link**: 在本项目根目录运行 `npm link`,然后在测试项目运行 `npm link rhine-lint`。
303
517
  - **Watch**: 也可以使用 `bun run dev` (如果配置了) 或手动监听文件变化。
304
518
 
305
- ### 4. 目录结构
519
+ ### 5. 目录结构
306
520
 
307
521
  ```text
308
522
  rhine-lint/
@@ -321,6 +535,12 @@ rhine-lint/
321
535
 
322
536
  ---
323
537
 
538
+ ## LLM Prompt
539
+ 如果你是大模型,在执行任务时请注意以下事项。
540
+ 1. 包管理器使用bun。
541
+ 2. 项目所有输出打印的日志用英文,代码注释用中文。
542
+ 3. 所有内容要结构规范清晰,分工明确,最佳实践。
543
+
324
544
  ## License
325
545
 
326
546
  MIT © [RhineAI](https://github.com/RhineAI)
package/dist/cli.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  // import cac from "cac";
4
+ import path from "node:path";
4
5
  import { loadUserConfig, generateTempConfig, cleanup } from "./core/config.js";
5
6
  import { runEslint, runPrettier } from "./core/runner.js";
6
- import { logError, logSuccess, logInfo } from "./utils/logger.js";
7
+ import { logError, logSuccess, logInfo, logTime } from "./utils/logger.js";
7
8
  const require = createRequire(import.meta.url);
8
9
  const pkg = require('../package.json');
9
10
  const cac = require('cac');
@@ -19,20 +20,32 @@ cli
19
20
  .option("--ignore [pattern]", "Add ignore pattern (can be used multiple times)")
20
21
  .option("--no-ignore", "Disable all ignore rules (including .gitignore)")
21
22
  .option("--cache-dir <dir>", "Custom temporary cache directory")
23
+ .option("--time", "Show elapsed time for each phase")
22
24
  .option("--debug", "Enable debug mode")
23
25
  .action(async (files, options) => {
24
26
  const cwd = process.cwd();
25
27
  // If files is empty, default to "."
26
28
  const targetFiles = files.length > 0 ? files : ["."];
27
29
  let usedCachePath;
30
+ // 计时功能:从 CLI 选项或配置文件获取
31
+ let showTime = options.time ?? false;
32
+ const startTotal = Date.now();
33
+ let startPhase = startTotal;
28
34
  try {
29
35
  logInfo(`Starting Rhine Lint v${version}`);
30
36
  // 1. Load User Config
31
37
  // Note: We currently auto-detect config. explicit --config path support in loadUserConfig could be added if needed,
32
38
  // but loadConfig from jiti handles discovery well.
33
39
  const userConfigResult = await loadUserConfig(cwd);
40
+ // 从配置文件读取 time 选项(CLI 优先)
41
+ if (options.time === undefined && userConfigResult.config.time !== undefined) {
42
+ showTime = userConfigResult.config.time;
43
+ }
34
44
  if (userConfigResult.path) {
35
- logInfo(`Using config: ${userConfigResult.path}`);
45
+ // Show relative path if config is inside project
46
+ const relativePath = path.relative(cwd, userConfigResult.path);
47
+ const displayPath = relativePath.startsWith('..') ? userConfigResult.path : relativePath;
48
+ logInfo(`Using config: ${displayPath}`);
36
49
  }
37
50
  else {
38
51
  logInfo("Using default configuration");
@@ -49,11 +62,25 @@ cli
49
62
  }
50
63
  const temps = await generateTempConfig(cwd, userConfigResult, options.level, options.cacheDir, options.debug, options.projectTypeCheck, options.tsconfig, ignorePatterns, noIgnore);
51
64
  usedCachePath = temps.cachePath; // Save for cleanup
65
+ // 计时:第一阶段(准备阶段)
66
+ if (showTime) {
67
+ logTime("Preparation", Date.now() - startPhase);
68
+ startPhase = Date.now();
69
+ }
52
70
  // 3. Run ESLint
53
71
  const eslintResult = await runEslint(cwd, temps.eslintPath, options.fix, targetFiles);
72
+ // 计时:第二阶段(ESLint)
73
+ if (showTime) {
74
+ logTime("ESLint", Date.now() - startPhase);
75
+ startPhase = Date.now();
76
+ }
54
77
  console.log();
55
78
  // 4. Run Prettier
56
79
  const prettierResult = await runPrettier(cwd, temps.prettierPath, options.fix, targetFiles);
80
+ // 计时:第三阶段(Prettier)
81
+ if (showTime) {
82
+ logTime("Prettier", Date.now() - startPhase);
83
+ }
57
84
  console.log();
58
85
  if (eslintResult || prettierResult) {
59
86
  logError("Linting completed with issues:");
package/dist/config.d.ts CHANGED
@@ -2,6 +2,12 @@ export type Config = {
2
2
  type?: 'js' | 'ts' | 'frontend' | 'react' | 'nextjs';
3
3
  cacheDir?: string;
4
4
  fix?: boolean;
5
+ /**
6
+ * Enable timing output for each phase.
7
+ * Shows elapsed time for: preparation, ESLint, and Prettier phases.
8
+ * @default false
9
+ */
10
+ time?: boolean;
5
11
  /**
6
12
  * Enable project-based type checking with tsconfig.
7
13
  * This enables `projectService` and `strictTypeChecked` rules.
@@ -1,7 +1,7 @@
1
1
  import { createJiti } from "jiti";
2
2
  import path from "node:path";
3
3
  import fs from "fs-extra";
4
- import { logger, logInfo } from "../utils/logger.js";
4
+ import { logger } from "../utils/logger.js";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { createRequire } from "node:module";
7
7
  import { createHash } from "node:crypto";
@@ -40,7 +40,6 @@ export async function loadUserConfig(cwd) {
40
40
  try {
41
41
  const configModule = jiti(configPath);
42
42
  const config = configModule.default || configModule;
43
- logInfo(`Using config file: ${configPath}`);
44
43
  return { config, path: configPath };
45
44
  }
46
45
  catch (e) {
@@ -49,7 +48,6 @@ export async function loadUserConfig(cwd) {
49
48
  }
50
49
  }
51
50
  }
52
- logInfo("No config file found, using default configuration.");
53
51
  return { config: {} };
54
52
  }
55
53
  export async function generateTempConfig(cwd, userConfigResult, cliLevel, cliCacheDir, debug, cliProjectTypeCheck, cliTsconfig, cliIgnorePatterns = [], noIgnore = false) {
@@ -3,3 +3,9 @@ export declare function logInfo(message: string): void;
3
3
  export declare function logSuccess(message: string): void;
4
4
  export declare function logWarn(message: string): void;
5
5
  export declare function logError(message: string, error?: unknown): void;
6
+ /**
7
+ * 输出灰色的时间信息
8
+ * @param phase - 阶段名称
9
+ * @param elapsedMs - 耗时(毫秒)
10
+ */
11
+ export declare function logTime(phase: string, elapsedMs: number): void;
@@ -1,6 +1,6 @@
1
1
  import { createConsola } from "consola";
2
2
  import { colors } from "consola/utils";
3
- const { cyan, red, green, yellow } = colors;
3
+ const { cyan, red, green, yellow, gray } = colors;
4
4
  // Custom logger to satisfy "RL" prefix requirement
5
5
  const rLine = (colorFn, type, msg) => {
6
6
  console.log(`${colorFn("RL")} ${msg}`);
@@ -27,3 +27,14 @@ export function logError(message, error) {
27
27
  console.error(`${red("RL")} ${message}`);
28
28
  }
29
29
  }
30
+ /**
31
+ * 输出灰色的时间信息
32
+ * @param phase - 阶段名称
33
+ * @param elapsedMs - 耗时(毫秒)
34
+ */
35
+ export function logTime(phase, elapsedMs) {
36
+ const formatted = elapsedMs >= 1000
37
+ ? `${(elapsedMs / 1000).toFixed(2)}s`
38
+ : `${elapsedMs}ms`;
39
+ console.log(gray(`[Time] ${phase}: ${formatted}`));
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhine-lint",
3
- "version": "1.3.6",
3
+ "version": "1.4.0",
4
4
  "module": "./dist/index.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",