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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/cli.js +229 -0
  4. 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
+ }