@howells/lint 0.1.5 → 0.1.6

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 CHANGED
@@ -19,9 +19,10 @@ 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. Replace `eslint`, `next lint`, `prettier`, or direct `biome` scripts with `howells-lint` and `howells-format`.
23
- 3. Replace the project `biome.json` or `biome.jsonc` with a minimal file that only extends one shared preset.
24
- 4. Remove direct `eslint`, `eslint-config-*`, `eslint-plugin-*`, `prettier`, `@biomejs/biome`, and `ultracite` dependencies once the project is green.
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`, and `ultracite` dependencies once the project is green.
25
26
 
26
27
  ## Keep local config thin
27
28
 
@@ -35,7 +36,7 @@ The normal local config should look like this:
35
36
 
36
37
  Acceptable local additions:
37
38
 
38
- - repo-specific file includes or force-ignores that cannot be expressed better in scripts
39
+ - repo-specific file includes or force-ignores for generated files that are unique to one project
39
40
  - one-off rule changes tied to a genuine platform constraint
40
41
  - temporary compatibility shims during migration
41
42
 
package/README.md CHANGED
@@ -10,36 +10,63 @@ The goal is not to invent a second lint philosophy. The goal is to:
10
10
  - give every consumer the same small preset matrix
11
11
  - discourage repo-local overrides unless the project has a genuinely unique constraint
12
12
 
13
- ## Install
13
+ ## Agent Setup Checklist
14
+
15
+ When configuring a project, do this in order:
16
+
17
+ 1. Require Node 22.18.0+ and pnpm in the root `package.json`, and pin `.node-version` to `22.18.0`.
18
+ 2. Install only `@howells/lint` as the direct lint dependency.
19
+ 3. Add a `biome.json` that extends the closest presets.
20
+ 4. Add read-only `lint`, mutating `lint:fix`, and optional `lint:strict` scripts.
21
+ 5. If the project is a monorepo, add root workspace scripts that run `howells-workspace-check`.
22
+ 6. Verify with `pnpm lint` and, when configured, `pnpm lint:strict`.
23
+
24
+ ## Requirements
25
+
26
+ All projects using this package should declare the runtime and package manager explicitly:
27
+
28
+ ```json
29
+ {
30
+ "packageManager": "pnpm@10.23.0",
31
+ "engines": {
32
+ "node": ">=22.18.0"
33
+ }
34
+ }
35
+ ```
36
+
37
+ Also add a root `.node-version` file:
38
+
39
+ ```text
40
+ 22.18.0
41
+ ```
42
+
43
+ Install the shared tooling:
14
44
 
15
45
  ```bash
16
46
  pnpm add -D @howells/lint
17
47
  ```
18
48
 
19
- ## Presets
49
+ Do not add `@biomejs/biome`, `ultracite`, or `@manypkg/cli` directly unless you are developing this package itself. They are pinned transitively here.
20
50
 
21
- Choose the closest preset instead of starting from a generic base and patching it locally:
51
+ ## Biome Presets
22
52
 
23
- - `@howells/lint/biome/core`
24
- - `@howells/lint/biome/react`
25
- - `@howells/lint/biome/next`
53
+ Choose the closest preset instead of starting from a generic base and patching it locally:
26
54
 
27
- These presets already:
55
+ - `@howells/lint/biome/core` for Node or non-React TypeScript packages
56
+ - `@howells/lint/biome/react` for React packages
57
+ - `@howells/lint/biome/next` for Next.js apps
28
58
 
29
- - pin Biome and Ultracite transitively
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
59
+ 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
60
 
36
- ## Usage
61
+ 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
62
 
38
63
  Node or non-React TypeScript package:
39
64
 
40
65
  ```json
41
66
  {
42
- "extends": ["@howells/lint/biome/core"]
67
+ "$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
68
+ "extends": ["@howells/lint/biome/core"],
69
+ "root": true
43
70
  }
44
71
  ```
45
72
 
@@ -47,7 +74,9 @@ React package:
47
74
 
48
75
  ```json
49
76
  {
50
- "extends": ["@howells/lint/biome/react"]
77
+ "$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
78
+ "extends": ["@howells/lint/biome/core", "@howells/lint/biome/react"],
79
+ "root": true
51
80
  }
52
81
  ```
53
82
 
@@ -55,23 +84,19 @@ Next.js app:
55
84
 
56
85
  ```json
57
86
  {
58
- "extends": ["@howells/lint/biome/next"]
87
+ "$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
88
+ "extends": [
89
+ "@howells/lint/biome/core",
90
+ "@howells/lint/biome/react",
91
+ "@howells/lint/biome/next"
92
+ ],
93
+ "root": true
59
94
  }
60
95
  ```
61
96
 
62
- ## Binaries
97
+ ## Package Scripts
63
98
 
64
- Installers only need `@howells/lint` as a direct dependency. Use the package binaries instead of adding `@biomejs/biome`, `ultracite`, or `@manypkg/cli` separately:
65
-
66
- - `howells-biome` proxies to the pinned Biome binary
67
- - `howells-ultracite` proxies to the pinned Ultracite binary
68
- - `howells-lint` defaults to `biome check .`
69
- - `howells-lint-strict` runs the high-signal Biome security, correctness, and suspicious lint rules
70
- - `howells-format` defaults to `biome check . --write`
71
- - `howells-workspace-check` validates root workspace hygiene, then runs `manypkg check`
72
- - `howells-workspace-fix` defaults to `manypkg fix`
73
-
74
- Example scripts:
99
+ Every package or single-package app should use this shape:
75
100
 
76
101
  ```json
77
102
  {
@@ -85,33 +110,57 @@ Example scripts:
85
110
 
86
111
  Keep `lint` non-mutating. Put all `--write` behavior in `lint:fix` or `format` so CI and local checks have the same semantics.
87
112
 
88
- Monorepo root scripts should compose package linting with workspace validation:
113
+ 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
114
 
90
115
  ```json
91
116
  {
92
117
  "scripts": {
93
- "lint": "turbo run lint && howells-workspace-check",
94
- "lint:fix": "turbo run lint:fix && howells-workspace-fix",
95
- "lint:strict": "turbo run lint:strict"
118
+ "lint": "howells-lint apps/web packages/ui",
119
+ "lint:fix": "howells-format apps/web packages/ui"
96
120
  }
97
121
  }
98
122
  ```
99
123
 
100
- `howells-workspace-check` expects workspace roots to declare `packageManager: "pnpm@..."`, require Node 20+ in `engines.node`, and keep `pnpm-workspace.yaml` present when using workspace package directories.
124
+ ## Monorepo Roots
101
125
 
102
- CI should call `pnpm lint` or `pnpm check` so these root checks are not bypassed by a direct `turbo lint` command.
126
+ 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.
103
127
 
104
- Prefer explicit script targets over config churn when the only difference is scope:
128
+ A monorepo root should have:
105
129
 
106
130
  ```json
107
131
  {
132
+ "packageManager": "pnpm@10.23.0",
133
+ "engines": {
134
+ "node": ">=22.18.0"
135
+ },
108
136
  "scripts": {
109
- "lint": "howells-lint apps/web packages/ui",
110
- "lint:fix": "howells-format apps/web packages/ui"
137
+ "lint": "turbo run lint && howells-workspace-check",
138
+ "lint:fix": "turbo run lint:fix && howells-workspace-fix",
139
+ "lint:strict": "turbo run lint:strict",
140
+ "check": "pnpm lint && pnpm typecheck && pnpm test"
141
+ },
142
+ "devDependencies": {
143
+ "@howells/lint": "^0.1.6"
111
144
  }
112
145
  }
113
146
  ```
114
147
 
148
+ `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`.
149
+
150
+ CI should call `pnpm lint` or `pnpm check` so root workspace checks are not bypassed by a direct `turbo lint` command.
151
+
152
+ ## Binaries
153
+
154
+ Installers only need `@howells/lint` as a direct dependency. Use these package binaries:
155
+
156
+ - `howells-biome` proxies to the pinned Biome binary
157
+ - `howells-ultracite` proxies to the pinned Ultracite binary
158
+ - `howells-lint` defaults to `biome check .`
159
+ - `howells-lint-strict` runs high-signal Biome security, correctness, and suspicious lint rules
160
+ - `howells-format` defaults to `biome check . --write`
161
+ - `howells-workspace-check` validates root workspace hygiene, then runs `manypkg check`
162
+ - `howells-workspace-fix` runs `manypkg fix`
163
+
115
164
  ## Rules
116
165
 
117
166
  - Do not add local overrides just to preserve old ESLint behavior.
@@ -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 isNode20Engine(range) {
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 (/(^|[<>=~^|])1[0-9](\.|[^0-9]|$)/.test(normalizedRange)) {
81
+ if (!match?.groups?.major || !match.groups.minor) {
58
82
  return false;
59
83
  }
60
84
 
61
- return />=?20(\.|[^0-9]|$)|\^20(\.|[^0-9]|$)|~20(\.|[^0-9]|$)/.test(
62
- normalizedRange,
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 (!isNode20Engine(packageJson.engines?.node)) {
80
- errors.push("root package.json engines.node must require Node 20+");
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
- "$schema": "https://biomejs.dev/schemas/2.4.12/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
- "!!**/dist",
13
- "!!**/build",
14
- "!!**/.next",
15
- "!!**/.turbo",
16
- "!!**/coverage",
17
- "!!**/storybook-static",
18
- "!!**/.vercel/output",
19
- "!!**/out"
20
- ]
21
- },
22
- "vcs": {
23
- "enabled": true,
24
- "clientKind": "git",
25
- "useIgnoreFile": true
26
- }
2
+ "$schema": "https://biomejs.dev/schemas/2.4.14/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
- "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
3
- "extends": ["./react.json", "ultracite/biome/next"],
4
- "css": {
5
- "parser": {
6
- "tailwindDirectives": true
7
- }
8
- }
2
+ "$schema": "https://biomejs.dev/schemas/2.4.14/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
- "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
3
- "extends": ["./core.json", "ultracite/biome/react"],
4
- "css": {
5
- "parser": {
6
- "tailwindDirectives": true
7
- }
8
- }
2
+ "$schema": "https://biomejs.dev/schemas/2.4.14/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/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@howells/lint",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Pinned Biome and Ultracite presets for Howells projects.",
5
5
  "license": "MIT",
6
+ "packageManager": "pnpm@10.23.0",
6
7
  "engines": {
7
- "node": ">=20.19"
8
+ "node": ">=22.18.0"
8
9
  },
9
10
  "files": [
10
11
  "biome/*.json",
@@ -22,9 +23,9 @@
22
23
  "howells-workspace-fix": "bin/howells-workspace-fix.mjs"
23
24
  },
24
25
  "dependencies": {
25
- "@biomejs/biome": "2.4.12",
26
+ "@biomejs/biome": "2.4.14",
26
27
  "@manypkg/cli": "^0.25.1",
27
- "ultracite": "7.6.0"
28
+ "ultracite": "7.6.2"
28
29
  },
29
30
  "exports": {
30
31
  "./package.json": "./package.json",