@howells/lint 0.1.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/MIGRATIONS.md ADDED
@@ -0,0 +1,73 @@
1
+ # Adoption Notes
2
+
3
+ Use these notes when replacing an existing ESLint, Prettier, or ad hoc Biome setup with `@howells/lint`.
4
+
5
+ ## Primary rule
6
+
7
+ Do not migrate an old local lint philosophy into a new local Biome override.
8
+
9
+ Pick the closest shared preset first. Only add local config after you can explain why the repo is a real exception.
10
+
11
+ ## Preset selection
12
+
13
+ - Node or non-React TypeScript: `@howells/lint/biome/core`
14
+ - React package or app without Next.js specifics: `@howells/lint/biome/react`
15
+ - Next.js app: `@howells/lint/biome/next`
16
+
17
+ If none of these fit cleanly, the likely answer is a new shared preset here, not a repo-specific fork.
18
+
19
+ ## Migration steps
20
+
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.
25
+
26
+ ## Keep local config thin
27
+
28
+ The normal local config should look like this:
29
+
30
+ ```json
31
+ {
32
+ "extends": ["@howells/lint/biome/next"]
33
+ }
34
+ ```
35
+
36
+ Acceptable local additions:
37
+
38
+ - repo-specific file includes or force-ignores that cannot be expressed better in scripts
39
+ - one-off rule changes tied to a genuine platform constraint
40
+ - temporary compatibility shims during migration
41
+
42
+ Avoid:
43
+
44
+ - copying old ESLint rule customizations into Biome
45
+ - broad `linter.rules` sections to preserve team habit
46
+ - local wrapper configs like `base.json` or `library.json`
47
+ - repeating the same override across multiple repos
48
+
49
+ ## Promote shared patterns quickly
50
+
51
+ If the same override or ignore pattern appears in more than one active project, treat that as pressure on `@howells/lint`.
52
+
53
+ Either:
54
+
55
+ - move the shared behavior into an existing preset, or
56
+ - add a new preset with a name that describes the actual environment
57
+
58
+ Do not normalize repeated local exceptions.
59
+
60
+ ## Prefer scope in scripts
61
+
62
+ If one repo only needs a narrower target, prefer script-level scope:
63
+
64
+ ```json
65
+ {
66
+ "scripts": {
67
+ "lint": "howells-lint apps/web packages/ui",
68
+ "lint:fix": "howells-format apps/web packages/ui"
69
+ }
70
+ }
71
+ ```
72
+
73
+ That is usually better than teaching the config about the repo layout.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # `@howells/lint`
2
+
3
+ Pinned Biome and Ultracite presets for Howells projects.
4
+
5
+ The goal is not to invent a second lint philosophy. The goal is to:
6
+
7
+ - pin a single `@biomejs/biome` version
8
+ - pin a single `ultracite` version
9
+ - give every consumer the same small preset matrix
10
+ - discourage repo-local overrides unless the project has a genuinely unique constraint
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add -D @howells/lint
16
+ ```
17
+
18
+ ## Presets
19
+
20
+ Choose the closest preset instead of starting from a generic base and patching it locally:
21
+
22
+ - `@howells/lint/biome/core`
23
+ - `@howells/lint/biome/react`
24
+ - `@howells/lint/biome/next`
25
+
26
+ These presets already:
27
+
28
+ - pin Biome and Ultracite transitively
29
+ - enable VCS ignore file support
30
+ - ignore common build output directories
31
+ - keep `ignoreUnknown` on so mixed repos do not need defensive local config
32
+
33
+ ## Usage
34
+
35
+ Node or non-React TypeScript package:
36
+
37
+ ```json
38
+ {
39
+ "extends": ["@howells/lint/biome/core"]
40
+ }
41
+ ```
42
+
43
+ React package:
44
+
45
+ ```json
46
+ {
47
+ "extends": ["@howells/lint/biome/react"]
48
+ }
49
+ ```
50
+
51
+ Next.js app:
52
+
53
+ ```json
54
+ {
55
+ "extends": ["@howells/lint/biome/next"]
56
+ }
57
+ ```
58
+
59
+ ## Binaries
60
+
61
+ Installers only need `@howells/lint` as a direct dependency. Use the package binaries instead of adding `@biomejs/biome` or `ultracite` separately:
62
+
63
+ - `howells-biome` proxies to the pinned Biome binary
64
+ - `howells-ultracite` proxies to the pinned Ultracite binary
65
+ - `howells-lint` defaults to `biome check .`
66
+ - `howells-format` defaults to `biome check . --write`
67
+
68
+ Example scripts:
69
+
70
+ ```json
71
+ {
72
+ "scripts": {
73
+ "lint": "howells-lint",
74
+ "lint:fix": "howells-format"
75
+ }
76
+ }
77
+ ```
78
+
79
+ Prefer explicit script targets over config churn when the only difference is scope:
80
+
81
+ ```json
82
+ {
83
+ "scripts": {
84
+ "lint": "howells-lint apps/web packages/ui",
85
+ "lint:fix": "howells-format apps/web packages/ui"
86
+ }
87
+ }
88
+ ```
89
+
90
+ ## Rules
91
+
92
+ - Do not add local overrides just to preserve old ESLint behavior.
93
+ - Do not create local `base`, `shared`, or `custom` Biome wrappers.
94
+ - If multiple repos need the same exception, add or adjust a preset here.
95
+ - If a repo needs framework-specific linting, choose the matching preset instead of layering rules manually.
96
+ - Prefer inline `biome-ignore` comments for truly isolated exceptions over broad config overrides.
97
+
98
+ ## Upstream
99
+
100
+ This package wraps:
101
+
102
+ - [Biome configuration docs](https://biomejs.dev/reference/configuration/)
103
+ - [Ultracite configuration docs](https://www.ultracite.ai/configuration)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runPackageBin } from "./run-package-bin.mjs";
4
+
5
+ runPackageBin("@biomejs/biome", "biome", process.argv.slice(2));
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runPackageBin } from "./run-package-bin.mjs";
4
+
5
+ const targets = process.argv.slice(2);
6
+ const args =
7
+ targets.length > 0
8
+ ? ["check", "--write", ...targets]
9
+ : ["check", "--write", "."];
10
+
11
+ runPackageBin("@biomejs/biome", "biome", args);
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runPackageBin } from "./run-package-bin.mjs";
4
+
5
+ const args = process.argv.slice(2);
6
+ const biomeCommands = new Set([
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
+ ]);
23
+ const passthroughOptions = new Set(["--help", "-h", "--version", "-V"]);
24
+
25
+ const resolvedArgs =
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
+
36
+ runPackageBin("@biomejs/biome", "biome", resolvedArgs);
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runPackageBin } from "./run-package-bin.mjs";
4
+
5
+ runPackageBin("ultracite", "ultracite", process.argv.slice(2));
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { createRequire } from "node:module";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const currentDir = dirname(fileURLToPath(import.meta.url));
11
+
12
+ function resolvePackageJsonPath(packageName) {
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(searchDir, "..", "node_modules", ...packageSegments, "package.json");
21
+
22
+ if (existsSync(candidate)) {
23
+ return candidate;
24
+ }
25
+
26
+ const parentDir = dirname(searchDir);
27
+
28
+ if (parentDir === searchDir) {
29
+ break;
30
+ }
31
+
32
+ searchDir = parentDir;
33
+ }
34
+ }
35
+
36
+ throw new Error(`Could not resolve package.json for package '${packageName}'.`);
37
+ }
38
+
39
+ function resolvePackageBin(packageName, binName) {
40
+ const packageJsonPath = resolvePackageJsonPath(packageName);
41
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
42
+ const packageDir = dirname(packageJsonPath);
43
+ const binField = packageJson.bin;
44
+
45
+ if (typeof binField === "string") {
46
+ return join(packageDir, binField);
47
+ }
48
+
49
+ if (binField && typeof binField === "object" && binField[binName]) {
50
+ return join(packageDir, binField[binName]);
51
+ }
52
+
53
+ throw new Error(`Could not resolve bin '${binName}' for package '${packageName}'.`);
54
+ }
55
+
56
+ export function runPackageBin(packageName, binName, args) {
57
+ const binPath = resolvePackageBin(packageName, binName);
58
+ const result = spawnSync(binPath, args, {
59
+ stdio: "inherit",
60
+ env: process.env
61
+ });
62
+
63
+ if (result.error) {
64
+ throw result.error;
65
+ }
66
+
67
+ process.exit(result.status ?? 1);
68
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
3
+ "extends": ["ultracite/biome/core"],
4
+ "files": {
5
+ "ignoreUnknown": true,
6
+ "includes": [
7
+ "**",
8
+ "!!**/dist",
9
+ "!!**/build",
10
+ "!!**/.next",
11
+ "!!**/.turbo",
12
+ "!!**/coverage",
13
+ "!!**/storybook-static",
14
+ "!!**/.vercel/output",
15
+ "!!**/out"
16
+ ]
17
+ },
18
+ "vcs": {
19
+ "enabled": true,
20
+ "clientKind": "git",
21
+ "useIgnoreFile": true
22
+ }
23
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
3
+ "extends": ["./react.json", "ultracite/biome/next"]
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
3
+ "extends": ["./core.json", "ultracite/biome/react"]
4
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@howells/lint",
3
+ "version": "0.1.0",
4
+ "description": "Pinned Biome and Ultracite presets for Howells projects.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "biome/*.json",
8
+ "bin/*.mjs",
9
+ "README.md",
10
+ "MIGRATIONS.md"
11
+ ],
12
+ "bin": {
13
+ "howells-biome": "bin/howells-biome.mjs",
14
+ "howells-lint": "bin/howells-lint.mjs",
15
+ "howells-format": "bin/howells-format.mjs",
16
+ "howells-ultracite": "bin/howells-ultracite.mjs"
17
+ },
18
+ "dependencies": {
19
+ "@biomejs/biome": "2.4.10",
20
+ "ultracite": "7.4.3"
21
+ },
22
+ "exports": {
23
+ "./package.json": "./package.json",
24
+ "./biome/core": "./biome/core.json",
25
+ "./biome/react": "./biome/react.json",
26
+ "./biome/next": "./biome/next.json"
27
+ }
28
+ }