@eimerreis/linting 0.2.0 → 0.3.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/README.md CHANGED
@@ -4,6 +4,8 @@ Personal linting and formatting defaults for new JavaScript/TypeScript projects.
4
4
 
5
5
  Requires Node `^20.19.0 || >=22.12.0`.
6
6
 
7
+ Zero peer-dependency setup for consumers: install only `@eimerreis/linting`.
8
+
7
9
  Built on:
8
10
 
9
11
  - `oxlint` for linting
@@ -19,10 +21,10 @@ Default focus:
19
21
  ## Install
20
22
 
21
23
  ```bash
22
- npm add -D @eimerreis/linting oxlint oxfmt
24
+ npm add -D @eimerreis/linting
23
25
  ```
24
26
 
25
- `lint` also executes `react-doctor` via:
27
+ `lint` also executes react-doctor via:
26
28
 
27
29
  ```bash
28
30
  npx react-doctor -y .
@@ -63,11 +65,25 @@ eimerreis-linting format [targetDir] [--check]
63
65
  ```
64
66
 
65
67
  - `init --force`: overwrite existing `.oxlintrc.json` / `.oxfmtrc.json` and script values
66
- - `lint --fix`: run `oxlint --fix .` and then `npx react-doctor -y .`
68
+ - `lint --fix`: run `oxlint --fix .` and then run react-doctor
67
69
  - `format --check`: run `oxfmt --check .`
68
70
 
69
71
  `react-doctor` runs only when the target package has `react`, `react-dom`, or `next` in dependencies/devDependencies/peerDependencies.
70
72
 
73
+ ### First-time one-shot usage
74
+
75
+ You do not need to run `init` first.
76
+
77
+ ```bash
78
+ npx @eimerreis/linting lint
79
+ npx @eimerreis/linting format --check
80
+ ```
81
+
82
+ Behavior:
83
+
84
+ - If project config exists (`.oxlintrc.*` / `oxlint.config.*`, `.oxfmtrc.*` / `oxfmt.config.*`), it uses that.
85
+ - If not, it falls back to the package's built-in defaults.
86
+
71
87
  ## Manual Setup
72
88
 
73
89
  If you do not want to use the init script, create the config files manually.
@@ -76,7 +92,7 @@ If you do not want to use the init script, create the config files manually.
76
92
 
77
93
  ```json
78
94
  {
79
- "extends": ["./node_modules/@eimerreis/linting/oxlint.config.json"]
95
+ "extends": ["./node_modules/@eimerreis/linting/oxlint.config.json"]
80
96
  }
81
97
  ```
82
98
 
@@ -84,6 +100,55 @@ If you do not want to use the init script, create the config files manually.
84
100
 
85
101
  ```json
86
102
  {
87
- "extends": ["./node_modules/@eimerreis/linting/oxfmt.config.json"]
103
+ "extends": ["./node_modules/@eimerreis/linting/oxfmt.config.json"]
88
104
  }
89
105
  ```
106
+
107
+ ## Scripts
108
+
109
+ Generated project scripts:
110
+
111
+ ```json
112
+ {
113
+ "scripts": {
114
+ "lint": "eimerreis-linting lint",
115
+ "lint:fix": "eimerreis-linting lint --fix",
116
+ "format": "eimerreis-linting format",
117
+ "format:check": "eimerreis-linting format --check"
118
+ }
119
+ }
120
+ ```
121
+
122
+ `init` also adds this dev dependency automatically (if missing):
123
+
124
+ ```json
125
+ {
126
+ "devDependencies": {
127
+ "@eimerreis/linting": "^<current-version>"
128
+ }
129
+ }
130
+ ```
131
+
132
+ ## Publish
133
+
134
+ ### Git-based release workflow (Changesets + GitHub Actions)
135
+
136
+ 1. Create a changeset:
137
+
138
+ ```bash
139
+ npm run changeset
140
+ ```
141
+
142
+ 2. Commit `.changeset/*.md` and push to `main`.
143
+ 3. Workflow `Release` checks for pending changesets.
144
+ 4. If changesets exist, it versions, publishes via npm trusted publishing (OIDC), and commits `chore: version packages`.
145
+
146
+ Trusted publishing setup (one-time on npmjs.com):
147
+
148
+ 1. Package `@eimerreis/linting` -> Settings -> Trusted Publisher
149
+ 2. Provider: GitHub Actions
150
+ 3. Organization/user: `eimerreis`
151
+ 4. Repository: `eimerreis-linting`
152
+ 5. Workflow filename: `release.yml`
153
+
154
+ No `NPM_TOKEN` secret is required for publishing.
package/bin/init.mjs CHANGED
@@ -2,255 +2,396 @@
2
2
 
3
3
  import { spawn } from "node:child_process";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { fileURLToPath } from "node:url";
5
+ import { createRequire } from "node:module";
6
6
  import { dirname, resolve } from "node:path";
7
7
  import process from "node:process";
8
+ import { fileURLToPath } from "node:url";
8
9
 
9
10
  const packageName = "@eimerreis/linting";
10
11
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
+ const requireFromHere = createRequire(import.meta.url);
11
13
 
12
14
  function ensureDirectory(filePath) {
13
- const dir = dirname(filePath);
14
- if (!existsSync(dir)) {
15
- mkdirSync(dir, { recursive: true });
16
- }
15
+ const dir = dirname(filePath);
16
+ if (!existsSync(dir)) {
17
+ mkdirSync(dir, { recursive: true });
18
+ }
17
19
  }
18
20
 
19
21
  function writeJson(filePath, data) {
20
- ensureDirectory(filePath);
21
- writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
22
+ ensureDirectory(filePath);
23
+ writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
22
24
  }
23
25
 
24
26
  function readJson(filePath) {
25
- return JSON.parse(readFileSync(filePath, "utf8"));
27
+ return JSON.parse(readFileSync(filePath, "utf8"));
26
28
  }
27
29
 
28
- function printUsage() {
29
- console.log("Usage:");
30
- console.log(" eimerreis-linting init [targetDir] [--force]");
31
- console.log(" eimerreis-linting lint [targetDir] [--fix]");
32
- console.log(" eimerreis-linting format [targetDir] [--check]");
30
+ function getSelfDependencyRange() {
31
+ try {
32
+ const selfPackageJsonPath = resolve(packageRoot, "package.json");
33
+ const selfPackageJson = readJson(selfPackageJsonPath);
34
+ if (selfPackageJson.version) {
35
+ return `^${selfPackageJson.version}`;
36
+ }
37
+ } catch {
38
+ // ignore
39
+ }
40
+
41
+ return "latest";
33
42
  }
34
43
 
35
- function parseCommand(rawArgs) {
36
- const firstArg = rawArgs[0];
37
- if (!firstArg || firstArg === "init" || firstArg.startsWith("-")) {
38
- return {
39
- command: "init",
40
- args: firstArg === "init" ? rawArgs.slice(1) : rawArgs,
41
- };
42
- }
44
+ function resolvePackageBin(dependencyName, binRelativePath) {
45
+ const entryPointPath = requireFromHere.resolve(dependencyName);
46
+ let cursor = dirname(entryPointPath);
47
+
48
+ while (cursor !== dirname(cursor)) {
49
+ const packageJsonPath = resolve(cursor, "package.json");
50
+ if (existsSync(packageJsonPath)) {
51
+ const packageJson = readJson(packageJsonPath);
52
+ if (packageJson.name === dependencyName) {
53
+ const binPath = resolve(cursor, binRelativePath);
54
+ if (!existsSync(binPath)) {
55
+ throw new Error(`Cannot find ${dependencyName} binary at ${binPath}`);
56
+ }
57
+ return binPath;
58
+ }
59
+ }
60
+ cursor = dirname(cursor);
61
+ }
43
62
 
44
- return {
45
- command: firstArg,
46
- args: rawArgs.slice(1),
47
- };
63
+ throw new Error(`Unable to resolve package root for ${dependencyName}`);
48
64
  }
49
65
 
50
- function parsePathAndFlags(args, allowedFlags) {
51
- let targetDirArg;
52
- const flags = new Set();
53
-
54
- for (const arg of args) {
55
- if (arg.startsWith("-")) {
56
- if (!allowedFlags.includes(arg)) {
57
- throw new Error(`Unknown flag: ${arg}`);
58
- }
59
- flags.add(arg);
60
- continue;
66
+ function resolvePackageEntry(dependencyName) {
67
+ return requireFromHere.resolve(dependencyName);
68
+ }
69
+
70
+ function hasInstalledPackage(dependencyName) {
71
+ try {
72
+ resolvePackageEntry(dependencyName);
73
+ return true;
74
+ } catch {
75
+ return false;
61
76
  }
77
+ }
62
78
 
63
- if (targetDirArg) {
64
- throw new Error(`Unexpected argument: ${arg}`);
79
+ function resolveExistingConfigPath(targetDir, configCandidates) {
80
+ for (const candidate of configCandidates) {
81
+ const candidatePath = resolve(targetDir, candidate);
82
+ if (existsSync(candidatePath)) {
83
+ return candidatePath;
84
+ }
65
85
  }
66
86
 
67
- targetDirArg = arg;
68
- }
87
+ return null;
88
+ }
69
89
 
70
- return {
71
- targetDir: resolve(process.cwd(), targetDirArg ?? "."),
72
- flags,
73
- };
90
+ function resolveLintConfigPath(targetDir) {
91
+ const localConfigPath = resolveExistingConfigPath(targetDir, [
92
+ ".oxlintrc.json",
93
+ ".oxlintrc.jsonc",
94
+ ".oxlintrc.js",
95
+ ".oxlintrc.mjs",
96
+ ".oxlintrc.cjs",
97
+ ".oxlintrc.ts",
98
+ ".oxlintrc.mts",
99
+ ".oxlintrc.cts",
100
+ "oxlint.config.js",
101
+ "oxlint.config.mjs",
102
+ "oxlint.config.cjs",
103
+ "oxlint.config.ts",
104
+ "oxlint.config.mts",
105
+ "oxlint.config.cts",
106
+ ]);
107
+
108
+ if (localConfigPath) {
109
+ return localConfigPath;
110
+ }
111
+
112
+ return resolve(packageRoot, "oxlint.config.json");
74
113
  }
75
114
 
76
- function runCommand(command, commandArgs, cwd) {
77
- return new Promise((resolvePromise) => {
78
- const child = spawn(command, commandArgs, {
79
- cwd,
80
- stdio: "inherit",
81
- shell: process.platform === "win32",
82
- });
115
+ function resolveFormatConfigPath(targetDir) {
116
+ const localConfigPath = resolveExistingConfigPath(targetDir, [
117
+ ".oxfmtrc.json",
118
+ ".oxfmtrc.jsonc",
119
+ ".oxfmtrc.js",
120
+ ".oxfmtrc.mjs",
121
+ ".oxfmtrc.cjs",
122
+ ".oxfmtrc.ts",
123
+ ".oxfmtrc.mts",
124
+ ".oxfmtrc.cts",
125
+ "oxfmt.config.js",
126
+ "oxfmt.config.mjs",
127
+ "oxfmt.config.cjs",
128
+ "oxfmt.config.ts",
129
+ "oxfmt.config.mts",
130
+ "oxfmt.config.cts",
131
+ ]);
132
+
133
+ if (localConfigPath) {
134
+ return localConfigPath;
135
+ }
83
136
 
84
- child.on("error", (error) => {
85
- console.error(`failed to run ${command}: ${error.message}`);
86
- resolvePromise(1);
87
- });
137
+ return resolve(packageRoot, "oxfmt.config.json");
138
+ }
139
+
140
+ function printUsage() {
141
+ console.log("Usage:");
142
+ console.log(" eimerreis-linting init [targetDir] [--force]");
143
+ console.log(" eimerreis-linting lint [targetDir] [--fix]");
144
+ console.log(" eimerreis-linting format [targetDir] [--check]");
145
+ }
146
+
147
+ function parseCommand(rawArgs) {
148
+ const firstArg = rawArgs[0];
149
+ if (!firstArg || firstArg === "init" || firstArg.startsWith("-")) {
150
+ return {
151
+ command: "init",
152
+ args: firstArg === "init" ? rawArgs.slice(1) : rawArgs,
153
+ };
154
+ }
88
155
 
89
- child.on("close", (code) => {
90
- resolvePromise(code ?? 1);
156
+ return {
157
+ command: firstArg,
158
+ args: rawArgs.slice(1),
159
+ };
160
+ }
161
+
162
+ function parsePathAndFlags(args, allowedFlags) {
163
+ let targetDirArg;
164
+ const flags = new Set();
165
+
166
+ for (const arg of args) {
167
+ if (arg.startsWith("-")) {
168
+ if (!allowedFlags.includes(arg)) {
169
+ throw new Error(`Unknown flag: ${arg}`);
170
+ }
171
+ flags.add(arg);
172
+ continue;
173
+ }
174
+
175
+ if (targetDirArg) {
176
+ throw new Error(`Unexpected argument: ${arg}`);
177
+ }
178
+
179
+ targetDirArg = arg;
180
+ }
181
+
182
+ return {
183
+ targetDir: resolve(process.cwd(), targetDirArg ?? "."),
184
+ flags,
185
+ };
186
+ }
187
+
188
+ function runCommand(command, commandArgs, cwd) {
189
+ return new Promise((resolvePromise) => {
190
+ const child = spawn(command, commandArgs, {
191
+ cwd,
192
+ stdio: "inherit",
193
+ shell: process.platform === "win32",
194
+ });
195
+
196
+ child.on("error", (error) => {
197
+ console.error(`failed to run ${command}: ${error.message}`);
198
+ resolvePromise(1);
199
+ });
200
+
201
+ child.on("close", (code) => {
202
+ resolvePromise(code ?? 1);
203
+ });
91
204
  });
92
- });
93
205
  }
94
206
 
95
207
  function hasReactProject(cwd) {
96
- const packageJsonPath = resolve(cwd, "package.json");
208
+ const packageJsonPath = resolve(cwd, "package.json");
97
209
 
98
- if (!existsSync(packageJsonPath)) {
99
- return false;
100
- }
210
+ if (!existsSync(packageJsonPath)) {
211
+ return false;
212
+ }
101
213
 
102
- try {
103
- const packageJson = readJson(packageJsonPath);
104
- const dependencyFields = ["dependencies", "devDependencies", "peerDependencies"];
214
+ try {
215
+ const packageJson = readJson(packageJsonPath);
216
+ const dependencyFields = ["dependencies", "devDependencies", "peerDependencies"];
105
217
 
106
- return dependencyFields.some((field) => {
107
- const deps = packageJson[field];
108
- return Boolean(deps?.react || deps?.["react-dom"] || deps?.next);
109
- });
110
- } catch {
111
- return false;
112
- }
218
+ return dependencyFields.some((field) => {
219
+ const deps = packageJson[field];
220
+ return Boolean(deps?.react || deps?.["react-dom"] || deps?.next);
221
+ });
222
+ } catch {
223
+ return false;
224
+ }
113
225
  }
114
226
 
115
227
  function upsertScript(packageJson, name, command, force) {
116
- if (!packageJson.scripts || typeof packageJson.scripts !== "object") {
117
- packageJson.scripts = {};
118
- }
119
- if (!packageJson.scripts[name] || force) {
120
- packageJson.scripts[name] = command;
121
- }
228
+ if (!packageJson.scripts || typeof packageJson.scripts !== "object") {
229
+ packageJson.scripts = {};
230
+ }
231
+ if (!packageJson.scripts[name] || force) {
232
+ packageJson.scripts[name] = command;
233
+ }
122
234
  }
123
235
 
124
236
  function createExtendsConfig(targetPath, exportPath, force) {
125
- if (existsSync(targetPath) && !force) {
126
- console.log(`skip ${targetPath} (already exists)`);
127
- return;
128
- }
129
-
130
- writeJson(targetPath, {
131
- extends: [`./node_modules/${packageName}/${exportPath}`],
132
- });
133
- console.log(`write ${targetPath}`);
237
+ if (existsSync(targetPath) && !force) {
238
+ console.log(`skip ${targetPath} (already exists)`);
239
+ return;
240
+ }
241
+
242
+ writeJson(targetPath, {
243
+ extends: [`./node_modules/${packageName}/${exportPath}`],
244
+ });
245
+ console.log(`write ${targetPath}`);
134
246
  }
135
247
 
136
248
  function maybeUpdatePackageJson(targetPackageJsonPath, force) {
137
- if (!existsSync(targetPackageJsonPath)) {
138
- console.log("skip package.json (not found)");
139
- return;
140
- }
249
+ if (!existsSync(targetPackageJsonPath)) {
250
+ console.log("skip package.json (not found)");
251
+ return;
252
+ }
141
253
 
142
- const packageJson = readJson(targetPackageJsonPath);
254
+ const packageJson = readJson(targetPackageJsonPath);
143
255
 
144
- upsertScript(packageJson, "lint", "eimerreis-linting lint", force);
145
- upsertScript(packageJson, "lint:fix", "eimerreis-linting lint --fix", force);
146
- upsertScript(packageJson, "format", "eimerreis-linting format", force);
147
- upsertScript(packageJson, "format:check", "eimerreis-linting format --check", force);
256
+ if (!packageJson.devDependencies || typeof packageJson.devDependencies !== "object") {
257
+ packageJson.devDependencies = {};
258
+ }
148
259
 
149
- writeJson(targetPackageJsonPath, packageJson);
150
- console.log(`update ${targetPackageJsonPath}`);
260
+ const selfDependencyRange = getSelfDependencyRange();
261
+ if (!packageJson.devDependencies[packageName] || force) {
262
+ packageJson.devDependencies[packageName] = selfDependencyRange;
263
+ }
264
+
265
+ upsertScript(packageJson, "lint", "eimerreis-linting lint", force);
266
+ upsertScript(packageJson, "lint:fix", "eimerreis-linting lint --fix", force);
267
+ upsertScript(packageJson, "format", "eimerreis-linting format", force);
268
+ upsertScript(packageJson, "format:check", "eimerreis-linting format --check", force);
269
+
270
+ writeJson(targetPackageJsonPath, packageJson);
271
+ console.log(`update ${targetPackageJsonPath}`);
151
272
  }
152
273
 
153
274
  function printNextSteps(targetDir) {
154
- const relativePath = targetDir === process.cwd() ? "." : targetDir;
155
- console.log("done");
156
- console.log("next steps:");
157
- console.log(`1) cd ${relativePath}`);
158
- console.log("2) npm add -D @eimerreis/linting oxlint oxfmt");
159
- console.log("3) npm run lint && npm run format:check");
275
+ const relativePath = targetDir === process.cwd() ? "." : targetDir;
276
+ console.log("done");
277
+ console.log("next steps:");
278
+ console.log(`1) cd ${relativePath}`);
279
+ console.log("2) npm install");
280
+ console.log("3) npm run lint && npm run format:check");
160
281
  }
161
282
 
162
- function runInit(args) {
163
- const { targetDir, flags } = parsePathAndFlags(args, ["--force"]);
164
- const force = flags.has("--force");
165
- const targetPackageJsonPath = resolve(targetDir, "package.json");
166
- const oxlintPath = resolve(targetDir, ".oxlintrc.json");
167
- const oxfmtPath = resolve(targetDir, ".oxfmtrc.json");
168
-
169
- if (!existsSync(packageRoot)) {
170
- console.error("init failed: package root not found");
171
- process.exit(1);
172
- }
173
-
174
- createExtendsConfig(oxlintPath, "oxlint.config.json", force);
175
- createExtendsConfig(oxfmtPath, "oxfmt.config.json", force);
176
- maybeUpdatePackageJson(targetPackageJsonPath, force);
177
- printNextSteps(targetDir);
178
- }
283
+ async function runInit(args) {
284
+ const { targetDir, flags } = parsePathAndFlags(args, ["--force"]);
285
+ const force = flags.has("--force");
286
+ const targetPackageJsonPath = resolve(targetDir, "package.json");
287
+ const oxlintPath = resolve(targetDir, ".oxlintrc.json");
288
+ const oxfmtPath = resolve(targetDir, ".oxfmtrc.json");
179
289
 
180
- async function runLint(args) {
181
- const { targetDir, flags } = parsePathAndFlags(args, ["--fix"]);
182
- const lintArgs = ["--no-install", "oxlint"];
290
+ if (!existsSync(packageRoot)) {
291
+ console.error("init failed: package root not found");
292
+ process.exit(1);
293
+ }
183
294
 
184
- if (flags.has("--fix")) {
185
- lintArgs.push("--fix");
186
- }
295
+ createExtendsConfig(oxlintPath, "oxlint.config.json", force);
296
+ createExtendsConfig(oxfmtPath, "oxfmt.config.json", force);
297
+ maybeUpdatePackageJson(targetPackageJsonPath, force);
187
298
 
188
- lintArgs.push(".");
299
+ printNextSteps(targetDir);
300
+ }
189
301
 
190
- const lintExitCode = await runCommand("npx", lintArgs, targetDir);
191
- if (lintExitCode !== 0) {
192
- process.exit(lintExitCode);
193
- }
302
+ async function runLint(args) {
303
+ if (!hasInstalledPackage("oxlint")) {
304
+ console.error("oxlint is not installed. Run: npm install");
305
+ process.exit(1);
306
+ }
194
307
 
195
- if (!hasReactProject(targetDir)) {
196
- console.log("skip react-doctor (no react/next dependency found)");
197
- return;
198
- }
308
+ const { targetDir, flags } = parsePathAndFlags(args, ["--fix"]);
309
+ const oxlintBinPath = resolvePackageBin("oxlint", "bin/oxlint");
310
+ const lintArgs = [oxlintBinPath];
311
+ const lintConfigPath = resolveLintConfigPath(targetDir);
199
312
 
200
- const doctorExitCode = await runCommand("npx", ["react-doctor", "-y", "."], targetDir);
201
- if (doctorExitCode !== 0) {
202
- process.exit(doctorExitCode);
203
- }
204
- }
313
+ lintArgs.push("-c", lintConfigPath);
205
314
 
206
- async function runFormat(args) {
207
- const { targetDir, flags } = parsePathAndFlags(args, ["--check"]);
208
- const formatArgs = ["--no-install", "oxfmt"];
315
+ if (flags.has("--fix")) {
316
+ lintArgs.push("--fix");
317
+ }
209
318
 
210
- if (flags.has("--check")) {
211
- formatArgs.push("--check");
212
- }
319
+ lintArgs.push(".");
320
+
321
+ const lintExitCode = await runCommand(process.execPath, lintArgs, targetDir);
322
+ if (lintExitCode !== 0) {
323
+ process.exit(lintExitCode);
324
+ }
213
325
 
214
- formatArgs.push(".");
326
+ if (!hasReactProject(targetDir)) {
327
+ console.log("skip react-doctor (no react/next dependency found)");
328
+ return;
329
+ }
215
330
 
216
- const formatExitCode = await runCommand("npx", formatArgs, targetDir);
217
- if (formatExitCode !== 0) {
218
- process.exit(formatExitCode);
219
- }
331
+ const reactDoctorEntryPath = resolvePackageEntry("react-doctor");
332
+ const doctorExitCode = await runCommand(process.execPath, [reactDoctorEntryPath, "-y", "."], targetDir);
333
+ if (doctorExitCode !== 0) {
334
+ process.exit(doctorExitCode);
335
+ }
220
336
  }
221
337
 
222
- async function main() {
223
- try {
224
- const rawArgs = process.argv.slice(2);
225
- if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
226
- printUsage();
227
- process.exit(0);
338
+ async function runFormat(args) {
339
+ if (!hasInstalledPackage("oxfmt")) {
340
+ console.error("oxfmt is not installed. Run: npm install");
341
+ process.exit(1);
228
342
  }
229
343
 
230
- const { command, args } = parseCommand(rawArgs);
344
+ const { targetDir, flags } = parsePathAndFlags(args, ["--check"]);
345
+ const oxfmtBinPath = resolvePackageBin("oxfmt", "bin/oxfmt");
346
+ const formatArgs = [oxfmtBinPath];
347
+ const formatConfigPath = resolveFormatConfigPath(targetDir);
231
348
 
232
- if (command === "init") {
233
- runInit(args);
234
- return;
235
- }
349
+ formatArgs.push("-c", formatConfigPath);
236
350
 
237
- if (command === "lint") {
238
- await runLint(args);
239
- return;
351
+ if (flags.has("--check")) {
352
+ formatArgs.push("--check");
240
353
  }
241
354
 
242
- if (command === "format") {
243
- await runFormat(args);
244
- return;
355
+ formatArgs.push(".");
356
+
357
+ const formatExitCode = await runCommand(process.execPath, formatArgs, targetDir);
358
+ if (formatExitCode !== 0) {
359
+ process.exit(formatExitCode);
245
360
  }
361
+ }
246
362
 
247
- console.error(`Unknown command: ${command}`);
248
- printUsage();
249
- process.exit(1);
250
- } catch (error) {
251
- console.error(error instanceof Error ? error.message : String(error));
252
- process.exit(1);
253
- }
363
+ async function main() {
364
+ try {
365
+ const rawArgs = process.argv.slice(2);
366
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
367
+ printUsage();
368
+ process.exit(0);
369
+ }
370
+
371
+ const { command, args } = parseCommand(rawArgs);
372
+
373
+ if (command === "init") {
374
+ await runInit(args);
375
+ return;
376
+ }
377
+
378
+ if (command === "lint") {
379
+ await runLint(args);
380
+ return;
381
+ }
382
+
383
+ if (command === "format") {
384
+ await runFormat(args);
385
+ return;
386
+ }
387
+
388
+ console.error(`Unknown command: ${command}`);
389
+ printUsage();
390
+ process.exit(1);
391
+ } catch (error) {
392
+ console.error(error instanceof Error ? error.message : String(error));
393
+ process.exit(1);
394
+ }
254
395
  }
255
396
 
256
397
  main();
package/oxfmt.config.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
- "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
- "printWidth": 125,
4
- "tabWidth": 4,
5
- "semi": true,
6
- "singleQuote": false,
7
- "trailingComma": "es5",
8
- "sortImports": {
9
- "order": "asc",
10
- "ignoreCase": true,
11
- "newlinesBetween": true,
12
- "groups": ["builtin", "external", "internal", ["parent", "sibling", "index"]],
13
- "internalPattern": ["@/", "~/"]
14
- },
15
- "sortTailwindcss": {
16
- "functions": ["cn", "clsx", "cva", "tw"]
17
- },
18
- "ignorePatterns": ["node_modules/**", ".next/**", "dist/**", "build/**", "coverage/**", "out/**"]
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "printWidth": 125,
4
+ "tabWidth": 4,
5
+ "semi": true,
6
+ "singleQuote": false,
7
+ "trailingComma": "es5",
8
+ "sortImports": {
9
+ "order": "asc",
10
+ "ignoreCase": true,
11
+ "newlinesBetween": true,
12
+ "groups": ["builtin", "external", "internal", ["parent", "sibling", "index"]],
13
+ "internalPattern": ["@/", "~/"]
14
+ },
15
+ "sortTailwindcss": {
16
+ "functions": ["cn", "clsx", "cva", "tw"]
17
+ },
18
+ "ignorePatterns": ["node_modules/**", ".next/**", "dist/**", "build/**", "coverage/**", "out/**"]
19
19
  }
@@ -1,38 +1,38 @@
1
1
  {
2
- "$schema": "./node_modules/oxlint/configuration_schema.json",
3
- "plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "nextjs", "import", "vitest"],
4
- "categories": {
5
- "correctness": "error",
6
- "suspicious": "warn"
7
- },
8
- "env": {
9
- "browser": true,
10
- "node": true,
11
- "es6": true
12
- },
13
- "rules": {
14
- "eslint/no-unused-vars": [
15
- "warn",
16
- {
17
- "varsIgnorePattern": "_"
18
- }
19
- ],
20
- "typescript/no-explicit-any": "off",
21
- "react/prop-types": "off",
22
- "react/react-in-jsx-scope": "off",
23
- "react-hooks/refs": "off",
24
- "react-hooks/incompatible-library": "off",
25
- "import/no-duplicates": "error",
26
- "import/no-self-import": "error",
27
- "import/no-cycle": "warn"
28
- },
29
- "ignorePatterns": ["node_modules/**", ".next/**", "dist/**", "build/**", "coverage/**", "out/**"],
30
- "overrides": [
31
- {
32
- "files": ["**/*.{test,spec}.{js,jsx,ts,tsx}", "**/__tests__/**/*.{js,jsx,ts,tsx}"],
33
- "rules": {
34
- "typescript/no-explicit-any": "off"
35
- }
36
- }
37
- ]
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "nextjs", "import", "vitest"],
4
+ "categories": {
5
+ "correctness": "error",
6
+ "suspicious": "warn"
7
+ },
8
+ "env": {
9
+ "browser": true,
10
+ "node": true,
11
+ "es6": true
12
+ },
13
+ "rules": {
14
+ "eslint/no-unused-vars": [
15
+ "warn",
16
+ {
17
+ "varsIgnorePattern": "_"
18
+ }
19
+ ],
20
+ "typescript/no-explicit-any": "off",
21
+ "react/prop-types": "off",
22
+ "react/react-in-jsx-scope": "off",
23
+ "react-hooks/refs": "off",
24
+ "react-hooks/incompatible-library": "off",
25
+ "import/no-duplicates": "error",
26
+ "import/no-self-import": "error",
27
+ "import/no-cycle": "warn"
28
+ },
29
+ "ignorePatterns": ["node_modules/**", ".next/**", "dist/**", "build/**", "coverage/**", "out/**"],
30
+ "overrides": [
31
+ {
32
+ "files": ["**/*.{test,spec}.{js,jsx,ts,tsx}", "**/__tests__/**/*.{js,jsx,ts,tsx}"],
33
+ "rules": {
34
+ "typescript/no-explicit-any": "off"
35
+ }
36
+ }
37
+ ]
38
38
  }
package/package.json CHANGED
@@ -1,60 +1,59 @@
1
1
  {
2
- "name": "@eimerreis/linting",
3
- "version": "0.2.0",
4
- "description": "Personal OXC linting and formatting defaults",
5
- "keywords": [
6
- "format",
7
- "lint",
8
- "nextjs",
9
- "oxc",
10
- "oxfmt",
11
- "oxlint",
12
- "react",
13
- "tailwind",
14
- "typescript"
15
- ],
16
- "license": "MIT",
17
- "repository": {
18
- "url": "https://github.com/eimerreis/linting"
19
- },
20
- "bin": {
21
- "eimerreis-linting": "bin/init.mjs"
22
- },
23
- "files": [
24
- "bin",
25
- "oxlint.config.json",
26
- "oxfmt.config.json",
27
- "README.md",
28
- "LICENSE"
29
- ],
30
- "type": "module",
31
- "exports": {
32
- "./oxlint": "./oxlint.config.json",
33
- "./oxfmt": "./oxfmt.config.json",
34
- "./package.json": "./package.json"
35
- },
36
- "publishConfig": {
37
- "access": "public"
38
- },
39
- "scripts": {
40
- "changeset": "changeset",
41
- "lint": "node ./bin/init.mjs lint",
42
- "lint:fix": "node ./bin/init.mjs lint --fix",
43
- "format": "node ./bin/init.mjs format",
44
- "format:check": "node ./bin/init.mjs format --check",
45
- "version-packages": "changeset version",
46
- "release": "changeset publish"
47
- },
48
- "devDependencies": {
49
- "@changesets/cli": "^2.29.7",
50
- "oxfmt": "^0.41.0",
51
- "oxlint": "^1.56.0"
52
- },
53
- "peerDependencies": {
54
- "oxfmt": "^0.41.0",
55
- "oxlint": "^1.56.0"
56
- },
57
- "engines": {
58
- "node": "^20.19.0 || >=22.12.0"
59
- }
2
+ "name": "@eimerreis/linting",
3
+ "version": "0.3.0",
4
+ "description": "Personal OXC linting and formatting defaults",
5
+ "keywords": [
6
+ "format",
7
+ "lint",
8
+ "nextjs",
9
+ "oxc",
10
+ "oxfmt",
11
+ "oxlint",
12
+ "react",
13
+ "tailwind",
14
+ "typescript"
15
+ ],
16
+ "license": "MIT",
17
+ "repository": {
18
+ "url": "https://github.com/eimerreis/linting"
19
+ },
20
+ "bin": {
21
+ "eimerreis-linting": "bin/init.mjs"
22
+ },
23
+ "files": [
24
+ "bin",
25
+ "oxlint.config.json",
26
+ "oxfmt.config.json",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "type": "module",
31
+ "exports": {
32
+ "./oxlint": "./oxlint.config.json",
33
+ "./oxfmt": "./oxfmt.config.json",
34
+ "./package.json": "./package.json"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "changeset": "changeset",
41
+ "lint": "node ./bin/init.mjs lint",
42
+ "lint:fix": "node ./bin/init.mjs lint --fix",
43
+ "format": "node ./bin/init.mjs format",
44
+ "format:check": "node ./bin/init.mjs format --check",
45
+ "version-packages": "changeset version",
46
+ "release": "changeset publish"
47
+ },
48
+ "dependencies": {
49
+ "oxfmt": "^0.41.0",
50
+ "oxlint": "^1.56.0",
51
+ "react-doctor": "^0.0.30"
52
+ },
53
+ "devDependencies": {
54
+ "@changesets/cli": "^2.29.7"
55
+ },
56
+ "engines": {
57
+ "node": "^20.19.0 || >=22.12.0"
58
+ }
60
59
  }