@howells/lint 0.2.3 → 0.2.5

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
@@ -69,7 +69,7 @@ Node or non-React TypeScript package:
69
69
 
70
70
  ```json
71
71
  {
72
- "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
72
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
73
73
  "extends": ["@howells/lint/biome/core"],
74
74
  "root": true
75
75
  }
@@ -79,7 +79,7 @@ React package:
79
79
 
80
80
  ```json
81
81
  {
82
- "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
82
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
83
83
  "extends": ["@howells/lint/biome/core", "@howells/lint/biome/react"],
84
84
  "root": true
85
85
  }
@@ -89,12 +89,8 @@ Next.js app:
89
89
 
90
90
  ```json
91
91
  {
92
- "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
93
- "extends": [
94
- "@howells/lint/biome/core",
95
- "@howells/lint/biome/react",
96
- "@howells/lint/biome/next"
97
- ],
92
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
93
+ "extends": ["@howells/lint/biome/core", "@howells/lint/biome/react", "@howells/lint/biome/next"],
98
94
  "root": true
99
95
  }
100
96
  ```
@@ -103,7 +99,9 @@ Next.js app:
103
99
 
104
100
  Use this lane when a project wants Oxlint and Oxfmt instead of Biome. React and Next presets stack the relevant Ultracite Ox rules with [React Doctor](https://react.doctor) rules in one config.
105
101
 
106
- React Doctor severities are passed through as published by React Doctor. Native Oxlint Next.js severities come from Oxlint's official `nextjs` plugin via Ultracite's Next preset. `@howells/lint` adds canonical Howells policy on top for file naming, barrel files, env access, and tests.
102
+ React Doctor severities are passed through as published by React Doctor. Native Oxlint Next.js severities come from Oxlint's official `nextjs` plugin via Ultracite's Next preset. `@howells/lint` adds canonical Howells policy on top for file naming, barrel files, env access, file size, function size, complexity, and tests.
103
+
104
+ The core Oxlint preset enables native Oxlint rules that keep code files navigable: `max-lines` errors above 600 non-comment, non-blank lines; `max-lines-per-function` errors above 120 non-comment, non-blank lines; `max-statements` errors above 45 statements per function; and `complexity` errors above cyclomatic complexity 15. Generated files should be ignored at the project level; rare intentional exceptions should use an exact-file override with a short refactor note.
107
105
 
108
106
  Choose the closest preset:
109
107
 
@@ -163,7 +161,7 @@ export default defineConfig({
163
161
  });
164
162
  ```
165
163
 
166
- Run boundary configs from the monorepo root so element patterns such as `apps/*/**` and `packages/*/**` match the project tree.
164
+ Run boundary configs from the monorepo root so element patterns such as `apps/*` and `packages/*` match each workspace member as a single architectural element.
167
165
 
168
166
  Mixed monorepo with a Next.js app and Node-only packages:
169
167
 
package/bin/env.mjs ADDED
@@ -0,0 +1,3 @@
1
+ /* oxlint-disable no-restricted-properties -- CLI wrappers intentionally pass the caller environment to child processes. */
2
+
3
+ export const inheritedEnv = () => process.env;
@@ -3,9 +3,6 @@
3
3
  import { runPackageBin } from "./run-package-bin.mjs";
4
4
 
5
5
  const targets = process.argv.slice(2);
6
- const args =
7
- targets.length > 0
8
- ? ["check", "--write", ...targets]
9
- : ["check", "--write", "."];
6
+ const args = targets.length > 0 ? ["check", "--write", ...targets] : ["check", "--write", "."];
10
7
 
11
8
  runPackageBin("@biomejs/biome", "biome", args);
@@ -4,39 +4,38 @@ import { runPackageBin } from "./run-package-bin.mjs";
4
4
 
5
5
  const args = process.argv.slice(2);
6
6
  const passthroughOptions = new Set(["--help", "-h", "--version", "-V"]);
7
- const targets =
8
- args.length === 0 || args[0].startsWith("-") ? [".", ...args] : args;
7
+ const targets = args.length === 0 || args[0].startsWith("-") ? [".", ...args] : args;
9
8
 
10
9
  const strictRuleOptions = [
11
- "--only=security",
12
- "--skip=security/noSecrets",
13
- "--only=correctness/noConstAssign",
14
- "--only=correctness/noUnreachable",
15
- "--only=correctness/noInvalidConstructorSuper",
16
- "--only=correctness/noSetterReturn",
17
- "--only=correctness/noUnsafeFinally",
18
- "--only=correctness/noUnsafeOptionalChaining",
19
- "--only=correctness/noGlobalObjectCalls",
20
- "--only=correctness/noSelfAssign",
21
- "--only=correctness/noSwitchDeclarations",
22
- "--only=suspicious/noDebugger",
23
- "--only=suspicious/noDoubleEquals",
24
- "--only=suspicious/noExplicitAny",
25
- "--only=suspicious/noCatchAssign",
26
- "--only=suspicious/noFunctionAssign",
27
- "--only=suspicious/noGlobalAssign",
28
- "--only=suspicious/noRedeclare",
29
- "--only=suspicious/noSparseArray",
30
- "--only=suspicious/noVar",
31
- "--only=suspicious/noDuplicateCase",
32
- "--only=suspicious/noDuplicateObjectKeys",
33
- "--only=suspicious/noDuplicateParameters",
34
- "--only=suspicious/noFallthroughSwitchClause",
35
- "--only=suspicious/noFocusedTests",
10
+ "--only=security",
11
+ "--skip=security/noSecrets",
12
+ "--only=correctness/noConstAssign",
13
+ "--only=correctness/noUnreachable",
14
+ "--only=correctness/noInvalidConstructorSuper",
15
+ "--only=correctness/noSetterReturn",
16
+ "--only=correctness/noUnsafeFinally",
17
+ "--only=correctness/noUnsafeOptionalChaining",
18
+ "--only=correctness/noGlobalObjectCalls",
19
+ "--only=correctness/noSelfAssign",
20
+ "--only=correctness/noSwitchDeclarations",
21
+ "--only=suspicious/noDebugger",
22
+ "--only=suspicious/noDoubleEquals",
23
+ "--only=suspicious/noExplicitAny",
24
+ "--only=suspicious/noCatchAssign",
25
+ "--only=suspicious/noFunctionAssign",
26
+ "--only=suspicious/noGlobalAssign",
27
+ "--only=suspicious/noRedeclare",
28
+ "--only=suspicious/noSparseArray",
29
+ "--only=suspicious/noVar",
30
+ "--only=suspicious/noDuplicateCase",
31
+ "--only=suspicious/noDuplicateObjectKeys",
32
+ "--only=suspicious/noDuplicateParameters",
33
+ "--only=suspicious/noFallthroughSwitchClause",
34
+ "--only=suspicious/noFocusedTests",
36
35
  ];
37
36
 
38
37
  const resolvedArgs = passthroughOptions.has(args[0])
39
- ? args
40
- : ["lint", ...strictRuleOptions, ...targets];
38
+ ? args
39
+ : ["lint", ...strictRuleOptions, ...targets];
41
40
 
42
41
  runPackageBin("@biomejs/biome", "biome", resolvedArgs);
@@ -4,33 +4,41 @@ import { runPackageBin } from "./run-package-bin.mjs";
4
4
 
5
5
  const args = process.argv.slice(2);
6
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",
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
- 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];
25
+ const resolveBiomeArgs = (inputArgs) => {
26
+ if (inputArgs.length === 0) {
27
+ return ["check", "."];
28
+ }
29
+
30
+ const [firstArg] = inputArgs;
31
+ if (biomeCommands.has(firstArg) || passthroughOptions.has(firstArg)) {
32
+ return inputArgs;
33
+ }
34
+
35
+ if (firstArg.startsWith("-")) {
36
+ return ["check", ".", ...inputArgs];
37
+ }
38
+
39
+ return ["check", ...inputArgs];
40
+ };
41
+
42
+ const resolvedArgs = resolveBiomeArgs(args);
35
43
 
36
44
  runPackageBin("@biomejs/biome", "biome", resolvedArgs);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from "node:child_process";
4
+ import { inheritedEnv } from "./env.mjs";
4
5
  import { resolvePackageBin } from "./run-package-bin.mjs";
5
6
 
6
7
  const args = process.argv.slice(2);
@@ -8,21 +9,21 @@ const targets = args.filter((arg) => !arg.startsWith("-"));
8
9
  const oxlintOptions = args.filter((arg) => arg.startsWith("-"));
9
10
  const resolvedTargets = targets.length > 0 ? targets : ["."];
10
11
 
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
- });
12
+ const run = (packageName, binName, commandArgs) => {
13
+ const binPath = resolvePackageBin(packageName, binName);
14
+ const result = spawnSync(binPath, commandArgs, {
15
+ env: inheritedEnv(),
16
+ stdio: "inherit",
17
+ });
17
18
 
18
- if (result.error) {
19
- throw result.error;
20
- }
19
+ if (result.error) {
20
+ throw result.error;
21
+ }
21
22
 
22
- if ((result.status ?? 1) !== 0) {
23
- process.exit(result.status ?? 1);
24
- }
25
- }
23
+ if ((result.status ?? 1) !== 0) {
24
+ process.exit(result.status ?? 1);
25
+ }
26
+ };
26
27
 
27
28
  run("oxfmt", "oxfmt", ["--check", ...resolvedTargets]);
28
29
  run("oxlint", "oxlint", [...oxlintOptions, ...resolvedTargets]);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from "node:child_process";
4
+ import { inheritedEnv } from "./env.mjs";
4
5
  import { resolvePackageBin } from "./run-package-bin.mjs";
5
6
 
6
7
  const args = process.argv.slice(2);
@@ -10,25 +11,25 @@ const targets = filteredArgs.filter((arg) => !arg.startsWith("-"));
10
11
  const oxlintOptions = filteredArgs.filter((arg) => arg.startsWith("-"));
11
12
  const resolvedTargets = targets.length > 0 ? targets : ["."];
12
13
 
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
- });
14
+ const run = (packageName, binName, commandArgs) => {
15
+ const binPath = resolvePackageBin(packageName, binName);
16
+ const result = spawnSync(binPath, commandArgs, {
17
+ env: inheritedEnv(),
18
+ stdio: "inherit",
19
+ });
19
20
 
20
- if (result.error) {
21
- throw result.error;
22
- }
21
+ if (result.error) {
22
+ throw result.error;
23
+ }
23
24
 
24
- if ((result.status ?? 1) !== 0) {
25
- process.exit(result.status ?? 1);
26
- }
27
- }
25
+ if ((result.status ?? 1) !== 0) {
26
+ process.exit(result.status ?? 1);
27
+ }
28
+ };
28
29
 
29
30
  run("oxfmt", "oxfmt", ["--write", ...resolvedTargets]);
30
31
  run("oxlint", "oxlint", [
31
- useDangerousFixes ? "--fix-dangerously" : "--fix",
32
- ...oxlintOptions,
33
- ...resolvedTargets,
32
+ useDangerousFixes ? "--fix-dangerously" : "--fix",
33
+ ...oxlintOptions,
34
+ ...resolvedTargets,
34
35
  ]);
@@ -7,19 +7,19 @@ import { runWorkspacePreflight } from "./workspace-preflight.mjs";
7
7
  const args = process.argv.slice(2);
8
8
 
9
9
  if (handleManypkgMetadataCommand("check", args)) {
10
- process.exit(0);
10
+ process.exit(0);
11
11
  }
12
12
 
13
13
  const errors = runWorkspacePreflight();
14
14
 
15
15
  if (errors.length > 0) {
16
- console.error("Workspace hygiene check failed:");
16
+ console.error("Workspace hygiene check failed:");
17
17
 
18
- for (const error of errors) {
19
- console.error(`- ${error}`);
20
- }
18
+ for (const error of errors) {
19
+ console.error(`- ${error}`);
20
+ }
21
21
 
22
- process.exit(1);
22
+ process.exit(1);
23
23
  }
24
24
 
25
25
  runPackageBin("@manypkg/cli", "manypkg", ["check", ...args]);
@@ -7,34 +7,34 @@ const require = createRequire(import.meta.url);
7
7
  const helpOptions = new Set(["--help", "-h"]);
8
8
  const versionOptions = new Set(["--version", "-V"]);
9
9
 
10
- export function printManypkgCommandHelp(command) {
11
- console.log(`Usage: howells-workspace-${command} [options]\n`);
12
- console.log(`Runs: manypkg ${command} [options]`);
13
- }
14
-
15
- export function printManypkgCliVersion() {
16
- const { version } = require("@manypkg/cli/package.json");
17
- console.log(version);
18
- }
19
-
20
- export function handleManypkgMetadataCommand(command, args) {
21
- if (helpOptions.has(args[0])) {
22
- printManypkgCommandHelp(command);
23
- return true;
24
- }
25
-
26
- if (versionOptions.has(args[0])) {
27
- printManypkgCliVersion();
28
- return true;
29
- }
30
-
31
- return false;
32
- }
33
-
34
- export function runManypkgCommand(command, args) {
35
- if (handleManypkgMetadataCommand(command, args)) {
36
- process.exit(0);
37
- }
38
-
39
- runPackageBin("@manypkg/cli", "manypkg", [command, ...args]);
40
- }
10
+ export const printManypkgCommandHelp = (command) => {
11
+ console.log(`Usage: howells-workspace-${command} [options]\n`);
12
+ console.log(`Runs: manypkg ${command} [options]`);
13
+ };
14
+
15
+ export const printManypkgCliVersion = () => {
16
+ const { version } = require("@manypkg/cli/package.json");
17
+ console.log(version);
18
+ };
19
+
20
+ export const handleManypkgMetadataCommand = (command, args) => {
21
+ if (helpOptions.has(args[0])) {
22
+ printManypkgCommandHelp(command);
23
+ return true;
24
+ }
25
+
26
+ if (versionOptions.has(args[0])) {
27
+ printManypkgCliVersion();
28
+ return true;
29
+ }
30
+
31
+ return false;
32
+ };
33
+
34
+ export const runManypkgCommand = (command, args) => {
35
+ if (handleManypkgMetadataCommand(command, args)) {
36
+ process.exit(0);
37
+ }
38
+
39
+ runPackageBin("@manypkg/cli", "manypkg", [command, ...args]);
40
+ };
@@ -4,75 +4,65 @@ import { spawnSync } from "node:child_process";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { createRequire } from "node:module";
6
6
  import { dirname, join } from "node:path";
7
- import { fileURLToPath } from "node:url";
7
+ import { inheritedEnv } from "./env.mjs";
8
8
 
9
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(
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
- );
45
- }
46
-
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;
52
-
53
- if (typeof binField === "string") {
54
- return join(packageDir, binField);
55
- }
56
-
57
- if (binField && typeof binField === "object" && binField[binName]) {
58
- return join(packageDir, binField[binName]);
59
- }
60
-
61
- throw new Error(
62
- `Could not resolve bin '${binName}' for package '${packageName}'.`,
63
- );
64
- }
65
-
66
- export function runPackageBin(packageName, binName, args) {
67
- const binPath = resolvePackageBin(packageName, binName);
68
- const result = spawnSync(binPath, args, {
69
- stdio: "inherit",
70
- env: process.env,
71
- });
72
-
73
- if (result.error) {
74
- throw result.error;
75
- }
76
-
77
- process.exit(result.status ?? 1);
78
- }
10
+ const currentDir = import.meta.dirname;
11
+
12
+ const 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
+ export const resolvePackageBin = (packageName, binName) => {
40
+ const packageJsonPath = resolvePackageJsonPath(packageName);
41
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
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 const runPackageBin = (packageName, binName, args) => {
57
+ const binPath = resolvePackageBin(packageName, binName);
58
+ const result = spawnSync(binPath, args, {
59
+ env: inheritedEnv(),
60
+ stdio: "inherit",
61
+ });
62
+
63
+ if (result.error) {
64
+ throw result.error;
65
+ }
66
+
67
+ process.exit(result.status ?? 1);
68
+ };