@letstri/oxc-config 0.2.0 → 0.3.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
@@ -4,8 +4,8 @@ Opinionated, shared [oxlint](https://oxc.rs/docs/guide/usage/linter.html) and [o
4
4
 
5
5
  > [!NOTE]
6
6
  > This is an **opinionated** config — it ships a curated set of rules and
7
- > formatting defaults meant to work out of the box. Override anything you
8
- > disagree with via the [`override`](#overrides) option.
7
+ > formatting defaults meant to work out of the box. [Override](#overrides)
8
+ > anything you disagree with.
9
9
 
10
10
  ## Install
11
11
 
@@ -44,7 +44,7 @@ Set up @letstri/oxc-config (oxlint + oxfmt) in this project:
44
44
  - "format:check": "oxfmt --check"
45
45
  6. Framework plugins auto-enable from the nearest package.json. If a framework
46
46
  dep (react/vue/next/vitest/jest/typescript) lives in a nested workspace,
47
- enable it manually, e.g. `oxlintConfig({ vue: true })`.
47
+ enable it manually, e.g. `oxlintConfig({ plugins: ['vue'] })`.
48
48
  7. Add the VS Code and Zed editor settings from the @letstri/oxc-config README.
49
49
  8. Run `pnpm lint` and `pnpm format` and fix anything reported.
50
50
  ````
@@ -96,39 +96,40 @@ export default oxlintConfig({
96
96
 
97
97
  ### Overrides
98
98
 
99
- Anything you pass to `oxlintConfig` / `oxfmtConfig` is deep-merged over the base
100
- config via [defu](https://github.com/unjs/defu):
99
+ `oxlintConfig` / `oxfmtConfig` accept **any number of config objects**, all
100
+ deep-merged over the base config via [defu](https://github.com/unjs/defu) (arrays
101
+ are concatenated, so plugins from different pieces combine instead of
102
+ overwriting):
101
103
 
102
104
  ```ts
103
- export default oxlintConfig({
104
- rules: {
105
- 'no-console': 'off',
106
- },
107
- })
105
+ export default oxlintConfig({ rules: { 'no-console': 'off' } }, { plugins: ['vue'] })
108
106
  ```
109
107
 
110
108
  ### Tailwind
111
109
 
112
- `tailwind(options?)` returns a config chunk for
110
+ `tailwind({ entryPoint })` returns a config chunk for
113
111
  [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
114
- Spread it into `oxlintConfig`:
112
+ Pass it as an argument to `oxlintConfig`:
115
113
 
116
114
  ```ts
117
115
  import { oxlintConfig, tailwind } from '@letstri/oxc-config'
118
116
 
119
- export default oxlintConfig({
120
- ...tailwind({ entryPoint: 'app/globals.css' }),
121
- })
117
+ export default oxlintConfig(
118
+ { plugins: ['react', 'jsx-a11y'] },
119
+ tailwind({ entryPoint: 'app/globals.css' }),
120
+ )
122
121
  ```
123
122
 
124
- `entryPoint` is your Tailwind entry CSS, so the plugin can resolve class names. The plugin is an **optional peer dependency** — install it yourself:
123
+ Because arguments are merged (not spread), Tailwind's plugins combine with the
124
+ ones above rather than overwriting them. `entryPoint` (required) is your Tailwind
125
+ entry CSS, so the plugin can resolve class names. The plugin is an **optional
126
+ peer dependency** — install it yourself:
125
127
 
126
128
  ```bash
127
129
  pnpm add -D eslint-plugin-better-tailwindcss
128
130
  ```
129
131
 
130
- If the plugin is missing, `tailwind()` logs a reminder and returns an empty
131
- config instead of crashing oxlint.
132
+ If the plugin is missing, `tailwind()` throws with an install hint.
132
133
 
133
134
  ## Editor setup
134
135
 
package/dist/index.d.mts CHANGED
@@ -1,17 +1,17 @@
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];
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];
6
5
  /**
7
6
  * Build an oxlint config.
8
7
  *
9
8
  * Plugins are auto-enabled by detecting their package (`typescript`, `react`,
10
9
  * `vue`, `next`, `vitest`, `jest`) in the nearest `package.json`. To enable a
11
10
  * plugin whose dependency lives elsewhere — e.g. a nested workspace like
12
- * `apps/web/package.json` — add it through `overrides.plugins`.
11
+ * `apps/web/package.json` — add it through `plugins`.
13
12
  *
14
- * `overrides` is deep-merged over the base config via defu.
13
+ * Pass any number of config objects; they are deep-merged (arrays concatenated),
14
+ * so pieces like {@link tailwind} compose without clobbering each other.
15
15
  *
16
16
  * @example
17
17
  * ```ts
@@ -25,11 +25,25 @@ type OxfmtOptions = Parameters<typeof defineConfig>[0];
25
25
  * ```
26
26
  * @example
27
27
  * ```ts
28
- * // add Tailwind linting
29
- * export default oxlintConfig({ ...tailwind('app/globals.css') })
28
+ * // compose Tailwind — its plugins merge with the ones above, not overwrite
29
+ * export default oxlintConfig(
30
+ * { plugins: ['react', 'jsx-a11y'] },
31
+ * tailwind({ entryPoint: 'app/globals.css' }),
32
+ * )
30
33
  * ```
31
34
  */
32
- declare function oxlintConfig(overrides?: OxlintOptions): OxlintOptions;
35
+ declare function oxlintConfig(...overrides: OxlintOptions$1[]): OxlintOptions$1;
36
+ //#endregion
37
+ //#region src/oxfmt.d.ts
38
+ type OxfmtOptions = Parameters<typeof defineConfig$1>[0];
39
+ /**
40
+ * Build an oxfmt config. Pass any number of config objects; they are deep-merged
41
+ * over the base config via defu.
42
+ */
43
+ declare function oxfmtConfig(...overrides: OxfmtOptions[]): OxfmtOptions;
44
+ //#endregion
45
+ //#region src/tailwind.d.ts
46
+ type OxlintOptions = Parameters<typeof defineConfig>[0];
33
47
  interface TailwindOptions {
34
48
  /**
35
49
  * Path to the Tailwind entry CSS. Required — the plugin needs it to resolve
@@ -45,17 +59,16 @@ interface TailwindOptions {
45
59
  }
46
60
  /**
47
61
  * Tailwind linting via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
48
- * Spread the result into {@link oxlintConfig}:
62
+ * Pass the result as an argument to `oxlintConfig`:
49
63
  *
50
64
  * ```ts
51
- * export default oxlintConfig({ ...tailwind({ entryPoint: 'app/globals.css' }) })
65
+ * export default oxlintConfig(tailwind({ entryPoint: 'app/globals.css' }))
52
66
  * ```
53
67
  *
54
68
  * The plugin is an optional peer dependency — install it yourself
55
- * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, a warning
56
- * is logged and an empty config is returned so oxlint does not crash.
69
+ * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, an error
70
+ * is thrown.
57
71
  */
58
72
  declare function tailwind({ entryPoint, cwd }: TailwindOptions): OxlintOptions;
59
- declare function oxfmtConfig(overrides?: OxfmtOptions): OxfmtOptions;
60
73
  //#endregion
61
74
  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,11 +496,6 @@ const basePlugins = [
489
496
  "promise",
490
497
  "oxc"
491
498
  ];
492
- const TAILWIND_PLUGIN = "eslint-plugin-better-tailwindcss";
493
- /**
494
- * Map of a dependency to the oxlint plugins it enables. A plugin is registered
495
- * when any of its packages is found in the nearest `package.json`.
496
- */
497
499
  const pluginDetectors = {
498
500
  typescript: {
499
501
  packages: ["typescript"],
@@ -520,18 +522,6 @@ const pluginDetectors = {
520
522
  plugins: ["jest"]
521
523
  }
522
524
  };
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
525
  function resolvePlugins(cwd) {
536
526
  const installed = getInstalledPackages(cwd);
537
527
  const plugins = [...basePlugins];
@@ -544,9 +534,10 @@ function resolvePlugins(cwd) {
544
534
  * Plugins are auto-enabled by detecting their package (`typescript`, `react`,
545
535
  * `vue`, `next`, `vitest`, `jest`) in the nearest `package.json`. To enable a
546
536
  * plugin whose dependency lives elsewhere — e.g. a nested workspace like
547
- * `apps/web/package.json` — add it through `overrides.plugins`.
537
+ * `apps/web/package.json` — add it through `plugins`.
548
538
  *
549
- * `overrides` is deep-merged over the base config via defu.
539
+ * Pass any number of config objects; they are deep-merged (arrays concatenated),
540
+ * so pieces like {@link tailwind} compose without clobbering each other.
550
541
  *
551
542
  * @example
552
543
  * ```ts
@@ -560,31 +551,51 @@ function resolvePlugins(cwd) {
560
551
  * ```
561
552
  * @example
562
553
  * ```ts
563
- * // add Tailwind linting
564
- * export default oxlintConfig({ ...tailwind('app/globals.css') })
554
+ * // compose Tailwind — its plugins merge with the ones above, not overwrite
555
+ * export default oxlintConfig(
556
+ * { plugins: ['react', 'jsx-a11y'] },
557
+ * tailwind({ entryPoint: 'app/globals.css' }),
558
+ * )
565
559
  * ```
566
560
  */
567
- function oxlintConfig(overrides = {}) {
568
- return defu(overrides, { plugins: resolvePlugins(process.cwd()) }, baseOxlintConfig);
561
+ function oxlintConfig(...overrides) {
562
+ return defu({}, ...overrides, { plugins: resolvePlugins(process.cwd()) }, baseOxlintConfig);
563
+ }
564
+ //#endregion
565
+ //#region src/oxfmt.ts
566
+ const baseOxfmtConfig = defineConfig$1({
567
+ printWidth: 100,
568
+ singleQuote: true,
569
+ semi: false,
570
+ arrowParens: "avoid",
571
+ sortImports: true,
572
+ quoteProps: "consistent"
573
+ });
574
+ /**
575
+ * Build an oxfmt config. Pass any number of config objects; they are deep-merged
576
+ * over the base config via defu.
577
+ */
578
+ function oxfmtConfig(...overrides) {
579
+ return defu({}, ...overrides, baseOxfmtConfig);
569
580
  }
581
+ //#endregion
582
+ //#region src/tailwind.ts
583
+ const TAILWIND_PLUGIN = "eslint-plugin-better-tailwindcss";
570
584
  /**
571
585
  * Tailwind linting via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
572
- * Spread the result into {@link oxlintConfig}:
586
+ * Pass the result as an argument to `oxlintConfig`:
573
587
  *
574
588
  * ```ts
575
- * export default oxlintConfig({ ...tailwind({ entryPoint: 'app/globals.css' }) })
589
+ * export default oxlintConfig(tailwind({ entryPoint: 'app/globals.css' }))
576
590
  * ```
577
591
  *
578
592
  * The plugin is an optional peer dependency — install it yourself
579
- * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, a warning
580
- * is logged and an empty config is returned so oxlint does not crash.
593
+ * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, an error
594
+ * is thrown.
581
595
  */
582
596
  function tailwind({ entryPoint, cwd = process.cwd() }) {
583
- if (!getInstalledPackages(cwd).has(TAILWIND_PLUGIN)) {
584
- console.warn(`[@letstri/oxc-config] Tailwind linting needs "${TAILWIND_PLUGIN}". Install it: pnpm add -D ${TAILWIND_PLUGIN}`);
585
- return {};
586
- }
587
- return defineConfig$1({
597
+ if (!getInstalledPackages(cwd).has(TAILWIND_PLUGIN)) throw new Error(`[@letstri/oxc-config] Tailwind linting needs "${TAILWIND_PLUGIN}". Install it: pnpm add -D ${TAILWIND_PLUGIN}`);
598
+ return defineConfig({
588
599
  jsPlugins: [TAILWIND_PLUGIN],
589
600
  settings: { "better-tailwindcss": { entryPoint } },
590
601
  rules: {
@@ -599,8 +610,5 @@ function tailwind({ entryPoint, cwd = process.cwd() }) {
599
610
  }
600
611
  });
601
612
  }
602
- function oxfmtConfig(overrides = {}) {
603
- return defu(overrides, baseOxfmtConfig);
604
- }
605
613
  //#endregion
606
614
  export { oxfmtConfig, oxlintConfig, tailwind };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letstri/oxc-config",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Opinionated Oxlint and Oxfmt configs",
5
5
  "homepage": "https://github.com/letstri/oxc-config#readme",
6
6
  "bugs": {