@howells/lint 0.1.5 → 0.1.7
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/MIGRATIONS.md +23 -5
- package/README.md +160 -37
- package/bin/howells-format.mjs +3 -3
- package/bin/howells-lint.mjs +24 -24
- package/bin/howells-ox-check.mjs +28 -0
- package/bin/howells-ox-fix.mjs +34 -0
- package/bin/howells-oxfmt.mjs +5 -0
- package/bin/howells-oxlint.mjs +5 -0
- package/bin/run-package-bin.mjs +56 -46
- package/bin/workspace-preflight.mjs +49 -6
- package/biome/core.json +40 -25
- package/biome/next.json +36 -7
- package/biome/react.json +36 -7
- package/oxfmt/index.d.mts +4 -0
- package/oxfmt/index.mjs +1 -0
- package/oxlint/core.d.mts +4 -0
- package/oxlint/core.mjs +1 -0
- package/oxlint/next.d.mts +4 -0
- package/oxlint/next.mjs +1 -0
- package/oxlint/react.d.mts +4 -0
- package/oxlint/react.mjs +1 -0
- package/package.json +34 -6
package/MIGRATIONS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Adoption Notes
|
|
2
2
|
|
|
3
|
-
Use these notes when replacing an existing ESLint, Prettier, or ad hoc Biome setup with `@howells/lint`.
|
|
3
|
+
Use these notes when replacing an existing ESLint, Prettier, Oxc, or ad hoc Biome setup with `@howells/lint`.
|
|
4
4
|
|
|
5
5
|
## Primary rule
|
|
6
6
|
|
|
@@ -19,9 +19,27 @@ If none of these fit cleanly, the likely answer is a new shared preset here, not
|
|
|
19
19
|
## Migration steps
|
|
20
20
|
|
|
21
21
|
1. Add `@howells/lint` as a dev dependency.
|
|
22
|
-
2.
|
|
23
|
-
3. Replace
|
|
24
|
-
4.
|
|
22
|
+
2. Pin Node with `.node-version` set to `22.18.0` and `engines.node` set to `>=22.18.0`.
|
|
23
|
+
3. Replace `eslint`, `next lint`, `prettier`, or direct `biome` scripts with `howells-lint` and `howells-format`.
|
|
24
|
+
4. Replace the project `biome.json` or `biome.jsonc` with a minimal file that only extends one shared preset.
|
|
25
|
+
5. Remove direct `eslint`, `eslint-config-*`, `eslint-plugin-*`, `prettier`, `@biomejs/biome`, `oxlint`, `oxfmt`, `oxlint-tsgolint`, and `ultracite` dependencies once the project is green.
|
|
26
|
+
|
|
27
|
+
## Oxlint/Oxfmt opt-in
|
|
28
|
+
|
|
29
|
+
Biome remains the default migration target. Use Oxlint/Oxfmt only for projects that deliberately choose the Oxc lane.
|
|
30
|
+
|
|
31
|
+
For an Oxlint/Oxfmt project, add `oxlint.config.ts` and `oxfmt.config.ts` using the exports from `@howells/lint`, then use:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"scripts": {
|
|
36
|
+
"lint": "howells-ox-check .",
|
|
37
|
+
"lint:fix": "howells-ox-fix ."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Do not run Biome and Oxlint/Oxfmt together indefinitely. If both are present during a migration, write down which command is authoritative and remove the other once the migration is green.
|
|
25
43
|
|
|
26
44
|
## Keep local config thin
|
|
27
45
|
|
|
@@ -35,7 +53,7 @@ The normal local config should look like this:
|
|
|
35
53
|
|
|
36
54
|
Acceptable local additions:
|
|
37
55
|
|
|
38
|
-
- repo-specific file includes or force-ignores that
|
|
56
|
+
- repo-specific file includes or force-ignores for generated files that are unique to one project
|
|
39
57
|
- one-off rule changes tied to a genuine platform constraint
|
|
40
58
|
- temporary compatibility shims during migration
|
|
41
59
|
|
package/README.md
CHANGED
|
@@ -1,45 +1,76 @@
|
|
|
1
1
|
# `@howells/lint`
|
|
2
2
|
|
|
3
|
-
Pinned Biome and Ultracite presets for Howells projects.
|
|
3
|
+
Pinned Biome, Oxlint/Oxfmt, and Ultracite presets for Howells projects.
|
|
4
4
|
|
|
5
5
|
The goal is not to invent a second lint philosophy. The goal is to:
|
|
6
6
|
|
|
7
7
|
- pin a single `@biomejs/biome` version
|
|
8
|
+
- pin a single `oxlint` version
|
|
9
|
+
- pin a single `oxfmt` version
|
|
8
10
|
- pin a single `ultracite` version
|
|
9
11
|
- pin a single `@manypkg/cli` version for monorepo consistency checks
|
|
10
12
|
- give every consumer the same small preset matrix
|
|
11
13
|
- discourage repo-local overrides unless the project has a genuinely unique constraint
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Biome is the default toolchain. Oxlint/Oxfmt is offered as an explicit opt-in lane for JavaScript and TypeScript projects that want the Oxc stack's speed and ESLint-style rule coverage.
|
|
16
|
+
|
|
17
|
+
## Agent Setup Checklist
|
|
18
|
+
|
|
19
|
+
When configuring a project, do this in order:
|
|
20
|
+
|
|
21
|
+
1. Require Node 22.18.0+ and pnpm in the root `package.json`, and pin `.node-version` to `22.18.0`.
|
|
22
|
+
2. Install only `@howells/lint` as the direct lint dependency.
|
|
23
|
+
3. Add a `biome.json` that extends the closest presets.
|
|
24
|
+
4. Add read-only `lint`, mutating `lint:fix`, and optional `lint:strict` scripts.
|
|
25
|
+
5. If the project is a monorepo, add root workspace scripts that run `howells-workspace-check`.
|
|
26
|
+
6. Verify with `pnpm lint` and, when configured, `pnpm lint:strict`.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
All projects using this package should declare the runtime and package manager explicitly:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"packageManager": "pnpm@10.23.0",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.18.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Also add a root `.node-version` file:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
22.18.0
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Install the shared tooling:
|
|
14
48
|
|
|
15
49
|
```bash
|
|
16
50
|
pnpm add -D @howells/lint
|
|
17
51
|
```
|
|
18
52
|
|
|
19
|
-
|
|
53
|
+
Do not add `@biomejs/biome`, `oxlint`, `oxfmt`, `oxlint-tsgolint`, `ultracite`, or `@manypkg/cli` directly unless you are developing this package itself. They are pinned transitively here.
|
|
20
54
|
|
|
21
|
-
|
|
55
|
+
## Biome Presets
|
|
22
56
|
|
|
23
|
-
|
|
24
|
-
- `@howells/lint/biome/react`
|
|
25
|
-
- `@howells/lint/biome/next`
|
|
57
|
+
Choose the closest preset instead of starting from a generic base and patching it locally:
|
|
26
58
|
|
|
27
|
-
|
|
59
|
+
- `@howells/lint/biome/core` for Node or non-React TypeScript packages
|
|
60
|
+
- `@howells/lint/biome/react` for React packages
|
|
61
|
+
- `@howells/lint/biome/next` for Next.js apps
|
|
28
62
|
|
|
29
|
-
|
|
30
|
-
- enable VCS ignore file support
|
|
31
|
-
- ignore common build output directories
|
|
32
|
-
- keep `ignoreUnknown` on so mixed repos do not need defensive local config
|
|
33
|
-
- enforce 2-space indentation consistently
|
|
34
|
-
- enable Tailwind CSS directives on DOM-oriented presets
|
|
63
|
+
These presets already pin Biome and Ultracite, enable VCS ignore file support, ignore common build output directories, keep `ignoreUnknown` on for mixed repos, enforce 2-space indentation, and enable Tailwind CSS directives on DOM-oriented presets.
|
|
35
64
|
|
|
36
|
-
|
|
65
|
+
The shared presets exclude generated and output folders seen across Howells projects: `node_modules`, `.next`, `.turbo`, `.vercel`, `dist`, `build`, `coverage`, `out`, `storybook-static`, `playwright-report`, `test-results`, `.source`, `.cache`, `.expo`, `.output`, `.wrangler`, `.svelte-kit`, `.nuxt`, `.vite`, `.vinxi`, `dev-dist`, `tmp`, and `temp`. Keep repo-local excludes only for genuinely project-specific generated files or data directories.
|
|
37
66
|
|
|
38
67
|
Node or non-React TypeScript package:
|
|
39
68
|
|
|
40
69
|
```json
|
|
41
70
|
{
|
|
42
|
-
"
|
|
71
|
+
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
72
|
+
"extends": ["@howells/lint/biome/core"],
|
|
73
|
+
"root": true
|
|
43
74
|
}
|
|
44
75
|
```
|
|
45
76
|
|
|
@@ -47,7 +78,9 @@ React package:
|
|
|
47
78
|
|
|
48
79
|
```json
|
|
49
80
|
{
|
|
50
|
-
"
|
|
81
|
+
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
82
|
+
"extends": ["@howells/lint/biome/core", "@howells/lint/biome/react"],
|
|
83
|
+
"root": true
|
|
51
84
|
}
|
|
52
85
|
```
|
|
53
86
|
|
|
@@ -55,23 +88,69 @@ Next.js app:
|
|
|
55
88
|
|
|
56
89
|
```json
|
|
57
90
|
{
|
|
58
|
-
"
|
|
91
|
+
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
92
|
+
"extends": [
|
|
93
|
+
"@howells/lint/biome/core",
|
|
94
|
+
"@howells/lint/biome/react",
|
|
95
|
+
"@howells/lint/biome/next"
|
|
96
|
+
],
|
|
97
|
+
"root": true
|
|
59
98
|
}
|
|
60
99
|
```
|
|
61
100
|
|
|
62
|
-
##
|
|
101
|
+
## Oxlint/Oxfmt Presets
|
|
63
102
|
|
|
64
|
-
|
|
103
|
+
Use this lane only when a project deliberately wants Oxlint and Oxfmt instead of Biome for day-to-day linting and formatting. The default `howells-lint` and `howells-format` commands stay on Biome.
|
|
65
104
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
105
|
+
Create an `oxlint.config.ts`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { defineConfig } from "oxlint";
|
|
109
|
+
import core from "@howells/lint/oxlint/core";
|
|
110
|
+
|
|
111
|
+
export default defineConfig({
|
|
112
|
+
extends: [core],
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
For React or Next.js projects, add the matching presets:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { defineConfig } from "oxlint";
|
|
120
|
+
import core from "@howells/lint/oxlint/core";
|
|
121
|
+
import react from "@howells/lint/oxlint/react";
|
|
122
|
+
import next from "@howells/lint/oxlint/next";
|
|
123
|
+
|
|
124
|
+
export default defineConfig({
|
|
125
|
+
extends: [core, react, next],
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Create an `oxfmt.config.ts`:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { defineConfig } from "oxfmt";
|
|
133
|
+
import howells from "@howells/lint/oxfmt";
|
|
134
|
+
|
|
135
|
+
export default defineConfig({
|
|
136
|
+
extends: [howells],
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Oxlint type-aware rules are available through the pinned `oxlint-tsgolint` dependency. Enable them in the root Oxlint config when the project is ready for TypeScript 7 / `typescript-go` constraints:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
export default defineConfig({
|
|
144
|
+
extends: [core],
|
|
145
|
+
options: {
|
|
146
|
+
typeAware: true,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Package Scripts
|
|
73
152
|
|
|
74
|
-
|
|
153
|
+
Every package or single-package app should use this shape:
|
|
75
154
|
|
|
76
155
|
```json
|
|
77
156
|
{
|
|
@@ -85,37 +164,79 @@ Example scripts:
|
|
|
85
164
|
|
|
86
165
|
Keep `lint` non-mutating. Put all `--write` behavior in `lint:fix` or `format` so CI and local checks have the same semantics.
|
|
87
166
|
|
|
88
|
-
|
|
167
|
+
Prefer `howells-lint .` over raw `biome check` or long target lists. Use explicit script targets only when the package has a real scope constraint:
|
|
89
168
|
|
|
90
169
|
```json
|
|
91
170
|
{
|
|
92
171
|
"scripts": {
|
|
93
|
-
"lint": "
|
|
94
|
-
"lint:fix": "
|
|
95
|
-
|
|
172
|
+
"lint": "howells-lint apps/web packages/ui",
|
|
173
|
+
"lint:fix": "howells-format apps/web packages/ui"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
For an Oxlint/Oxfmt project, keep the command names explicit:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"scripts": {
|
|
183
|
+
"lint": "howells-ox-check .",
|
|
184
|
+
"lint:fix": "howells-ox-fix ."
|
|
96
185
|
}
|
|
97
186
|
}
|
|
98
187
|
```
|
|
99
188
|
|
|
100
|
-
`howells-
|
|
189
|
+
Use `howells-ox-fix --unsafe .` only when you deliberately want Oxlint's dangerous fixes.
|
|
101
190
|
|
|
102
|
-
|
|
191
|
+
## Monorepo Roots
|
|
103
192
|
|
|
104
|
-
|
|
193
|
+
Use workspace checks only at the monorepo root. Do not add `howells-workspace-check` to individual packages, and do not add it to single-package apps.
|
|
194
|
+
|
|
195
|
+
A monorepo root should have:
|
|
105
196
|
|
|
106
197
|
```json
|
|
107
198
|
{
|
|
199
|
+
"packageManager": "pnpm@10.23.0",
|
|
200
|
+
"engines": {
|
|
201
|
+
"node": ">=22.18.0"
|
|
202
|
+
},
|
|
108
203
|
"scripts": {
|
|
109
|
-
"lint": "
|
|
110
|
-
"lint:fix": "howells-
|
|
204
|
+
"lint": "turbo run lint && howells-workspace-check",
|
|
205
|
+
"lint:fix": "turbo run lint:fix && howells-workspace-fix",
|
|
206
|
+
"lint:strict": "turbo run lint:strict",
|
|
207
|
+
"check": "pnpm lint && pnpm typecheck && pnpm test"
|
|
208
|
+
},
|
|
209
|
+
"devDependencies": {
|
|
210
|
+
"@howells/lint": "^0.1.7"
|
|
111
211
|
}
|
|
112
212
|
}
|
|
113
213
|
```
|
|
114
214
|
|
|
215
|
+
`howells-workspace-check` validates that the root declares `packageManager: "pnpm@..."`, requires Node 22.18.0+ in `engines.node`, pins `.node-version` to `22.18.0`, keeps `pnpm-workspace.yaml` present when workspace package directories exist, and passes `manypkg check`.
|
|
216
|
+
|
|
217
|
+
CI should call `pnpm lint` or `pnpm check` so root workspace checks are not bypassed by a direct `turbo lint` command.
|
|
218
|
+
|
|
219
|
+
## Binaries
|
|
220
|
+
|
|
221
|
+
Installers only need `@howells/lint` as a direct dependency. Use these package binaries:
|
|
222
|
+
|
|
223
|
+
- `howells-biome` proxies to the pinned Biome binary
|
|
224
|
+
- `howells-ultracite` proxies to the pinned Ultracite binary
|
|
225
|
+
- `howells-lint` defaults to `biome check .`
|
|
226
|
+
- `howells-lint-strict` runs high-signal Biome security, correctness, and suspicious lint rules
|
|
227
|
+
- `howells-format` defaults to `biome check . --write`
|
|
228
|
+
- `howells-oxlint` proxies to the pinned Oxlint binary
|
|
229
|
+
- `howells-oxfmt` proxies to the pinned Oxfmt binary
|
|
230
|
+
- `howells-ox-check` runs `oxfmt --check`, then `oxlint`
|
|
231
|
+
- `howells-ox-fix` runs `oxfmt --write`, then `oxlint --fix`
|
|
232
|
+
- `howells-workspace-check` validates root workspace hygiene, then runs `manypkg check`
|
|
233
|
+
- `howells-workspace-fix` runs `manypkg fix`
|
|
234
|
+
|
|
115
235
|
## Rules
|
|
116
236
|
|
|
117
237
|
- Do not add local overrides just to preserve old ESLint behavior.
|
|
118
238
|
- Do not create local `base`, `shared`, or `custom` Biome wrappers.
|
|
239
|
+
- Do not mix Biome and Oxlint/Oxfmt scripts in the same package unless the project has a deliberate migration plan.
|
|
119
240
|
- If multiple repos need the same exception, add or adjust a preset here.
|
|
120
241
|
- If a repo needs framework-specific linting, choose the matching preset instead of layering rules manually.
|
|
121
242
|
- Prefer inline `biome-ignore` comments for truly isolated exceptions over broad config overrides.
|
|
@@ -159,4 +280,6 @@ Add this to `.claude/settings.json` so files are formatted on edit and linted on
|
|
|
159
280
|
This package wraps:
|
|
160
281
|
|
|
161
282
|
- [Biome configuration docs](https://biomejs.dev/reference/configuration/)
|
|
283
|
+
- [Oxlint configuration docs](https://oxc.rs/docs/guide/usage/linter/config-file-reference.html)
|
|
284
|
+
- [Oxfmt configuration docs](https://oxc.rs/docs/guide/usage/formatter/config-file-reference)
|
|
162
285
|
- [Ultracite configuration docs](https://www.ultracite.ai/configuration)
|
package/bin/howells-format.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { runPackageBin } from "./run-package-bin.mjs";
|
|
|
4
4
|
|
|
5
5
|
const targets = process.argv.slice(2);
|
|
6
6
|
const args =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
targets.length > 0
|
|
8
|
+
? ["check", "--write", ...targets]
|
|
9
|
+
: ["check", "--write", "."];
|
|
10
10
|
|
|
11
11
|
runPackageBin("@biomejs/biome", "biome", args);
|
package/bin/howells-lint.mjs
CHANGED
|
@@ -4,33 +4,33 @@ import { runPackageBin } from "./run-package-bin.mjs";
|
|
|
4
4
|
|
|
5
5
|
const args = process.argv.slice(2);
|
|
6
6
|
const biomeCommands = new Set([
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
"version",
|
|
8
|
+
"rage",
|
|
9
|
+
"start",
|
|
10
|
+
"stop",
|
|
11
|
+
"check",
|
|
12
|
+
"lint",
|
|
13
|
+
"format",
|
|
14
|
+
"ci",
|
|
15
|
+
"init",
|
|
16
|
+
"migrate",
|
|
17
|
+
"search",
|
|
18
|
+
"explain",
|
|
19
|
+
"clean",
|
|
20
|
+
"daemon",
|
|
21
|
+
"lsp-proxy",
|
|
22
22
|
]);
|
|
23
23
|
const passthroughOptions = new Set(["--help", "-h", "--version", "-V"]);
|
|
24
24
|
|
|
25
25
|
const resolvedArgs =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
args.length === 0
|
|
27
|
+
? ["check", "."]
|
|
28
|
+
: biomeCommands.has(args[0])
|
|
29
|
+
? args
|
|
30
|
+
: passthroughOptions.has(args[0])
|
|
31
|
+
? args
|
|
32
|
+
: args[0].startsWith("-")
|
|
33
|
+
? ["check", ".", ...args]
|
|
34
|
+
: ["check", ...args];
|
|
35
35
|
|
|
36
36
|
runPackageBin("@biomejs/biome", "biome", resolvedArgs);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { resolvePackageBin } from "./run-package-bin.mjs";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const targets = args.filter((arg) => !arg.startsWith("-"));
|
|
8
|
+
const oxlintOptions = args.filter((arg) => arg.startsWith("-"));
|
|
9
|
+
const resolvedTargets = targets.length > 0 ? targets : ["."];
|
|
10
|
+
|
|
11
|
+
function run(packageName, binName, commandArgs) {
|
|
12
|
+
const binPath = resolvePackageBin(packageName, binName);
|
|
13
|
+
const result = spawnSync(binPath, commandArgs, {
|
|
14
|
+
stdio: "inherit",
|
|
15
|
+
env: process.env,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (result.error) {
|
|
19
|
+
throw result.error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if ((result.status ?? 1) !== 0) {
|
|
23
|
+
process.exit(result.status ?? 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
run("oxfmt", "oxfmt", ["--check", ...resolvedTargets]);
|
|
28
|
+
run("oxlint", "oxlint", [...oxlintOptions, ...resolvedTargets]);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { resolvePackageBin } from "./run-package-bin.mjs";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const useDangerousFixes = args.includes("--unsafe");
|
|
8
|
+
const filteredArgs = args.filter((arg) => arg !== "--unsafe");
|
|
9
|
+
const targets = filteredArgs.filter((arg) => !arg.startsWith("-"));
|
|
10
|
+
const oxlintOptions = filteredArgs.filter((arg) => arg.startsWith("-"));
|
|
11
|
+
const resolvedTargets = targets.length > 0 ? targets : ["."];
|
|
12
|
+
|
|
13
|
+
function run(packageName, binName, commandArgs) {
|
|
14
|
+
const binPath = resolvePackageBin(packageName, binName);
|
|
15
|
+
const result = spawnSync(binPath, commandArgs, {
|
|
16
|
+
stdio: "inherit",
|
|
17
|
+
env: process.env,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (result.error) {
|
|
21
|
+
throw result.error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if ((result.status ?? 1) !== 0) {
|
|
25
|
+
process.exit(result.status ?? 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
run("oxfmt", "oxfmt", ["--write", ...resolvedTargets]);
|
|
30
|
+
run("oxlint", "oxlint", [
|
|
31
|
+
useDangerousFixes ? "--fix-dangerously" : "--fix",
|
|
32
|
+
...oxlintOptions,
|
|
33
|
+
...resolvedTargets,
|
|
34
|
+
]);
|
package/bin/run-package-bin.mjs
CHANGED
|
@@ -2,67 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
6
5
|
import { createRequire } from "node:module";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
|
|
12
12
|
function resolvePackageJsonPath(packageName) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
13
|
+
try {
|
|
14
|
+
return require.resolve(`${packageName}/package.json`);
|
|
15
|
+
} catch {
|
|
16
|
+
const packageSegments = packageName.split("/");
|
|
17
|
+
let searchDir = currentDir;
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
const candidate = join(
|
|
21
|
+
searchDir,
|
|
22
|
+
"..",
|
|
23
|
+
"node_modules",
|
|
24
|
+
...packageSegments,
|
|
25
|
+
"package.json",
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (existsSync(candidate)) {
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const parentDir = dirname(searchDir);
|
|
33
|
+
|
|
34
|
+
if (parentDir === searchDir) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
searchDir = parentDir;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Could not resolve package.json for package '${packageName}'.`,
|
|
44
|
+
);
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
function resolvePackageBin(packageName, binName) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
export function resolvePackageBin(packageName, binName) {
|
|
48
|
+
const packageJsonPath = resolvePackageJsonPath(packageName);
|
|
49
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
50
|
+
const packageDir = dirname(packageJsonPath);
|
|
51
|
+
const binField = packageJson.bin;
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
if (typeof binField === "string") {
|
|
54
|
+
return join(packageDir, binField);
|
|
55
|
+
}
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
if (binField && typeof binField === "object" && binField[binName]) {
|
|
58
|
+
return join(packageDir, binField[binName]);
|
|
59
|
+
}
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Could not resolve bin '${binName}' for package '${packageName}'.`,
|
|
63
|
+
);
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
export function runPackageBin(packageName, binName, args) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const binPath = resolvePackageBin(packageName, binName);
|
|
68
|
+
const result = spawnSync(binPath, args, {
|
|
69
|
+
stdio: "inherit",
|
|
70
|
+
env: process.env,
|
|
71
|
+
});
|
|
62
72
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
if (result.error) {
|
|
74
|
+
throw result.error;
|
|
75
|
+
}
|
|
66
76
|
|
|
67
|
-
|
|
77
|
+
process.exit(result.status ?? 1);
|
|
68
78
|
}
|
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
const workspaceDirs = ["apps", "packages", "services", "workers", "examples"];
|
|
5
|
+
const requiredNodeVersion = "22.18.0";
|
|
5
6
|
|
|
6
7
|
function readRootPackageJson() {
|
|
7
8
|
const packageJsonPath = join(process.cwd(), "package.json");
|
|
@@ -47,22 +48,57 @@ function hasLikelyWorkspaceLayout(packageJson) {
|
|
|
47
48
|
);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function
|
|
51
|
+
function isAtLeastRequiredNodeVersion(major, minor, patch = 0) {
|
|
52
|
+
if (major > 22) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (major < 22) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (minor > 18) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (minor < 18) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return patch >= 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isNode2218Engine(range) {
|
|
51
72
|
if (typeof range !== "string") {
|
|
52
73
|
return false;
|
|
53
74
|
}
|
|
54
75
|
|
|
55
76
|
const normalizedRange = range.replaceAll(/\s+/g, "");
|
|
77
|
+
const match = normalizedRange.match(
|
|
78
|
+
/^(?:>=|\^|~)?(?<major>\d+)(?:\.(?<minor>\d+))?(?:\.(?<patch>\d+))?$/,
|
|
79
|
+
);
|
|
56
80
|
|
|
57
|
-
if (
|
|
81
|
+
if (!match?.groups?.major || !match.groups.minor) {
|
|
58
82
|
return false;
|
|
59
83
|
}
|
|
60
84
|
|
|
61
|
-
return
|
|
62
|
-
|
|
85
|
+
return isAtLeastRequiredNodeVersion(
|
|
86
|
+
Number(match.groups.major),
|
|
87
|
+
Number(match.groups.minor),
|
|
88
|
+
Number(match.groups.patch ?? 0),
|
|
63
89
|
);
|
|
64
90
|
}
|
|
65
91
|
|
|
92
|
+
function readNodeVersionFile() {
|
|
93
|
+
const nodeVersionPath = join(process.cwd(), ".node-version");
|
|
94
|
+
|
|
95
|
+
if (!existsSync(nodeVersionPath)) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return readFileSync(nodeVersionPath, "utf8").trim();
|
|
100
|
+
}
|
|
101
|
+
|
|
66
102
|
export function runWorkspacePreflight() {
|
|
67
103
|
const { errors, packageJson } = readRootPackageJson();
|
|
68
104
|
|
|
@@ -76,8 +112,15 @@ export function runWorkspacePreflight() {
|
|
|
76
112
|
errors.push("root package.json packageManager must use pnpm");
|
|
77
113
|
}
|
|
78
114
|
|
|
79
|
-
if (!
|
|
80
|
-
errors.push(
|
|
115
|
+
if (!isNode2218Engine(packageJson.engines?.node)) {
|
|
116
|
+
errors.push(
|
|
117
|
+
`root package.json engines.node must require Node ${requiredNodeVersion}+`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const nodeVersion = readNodeVersionFile();
|
|
122
|
+
if (nodeVersion !== requiredNodeVersion) {
|
|
123
|
+
errors.push(`root .node-version must be ${requiredNodeVersion}`);
|
|
81
124
|
}
|
|
82
125
|
|
|
83
126
|
if (
|
package/biome/core.json
CHANGED
|
@@ -1,27 +1,42 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
3
|
+
"extends": ["ultracite/biome/core"],
|
|
4
|
+
"formatter": {
|
|
5
|
+
"indentStyle": "space",
|
|
6
|
+
"indentWidth": 2
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": true,
|
|
10
|
+
"includes": [
|
|
11
|
+
"**",
|
|
12
|
+
"!**/node_modules/**",
|
|
13
|
+
"!**/.next/**",
|
|
14
|
+
"!**/.turbo/**",
|
|
15
|
+
"!**/.vercel/**",
|
|
16
|
+
"!**/dist/**",
|
|
17
|
+
"!**/build/**",
|
|
18
|
+
"!**/coverage/**",
|
|
19
|
+
"!**/out/**",
|
|
20
|
+
"!**/storybook-static/**",
|
|
21
|
+
"!**/playwright-report/**",
|
|
22
|
+
"!**/test-results/**",
|
|
23
|
+
"!**/.source/**",
|
|
24
|
+
"!**/.cache/**",
|
|
25
|
+
"!**/.expo/**",
|
|
26
|
+
"!**/.output/**",
|
|
27
|
+
"!**/.wrangler/**",
|
|
28
|
+
"!**/.svelte-kit/**",
|
|
29
|
+
"!**/.nuxt/**",
|
|
30
|
+
"!**/.vite/**",
|
|
31
|
+
"!**/.vinxi/**",
|
|
32
|
+
"!**/dev-dist/**",
|
|
33
|
+
"!**/tmp/**",
|
|
34
|
+
"!**/temp/**"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"vcs": {
|
|
38
|
+
"enabled": true,
|
|
39
|
+
"clientKind": "git",
|
|
40
|
+
"useIgnoreFile": true
|
|
41
|
+
}
|
|
27
42
|
}
|
package/biome/next.json
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
3
|
+
"extends": ["./react.json", "ultracite/biome/next"],
|
|
4
|
+
"files": {
|
|
5
|
+
"ignoreUnknown": true,
|
|
6
|
+
"includes": [
|
|
7
|
+
"**",
|
|
8
|
+
"!**/node_modules/**",
|
|
9
|
+
"!**/.next/**",
|
|
10
|
+
"!**/.turbo/**",
|
|
11
|
+
"!**/.vercel/**",
|
|
12
|
+
"!**/dist/**",
|
|
13
|
+
"!**/build/**",
|
|
14
|
+
"!**/coverage/**",
|
|
15
|
+
"!**/out/**",
|
|
16
|
+
"!**/storybook-static/**",
|
|
17
|
+
"!**/playwright-report/**",
|
|
18
|
+
"!**/test-results/**",
|
|
19
|
+
"!**/.source/**",
|
|
20
|
+
"!**/.cache/**",
|
|
21
|
+
"!**/.expo/**",
|
|
22
|
+
"!**/.output/**",
|
|
23
|
+
"!**/.wrangler/**",
|
|
24
|
+
"!**/.svelte-kit/**",
|
|
25
|
+
"!**/.nuxt/**",
|
|
26
|
+
"!**/.vite/**",
|
|
27
|
+
"!**/.vinxi/**",
|
|
28
|
+
"!**/dev-dist/**",
|
|
29
|
+
"!**/tmp/**",
|
|
30
|
+
"!**/temp/**"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"css": {
|
|
34
|
+
"parser": {
|
|
35
|
+
"tailwindDirectives": true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
9
38
|
}
|
package/biome/react.json
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
3
|
+
"extends": ["./core.json", "ultracite/biome/react"],
|
|
4
|
+
"files": {
|
|
5
|
+
"ignoreUnknown": true,
|
|
6
|
+
"includes": [
|
|
7
|
+
"**",
|
|
8
|
+
"!**/node_modules/**",
|
|
9
|
+
"!**/.next/**",
|
|
10
|
+
"!**/.turbo/**",
|
|
11
|
+
"!**/.vercel/**",
|
|
12
|
+
"!**/dist/**",
|
|
13
|
+
"!**/build/**",
|
|
14
|
+
"!**/coverage/**",
|
|
15
|
+
"!**/out/**",
|
|
16
|
+
"!**/storybook-static/**",
|
|
17
|
+
"!**/playwright-report/**",
|
|
18
|
+
"!**/test-results/**",
|
|
19
|
+
"!**/.source/**",
|
|
20
|
+
"!**/.cache/**",
|
|
21
|
+
"!**/.expo/**",
|
|
22
|
+
"!**/.output/**",
|
|
23
|
+
"!**/.wrangler/**",
|
|
24
|
+
"!**/.svelte-kit/**",
|
|
25
|
+
"!**/.nuxt/**",
|
|
26
|
+
"!**/.vite/**",
|
|
27
|
+
"!**/.vinxi/**",
|
|
28
|
+
"!**/dev-dist/**",
|
|
29
|
+
"!**/tmp/**",
|
|
30
|
+
"!**/temp/**"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"css": {
|
|
34
|
+
"parser": {
|
|
35
|
+
"tailwindDirectives": true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
9
38
|
}
|
package/oxfmt/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "ultracite/oxfmt";
|
package/oxlint/core.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "ultracite/oxlint/core";
|
package/oxlint/next.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "ultracite/oxlint/next";
|
package/oxlint/react.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "ultracite/oxlint/react";
|
package/package.json
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howells/lint",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Pinned Biome and Ultracite presets for Howells projects.",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Pinned Biome, Oxlint/Oxfmt, and Ultracite presets for Howells projects.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"packageManager": "pnpm@10.23.0",
|
|
6
7
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
8
|
+
"node": ">=22.18.0"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"biome/*.json",
|
|
12
|
+
"oxfmt/*.d.mts",
|
|
13
|
+
"oxfmt/*.mjs",
|
|
14
|
+
"oxlint/*.d.mts",
|
|
15
|
+
"oxlint/*.mjs",
|
|
11
16
|
"bin/*.mjs",
|
|
12
17
|
"README.md",
|
|
13
18
|
"MIGRATIONS.md"
|
|
@@ -17,19 +22,42 @@
|
|
|
17
22
|
"howells-lint": "bin/howells-lint.mjs",
|
|
18
23
|
"howells-lint-strict": "bin/howells-lint-strict.mjs",
|
|
19
24
|
"howells-format": "bin/howells-format.mjs",
|
|
25
|
+
"howells-ox-check": "bin/howells-ox-check.mjs",
|
|
26
|
+
"howells-ox-fix": "bin/howells-ox-fix.mjs",
|
|
27
|
+
"howells-oxfmt": "bin/howells-oxfmt.mjs",
|
|
28
|
+
"howells-oxlint": "bin/howells-oxlint.mjs",
|
|
20
29
|
"howells-ultracite": "bin/howells-ultracite.mjs",
|
|
21
30
|
"howells-workspace-check": "bin/howells-workspace-check.mjs",
|
|
22
31
|
"howells-workspace-fix": "bin/howells-workspace-fix.mjs"
|
|
23
32
|
},
|
|
24
33
|
"dependencies": {
|
|
25
|
-
"@biomejs/biome": "2.4.
|
|
34
|
+
"@biomejs/biome": "2.4.15",
|
|
26
35
|
"@manypkg/cli": "^0.25.1",
|
|
27
|
-
"
|
|
36
|
+
"oxfmt": "0.49.0",
|
|
37
|
+
"oxlint": "1.64.0",
|
|
38
|
+
"oxlint-tsgolint": "0.22.1",
|
|
39
|
+
"ultracite": "7.7.0"
|
|
28
40
|
},
|
|
29
41
|
"exports": {
|
|
30
42
|
"./package.json": "./package.json",
|
|
31
43
|
"./biome/core": "./biome/core.json",
|
|
32
44
|
"./biome/react": "./biome/react.json",
|
|
33
|
-
"./biome/next": "./biome/next.json"
|
|
45
|
+
"./biome/next": "./biome/next.json",
|
|
46
|
+
"./oxfmt": {
|
|
47
|
+
"types": "./oxfmt/index.d.mts",
|
|
48
|
+
"default": "./oxfmt/index.mjs"
|
|
49
|
+
},
|
|
50
|
+
"./oxlint/core": {
|
|
51
|
+
"types": "./oxlint/core.d.mts",
|
|
52
|
+
"default": "./oxlint/core.mjs"
|
|
53
|
+
},
|
|
54
|
+
"./oxlint/react": {
|
|
55
|
+
"types": "./oxlint/react.d.mts",
|
|
56
|
+
"default": "./oxlint/react.mjs"
|
|
57
|
+
},
|
|
58
|
+
"./oxlint/next": {
|
|
59
|
+
"types": "./oxlint/next.d.mts",
|
|
60
|
+
"default": "./oxlint/next.mjs"
|
|
61
|
+
}
|
|
34
62
|
}
|
|
35
63
|
}
|