@askviraj/linter 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/dist/auto.js +4 -0
- package/dist/cli/args.js +96 -0
- package/dist/cli/commands/help.js +18 -0
- package/dist/cli/commands/init.js +153 -0
- package/dist/cli/commands/lint.js +47 -0
- package/dist/cli/commands/version.js +86 -0
- package/dist/cli/runner.js +28 -0
- package/dist/cli/types.js +6 -0
- package/dist/config/ignores.js +12 -0
- package/dist/config/index.js +29 -0
- package/dist/config/jsonc-files.js +6 -0
- package/dist/config/presets/astro.js +25 -0
- package/dist/config/presets/css.js +10 -0
- package/dist/config/presets/html.js +11 -0
- package/dist/config/presets/javascript.js +17 -0
- package/dist/config/presets/json.js +18 -0
- package/dist/config/presets/jsonc.js +21 -0
- package/dist/config/presets/markdown-ts.js +9 -0
- package/dist/config/presets/markdown.js +21 -0
- package/dist/config/presets/toml.js +9 -0
- package/dist/config/presets/typescript.js +33 -0
- package/dist/config/presets/yaml.js +10 -0
- package/dist/config-factory.js +24 -0
- package/dist/config-loader.js +42 -0
- package/dist/eslint.config.js +2 -0
- package/dist/index.js +3 -0
- package/package.json +85 -0
- package/readme.md +172 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Viraj Patel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/auto.js
ADDED
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ParseError } from "./types.js";
|
|
2
|
+
export function parseArgs(rawArgs) {
|
|
3
|
+
const flags = {
|
|
4
|
+
init: false,
|
|
5
|
+
force: false,
|
|
6
|
+
fix: false,
|
|
7
|
+
cache: false,
|
|
8
|
+
quiet: false,
|
|
9
|
+
ignore: true,
|
|
10
|
+
debug: false,
|
|
11
|
+
version: false,
|
|
12
|
+
help: false,
|
|
13
|
+
};
|
|
14
|
+
let cacheLocation;
|
|
15
|
+
let format;
|
|
16
|
+
let maxWarnings;
|
|
17
|
+
const positional = [];
|
|
18
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
19
|
+
const arg = rawArgs[i];
|
|
20
|
+
if (arg === undefined) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
switch (arg) {
|
|
24
|
+
case "--init":
|
|
25
|
+
flags.init = true;
|
|
26
|
+
break;
|
|
27
|
+
case "--force":
|
|
28
|
+
flags.force = true;
|
|
29
|
+
break;
|
|
30
|
+
case "--fix":
|
|
31
|
+
flags.fix = true;
|
|
32
|
+
break;
|
|
33
|
+
case "--cache":
|
|
34
|
+
flags.cache = true;
|
|
35
|
+
break;
|
|
36
|
+
case "--quiet":
|
|
37
|
+
flags.quiet = true;
|
|
38
|
+
break;
|
|
39
|
+
case "--no-ignore":
|
|
40
|
+
flags.ignore = false;
|
|
41
|
+
break;
|
|
42
|
+
case "--debug":
|
|
43
|
+
flags.debug = true;
|
|
44
|
+
break;
|
|
45
|
+
case "--version":
|
|
46
|
+
case "-v":
|
|
47
|
+
flags.version = true;
|
|
48
|
+
break;
|
|
49
|
+
case "--help":
|
|
50
|
+
case "-h":
|
|
51
|
+
flags.help = true;
|
|
52
|
+
break;
|
|
53
|
+
case "--cache-location": {
|
|
54
|
+
const next = rawArgs[++i];
|
|
55
|
+
if (!next) {
|
|
56
|
+
throw new ParseError("--cache-location requires a path argument");
|
|
57
|
+
}
|
|
58
|
+
cacheLocation = next;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "--format":
|
|
62
|
+
case "-f": {
|
|
63
|
+
const next = rawArgs[++i];
|
|
64
|
+
if (!next) {
|
|
65
|
+
throw new ParseError("--format requires a format name");
|
|
66
|
+
}
|
|
67
|
+
format = next;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "--max-warnings": {
|
|
71
|
+
const next = rawArgs[++i];
|
|
72
|
+
if (!next || Number.isNaN(Number(next))) {
|
|
73
|
+
throw new ParseError("--max-warnings requires a number");
|
|
74
|
+
}
|
|
75
|
+
maxWarnings = Number(next);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "--":
|
|
79
|
+
for (let j = i + 1; j < rawArgs.length; j++) {
|
|
80
|
+
const remaining = rawArgs[j];
|
|
81
|
+
if (remaining !== undefined) {
|
|
82
|
+
positional.push(remaining);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
i = rawArgs.length;
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
if (arg.startsWith("-")) {
|
|
89
|
+
throw new ParseError(`Unknown flag: ${arg}`);
|
|
90
|
+
}
|
|
91
|
+
positional.push(arg);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { flags, cacheLocation, format, maxWarnings, positional };
|
|
96
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function runHelp() {
|
|
2
|
+
console.log(`@askviraj/linter [options] [files...]
|
|
3
|
+
|
|
4
|
+
Options:
|
|
5
|
+
--init Scaffold eslint.config.mjs and .config/linter.yaml
|
|
6
|
+
--force Overwrite existing files when used with --init
|
|
7
|
+
--fix Automatically fix problems
|
|
8
|
+
--cache Only check changed files
|
|
9
|
+
--cache-location Path to the cache file or directory
|
|
10
|
+
--no-ignore Disable use of ignore files
|
|
11
|
+
--quiet Report errors only
|
|
12
|
+
--debug Enable debug logging
|
|
13
|
+
--format, -f Use a specific output format
|
|
14
|
+
--max-warnings Number of warnings to trigger nonzero exit code
|
|
15
|
+
--version, -v Show version
|
|
16
|
+
--help, -h Show help
|
|
17
|
+
`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { applyEdits, findNodeAtLocation, getNodeValue, modify, parseTree, } from "jsonc-parser";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
const ESLINT_CONFIG_MJS = `// Auto-generated by @askviraj/linter
|
|
5
|
+
// Run \`npx @askviraj/linter --init\` to regenerate.
|
|
6
|
+
export { default } from "@askviraj/linter/auto";
|
|
7
|
+
`;
|
|
8
|
+
const LINTER_YAML = `# @askviraj/linter configuration
|
|
9
|
+
#
|
|
10
|
+
# Add custom ignores, rule overrides, and additional config blocks below.
|
|
11
|
+
|
|
12
|
+
version: 1
|
|
13
|
+
|
|
14
|
+
# ignores:
|
|
15
|
+
# - "**/generated/**"
|
|
16
|
+
# - "**/*.d.ts"
|
|
17
|
+
#
|
|
18
|
+
# overrides:
|
|
19
|
+
# typescript:
|
|
20
|
+
# rules:
|
|
21
|
+
# "@typescript-eslint/no-explicit-any": "error"
|
|
22
|
+
#
|
|
23
|
+
# configs:
|
|
24
|
+
# - name: "custom"
|
|
25
|
+
# files: ["**/*.ts"]
|
|
26
|
+
# rules: {}
|
|
27
|
+
`;
|
|
28
|
+
const VSCODE_SETTINGS = {
|
|
29
|
+
"eslint.useFlatConfig": true,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Read an existing .vscode/settings.json and update/add eslint.useFlatConfig.
|
|
33
|
+
* Uses jsonc-parser so comments, trailing commas, and formatting are preserved.
|
|
34
|
+
*/
|
|
35
|
+
function updateVsCodeSettings(existingPath) {
|
|
36
|
+
const text = readFileSync(existingPath, "utf-8");
|
|
37
|
+
const root = parseTree(text);
|
|
38
|
+
if (!root) {
|
|
39
|
+
// Completely unparseable — fall back to a fresh object.
|
|
40
|
+
return {
|
|
41
|
+
content: JSON.stringify(VSCODE_SETTINGS, null, 2) + "\n",
|
|
42
|
+
action: "updated",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const node = findNodeAtLocation(root, ["eslint.useFlatConfig"]);
|
|
46
|
+
if (node) {
|
|
47
|
+
const value = getNodeValue(node);
|
|
48
|
+
if (value === true) {
|
|
49
|
+
return { content: text, action: "up-to-date" };
|
|
50
|
+
}
|
|
51
|
+
// Replace existing value.
|
|
52
|
+
const edits = modify(text, ["eslint.useFlatConfig"], true, {
|
|
53
|
+
formattingOptions: { tabSize: 2, insertSpaces: true, eol: "\n" },
|
|
54
|
+
});
|
|
55
|
+
return { content: applyEdits(text, edits), action: "updated" };
|
|
56
|
+
}
|
|
57
|
+
// Property missing — add it.
|
|
58
|
+
const edits = modify(text, ["eslint.useFlatConfig"], true, {
|
|
59
|
+
formattingOptions: { tabSize: 2, insertSpaces: true, eol: "\n" },
|
|
60
|
+
});
|
|
61
|
+
return { content: applyEdits(text, edits), action: "updated" };
|
|
62
|
+
}
|
|
63
|
+
export function runInit(options = {}) {
|
|
64
|
+
const cwd = options.cwd || process.cwd();
|
|
65
|
+
const actions = [];
|
|
66
|
+
let settingsError;
|
|
67
|
+
// 1. eslint.config.mjs
|
|
68
|
+
const eslintConfigPath = resolve(cwd, "eslint.config.mjs");
|
|
69
|
+
const eslintExisted = existsSync(eslintConfigPath);
|
|
70
|
+
if (eslintExisted && !options.force) {
|
|
71
|
+
actions.push({ path: eslintConfigPath, action: "skipped" });
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
try {
|
|
75
|
+
writeFileSync(eslintConfigPath, ESLINT_CONFIG_MJS);
|
|
76
|
+
actions.push({
|
|
77
|
+
path: eslintConfigPath,
|
|
78
|
+
action: eslintExisted ? "overwritten" : "created",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
throw new Error(`Failed to write ${eslintConfigPath}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 2. .config/linter.yaml
|
|
86
|
+
const configDir = resolve(cwd, ".config");
|
|
87
|
+
if (!existsSync(configDir)) {
|
|
88
|
+
mkdirSync(configDir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
const linterYamlPath = resolve(configDir, "linter.yaml");
|
|
91
|
+
const linterExisted = existsSync(linterYamlPath);
|
|
92
|
+
if (linterExisted && !options.force) {
|
|
93
|
+
actions.push({ path: linterYamlPath, action: "skipped" });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
try {
|
|
97
|
+
writeFileSync(linterYamlPath, LINTER_YAML);
|
|
98
|
+
actions.push({
|
|
99
|
+
path: linterYamlPath,
|
|
100
|
+
action: linterExisted ? "overwritten" : "created",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
throw new Error(`Failed to write ${linterYamlPath}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// 3. .vscode/settings.json
|
|
108
|
+
const vscodeDir = resolve(cwd, ".vscode");
|
|
109
|
+
if (!existsSync(vscodeDir)) {
|
|
110
|
+
mkdirSync(vscodeDir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
const settingsPath = resolve(vscodeDir, "settings.json");
|
|
113
|
+
if (existsSync(settingsPath)) {
|
|
114
|
+
try {
|
|
115
|
+
const result = updateVsCodeSettings(settingsPath);
|
|
116
|
+
if (result.action === "up-to-date") {
|
|
117
|
+
actions.push({ path: settingsPath, action: "up-to-date" });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
writeFileSync(settingsPath, result.content);
|
|
121
|
+
actions.push({ path: settingsPath, action: "updated" });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
settingsError = e instanceof Error ? e.message : String(e);
|
|
126
|
+
actions.push({ path: settingsPath, action: "failed" });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
try {
|
|
131
|
+
writeFileSync(settingsPath, JSON.stringify(VSCODE_SETTINGS, null, 2) + "\n");
|
|
132
|
+
actions.push({ path: settingsPath, action: "created" });
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
throw new Error(`Failed to write ${settingsPath}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
console.log("Initialized @askviraj/linter:");
|
|
139
|
+
for (const { path, action } of actions) {
|
|
140
|
+
console.log(` [${action}] ${path}`);
|
|
141
|
+
}
|
|
142
|
+
const skipped = actions.filter(a => a.action === "skipped");
|
|
143
|
+
if (skipped.length > 0) {
|
|
144
|
+
console.log("");
|
|
145
|
+
console.log("Some files were skipped because they already exist. Use --force to overwrite.");
|
|
146
|
+
}
|
|
147
|
+
if (settingsError) {
|
|
148
|
+
throw new Error(`Failed to update ${settingsPath}: ${settingsError}\n`
|
|
149
|
+
+ "The file contains invalid JSONC. Fix the file and try again.");
|
|
150
|
+
}
|
|
151
|
+
console.log("");
|
|
152
|
+
console.log("Note: If you have VS Code open, restart the ESLint server (", "Cmd+Shift+P → 'ESLint: Restart ESLint Server'", ") or restart VS Code for changes to take effect.");
|
|
153
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ESLint } from "eslint";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { createConfig } from "../../config-factory.js";
|
|
4
|
+
import { loadYamlOptions } from "../../config-loader.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
export async function runLint(args) {
|
|
7
|
+
if (args.flags.debug) {
|
|
8
|
+
const debug = require("debug");
|
|
9
|
+
debug.enable("eslint:*,-eslint:code-path,eslintrc:*");
|
|
10
|
+
}
|
|
11
|
+
const targets = args.positional.length > 0 ? args.positional : ["."];
|
|
12
|
+
const config = createConfig(loadYamlOptions(process.cwd()));
|
|
13
|
+
const eslint = new ESLint({
|
|
14
|
+
overrideConfigFile: true,
|
|
15
|
+
overrideConfig: config,
|
|
16
|
+
fix: args.flags.fix,
|
|
17
|
+
cache: args.flags.cache,
|
|
18
|
+
cacheLocation: args.cacheLocation,
|
|
19
|
+
errorOnUnmatchedPattern: false,
|
|
20
|
+
ignore: args.flags.ignore,
|
|
21
|
+
});
|
|
22
|
+
let results;
|
|
23
|
+
try {
|
|
24
|
+
results = await eslint.lintFiles(targets);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
throw new Error(e instanceof Error ? e.message : String(e), { cause: e });
|
|
28
|
+
}
|
|
29
|
+
if (args.flags.fix) {
|
|
30
|
+
await ESLint.outputFixes(results);
|
|
31
|
+
}
|
|
32
|
+
if (args.flags.quiet) {
|
|
33
|
+
results = ESLint.getErrorResults(results);
|
|
34
|
+
}
|
|
35
|
+
const formatter = await eslint.loadFormatter(args.format);
|
|
36
|
+
const output = await formatter.format(results);
|
|
37
|
+
if (output) {
|
|
38
|
+
console.log(output);
|
|
39
|
+
}
|
|
40
|
+
const errorCount = results.reduce((sum, r) => sum + r.errorCount, 0);
|
|
41
|
+
const warningCount = results.reduce((sum, r) => sum + r.warningCount, 0);
|
|
42
|
+
const failed = errorCount > 0
|
|
43
|
+
|| (args.maxWarnings !== undefined
|
|
44
|
+
&& args.maxWarnings >= 0
|
|
45
|
+
&& warningCount > args.maxWarnings);
|
|
46
|
+
return failed ? 1 : 0;
|
|
47
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, resolve, } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
function getPackageVersion(name, requireFn) {
|
|
7
|
+
try {
|
|
8
|
+
const mainPath = requireFn.resolve(name);
|
|
9
|
+
let dir = dirname(mainPath);
|
|
10
|
+
while (dir !== dirname(dir)) {
|
|
11
|
+
const pkgPath = resolve(dir, "package.json");
|
|
12
|
+
try {
|
|
13
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
14
|
+
if (pkg.name === name && pkg.version) {
|
|
15
|
+
return pkg.version;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Continue walking up.
|
|
20
|
+
}
|
|
21
|
+
dir = dirname(dir);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Package main not resolvable; try fallback.
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const pkgPath = resolve(moduleDir, "..", "..", "..", "node_modules", name, "package.json");
|
|
30
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
31
|
+
if (pkg.name === name && pkg.version) {
|
|
32
|
+
return pkg.version;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Not found in node_modules either.
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
export function runVersion() {
|
|
41
|
+
let ownVersion;
|
|
42
|
+
let deps;
|
|
43
|
+
try {
|
|
44
|
+
const ownPkgPath = require.resolve("../../../package.json");
|
|
45
|
+
const ownPkg = JSON.parse(readFileSync(ownPkgPath, "utf-8"));
|
|
46
|
+
ownVersion = ownPkg.version;
|
|
47
|
+
deps = ownPkg.dependencies;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new Error("Failed to read package metadata.");
|
|
51
|
+
}
|
|
52
|
+
if (!ownVersion) {
|
|
53
|
+
throw new Error("Package version not found.");
|
|
54
|
+
}
|
|
55
|
+
const patterns = [
|
|
56
|
+
/^eslint$/,
|
|
57
|
+
/^@eslint\//,
|
|
58
|
+
/^@typescript-eslint\//,
|
|
59
|
+
/^typescript-eslint$/,
|
|
60
|
+
/^@html-eslint\//,
|
|
61
|
+
/^@poupe\/eslint-plugin-/,
|
|
62
|
+
/^eslint-plugin-/,
|
|
63
|
+
];
|
|
64
|
+
const depNames = deps
|
|
65
|
+
? Object.keys(deps).filter(name => patterns.some(pattern => pattern.test(name)))
|
|
66
|
+
: [];
|
|
67
|
+
const pluginVersions = [];
|
|
68
|
+
for (const name of depNames) {
|
|
69
|
+
const version = getPackageVersion(name, require);
|
|
70
|
+
if (version) {
|
|
71
|
+
pluginVersions.push({ name, version });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
pluginVersions.sort((a, b) => a.name.localeCompare(b.name));
|
|
75
|
+
console.log(`v${ownVersion}`);
|
|
76
|
+
console.log("------");
|
|
77
|
+
const eslintEntry = pluginVersions.find(p => p.name === "eslint");
|
|
78
|
+
if (eslintEntry) {
|
|
79
|
+
console.log(`eslint v${eslintEntry.version}`);
|
|
80
|
+
}
|
|
81
|
+
for (const { name, version } of pluginVersions) {
|
|
82
|
+
if (name !== "eslint") {
|
|
83
|
+
console.log(`${name} v${version}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { parseArgs } from "./args.js";
|
|
2
|
+
import { runHelp } from "./commands/help.js";
|
|
3
|
+
import { runInit } from "./commands/init.js";
|
|
4
|
+
import { runLint } from "./commands/lint.js";
|
|
5
|
+
import { runVersion } from "./commands/version.js";
|
|
6
|
+
export async function run(rawArgs) {
|
|
7
|
+
try {
|
|
8
|
+
const args = parseArgs(rawArgs);
|
|
9
|
+
if (args.flags.help) {
|
|
10
|
+
runHelp();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (args.flags.version) {
|
|
14
|
+
runVersion();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (args.flags.init) {
|
|
18
|
+
runInit({ force: args.flags.force });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const exitCode = await runLint(args);
|
|
22
|
+
process.exit(exitCode);
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
console.error(e instanceof Error ? e.message : e);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { globalIgnores } from "eslint/config";
|
|
2
|
+
export const ignores = globalIgnores([
|
|
3
|
+
"**/node_modules/",
|
|
4
|
+
"**/dist/",
|
|
5
|
+
"**/.turbo/",
|
|
6
|
+
"**/.astro/",
|
|
7
|
+
"**/*.d.ts",
|
|
8
|
+
"**/AGENTS.md",
|
|
9
|
+
".agents/**/*.md",
|
|
10
|
+
".claude/**/*.md",
|
|
11
|
+
".github/**/*.md",
|
|
12
|
+
]);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineConfig } from "eslint/config";
|
|
2
|
+
import { ignores } from "./ignores.js";
|
|
3
|
+
import { astroConfig } from "./presets/astro.js";
|
|
4
|
+
import { cssConfig } from "./presets/css.js";
|
|
5
|
+
import { htmlConfig } from "./presets/html.js";
|
|
6
|
+
import { javascriptConfig } from "./presets/javascript.js";
|
|
7
|
+
import { jsonConfig } from "./presets/json.js";
|
|
8
|
+
import { jsoncConfig } from "./presets/jsonc.js";
|
|
9
|
+
import { markdownTsConfig } from "./presets/markdown-ts.js";
|
|
10
|
+
import { markdownConfig } from "./presets/markdown.js";
|
|
11
|
+
import { tomlConfig } from "./presets/toml.js";
|
|
12
|
+
import { typescriptConfig } from "./presets/typescript.js";
|
|
13
|
+
import { yamlConfig } from "./presets/yaml.js";
|
|
14
|
+
export function composeBaseConfig() {
|
|
15
|
+
return defineConfig([
|
|
16
|
+
ignores,
|
|
17
|
+
javascriptConfig,
|
|
18
|
+
typescriptConfig,
|
|
19
|
+
astroConfig,
|
|
20
|
+
jsonConfig,
|
|
21
|
+
jsoncConfig,
|
|
22
|
+
markdownConfig,
|
|
23
|
+
markdownTsConfig,
|
|
24
|
+
yamlConfig,
|
|
25
|
+
tomlConfig,
|
|
26
|
+
htmlConfig,
|
|
27
|
+
cssConfig,
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import tsParser from "@typescript-eslint/parser";
|
|
2
|
+
import astroParser from "astro-eslint-parser";
|
|
3
|
+
import astro from "eslint-plugin-astro";
|
|
4
|
+
import globals from "globals";
|
|
5
|
+
export const astroConfig = {
|
|
6
|
+
name: "astro",
|
|
7
|
+
files: ["**/*.astro"],
|
|
8
|
+
plugins: { astro },
|
|
9
|
+
languageOptions: {
|
|
10
|
+
globals: {
|
|
11
|
+
...globals.node,
|
|
12
|
+
...globals.astro,
|
|
13
|
+
},
|
|
14
|
+
parser: astroParser,
|
|
15
|
+
parserOptions: {
|
|
16
|
+
parser: tsParser,
|
|
17
|
+
extraFileExtensions: [".astro"],
|
|
18
|
+
sourceType: "module",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
extends: [astro.configs.recommended],
|
|
22
|
+
rules: {
|
|
23
|
+
"no-unreachable": "off",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import css from "@eslint/css";
|
|
2
|
+
import tailwindcss from "@poupe/eslint-plugin-tailwindcss";
|
|
3
|
+
export const cssConfig = {
|
|
4
|
+
files: ["**/*.css"],
|
|
5
|
+
extends: [css.configs.recommended, tailwindcss.configs["recommended"]],
|
|
6
|
+
name: "css",
|
|
7
|
+
rules: {
|
|
8
|
+
"css/no-invalid-at-rules": "off",
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
export const javascriptConfig = {
|
|
4
|
+
extends: [js.configs.recommended],
|
|
5
|
+
files: ["**/*.{js,mjs,cjs,jsx}"],
|
|
6
|
+
languageOptions: {
|
|
7
|
+
globals: {
|
|
8
|
+
...globals.browser,
|
|
9
|
+
...globals.node,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
name: "javascript",
|
|
13
|
+
plugins: { js },
|
|
14
|
+
rules: {
|
|
15
|
+
"no-console": "warn",
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import json from "@eslint/json";
|
|
2
|
+
import { JSONC_FILES } from "../jsonc-files.js";
|
|
3
|
+
export const jsonConfig = {
|
|
4
|
+
extends: [json.configs.recommended],
|
|
5
|
+
files: ["**/*.json"],
|
|
6
|
+
ignores: JSONC_FILES,
|
|
7
|
+
language: "json/json",
|
|
8
|
+
name: "json",
|
|
9
|
+
plugins: { json },
|
|
10
|
+
rules: {
|
|
11
|
+
"json/no-duplicate-keys": "error",
|
|
12
|
+
"json/no-empty-keys": "error",
|
|
13
|
+
"json/no-unnormalized-keys": "error",
|
|
14
|
+
"json/no-unsafe-values": "error",
|
|
15
|
+
"json/sort-keys": "off",
|
|
16
|
+
"json/top-level-interop": "error",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import json from "@eslint/json";
|
|
2
|
+
import { JSONC_FILES } from "../jsonc-files.js";
|
|
3
|
+
export const jsoncConfig = {
|
|
4
|
+
extends: [json.configs.recommended],
|
|
5
|
+
files: ["**/*.jsonc", ...JSONC_FILES],
|
|
6
|
+
language: "json/jsonc",
|
|
7
|
+
languageOptions: {
|
|
8
|
+
allowComments: true,
|
|
9
|
+
allowTrailingCommas: true,
|
|
10
|
+
},
|
|
11
|
+
name: "jsonc",
|
|
12
|
+
plugins: { json },
|
|
13
|
+
rules: {
|
|
14
|
+
"json/no-duplicate-keys": "error",
|
|
15
|
+
"json/no-empty-keys": "error",
|
|
16
|
+
"json/no-unnormalized-keys": "error",
|
|
17
|
+
"json/no-unsafe-values": "error",
|
|
18
|
+
"json/sort-keys": "off",
|
|
19
|
+
"json/top-level-interop": "error",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const markdownTsConfig = {
|
|
2
|
+
name: "markdown-typescript",
|
|
3
|
+
files: ["**/*.md/*.ts", "**/*.md/*.tsx"],
|
|
4
|
+
rules: {
|
|
5
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
6
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
7
|
+
"@typescript-eslint/no-unused-expressions": "off",
|
|
8
|
+
},
|
|
9
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import markdown from "@eslint/markdown";
|
|
2
|
+
export const markdownConfig = {
|
|
3
|
+
extends: [markdown.configs.recommended],
|
|
4
|
+
files: ["**/*.md"],
|
|
5
|
+
name: "markdown",
|
|
6
|
+
plugins: { markdown },
|
|
7
|
+
settings: { "markdown/frontmatter": true },
|
|
8
|
+
rules: {
|
|
9
|
+
"markdown/no-missing-label-refs": "off",
|
|
10
|
+
"markdown/no-bare-urls": "error",
|
|
11
|
+
"markdown/no-duplicate-headings": ["error", { checkSiblingsOnly: true }],
|
|
12
|
+
"markdown/no-empty-images": "error",
|
|
13
|
+
"markdown/no-empty-links": "error",
|
|
14
|
+
"markdown/no-missing-link-fragments": "error",
|
|
15
|
+
"markdown/no-multiple-h1": "error",
|
|
16
|
+
"markdown/no-reference-like-urls": "error",
|
|
17
|
+
"markdown/no-reversed-media-syntax": "error",
|
|
18
|
+
"markdown/no-space-in-emphasis": "error",
|
|
19
|
+
"markdown/table-column-count": "error",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import tseslint from "typescript-eslint";
|
|
4
|
+
export const typescriptConfig = {
|
|
5
|
+
extends: [js.configs.recommended, tseslint.configs.recommended],
|
|
6
|
+
files: ["**/*.{ts,mts,cts,tsx}"],
|
|
7
|
+
ignores: ["**/*.md/*.ts", "**/*.md/*.tsx"],
|
|
8
|
+
languageOptions: {
|
|
9
|
+
globals: {
|
|
10
|
+
...globals.browser,
|
|
11
|
+
...globals.node,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
name: "typescript",
|
|
15
|
+
plugins: {
|
|
16
|
+
js,
|
|
17
|
+
tseslint,
|
|
18
|
+
},
|
|
19
|
+
rules: {
|
|
20
|
+
"@typescript-eslint/no-empty-object-type": "off",
|
|
21
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
22
|
+
"@typescript-eslint/no-this-alias": "off",
|
|
23
|
+
"@typescript-eslint/no-unsafe-function-type": "off",
|
|
24
|
+
"@typescript-eslint/no-unused-vars": [
|
|
25
|
+
"error",
|
|
26
|
+
{
|
|
27
|
+
argsIgnorePattern: "^(_.*|.*Schema)$",
|
|
28
|
+
varsIgnorePattern: "^(_.*|.*Schema)$",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"require-yield": "off",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import yaml from "eslint-plugin-yml";
|
|
2
|
+
import * as yamlParser from "yaml-eslint-parser";
|
|
3
|
+
export const yamlConfig = {
|
|
4
|
+
extends: [yaml.configs["flat/recommended"]],
|
|
5
|
+
files: ["**/*.yaml", "**/*.yml"],
|
|
6
|
+
languageOptions: { parser: yamlParser },
|
|
7
|
+
name: "yaml",
|
|
8
|
+
plugins: { yml: yaml },
|
|
9
|
+
rules: {},
|
|
10
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { globalIgnores } from "eslint/config";
|
|
2
|
+
import baseConfig from "./eslint.config.js";
|
|
3
|
+
export function createConfig(options = { version: 1 }) {
|
|
4
|
+
const configs = [];
|
|
5
|
+
if (options.ignores && options.ignores.length > 0) {
|
|
6
|
+
configs.push(globalIgnores(options.ignores));
|
|
7
|
+
}
|
|
8
|
+
for (const base of baseConfig) {
|
|
9
|
+
const clone = { ...base };
|
|
10
|
+
const override = base.name ? options.overrides?.[base.name] : undefined;
|
|
11
|
+
if (override?.rules) {
|
|
12
|
+
clone.rules = {
|
|
13
|
+
...base.rules,
|
|
14
|
+
...override.rules,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
configs.push(clone);
|
|
18
|
+
}
|
|
19
|
+
if (options.configs) {
|
|
20
|
+
configs.push(...options.configs);
|
|
21
|
+
}
|
|
22
|
+
return configs;
|
|
23
|
+
}
|
|
24
|
+
export default createConfig;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync, readFileSync, } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
export function findLinterYaml(startDir) {
|
|
5
|
+
let dir = startDir;
|
|
6
|
+
while (true) {
|
|
7
|
+
const candidate = resolve(dir, ".config", "linter.yaml");
|
|
8
|
+
if (existsSync(candidate)) {
|
|
9
|
+
return candidate;
|
|
10
|
+
}
|
|
11
|
+
const parent = resolve(dir, "..");
|
|
12
|
+
if (parent === dir) {
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
dir = parent;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
export function loadYamlOptions(startDir) {
|
|
20
|
+
const yamlPath = findLinterYaml(startDir);
|
|
21
|
+
if (!yamlPath) {
|
|
22
|
+
return { version: 1 };
|
|
23
|
+
}
|
|
24
|
+
const content = readFileSync(yamlPath, "utf-8");
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = parse(content) || {};
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
throw new Error(`Failed to parse ${yamlPath}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
31
|
+
}
|
|
32
|
+
if (!("version" in parsed)) {
|
|
33
|
+
throw new Error(`Missing required "version" key in ${yamlPath}`);
|
|
34
|
+
}
|
|
35
|
+
const knownKeys = new Set(["version", "ignores", "overrides", "configs"]);
|
|
36
|
+
for (const key of Object.keys(parsed)) {
|
|
37
|
+
if (!knownKeys.has(key)) {
|
|
38
|
+
console.warn(`[@askviraj/linter] Warning: unknown key "${key}" in .config/linter.yaml`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@askviraj/linter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A linter & formatter CLI for JavaScript, TypeScript, Astro, JSON, JSONC, CSS, HTML, Markdown, YAML, TOML, and more.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lint",
|
|
7
|
+
"linter",
|
|
8
|
+
"eslint",
|
|
9
|
+
"cli",
|
|
10
|
+
"command-line",
|
|
11
|
+
"interface",
|
|
12
|
+
"javascript",
|
|
13
|
+
"typescript",
|
|
14
|
+
"astro",
|
|
15
|
+
"json",
|
|
16
|
+
"jsonc",
|
|
17
|
+
"css",
|
|
18
|
+
"html",
|
|
19
|
+
"markdown",
|
|
20
|
+
"yaml",
|
|
21
|
+
"toml"
|
|
22
|
+
],
|
|
23
|
+
"homepage": "https://github.com/virajp/linter",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/virajp/linter/issues"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/virajp/linter.git"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "Viraj Patel",
|
|
34
|
+
"email": "askviraj@gmail.com",
|
|
35
|
+
"url": "https://github.com/virajp"
|
|
36
|
+
},
|
|
37
|
+
"type": "module",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": "./dist/index.js",
|
|
40
|
+
"./auto": "./dist/auto.js"
|
|
41
|
+
},
|
|
42
|
+
"main": "dist/index.js",
|
|
43
|
+
"bin": {
|
|
44
|
+
"linter": "dist/index.js"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist/"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.build.json",
|
|
51
|
+
"prepublishOnly": "bun run build",
|
|
52
|
+
"test": "bun test tests/unit",
|
|
53
|
+
"test:all": "bun run build && bun test",
|
|
54
|
+
"test:integration": "bun run build && bun test tests/integration"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@eslint/css": "1.1.0",
|
|
58
|
+
"@eslint/js": "10.0.1",
|
|
59
|
+
"@eslint/json": "1.2.0",
|
|
60
|
+
"@eslint/markdown": "8.0.1",
|
|
61
|
+
"@html-eslint/eslint-plugin": "0.59.0",
|
|
62
|
+
"@poupe/eslint-plugin-tailwindcss": "0.3.1",
|
|
63
|
+
"@typescript-eslint/parser": "8.59.1",
|
|
64
|
+
"astro-eslint-parser": "1.4.0",
|
|
65
|
+
"eslint": "10.2.1",
|
|
66
|
+
"eslint-plugin-astro": "1.7.0",
|
|
67
|
+
"eslint-plugin-toml": "1.3.1",
|
|
68
|
+
"eslint-plugin-yml": "3.3.1",
|
|
69
|
+
"globals": "17.5.0",
|
|
70
|
+
"jsonc-parser": "3.3.1",
|
|
71
|
+
"typescript-eslint": "8.59.1",
|
|
72
|
+
"yaml": "2.8.3",
|
|
73
|
+
"yaml-eslint-parser": "2.0.0"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@bun-security-scanner/osv": "1.0.0",
|
|
77
|
+
"@types/bun": "1.3.13",
|
|
78
|
+
"@types/node": "25.6.0",
|
|
79
|
+
"sort-package-json": "3.6.1",
|
|
80
|
+
"typescript": "6.0.3"
|
|
81
|
+
},
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"eslint": "^10.0.0"
|
|
84
|
+
}
|
|
85
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# @askviraj/linter
|
|
2
|
+
|
|
3
|
+
A self-contained linter CLI for JavaScript, TypeScript, Astro, JSON, JSONC, CSS,
|
|
4
|
+
HTML, Markdown, YAML, TOML, and more. It bundles ESLint and all plugins so you
|
|
5
|
+
don't have to install them in every project.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Zero-config** — Works out of the box with an opinionated flat config
|
|
10
|
+
- **Self-contained** — Bundles ESLint and all plugins; no peer dependency
|
|
11
|
+
headaches
|
|
12
|
+
- **Multi-language** — One command lints 10+ file types
|
|
13
|
+
- **Fast** — Optional caching and native Bun support
|
|
14
|
+
- **VS Code ready** — First-class integration with the official ESLint extension
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
No installation required for one-off use:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Via Bun
|
|
22
|
+
bunx --bun @askviraj/linter
|
|
23
|
+
|
|
24
|
+
# Via npx
|
|
25
|
+
npx @askviraj/linter
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Install locally for faster repeated runs:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# With Bun
|
|
32
|
+
bun install --dev @askviraj/linter
|
|
33
|
+
|
|
34
|
+
# With npm
|
|
35
|
+
npm install --save-dev @askviraj/linter
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Lint the current directory
|
|
42
|
+
bunx --bun @askviraj/linter
|
|
43
|
+
|
|
44
|
+
# Lint specific files or directories
|
|
45
|
+
bunx --bun @askviraj/linter src/ tests/
|
|
46
|
+
|
|
47
|
+
# Auto-fix issues
|
|
48
|
+
bunx --bun @askviraj/linter --fix
|
|
49
|
+
|
|
50
|
+
# Scaffold config files for your project
|
|
51
|
+
bunx --bun @askviraj/linter --init
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Supported File Types
|
|
55
|
+
|
|
56
|
+
| Language | Extensions |
|
|
57
|
+
| ---------- | ------------------------------------------- |
|
|
58
|
+
| JavaScript | `.js`, `.mjs`, `.cjs`, `.jsx` |
|
|
59
|
+
| TypeScript | `.ts`, `.mts`, `.cts`, `.tsx` |
|
|
60
|
+
| Astro | `.astro` |
|
|
61
|
+
| JSON | `.json` |
|
|
62
|
+
| JSONC | `.jsonc`, `tsconfig.json`, `.vscode/*.json` |
|
|
63
|
+
| Markdown | `.md` |
|
|
64
|
+
| CSS | `.css` |
|
|
65
|
+
| YAML | `.yaml`, `.yml` |
|
|
66
|
+
| TOML | `.toml` |
|
|
67
|
+
| HTML | `.html` |
|
|
68
|
+
|
|
69
|
+
## CLI Reference
|
|
70
|
+
|
|
71
|
+
| Flag | Short | Description |
|
|
72
|
+
| ------------------------- | ----- | -------------------------------------------------------------------------------- |
|
|
73
|
+
| `--init` | | Scaffold `eslint.config.mjs`, `.config/linter.yaml`, and `.vscode/settings.json` |
|
|
74
|
+
| `--force` | | Overwrite existing files when used with `--init` |
|
|
75
|
+
| `--fix` | | Automatically fix problems |
|
|
76
|
+
| `--cache` | | Only check changed files |
|
|
77
|
+
| `--cache-location <path>` | | Path to the cache file or directory |
|
|
78
|
+
| `--no-ignore` | | Disable use of ignore files |
|
|
79
|
+
| `--quiet` | | Report errors only |
|
|
80
|
+
| `--debug` | | Enable ESLint debug logging |
|
|
81
|
+
| `--format`, `-f <name>` | | Use a specific output format |
|
|
82
|
+
| `--max-warnings <n>` | | Number of warnings to trigger nonzero exit code |
|
|
83
|
+
| `--version`, `-v` | | Show version and plugin versions |
|
|
84
|
+
| `--help`, `-h` | | Show help |
|
|
85
|
+
|
|
86
|
+
Positional arguments are treated as file or directory targets. Defaults to `.`
|
|
87
|
+
when none are provided. Use `--` to pass file names that look like flags (for
|
|
88
|
+
example, `linter -- --fix.js`).
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
### `.config/linter.yaml`
|
|
93
|
+
|
|
94
|
+
Create this file in your repo to customize the bundled config:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
version: 1
|
|
98
|
+
|
|
99
|
+
ignores:
|
|
100
|
+
- "**/generated/**"
|
|
101
|
+
- "**/*.d.ts"
|
|
102
|
+
|
|
103
|
+
overrides:
|
|
104
|
+
typescript:
|
|
105
|
+
rules:
|
|
106
|
+
"@typescript-eslint/no-explicit-any": "error"
|
|
107
|
+
|
|
108
|
+
configs:
|
|
109
|
+
- name: "custom"
|
|
110
|
+
files: [ "**/*.test.ts" ]
|
|
111
|
+
rules:
|
|
112
|
+
"no-console": "off"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Supported keys
|
|
116
|
+
|
|
117
|
+
| Key | Type | Description |
|
|
118
|
+
| ----------- | --------------------------- | --------------------------------------------------------- |
|
|
119
|
+
| `version` | `number \| string` | **Required.** Schema version (currently `1`). |
|
|
120
|
+
| `ignores` | `string[]` | Additional global ignore patterns. |
|
|
121
|
+
| `overrides` | `Record<string, { rules }>` | Rule overrides keyed by preset name. |
|
|
122
|
+
| `configs` | `unknown[]` | Raw ESLint config objects appended after the base config. |
|
|
123
|
+
|
|
124
|
+
#### Preset names for `overrides`
|
|
125
|
+
|
|
126
|
+
Use these names in the `overrides` section:
|
|
127
|
+
|
|
128
|
+
- `javascript`
|
|
129
|
+
- `typescript`
|
|
130
|
+
- `astro`
|
|
131
|
+
- `json`
|
|
132
|
+
- `jsonc`
|
|
133
|
+
- `markdown`
|
|
134
|
+
- `markdown-typescript`
|
|
135
|
+
- `yaml`
|
|
136
|
+
- `toml`
|
|
137
|
+
- `html`
|
|
138
|
+
- `css`
|
|
139
|
+
|
|
140
|
+
### Scaffolding
|
|
141
|
+
|
|
142
|
+
Run `--init` to create the following files in your project:
|
|
143
|
+
|
|
144
|
+
- `eslint.config.mjs` — imports `@askviraj/linter/auto`
|
|
145
|
+
- `.config/linter.yaml` — your customization file
|
|
146
|
+
- `.vscode/settings.json` — enables flat config mode
|
|
147
|
+
|
|
148
|
+
Use `--force` to overwrite existing files.
|
|
149
|
+
|
|
150
|
+
## VS Code Integration
|
|
151
|
+
|
|
152
|
+
The package exports `@askviraj/linter/auto` for use with the
|
|
153
|
+
`dbaeumer.vscode-eslint` extension.
|
|
154
|
+
|
|
155
|
+
After running `--init`, VS Code discovers `eslint.config.mjs` and loads the
|
|
156
|
+
bundled config automatically. Edit `.config/linter.yaml` to customize rules; the
|
|
157
|
+
extension picks up changes without restarting.
|
|
158
|
+
|
|
159
|
+
## How It Works
|
|
160
|
+
|
|
161
|
+
1. `npx` or `bunx` installs `@askviraj/linter` and its dependencies in a
|
|
162
|
+
temporary or local `node_modules`.
|
|
163
|
+
2. The bundled CLI reads `.config/linter.yaml` from the target repo (if present)
|
|
164
|
+
to load user customizations.
|
|
165
|
+
3. It builds the ESLint config by merging YAML overrides with the bundled base
|
|
166
|
+
config.
|
|
167
|
+
4. ESLint's programmatic API lints the current working directory. Plugins are
|
|
168
|
+
resolved from the package's own `node_modules`, not the target repo's.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT — see [LICENSE](./LICENSE) for details.
|