@santi020k/eslint-config-basic 3.0.0 → 3.2.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.
@@ -0,0 +1,217 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ const CONFIG_FILENAMES = [
6
+ "eslint.config.js",
7
+ "eslint.config.mjs",
8
+ "eslint.config.cjs",
9
+ "eslint.config.ts",
10
+ "eslint.config.mts",
11
+ "eslint.config.cts"
12
+ ];
13
+ const RULE_PACKAGE_HINTS = {
14
+ "@angular-eslint": "@santi020k/eslint-config-angular",
15
+ "@eslint-react": "@santi020k/eslint-config-react",
16
+ "@next/next": "@santi020k/eslint-config-next",
17
+ "@typescript-eslint": "@santi020k/eslint-config-typescript",
18
+ astro: "@santi020k/eslint-config-astro",
19
+ cypress: "@santi020k/eslint-config-testing",
20
+ jest: "@santi020k/eslint-config-testing",
21
+ "jsonc": "@santi020k/eslint-config-formats",
22
+ playwright: "@santi020k/eslint-config-testing",
23
+ react: "@santi020k/eslint-config-react",
24
+ "testing-library": "@santi020k/eslint-config-testing",
25
+ unicorn: "@santi020k/eslint-config-extensions",
26
+ vue: "@santi020k/eslint-config-vue"
27
+ };
28
+ const semver = createRequire(import.meta.url)("semver");
29
+ const readJson = (filePath) => {
30
+ if (!existsSync(filePath)) return null;
31
+ try {
32
+ return JSON.parse(readFileSync(filePath, "utf8"));
33
+ } catch {
34
+ return null;
35
+ }
36
+ };
37
+ const findConfigPath = (cwd) => CONFIG_FILENAMES.map((file) => join(cwd, file)).find((file) => existsSync(file)) ?? null;
38
+ const flattenConfig = async (value) => {
39
+ const resolved = await value;
40
+ if (Array.isArray(resolved)) {
41
+ const nested = await Promise.all(resolved.map((entry) => flattenConfig(entry)));
42
+ return nested.flat();
43
+ }
44
+ if (resolved && typeof resolved === "object") return [resolved];
45
+ return [];
46
+ };
47
+ const loadConfigEntries = async (configPath) => {
48
+ if (!configPath) return [];
49
+ try {
50
+ const loaded = await import(`${pathToFileURL(configPath).href}?basic-eslint-explain=${Date.now()}`);
51
+ if (!loaded || typeof loaded !== "object" || !("default" in loaded)) return [];
52
+ return await flattenConfig(loaded.default);
53
+ } catch {
54
+ return [];
55
+ }
56
+ };
57
+ const loadProjectEslint = (cwd) => {
58
+ const projectRequire = createRequire(join(cwd, "package.json"));
59
+ const eslintModule = projectRequire("eslint");
60
+ if (!eslintModule.ESLint) throw new Error("The installed eslint package does not expose the ESLint API.");
61
+ return new eslintModule.ESLint({ cwd });
62
+ };
63
+ const getRuleSeverity = (ruleValue) => Array.isArray(ruleValue) ? ruleValue[0] : ruleValue;
64
+ const isRuleEnabled = (ruleValue) => {
65
+ const severity = getRuleSeverity(ruleValue);
66
+ return severity !== 0 && severity !== "off";
67
+ };
68
+ const getPackageHint = (rule) => {
69
+ const prefix = rule.includes("/") ? rule.slice(0, rule.indexOf("/")) : rule;
70
+ const scopedPrefix = rule.startsWith("@") ? rule.split("/").slice(0, 2).join("/") : prefix;
71
+ return RULE_PACKAGE_HINTS[scopedPrefix] ?? RULE_PACKAGE_HINTS[prefix] ?? null;
72
+ };
73
+ const handleExplainRule = async (cwd = process.cwd(), options) => {
74
+ const configPath = findConfigPath(cwd);
75
+ const file = options.file ?? "eslint.config.js";
76
+ const eslint = loadProjectEslint(cwd);
77
+ const effective = await eslint.calculateConfigForFile(file);
78
+ const effectiveValue = effective?.rules?.[options.rule];
79
+ const entries = await loadConfigEntries(configPath);
80
+ const sources = entries.flatMap((entry, index) => {
81
+ if (!entry.rules || !Object.hasOwn(entry.rules, options.rule)) return [];
82
+ return [{
83
+ files: entry.files ?? null,
84
+ index,
85
+ name: entry.name ?? `anonymous config #${index + 1}`,
86
+ value: entry.rules[options.rule]
87
+ }];
88
+ });
89
+ const payload = {
90
+ configFile: configPath ? basename(configPath) : null,
91
+ effective: effectiveValue ?? null,
92
+ enabled: effectiveValue !== void 0 && isRuleEnabled(effectiveValue),
93
+ file,
94
+ packageHint: getPackageHint(options.rule),
95
+ rule: options.rule,
96
+ severity: effectiveValue === void 0 ? null : getRuleSeverity(effectiveValue),
97
+ sources
98
+ };
99
+ if (options.json) {
100
+ console.log(JSON.stringify(payload, null, 2));
101
+ } else {
102
+ console.log([
103
+ `ESLint rule explanation: ${payload.rule}`,
104
+ `- File: ${payload.file}`,
105
+ `- Config: ${payload.configFile ?? "none"}`,
106
+ `- Effective value: ${JSON.stringify(payload.effective)}`,
107
+ `- Enabled: ${payload.enabled ? "yes" : "no"}`,
108
+ `- Likely package: ${payload.packageHint ?? "core or custom config"}`,
109
+ `- Defining entries: ${payload.sources.length}`
110
+ ].join("\n"));
111
+ for (const source of payload.sources) {
112
+ console.log(` - ${source.name}: ${JSON.stringify(source.value)}`);
113
+ }
114
+ }
115
+ if (effectiveValue === void 0) process.exitCode = 1;
116
+ };
117
+ const getDependencyVersions = (manifest) => {
118
+ const fields = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
119
+ const dependencies = {};
120
+ for (const field of fields) {
121
+ const values = manifest?.[field];
122
+ if (!values || typeof values !== "object" || Array.isArray(values)) continue;
123
+ for (const [name, version] of Object.entries(values)) {
124
+ if (typeof version === "string") dependencies[name] = version;
125
+ }
126
+ }
127
+ return dependencies;
128
+ };
129
+ const readResolvedManifest = (projectRequire, packageName) => {
130
+ try {
131
+ let directory = dirname(projectRequire.resolve(packageName));
132
+ for (let depth = 0; depth < 6; depth++) {
133
+ const manifestPath = join(directory, "package.json");
134
+ const manifest = readJson(manifestPath);
135
+ if (manifest?.name === packageName) return manifest;
136
+ directory = dirname(directory);
137
+ }
138
+ } catch {
139
+ return null;
140
+ }
141
+ return null;
142
+ };
143
+ const readWorkspaceManifest = (cwd, packageName) => {
144
+ const packagesPath = join(cwd, "packages");
145
+ if (!existsSync(packagesPath)) return null;
146
+ for (const entry of readdirSync(packagesPath, { withFileTypes: true })) {
147
+ if (!entry.isDirectory()) continue;
148
+ const manifest = readJson(join(packagesPath, entry.name, "package.json"));
149
+ if (manifest?.name === packageName) return manifest;
150
+ }
151
+ return null;
152
+ };
153
+ const createCompatibilityReport = (cwd = process.cwd()) => {
154
+ const rootManifest = readJson(join(cwd, "package.json"));
155
+ const declared = getDependencyVersions(rootManifest);
156
+ const projectRequire = createRequire(join(cwd, "package.json"));
157
+ const configPackages = Object.keys(declared).filter((name) => name.startsWith("@santi020k/eslint-config-")).sort();
158
+ const runtimeVersions = {
159
+ eslint: readResolvedManifest(projectRequire, "eslint")?.version ?? null,
160
+ node: process.versions.node,
161
+ typescript: readResolvedManifest(projectRequire, "typescript")?.version ?? null
162
+ };
163
+ const packages = configPackages.map((name) => {
164
+ const manifest = readResolvedManifest(projectRequire, name) ?? readWorkspaceManifest(cwd, name);
165
+ const issues = [];
166
+ if (!manifest) {
167
+ issues.push("declared but not installed");
168
+ } else {
169
+ const nodeRange = manifest.engines?.node;
170
+ if (nodeRange && !semver.satisfies(runtimeVersions.node, nodeRange)) {
171
+ issues.push(`requires Node ${nodeRange}`);
172
+ }
173
+ for (const peer of ["eslint", "typescript"]) {
174
+ const range = manifest.peerDependencies?.[peer];
175
+ const version = runtimeVersions[peer];
176
+ if (range && version && !semver.satisfies(version, range)) {
177
+ issues.push(`requires ${peer} ${range}, resolved ${version}`);
178
+ }
179
+ }
180
+ }
181
+ return {
182
+ declared: declared[name],
183
+ issues,
184
+ name,
185
+ resolved: manifest?.version ?? null
186
+ };
187
+ });
188
+ return {
189
+ compatible: packages.every((item) => item.issues.length === 0),
190
+ packages,
191
+ runtime: runtimeVersions
192
+ };
193
+ };
194
+ const handleCompatibility = (cwd = process.cwd(), json = false) => {
195
+ const report = createCompatibilityReport(cwd);
196
+ if (json) {
197
+ console.log(JSON.stringify(report, null, 2));
198
+ } else {
199
+ console.log([
200
+ `ESLint compatibility: ${report.compatible ? "compatible" : "issues found"}`,
201
+ `- Node: ${report.runtime.node}`,
202
+ `- ESLint: ${report.runtime.eslint ?? "not installed"}`,
203
+ `- TypeScript: ${report.runtime.typescript ?? "not installed"}`
204
+ ].join("\n"));
205
+ for (const item of report.packages) {
206
+ console.log(
207
+ `- ${item.name}: ${item.resolved ?? "not installed"}` + (item.issues.length > 0 ? ` (${item.issues.join("; ")})` : "")
208
+ );
209
+ }
210
+ }
211
+ if (!report.compatible) process.exitCode = 1;
212
+ };
213
+ export {
214
+ createCompatibilityReport,
215
+ handleCompatibility,
216
+ handleExplainRule
217
+ };
@@ -0,0 +1,26 @@
1
+ interface V3MigrationContext {
2
+ declaredConfigPackages?: string[];
3
+ extensions: string[];
4
+ formats: string[];
5
+ frameworks: string[];
6
+ libraries: string[];
7
+ packageManager: string;
8
+ testing: string[];
9
+ tools: string[];
10
+ typescript: boolean;
11
+ }
12
+ interface V3MigrationOptions {
13
+ check?: boolean;
14
+ full?: boolean;
15
+ json?: boolean;
16
+ write?: boolean;
17
+ }
18
+ declare const getExplicitConfigFeaturePackages: (content: string) => string[];
19
+ declare const migrateConfigToV3: (content: string, mode: "full" | "lean") => {
20
+ changes: string[];
21
+ content: string;
22
+ featurePackages: string[];
23
+ };
24
+ declare const handleMigrateV3: (cwd: string, context: V3MigrationContext, options?: V3MigrationOptions) => void;
25
+
26
+ export { type V3MigrationContext, type V3MigrationOptions, getExplicitConfigFeaturePackages, handleMigrateV3, migrateConfigToV3 };