neatlint 2.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 +63 -0
- package/dist/cli.js +229 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StablDev
|
|
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/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# neatlint
|
|
2
|
+
|
|
3
|
+
A command-line interface (CLI) tool designed to automatically and seamlessly set up and configure ESLint and Prettier for modern JavaScript and TypeScript projects.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Configuring linting and formatting tools for new or existing projects can be tedious and prone to configuration conflicts. `neatlint` automates the entire setup process. It detects your package manager, initializes ESLint, installs Prettier, writes default configurations, patches configuration files to avoid rule conflicts, and adds execution scripts to your `package.json`.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Node.js version `^20.19.0`, `^22.13.0`, or `>=24`.
|
|
12
|
+
|
|
13
|
+
## Installation and Usage
|
|
14
|
+
|
|
15
|
+
To set up ESLint and Prettier in your project directory, run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx neatlint init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Alternatively, if you are using specific package managers, you can use:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Using bun
|
|
25
|
+
bunx neatlint init
|
|
26
|
+
# Using pnpm
|
|
27
|
+
pnpm dlx neatlint init
|
|
28
|
+
# Using yarn
|
|
29
|
+
yarn dlx neatlint init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## What neatlint Does
|
|
33
|
+
|
|
34
|
+
### 1. Runs ESLint Initialization
|
|
35
|
+
It invokes the interactive ESLint initialization tool appropriate for your package manager to guide you through creating an ESLint configuration.
|
|
36
|
+
|
|
37
|
+
### 2. Installs Dependencies
|
|
38
|
+
It installs the following packages as development dependencies:
|
|
39
|
+
- `prettier`
|
|
40
|
+
- `eslint-config-prettier`
|
|
41
|
+
|
|
42
|
+
### 3. Generates `.prettierrc`
|
|
43
|
+
Creates a `.prettierrc` file with the following default configuration:
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"semi": true,
|
|
47
|
+
"singleQuote": false,
|
|
48
|
+
"trailingComma": "es5"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 4. Patches ESLint Flat Config
|
|
53
|
+
Locates your ESLint configuration file (`eslint.config.js`, `eslint.config.mjs`, `eslint.config.ts`, or `eslint.config.mts`) and integrates `eslint-config-prettier` by importing and appending it to the export array, preventing formatting conflicts.
|
|
54
|
+
|
|
55
|
+
### 5. Configures `package.json` Scripts
|
|
56
|
+
Appends the following scripts to your `package.json` (skipping any script name that already exists):
|
|
57
|
+
- `"lint": "eslint ."` - Lints the workspace.
|
|
58
|
+
- `"format": "prettier . --write"` - Formats all files.
|
|
59
|
+
- `"format:check": "prettier . --check"` - Checks file formatting.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import cac from "cac";
|
|
5
|
+
import pc6 from "picocolors";
|
|
6
|
+
|
|
7
|
+
// src/package-manager.ts
|
|
8
|
+
function detectPackageManager() {
|
|
9
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
10
|
+
if (userAgent) {
|
|
11
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
12
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
13
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
14
|
+
if (userAgent.startsWith("npm")) return "npm";
|
|
15
|
+
}
|
|
16
|
+
return "npm";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/eslint-init.ts
|
|
20
|
+
import { execa } from "execa";
|
|
21
|
+
import pc from "picocolors";
|
|
22
|
+
async function runEslintInit(pm, cwd) {
|
|
23
|
+
let command;
|
|
24
|
+
let args;
|
|
25
|
+
switch (pm) {
|
|
26
|
+
case "npm":
|
|
27
|
+
command = "npm";
|
|
28
|
+
args = ["init", "@eslint/config@latest"];
|
|
29
|
+
break;
|
|
30
|
+
case "pnpm":
|
|
31
|
+
command = "pnpm";
|
|
32
|
+
args = ["create", "@eslint/config@latest"];
|
|
33
|
+
break;
|
|
34
|
+
case "yarn":
|
|
35
|
+
command = "yarn";
|
|
36
|
+
args = ["create", "@eslint/config"];
|
|
37
|
+
break;
|
|
38
|
+
case "bun":
|
|
39
|
+
command = "bunx";
|
|
40
|
+
args = ["@eslint/create-config@latest"];
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
console.log(pc.cyan("Running ESLint initializer..."));
|
|
44
|
+
await execa(command, args, {
|
|
45
|
+
cwd,
|
|
46
|
+
stdio: "inherit"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/install.ts
|
|
51
|
+
import { execa as execa2 } from "execa";
|
|
52
|
+
import pc2 from "picocolors";
|
|
53
|
+
async function installPrettier(pm, cwd) {
|
|
54
|
+
let command;
|
|
55
|
+
let args;
|
|
56
|
+
const deps = ["prettier", "eslint-config-prettier"];
|
|
57
|
+
switch (pm) {
|
|
58
|
+
case "npm":
|
|
59
|
+
command = "npm";
|
|
60
|
+
args = ["install", "-D", ...deps];
|
|
61
|
+
break;
|
|
62
|
+
case "pnpm":
|
|
63
|
+
command = "pnpm";
|
|
64
|
+
args = ["add", "-D", ...deps];
|
|
65
|
+
break;
|
|
66
|
+
case "yarn":
|
|
67
|
+
command = "yarn";
|
|
68
|
+
args = ["add", "-D", ...deps];
|
|
69
|
+
break;
|
|
70
|
+
case "bun":
|
|
71
|
+
command = "bun";
|
|
72
|
+
args = ["add", "-d", ...deps];
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
console.log(pc2.cyan("Installing Prettier..."));
|
|
76
|
+
await execa2(command, args, {
|
|
77
|
+
cwd,
|
|
78
|
+
stdio: "inherit"
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/prettier.ts
|
|
83
|
+
import fs from "fs/promises";
|
|
84
|
+
import path from "path";
|
|
85
|
+
import pc3 from "picocolors";
|
|
86
|
+
async function createPrettierRc(cwd) {
|
|
87
|
+
const prettierrcPath = path.join(cwd, ".prettierrc");
|
|
88
|
+
const content = {
|
|
89
|
+
semi: true,
|
|
90
|
+
singleQuote: false,
|
|
91
|
+
trailingComma: "es5"
|
|
92
|
+
};
|
|
93
|
+
console.log(pc3.cyan("Writing .prettierrc..."));
|
|
94
|
+
await fs.writeFile(prettierrcPath, JSON.stringify(content, null, 2) + "\n", "utf8");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/patch-eslint.ts
|
|
98
|
+
import fs2 from "fs/promises";
|
|
99
|
+
import path2 from "path";
|
|
100
|
+
import pc4 from "picocolors";
|
|
101
|
+
async function patchEslintConfig(cwd) {
|
|
102
|
+
console.log(pc4.cyan("Patching ESLint config..."));
|
|
103
|
+
const configFiles = [
|
|
104
|
+
"eslint.config.js",
|
|
105
|
+
"eslint.config.mjs",
|
|
106
|
+
"eslint.config.ts",
|
|
107
|
+
"eslint.config.mts"
|
|
108
|
+
];
|
|
109
|
+
let configPath = null;
|
|
110
|
+
let content = "";
|
|
111
|
+
for (const file of configFiles) {
|
|
112
|
+
const fullPath = path2.join(cwd, file);
|
|
113
|
+
try {
|
|
114
|
+
content = await fs2.readFile(fullPath, "utf8");
|
|
115
|
+
configPath = fullPath;
|
|
116
|
+
break;
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const manualSnippet = `
|
|
121
|
+
import eslintConfigPrettier from "eslint-config-prettier";
|
|
122
|
+
|
|
123
|
+
export default [
|
|
124
|
+
// existing config
|
|
125
|
+
eslintConfigPrettier,
|
|
126
|
+
];`;
|
|
127
|
+
if (!configPath) {
|
|
128
|
+
console.log(pc4.yellow("Warning: ESLint flat config file not found."));
|
|
129
|
+
console.log(pc4.yellow("Please add prettier manually:"));
|
|
130
|
+
console.log(pc4.white(manualSnippet));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (content.includes("eslint-config-prettier")) {
|
|
134
|
+
console.log(pc4.green("ESLint config already contains eslint-config-prettier."));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const exportDefaultRegex = /export\s+default\s+(?:[\w.]+\s*\()?\s*\[/;
|
|
138
|
+
if (!exportDefaultRegex.test(content)) {
|
|
139
|
+
console.log(pc4.yellow(`Warning: Could not automatically patch ${path2.basename(configPath)}.`));
|
|
140
|
+
console.log(pc4.yellow("Please add prettier manually:"));
|
|
141
|
+
console.log(pc4.white(manualSnippet));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const lastBracketIndex = content.lastIndexOf("]");
|
|
145
|
+
if (lastBracketIndex === -1) {
|
|
146
|
+
console.log(pc4.yellow(`Warning: Could not automatically patch ${path2.basename(configPath)}.`));
|
|
147
|
+
console.log(pc4.yellow("Please add prettier manually:"));
|
|
148
|
+
console.log(pc4.white(manualSnippet));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const before = content.slice(0, lastBracketIndex);
|
|
152
|
+
const after = content.slice(lastBracketIndex);
|
|
153
|
+
const trimmedBeforeRight = before.trimEnd();
|
|
154
|
+
const needsComma = trimmedBeforeRight.length > 0 && !trimmedBeforeRight.endsWith(",") && !trimmedBeforeRight.endsWith("[");
|
|
155
|
+
const importStatement = `import eslintConfigPrettier from "eslint-config-prettier";
|
|
156
|
+
`;
|
|
157
|
+
const injection = (needsComma ? "," : "") + "\n eslintConfigPrettier,\n";
|
|
158
|
+
let newContent = trimmedBeforeRight + injection + after;
|
|
159
|
+
const importRegex = /import\s+[\s\S]*?from\s+['"][^'"]+['"];?|import\s+['"][^'"]+['"];?/g;
|
|
160
|
+
let lastImportIndex = 0;
|
|
161
|
+
let match;
|
|
162
|
+
while ((match = importRegex.exec(newContent)) !== null) {
|
|
163
|
+
lastImportIndex = match.index + match[0].length;
|
|
164
|
+
}
|
|
165
|
+
if (lastImportIndex > 0) {
|
|
166
|
+
const beforeImport = newContent.slice(0, lastImportIndex);
|
|
167
|
+
const afterImport = newContent.slice(lastImportIndex).trimStart();
|
|
168
|
+
newContent = beforeImport + "\n" + importStatement + "\n" + afterImport;
|
|
169
|
+
} else {
|
|
170
|
+
newContent = importStatement + "\n" + newContent.trimStart();
|
|
171
|
+
}
|
|
172
|
+
await fs2.writeFile(configPath, newContent, "utf8");
|
|
173
|
+
console.log(pc4.green(`Patched ${path2.basename(configPath)}.`));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/package-json.ts
|
|
177
|
+
import fs3 from "fs/promises";
|
|
178
|
+
import path3 from "path";
|
|
179
|
+
import pc5 from "picocolors";
|
|
180
|
+
async function updatePackageJsonScripts(cwd) {
|
|
181
|
+
console.log(pc5.cyan("Updating package.json..."));
|
|
182
|
+
const pkgPath = path3.join(cwd, "package.json");
|
|
183
|
+
let pkg;
|
|
184
|
+
try {
|
|
185
|
+
const content = await fs3.readFile(pkgPath, "utf8");
|
|
186
|
+
pkg = JSON.parse(content);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.log(pc5.yellow("Warning: Could not read package.json"));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!pkg.scripts) {
|
|
192
|
+
pkg.scripts = {};
|
|
193
|
+
}
|
|
194
|
+
const newScripts = {
|
|
195
|
+
"lint": "eslint .",
|
|
196
|
+
"format": "prettier . --write",
|
|
197
|
+
"format:check": "prettier . --check"
|
|
198
|
+
};
|
|
199
|
+
for (const [key, value] of Object.entries(newScripts)) {
|
|
200
|
+
if (pkg.scripts[key]) {
|
|
201
|
+
console.log(pc5.yellow(`Warning: Script "${key}" already exists. Skipping.`));
|
|
202
|
+
} else {
|
|
203
|
+
pkg.scripts[key] = value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
await fs3.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/cli.ts
|
|
210
|
+
var cli = cac("neatlint");
|
|
211
|
+
cli.command("init", "Set up ESLint and Prettier").action(async () => {
|
|
212
|
+
try {
|
|
213
|
+
const cwd = process.cwd();
|
|
214
|
+
const pm = detectPackageManager();
|
|
215
|
+
console.log(pc6.green(`Using package manager: ${pm}`));
|
|
216
|
+
await runEslintInit(pm, cwd);
|
|
217
|
+
await installPrettier(pm, cwd);
|
|
218
|
+
await createPrettierRc(cwd);
|
|
219
|
+
await patchEslintConfig(cwd);
|
|
220
|
+
await updatePackageJsonScripts(cwd);
|
|
221
|
+
console.log(pc6.green("Done."));
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error(pc6.red("An error occurred:"));
|
|
224
|
+
console.error(err);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
cli.help();
|
|
229
|
+
cli.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neatlint",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "CLI tool to automatically set up ESLint and Prettier for a project.",
|
|
5
|
+
"author": "StablDev <thestabldev@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/stabldev/neatlint.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/stabldev/neatlint#readme",
|
|
12
|
+
"bugs": "https://github.com/stabldev/neatlint/issues",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"provenance": true
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/cli.js",
|
|
18
|
+
"bin": {
|
|
19
|
+
"neatlint": "./dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"eslint",
|
|
29
|
+
"prettier",
|
|
30
|
+
"linter",
|
|
31
|
+
"format",
|
|
32
|
+
"lint",
|
|
33
|
+
"code",
|
|
34
|
+
"style",
|
|
35
|
+
"quality",
|
|
36
|
+
"testing",
|
|
37
|
+
"typescript",
|
|
38
|
+
"javascript"
|
|
39
|
+
],
|
|
40
|
+
"devEngines": {
|
|
41
|
+
"packageManager": {
|
|
42
|
+
"name": "pnpm",
|
|
43
|
+
"version": "^11.1.1",
|
|
44
|
+
"onFail": "download"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"cac": "^7.0.0",
|
|
49
|
+
"execa": "^9.6.1",
|
|
50
|
+
"picocolors": "^1.1.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^25.7.0",
|
|
54
|
+
"tsup": "^8.5.1",
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^6.0.3"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"dev": "tsx src/cli.ts",
|
|
60
|
+
"build": "tsup src/cli.ts --format esm --clean"
|
|
61
|
+
}
|
|
62
|
+
}
|