rhine-lint 1.0.1 → 1.3.2

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
@@ -6,25 +6,22 @@
6
6
  <img src="https://img.shields.io/badge/style-opinionated-blue?style=flat-square" alt="style" />
7
7
  </p>
8
8
 
9
- > **现在的 Web 开发中,配置 ESLint、Prettier、TypeScript 以及各种插件(React, Next.js, CSS, Markdown...)往往是一场噩梦。**
10
- > 重复的样板代码、版本冲突、复杂的 Flat Config 迁移... **Rhine Lint** 旨在结束这一切。
11
-
12
9
  **Rhine Lint** 是一个「零配置」的现代化代码规范解决方案。它深度整合了 **ESLint (v9 Flat Config)** 与 **Prettier**,为你提供开箱即用的最佳实践。你无需再手动安装数十个 `eslint-plugin-*` 依赖,也无需编写数百行的配置文件。只需一个依赖,一行命令,即可获得顶级的代码质量守护。
13
10
 
14
- ## 特性 (Features)
11
+ ## 特性 Features
15
12
 
16
- - **⚡️ 零配置启动 (Zero Config)**: 默认提供适用于 TypeScript、React、Next.js 的最佳实践配置,安装即用。
17
- - **🛠️ 统一工具链 (Unified Toolchain)**: 一个 `rl` 命令同时执行代码检查 (Lint) 和代码格式化 (Format)。
18
- - **🏗️ 全栈支持 (Full Stack)**:
13
+ - **零配置启动 Zero Config**: 默认提供适用于 TypeScript、React、Next.js 的最佳实践配置,安装即用。
14
+ - **统一工具链 Unified Toolchain**: 一个 `rl` 命令同时执行代码检查 (Lint) 和代码格式化 (Format)。
15
+ - **全栈支持 Full Stack**:
19
16
  - **JavaScript / TypeScript**: 完整的类型检查支持。
20
17
  - **Frontend**: React (v18/v19), React Hooks, JSX A11y.
21
18
  - **Frameworks**: Next.js (Pages & App Router).
22
19
  - **Styles**: CSS, SCSS format supports.
23
20
  - **Others**: JSON, Markdown support.
24
- - **🔧 智能配置生成 (Smart Config)**: 运行时动态生成配置文件,无需担心 ESLint/Prettier 配置文件污染项目根目录。
25
- - **🧩 灵活扩展 (Extensible)**: 支持 `rhine-lint.config.ts` 进行规则覆盖或深度定制。
21
+ - **智能配置生成 Smart Config**: 运行时动态生成配置文件,无需担心 ESLint/Prettier 配置文件污染项目根目录。
22
+ - **灵活扩展 Extensible**: 支持 `rhine-lint.config.ts` 进行规则覆盖或深度定制。
26
23
 
27
- ## 📦 安装 (Installation)
24
+ ## 安装 Installation
28
25
 
29
26
  在你的项目中作为开发依赖安装:
30
27
 
@@ -42,7 +39,7 @@ pnpm add -D rhine-lint
42
39
  yarn add -D rhine-lint
43
40
  ```
44
41
 
45
- ## 🚀 快速开始 (Quick Start)
42
+ ## 快速开始 Quick Start
46
43
 
47
44
  ### 命令行使用 (CLI)
48
45
 
@@ -75,7 +72,7 @@ npx rl --level nextjs
75
72
  }
76
73
  ```
77
74
 
78
- ## ⚙️ 配置 (Configuration)
75
+ ## 配置 Configuration
79
76
 
80
77
  虽然 Rhine Lint 是零配置的,但也支持通过配置文件进行深度定制。它会自动检测项目根目录下的 `rhine-lint.config.{ts,js,mjs,json}`。
81
78
 
@@ -90,7 +87,10 @@ export default {
90
87
  level: 'nextjs',
91
88
 
92
89
  // 是否默认开启修复模式 (可选)
93
- fix: false,
90
+ fix: false,
91
+
92
+ // 自定义缓存目录 (可选)
93
+ // cacheDir: './.cache/rhine-lint',
94
94
 
95
95
  // ESLint 专项配置
96
96
  eslint: {
@@ -110,6 +110,7 @@ export default {
110
110
  'react/no-unknown-property': 'off'
111
111
  }
112
112
  }
113
+ // ...
113
114
  ]
114
115
  },
115
116
 
@@ -117,7 +118,8 @@ export default {
117
118
  prettier: {
118
119
  config: {
119
120
  printWidth: 100,
120
- semi: true
121
+ semi: true,
122
+ // ...
121
123
  }
122
124
  }
123
125
  } as Config;
@@ -130,9 +132,24 @@ CLI 参数优先级高于配置文件:
130
132
  - `--fix`: 自动修复错误。
131
133
  - `--config <path>`: 指定配置文件路径。
132
134
  - `--level <level>`: 强制指定项目类型(`js`, `ts`, `frontend`, `nextjs`)。
135
+ - `--ignore <pattern>`: 添加忽略模式 (支持多次使用, e.g. `--ignore dist --ignore coverage`)。
136
+ - `--no-ignore`: 强制禁用所有忽略规则 (包括 .gitignore)。
137
+ - `--debug`: 打印调试信息(包括生成的配置、忽略列表等)。
133
138
  - `--cache-dir <dir>`: 指定缓存目录(默认使用 `node_modules/.cache/rhine-lint`)。
134
139
 
135
- ## 🔍 项目级别 (Project Levels)
140
+ ### 缓存目录 Cache Directory
141
+
142
+ Rhine Lint 需要一个目录来存放运行时动态生成的 "Virtual Config" 文件。这些文件是临时的,通常不需要用户关心。
143
+ 缓存目录的解析优先级如下(由高到低):
144
+
145
+ 1. **CLI 参数**: 命令行中显式指定 `--cache-dir <path>`。
146
+ 2. **配置文件**: `rhine-lint.config.ts` 中的 `cacheDir` 字段。
147
+ 3. **默认位置 (标准)**: `node_modules/.cache/rhine-lint`(如果项目中有 `node_modules` 目录)。
148
+ 4. **回退位置**: `.cache/rhine-lint`(如果找不到 `node_modules`,则在项目根目录下创建)。
149
+
150
+ > **注意**: 如果你的项目触发了第 4 种情况(回退位置),建议将 `.cache/` 添加到你的 `.gitignore` 文件中,以免这些临时文件被提交到版本库。正常情况下,Rhine Lint 会在执行结束后尝试清理这些临时文件,但保留在 `.gitignore` 中是更安全的做法。
151
+
152
+ ## 项目级别 Project Levels
136
153
 
137
154
  Rhine Lint 根据 `level` 参数加载不同的规则集:
138
155
 
@@ -141,48 +158,114 @@ Rhine Lint 根据 `level` 参数加载不同的规则集:
141
158
  - **`frontend`** (默认): 前端 React 项目。包含 `ts` 级别所有规则,加上 `React`, `React Hooks`, `JSX` 相关规则。
142
159
  - **`nextjs`**: Next.js 项目。包含 `frontend` 级别所有规则,加上 `@next/eslint-plugin-next` 的 Core Web Vitals 等规则。
143
160
 
144
- ## 🧠 技术实现与原理 (Implementation Details)
161
+ ## 技术实现与原理 Implementation Insights
145
162
 
146
- Rhine Lint 不仅仅是一个简单的 ESLint 配置包,它是一个 **Linter Orchestrator (检查器编排工具)**。以下是其内部工作流程,帮助理解它是如何保持项目清洁的。
163
+ 本章节详细阐述 **Rhine Lint** 的内部工作机制。如果你希望为本项目贡献代码,或者想深度定制功能,可以通过以下内容快速上手。
147
164
 
148
- ### 1. 动态配置生成 (Dynamic Configuration Generation)
149
- 传统的 ESLint 配置共享方式通常要求用户在项目中创建一个 `eslint.config.js` 并 `extend` 一个包。Rhine Lint 采用了不同的策略:**(Virtual Configuration)**。
165
+ Rhine Lint 的核心本质是一个 **Configuration Factory (配置工厂)** **Execution Orchestrator (执行编排器)**。它并没有重写 Linter,而是站在巨人的肩膀上(ESLint & Prettier),通过一层薄封装来解决配置复杂性问题。
150
166
 
151
- 当你运行 `rl` 时:
152
- 1. **读取配置**: 它首先读取用户的 `rhine-lint.config.ts`。
153
- 2. **生成临时配置**: 在缓存目录(如 `node_modules/.cache/rhine-lint/`)中,它会基于内存中的逻辑动态生成真实的 `eslint.config.mjs` 和 `prettier.config.mjs` 文件。
154
- - 这个过程将 `rhine-lint` 内部预设的规则与用户的自定义规则进行合并。
155
- - 它自动处理了 `tsconfig.json` 的路径解析、Ignore 文件的合并等复杂逻辑。
156
- 3. **环境隔离**: 这种方式确保了你的项目根目录不会被各种工具的配置文件弄乱。
167
+ ### 1. 核心架构 Core Architecture
157
168
 
158
- ### 2. 执行流程 (Execution Flow)
159
- Rhine Lint 实际上是 ESLint 和 Prettier 之上的一个 **Wrapper**:
169
+ 整个执行流程可以分为三个阶段:**初始化 (Init)** -> **生成 (Generate)** -> **执行 (Execute)**。
160
170
 
161
171
  ```mermaid
162
- graph LR
163
- User[用户执行 rl] --> CLI[CLI Parser (cac)]
164
- CLI --> Config[Config Loader]
165
- Config --> Gen[Config Generator]
166
- Gen --> Cache[写入临时 Config (.cache/)]
172
+ graph TD
173
+ CLI[src/cli.ts] -->|解析参数| ConfigMgr[src/core/config.ts]
174
+
175
+ subgraph Configuration Phase
176
+ ConfigMgr -->|1. 读取用户配置| UserConfig[rhine-lint.config.ts]
177
+ ConfigMgr -->|2. 读取内置模板| Assets[src/assets/*.js]
178
+ ConfigMgr -->|3. 合并与编译| VirtualConfig[生成临时 Config\n.cache/rhine-lint/*.mjs]
179
+ end
167
180
 
168
- Cache --> AsyncRun{并发执行}
169
- AsyncRun --> ESLint[Spawn: ESLint]
170
- AsyncRun --> Prettier[Spawn: Prettier]
181
+ subgraph Execution Phase
182
+ CLI --> Executor[src/core/runner.ts]
183
+ Executor -->|Spawn Process| ESLint[(ESLint Binary)]
184
+ Executor -->|Spawn Process| Prettier[(Prettier Binary)]
185
+ ESLint -.->|读取| VirtualConfig
186
+ Prettier -.->|读取| VirtualConfig
187
+ end
171
188
 
172
- ESLint --> Output[输出结果]
173
- Prettier --> Output
189
+ ESLint -->|Output| Formatter[结果清洗与展示]
190
+ Prettier -->|Output| Formatter
174
191
  ```
175
192
 
176
- - **ESLint 执行**: 调用 `eslint` 二进制文件,指向生成的临时配置文件。利用 ESLint v9 的 Flat Config 系统,实现了极快的文件匹配和规则计算。
177
- - **Prettier 执行**: 调用 `prettier` 二进制文件,同样指向临时配置文件。
178
- - **结果聚合**: `rl` 会捕获子进程的输出流,进行清洗和格式化,最终以统一的格式呈现给用户。如果任一工具报错,`rl` 也会以非零状态码退出,确保 CI/CD 流程的正确性。
179
-
180
- ### 3. 技术栈 (Tech Stack)
181
- - **Runtime**: Node.js (支持 ESM).
182
- - **ESLint v9**: 全面拥抱 Flat Config,不再支持旧版 `.eslintrc`。
183
- - **Prettier**: 强固的代码格式化。
184
- - **TypeScript-ESLint**: 最新的 TS 解析器和规则插件。
185
- - **Core Plugins**: 集成了 `eslint-plugin-react`, `eslint-plugin-react-hooks`, `@next/eslint-plugin-next`, `eslint-plugin-import-x`, `eslint-plugin-unused-imports`, `@eslint/markdown`, `@eslint/css` 等数十个核心插件。
193
+ ### 2. 模块详解 Module Deep Dive
194
+
195
+ #### CLI 入口 (`src/cli.ts`)
196
+ - **职责**: 程序的入口点。
197
+ - **实现**: 使用 `cac` 库处理命令行参数(如 `--fix`, `--level`)。
198
+ - **逻辑**:
199
+ 1. 它不会直接调用 ESLint API,而是准备好环境路径。
200
+ 2. 调用 `generateTempConfig` 准备配置文件。
201
+ 3. 调用 `runEslint` `runPrettier` 启动子进程。
202
+ 4. 最终根据子进程的 exit code 决定 `rl` 命令是成功还是失败。
203
+
204
+ #### 配置生成器 (`src/core/config.ts`) 🔥核心
205
+ 这是项目最复杂的部分。为了实现「零配置」且不污染用户目录,我们采用 **虚拟配置 (Virtual Configuration)** 策略。
206
+
207
+ - **动态生成**: 我们不依赖用户项目里的 `.eslintrc`。相反,我们在运行时,在 `node_modules/.cache/rhine-lint/` 下生成一个真实的 `eslint.config.mjs`。
208
+ - **TypeScript 配置编译 (TS Compilation)**: 如果检测到用户的配置文件是 `.ts` 格式:
209
+ - 会自动调用内置的 TypeScript 编译器将其转译为 `.mjs` 模块。
210
+ - 转译后的文件被保存在缓存目录(如 `.cache/rhine-lint/rhine-lint.user-config.mjs`)。
211
+ - 生成的 ESLint 配置会指向这个编译后的 JS 文件,从而解决 Node.js 原生无法加载 TS 文件的限制。
212
+ - **智能缓存 (Smart Caching)**: 为了提高性能(尤其是 IDE 保存自动修复时),我们实现了一套基于指纹的缓存机制:
213
+ - **指纹计算**: 每次运行前会计算一个 SHA-256 哈希,包含:`package.json` 版本 + CLI 参数 + 用户配置文件内容 + `.gitignore` 状态。
214
+ - **极速命中**: 如果指纹与缓存的 `metadata.json` 匹配,则**完全跳过**繁重的转译、合并和文件写入操作,直接复用上次的配置。
215
+ - **JIT 加载**: 除了上述静态编译,对于部分模块加载我们使用 `jiti` 确保兼容性。
216
+ - **关键点**: 这种设计使得 `rhine-lint` 内部的依赖(如 `eslint-plugin-react`)可以被正确解析,而不需要用户显式安装它们。
217
+
218
+ #### 规则资产 (`src/assets/`)
219
+ 这里存放了 Lint 规则的「源头」。
220
+
221
+ - **`eslint.config.js`**: 这是一个 **Factory Function**。它导出一个 `createConfig(options)` 函数。
222
+ - **Flat Config**: 采用了 ESLint v9 的 Flat Config 数组格式。
223
+ - **按需加载**: 根据传入的 `options.level` (如 `frontend` 或 `nextjs`),它会动态 `push` 不同的配置块(Block)到数组中。例如,只有在 `nextjs` 模式下,才会加载 `@next/eslint-plugin-next` 相关规则。
224
+ - **插件集成**: 所有插件(`react`, `import-x`, `unused-imports` 等)都在这里被引入并配置。
225
+
226
+ #### 执行引擎 (`src/core/runner.ts`)
227
+ - **进程隔离**: 我们使用 Node.js 的 `child_process.spawn` 来调用 `eslint` 和 `prettier` 的可执行文件。
228
+ - **为什么不使用 API?**:
229
+ - 使用 API (如 `new ESLint()`) 可能会导致单例冲突,或者在某些边缘情况下与 CLI 行为不一致。
230
+ - 通过 spawn 调用 CLI 能够最大程度保证兼容性,并且利用多核 CPU 并行运行 Lint 和 Prettier。
231
+ - **输出清洗**: 原生的 ESLint 输出对于普通用户来说可能太过冗长。我们在这一层捕获 stdout/stderr,移除了 ANSI 乱码,并提取出关键的 "X problems found" 摘要信息,给用户最直观的反馈。
232
+
233
+ ### 3. 开发指引 Development Guide
234
+
235
+ 如果你想为 Rhine Lint 添加新功能,请遵循以下路径:
236
+
237
+ #### 添加一个新的 ESLint 插件
238
+ 1. **安装依赖**: 在 `rhine-lint` 项目中安装插件,例如 `bun add eslint-plugin-vue`。
239
+ 2. **注册插件**: 修改 `src/assets/eslint.config.js`。
240
+ - 导入插件。
241
+ - 在 `createConfig` 函数中,添加新的逻辑分支(例如 `if (OPTIONS.ENABLE_VUE) { ... }`)。
242
+ - 定义好 `plugins` 和 `rules`。
243
+ 3. **更新类型**: 在 `src/config.ts` 的 `Config` 类型定义中添加新的 Scope 开关。
244
+ 4. **测试**: 在 `playground` 目录中创建一个 Vue 文件,运行 `bun start --level vue` (假设你添加了 vue level) 进行验证。
245
+
246
+ #### 调试 (Debugging)
247
+ 本项目完全使用 TypeScript 编写。
248
+
249
+ - **Build**: `bun run build` (使用 `tsc` 编译到 `dist/`)。
250
+ - **Link**: 在本项目根目录运行 `npm link`,然后在测试项目运行 `npm link rhine-lint`。
251
+ - **Watch**: 也可以使用 `bun run dev` (如果配置了) 或手动监听文件变化。
252
+
253
+ ### 4. 目录结构
254
+
255
+ ```text
256
+ rhine-lint/
257
+ ├── src/
258
+ │ ├── assets/ # 默认的配置文件模板 (ESLint/Prettier)
259
+ │ ├── core/
260
+ │ │ ├── config.ts # 配置加载与临时文件生成逻辑
261
+ │ │ └── runner.ts # 子进程执行器
262
+ │ ├── utils/ # 工具函数 (Logger 等)
263
+ │ ├── cli.ts # 命令行入口
264
+ │ ├── config.ts # 类型定义
265
+ │ └── index.ts # 导出给用户的 API
266
+ ├── scripts/ # 构建脚本
267
+ └── package.json
268
+ ```
186
269
 
187
270
  ---
188
271
 
@@ -4,7 +4,6 @@ import { fileURLToPath } from 'node:url'
4
4
  import nextPlugin from '@next/eslint-plugin-next'
5
5
  import reactPlugin from 'eslint-plugin-react';
6
6
  import reactHooksPlugin from 'eslint-plugin-react-hooks';
7
- import { fixupConfigRules } from '@eslint/compat'
8
7
 
9
8
  import css from '@eslint/css'
10
9
 
@@ -15,7 +14,6 @@ import css from '@eslint/css'
15
14
  // ...
16
15
 
17
16
 
18
- import { FlatCompat } from '@eslint/eslintrc'
19
17
  import js from '@eslint/js'
20
18
  import json from '@eslint/json'
21
19
  import markdown from '@eslint/markdown'
@@ -29,9 +27,6 @@ import tseslint from 'typescript-eslint'
29
27
 
30
28
  const __filename = fileURLToPath(import.meta.url)
31
29
  const __dirname = path.dirname(__filename)
32
- const compat = new FlatCompat({
33
- baseDirectory: __dirname,
34
- })
35
30
 
36
31
  const globalConfig = defineConfig([
37
32
  {
@@ -50,7 +45,7 @@ export default function createConfig(overrides = {}) {
50
45
  const OPTIONS = {
51
46
  ENABLE_SCRIPT: true, // Set to enable typescript javascript file features
52
47
  ENABLE_TYPE_CHECKED: true, // Set to enable type features
53
- ENABLE_PROJECT_BASE_TYPE_CHECKED: false, // Set to enable project-based type features
48
+ ENABLE_PROJECT_BASE_TYPE_CHECKED: true, // Set to enable project-based type features
54
49
  ENABLE_FRONTEND: true, // Set to enable JSX, React, Reacts Hooks, and other frontend features
55
50
  ENABLE_NEXT: false, // Set to enable Next.js and other frontend features
56
51
  ENABLE_MARKDOWN: true, // Set to enable markdown file features
package/dist/cli.js CHANGED
@@ -14,7 +14,9 @@ cli
14
14
  .option("--fix", "Fix lint errors")
15
15
  .option("--config <path>", "Path to config file")
16
16
  .option("--level <level>", "Project level (js, ts, frontend, nextjs)")
17
+ .option("--no-project-type-check", "Disable project-based type checking (faster for single files)")
17
18
  .option("--cache-dir <dir>", "Custom temporary cache directory")
19
+ .option("--debug", "Enable debug mode")
18
20
  .action(async (files, options) => {
19
21
  const cwd = process.cwd();
20
22
  // If files is empty, default to "."
@@ -34,7 +36,7 @@ cli
34
36
  }
35
37
  console.log();
36
38
  // 2. Generate Temp Configs
37
- const temps = await generateTempConfig(cwd, userConfigResult, options.level, options.cacheDir);
39
+ const temps = await generateTempConfig(cwd, userConfigResult, options.level, options.cacheDir, options.debug, options.projectTypeCheck);
38
40
  usedCachePath = temps.cachePath; // Save for cleanup
39
41
  // 3. Run ESLint
40
42
  const eslintResult = await runEslint(cwd, temps.eslintPath, options.fix, targetFiles);
package/dist/config.d.ts CHANGED
@@ -6,6 +6,14 @@ export type Config = {
6
6
  config?: [
7
7
  ];
8
8
  overlay?: boolean;
9
+ /**
10
+ * Enable project-based type checking with tsconfig.
11
+ * This enables `projectService` and `strictTypeChecked` rules.
12
+ * Slower but more accurate type-aware linting.
13
+ * Set to `false` to disable for faster single-file processing.
14
+ * @default true
15
+ */
16
+ projectTypeCheck?: boolean;
9
17
  };
10
18
  prettier?: {
11
19
  config?: {};
@@ -6,7 +6,7 @@ export declare function loadUserConfig(cwd: string): Promise<{
6
6
  export declare function generateTempConfig(cwd: string, userConfigResult: {
7
7
  config: Config;
8
8
  path?: string;
9
- }, cliLevel?: string, cliCacheDir?: string): Promise<{
9
+ }, cliLevel?: string, cliCacheDir?: string, debug?: boolean, cliProjectTypeCheck?: boolean): Promise<{
10
10
  eslintPath: string;
11
11
  prettierPath: string;
12
12
  cachePath: string;
@@ -1,9 +1,12 @@
1
+ import { createJiti } from "jiti";
1
2
  import path from "node:path";
2
3
  import fs from "fs-extra";
3
- import { logger } from "../utils/logger.js";
4
+ import { logger, logInfo } from "../utils/logger.js";
4
5
  import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import { createRequire } from "node:module";
7
+ import { createHash } from "node:crypto";
6
8
  const require = createRequire(import.meta.url);
9
+ const pkg = require('../../package.json');
7
10
  function getAssetPath(filename) {
8
11
  return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../assets", filename);
9
12
  }
@@ -19,80 +22,198 @@ const CONFIG_FILENAMES = [
19
22
  "config/rhine-lint.config.cjs",
20
23
  "config/rhine-lint.config.json",
21
24
  ];
25
+ function getCacheDir(cwd, userConfig, cliCacheDir) {
26
+ if (cliCacheDir)
27
+ return path.resolve(cwd, cliCacheDir);
28
+ if (userConfig?.cacheDir)
29
+ return path.resolve(cwd, userConfig.cacheDir);
30
+ const nodeModulesPath = path.join(cwd, "node_modules");
31
+ if (fs.existsSync(nodeModulesPath))
32
+ return path.join(nodeModulesPath, ".cache", "rhine-lint");
33
+ return path.join(cwd, ".cache", "rhine-lint");
34
+ }
22
35
  export async function loadUserConfig(cwd) {
23
- let configPath;
24
- for (const file of CONFIG_FILENAMES) {
25
- const p = path.join(cwd, file);
26
- if (await fs.pathExists(p)) {
27
- configPath = p;
28
- break;
36
+ const jiti = createJiti(fileURLToPath(import.meta.url));
37
+ for (const filename of CONFIG_FILENAMES) {
38
+ const configPath = path.join(cwd, filename);
39
+ if (await fs.pathExists(configPath)) {
40
+ try {
41
+ const configModule = jiti(configPath);
42
+ const config = configModule.default || configModule;
43
+ logInfo(`Using config file: ${configPath}`);
44
+ return { config, path: configPath };
45
+ }
46
+ catch (e) {
47
+ logger.error(`Failed to load config file ${configPath}:`, e);
48
+ process.exit(1);
49
+ }
29
50
  }
30
51
  }
31
- if (!configPath) {
32
- return { config: {} };
33
- }
52
+ logInfo("No config file found, using default configuration.");
53
+ return { config: {} };
54
+ }
55
+ export async function generateTempConfig(cwd, userConfigResult, cliLevel, cliCacheDir, debug, cliProjectTypeCheck) {
56
+ const cachePath = getCacheDir(cwd, userConfigResult.config, cliCacheDir);
57
+ await fs.ensureDir(cachePath);
58
+ const eslintTempPath = path.join(cachePath, "eslint.config.mjs");
59
+ const prettierTempPath = path.join(cachePath, "prettier.config.mjs");
60
+ const metaPath = path.join(cachePath, "metadata.json");
61
+ const gitignorePath = path.join(cwd, ".gitignore");
62
+ // Determine projectTypeCheck: CLI flag takes precedence over config file, default is true
63
+ // When --no-project-type-check is used, options.projectTypeCheck will be false
64
+ const projectTypeCheck = cliProjectTypeCheck ?? userConfigResult.config.eslint?.projectTypeCheck ?? true;
65
+ let calculatedHash = "";
34
66
  try {
35
- // logInfo(`Loading config from ${configPath}`);
36
- const isJson = configPath.endsWith(".json");
37
- let loaded;
38
- if (isJson) {
39
- loaded = await fs.readJson(configPath);
67
+ const hash = createHash("sha256");
68
+ hash.update(pkg.version || "0.0.0");
69
+ hash.update(cliLevel || "default");
70
+ hash.update(projectTypeCheck ? "ptc-on" : "ptc-off");
71
+ if (userConfigResult.path && await fs.pathExists(userConfigResult.path)) {
72
+ const content = await fs.readFile(userConfigResult.path);
73
+ hash.update(content);
74
+ }
75
+ if (await fs.pathExists(gitignorePath)) {
76
+ const content = await fs.readFile(gitignorePath);
77
+ hash.update(content);
40
78
  }
41
79
  else {
42
- const importUrl = pathToFileURL(configPath).href;
43
- const mod = await import(importUrl);
44
- loaded = mod.default || mod;
80
+ hash.update("no-gitignore");
81
+ }
82
+ calculatedHash = hash.digest("hex");
83
+ if (await fs.pathExists(metaPath)) {
84
+ const meta = await fs.readJson(metaPath);
85
+ if (meta.hash === calculatedHash && await fs.pathExists(eslintTempPath) && await fs.pathExists(prettierTempPath)) {
86
+ logger.debug(`Cache hit! Configs reused from ${cachePath}`);
87
+ return { eslintPath: eslintTempPath, prettierPath: prettierTempPath, cachePath };
88
+ }
45
89
  }
46
- return {
47
- config: loaded || {},
48
- path: configPath
49
- };
50
90
  }
51
91
  catch (e) {
52
- logger.debug("Failed to load user config.", e);
53
- logger.warn(`Found config file at ${configPath} but failed to load it.`);
54
- return { config: {} };
92
+ logger.debug("Cache check failed, regenerating...", e);
55
93
  }
56
- }
57
- function getCacheDir(cwd, userConfig, cliCacheDir) {
58
- // 1. CLI option
59
- if (cliCacheDir) {
60
- return path.resolve(cwd, cliCacheDir);
94
+ let ignoredPatterns = [];
95
+ // Parse .gitignore file and convert to ESLint ignore patterns
96
+ // ESLint ignores are relative to the cwd where ESLint runs (which is the project root)
97
+ // NOT relative to the config file location
98
+ const parseGitignore = (content) => {
99
+ const patterns = [];
100
+ const lines = content.split('\n');
101
+ for (let line of lines) {
102
+ // Remove Windows line endings and trim
103
+ line = line.replace(/\r$/, '').trim();
104
+ // Skip empty lines and comments
105
+ if (!line || line.startsWith('#'))
106
+ continue;
107
+ // Handle negation (un-ignore)
108
+ const isNegation = line.startsWith('!');
109
+ if (isNegation) {
110
+ line = line.slice(1);
111
+ }
112
+ // Determine if pattern is rooted (starts with /)
113
+ const isRooted = line.startsWith('/');
114
+ if (isRooted) {
115
+ line = line.slice(1);
116
+ }
117
+ // Normalize path separators
118
+ line = line.replace(/\\/g, '/');
119
+ // Handle directory patterns (ends with /)
120
+ const isDir = line.endsWith('/');
121
+ if (isDir) {
122
+ line = line.slice(0, -1);
123
+ }
124
+ // Build the ESLint ignore pattern
125
+ // Patterns are relative to the cwd where ESLint runs (project root)
126
+ let pattern;
127
+ if (isRooted) {
128
+ // Rooted patterns: only match at project root
129
+ // e.g., /node_modules -> node_modules/**
130
+ pattern = line;
131
+ }
132
+ else {
133
+ // Non-rooted patterns: match anywhere in the tree
134
+ // e.g., .next -> **/.next/**
135
+ pattern = `**/${line}`;
136
+ }
137
+ // Add /** suffix for directories and patterns that should match all contents
138
+ if (isDir || !line.includes('*')) {
139
+ // Check if it's likely a directory (no extension or common directory names)
140
+ const shouldMatchContents = isDir ||
141
+ !path.extname(line) ||
142
+ line.endsWith('.next') ||
143
+ line.endsWith('.git') ||
144
+ line.endsWith('.cache');
145
+ if (shouldMatchContents && !pattern.endsWith('/**')) {
146
+ pattern += '/**';
147
+ }
148
+ }
149
+ // Apply negation prefix for ESLint if this was an un-ignore pattern
150
+ if (isNegation) {
151
+ pattern = '!' + pattern;
152
+ }
153
+ patterns.push(pattern);
154
+ }
155
+ return patterns;
156
+ };
157
+ // Default directories to always ignore (relative to project root)
158
+ const defaultIgnores = [
159
+ 'node_modules', 'dist', '.next', '.git', '.output', '.nuxt', 'coverage', '.cache'
160
+ ].map(dir => `**/${dir}/**`);
161
+ if (await fs.pathExists(gitignorePath)) {
162
+ try {
163
+ const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
164
+ if (debug) {
165
+ console.log("-----------------------------------------");
166
+ console.log("DEBUG: .gitignore content preview:");
167
+ console.log(gitignoreContent.substring(0, 500));
168
+ console.log("-----------------------------------------");
169
+ }
170
+ const parsedPatterns = parseGitignore(gitignoreContent);
171
+ if (debug) {
172
+ console.log("-----------------------------------------");
173
+ console.log("DEBUG: Parsed gitignore patterns:");
174
+ parsedPatterns.forEach((p, i) => console.log(` [${i}] "${p}"`));
175
+ console.log("-----------------------------------------");
176
+ }
177
+ // Merge defaults with parsed patterns, removing duplicates
178
+ const allPatterns = [...defaultIgnores, ...parsedPatterns];
179
+ ignoredPatterns = [...new Set(allPatterns)];
180
+ }
181
+ catch (e) {
182
+ logger.debug("Failed to parse .gitignore", e);
183
+ ignoredPatterns = defaultIgnores;
184
+ }
61
185
  }
62
- // 2. Config option
63
- if (userConfig?.cacheDir) {
64
- return path.resolve(cwd, userConfig.cacheDir);
186
+ else {
187
+ ignoredPatterns = defaultIgnores;
65
188
  }
66
- // 3. Default: node_modules/.cache/rhine-lint
67
- const nodeModulesPath = path.join(cwd, "node_modules");
68
- if (fs.existsSync(nodeModulesPath)) {
69
- return path.join(nodeModulesPath, ".cache", "rhine-lint");
189
+ if (debug) {
190
+ console.log("-----------------------------------------");
191
+ console.log("DEBUG: Final Ignores List:");
192
+ ignoredPatterns.forEach(p => console.log(p));
193
+ console.log("-----------------------------------------");
70
194
  }
71
- // 4. Fallback: .cache/rhine-lint in root
72
- return path.join(cwd, ".cache", "rhine-lint");
73
- }
74
- export async function generateTempConfig(cwd, userConfigResult, cliLevel, cliCacheDir) {
75
- const cachePath = getCacheDir(cwd, userConfigResult.config, cliCacheDir);
76
- await fs.ensureDir(cachePath);
77
- const eslintTempPath = path.join(cachePath, "eslint.config.mjs");
78
- const prettierTempPath = path.join(cachePath, "prettier.config.mjs");
79
195
  const defaultEslintPath = pathToFileURL(getAssetPath("eslint.config.js")).href;
80
196
  const defaultPrettierPath = pathToFileURL(getAssetPath("prettier.config.js")).href;
81
197
  const userConfigUrl = userConfigResult.path ? pathToFileURL(userConfigResult.path).href : null;
82
198
  const defuUrl = pathToFileURL(require.resolve("defu")).href;
83
199
  const jitiUrl = pathToFileURL(require.resolve("jiti")).href;
84
- // Resolve @eslint/compat for includeIgnoreFile if needed
85
- // We assume @eslint/compat is installed as dependency of rhine-lint
86
- let compatUrl = null;
87
- try {
88
- compatUrl = pathToFileURL(require.resolve("@eslint/compat")).href;
89
- }
90
- catch (e) {
91
- // ignore
200
+ let finalUserConfigUrl = userConfigUrl;
201
+ if (userConfigResult.path && (userConfigResult.path.endsWith('.ts') || userConfigResult.path.endsWith('.mts') || userConfigResult.path.endsWith('.cts'))) {
202
+ try {
203
+ const ts = await import('typescript');
204
+ const fileContent = await fs.readFile(userConfigResult.path, 'utf-8');
205
+ const transpiled = ts.transpileModule(fileContent, {
206
+ compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ESNext }
207
+ });
208
+ const compiledName = 'rhine-lint.user-config.mjs';
209
+ const compiledPath = path.join(cachePath, compiledName);
210
+ await fs.writeFile(compiledPath, transpiled.outputText);
211
+ finalUserConfigUrl = pathToFileURL(compiledPath).href;
212
+ }
213
+ catch (e) {
214
+ logger.debug("Failed to transpile user config.", e);
215
+ }
92
216
  }
93
- const gitignorePath = path.join(cwd, ".gitignore");
94
- const hasGitignore = await fs.pathExists(gitignorePath);
95
- const gitignoreUrl = hasGitignore ? pathToFileURL(gitignorePath).href : null;
96
217
  const isEslintOverlay = userConfigResult.config.eslint?.overlay ?? false;
97
218
  const isPrettierOverlay = userConfigResult.config.prettier?.overlay ?? false;
98
219
  const eslintContent = `
@@ -101,36 +222,35 @@ import { fileURLToPath } from "node:url";
101
222
  import path from "node:path";
102
223
  import createConfig from "${defaultEslintPath}";
103
224
  import { defu } from "${defuUrl}";
104
- ${compatUrl && hasGitignore ? `import { includeIgnoreFile } from "${compatUrl}";` : ''}
105
-
106
225
  const __filename = fileURLToPath(import.meta.url);
107
226
  const __dirname = path.dirname(__filename);
108
227
  const jiti = createJiti(__filename);
109
228
 
110
- ${userConfigUrl ? `
111
- const loaded = await jiti.import("${userConfigUrl.replace('file:///', '').replace(/%20/g, ' ')}", { default: true });
229
+ ${finalUserConfigUrl ? `
230
+ const loaded = await jiti.import("${finalUserConfigUrl.replace('file:///', '').replace(/%20/g, ' ')}", { default: true });
112
231
  const userOne = loaded.default || loaded;
113
232
  ` : 'const userOne = {};'}
114
233
 
115
234
  const userEslint = userOne.eslint || {};
116
235
  const level = "${cliLevel || ''}" || userOne.level || "frontend";
117
236
 
118
- let overrides = {};
237
+ // Project-based type checking: CLI flag (${projectTypeCheck}) takes precedence over config file
238
+ const projectTypeCheck = ${projectTypeCheck} || userEslint.projectTypeCheck || false;
239
+
240
+ let overrides = { ENABLE_PROJECT_BASE_TYPE_CHECKED: projectTypeCheck };
119
241
  switch (level) {
120
- case "nextjs": overrides = { ENABLE_NEXT: true }; break;
121
- case "frontend": overrides = { ENABLE_FRONTEND: true, ENABLE_NEXT: false }; break;
122
- case "ts": overrides = { ENABLE_FRONTEND: false }; break;
123
- case "js": overrides = { ENABLE_FRONTEND: false, ENABLE_TYPE_CHECKED: false }; break;
242
+ case "nextjs": overrides = { ...overrides, ENABLE_NEXT: true }; break;
243
+ case "frontend": overrides = { ...overrides, ENABLE_FRONTEND: true, ENABLE_NEXT: false }; break;
244
+ case "ts": overrides = { ...overrides, ENABLE_FRONTEND: false }; break;
245
+ case "js": overrides = { ...overrides, ENABLE_FRONTEND: false, ENABLE_TYPE_CHECKED: false }; break;
124
246
  }
125
247
 
126
248
  const defaultEslint = createConfig(overrides);
127
-
128
249
  let finalConfig;
129
-
130
- // Prepend ignore file if exists
131
250
  const prefixConfig = [];
132
- ${compatUrl && hasGitignore && gitignoreUrl ? `
133
- prefixConfig.push(includeIgnoreFile("${gitignoreUrl.replace('file:///', '').replace(/%20/g, ' ')}"));
251
+
252
+ ${ignoredPatterns.length > 0 ? `
253
+ prefixConfig.push({ ignores: ${JSON.stringify(ignoredPatterns)} });
134
254
  ` : ''}
135
255
 
136
256
  if (${isEslintOverlay} || userEslint.overlay) {
@@ -138,7 +258,6 @@ if (${isEslintOverlay} || userEslint.overlay) {
138
258
  finalConfig = [...prefixConfig, ...defaultEslint, ...userEslint.config];
139
259
  } else {
140
260
  finalConfig = defu(userEslint, defaultEslint);
141
- // Note: merging object into array-based config is weird, assume flat config array concatenation for robustness
142
261
  if (!Array.isArray(finalConfig)) finalConfig = [...prefixConfig, finalConfig];
143
262
  }
144
263
  } else {
@@ -175,14 +294,11 @@ export default finalConfig;
175
294
  `;
176
295
  await fs.writeFile(eslintTempPath, eslintContent);
177
296
  await fs.writeFile(prettierTempPath, prettierContent);
297
+ await fs.writeJson(metaPath, { hash: calculatedHash, timestamp: Date.now() });
178
298
  return { eslintPath: eslintTempPath, prettierPath: prettierTempPath, cachePath };
179
299
  }
180
300
  export async function cleanup(cachePath) {
181
301
  if (cachePath && await fs.pathExists(cachePath)) {
182
- // Safety check: ensure we are deleting a temp dir we own
183
- // If it's node_modules/.cache/rhine-lint, delete it
184
- // If it's custom, delete it (careful?)
185
- // Generally standard to remove temp config dir.
186
- await fs.remove(cachePath);
302
+ // await fs.remove(cachePath);
187
303
  }
188
304
  }
@@ -1,38 +1,82 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import fs from "fs-extra";
2
5
  import { logger, logInfo, logError } from "../utils/logger.js";
3
- const IS_BUN = typeof process.versions.bun !== "undefined";
4
- const EXECUTOR = IS_BUN ? "bunx" : "npx";
5
- // Helper to strip ANSI codes for easier regex matching
6
+ const require = createRequire(import.meta.url);
6
7
  function stripAnsi(str) {
7
8
  return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
8
9
  }
10
+ /**
11
+ * Robustly resolve a binary path from a package, handling exports restrictions.
12
+ */
13
+ function resolveBin(pkgName, binPathRelative) {
14
+ // 1. Try strict resolve (fastest, but might be blocked by exports)
15
+ try {
16
+ return require.resolve(`${pkgName}/${binPathRelative}`);
17
+ }
18
+ catch (e) {
19
+ if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' && e.code !== 'MODULE_NOT_FOUND') {
20
+ logger.debug(`Resolve error for ${pkgName}/${binPathRelative}:`, e);
21
+ }
22
+ }
23
+ // 2. Fallback: Resolve main entry point, then traverse up to find package root
24
+ try {
25
+ const mainPath = require.resolve(pkgName);
26
+ let currentDir = path.dirname(mainPath);
27
+ // Traverse up (max 5 levels) to find package.json
28
+ for (let i = 0; i < 5; i++) {
29
+ const pkgJsonPath = path.join(currentDir, "package.json");
30
+ if (fs.existsSync(pkgJsonPath)) {
31
+ try {
32
+ const pkg = fs.readJsonSync(pkgJsonPath);
33
+ if (pkg.name === pkgName) {
34
+ // Found it! Construct bin path
35
+ const binPath = path.join(currentDir, binPathRelative);
36
+ if (fs.existsSync(binPath)) {
37
+ return binPath;
38
+ }
39
+ }
40
+ }
41
+ catch {
42
+ // Ignore parsing errors
43
+ }
44
+ }
45
+ if (currentDir === path.dirname(currentDir))
46
+ break; // Root reached
47
+ currentDir = path.dirname(currentDir);
48
+ }
49
+ }
50
+ catch (e) {
51
+ logger.debug(`Deep resolve failed for ${pkgName}:`, e);
52
+ }
53
+ // 3. Fallback to system PATH (bare command)
54
+ return pkgName;
55
+ }
9
56
  export async function runCommandWithOutput(command, args, cwd) {
10
57
  return new Promise((resolve, reject) => {
11
58
  logger.debug(`Executing: ${command} ${args.join(" ")}`);
12
59
  const proc = spawn(command, args, {
13
60
  cwd,
14
- stdio: ["inherit", "pipe", "pipe"], // Pipe stdout/stderr so we can read it
15
- shell: true,
61
+ stdio: ["inherit", "pipe", "pipe"],
62
+ shell: false,
16
63
  });
17
64
  let output = "";
18
65
  if (proc.stdout) {
19
66
  proc.stdout.on("data", (data) => {
20
- process.stdout.write(data); // Passthrough to console
67
+ process.stdout.write(data);
21
68
  output += data.toString();
22
69
  });
23
70
  }
24
71
  if (proc.stderr) {
25
72
  proc.stderr.on("data", (data) => {
26
- process.stderr.write(data); // Passthrough to console
73
+ process.stderr.write(data);
27
74
  output += data.toString();
28
75
  });
29
76
  }
30
77
  proc.on("close", (code) => {
31
- // Resolve all exit codes (even 1 or 2 or others) so we can parse output.
32
- // But we might want to differentiate "crash" vs "lint failure".
33
- // ESLint exit code 1 = user lint error. code 2 = config/crash error.
34
78
  if (code === null)
35
- code = 1; // Default to error if null
79
+ code = 1;
36
80
  resolve({ output, code });
37
81
  });
38
82
  proc.on("error", (err) => {
@@ -40,83 +84,75 @@ export async function runCommandWithOutput(command, args, cwd) {
40
84
  });
41
85
  });
42
86
  }
43
- // Return type: null means success (no errors), string means summary of errors/warnings
44
87
  export async function runEslint(cwd, configPath, fix, files = ["."]) {
45
88
  logInfo("Running ESLint...");
46
89
  console.log();
90
+ const eslintBin = resolveBin("eslint", "bin/eslint.js");
47
91
  const args = [
48
- "eslint",
92
+ eslintBin,
49
93
  ...files,
50
94
  "--config", configPath,
51
95
  ...(fix ? ["--fix"] : []),
52
96
  ];
53
97
  try {
54
- const { output, code } = await runCommandWithOutput(EXECUTOR, args, cwd);
55
- if (!output.endsWith('\n')) {
56
- console.log();
98
+ const { output, code } = await runCommandWithOutput(process.execPath, args, cwd);
99
+ if (code !== 0 && code !== 1) {
100
+ return `Process failed with exit code ${code}`;
57
101
  }
58
102
  const cleanOutput = stripAnsi(output);
59
- // Try to match standard ESLint summary: "✖ 5 problems (5 errors, 0 warnings)"
60
- const match = cleanOutput.match(/✖ (\d+) problems? \((\d+) errors?, (\d+) warnings?\)/);
61
- if (match) {
62
- return `${match[1]} problems (${match[2]} errors, ${match[3]} warnings)`;
63
- }
64
- // Check if there are errors but no summary line
65
- if (cleanOutput.includes("error") || cleanOutput.includes("warning")) {
66
- // Maybe custom format or specific error
67
- // Try to count occurences of "error"
68
- const errorCount = (cleanOutput.match(/error/gi) || []).length;
69
- if (errorCount > 0)
70
- return `${errorCount} issues found`;
71
- return "Issues found";
103
+ const problemMatch = cleanOutput.match(/(\d+) problems? \((\d+) errors?, (\d+) warnings?\)/);
104
+ if (problemMatch) {
105
+ return problemMatch[0];
72
106
  }
73
- // Key Fix: If exit code is non-zero and we haven't found a "summary" above, it's a crash or unparsed error.
74
- if (code !== 0) {
75
- return `Process failed with exit code ${code}`;
107
+ const errorMatch = cleanOutput.match(/(\d+) error/);
108
+ if (errorMatch) {
109
+ return `${errorMatch[0]} found`;
76
110
  }
77
- return null;
111
+ if (code === 0)
112
+ return null;
113
+ return "Issues found";
78
114
  }
79
115
  catch (e) {
80
- logError("ESLint execution failed.", e);
81
- throw e;
116
+ logError("Failed to run ESLint", e);
117
+ return e.message || "Unknown error";
82
118
  }
83
119
  }
84
120
  export async function runPrettier(cwd, configPath, fix, files = ["."]) {
85
121
  logInfo("Running Prettier...");
86
- console.log();
122
+ // Try Prettier v3 path first, then fallback (v3: bin/prettier.cjs, v2: bin-prettier.js)
123
+ let prettierBin = resolveBin("prettier", "bin/prettier.cjs");
124
+ // If resolved to "prettier" (PATH) or logic failed, check if file exists, if not try legacy
125
+ // Actually resolveBin returns bare "prettier" if not found.
126
+ // If it is just "prettier", we might want to try legacy path too before giving up.
127
+ if (prettierBin === "prettier" || !fs.existsSync(prettierBin)) {
128
+ const legacy = resolveBin("prettier", "bin-prettier.js");
129
+ if (legacy !== "prettier" && fs.existsSync(legacy)) {
130
+ prettierBin = legacy;
131
+ }
132
+ }
87
133
  const args = [
88
- "prettier",
89
- ...files,
90
- "--config", configPath,
134
+ prettierBin,
91
135
  ...(fix ? ["--write"] : ["--check"]),
136
+ "--config", configPath,
137
+ "--ignore-path", ".gitignore",
138
+ ...files
92
139
  ];
93
140
  try {
94
- const { output, code } = await runCommandWithOutput(EXECUTOR, args, cwd);
95
- if (!output.endsWith('\n')) {
96
- console.log();
97
- }
98
- const cleanOutput = stripAnsi(output);
99
- if (!fix) {
100
- // In check mode with issues: "Code style issues found in 2 files"
101
- const match = cleanOutput.match(/Code style issues found in (\d+) files?/);
102
- if (match) {
103
- return `${match[1]} unformatted files`;
104
- }
105
- if (cleanOutput.includes("[warn]")) {
106
- return "Style issues found";
107
- }
108
- }
109
- // Prettier specific: exit code 2 usually means error/crash. code 1 (in check mode) means unformatted.
141
+ const { output, code } = await runCommandWithOutput(process.execPath, args, cwd);
110
142
  if (code !== 0) {
111
- if (fix && code !== 0)
112
- return `Process failed with exit code ${code}`;
113
- // In check mode code 1 is covered above usually, but if fallback:
114
- return "Style issues found";
143
+ if (!fix) {
144
+ if (stripAnsi(output).includes("Code style issues found")) {
145
+ return "Code style issues found";
146
+ }
147
+ if (code === 1)
148
+ return "Formatting issues found";
149
+ }
150
+ return `Process failed with exit code ${code}`;
115
151
  }
116
152
  return null;
117
153
  }
118
154
  catch (e) {
119
- logError("Prettier execution failed.", e);
120
- throw e;
155
+ logError("Failed to run Prettier", e);
156
+ return e.message || "Unknown error";
121
157
  }
122
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhine-lint",
3
- "version": "1.0.1",
3
+ "version": "1.3.2",
4
4
  "module": "./dist/index.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,17 +34,18 @@
34
34
  "prepublishOnly": "npm run build"
35
35
  },
36
36
  "dependencies": {
37
- "@eslint/compat": "^1.3.2",
38
37
  "@eslint/css": "^0.10.0",
39
38
  "@eslint/eslintrc": "^3.3.1",
40
39
  "@eslint/json": "^0.13.1",
41
40
  "@eslint/markdown": "^7.1.0",
42
41
  "@next/eslint-plugin-next": "^16.1.1",
42
+ "ajv": "^8.12.0",
43
43
  "cac": "^6.7.14",
44
44
  "consola": "^3.4.2",
45
45
  "defu": "^6.1.4",
46
46
  "eslint": "^9.39.2",
47
47
  "eslint-config-prettier": "^10.1.8",
48
+ "eslint-import-resolver-typescript": "^4.4.4",
48
49
  "eslint-plugin-import-x": "^4.16.1",
49
50
  "eslint-plugin-react": "^7.37.5",
50
51
  "eslint-plugin-react-hooks": "^7.0.1",