@maz-ui/eslint-config 5.0.0-canary.ef64a02.0 → 5.3.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 +164 -90
- package/dist/index.d.mts +148 -18
- package/dist/index.d.ts +148 -18
- package/dist/index.mjs +380 -67
- package/package.json +23 -19
package/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# @maz-ui/eslint-config
|
|
2
2
|
|
|
3
|
-
Reusable ESLint configuration for JavaScript/TypeScript projects.
|
|
3
|
+
Reusable ESLint configuration for JavaScript/TypeScript projects, built on top of [`@antfu/eslint-config`](https://github.com/antfu/eslint-config).
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- 🚀 **Based on @antfu/eslint-config**
|
|
8
|
-
- 🛡️ **
|
|
9
|
-
- 🎨 **Tailwind CSS** -
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- 🚀 **Based on @antfu/eslint-config** — modern, performant, flat-config first
|
|
8
|
+
- 🛡️ **TypeScript-first** — strict mode, sensible defaults
|
|
9
|
+
- 🎨 **Tailwind CSS v3 + v4** — via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss) (compat ESLint 7→10, both Tailwind versions)
|
|
10
|
+
- 🧩 **Custom `maz/` rules** — ships its own ESLint plugin namespaced `maz/*` (see [Custom rules](#custom-rules))
|
|
11
|
+
- 🔍 **SonarJS** — code quality / cognitive complexity
|
|
12
|
+
- ♿ **Vue accessibility** — opt-in `eslint-plugin-vuejs-accessibility`
|
|
13
|
+
- 📐 **Formatters** — Prettier-style formatting via `eslint-plugin-format`
|
|
14
|
+
- ⚙️ **Configurable** — pick presets, add per-file rules, custom ignore globs
|
|
13
15
|
|
|
14
16
|
## Installation
|
|
15
17
|
|
|
@@ -26,139 +28,211 @@ import { defineConfig } from '@maz-ui/eslint-config'
|
|
|
26
28
|
export default defineConfig()
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Vue / Nuxt are auto-detected from your `package.json`.
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
30
34
|
|
|
31
35
|
```js
|
|
32
36
|
// eslint.config.js
|
|
33
37
|
import { defineConfig } from '@maz-ui/eslint-config'
|
|
34
38
|
|
|
35
39
|
export default defineConfig({
|
|
36
|
-
//
|
|
37
|
-
env: 'production', // 'development' | 'production'
|
|
40
|
+
env: 'production', // 'development' | 'production' — affects no-console / no-debugger severity
|
|
38
41
|
|
|
39
|
-
//
|
|
42
|
+
// Antfu options (forwarded as-is)
|
|
40
43
|
typescript: true,
|
|
41
|
-
tailwindcss: true,
|
|
42
|
-
sonarjs: true,
|
|
43
44
|
formatters: true,
|
|
45
|
+
unicorn: true,
|
|
46
|
+
|
|
47
|
+
// Maz-UI options
|
|
48
|
+
sonarjs: true,
|
|
49
|
+
vueAccessibility: false,
|
|
50
|
+
tailwindcss: 'recommended', // see "Tailwind support" below
|
|
44
51
|
|
|
45
|
-
//
|
|
52
|
+
// Extra ignore globs (merged with defaults)
|
|
46
53
|
ignores: ['custom-dist/**'],
|
|
47
54
|
|
|
48
55
|
// Custom rules
|
|
49
56
|
rules: {
|
|
50
57
|
'no-console': 'error',
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// 'silent' | 'default' | 'debug' | 'verbose' — see "Logging" below.
|
|
61
|
+
logLevel: 'default',
|
|
53
62
|
})
|
|
54
63
|
```
|
|
55
64
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
interface MazESLintOptions {
|
|
60
|
-
typescript?: boolean // TypeScript support (default: true)
|
|
61
|
-
tailwindcss?: boolean // Tailwind CSS rules (default: true)
|
|
62
|
-
sonarjs?: boolean // SonarJS rules (default: true)
|
|
63
|
-
formatters?: boolean // Formatters support (default: true)
|
|
64
|
-
env?: 'development' | 'production' // Environment (default: 'development')
|
|
65
|
-
ignores?: string[] // Files to ignore
|
|
66
|
-
rules?: Record<string, any> // Custom ESLint rules
|
|
67
|
-
}
|
|
68
|
-
```
|
|
65
|
+
## Logging
|
|
69
66
|
|
|
70
|
-
|
|
67
|
+
The preset prints a titled box summarizing what it resolved (typescript, vue, tailwindcss preset, sonarjs, …). Powered by `@maz-ui/node`'s consola-based logger.
|
|
71
68
|
|
|
72
|
-
|
|
69
|
+
| Value | What you see |
|
|
70
|
+
| --------------------------- | -------------------------------------------------- |
|
|
71
|
+
| `'silent'` | Nothing. |
|
|
72
|
+
| `'default'` _(TTY default)_ | Titled box of resolved feature toggles. |
|
|
73
|
+
| `'debug'` | Above + each plugin / preset / overrides addition. |
|
|
74
|
+
| `'verbose'` | Above + final block count and ignore-glob count. |
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
import { baseRules, sonarjsRules, tailwindcssRules } from '@maz-ui/eslint-config'
|
|
76
|
+
The default is **`'default'` in interactive terminals, `'silent'` otherwise** (CI, pipes, JSON / SARIF formatters). This avoids polluting machine-readable ESLint output. Override by passing `logLevel` explicitly.
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
rules: {
|
|
80
|
-
...baseRules,
|
|
81
|
-
...sonarjsRules,
|
|
82
|
-
// Your custom rules
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
]
|
|
86
|
-
```
|
|
78
|
+
## Tailwind support
|
|
87
79
|
|
|
88
|
-
|
|
80
|
+
Powered by [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss) — compatible with ESLint 7→10 and Tailwind v3 + v4.
|
|
89
81
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
| Value | Behavior |
|
|
83
|
+
| ----------------------- | ----------------------------------------------------------------------- |
|
|
84
|
+
| `false` _(default)_ | Disabled. |
|
|
85
|
+
| `true` | Same as `'recommended'` with default settings. |
|
|
86
|
+
| `'recommended'` | Stylistic + correctness rules. |
|
|
87
|
+
| `'stylistic'` | Formatting rules only (class order, line wrapping, …). |
|
|
88
|
+
| `'correctness'` | Validation rules only (`no-unknown-classes`, `no-conflicting-classes`). |
|
|
89
|
+
| `MazTailwindcssOptions` | Pick a preset and pass plugin settings. |
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
```js
|
|
92
|
+
defineConfig({
|
|
93
|
+
tailwindcss: {
|
|
94
|
+
preset: 'recommended',
|
|
95
|
+
// Tailwind v4: path to the CSS that does `@import "tailwindcss" prefix(maz)`
|
|
96
|
+
entryPoint: 'src/main.css',
|
|
97
|
+
// Tailwind v3 instead:
|
|
98
|
+
// tailwindConfig: 'tailwind.config.ts',
|
|
99
|
+
detectComponentClasses: true,
|
|
100
|
+
// Custom `maz/tailwind-no-arbitrary-px` rule — see "Custom rules" below.
|
|
101
|
+
noArbitraryPx: true,
|
|
102
|
+
},
|
|
102
103
|
})
|
|
103
104
|
```
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
> [!IMPORTANT]
|
|
107
|
+
> The plugin's `no-unknown-classes` rule needs to know about your Tailwind setup (prefix, custom utilities, theme tokens). Without `entryPoint` (v4) or `tailwindConfig` (v3), it falls back to the default Tailwind config and will flood with false positives in projects that customize either. **Pass the path explicitly** if your Tailwind setup is non-standard.
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
## Custom rules
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
- Code quality rules
|
|
111
|
-
- Optimized TypeScript support
|
|
111
|
+
This preset ships its own ESLint plugin under the `maz/*` namespace. Rules are organized by category — `maz/tailwind-*` for Tailwind-specific rules, ready for `maz/js-*` and others as they get added.
|
|
112
112
|
|
|
113
|
-
###
|
|
113
|
+
### `maz/tailwind-no-arbitrary-px`
|
|
114
114
|
|
|
115
|
-
-
|
|
116
|
-
- Duplicate code detection
|
|
117
|
-
- Security best practices
|
|
115
|
+
Forbids `px` units inside Tailwind arbitrary value classes (`w-[16px]`, `m-[-16px]`, `[gap:24px]`, …) and **autofixes** them to `rem` (or `em`) using the configured root font-size. Whitespace inside brackets (e.g. `[up to 100px]`) is left alone so plain prose is never rewritten.
|
|
118
116
|
|
|
119
|
-
|
|
117
|
+
> [!NOTE]
|
|
118
|
+
> The rule and the `maz` plugin are only registered when `tailwindcss` is enabled (any value other than `false`). With `tailwindcss: false` _(default)_ neither the plugin nor the rule appear in the final flat-config. To use the rule in a project that doesn't opt into the full Tailwind preset, register it manually (see [Use the plugin directly](#use-the-plugin-directly)).
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
- Contradictory class detection
|
|
123
|
-
- Valid Tailwind syntax
|
|
120
|
+
When `tailwindcss` is on, the rule is enabled with defaults. You can tune it either way:
|
|
124
121
|
|
|
125
|
-
|
|
122
|
+
**Standard ESLint override** _(idiomatic, max control)_
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
```js
|
|
125
|
+
defineConfig({
|
|
126
|
+
tailwindcss: 'recommended',
|
|
127
|
+
rules: {
|
|
128
|
+
'maz/tailwind-no-arbitrary-px': [
|
|
129
|
+
'error',
|
|
130
|
+
{ baseFontSize: 10, unit: 'em' }
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
```
|
|
130
135
|
|
|
131
|
-
|
|
136
|
+
User `rules` overrides are applied in a _trailing_ block, so they win over the rule's defaults wired in by `tailwindcssConfigs`.
|
|
132
137
|
|
|
133
|
-
|
|
138
|
+
**Ergonomic shortcut** _(set the defaults via `tailwindcss.noArbitraryPx`)_
|
|
134
139
|
|
|
135
140
|
```js
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
defineConfig({
|
|
142
|
+
tailwindcss: {
|
|
143
|
+
preset: 'recommended',
|
|
144
|
+
noArbitraryPx: {
|
|
145
|
+
baseFontSize: 16, // default — convert px / 16 → rem
|
|
146
|
+
unit: 'rem', // default — target unit ('rem' | 'em')
|
|
147
|
+
severity: 'error', // default — 'off' | 'warn' | 'error'
|
|
148
|
+
},
|
|
149
|
+
},
|
|
139
150
|
})
|
|
140
151
|
```
|
|
141
152
|
|
|
142
|
-
|
|
153
|
+
| `noArbitraryPx` value | Behavior |
|
|
154
|
+
| ---------------------------------- | -------------------------------------------------- |
|
|
155
|
+
| `true` _(default)_ | Enabled with defaults (`baseFontSize: 16`, `rem`). |
|
|
156
|
+
| `false` | Disabled. |
|
|
157
|
+
| `{ baseFontSize, unit, severity }` | Override any subset of the defaults. |
|
|
158
|
+
|
|
159
|
+
If both are set, the standard `rules` override wins (last block applied).
|
|
160
|
+
|
|
161
|
+
Examples after autofix (with `baseFontSize: 16`):
|
|
162
|
+
|
|
163
|
+
| Before | After |
|
|
164
|
+
| --------------------- | ----------------------- |
|
|
165
|
+
| `w-[16px]` | `w-[1rem]` |
|
|
166
|
+
| `m-[-16px]` | `m-[-1rem]` |
|
|
167
|
+
| `tracking-[.5px]` | `tracking-[0.03125rem]` |
|
|
168
|
+
| `p-[16px_8px]` | `p-[1rem_0.5rem]` |
|
|
169
|
+
| `[gap:24px]` | `[gap:1.5rem]` |
|
|
170
|
+
| `w-[calc(100%-16px)]` | `w-[calc(100%-1rem)]` |
|
|
171
|
+
|
|
172
|
+
### Use the plugin directly
|
|
173
|
+
|
|
174
|
+
If you don't want the full Tailwind preset (or just prefer wiring rules yourself), import `mazPlugin` and register it in your own flat-config block. This is also the way to use `maz/tailwind-*` rules without enabling `tailwindcss` in `defineConfig`:
|
|
143
175
|
|
|
144
176
|
```js
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
177
|
+
import { mazPlugin } from '@maz-ui/eslint-config'
|
|
178
|
+
|
|
179
|
+
export default [
|
|
180
|
+
{
|
|
181
|
+
files: ['**/*.{ts,tsx,vue}'],
|
|
182
|
+
plugins: { maz: mazPlugin },
|
|
183
|
+
rules: {
|
|
184
|
+
'maz/tailwind-no-arbitrary-px': [
|
|
185
|
+
'error',
|
|
186
|
+
{ baseFontSize: 16 }
|
|
187
|
+
],
|
|
188
|
+
},
|
|
149
189
|
}
|
|
150
|
-
|
|
190
|
+
]
|
|
151
191
|
```
|
|
152
192
|
|
|
153
|
-
|
|
193
|
+
## Advanced
|
|
194
|
+
|
|
195
|
+
### Compose your own config
|
|
154
196
|
|
|
155
197
|
```js
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
198
|
+
import {
|
|
199
|
+
baseRules,
|
|
200
|
+
mazPlugin,
|
|
201
|
+
sonarjsRules,
|
|
202
|
+
tailwindcssConfigs,
|
|
203
|
+
vueRules,
|
|
204
|
+
} from '@maz-ui/eslint-config'
|
|
205
|
+
|
|
206
|
+
export default [
|
|
207
|
+
{
|
|
208
|
+
plugins: { maz: mazPlugin },
|
|
209
|
+
rules: {
|
|
210
|
+
...baseRules(true), // production = true
|
|
211
|
+
...sonarjsRules,
|
|
212
|
+
...vueRules,
|
|
213
|
+
'maz/tailwind-no-arbitrary-px': 'error',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
...tailwindcssConfigs('stylistic', {
|
|
217
|
+
entryPoint: 'src/main.css',
|
|
218
|
+
noArbitraryPx: { unit: 'em' },
|
|
219
|
+
}),
|
|
220
|
+
]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Vue project
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
defineConfig({
|
|
227
|
+
vue: true, // auto-detected if `vue` / `nuxt` is in deps
|
|
228
|
+
tailwindcss: 'recommended',
|
|
159
229
|
rules: {
|
|
160
|
-
'
|
|
161
|
-
|
|
162
|
-
}
|
|
230
|
+
'vue/custom-event-name-casing': ['error', 'kebab-case'],
|
|
231
|
+
},
|
|
163
232
|
})
|
|
164
233
|
```
|
|
234
|
+
|
|
235
|
+
## Compatibility
|
|
236
|
+
|
|
237
|
+
- **ESLint** `^9.0.0 || ^10.0.0`
|
|
238
|
+
- **Node.js** `>=20.19.0`
|
package/dist/index.d.mts
CHANGED
|
@@ -1,13 +1,102 @@
|
|
|
1
1
|
import { antfu, OptionsConfig, Rules, TypedFlatConfigItem } from '@antfu/eslint-config';
|
|
2
|
-
import { Linter } from 'eslint';
|
|
2
|
+
import { Linter, ESLint, Rule } from 'eslint';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mirror of `@maz-ui/node`'s LogLevel — kept inline here to avoid making this
|
|
6
|
+
* preset depend on `@maz-ui/node`, which would create a build cycle (the
|
|
7
|
+
* `eslint.config.ts` of `@maz-ui/node` imports `@maz-ui/eslint-config`, which
|
|
8
|
+
* itself would depend on `@maz-ui/node` for the same logger). Direct usage of
|
|
9
|
+
* `consola` keeps the dependency graph acyclic.
|
|
10
|
+
*/
|
|
11
|
+
type LogLevel = 'silent' | 'error' | 'warning' | 'normal' | 'default' | 'debug' | 'trace' | 'verbose';
|
|
3
12
|
|
|
4
13
|
type MazESLintUserConfig = TypedFlatConfigItem | TypedFlatConfigItem[] | Linter.Config | Linter.Config[];
|
|
14
|
+
type TailwindcssPreset = 'recommended' | 'stylistic' | 'correctness';
|
|
15
|
+
/**
|
|
16
|
+
* Settings forwarded to `eslint-plugin-better-tailwindcss` under
|
|
17
|
+
* `settings['better-tailwindcss']`. See the plugin docs for the canonical
|
|
18
|
+
* meaning of each field.
|
|
19
|
+
*/
|
|
20
|
+
interface MazTailwindcssOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Which preset to load. Each one comes in `*-warn` and `*-error` flavors
|
|
23
|
+
* (defaults match the plugin's defaults: stylistic→warn, correctness→error).
|
|
24
|
+
*
|
|
25
|
+
* @default 'recommended'
|
|
26
|
+
*/
|
|
27
|
+
preset?: TailwindcssPreset;
|
|
28
|
+
/**
|
|
29
|
+
* Tailwind v4: path to the CSS entry file (the one that does
|
|
30
|
+
* `@import "tailwindcss"`, possibly with `prefix(...)`). Required for the
|
|
31
|
+
* plugin to know about your prefix and `@theme` tokens.
|
|
32
|
+
*/
|
|
33
|
+
entryPoint?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Tailwind v3: path to `tailwind.config.{js,ts,cjs,mjs}`. Use `entryPoint`
|
|
36
|
+
* for v4 instead.
|
|
37
|
+
*/
|
|
38
|
+
tailwindConfig?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Tailwind v4: enable detection of custom component classes (`@layer
|
|
41
|
+
* components { … }`) so they are not reported as unknown.
|
|
42
|
+
*
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
detectComponentClasses?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Working directory used to resolve `tailwindcss` and the config files
|
|
48
|
+
* above. Useful in monorepos when ESLint runs from the repo root.
|
|
49
|
+
*/
|
|
50
|
+
cwd?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Path to a `tsconfig.json` used to resolve `paths` aliases inside the
|
|
53
|
+
* Tailwind config.
|
|
54
|
+
*/
|
|
55
|
+
tsconfig?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Configure the `maz/tailwind-no-arbitrary-px` rule (forbids `px`
|
|
58
|
+
* values inside Tailwind arbitrary value classes such as `w-[16px]`).
|
|
59
|
+
*
|
|
60
|
+
* - `false`: disable the rule.
|
|
61
|
+
* - `true` *(default)*: enable with defaults (`baseFontSize: 16`,
|
|
62
|
+
* `unit: 'rem'`).
|
|
63
|
+
* - Object: enable with custom options.
|
|
64
|
+
*/
|
|
65
|
+
noArbitraryPx?: boolean | {
|
|
66
|
+
/**
|
|
67
|
+
* Root font size, in pixels, used to convert `Npx` to `rem`/`em`.
|
|
68
|
+
*
|
|
69
|
+
* @default 16
|
|
70
|
+
*/
|
|
71
|
+
baseFontSize?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Output unit for the autofix.
|
|
74
|
+
*
|
|
75
|
+
* @default 'rem'
|
|
76
|
+
*/
|
|
77
|
+
unit?: 'rem' | 'em';
|
|
78
|
+
/**
|
|
79
|
+
* ESLint severity for the rule.
|
|
80
|
+
*
|
|
81
|
+
* @default 'error'
|
|
82
|
+
*/
|
|
83
|
+
severity?: 'off' | 'warn' | 'error';
|
|
84
|
+
};
|
|
85
|
+
}
|
|
5
86
|
interface MazESLintOptions extends OptionsConfig {
|
|
6
87
|
/**
|
|
7
|
-
*
|
|
88
|
+
* Tailwind CSS support — wires `eslint-plugin-better-tailwindcss`.
|
|
89
|
+
*
|
|
90
|
+
* - `false` *(default)*: disabled.
|
|
91
|
+
* - `true`: load the `'recommended'` preset with default settings.
|
|
92
|
+
* - `'recommended' | 'stylistic' | 'correctness'`: pick a preset, default
|
|
93
|
+
* settings.
|
|
94
|
+
* - `MazTailwindcssOptions`: pick a preset and pass settings (e.g.
|
|
95
|
+
* `entryPoint` so the plugin learns your v4 prefix).
|
|
96
|
+
*
|
|
8
97
|
* @default false
|
|
9
98
|
*/
|
|
10
|
-
tailwindcss?: boolean;
|
|
99
|
+
tailwindcss?: boolean | TailwindcssPreset | MazTailwindcssOptions;
|
|
11
100
|
/**
|
|
12
101
|
* Enable SonarJS rules for code quality
|
|
13
102
|
* @default true
|
|
@@ -32,6 +121,19 @@ interface MazESLintOptions extends OptionsConfig {
|
|
|
32
121
|
* Additional rules to merge
|
|
33
122
|
*/
|
|
34
123
|
rules?: Partial<Rules>;
|
|
124
|
+
/**
|
|
125
|
+
* Verbosity of the logs emitted while resolving the config. Defaults to
|
|
126
|
+
* `'default'` so the resolved-configuration box is shown when the preset
|
|
127
|
+
* loads. Set to `'silent'` to hide it, or `'debug'` / `'verbose'` to dig
|
|
128
|
+
* deeper.
|
|
129
|
+
*
|
|
130
|
+
* - `'silent'`: nothing is printed.
|
|
131
|
+
* - `'default'` *(default)*: a titled box summarizing the resolved feature
|
|
132
|
+
* toggles.
|
|
133
|
+
* - `'debug'`: also logs each plugin / preset / overrides addition.
|
|
134
|
+
* - `'verbose'`: also logs the final shape (config block count, etc.).
|
|
135
|
+
*/
|
|
136
|
+
logLevel?: LogLevel;
|
|
35
137
|
}
|
|
36
138
|
type MazESLintConfig = ReturnType<typeof antfu>;
|
|
37
139
|
|
|
@@ -68,24 +170,52 @@ declare const sonarjsTestRules: {
|
|
|
68
170
|
};
|
|
69
171
|
|
|
70
172
|
/**
|
|
71
|
-
* Tailwind
|
|
72
|
-
*
|
|
73
|
-
* These rules only activate when the user opts in via
|
|
74
|
-
* `defineMazEslintConfig({ tailwindcss: true })` AND is on ESLint 9 +
|
|
75
|
-
* Tailwind v3. The plugin itself does not yet support ESLint 10+ or
|
|
76
|
-
* Tailwind v4 (upstream — uses the removed context.getSourceCode API).
|
|
173
|
+
* Files where Tailwind classes commonly live. Exposed for users who want to
|
|
174
|
+
* compose their own config.
|
|
77
175
|
*/
|
|
78
|
-
declare const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
176
|
+
declare const TAILWINDCSS_DEFAULT_FILES: string[];
|
|
177
|
+
/**
|
|
178
|
+
* Build the flat-config block(s) that wire up
|
|
179
|
+
* `eslint-plugin-better-tailwindcss` AND the custom `maz/tailwind-*`
|
|
180
|
+
* rules shipped by this package. The plugin already ships preset
|
|
181
|
+
* objects with `plugins` + `rules`; we spread them and add `files` plus
|
|
182
|
+
* `settings['better-tailwindcss']` so the user's options reach the
|
|
183
|
+
* plugin.
|
|
184
|
+
*/
|
|
185
|
+
declare function tailwindcssConfigs(preset: TailwindcssPreset, settings: MazTailwindcssOptions): Linter.Config[];
|
|
85
186
|
|
|
86
187
|
declare const vueRules: {
|
|
87
188
|
'vue/custom-event-name-casing': ["error", "kebab-case"];
|
|
88
189
|
};
|
|
190
|
+
declare const vueSfcOnlyRules: {
|
|
191
|
+
'no-useless-assignment': "off";
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The `maz` ESLint plugin bundling all custom rules shipped by
|
|
196
|
+
* `@maz-ui/eslint-config`. Rules are namespaced by category:
|
|
197
|
+
*
|
|
198
|
+
* - `maz/tailwind-*` — Tailwind-specific rules
|
|
199
|
+
* - `maz/js-*` — general JS/TS rules (when added)
|
|
200
|
+
* - …
|
|
201
|
+
*
|
|
202
|
+
* The plugin is auto-wired by the relevant config block in
|
|
203
|
+
* `src/configs/*.ts`, but it is also exported so users can compose
|
|
204
|
+
* their own flat-config setup.
|
|
205
|
+
*/
|
|
206
|
+
declare const mazPlugin: ESLint.Plugin;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Registry of all custom rules shipped by `@maz-ui/eslint-config`.
|
|
210
|
+
*
|
|
211
|
+
* Keys here are the final rule names exposed by the `maz` plugin, so a
|
|
212
|
+
* file in `src/rules/tailwind/no-arbitrary-px.ts` becomes
|
|
213
|
+
* `maz/tailwind-no-arbitrary-px` when wired through the plugin.
|
|
214
|
+
*
|
|
215
|
+
* Add new rules by dropping a file in the matching category folder
|
|
216
|
+
* (`tailwind/`, `js/`, …) and registering it below.
|
|
217
|
+
*/
|
|
218
|
+
declare const rules: Record<string, Rule.RuleModule>;
|
|
89
219
|
|
|
90
220
|
/**
|
|
91
221
|
* Create ESLint configuration for Maz-UI and JavaScript/TypeScript projects
|
|
@@ -107,5 +237,5 @@ declare const vueRules: {
|
|
|
107
237
|
*/
|
|
108
238
|
declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
|
|
109
239
|
|
|
110
|
-
export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules,
|
|
111
|
-
export type { MazESLintConfig, MazESLintOptions };
|
|
240
|
+
export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, mazPlugin, rules as mazRules, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
|
|
241
|
+
export type { MazESLintConfig, MazESLintOptions, MazTailwindcssOptions, TailwindcssPreset };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,102 @@
|
|
|
1
1
|
import { antfu, OptionsConfig, Rules, TypedFlatConfigItem } from '@antfu/eslint-config';
|
|
2
|
-
import { Linter } from 'eslint';
|
|
2
|
+
import { Linter, ESLint, Rule } from 'eslint';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mirror of `@maz-ui/node`'s LogLevel — kept inline here to avoid making this
|
|
6
|
+
* preset depend on `@maz-ui/node`, which would create a build cycle (the
|
|
7
|
+
* `eslint.config.ts` of `@maz-ui/node` imports `@maz-ui/eslint-config`, which
|
|
8
|
+
* itself would depend on `@maz-ui/node` for the same logger). Direct usage of
|
|
9
|
+
* `consola` keeps the dependency graph acyclic.
|
|
10
|
+
*/
|
|
11
|
+
type LogLevel = 'silent' | 'error' | 'warning' | 'normal' | 'default' | 'debug' | 'trace' | 'verbose';
|
|
3
12
|
|
|
4
13
|
type MazESLintUserConfig = TypedFlatConfigItem | TypedFlatConfigItem[] | Linter.Config | Linter.Config[];
|
|
14
|
+
type TailwindcssPreset = 'recommended' | 'stylistic' | 'correctness';
|
|
15
|
+
/**
|
|
16
|
+
* Settings forwarded to `eslint-plugin-better-tailwindcss` under
|
|
17
|
+
* `settings['better-tailwindcss']`. See the plugin docs for the canonical
|
|
18
|
+
* meaning of each field.
|
|
19
|
+
*/
|
|
20
|
+
interface MazTailwindcssOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Which preset to load. Each one comes in `*-warn` and `*-error` flavors
|
|
23
|
+
* (defaults match the plugin's defaults: stylistic→warn, correctness→error).
|
|
24
|
+
*
|
|
25
|
+
* @default 'recommended'
|
|
26
|
+
*/
|
|
27
|
+
preset?: TailwindcssPreset;
|
|
28
|
+
/**
|
|
29
|
+
* Tailwind v4: path to the CSS entry file (the one that does
|
|
30
|
+
* `@import "tailwindcss"`, possibly with `prefix(...)`). Required for the
|
|
31
|
+
* plugin to know about your prefix and `@theme` tokens.
|
|
32
|
+
*/
|
|
33
|
+
entryPoint?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Tailwind v3: path to `tailwind.config.{js,ts,cjs,mjs}`. Use `entryPoint`
|
|
36
|
+
* for v4 instead.
|
|
37
|
+
*/
|
|
38
|
+
tailwindConfig?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Tailwind v4: enable detection of custom component classes (`@layer
|
|
41
|
+
* components { … }`) so they are not reported as unknown.
|
|
42
|
+
*
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
detectComponentClasses?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Working directory used to resolve `tailwindcss` and the config files
|
|
48
|
+
* above. Useful in monorepos when ESLint runs from the repo root.
|
|
49
|
+
*/
|
|
50
|
+
cwd?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Path to a `tsconfig.json` used to resolve `paths` aliases inside the
|
|
53
|
+
* Tailwind config.
|
|
54
|
+
*/
|
|
55
|
+
tsconfig?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Configure the `maz/tailwind-no-arbitrary-px` rule (forbids `px`
|
|
58
|
+
* values inside Tailwind arbitrary value classes such as `w-[16px]`).
|
|
59
|
+
*
|
|
60
|
+
* - `false`: disable the rule.
|
|
61
|
+
* - `true` *(default)*: enable with defaults (`baseFontSize: 16`,
|
|
62
|
+
* `unit: 'rem'`).
|
|
63
|
+
* - Object: enable with custom options.
|
|
64
|
+
*/
|
|
65
|
+
noArbitraryPx?: boolean | {
|
|
66
|
+
/**
|
|
67
|
+
* Root font size, in pixels, used to convert `Npx` to `rem`/`em`.
|
|
68
|
+
*
|
|
69
|
+
* @default 16
|
|
70
|
+
*/
|
|
71
|
+
baseFontSize?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Output unit for the autofix.
|
|
74
|
+
*
|
|
75
|
+
* @default 'rem'
|
|
76
|
+
*/
|
|
77
|
+
unit?: 'rem' | 'em';
|
|
78
|
+
/**
|
|
79
|
+
* ESLint severity for the rule.
|
|
80
|
+
*
|
|
81
|
+
* @default 'error'
|
|
82
|
+
*/
|
|
83
|
+
severity?: 'off' | 'warn' | 'error';
|
|
84
|
+
};
|
|
85
|
+
}
|
|
5
86
|
interface MazESLintOptions extends OptionsConfig {
|
|
6
87
|
/**
|
|
7
|
-
*
|
|
88
|
+
* Tailwind CSS support — wires `eslint-plugin-better-tailwindcss`.
|
|
89
|
+
*
|
|
90
|
+
* - `false` *(default)*: disabled.
|
|
91
|
+
* - `true`: load the `'recommended'` preset with default settings.
|
|
92
|
+
* - `'recommended' | 'stylistic' | 'correctness'`: pick a preset, default
|
|
93
|
+
* settings.
|
|
94
|
+
* - `MazTailwindcssOptions`: pick a preset and pass settings (e.g.
|
|
95
|
+
* `entryPoint` so the plugin learns your v4 prefix).
|
|
96
|
+
*
|
|
8
97
|
* @default false
|
|
9
98
|
*/
|
|
10
|
-
tailwindcss?: boolean;
|
|
99
|
+
tailwindcss?: boolean | TailwindcssPreset | MazTailwindcssOptions;
|
|
11
100
|
/**
|
|
12
101
|
* Enable SonarJS rules for code quality
|
|
13
102
|
* @default true
|
|
@@ -32,6 +121,19 @@ interface MazESLintOptions extends OptionsConfig {
|
|
|
32
121
|
* Additional rules to merge
|
|
33
122
|
*/
|
|
34
123
|
rules?: Partial<Rules>;
|
|
124
|
+
/**
|
|
125
|
+
* Verbosity of the logs emitted while resolving the config. Defaults to
|
|
126
|
+
* `'default'` so the resolved-configuration box is shown when the preset
|
|
127
|
+
* loads. Set to `'silent'` to hide it, or `'debug'` / `'verbose'` to dig
|
|
128
|
+
* deeper.
|
|
129
|
+
*
|
|
130
|
+
* - `'silent'`: nothing is printed.
|
|
131
|
+
* - `'default'` *(default)*: a titled box summarizing the resolved feature
|
|
132
|
+
* toggles.
|
|
133
|
+
* - `'debug'`: also logs each plugin / preset / overrides addition.
|
|
134
|
+
* - `'verbose'`: also logs the final shape (config block count, etc.).
|
|
135
|
+
*/
|
|
136
|
+
logLevel?: LogLevel;
|
|
35
137
|
}
|
|
36
138
|
type MazESLintConfig = ReturnType<typeof antfu>;
|
|
37
139
|
|
|
@@ -68,24 +170,52 @@ declare const sonarjsTestRules: {
|
|
|
68
170
|
};
|
|
69
171
|
|
|
70
172
|
/**
|
|
71
|
-
* Tailwind
|
|
72
|
-
*
|
|
73
|
-
* These rules only activate when the user opts in via
|
|
74
|
-
* `defineMazEslintConfig({ tailwindcss: true })` AND is on ESLint 9 +
|
|
75
|
-
* Tailwind v3. The plugin itself does not yet support ESLint 10+ or
|
|
76
|
-
* Tailwind v4 (upstream — uses the removed context.getSourceCode API).
|
|
173
|
+
* Files where Tailwind classes commonly live. Exposed for users who want to
|
|
174
|
+
* compose their own config.
|
|
77
175
|
*/
|
|
78
|
-
declare const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
176
|
+
declare const TAILWINDCSS_DEFAULT_FILES: string[];
|
|
177
|
+
/**
|
|
178
|
+
* Build the flat-config block(s) that wire up
|
|
179
|
+
* `eslint-plugin-better-tailwindcss` AND the custom `maz/tailwind-*`
|
|
180
|
+
* rules shipped by this package. The plugin already ships preset
|
|
181
|
+
* objects with `plugins` + `rules`; we spread them and add `files` plus
|
|
182
|
+
* `settings['better-tailwindcss']` so the user's options reach the
|
|
183
|
+
* plugin.
|
|
184
|
+
*/
|
|
185
|
+
declare function tailwindcssConfigs(preset: TailwindcssPreset, settings: MazTailwindcssOptions): Linter.Config[];
|
|
85
186
|
|
|
86
187
|
declare const vueRules: {
|
|
87
188
|
'vue/custom-event-name-casing': ["error", "kebab-case"];
|
|
88
189
|
};
|
|
190
|
+
declare const vueSfcOnlyRules: {
|
|
191
|
+
'no-useless-assignment': "off";
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The `maz` ESLint plugin bundling all custom rules shipped by
|
|
196
|
+
* `@maz-ui/eslint-config`. Rules are namespaced by category:
|
|
197
|
+
*
|
|
198
|
+
* - `maz/tailwind-*` — Tailwind-specific rules
|
|
199
|
+
* - `maz/js-*` — general JS/TS rules (when added)
|
|
200
|
+
* - …
|
|
201
|
+
*
|
|
202
|
+
* The plugin is auto-wired by the relevant config block in
|
|
203
|
+
* `src/configs/*.ts`, but it is also exported so users can compose
|
|
204
|
+
* their own flat-config setup.
|
|
205
|
+
*/
|
|
206
|
+
declare const mazPlugin: ESLint.Plugin;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Registry of all custom rules shipped by `@maz-ui/eslint-config`.
|
|
210
|
+
*
|
|
211
|
+
* Keys here are the final rule names exposed by the `maz` plugin, so a
|
|
212
|
+
* file in `src/rules/tailwind/no-arbitrary-px.ts` becomes
|
|
213
|
+
* `maz/tailwind-no-arbitrary-px` when wired through the plugin.
|
|
214
|
+
*
|
|
215
|
+
* Add new rules by dropping a file in the matching category folder
|
|
216
|
+
* (`tailwind/`, `js/`, …) and registering it below.
|
|
217
|
+
*/
|
|
218
|
+
declare const rules: Record<string, Rule.RuleModule>;
|
|
89
219
|
|
|
90
220
|
/**
|
|
91
221
|
* Create ESLint configuration for Maz-UI and JavaScript/TypeScript projects
|
|
@@ -107,5 +237,5 @@ declare const vueRules: {
|
|
|
107
237
|
*/
|
|
108
238
|
declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
|
|
109
239
|
|
|
110
|
-
export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules,
|
|
111
|
-
export type { MazESLintConfig, MazESLintOptions };
|
|
240
|
+
export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, mazPlugin, rules as mazRules, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
|
|
241
|
+
export type { MazESLintConfig, MazESLintOptions, MazTailwindcssOptions, TailwindcssPreset };
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
2
|
import { join } from 'node:path';
|
|
4
3
|
import antfu from '@antfu/eslint-config';
|
|
5
4
|
import { configs } from 'eslint-plugin-sonarjs';
|
|
6
5
|
import vueA11y from 'eslint-plugin-vuejs-accessibility';
|
|
6
|
+
import { createConsola } from 'consola';
|
|
7
|
+
import betterTailwindcss from 'eslint-plugin-better-tailwindcss';
|
|
7
8
|
|
|
8
9
|
function baseRules(isProduction) {
|
|
9
10
|
return {
|
|
10
11
|
// Console warnings in development, errors in production
|
|
11
|
-
"no-console": [
|
|
12
|
+
"no-console": [
|
|
13
|
+
isProduction ? "error" : "warn"
|
|
14
|
+
],
|
|
12
15
|
// Code quality
|
|
13
16
|
"prefer-regex-literals": "off",
|
|
14
17
|
"require-await": "error",
|
|
@@ -17,20 +20,30 @@ function baseRules(isProduction) {
|
|
|
17
20
|
// Disable overly strict rules
|
|
18
21
|
"no-unused-vars": "off",
|
|
19
22
|
// Handled by TypeScript
|
|
20
|
-
"ts/no-unused-vars": [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
"ts/no-unused-vars": [
|
|
24
|
+
"error",
|
|
25
|
+
{
|
|
26
|
+
argsIgnorePattern: "^_",
|
|
27
|
+
varsIgnorePattern: "^_"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
24
30
|
// Allow reasonable complexity
|
|
25
|
-
"complexity": [
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
"complexity": [
|
|
32
|
+
"error",
|
|
33
|
+
{ max: 20 }
|
|
34
|
+
],
|
|
35
|
+
"max-depth": [
|
|
36
|
+
"error",
|
|
37
|
+
{ max: 4 }
|
|
38
|
+
],
|
|
39
|
+
"max-nested-callbacks": [
|
|
40
|
+
"error",
|
|
41
|
+
{ max: 3 }
|
|
42
|
+
],
|
|
28
43
|
// ESLint 10 recommended rules (not yet in @eslint/js v9)
|
|
29
44
|
"no-unassigned-vars": "error",
|
|
30
45
|
"no-useless-assignment": "error",
|
|
31
46
|
"preserve-caught-error": "error",
|
|
32
|
-
// Unicorn
|
|
33
|
-
"unicorn/prefer-global-this": "error",
|
|
34
47
|
// Test
|
|
35
48
|
"test/prefer-lowercase-title": "off",
|
|
36
49
|
// e18e
|
|
@@ -71,6 +84,29 @@ const GLOBAL_IGNORES = [
|
|
|
71
84
|
"**/components.d.ts"
|
|
72
85
|
];
|
|
73
86
|
|
|
87
|
+
const LEVELS = {
|
|
88
|
+
silent: Number.NEGATIVE_INFINITY,
|
|
89
|
+
error: 0,
|
|
90
|
+
warning: 1,
|
|
91
|
+
normal: 2,
|
|
92
|
+
default: 3,
|
|
93
|
+
debug: 4,
|
|
94
|
+
trace: 5,
|
|
95
|
+
verbose: Number.POSITIVE_INFINITY
|
|
96
|
+
};
|
|
97
|
+
function createLogger() {
|
|
98
|
+
const consola = createConsola();
|
|
99
|
+
return {
|
|
100
|
+
setLevel: (level) => {
|
|
101
|
+
consola.level = LEVELS[level];
|
|
102
|
+
},
|
|
103
|
+
info: (msg) => consola.info(msg),
|
|
104
|
+
debug: (msg) => consola.debug(msg),
|
|
105
|
+
verbose: (msg) => consola.verbose(msg),
|
|
106
|
+
box: (opts) => consola.box(opts)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
74
110
|
const markdown = {
|
|
75
111
|
files: ["**/*.md/**"],
|
|
76
112
|
rules: {
|
|
@@ -103,26 +139,202 @@ const sonarjsTestRules = {
|
|
|
103
139
|
"sonarjs/no-duplicate-string": "off"
|
|
104
140
|
};
|
|
105
141
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
142
|
+
const DEFAULT_BASE_FONT_SIZE = 16;
|
|
143
|
+
const DEFAULT_UNIT = "rem";
|
|
144
|
+
const ARBITRARY_BRACKET = /\[(?=([^\s\]]+))\1\]/g;
|
|
145
|
+
const HAS_PX_VALUE = /\dpx/i;
|
|
146
|
+
const PX_VALUE = /(-?(?=(\d+(?:\.\d+)?|\.\d+))\2)px/gi;
|
|
147
|
+
function convertPx(pxStr, base, unit) {
|
|
148
|
+
const px = Number.parseFloat(pxStr);
|
|
149
|
+
const value = px / base;
|
|
150
|
+
const formatted = Number.parseFloat(value.toFixed(6)).toString();
|
|
151
|
+
return `${formatted}${unit}`;
|
|
152
|
+
}
|
|
153
|
+
function findReplacements(input, base, unit) {
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const match of input.matchAll(ARBITRARY_BRACKET)) {
|
|
156
|
+
const full = match[0];
|
|
157
|
+
const inner = match[1];
|
|
158
|
+
if (!HAS_PX_VALUE.test(inner))
|
|
159
|
+
continue;
|
|
160
|
+
const fixedInner = inner.replace(PX_VALUE, (_m, num) => convertPx(num, base, unit));
|
|
161
|
+
out.push({
|
|
162
|
+
original: full,
|
|
163
|
+
fixed: `[${fixedInner}]`,
|
|
164
|
+
index: match.index,
|
|
165
|
+
length: full.length
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
function applyReplacements(input, replacements) {
|
|
171
|
+
let output = "";
|
|
172
|
+
let cursor = 0;
|
|
173
|
+
for (const r of replacements) {
|
|
174
|
+
output += input.slice(cursor, r.index) + r.fixed;
|
|
175
|
+
cursor = r.index + r.length;
|
|
176
|
+
}
|
|
177
|
+
output += input.slice(cursor);
|
|
178
|
+
return output;
|
|
179
|
+
}
|
|
180
|
+
const rule = {
|
|
181
|
+
meta: {
|
|
182
|
+
type: "problem",
|
|
183
|
+
docs: {
|
|
184
|
+
description: "Disallow `px` units inside Tailwind arbitrary value classes; prefer `rem` (or `em`).",
|
|
185
|
+
recommended: false
|
|
186
|
+
},
|
|
187
|
+
fixable: "code",
|
|
188
|
+
schema: [
|
|
189
|
+
{
|
|
190
|
+
type: "object",
|
|
191
|
+
additionalProperties: false,
|
|
192
|
+
properties: {
|
|
193
|
+
baseFontSize: { type: "number", minimum: 1 },
|
|
194
|
+
unit: {
|
|
195
|
+
type: "string",
|
|
196
|
+
enum: ["rem", "em"]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
messages: {
|
|
202
|
+
preferRelativeUnit: "Tailwind class uses px; replace `{{original}}` with `{{fixed}}`."
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
create(context) {
|
|
206
|
+
const options = context.options[0] ?? {};
|
|
207
|
+
const base = options.baseFontSize ?? DEFAULT_BASE_FONT_SIZE;
|
|
208
|
+
const unit = options.unit ?? DEFAULT_UNIT;
|
|
209
|
+
const { sourceCode } = context;
|
|
210
|
+
function check(node) {
|
|
211
|
+
const raw = sourceCode.getText(node);
|
|
212
|
+
const replacements = findReplacements(raw, base, unit);
|
|
213
|
+
if (replacements.length === 0)
|
|
214
|
+
return;
|
|
215
|
+
const fixedRaw = applyReplacements(raw, replacements);
|
|
216
|
+
for (const r of replacements) {
|
|
217
|
+
context.report({
|
|
218
|
+
node,
|
|
219
|
+
messageId: "preferRelativeUnit",
|
|
220
|
+
data: { original: r.original, fixed: r.fixed },
|
|
221
|
+
fix: (fixer) => fixer.replaceText(node, fixedRaw)
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const jsVisitor = {
|
|
226
|
+
Literal(node) {
|
|
227
|
+
if (typeof node.value !== "string")
|
|
228
|
+
return;
|
|
229
|
+
check(node);
|
|
230
|
+
},
|
|
231
|
+
TemplateElement(node) {
|
|
232
|
+
check(node);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const parserServices = context.sourceCode.parserServices;
|
|
236
|
+
if (parserServices.defineTemplateBodyVisitor) {
|
|
237
|
+
return parserServices.defineTemplateBodyVisitor(
|
|
238
|
+
{
|
|
239
|
+
VLiteral: (node) => check(node)
|
|
240
|
+
},
|
|
241
|
+
jsVisitor
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return jsVisitor;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const rules = {
|
|
249
|
+
"tailwind-no-arbitrary-px": rule
|
|
116
250
|
};
|
|
117
251
|
|
|
252
|
+
const mazPlugin = {
|
|
253
|
+
meta: {
|
|
254
|
+
name: "maz"
|
|
255
|
+
},
|
|
256
|
+
rules
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const TAILWINDCSS_FILES = ["**/*.{js,jsx,cjs,mjs,ts,mts,cts,tsx,vue,html,svelte,astro,astrojs,css,scss}"];
|
|
260
|
+
const TAILWINDCSS_DEFAULT_FILES = TAILWINDCSS_FILES;
|
|
261
|
+
function resolveNoArbitraryPx(setting) {
|
|
262
|
+
if (setting === false) {
|
|
263
|
+
return {
|
|
264
|
+
severity: "off",
|
|
265
|
+
options: { baseFontSize: 16, unit: "rem" }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (setting === void 0 || setting === true) {
|
|
269
|
+
return {
|
|
270
|
+
severity: "error",
|
|
271
|
+
options: { baseFontSize: 16, unit: "rem" }
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
severity: setting.severity ?? "error",
|
|
276
|
+
options: {
|
|
277
|
+
baseFontSize: setting.baseFontSize ?? 16,
|
|
278
|
+
unit: setting.unit ?? "rem"
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function tailwindcssConfigs(preset, settings) {
|
|
283
|
+
const presetConfig = betterTailwindcss.configs[preset];
|
|
284
|
+
const tailwindSettings = {};
|
|
285
|
+
if (settings.entryPoint)
|
|
286
|
+
tailwindSettings.entryPoint = settings.entryPoint;
|
|
287
|
+
if (settings.tailwindConfig)
|
|
288
|
+
tailwindSettings.tailwindConfig = settings.tailwindConfig;
|
|
289
|
+
if (settings.detectComponentClasses !== void 0)
|
|
290
|
+
tailwindSettings.detectComponentClasses = settings.detectComponentClasses;
|
|
291
|
+
if (settings.cwd)
|
|
292
|
+
tailwindSettings.cwd = settings.cwd;
|
|
293
|
+
if (settings.tsconfig)
|
|
294
|
+
tailwindSettings.tsconfig = settings.tsconfig;
|
|
295
|
+
const noArbitraryPx = resolveNoArbitraryPx(settings.noArbitraryPx);
|
|
296
|
+
return [
|
|
297
|
+
{
|
|
298
|
+
...presetConfig,
|
|
299
|
+
files: TAILWINDCSS_FILES,
|
|
300
|
+
plugins: {
|
|
301
|
+
...presetConfig.plugins,
|
|
302
|
+
maz: mazPlugin
|
|
303
|
+
},
|
|
304
|
+
settings: {
|
|
305
|
+
"better-tailwindcss": tailwindSettings
|
|
306
|
+
},
|
|
307
|
+
rules: {
|
|
308
|
+
...presetConfig.rules,
|
|
309
|
+
"better-tailwindcss/no-unknown-classes": "off",
|
|
310
|
+
"better-tailwindcss/enforce-consistent-line-wrapping": "off",
|
|
311
|
+
"better-tailwindcss/no-concatenated-classes": "off",
|
|
312
|
+
"maz/tailwind-no-arbitrary-px": [noArbitraryPx.severity, noArbitraryPx.options]
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
118
318
|
const testRules = {
|
|
119
319
|
"max-nested-callbacks": "off"
|
|
120
320
|
};
|
|
121
321
|
|
|
322
|
+
const unicorn = {
|
|
323
|
+
files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
|
|
324
|
+
rules: {
|
|
325
|
+
"unicorn/prefer-global-this": "error"
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
122
329
|
const vueRules = {
|
|
123
330
|
"vue/custom-event-name-casing": ["error", "kebab-case"]
|
|
124
331
|
};
|
|
332
|
+
const vueSfcOnlyRules = {
|
|
333
|
+
"no-useless-assignment": "off"
|
|
334
|
+
};
|
|
125
335
|
|
|
336
|
+
process.env.SYNCKIT_TIMEOUT ??= "120000";
|
|
337
|
+
const TAG = "[@maz-ui/eslint-config]";
|
|
126
338
|
const defaultOptions = {
|
|
127
339
|
formatters: true,
|
|
128
340
|
typescript: true,
|
|
@@ -139,39 +351,129 @@ function getPackageJson() {
|
|
|
139
351
|
return void 0;
|
|
140
352
|
}
|
|
141
353
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
} catch {
|
|
148
|
-
return 0;
|
|
149
|
-
}
|
|
354
|
+
function hasDependency(pkg, ...names) {
|
|
355
|
+
if (!pkg)
|
|
356
|
+
return false;
|
|
357
|
+
const buckets = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
|
|
358
|
+
return names.some((name) => buckets.some((bucket) => bucket && name in bucket));
|
|
150
359
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
360
|
+
function resolveVue(option) {
|
|
361
|
+
if (option === true)
|
|
362
|
+
return { value: true, source: "explicit" };
|
|
363
|
+
if (option === false)
|
|
364
|
+
return { value: false, source: "explicit" };
|
|
365
|
+
return {
|
|
366
|
+
value: hasDependency(getPackageJson(), "vue", "nuxt"),
|
|
367
|
+
source: "auto-detected"
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function resolveTailwindcss(option) {
|
|
371
|
+
if (option === void 0)
|
|
372
|
+
return { value: false, source: "default" };
|
|
373
|
+
if (option === false)
|
|
374
|
+
return { value: false, source: "explicit" };
|
|
375
|
+
if (option === true) {
|
|
376
|
+
return {
|
|
377
|
+
value: { preset: "recommended", settings: {} },
|
|
378
|
+
source: "explicit"
|
|
379
|
+
};
|
|
158
380
|
}
|
|
381
|
+
if (typeof option === "string") {
|
|
382
|
+
return {
|
|
383
|
+
value: { preset: option, settings: {} },
|
|
384
|
+
source: "explicit"
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
value: { preset: option.preset ?? "recommended", settings: option },
|
|
389
|
+
source: "explicit"
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function resolveOptions(opts, env) {
|
|
393
|
+
const vue = resolveVue(opts.vue);
|
|
394
|
+
const tailwind = resolveTailwindcss(opts.tailwindcss);
|
|
395
|
+
return {
|
|
396
|
+
typescript: opts.typescript !== false,
|
|
397
|
+
formatters: opts.formatters !== false,
|
|
398
|
+
unicorn: opts.unicorn !== false,
|
|
399
|
+
sonarjs: opts.sonarjs !== false,
|
|
400
|
+
vue: vue.value,
|
|
401
|
+
vueSource: vue.source,
|
|
402
|
+
vueAccessibility: opts.vueAccessibility === true,
|
|
403
|
+
tailwindcss: tailwind.value,
|
|
404
|
+
tailwindcssSource: tailwind.source,
|
|
405
|
+
env
|
|
406
|
+
};
|
|
159
407
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
408
|
+
function formatSourceTag(source) {
|
|
409
|
+
if (source === "auto-detected")
|
|
410
|
+
return " (auto-detected)";
|
|
411
|
+
if (source === "default")
|
|
412
|
+
return " (default)";
|
|
413
|
+
return "";
|
|
414
|
+
}
|
|
415
|
+
function formatResolutionBox(r) {
|
|
416
|
+
const tailwindLabel = r.tailwindcss === false ? "off" : r.tailwindcss.preset;
|
|
417
|
+
const rows = [
|
|
418
|
+
[
|
|
419
|
+
"typescript",
|
|
420
|
+
String(r.typescript)
|
|
421
|
+
],
|
|
422
|
+
["vue", `${r.vue}${formatSourceTag(r.vueSource)}`],
|
|
423
|
+
[
|
|
424
|
+
"vueAccessibility",
|
|
425
|
+
String(r.vueAccessibility)
|
|
426
|
+
],
|
|
427
|
+
[
|
|
428
|
+
"sonarjs",
|
|
429
|
+
String(r.sonarjs)
|
|
430
|
+
],
|
|
431
|
+
["tailwindcss", `${tailwindLabel}${formatSourceTag(r.tailwindcssSource)}`],
|
|
432
|
+
[
|
|
433
|
+
"formatters",
|
|
434
|
+
String(r.formatters)
|
|
435
|
+
],
|
|
436
|
+
[
|
|
437
|
+
"unicorn",
|
|
438
|
+
String(r.unicorn)
|
|
439
|
+
],
|
|
440
|
+
["env", r.env]
|
|
441
|
+
];
|
|
442
|
+
const labelWidth = Math.max(...rows.map(([k]) => k.length));
|
|
443
|
+
return rows.map(([k, v]) => `${k.padEnd(labelWidth)} ${v}`).join("\n");
|
|
163
444
|
}
|
|
164
445
|
function defineConfig(options = {}, ...userConfigs) {
|
|
165
|
-
const
|
|
446
|
+
const log = createLogger();
|
|
447
|
+
const defaultLogLevel = process.stdout.isTTY ? "default" : "silent";
|
|
448
|
+
log.setLevel(options.logLevel ?? defaultLogLevel);
|
|
449
|
+
const opts = {
|
|
450
|
+
...defaultOptions,
|
|
451
|
+
...options,
|
|
452
|
+
ignores: [
|
|
453
|
+
...GLOBAL_IGNORES,
|
|
454
|
+
...options.ignores || []
|
|
455
|
+
]
|
|
456
|
+
};
|
|
166
457
|
const env = opts.env || process.env.NODE_ENV || "production";
|
|
458
|
+
const resolved = resolveOptions(opts, env);
|
|
459
|
+
log.box({
|
|
460
|
+
title: "@maz-ui/eslint-config",
|
|
461
|
+
message: formatResolutionBox(resolved),
|
|
462
|
+
style: { borderColor: "cyan", padding: 1 }
|
|
463
|
+
});
|
|
167
464
|
const additionalConfigs = [];
|
|
168
|
-
if (
|
|
465
|
+
if (resolved.vue) {
|
|
169
466
|
additionalConfigs.push({
|
|
170
467
|
files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
|
|
171
468
|
rules: vueRules
|
|
172
469
|
});
|
|
470
|
+
additionalConfigs.push({
|
|
471
|
+
files: ["**/*.vue"],
|
|
472
|
+
rules: vueSfcOnlyRules
|
|
473
|
+
});
|
|
474
|
+
log.debug(`${TAG} Vue: applied vueRules to JS/TS/Vue files + SFC-only overrides to *.vue`);
|
|
173
475
|
}
|
|
174
|
-
if (
|
|
476
|
+
if (resolved.sonarjs) {
|
|
175
477
|
const sonarjsFiles = ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"];
|
|
176
478
|
additionalConfigs.push({
|
|
177
479
|
...configs.recommended,
|
|
@@ -182,11 +484,21 @@ function defineConfig(options = {}, ...userConfigs) {
|
|
|
182
484
|
}
|
|
183
485
|
});
|
|
184
486
|
additionalConfigs.push({
|
|
185
|
-
files: [
|
|
487
|
+
files: [
|
|
488
|
+
"**/*.spec.ts",
|
|
489
|
+
"**/*.test.ts",
|
|
490
|
+
"**/*.spec.js",
|
|
491
|
+
"**/*.test.js"
|
|
492
|
+
],
|
|
186
493
|
rules: sonarjsTestRules
|
|
187
494
|
});
|
|
495
|
+
log.debug(`${TAG} SonarJS: applied recommended preset + ${Object.keys(sonarjsRules).length} extra rules + test-file relaxations`);
|
|
496
|
+
}
|
|
497
|
+
if (resolved.unicorn) {
|
|
498
|
+
additionalConfigs.push(unicorn);
|
|
499
|
+
log.debug(`${TAG} Unicorn: applied JS/TS-scoped rule overrides`);
|
|
188
500
|
}
|
|
189
|
-
if (
|
|
501
|
+
if (resolved.vueAccessibility) {
|
|
190
502
|
const vueA11yConfigs = vueA11y.configs["flat/recommended"];
|
|
191
503
|
const configsArray = Array.isArray(vueA11yConfigs) ? vueA11yConfigs : [vueA11yConfigs];
|
|
192
504
|
const fixedConfigs = configsArray.map((config) => {
|
|
@@ -207,39 +519,40 @@ function defineConfig(options = {}, ...userConfigs) {
|
|
|
207
519
|
return config;
|
|
208
520
|
});
|
|
209
521
|
additionalConfigs.push(...fixedConfigs);
|
|
522
|
+
log.debug(`${TAG} Vue a11y: applied ${fixedConfigs.length} flat-config block(s) from eslint-plugin-vuejs-accessibility`);
|
|
210
523
|
}
|
|
211
|
-
if (
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (!tailwindPlugin) {
|
|
218
|
-
console.warn("[maz-eslint-config] opts.tailwindcss is true but eslint-plugin-tailwindcss is not installed. Add it to your devDependencies to enable the Tailwind CSS lint rules.");
|
|
219
|
-
} else {
|
|
220
|
-
const tailwindConfigs = tailwindPlugin.configs?.["flat/recommended"];
|
|
221
|
-
if (Array.isArray(tailwindConfigs)) {
|
|
222
|
-
additionalConfigs.push(...tailwindConfigs);
|
|
223
|
-
}
|
|
224
|
-
additionalConfigs.push({ rules: tailwindcssRules });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
524
|
+
if (resolved.tailwindcss) {
|
|
525
|
+
const blocks = tailwindcssConfigs(resolved.tailwindcss.preset, resolved.tailwindcss.settings);
|
|
526
|
+
additionalConfigs.push(...blocks);
|
|
527
|
+
const settingKeys = Object.keys(resolved.tailwindcss.settings).filter((k) => k !== "preset");
|
|
528
|
+
const settingsHint = settingKeys.length > 0 ? ` with settings (${settingKeys.join(", ")})` : "";
|
|
529
|
+
log.debug(`${TAG} Tailwind: loaded "${resolved.tailwindcss.preset}" preset${settingsHint}`);
|
|
227
530
|
}
|
|
228
531
|
additionalConfigs.push({
|
|
229
|
-
files: [
|
|
532
|
+
files: [
|
|
533
|
+
"**/*.spec.ts",
|
|
534
|
+
"**/*.test.ts",
|
|
535
|
+
"**/*.spec.js",
|
|
536
|
+
"**/*.test.js"
|
|
537
|
+
],
|
|
230
538
|
rules: testRules
|
|
231
539
|
});
|
|
540
|
+
log.debug(`${TAG} Tests: relaxed rules applied to *.spec.{ts,js} / *.test.{ts,js}`);
|
|
541
|
+
if (options.rules && Object.keys(options.rules).length > 0)
|
|
542
|
+
log.debug(`${TAG} User: merged ${Object.keys(options.rules).length} rule override(s)`);
|
|
543
|
+
if (userConfigs.length > 0)
|
|
544
|
+
log.debug(`${TAG} User: appended ${userConfigs.length} flat-config argument(s)`);
|
|
545
|
+
log.verbose(`${TAG} Final config block count: ${additionalConfigs.length + userConfigs.length + 1} (additional: ${additionalConfigs.length}, user: ${userConfigs.length}, +markdown)`);
|
|
546
|
+
log.verbose(`${TAG} Ignore globs: ${opts.ignores.length}`);
|
|
547
|
+
const userRulesBlock = opts.rules && Object.keys(opts.rules).length > 0 ? { rules: opts.rules } : void 0;
|
|
232
548
|
return antfu({
|
|
233
549
|
formatters: opts.formatters,
|
|
234
550
|
...opts,
|
|
235
|
-
rules:
|
|
236
|
-
...baseRules(env === "production"),
|
|
237
|
-
...opts.rules
|
|
238
|
-
},
|
|
551
|
+
rules: baseRules(env === "production"),
|
|
239
552
|
ignores: (() => {
|
|
240
553
|
return opts.ignores;
|
|
241
554
|
})
|
|
242
|
-
}, ...additionalConfigs, ...userConfigs, markdown);
|
|
555
|
+
}, ...additionalConfigs, ...userConfigs, ...userRulesBlock ? [userRulesBlock] : [], markdown);
|
|
243
556
|
}
|
|
244
557
|
|
|
245
|
-
export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules,
|
|
558
|
+
export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, mazPlugin, rules as mazRules, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maz-ui/eslint-config",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.3.0",
|
|
5
5
|
"description": "ESLint configuration for JavaScript/TypeScript projects",
|
|
6
6
|
"author": "Louis Mazel <me@loicmazuel.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"exports": {
|
|
26
26
|
".": {
|
|
27
27
|
"types": "./dist/index.d.ts",
|
|
28
|
-
"import": "./dist/index.mjs"
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"default": "./dist/index.mjs"
|
|
29
30
|
}
|
|
30
31
|
},
|
|
31
32
|
"main": "./dist/index.mjs",
|
|
@@ -36,28 +37,29 @@
|
|
|
36
37
|
"dist"
|
|
37
38
|
],
|
|
38
39
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
40
|
+
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
|
-
"eslint": "
|
|
43
|
-
"eslint-plugin-tailwindcss": "^3.18.0"
|
|
44
|
-
},
|
|
45
|
-
"peerDependenciesMeta": {
|
|
46
|
-
"eslint-plugin-tailwindcss": {
|
|
47
|
-
"optional": true
|
|
48
|
-
}
|
|
43
|
+
"eslint": "^9.10.0 || ^10.0.0"
|
|
49
44
|
},
|
|
50
45
|
"dependencies": {
|
|
51
|
-
"@antfu/eslint-config": "^
|
|
46
|
+
"@antfu/eslint-config": "^10.0.0",
|
|
47
|
+
"consola": "^3.4.2",
|
|
48
|
+
"eslint-plugin-better-tailwindcss": "^4.10.0",
|
|
52
49
|
"eslint-plugin-format": "^2.0.1",
|
|
53
|
-
"eslint-plugin-sonarjs": "^4.
|
|
54
|
-
"eslint-plugin-vuejs-accessibility": "^2.
|
|
50
|
+
"eslint-plugin-sonarjs": "^4.2.2",
|
|
51
|
+
"eslint-plugin-vuejs-accessibility": "^2.6.3"
|
|
55
52
|
},
|
|
56
53
|
"devDependencies": {
|
|
57
|
-
"@types/
|
|
58
|
-
"eslint": "^
|
|
59
|
-
"
|
|
60
|
-
"
|
|
54
|
+
"@types/node": "^26.6.6",
|
|
55
|
+
"@typescript-eslint/rule-tester": "^8.72.1",
|
|
56
|
+
"@vitest/coverage-v8": "^5.1.0",
|
|
57
|
+
"cross-env": "^10.1.0",
|
|
58
|
+
"eslint": "^10.14.0",
|
|
59
|
+
"typescript": "^6.0.3",
|
|
60
|
+
"unbuild": "^3.6.1",
|
|
61
|
+
"vitest": "5.1.0",
|
|
62
|
+
"vue-eslint-parser": "^10.4.1"
|
|
61
63
|
},
|
|
62
64
|
"lint-staged": {
|
|
63
65
|
"*.{js,jsx,ts,tsx,mjs,mts,cjs,md,yml,json}": [
|
|
@@ -65,11 +67,13 @@
|
|
|
65
67
|
]
|
|
66
68
|
},
|
|
67
69
|
"scripts": {
|
|
68
|
-
"codegen": "nx build",
|
|
69
70
|
"build": "unbuild",
|
|
70
71
|
"dev": "unbuild --stub",
|
|
71
72
|
"typecheck": "tsc --noEmit --skipLibCheck",
|
|
72
73
|
"lint": "cross-env NODE_ENV=production eslint .",
|
|
73
|
-
"lint:fix": "pnpm lint --fix"
|
|
74
|
+
"lint:fix": "pnpm lint --fix",
|
|
75
|
+
"test:unit": "vitest run",
|
|
76
|
+
"test:unit:coverage": "vitest run --coverage",
|
|
77
|
+
"test:unit:watch": "vitest watch"
|
|
74
78
|
}
|
|
75
79
|
}
|