@monke-together-strong/oxc-config 0.1.1 → 0.4.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 +61 -10
- package/base.json +18 -0
- package/dist/oxfmt.d.mts +3 -3
- package/dist/oxfmt.mjs +8 -1
- package/dist/oxlint.d.mts +19 -4
- package/dist/oxlint.mjs +297 -10
- package/package.json +14 -8
package/README.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
# `@monke-together-strong/oxc-config`
|
|
2
2
|
|
|
3
|
-
Shared Oxlint and Oxfmt presets maintained by Monke Together Strong.
|
|
3
|
+
Shared TypeScript, Oxlint, and Oxfmt presets maintained by Monke Together Strong.
|
|
4
|
+
|
|
5
|
+
## TypeScript
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun add -D @monke-together-strong/oxc-config typescript
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"extends": "@monke-together-strong/oxc-config/base.json",
|
|
14
|
+
"compilerOptions": {
|
|
15
|
+
"module": "NodeNext",
|
|
16
|
+
"moduleResolution": "NodeNext",
|
|
17
|
+
"noEmit": true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The base preset contains runtime-neutral correctness and soundness settings. Consumers remain
|
|
23
|
+
responsible for environment-specific options such as `lib`, `module`, `moduleResolution`, JSX,
|
|
24
|
+
runtime types, and emit behavior.
|
|
25
|
+
|
|
26
|
+
## Oxc
|
|
4
27
|
|
|
5
28
|
The presets compose Ultracite's native Oxc configuration while allowing repository-specific settings to override shared defaults.
|
|
6
29
|
|
|
@@ -45,22 +68,50 @@ export { default } from "@monke-together-strong/oxc-config/oxfmt";
|
|
|
45
68
|
|
|
46
69
|
```ts
|
|
47
70
|
// oxlint.config.ts
|
|
48
|
-
|
|
71
|
+
import { createOxlintConfig } from "@monke-together-strong/oxc-config/oxlint";
|
|
72
|
+
|
|
73
|
+
export default createOxlintConfig();
|
|
49
74
|
```
|
|
50
75
|
|
|
51
|
-
The Oxfmt preset uses a print width of 100
|
|
76
|
+
The Oxfmt preset formats JSDoc and uses a print width of 100. It formats first-party skill code and metadata while excluding imported skills, imported references, skill Markdown, and `AGENTS.md`. Consumer `ignorePatterns` append after these shared ignores; other consumer fields override the shared formatter defaults.
|
|
52
77
|
|
|
53
|
-
The default Oxlint preset composes Ultracite's core and Vitest presets, enables type-aware linting and TypeScript type checking, and
|
|
78
|
+
The default Oxlint preset composes Ultracite's core, anti-slop, and Vitest presets, enables type-aware linting and TypeScript type checking, and rejects console calls and direct environment access. Environment variables should be read and validated through a centralized configuration boundary. It uses `eslint-plugin-perfectionist` to sort object literals, destructuring, object types, interfaces, JSX props, enums, and heritage clauses while treating comments as intentional object and enum partitions, and disables Ultracite's `sort-keys` rule. It disables anti-slop's `no-array-filter-map` because its suggested `flatMap` and iterator-helper rewrites run slower than `filter().map()` on Bun. It also loads `eslint-plugin-jsdoc` through Oxlint's JavaScript-plugin compatibility layer to reject undefined JSDoc types that Oxlint does not yet implement natively. Files named `*.test.*`, `*.spec.*`, or stored under `__tests__` are assumed to use Vitest. Its framework-neutral test policy also covers files under `test/` and `tests/`, where `typescript/no-unsafe-type-assertion` is disabled because typed mock and fixture setup is not yet ergonomic enough to justify the churn.
|
|
54
79
|
|
|
55
|
-
|
|
80
|
+
### Framework presets
|
|
81
|
+
|
|
82
|
+
`frameworks` enables Ultracite's React, TanStack, and shadcn presets and React Doctor's rules in the listed directories, relative to the config. `"."` covers the whole repository.
|
|
56
83
|
|
|
57
84
|
```ts
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
85
|
+
export default createOxlintConfig({
|
|
86
|
+
frameworks: {
|
|
87
|
+
react: ["apps/web"],
|
|
88
|
+
"react-doctor": ["apps/web"],
|
|
89
|
+
shadcn: ["apps/web"],
|
|
90
|
+
tanstack: ["apps/web"]
|
|
91
|
+
},
|
|
92
|
+
rules: {
|
|
93
|
+
"shadcn/no-raw-colors": ["error", { allow: ["text-body"] }]
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Install `@shadcn/lint` for `shadcn` and `oxlint-plugin-react-doctor` for `react-doctor`. React Doctor's TanStack rules apply where the `react-doctor` and `tanstack` scopes overlap.
|
|
99
|
+
|
|
100
|
+
`createOxlintConfig` throws when the root or a workspace `package.json` depends on one of these packages without a matching `frameworks` entry:
|
|
101
|
+
|
|
102
|
+
- `react`, `@tanstack/react-router`, and `@tanstack/react-start` require the framework to cover the package that depends on them.
|
|
103
|
+
- `@shadcn/lint` and `oxlint-plugin-react-doctor` require the framework to list at least one directory.
|
|
61
104
|
|
|
105
|
+
Workspaces come from `workspaces` in `package.json` and `packages` in `pnpm-workspace.yaml`. Set a framework to `false` to decline it. The check reads manifests from `root`, which defaults to the nearest directory at or above the working directory that contains `.git`. If the config is not at the repository root, pass its directory as `root`.
|
|
106
|
+
|
|
107
|
+
Root `rules` for a directory-scoped framework's plugins, such as `react/*` or `shadcn/*`, apply inside its directories. When `react` is directory-scoped, an `overrides` entry that changes `react/*`, `react-perf/*`, or `jsx-a11y/*` rules must list those plugins in `plugins`. Otherwise Oxlint ignores the change.
|
|
108
|
+
|
|
109
|
+
### Vitest scope
|
|
110
|
+
|
|
111
|
+
Mixed-framework repositories can exclude framework-owned paths from the Vitest preset:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
62
114
|
export default createOxlintConfig({
|
|
63
|
-
extends: [react, tanstack],
|
|
64
115
|
ignorePatterns: [".repo-specific-output"],
|
|
65
116
|
overrides: [
|
|
66
117
|
{
|
|
@@ -78,6 +129,6 @@ export default createOxlintConfig({
|
|
|
78
129
|
});
|
|
79
130
|
```
|
|
80
131
|
|
|
81
|
-
`extends`, `ignorePatterns`, and `overrides` append after
|
|
132
|
+
`extends`, `ignorePatterns`, `jsPlugins`, and `overrides` append after the preset's values. Consumer `rules`, `options`, and `settings` override the preset's defaults. Other Oxlint fields pass through at the root.
|
|
82
133
|
|
|
83
134
|
The presets are published on public npm under the MIT license.
|
package/base.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"allowUnreachableCode": false,
|
|
5
|
+
"allowUnusedLabels": false,
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"moduleDetection": "force",
|
|
9
|
+
"noFallthroughCasesInSwitch": true,
|
|
10
|
+
"noImplicitOverride": true,
|
|
11
|
+
"noImplicitReturns": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"noUnusedLocals": true,
|
|
14
|
+
"noUnusedParameters": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strict": true
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/oxfmt.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OxfmtConfig } from "oxfmt";
|
|
2
2
|
//#region src/oxfmt.d.ts
|
|
3
|
-
declare function createOxfmtConfig(options?: OxfmtConfig): OxfmtConfig;
|
|
4
|
-
declare const oxfmtConfig: OxfmtConfig;
|
|
3
|
+
export declare function createOxfmtConfig(options?: OxfmtConfig): OxfmtConfig;
|
|
4
|
+
export declare const oxfmtConfig: OxfmtConfig;
|
|
5
5
|
//#endregion
|
|
6
|
-
export {
|
|
6
|
+
export { oxfmtConfig as default };
|
package/dist/oxfmt.mjs
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import ultracite from "ultracite/oxfmt";
|
|
2
2
|
//#region src/oxfmt.ts
|
|
3
|
-
const sharedIgnorePatterns = [
|
|
3
|
+
const sharedIgnorePatterns = [
|
|
4
|
+
"skills/imported/**",
|
|
5
|
+
"skills/references/imported/**",
|
|
6
|
+
"skills/**/*.md",
|
|
7
|
+
"AGENTS.md",
|
|
8
|
+
"**/routeTree.gen.ts"
|
|
9
|
+
];
|
|
4
10
|
function createOxfmtConfig(options = {}) {
|
|
5
11
|
const { ignorePatterns = [], ...config } = options;
|
|
6
12
|
return {
|
|
7
13
|
...ultracite,
|
|
14
|
+
jsdoc: true,
|
|
8
15
|
printWidth: 100,
|
|
9
16
|
proseWrap: "preserve",
|
|
10
17
|
trailingComma: "none",
|
package/dist/oxlint.d.mts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { OxlintConfig } from "oxlint";
|
|
2
|
+
//#region src/frameworks.d.ts
|
|
3
|
+
declare const frameworkNames: readonly ["react", "react-doctor", "shadcn", "tanstack"];
|
|
4
|
+
type Framework = (typeof frameworkNames)[number];
|
|
5
|
+
/**
|
|
6
|
+
* Directories each framework preset lints, relative to the config's directory. `"."` covers the
|
|
7
|
+
* whole repository, and `false` declines a framework that a dependency would otherwise require.
|
|
8
|
+
*/
|
|
9
|
+
type FrameworkScopes = Partial<Record<Framework, false | readonly string[]>>;
|
|
10
|
+
//#endregion
|
|
2
11
|
//#region src/oxlint.d.ts
|
|
3
|
-
interface CreateOxlintConfigOptions extends OxlintConfig {
|
|
12
|
+
export interface CreateOxlintConfigOptions extends OxlintConfig {
|
|
13
|
+
/** Ultracite framework presets and the directories they lint. */
|
|
14
|
+
frameworks?: FrameworkScopes;
|
|
15
|
+
/**
|
|
16
|
+
* Directory holding the config, whose package manifests are checked for framework dependencies.
|
|
17
|
+
* Defaults to the nearest directory at or above the working directory that contains `.git`.
|
|
18
|
+
*/
|
|
19
|
+
root?: string;
|
|
4
20
|
/** Files receiving framework-neutral test policy, including test helpers. */
|
|
5
21
|
testFiles?: readonly string[];
|
|
6
22
|
/** Non-Vitest test files to exclude from the default Vitest preset. */
|
|
7
23
|
vitestExcludeFiles?: readonly string[];
|
|
8
24
|
}
|
|
9
|
-
declare function createOxlintConfig(options?: CreateOxlintConfigOptions): OxlintConfig;
|
|
10
|
-
declare const oxlintConfig: OxlintConfig;
|
|
25
|
+
export declare function createOxlintConfig(options?: CreateOxlintConfigOptions): OxlintConfig;
|
|
11
26
|
//#endregion
|
|
12
|
-
export {
|
|
27
|
+
export type { Framework, FrameworkScopes };
|
package/dist/oxlint.mjs
CHANGED
|
@@ -1,19 +1,235 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import antiSlop from "ultracite/oxlint/anti-slop";
|
|
1
3
|
import core from "ultracite/oxlint/core";
|
|
2
4
|
import vitest from "ultracite/oxlint/vitest";
|
|
5
|
+
import { existsSync, globSync, readFileSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { selectJsPlugins } from "ultracite/oxlint/js-plugins";
|
|
8
|
+
import react from "ultracite/oxlint/react";
|
|
9
|
+
import shadcn from "ultracite/oxlint/shadcn";
|
|
10
|
+
import tanstack from "ultracite/oxlint/tanstack";
|
|
11
|
+
import tanstackReactDoctor from "ultracite/oxlint/tanstack/js-plugins";
|
|
12
|
+
import { parse } from "yaml";
|
|
13
|
+
import * as z from "zod";
|
|
14
|
+
//#region src/frameworks.ts
|
|
15
|
+
const frameworkNames = [
|
|
16
|
+
"react",
|
|
17
|
+
"react-doctor",
|
|
18
|
+
"shadcn",
|
|
19
|
+
"tanstack"
|
|
20
|
+
];
|
|
21
|
+
const frameworkPresets = {
|
|
22
|
+
react: {
|
|
23
|
+
jsPluginNamespaces: [],
|
|
24
|
+
presets: (directories) => [{
|
|
25
|
+
directories,
|
|
26
|
+
preset: react
|
|
27
|
+
}]
|
|
28
|
+
},
|
|
29
|
+
"react-doctor": {
|
|
30
|
+
jsPluginNamespaces: ["react-doctor"],
|
|
31
|
+
presets: (directories, scopes) => [{
|
|
32
|
+
directories,
|
|
33
|
+
preset: selectJsPlugins(["react-doctor"])
|
|
34
|
+
}, {
|
|
35
|
+
directories: overlap(directories, normalizeDirectories(scopes.tanstack)),
|
|
36
|
+
preset: tanstackReactDoctor
|
|
37
|
+
}]
|
|
38
|
+
},
|
|
39
|
+
shadcn: {
|
|
40
|
+
jsPluginNamespaces: ["shadcn"],
|
|
41
|
+
presets: (directories) => [{
|
|
42
|
+
directories,
|
|
43
|
+
preset: shadcn
|
|
44
|
+
}]
|
|
45
|
+
},
|
|
46
|
+
tanstack: {
|
|
47
|
+
jsPluginNamespaces: [],
|
|
48
|
+
presets: (directories) => [{
|
|
49
|
+
directories,
|
|
50
|
+
preset: tanstack
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const frameworkDependencies = /* @__PURE__ */ new Map([
|
|
55
|
+
["@shadcn/lint", {
|
|
56
|
+
coversPackage: false,
|
|
57
|
+
framework: "shadcn"
|
|
58
|
+
}],
|
|
59
|
+
["@tanstack/react-router", {
|
|
60
|
+
coversPackage: true,
|
|
61
|
+
framework: "tanstack"
|
|
62
|
+
}],
|
|
63
|
+
["@tanstack/react-start", {
|
|
64
|
+
coversPackage: true,
|
|
65
|
+
framework: "tanstack"
|
|
66
|
+
}],
|
|
67
|
+
["oxlint-plugin-react-doctor", {
|
|
68
|
+
coversPackage: false,
|
|
69
|
+
framework: "react-doctor"
|
|
70
|
+
}],
|
|
71
|
+
["react", {
|
|
72
|
+
coversPackage: true,
|
|
73
|
+
framework: "react"
|
|
74
|
+
}]
|
|
75
|
+
]);
|
|
76
|
+
const manifestSchema = z.object({
|
|
77
|
+
dependencies: z.record(z.string(), z.unknown()).optional(),
|
|
78
|
+
devDependencies: z.record(z.string(), z.unknown()).optional(),
|
|
79
|
+
peerDependencies: z.record(z.string(), z.unknown()).optional(),
|
|
80
|
+
workspaces: z.union([z.array(z.string()), z.object({ packages: z.array(z.string()).optional() })]).optional()
|
|
81
|
+
});
|
|
82
|
+
const pnpmWorkspaceSchema = z.object({ packages: z.array(z.string()).optional() }).nullable();
|
|
83
|
+
/**
|
|
84
|
+
* Returns the nearest directory at or above `start` that contains `.git`, or `start` when none
|
|
85
|
+
* does. Vite+ evaluates the config with the invoking directory, which can be a subdirectory of the
|
|
86
|
+
* repository, as the working directory.
|
|
87
|
+
*/
|
|
88
|
+
function findRepositoryRoot(start = process.cwd()) {
|
|
89
|
+
for (let directory = start;; directory = path.dirname(directory)) {
|
|
90
|
+
if (existsSync(path.join(directory, ".git"))) return directory;
|
|
91
|
+
if (path.dirname(directory) === directory) return start;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Throws when a package depends on a framework that `scopes` neither covers nor declines. */
|
|
95
|
+
function checkFrameworkCoverage(root, scopes) {
|
|
96
|
+
const rootManifest = readManifest(root);
|
|
97
|
+
if (!rootManifest) return;
|
|
98
|
+
const problems = [".", ...workspaceDirectories(root, rootManifest)].flatMap((directory) => {
|
|
99
|
+
const manifestPath = directory === "." ? "package.json" : `${directory}/package.json`;
|
|
100
|
+
const location = directory === "." ? "the repository root" : directory;
|
|
101
|
+
return dependencyNames(readManifest(path.join(root, directory))).flatMap((dependency) => {
|
|
102
|
+
const signal = frameworkDependencies.get(dependency);
|
|
103
|
+
const scope = signal && scopes[signal.framework];
|
|
104
|
+
if (!signal || scope === false) return [];
|
|
105
|
+
if (!signal.coversPackage) return normalizeDirectories(scope).length === 0 ? [`${manifestPath} depends on ${dependency}, but frameworks["${signal.framework}"] lists no directories.`] : [];
|
|
106
|
+
return covers(scope ?? [], directory) ? [] : [`${manifestPath} depends on ${dependency}, but frameworks["${signal.framework}"] does not cover ${location}.`];
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
if (problems.length > 0) throw new Error([
|
|
110
|
+
"Framework presets do not cover these dependencies:",
|
|
111
|
+
...problems.map((problem) => `- ${problem}`),
|
|
112
|
+
"Set each framework to the directories it lints (\".\" for the whole repository), or to false to decline it."
|
|
113
|
+
].join("\n"));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Applies framework presets. Repository-wide presets extend the config. Directory-scoped presets
|
|
117
|
+
* become overrides, followed by a copy of the root `rules` for their plugins, because Oxlint
|
|
118
|
+
* applies overrides after root rules.
|
|
119
|
+
*/
|
|
120
|
+
function resolveFrameworks(scopes, rules) {
|
|
121
|
+
const resolved = {
|
|
122
|
+
extends: [],
|
|
123
|
+
overrides: [],
|
|
124
|
+
settings: {}
|
|
125
|
+
};
|
|
126
|
+
for (const framework of frameworkNames) {
|
|
127
|
+
const directories = normalizeDirectories(scopes[framework]);
|
|
128
|
+
if (directories.length === 0) continue;
|
|
129
|
+
const { jsPluginNamespaces, presets } = frameworkPresets[framework];
|
|
130
|
+
const placed = presets(directories, scopes).filter((entry) => entry.directories.length > 0);
|
|
131
|
+
for (const { directories: presetDirectories, preset } of placed) {
|
|
132
|
+
resolved.settings = {
|
|
133
|
+
...resolved.settings,
|
|
134
|
+
...preset.settings
|
|
135
|
+
};
|
|
136
|
+
if (presetDirectories.includes(".")) resolved.extends.push(preset);
|
|
137
|
+
else resolved.overrides.push(...scopePreset(preset, presetDirectories));
|
|
138
|
+
}
|
|
139
|
+
const scoped = [...new Set(placed.flatMap((entry) => entry.directories.includes(".") ? [] : entry.directories))];
|
|
140
|
+
const plugins = placed.flatMap(({ preset }) => preset.plugins ?? []);
|
|
141
|
+
const namespaces = /* @__PURE__ */ new Set([...plugins, ...jsPluginNamespaces]);
|
|
142
|
+
const tuning = Object.entries(rules).filter(([rule]) => namespaces.has(rule.split("/")[0] ?? ""));
|
|
143
|
+
if (scoped.length > 0 && tuning.length > 0) resolved.overrides.push({
|
|
144
|
+
files: within(scoped, ["**"]),
|
|
145
|
+
plugins,
|
|
146
|
+
rules: Object.fromEntries(tuning)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return resolved;
|
|
150
|
+
}
|
|
151
|
+
function scopePreset(preset, directories) {
|
|
152
|
+
const scoped = { files: within(directories, ["**"]) };
|
|
153
|
+
if (preset.plugins) scoped.plugins = preset.plugins;
|
|
154
|
+
if (preset.jsPlugins) scoped.jsPlugins = preset.jsPlugins;
|
|
155
|
+
if (preset.rules) scoped.rules = preset.rules;
|
|
156
|
+
return [scoped, ...(preset.overrides ?? []).map((override) => {
|
|
157
|
+
const nested = {
|
|
158
|
+
...override,
|
|
159
|
+
files: within(directories, override.files)
|
|
160
|
+
};
|
|
161
|
+
if (override.excludeFiles) nested.excludeFiles = within(directories, override.excludeFiles);
|
|
162
|
+
return nested;
|
|
163
|
+
})];
|
|
164
|
+
}
|
|
165
|
+
function within(directories, globs) {
|
|
166
|
+
return directories.flatMap((directory) => globs.map((glob) => `${directory}/${glob}`));
|
|
167
|
+
}
|
|
168
|
+
function normalizeDirectories(scope) {
|
|
169
|
+
return (scope === false ? [] : scope ?? []).map((directory) => path.posix.normalize(directory).replace(/\/$/u, ""));
|
|
170
|
+
}
|
|
171
|
+
function covers(scope, directory) {
|
|
172
|
+
return normalizeDirectories(scope).some((scoped) => scoped === "." || directory === scoped || directory.startsWith(`${scoped}/`));
|
|
173
|
+
}
|
|
174
|
+
/** Returns the directories covered by both scopes, keeping the narrower directory of each pair. */
|
|
175
|
+
function overlap(left, right) {
|
|
176
|
+
return [...new Set(left.flatMap((outer) => right.flatMap((inner) => {
|
|
177
|
+
if (covers([outer], inner)) return [inner];
|
|
178
|
+
return covers([inner], outer) ? [outer] : [];
|
|
179
|
+
})))];
|
|
180
|
+
}
|
|
181
|
+
function readManifest(directory) {
|
|
182
|
+
const file = path.join(directory, "package.json");
|
|
183
|
+
return existsSync(file) ? manifestSchema.parse(JSON.parse(readFileSync(file, "utf-8"))) : void 0;
|
|
184
|
+
}
|
|
185
|
+
function dependencyNames(manifest) {
|
|
186
|
+
return Object.keys({
|
|
187
|
+
...manifest?.dependencies,
|
|
188
|
+
...manifest?.devDependencies,
|
|
189
|
+
...manifest?.peerDependencies
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function workspaceDirectories(root, manifest) {
|
|
193
|
+
const pnpmWorkspace = path.join(root, "pnpm-workspace.yaml");
|
|
194
|
+
const patterns = [...Array.isArray(manifest.workspaces) ? manifest.workspaces : manifest.workspaces?.packages ?? [], ...existsSync(pnpmWorkspace) ? pnpmWorkspaceSchema.parse(parse(readFileSync(pnpmWorkspace, "utf-8")))?.packages ?? [] : []];
|
|
195
|
+
const expand = (globs) => new Set(globs.flatMap((glob) => globSync(glob, { cwd: root })).map((match) => path.posix.normalize(match.split(path.sep).join("/"))));
|
|
196
|
+
const excluded = expand(patterns.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1)));
|
|
197
|
+
return [...expand(patterns.filter((pattern) => !pattern.startsWith("!")))].filter((directory) => directory !== "." && !excluded.has(directory) && existsSync(path.join(root, directory, "package.json")));
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
3
200
|
//#region src/oxlint.ts
|
|
4
|
-
const
|
|
201
|
+
const localRequire = createRequire(import.meta.url);
|
|
202
|
+
const jsdocPlugin = {
|
|
203
|
+
name: "jsdoc-js",
|
|
204
|
+
specifier: localRequire.resolve("eslint-plugin-jsdoc")
|
|
205
|
+
};
|
|
206
|
+
const perfectionistPlugin = {
|
|
207
|
+
name: "perfectionist",
|
|
208
|
+
specifier: localRequire.resolve("eslint-plugin-perfectionist")
|
|
209
|
+
};
|
|
210
|
+
const defaultTestFiles = [
|
|
211
|
+
"**/*.{test,spec}.{ts,tsx,js,jsx,mts,cts,mjs,cjs}",
|
|
212
|
+
"**/__tests__/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}",
|
|
213
|
+
"**/__mocks__/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}",
|
|
214
|
+
"**/{test,tests}/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"
|
|
215
|
+
];
|
|
216
|
+
const executableTestFiles = ["**/*.{test,spec}.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"];
|
|
217
|
+
const generatedFileIgnorePatterns = ["**/routeTree.gen.ts"];
|
|
5
218
|
function createOxlintConfig(options = {}) {
|
|
6
|
-
const { extends: extensions = [], ignorePatterns = [], options: lintOptions = {}, overrides = [], rules = {}, testFiles = defaultTestFiles, vitestExcludeFiles = [], ...config } = options;
|
|
219
|
+
const { extends: extensions = [], frameworks = {}, ignorePatterns = [], jsPlugins = [], options: lintOptions = {}, overrides = [], root = findRepositoryRoot(), rules = {}, settings = {}, testFiles = defaultTestFiles, vitestExcludeFiles = [], ...config } = options;
|
|
220
|
+
checkFrameworkCoverage(root, frameworks);
|
|
221
|
+
const framework = resolveFrameworks(frameworks, rules);
|
|
7
222
|
const vitestOverrides = (vitest.overrides ?? []).map((override) => {
|
|
8
223
|
const excludeFiles = [...override.excludeFiles ?? [], ...vitestExcludeFiles];
|
|
9
|
-
|
|
224
|
+
const resolvedOverride = {
|
|
10
225
|
...override,
|
|
11
|
-
...excludeFiles.length > 0 ? { excludeFiles } : {},
|
|
12
226
|
rules: {
|
|
13
227
|
...override.rules,
|
|
14
228
|
"vitest/max-expects": "off"
|
|
15
229
|
}
|
|
16
230
|
};
|
|
231
|
+
if (excludeFiles.length > 0) resolvedOverride.excludeFiles = excludeFiles;
|
|
232
|
+
return resolvedOverride;
|
|
17
233
|
});
|
|
18
234
|
const vitestConfig = {
|
|
19
235
|
...vitest,
|
|
@@ -23,42 +239,113 @@ function createOxlintConfig(options = {}) {
|
|
|
23
239
|
...config,
|
|
24
240
|
extends: [
|
|
25
241
|
core,
|
|
242
|
+
antiSlop,
|
|
26
243
|
vitestConfig,
|
|
244
|
+
...framework.extends,
|
|
27
245
|
...extensions
|
|
28
246
|
],
|
|
29
|
-
ignorePatterns: [
|
|
247
|
+
ignorePatterns: [
|
|
248
|
+
...core.ignorePatterns ?? [],
|
|
249
|
+
...generatedFileIgnorePatterns,
|
|
250
|
+
...ignorePatterns
|
|
251
|
+
],
|
|
252
|
+
jsPlugins: [
|
|
253
|
+
jsdocPlugin,
|
|
254
|
+
perfectionistPlugin,
|
|
255
|
+
...jsPlugins ?? []
|
|
256
|
+
],
|
|
30
257
|
options: {
|
|
31
258
|
typeAware: true,
|
|
32
259
|
typeCheck: true,
|
|
33
260
|
...lintOptions
|
|
34
261
|
},
|
|
35
262
|
overrides: [
|
|
263
|
+
{
|
|
264
|
+
files: executableTestFiles,
|
|
265
|
+
rules: { complexity: "off" }
|
|
266
|
+
},
|
|
36
267
|
{
|
|
37
268
|
files: [...testFiles],
|
|
38
|
-
rules: {
|
|
269
|
+
rules: {
|
|
270
|
+
"no-await-in-loop": "off",
|
|
271
|
+
"no-console": "off",
|
|
272
|
+
"no-inline-comments": "off",
|
|
273
|
+
"no-script-url": "off",
|
|
274
|
+
"node/no-process-env": "off",
|
|
275
|
+
"require-await": "off",
|
|
276
|
+
"typescript/no-dynamic-delete": "off",
|
|
277
|
+
"typescript/no-unsafe-type-assertion": "off",
|
|
278
|
+
"typescript/restrict-template-expressions": "off",
|
|
279
|
+
"typescript/unbound-method": "off",
|
|
280
|
+
"unicorn/consistent-function-scoping": "off",
|
|
281
|
+
"unicorn/no-object-as-default-parameter": "off"
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
files: ["**/skills/**"],
|
|
286
|
+
rules: { "node/no-process-env": "off" }
|
|
39
287
|
},
|
|
40
288
|
{
|
|
41
289
|
files: ["**/*.{ts,tsx}"],
|
|
42
290
|
rules: { "unicorn/no-useless-undefined": ["error", { checkArguments: false }] }
|
|
43
291
|
},
|
|
292
|
+
...framework.overrides,
|
|
44
293
|
...overrides
|
|
45
294
|
],
|
|
46
295
|
rules: {
|
|
296
|
+
"anti-slop/no-array-filter-map": "off",
|
|
297
|
+
"anti-slop/no-conditional-empty-object-spread": "off",
|
|
298
|
+
"class-methods-use-this": "off",
|
|
299
|
+
eqeqeq: ["error", "smart"],
|
|
47
300
|
"func-style": "off",
|
|
301
|
+
"jsdoc-js/no-undefined-types": "error",
|
|
302
|
+
"max-classes-per-file": "off",
|
|
303
|
+
"no-console": "error",
|
|
304
|
+
"no-eq-null": "off",
|
|
305
|
+
"no-negated-condition": "off",
|
|
48
306
|
"no-nested-ternary": "off",
|
|
49
|
-
"no-use-before-define": ["error", {
|
|
307
|
+
"no-use-before-define": ["error", {
|
|
308
|
+
classes: false,
|
|
309
|
+
functions: false,
|
|
310
|
+
variables: false
|
|
311
|
+
}],
|
|
50
312
|
"no-warning-comments": "off",
|
|
313
|
+
"node/no-process-env": "error",
|
|
314
|
+
"perfectionist/sort-enums": ["error", {
|
|
315
|
+
partitionByComment: true,
|
|
316
|
+
sortByValue: "always"
|
|
317
|
+
}],
|
|
318
|
+
"perfectionist/sort-heritage-clauses": "error",
|
|
319
|
+
"perfectionist/sort-interfaces": "error",
|
|
320
|
+
"perfectionist/sort-jsx-props": "error",
|
|
321
|
+
"perfectionist/sort-object-types": "error",
|
|
322
|
+
"perfectionist/sort-objects": ["error", { partitionByComment: true }],
|
|
51
323
|
"prefer-regex-literals": "off",
|
|
52
324
|
"promise/avoid-new": "off",
|
|
53
325
|
"promise/prefer-await-to-callbacks": "off",
|
|
54
326
|
"promise/prefer-await-to-then": "off",
|
|
327
|
+
"sort-keys": "off",
|
|
328
|
+
"typescript/consistent-return": "off",
|
|
329
|
+
"typescript/explicit-module-boundary-types": "off",
|
|
330
|
+
"typescript/no-extraneous-class": ["error", { allowWithDecorator: true }],
|
|
331
|
+
"typescript/parameter-properties": "off",
|
|
332
|
+
"typescript/prefer-nullish-coalescing": ["error", { ignorePrimitives: { string: true } }],
|
|
55
333
|
"typescript/promise-function-async": "off",
|
|
56
|
-
"typescript/
|
|
334
|
+
"typescript/strict-boolean-expressions": ["error", {
|
|
335
|
+
allowNullableBoolean: true,
|
|
336
|
+
allowNullableObject: true,
|
|
337
|
+
allowNullableString: true
|
|
338
|
+
}],
|
|
339
|
+
"unicorn/no-array-reduce": "off",
|
|
340
|
+
"unicorn/no-negated-condition": "off",
|
|
57
341
|
"unicorn/no-nested-ternary": "off",
|
|
58
342
|
...rules
|
|
343
|
+
},
|
|
344
|
+
settings: {
|
|
345
|
+
...framework.settings,
|
|
346
|
+
...settings
|
|
59
347
|
}
|
|
60
348
|
};
|
|
61
349
|
}
|
|
62
|
-
const oxlintConfig = createOxlintConfig();
|
|
63
350
|
//#endregion
|
|
64
|
-
export { createOxlintConfig
|
|
351
|
+
export { createOxlintConfig };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monke-together-strong/oxc-config",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared Oxlint and Oxfmt presets.",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Shared TypeScript, Oxlint, and Oxfmt presets.",
|
|
5
5
|
"homepage": "https://github.com/monke-together-strong/monke-tools/tree/main/packages/oxc-config#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/monke-together-strong/monke-tools/issues"
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
"directory": "packages/oxc-config"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
+
"base.json",
|
|
16
17
|
"dist",
|
|
17
18
|
"LICENSE",
|
|
18
19
|
"README.md"
|
|
19
20
|
],
|
|
20
21
|
"type": "module",
|
|
21
22
|
"exports": {
|
|
23
|
+
"./base.json": "./base.json",
|
|
22
24
|
"./oxfmt": {
|
|
23
25
|
"types": "./dist/oxfmt.d.mts",
|
|
24
26
|
"import": "./dist/oxfmt.mjs"
|
|
@@ -35,15 +37,19 @@
|
|
|
35
37
|
"prepack": "vp pack"
|
|
36
38
|
},
|
|
37
39
|
"dependencies": {
|
|
38
|
-
"
|
|
40
|
+
"eslint-plugin-jsdoc": "63.3.3",
|
|
41
|
+
"eslint-plugin-perfectionist": "5.10.1",
|
|
42
|
+
"ultracite": "8.0.1",
|
|
43
|
+
"yaml": "^2.8.3",
|
|
44
|
+
"zod": "^4.5.4"
|
|
39
45
|
},
|
|
40
46
|
"devDependencies": {
|
|
41
|
-
"oxfmt": "0.
|
|
42
|
-
"oxlint": "1.
|
|
47
|
+
"oxfmt": "0.73.0",
|
|
48
|
+
"oxlint": "1.88.0"
|
|
43
49
|
},
|
|
44
50
|
"peerDependencies": {
|
|
45
|
-
"oxfmt": "
|
|
46
|
-
"oxlint": "^1.
|
|
47
|
-
"oxlint-tsgolint": ">=7.0.
|
|
51
|
+
"oxfmt": ">=0.71.0 <1",
|
|
52
|
+
"oxlint": "^1.86.0",
|
|
53
|
+
"oxlint-tsgolint": ">=7.0.2004"
|
|
48
54
|
}
|
|
49
55
|
}
|