@letstri/oxc-config 0.5.0 → 0.7.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 CHANGED
@@ -29,46 +29,12 @@ npx oxc-config init --vscode --zed # just the editor settings
29
29
 
30
30
  With flags, only the chosen targets run; with none in a non-interactive shell,
31
31
  all four run. Config files are skipped if they already exist (`--force` to
32
- overwrite); editor settings are deep-merged into any existing files. See
33
- [Editor setup](#editor-setup).
34
-
35
- ## AI setup prompt
36
-
37
- Paste this into Claude Code, Cursor, or any coding agent to wire everything up:
38
-
39
- <details>
40
- <summary>Show prompt</summary>
41
-
42
- ````text
43
- Set up @letstri/oxc-config (oxlint + oxfmt) in this project:
44
-
45
- 1. Install dev deps: `@letstri/oxc-config oxlint oxfmt`.
46
- 2. Remove ESLint and Prettier: their configs, deps, and scripts.
47
- 3. Create `oxlint.config.ts`:
48
- ```ts
49
- import { oxlintConfig } from '@letstri/oxc-config'
50
-
51
- export default oxlintConfig()
52
- ```
53
- 4. Create `oxfmt.config.ts`:
54
- ```ts
55
- import { oxfmtConfig } from '@letstri/oxc-config'
56
-
57
- export default oxfmtConfig()
58
- ```
59
- 5. Add package.json scripts:
60
- - "lint": "oxlint"
61
- - "lint:fix": "oxlint --fix"
62
- - "format": "oxfmt"
63
- - "format:check": "oxfmt --check"
64
- 6. Framework plugins auto-enable from the nearest package.json. If a framework
65
- dep (react/vue/next/vitest/jest/typescript) lives in a nested workspace,
66
- enable it manually, e.g. `oxlintConfig({ plugins: ['vue'] })`.
67
- 7. Add the VS Code and Zed editor settings from the @letstri/oxc-config README.
68
- 8. Run `pnpm lint` and `pnpm format` and fix anything reported.
69
- ````
70
-
71
- </details>
32
+ overwrite); editor settings (VS Code + Zed) are deep-merged into any existing
33
+ files, so your other settings are kept.
34
+
35
+ VS Code also needs the [`oxc.oxc-vscode`](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode)
36
+ extension (the CLI adds it to `.vscode/extensions.json`). Zed has the oxc
37
+ language servers built in.
72
38
 
73
39
  ## Usage
74
40
 
@@ -126,16 +92,16 @@ export default oxlintConfig({ rules: { 'no-console': 'off' } }, { plugins: ['vue
126
92
 
127
93
  ### Tailwind
128
94
 
129
- `tailwind({ entryPoint })` returns a config chunk for
130
- [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
95
+ `tailwindPlugin({ entryPoint })` returns a config chunk for
96
+ [`oxlint-tailwindcss`](https://github.com/sergioazoc/oxlint-tailwindcss) (Tailwind v4).
131
97
  Pass it as an argument to `oxlintConfig`:
132
98
 
133
99
  ```ts
134
- import { oxlintConfig, tailwind } from '@letstri/oxc-config'
100
+ import { oxlintConfig, tailwindPlugin } from '@letstri/oxc-config'
135
101
 
136
102
  export default oxlintConfig(
137
103
  { plugins: ['react', 'jsx-a11y'] },
138
- tailwind({ entryPoint: 'app/globals.css' }),
104
+ tailwindPlugin({ entryPoint: 'app/globals.css' }),
139
105
  )
140
106
  ```
141
107
 
@@ -145,47 +111,29 @@ ones above rather than overwriting them.
145
111
  Options:
146
112
 
147
113
  - `entryPoint` (required) — your Tailwind entry CSS, so the plugin can resolve
148
- class names.
114
+ class names. In a monorepo, pass an array of glob → CSS mappings (last match
115
+ wins, so end with a `'**'` catch-all):
116
+
117
+ ```ts
118
+ tailwindPlugin({
119
+ entryPoint: [
120
+ { files: 'packages/ui/**', use: 'packages/ui/src/styles.css' },
121
+ { files: '**', use: 'src/global.css' },
122
+ ],
123
+ })
124
+ ```
125
+
149
126
  - `ignoreClasses` — class names to exempt from `no-unknown-classes` (e.g. classes
150
127
  a component library generates that the plugin can't resolve):
151
128
 
152
129
  ```ts
153
- tailwind({ entryPoint: 'app/globals.css', ignoreClasses: ['toaster'] })
130
+ tailwindPlugin({ entryPoint: 'app/globals.css', ignoreClasses: ['toaster'] })
154
131
  ```
155
132
 
156
133
  The plugin is an **optional peer dependency** — install it yourself:
157
134
 
158
135
  ```bash
159
- pnpm add -D eslint-plugin-better-tailwindcss
160
- ```
161
-
162
- If the plugin is missing, `tailwind()` throws with an install hint.
163
-
164
- ## Editor setup
165
-
166
- Both editors use the official [oxc](https://oxc.rs) tooling — `oxlint` for
167
- linting and `oxfmt` for formatting — replacing ESLint and Prettier.
168
-
169
- `oxc-config init` writes (or updates) the editor configs for you, deep-merging
170
- into any existing files so your other settings are kept:
171
-
172
- ```bash
173
- # both editors
174
- pnpm exec oxc-config init --vscode --zed
136
+ pnpm add -D oxlint-tailwindcss
175
137
  ```
176
138
 
177
- It's idempotent safe to re-run to pull the latest recommended settings.
178
-
179
- ### VS Code
180
-
181
- Install the [`oxc.oxc-vscode`](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode)
182
- extension (`init --vscode` also adds it to `.vscode/extensions.json`). It writes
183
- `.vscode/settings.json` — oxlint as linter, oxfmt as the default formatter with
184
- format-on-save, and Prettier's import organization turned off.
185
-
186
- ### Zed
187
-
188
- Zed ships with the oxc language servers built in, so no extension is needed.
189
- `init --zed` writes `.zed/settings.json` — oxfmt as the formatter (with
190
- `source.fixAll.oxc` on save for JS/TS) and Prettier disabled, across the file
191
- types oxfmt supports.
139
+ If the plugin is missing, `tailwindPlugin()` throws with an install hint.
package/dist/index.d.mts CHANGED
@@ -1,7 +1,21 @@
1
1
  import { defineConfig } from "oxlint";
2
2
  import { defineConfig as defineConfig$1 } from "oxfmt";
3
3
  //#region src/oxlint.d.ts
4
- type OxlintOptions$1 = Parameters<typeof defineConfig>[0];
4
+ type OxlintOptions = Parameters<typeof defineConfig>[0];
5
+ type OxlintPlugin = NonNullable<NonNullable<OxlintOptions>['plugins']>[number];
6
+ declare const basePlugins: readonly ["import", "unicorn", "jsdoc", "node", "promise", "oxc"];
7
+ /**
8
+ * Plugins always enabled by the base config — excluded from the `plugins` you
9
+ * can add, since listing them again is redundant.
10
+ */
11
+ type BasePlugin = (typeof basePlugins)[number];
12
+ /**
13
+ * Oxlint config accepted by {@link oxlintConfig}. Same as oxlint's own config,
14
+ * but `plugins` can't include the always-on base plugins.
15
+ */
16
+ type OxlintConfig = Omit<NonNullable<OxlintOptions>, 'plugins'> & {
17
+ plugins?: Exclude<OxlintPlugin, BasePlugin>[];
18
+ };
5
19
  /**
6
20
  * Build an oxlint config.
7
21
  *
@@ -10,8 +24,9 @@ type OxlintOptions$1 = Parameters<typeof defineConfig>[0];
10
24
  * plugin whose dependency lives elsewhere — e.g. a nested workspace like
11
25
  * `apps/web/package.json` — add it through `plugins`.
12
26
  *
13
- * Pass any number of config objects; they are deep-merged (arrays concatenated),
14
- * so pieces like {@link tailwind} compose without clobbering each other.
27
+ * Pass any number of config objects; they are deep-merged (arrays concatenated,
28
+ * with `plugins` de-duplicated), so pieces like {@link tailwindPlugin} compose
29
+ * without clobbering each other.
15
30
  *
16
31
  * @example
17
32
  * ```ts
@@ -28,11 +43,11 @@ type OxlintOptions$1 = Parameters<typeof defineConfig>[0];
28
43
  * // compose Tailwind — its plugins merge with the ones above, not overwrite
29
44
  * export default oxlintConfig(
30
45
  * { plugins: ['react', 'jsx-a11y'] },
31
- * tailwind({ entryPoint: 'app/globals.css' }),
46
+ * tailwindPlugin({ entryPoint: 'app/globals.css' }),
32
47
  * )
33
48
  * ```
34
49
  */
35
- declare function oxlintConfig(...overrides: OxlintOptions$1[]): OxlintOptions$1;
50
+ declare function oxlintConfig(...overrides: OxlintConfig[]): OxlintOptions;
36
51
  //#endregion
37
52
  //#region src/oxfmt.d.ts
38
53
  type OxfmtOptions = Parameters<typeof defineConfig$1>[0];
@@ -43,13 +58,25 @@ type OxfmtOptions = Parameters<typeof defineConfig$1>[0];
43
58
  declare function oxfmtConfig(...overrides: OxfmtOptions[]): OxfmtOptions;
44
59
  //#endregion
45
60
  //#region src/tailwind.d.ts
46
- type OxlintOptions = Parameters<typeof defineConfig>[0];
47
61
  interface TailwindOptions {
48
62
  /**
49
- * Path to the Tailwind entry CSS. Required the plugin needs it to resolve
50
- * class names.
63
+ * Path to the Tailwind entry CSS, so the plugin can resolve class names.
64
+ * Required. In a monorepo, pass an array of glob → CSS mappings; the last
65
+ * matching entry wins, so end with a `'**'` catch-all.
66
+ *
67
+ * @example 'src/global.css'
68
+ * @example
69
+ * ```ts
70
+ * [
71
+ * { files: 'packages/ui/**', use: 'packages/ui/src/styles.css' },
72
+ * { files: '**', use: 'src/global.css' },
73
+ * ]
74
+ * ```
51
75
  */
52
- entryPoint: string;
76
+ entryPoint: string | {
77
+ files: string;
78
+ use: string;
79
+ }[];
53
80
  /**
54
81
  * Class names to exempt from `no-unknown-classes` — e.g. classes generated by
55
82
  * component libraries that the plugin can't resolve from the entry CSS.
@@ -63,20 +90,19 @@ interface TailwindOptions {
63
90
  cwd?: string;
64
91
  }
65
92
  /**
66
- * Tailwind linting via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
93
+ * Tailwind v4 linting via [`oxlint-tailwindcss`](https://github.com/sergioazoc/oxlint-tailwindcss).
67
94
  * Pass the result as an argument to `oxlintConfig`:
68
95
  *
69
96
  * ```ts
70
- * export default oxlintConfig(tailwind({
97
+ * export default oxlintConfig(tailwindPlugin({
71
98
  * entryPoint: 'app/globals.css',
72
99
  * ignoreClasses: ['toaster'],
73
100
  * }))
74
101
  * ```
75
102
  *
76
103
  * The plugin is an optional peer dependency — install it yourself
77
- * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, an error
78
- * is thrown.
104
+ * (`pnpm add -D oxlint-tailwindcss`). If it is missing, an error is thrown.
79
105
  */
80
- declare function tailwind({ entryPoint, ignoreClasses, cwd }: TailwindOptions): OxlintOptions;
106
+ declare function tailwindPlugin({ entryPoint, ignoreClasses, cwd }: TailwindOptions): OxlintConfig;
81
107
  //#endregion
82
- export { oxfmtConfig, oxlintConfig, tailwind };
108
+ export { oxfmtConfig, oxlintConfig, tailwindPlugin };
package/dist/index.mjs CHANGED
@@ -536,8 +536,9 @@ function resolvePlugins(cwd) {
536
536
  * plugin whose dependency lives elsewhere — e.g. a nested workspace like
537
537
  * `apps/web/package.json` — add it through `plugins`.
538
538
  *
539
- * Pass any number of config objects; they are deep-merged (arrays concatenated),
540
- * so pieces like {@link tailwind} compose without clobbering each other.
539
+ * Pass any number of config objects; they are deep-merged (arrays concatenated,
540
+ * with `plugins` de-duplicated), so pieces like {@link tailwindPlugin} compose
541
+ * without clobbering each other.
541
542
  *
542
543
  * @example
543
544
  * ```ts
@@ -554,12 +555,14 @@ function resolvePlugins(cwd) {
554
555
  * // compose Tailwind — its plugins merge with the ones above, not overwrite
555
556
  * export default oxlintConfig(
556
557
  * { plugins: ['react', 'jsx-a11y'] },
557
- * tailwind({ entryPoint: 'app/globals.css' }),
558
+ * tailwindPlugin({ entryPoint: 'app/globals.css' }),
558
559
  * )
559
560
  * ```
560
561
  */
561
562
  function oxlintConfig(...overrides) {
562
- return defu({}, ...overrides, { plugins: resolvePlugins(process.cwd()) }, baseOxlintConfig);
563
+ const merged = defu({}, ...overrides, { plugins: resolvePlugins(process.cwd()) }, baseOxlintConfig);
564
+ if (merged?.plugins) merged.plugins = [...new Set(merged.plugins)];
565
+ return merged;
563
566
  }
564
567
  //#endregion
565
568
  //#region src/oxfmt.ts
@@ -580,38 +583,40 @@ function oxfmtConfig(...overrides) {
580
583
  }
581
584
  //#endregion
582
585
  //#region src/tailwind.ts
583
- const TAILWIND_PLUGIN = "eslint-plugin-better-tailwindcss";
586
+ const TAILWIND_PLUGIN = "oxlint-tailwindcss";
584
587
  /**
585
- * Tailwind linting via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss).
588
+ * Tailwind v4 linting via [`oxlint-tailwindcss`](https://github.com/sergioazoc/oxlint-tailwindcss).
586
589
  * Pass the result as an argument to `oxlintConfig`:
587
590
  *
588
591
  * ```ts
589
- * export default oxlintConfig(tailwind({
592
+ * export default oxlintConfig(tailwindPlugin({
590
593
  * entryPoint: 'app/globals.css',
591
594
  * ignoreClasses: ['toaster'],
592
595
  * }))
593
596
  * ```
594
597
  *
595
598
  * The plugin is an optional peer dependency — install it yourself
596
- * (`pnpm add -D eslint-plugin-better-tailwindcss`). If it is missing, an error
597
- * is thrown.
599
+ * (`pnpm add -D oxlint-tailwindcss`). If it is missing, an error is thrown.
598
600
  */
599
- function tailwind({ entryPoint, ignoreClasses = [], cwd = process.cwd() }) {
601
+ function tailwindPlugin({ entryPoint, ignoreClasses = [], cwd = process.cwd() }) {
600
602
  if (!getInstalledPackages(cwd).has(TAILWIND_PLUGIN)) throw new Error(`[@letstri/oxc-config] Tailwind linting needs "${TAILWIND_PLUGIN}". Install it: pnpm add -D ${TAILWIND_PLUGIN}`);
601
603
  return defineConfig({
602
604
  jsPlugins: [TAILWIND_PLUGIN],
603
- settings: { "better-tailwindcss": { entryPoint } },
605
+ settings: { tailwindcss: { entryPoint } },
604
606
  rules: {
605
- "better-tailwindcss/enforce-consistent-class-order": "error",
606
- "better-tailwindcss/enforce-consistent-line-wrapping": "off",
607
- "better-tailwindcss/no-unnecessary-whitespace": "off",
608
- "better-tailwindcss/enforce-canonical-classes": "error",
609
- "better-tailwindcss/no-conflicting-classes": "error",
610
- "better-tailwindcss/no-deprecated-classes": "error",
611
- "better-tailwindcss/no-duplicate-classes": "error",
612
- "better-tailwindcss/no-unknown-classes": ["error", { ignore: ignoreClasses }]
607
+ "tailwindcss/enforce-sort-order": "error",
608
+ "tailwindcss/enforce-consistent-line-wrapping": "off",
609
+ "tailwindcss/no-unnecessary-whitespace": "off",
610
+ "tailwindcss/enforce-canonical": "error",
611
+ "tailwindcss/consistent-variant-order": "error",
612
+ "tailwindcss/enforce-consistent-important-position": "error",
613
+ "tailwindcss/no-conflicting-classes": "error",
614
+ "tailwindcss/no-deprecated-classes": "error",
615
+ "tailwindcss/no-duplicate-classes": "error",
616
+ "tailwindcss/no-unnecessary-arbitrary-value": "warn",
617
+ "tailwindcss/no-unknown-classes": ["error", { allowlist: ignoreClasses }]
613
618
  }
614
619
  });
615
620
  }
616
621
  //#endregion
617
- export { oxfmtConfig, oxlintConfig, tailwind };
622
+ export { oxfmtConfig, oxlintConfig, tailwindPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letstri/oxc-config",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Opinionated Oxlint and Oxfmt configs",
5
5
  "homepage": "https://github.com/letstri/oxc-config#readme",
6
6
  "bugs": {
@@ -46,12 +46,12 @@
46
46
  "typescript": "^6.0.3"
47
47
  },
48
48
  "peerDependencies": {
49
- "eslint-plugin-better-tailwindcss": ">=4",
50
49
  "oxfmt": ">=0.58.0",
51
- "oxlint": ">=1.73.0"
50
+ "oxlint": ">=1.73.0",
51
+ "oxlint-tailwindcss": ">=1"
52
52
  },
53
53
  "peerDependenciesMeta": {
54
- "eslint-plugin-better-tailwindcss": {
54
+ "oxlint-tailwindcss": {
55
55
  "optional": true
56
56
  }
57
57
  },