rhine-lint 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.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/assets/eslint.config.d.ts +810 -0
- package/dist/assets/eslint.config.js +375 -0
- package/dist/assets/prettier.config.d.ts +13 -0
- package/dist/assets/prettier.config.js +13 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +76 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +1 -0
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.js +188 -0
- package/dist/core/runner.d.ts +6 -0
- package/dist/core/runner.js +122 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +29 -0
- package/package.json +62 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
function getAssetPath(filename) {
|
|
8
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../assets", filename);
|
|
9
|
+
}
|
|
10
|
+
const CONFIG_FILENAMES = [
|
|
11
|
+
"rhine-lint.config.ts",
|
|
12
|
+
"rhine-lint.config.js",
|
|
13
|
+
"rhine-lint.config.mjs",
|
|
14
|
+
"rhine-lint.config.cjs",
|
|
15
|
+
"rhine-lint.config.json",
|
|
16
|
+
"config/rhine-lint.config.ts",
|
|
17
|
+
"config/rhine-lint.config.js",
|
|
18
|
+
"config/rhine-lint.config.mjs",
|
|
19
|
+
"config/rhine-lint.config.cjs",
|
|
20
|
+
"config/rhine-lint.config.json",
|
|
21
|
+
];
|
|
22
|
+
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;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!configPath) {
|
|
32
|
+
return { config: {} };
|
|
33
|
+
}
|
|
34
|
+
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);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const importUrl = pathToFileURL(configPath).href;
|
|
43
|
+
const mod = await import(importUrl);
|
|
44
|
+
loaded = mod.default || mod;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
config: loaded || {},
|
|
48
|
+
path: configPath
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
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: {} };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function getCacheDir(cwd, userConfig, cliCacheDir) {
|
|
58
|
+
// 1. CLI option
|
|
59
|
+
if (cliCacheDir) {
|
|
60
|
+
return path.resolve(cwd, cliCacheDir);
|
|
61
|
+
}
|
|
62
|
+
// 2. Config option
|
|
63
|
+
if (userConfig?.cacheDir) {
|
|
64
|
+
return path.resolve(cwd, userConfig.cacheDir);
|
|
65
|
+
}
|
|
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");
|
|
70
|
+
}
|
|
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
|
+
const defaultEslintPath = pathToFileURL(getAssetPath("eslint.config.js")).href;
|
|
80
|
+
const defaultPrettierPath = pathToFileURL(getAssetPath("prettier.config.js")).href;
|
|
81
|
+
const userConfigUrl = userConfigResult.path ? pathToFileURL(userConfigResult.path).href : null;
|
|
82
|
+
const defuUrl = pathToFileURL(require.resolve("defu")).href;
|
|
83
|
+
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
|
|
92
|
+
}
|
|
93
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
94
|
+
const hasGitignore = await fs.pathExists(gitignorePath);
|
|
95
|
+
const gitignoreUrl = hasGitignore ? pathToFileURL(gitignorePath).href : null;
|
|
96
|
+
const isEslintOverlay = userConfigResult.config.eslint?.overlay ?? false;
|
|
97
|
+
const isPrettierOverlay = userConfigResult.config.prettier?.overlay ?? false;
|
|
98
|
+
const eslintContent = `
|
|
99
|
+
import { createJiti } from "${jitiUrl}";
|
|
100
|
+
import { fileURLToPath } from "node:url";
|
|
101
|
+
import path from "node:path";
|
|
102
|
+
import createConfig from "${defaultEslintPath}";
|
|
103
|
+
import { defu } from "${defuUrl}";
|
|
104
|
+
${compatUrl && hasGitignore ? `import { includeIgnoreFile } from "${compatUrl}";` : ''}
|
|
105
|
+
|
|
106
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
107
|
+
const __dirname = path.dirname(__filename);
|
|
108
|
+
const jiti = createJiti(__filename);
|
|
109
|
+
|
|
110
|
+
${userConfigUrl ? `
|
|
111
|
+
const loaded = await jiti.import("${userConfigUrl.replace('file:///', '').replace(/%20/g, ' ')}", { default: true });
|
|
112
|
+
const userOne = loaded.default || loaded;
|
|
113
|
+
` : 'const userOne = {};'}
|
|
114
|
+
|
|
115
|
+
const userEslint = userOne.eslint || {};
|
|
116
|
+
const level = "${cliLevel || ''}" || userOne.level || "frontend";
|
|
117
|
+
|
|
118
|
+
let overrides = {};
|
|
119
|
+
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;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const defaultEslint = createConfig(overrides);
|
|
127
|
+
|
|
128
|
+
let finalConfig;
|
|
129
|
+
|
|
130
|
+
// Prepend ignore file if exists
|
|
131
|
+
const prefixConfig = [];
|
|
132
|
+
${compatUrl && hasGitignore && gitignoreUrl ? `
|
|
133
|
+
prefixConfig.push(includeIgnoreFile("${gitignoreUrl.replace('file:///', '').replace(/%20/g, ' ')}"));
|
|
134
|
+
` : ''}
|
|
135
|
+
|
|
136
|
+
if (${isEslintOverlay} || userEslint.overlay) {
|
|
137
|
+
if (Array.isArray(userEslint.config)) {
|
|
138
|
+
finalConfig = [...prefixConfig, ...defaultEslint, ...userEslint.config];
|
|
139
|
+
} else {
|
|
140
|
+
finalConfig = defu(userEslint, defaultEslint);
|
|
141
|
+
// Note: merging object into array-based config is weird, assume flat config array concatenation for robustness
|
|
142
|
+
if (!Array.isArray(finalConfig)) finalConfig = [...prefixConfig, finalConfig];
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const userConf = userEslint.config || [];
|
|
146
|
+
const defaultConf = Array.isArray(defaultEslint) ? defaultEslint : [defaultEslint];
|
|
147
|
+
finalConfig = [...prefixConfig, ...defaultConf, ...userConf];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export default finalConfig;
|
|
151
|
+
`;
|
|
152
|
+
const prettierContent = `
|
|
153
|
+
import { createJiti } from "${jitiUrl}";
|
|
154
|
+
import defaultOne from "${defaultPrettierPath}";
|
|
155
|
+
import { defu } from "${defuUrl}";
|
|
156
|
+
|
|
157
|
+
const jiti = createJiti(import.meta.url);
|
|
158
|
+
|
|
159
|
+
${userConfigUrl ? `
|
|
160
|
+
const loaded = await jiti.import("${userConfigUrl}", { default: true });
|
|
161
|
+
const userOne = loaded.default || loaded;
|
|
162
|
+
` : 'const userOne = {};'}
|
|
163
|
+
|
|
164
|
+
const userPrettier = userOne.prettier || {};
|
|
165
|
+
const defaultPrettier = defaultOne;
|
|
166
|
+
|
|
167
|
+
let finalConfig;
|
|
168
|
+
if (${isPrettierOverlay} || userPrettier.overlay) {
|
|
169
|
+
finalConfig = defu(userPrettier.config || {}, defaultPrettier);
|
|
170
|
+
} else {
|
|
171
|
+
finalConfig = defu(userPrettier.config || {}, defaultPrettier);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default finalConfig;
|
|
175
|
+
`;
|
|
176
|
+
await fs.writeFile(eslintTempPath, eslintContent);
|
|
177
|
+
await fs.writeFile(prettierTempPath, prettierContent);
|
|
178
|
+
return { eslintPath: eslintTempPath, prettierPath: prettierTempPath, cachePath };
|
|
179
|
+
}
|
|
180
|
+
export async function cleanup(cachePath) {
|
|
181
|
+
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);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function runCommandWithOutput(command: string, args: string[], cwd: string): Promise<{
|
|
2
|
+
output: string;
|
|
3
|
+
code: number;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function runEslint(cwd: string, configPath: string, fix: boolean, files?: string[]): Promise<string | null>;
|
|
6
|
+
export declare function runPrettier(cwd: string, configPath: string, fix: boolean, files?: string[]): Promise<string | null>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
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
|
+
function stripAnsi(str) {
|
|
7
|
+
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
8
|
+
}
|
|
9
|
+
export async function runCommandWithOutput(command, args, cwd) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
logger.debug(`Executing: ${command} ${args.join(" ")}`);
|
|
12
|
+
const proc = spawn(command, args, {
|
|
13
|
+
cwd,
|
|
14
|
+
stdio: ["inherit", "pipe", "pipe"], // Pipe stdout/stderr so we can read it
|
|
15
|
+
shell: true,
|
|
16
|
+
});
|
|
17
|
+
let output = "";
|
|
18
|
+
if (proc.stdout) {
|
|
19
|
+
proc.stdout.on("data", (data) => {
|
|
20
|
+
process.stdout.write(data); // Passthrough to console
|
|
21
|
+
output += data.toString();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (proc.stderr) {
|
|
25
|
+
proc.stderr.on("data", (data) => {
|
|
26
|
+
process.stderr.write(data); // Passthrough to console
|
|
27
|
+
output += data.toString();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
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
|
+
if (code === null)
|
|
35
|
+
code = 1; // Default to error if null
|
|
36
|
+
resolve({ output, code });
|
|
37
|
+
});
|
|
38
|
+
proc.on("error", (err) => {
|
|
39
|
+
reject(err);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Return type: null means success (no errors), string means summary of errors/warnings
|
|
44
|
+
export async function runEslint(cwd, configPath, fix, files = ["."]) {
|
|
45
|
+
logInfo("Running ESLint...");
|
|
46
|
+
console.log();
|
|
47
|
+
const args = [
|
|
48
|
+
"eslint",
|
|
49
|
+
...files,
|
|
50
|
+
"--config", configPath,
|
|
51
|
+
...(fix ? ["--fix"] : []),
|
|
52
|
+
];
|
|
53
|
+
try {
|
|
54
|
+
const { output, code } = await runCommandWithOutput(EXECUTOR, args, cwd);
|
|
55
|
+
if (!output.endsWith('\n')) {
|
|
56
|
+
console.log();
|
|
57
|
+
}
|
|
58
|
+
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";
|
|
72
|
+
}
|
|
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}`;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
logError("ESLint execution failed.", e);
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function runPrettier(cwd, configPath, fix, files = ["."]) {
|
|
85
|
+
logInfo("Running Prettier...");
|
|
86
|
+
console.log();
|
|
87
|
+
const args = [
|
|
88
|
+
"prettier",
|
|
89
|
+
...files,
|
|
90
|
+
"--config", configPath,
|
|
91
|
+
...(fix ? ["--write"] : ["--check"]),
|
|
92
|
+
];
|
|
93
|
+
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.
|
|
110
|
+
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";
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
logError("Prettier execution failed.", e);
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './config.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './config.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const logger: import("consola").ConsolaInstance;
|
|
2
|
+
export declare function logInfo(message: string): void;
|
|
3
|
+
export declare function logSuccess(message: string): void;
|
|
4
|
+
export declare function logWarn(message: string): void;
|
|
5
|
+
export declare function logError(message: string, error?: unknown): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createConsola } from "consola";
|
|
2
|
+
import { colors } from "consola/utils";
|
|
3
|
+
const { cyan, red, green, yellow } = colors;
|
|
4
|
+
// Custom logger to satisfy "RL" prefix requirement
|
|
5
|
+
const rLine = (colorFn, type, msg) => {
|
|
6
|
+
console.log(`${colorFn("RL")} ${msg}`);
|
|
7
|
+
};
|
|
8
|
+
export const logger = createConsola({
|
|
9
|
+
level: 3,
|
|
10
|
+
});
|
|
11
|
+
export function logInfo(message) {
|
|
12
|
+
// Manually format to match requested style: "RL <message>"
|
|
13
|
+
console.log(`${cyan("RL")} ${message}`);
|
|
14
|
+
}
|
|
15
|
+
export function logSuccess(message) {
|
|
16
|
+
console.log(`${green("RL")} ${message}`);
|
|
17
|
+
}
|
|
18
|
+
export function logWarn(message) {
|
|
19
|
+
console.log(`${yellow("RL")} ${message}`);
|
|
20
|
+
}
|
|
21
|
+
export function logError(message, error) {
|
|
22
|
+
// For errors, we want red "RL"
|
|
23
|
+
if (error) {
|
|
24
|
+
console.error(`${red("RL")} ${message}`, error);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error(`${red("RL")} ${message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rhine-lint",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"module": "./dist/index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"rl": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"description": "An opinionated linting solution merging ESLint and Prettier with zero config.",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"eslint",
|
|
14
|
+
"prettier",
|
|
15
|
+
"lint",
|
|
16
|
+
"typescript",
|
|
17
|
+
"nextjs",
|
|
18
|
+
"react",
|
|
19
|
+
"opinionated"
|
|
20
|
+
],
|
|
21
|
+
"author": "RhineAI <hran@rhine.ai>",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/RhineAI/rhine-lint.git"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc && node scripts/copy-assets.js",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@eslint/compat": "^1.3.2",
|
|
38
|
+
"@eslint/css": "^0.10.0",
|
|
39
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
40
|
+
"@eslint/json": "^0.13.1",
|
|
41
|
+
"@eslint/markdown": "^7.1.0",
|
|
42
|
+
"@next/eslint-plugin-next": "^16.1.1",
|
|
43
|
+
"cac": "^6.7.14",
|
|
44
|
+
"consola": "^3.4.2",
|
|
45
|
+
"defu": "^6.1.4",
|
|
46
|
+
"eslint": "^9.39.2",
|
|
47
|
+
"eslint-config-prettier": "^10.1.8",
|
|
48
|
+
"eslint-plugin-import-x": "^4.16.1",
|
|
49
|
+
"eslint-plugin-react": "^7.37.5",
|
|
50
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
51
|
+
"eslint-plugin-tsdoc": "^0.5.0",
|
|
52
|
+
"eslint-plugin-unused-imports": "^4.2.0",
|
|
53
|
+
"fs-extra": "^11.3.3",
|
|
54
|
+
"jiti": "^2.5.1",
|
|
55
|
+
"prettier": "^3.6.2",
|
|
56
|
+
"typescript": "^5",
|
|
57
|
+
"typescript-eslint": "^8.39.1"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/fs-extra": "^11.0.4"
|
|
61
|
+
}
|
|
62
|
+
}
|