@letstri/oxc-config 0.1.0 → 0.2.1

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
@@ -71,59 +71,65 @@ export default oxfmtConfig()
71
71
 
72
72
  ### Plugins
73
73
 
74
- Framework plugins are enabled automatically by detecting dependencies in the
75
- nearest `package.json` (`typescript`, `react`, `vue`, `next`, `vitest`, `jest`,
76
- `tailwind`). Toggle any of them manually — useful when the dependency lives in a
77
- nested workspace such as `apps/web/package.json`:
74
+ Plugins are enabled automatically by detecting dependencies in the nearest
75
+ `package.json`:
76
+
77
+ | Dependency | Plugins enabled |
78
+ | ------------ | ------------------- |
79
+ | `typescript` | `typescript` |
80
+ | `react` | `react`, `jsx-a11y` |
81
+ | `vue` | `vue` |
82
+ | `next` | `nextjs` |
83
+ | `vitest` | `vitest` |
84
+ | `jest` | `jest` |
85
+
86
+ Detection only reads the **nearest** `package.json`. If a dependency isn't found
87
+ there — e.g. it lives in a nested workspace like `apps/web/package.json`, or is
88
+ hoisted somewhere the scan doesn't see — its plugin won't be enabled. Add it
89
+ manually via `plugins`:
78
90
 
79
91
  ```ts
80
92
  export default oxlintConfig({
81
- vue: true, // force on
82
- jest: false, // force off
93
+ plugins: ['vue'],
83
94
  })
84
95
  ```
85
96
 
86
- ### Tailwind
87
-
88
- `tailwind: true` enables [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss)
89
- (auto-detected from a `tailwindcss` dependency). The plugin is an **optional peer
90
- dependency** — install it yourself:
91
-
92
- ```bash
93
- pnpm add -D eslint-plugin-better-tailwindcss
94
- ```
97
+ ### Overrides
95
98
 
96
- If Tailwind is enabled but the plugin is missing, the config logs a reminder and
97
- skips Tailwind linting instead of crashing. Point the plugin at your Tailwind
98
- entry CSS so it can resolve class names:
99
+ Anything you pass to `oxlintConfig` / `oxfmtConfig` is deep-merged over the base
100
+ config via [defu](https://github.com/unjs/defu):
99
101
 
100
102
  ```ts
101
103
  export default oxlintConfig({
102
- tailwind: true,
103
- override: {
104
- settings: {
105
- 'better-tailwindcss': {
106
- entryPoint: 'src/styles/globals.css',
107
- },
108
- },
104
+ rules: {
105
+ 'no-console': 'off',
109
106
  },
110
107
  })
111
108
  ```
112
109
 
113
- ### Overrides
110
+ ### Tailwind
114
111
 
115
- `override` is deep-merged over the base config via [defu](https://github.com/unjs/defu):
112
+ `tailwind({ entryPoint })` returns a config chunk for
113
+ [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
114
+ Spread it into `oxlintConfig`:
116
115
 
117
116
  ```ts
117
+ import { oxlintConfig, tailwind } from '@letstri/oxc-config'
118
+
118
119
  export default oxlintConfig({
119
- override: {
120
- rules: {
121
- 'no-console': 'off',
122
- },
123
- },
120
+ ...tailwind({ entryPoint: 'app/globals.css' }),
124
121
  })
125
122
  ```
126
123
 
124
+ `entryPoint` (required) is your Tailwind entry CSS, so the plugin can resolve
125
+ class names. The plugin is an **optional peer dependency** — install it yourself:
126
+
127
+ ```bash
128
+ pnpm add -D eslint-plugin-better-tailwindcss
129
+ ```
130
+
131
+ If the plugin is missing, `tailwind()` throws with an install hint.
132
+
127
133
  ## Editor setup
128
134
 
129
135
  Both editors use the official [oxc](https://oxc.rs) tooling — `oxlint` for
package/dist/index.d.mts CHANGED
@@ -1,61 +1,64 @@
1
- import { defineConfig } from "oxfmt";
2
- import { defineConfig as defineConfig$1 } from "oxlint";
3
- //#region src/index.d.ts
4
- type OxlintOptions = Parameters<typeof defineConfig$1>[0];
5
- type OxfmtOptions = Parameters<typeof defineConfig>[0];
6
- declare const pluginToggles: {
7
- typescript: {
8
- packages: string[];
9
- plugins: "typescript"[];
10
- };
11
- react: {
12
- packages: string[];
13
- plugins: ("react" | "jsx-a11y" | "react-perf")[];
14
- };
15
- vue: {
16
- packages: string[];
17
- plugins: "vue"[];
18
- };
19
- next: {
20
- packages: string[];
21
- plugins: "nextjs"[];
22
- };
23
- vitest: {
24
- packages: string[];
25
- plugins: "vitest"[];
26
- };
27
- jest: {
28
- packages: string[];
29
- plugins: "jest"[];
30
- };
31
- };
32
- type ToggleName = keyof typeof pluginToggles;
33
- interface OxlintConfigOptions extends Partial<Record<ToggleName, boolean>> {
34
- /**
35
- * Enable `eslint-plugin-better-tailwindcss`. Auto-detected from a `tailwindcss`
36
- * dependency; set it explicitly when Tailwind lives in a nested workspace.
37
- *
38
- * The plugin is an optional peer dependency — install it yourself
39
- * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, a warning
40
- * is logged and Tailwind linting is skipped. Point the plugin at your entry
41
- * CSS via `override.settings`.
42
- */
43
- tailwind?: boolean;
1
+ import { defineConfig } from "oxlint";
2
+ import { defineConfig as defineConfig$1 } from "oxfmt";
3
+ //#region src/oxlint.d.ts
4
+ type OxlintOptions$1 = Parameters<typeof defineConfig>[0];
5
+ /**
6
+ * Build an oxlint config.
7
+ *
8
+ * Plugins are auto-enabled by detecting their package (`typescript`, `react`,
9
+ * `vue`, `next`, `vitest`, `jest`) in the nearest `package.json`. To enable a
10
+ * plugin whose dependency lives elsewhere — e.g. a nested workspace like
11
+ * `apps/web/package.json` — add it through `overrides.plugins`.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // auto-detect from the current package.json
16
+ * export default oxlintConfig()
17
+ * ```
18
+ * @example
19
+ * ```ts
20
+ * // enable a plugin by hand, tweak a rule
21
+ * export default oxlintConfig({ plugins: ['vue'], rules: { 'no-console': 'off' } })
22
+ * ```
23
+ * @example
24
+ * ```ts
25
+ * // add Tailwind linting
26
+ * export default oxlintConfig({ ...tailwind({ entryPoint: 'app/globals.css' }) })
27
+ * ```
28
+ */
29
+ declare function oxlintConfig(overrides?: OxlintOptions$1): OxlintOptions$1;
30
+ //#endregion
31
+ //#region src/oxfmt.d.ts
32
+ type OxfmtOptions = Parameters<typeof defineConfig$1>[0];
33
+ declare function oxfmtConfig(overrides?: OxfmtOptions): OxfmtOptions;
34
+ //#endregion
35
+ //#region src/tailwind.d.ts
36
+ type OxlintOptions = Parameters<typeof defineConfig>[0];
37
+ interface TailwindOptions {
44
38
  /**
45
- * Deep-merged over the base config via defu.
39
+ * Path to the Tailwind entry CSS. Required — the plugin needs it to resolve
40
+ * class names.
46
41
  */
47
- override?: OxlintOptions;
42
+ entryPoint: string;
48
43
  /**
49
- * Directory whose `package.json` is scanned to auto-detect plugins.
44
+ * Directory scanned to check the plugin is installed.
50
45
  *
51
46
  * @default process.cwd()
52
47
  */
53
48
  cwd?: string;
54
49
  }
55
- interface OxfmtConfigOptions {
56
- override?: OxfmtOptions;
57
- }
58
- declare function oxlintConfig(options?: OxlintConfigOptions): OxlintOptions;
59
- declare function oxfmtConfig({ override }?: OxfmtConfigOptions): OxfmtOptions;
50
+ /**
51
+ * Tailwind linting via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
52
+ * Spread the result into `oxlintConfig`:
53
+ *
54
+ * ```ts
55
+ * export default oxlintConfig({ ...tailwind({ entryPoint: 'app/globals.css' }) })
56
+ * ```
57
+ *
58
+ * The plugin is an optional peer dependency — install it yourself
59
+ * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, an error
60
+ * is thrown.
61
+ */
62
+ declare function tailwind({ entryPoint, cwd }: TailwindOptions): OxlintOptions;
60
63
  //#endregion
61
- export { oxfmtConfig, oxlintConfig };
64
+ export { oxfmtConfig, oxlintConfig, tailwind };
package/dist/index.mjs CHANGED
@@ -1,11 +1,25 @@
1
- import { readFileSync } from "node:fs";
2
- import { resolve } from "node:path";
3
1
  import process from "node:process";
4
2
  import { defu } from "defu";
5
- import { defineConfig } from "oxfmt";
6
- import { defineConfig as defineConfig$1 } from "oxlint";
7
- //#region src/index.ts
8
- const baseOxlintConfig = defineConfig$1({
3
+ import { defineConfig } from "oxlint";
4
+ import { readFileSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import { defineConfig as defineConfig$1 } from "oxfmt";
7
+ //#region src/utils.ts
8
+ function getInstalledPackages(cwd) {
9
+ const names = /* @__PURE__ */ new Set();
10
+ try {
11
+ const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf-8"));
12
+ for (const field of [
13
+ pkg.dependencies,
14
+ pkg.devDependencies,
15
+ pkg.peerDependencies
16
+ ]) for (const name of Object.keys(field ?? {})) names.add(name);
17
+ } catch {}
18
+ return names;
19
+ }
20
+ //#endregion
21
+ //#region src/oxlint.ts
22
+ const baseOxlintConfig = defineConfig({
9
23
  categories: {
10
24
  correctness: "error",
11
25
  suspicious: "warn",
@@ -474,13 +488,6 @@ const baseOxlintConfig = defineConfig$1({
474
488
  }
475
489
  ]
476
490
  });
477
- const baseOxfmtConfig = defineConfig({
478
- singleQuote: true,
479
- semi: false,
480
- arrowParens: "avoid",
481
- sortImports: true,
482
- quoteProps: "consistent"
483
- });
484
491
  const basePlugins = [
485
492
  "import",
486
493
  "unicorn",
@@ -489,18 +496,14 @@ const basePlugins = [
489
496
  "promise",
490
497
  "oxc"
491
498
  ];
492
- const pluginToggles = {
499
+ const pluginDetectors = {
493
500
  typescript: {
494
501
  packages: ["typescript"],
495
502
  plugins: ["typescript"]
496
503
  },
497
504
  react: {
498
505
  packages: ["react"],
499
- plugins: [
500
- "react",
501
- "jsx-a11y",
502
- "react-perf"
503
- ]
506
+ plugins: ["react", "jsx-a11y"]
504
507
  },
505
508
  vue: {
506
509
  packages: ["vue"],
@@ -519,35 +522,19 @@ const pluginToggles = {
519
522
  plugins: ["jest"]
520
523
  }
521
524
  };
522
- const tailwindConfig = defineConfig$1({ jsPlugins: ["eslint-plugin-better-tailwindcss"] });
523
- function getInstalledPackages(cwd) {
524
- const names = /* @__PURE__ */ new Set();
525
- try {
526
- const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf-8"));
527
- for (const field of [
528
- pkg.dependencies,
529
- pkg.devDependencies,
530
- pkg.peerDependencies
531
- ]) for (const name of Object.keys(field ?? {})) names.add(name);
532
- } catch {}
533
- return names;
534
- }
535
- function resolvePlugins(options, installed) {
525
+ function resolvePlugins(cwd) {
526
+ const installed = getInstalledPackages(cwd);
536
527
  const plugins = [...basePlugins];
537
- for (const name of Object.keys(pluginToggles)) {
538
- const toggle = pluginToggles[name];
539
- if (options[name] ?? toggle.packages.some((pkg) => installed.has(pkg))) plugins.push(...toggle.plugins);
540
- }
528
+ for (const { packages, plugins: enabled } of Object.values(pluginDetectors)) if (packages.some((pkg) => installed.has(pkg))) plugins.push(...enabled);
541
529
  return [...new Set(plugins)];
542
530
  }
543
531
  /**
544
532
  * Build an oxlint config.
545
533
  *
546
- * Framework plugins (`typescript`, `react`, `vue`, `next`, `vitest`, `jest`,
547
- * `tailwind`) are auto-enabled by detecting their package in the nearest
548
- * `package.json`. If a dependency is not found there — e.g. it lives in a nested
549
- * workspace like `apps/web/package.json` — its plugin stays off, so enable it
550
- * manually:
534
+ * Plugins are auto-enabled by detecting their package (`typescript`, `react`,
535
+ * `vue`, `next`, `vitest`, `jest`) in the nearest `package.json`. To enable a
536
+ * plugin whose dependency lives elsewhere — e.g. a nested workspace like
537
+ * `apps/web/package.json` — add it through `overrides.plugins`.
551
538
  *
552
539
  * @example
553
540
  * ```ts
@@ -556,33 +543,61 @@ function resolvePlugins(options, installed) {
556
543
  * ```
557
544
  * @example
558
545
  * ```ts
559
- * // dep lives in a nested workspace turn the plugin on by hand
560
- * export default oxlintConfig({ vue: true })
546
+ * // enable a plugin by hand, tweak a rule
547
+ * export default oxlintConfig({ plugins: ['vue'], rules: { 'no-console': 'off' } })
561
548
  * ```
562
549
  * @example
563
550
  * ```ts
564
- * // Tailwind, pointed at the entry CSS so classes can be resolved
565
- * export default oxlintConfig({
566
- * tailwind: true,
567
- * override: {
568
- * settings: { 'better-tailwindcss': { entryPoint: 'src/styles/globals.css' } },
569
- * },
570
- * })
551
+ * // add Tailwind linting
552
+ * export default oxlintConfig({ ...tailwind({ entryPoint: 'app/globals.css' }) })
571
553
  * ```
572
554
  */
573
- const TAILWIND_PLUGIN = "eslint-plugin-better-tailwindcss";
574
- function oxlintConfig(options = {}) {
575
- const { override = {} } = options;
576
- const installed = getInstalledPackages(options.cwd ?? process.cwd());
577
- let tailwind = options.tailwind ?? installed.has("tailwindcss");
578
- if (tailwind && !installed.has(TAILWIND_PLUGIN)) {
579
- console.warn(`[@letstri/oxc-config] Tailwind linting needs "${TAILWIND_PLUGIN}". Install it: pnpm add -D ${TAILWIND_PLUGIN}`);
580
- tailwind = false;
581
- }
582
- return defu(override, { plugins: resolvePlugins(options, installed) }, tailwind ? tailwindConfig : {}, baseOxlintConfig);
555
+ function oxlintConfig(overrides = {}) {
556
+ return defu(overrides, { plugins: resolvePlugins(process.cwd()) }, baseOxlintConfig);
583
557
  }
584
- function oxfmtConfig({ override = {} } = {}) {
585
- return defu(override, baseOxfmtConfig);
558
+ //#endregion
559
+ //#region src/oxfmt.ts
560
+ const baseOxfmtConfig = defineConfig$1({
561
+ singleQuote: true,
562
+ semi: false,
563
+ arrowParens: "avoid",
564
+ sortImports: true,
565
+ quoteProps: "consistent"
566
+ });
567
+ function oxfmtConfig(overrides = {}) {
568
+ return defu(overrides, baseOxfmtConfig);
569
+ }
570
+ //#endregion
571
+ //#region src/tailwind.ts
572
+ const TAILWIND_PLUGIN = "eslint-plugin-better-tailwindcss";
573
+ /**
574
+ * Tailwind linting via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
575
+ * Spread the result into `oxlintConfig`:
576
+ *
577
+ * ```ts
578
+ * export default oxlintConfig({ ...tailwind({ entryPoint: 'app/globals.css' }) })
579
+ * ```
580
+ *
581
+ * The plugin is an optional peer dependency — install it yourself
582
+ * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, an error
583
+ * is thrown.
584
+ */
585
+ function tailwind({ entryPoint, cwd = process.cwd() }) {
586
+ if (!getInstalledPackages(cwd).has(TAILWIND_PLUGIN)) throw new Error(`[@letstri/oxc-config] Tailwind linting needs "${TAILWIND_PLUGIN}". Install it: pnpm add -D ${TAILWIND_PLUGIN}`);
587
+ return defineConfig({
588
+ jsPlugins: [TAILWIND_PLUGIN],
589
+ settings: { "better-tailwindcss": { entryPoint } },
590
+ rules: {
591
+ "better-tailwindcss/enforce-consistent-class-order": "error",
592
+ "better-tailwindcss/enforce-consistent-line-wrapping": "off",
593
+ "better-tailwindcss/no-unnecessary-whitespace": "off",
594
+ "better-tailwindcss/enforce-canonical-classes": "error",
595
+ "better-tailwindcss/no-conflicting-classes": "error",
596
+ "better-tailwindcss/no-deprecated-classes": "error",
597
+ "better-tailwindcss/no-duplicate-classes": "error",
598
+ "better-tailwindcss/no-unknown-classes": "error"
599
+ }
600
+ });
586
601
  }
587
602
  //#endregion
588
- export { oxfmtConfig, oxlintConfig };
603
+ export { oxfmtConfig, oxlintConfig, tailwind };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letstri/oxc-config",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Opinionated Oxlint and Oxfmt configs",
5
5
  "homepage": "https://github.com/letstri/oxc-config#readme",
6
6
  "bugs": {
@@ -27,19 +27,6 @@
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "scripts": {
31
- "build": "tsdown",
32
- "dev": "tsdown --watch",
33
- "check-types": "tsc --noEmit",
34
- "lint": "oxlint -c oxlint.config.ts",
35
- "lint:fix": "oxlint -c oxlint.config.ts --fix",
36
- "format": "oxfmt -c oxfmt.config.ts",
37
- "format:check": "oxfmt -c oxfmt.config.ts --check",
38
- "check": "run-p lint check-types format:check",
39
- "update": "taze -r -I major",
40
- "prepare": "husky",
41
- "prepublishOnly": "pnpm run build"
42
- },
43
30
  "dependencies": {
44
31
  "defu": "^6.1.7"
45
32
  },
@@ -66,5 +53,15 @@
66
53
  "engines": {
67
54
  "node": ">=22"
68
55
  },
69
- "packageManager": "pnpm@11.11.0"
70
- }
56
+ "scripts": {
57
+ "build": "tsdown",
58
+ "dev": "tsdown --watch",
59
+ "check-types": "tsc --noEmit",
60
+ "lint": "oxlint -c oxlint.config.ts",
61
+ "lint:fix": "oxlint -c oxlint.config.ts --fix",
62
+ "format": "oxfmt -c oxfmt.config.ts",
63
+ "format:check": "oxfmt -c oxfmt.config.ts --check",
64
+ "check": "run-p lint check-types format:check",
65
+ "update": "taze -r -I major"
66
+ }
67
+ }