@quentinhsu/biome-config 0.1.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.
Files changed (32) hide show
  1. package/README.md +138 -0
  2. package/dist/build.mjs +381 -0
  3. package/dist/index.jsonc +146 -0
  4. package/dist/index.mjs +351 -0
  5. package/dist/next.jsonc +163 -0
  6. package/dist/nuxt.jsonc +218 -0
  7. package/dist/react.jsonc +161 -0
  8. package/dist/types/scripts/generate-biome-types.d.ts +1 -0
  9. package/dist/types/src/build.d.ts +1 -0
  10. package/dist/types/src/constants/biome.d.ts +1 -0
  11. package/dist/types/src/generated/biome/index.d.ts +11 -0
  12. package/dist/types/src/generated/biome/no-compare-neg-zero-configuration.d.ts +986 -0
  13. package/dist/types/src/generated/biome/no-global-object-calls-options.d.ts +306 -0
  14. package/dist/types/src/generated/biome/no-nested-ternary-options.d.ts +292 -0
  15. package/dist/types/src/generated/biome/no-octal-escape-options.d.ts +1138 -0
  16. package/dist/types/src/generated/biome/no-shadow-configuration.d.ts +241 -0
  17. package/dist/types/src/generated/biome/nursery.d.ts +1023 -0
  18. package/dist/types/src/generated/biome/rule-with-no-duplicate-custom-properties-options.d.ts +1095 -0
  19. package/dist/types/src/generated/biome/rule-with-no-useless-catch-options.d.ts +1430 -0
  20. package/dist/types/src/generated/biome/rule-with-use-image-size-options.d.ts +1339 -0
  21. package/dist/types/src/generated/biome/schema.d.ts +291 -0
  22. package/dist/types/src/generated/biome/use-valid-lang-configuration.d.ts +163 -0
  23. package/dist/types/src/index.d.ts +15 -0
  24. package/dist/types/src/presets/next.d.ts +1 -0
  25. package/dist/types/src/presets/nuxt.d.ts +1 -0
  26. package/dist/types/src/presets/react.d.ts +1 -0
  27. package/dist/types/src/presets/vue.d.ts +1 -0
  28. package/dist/types/src/source/index.d.ts +2 -0
  29. package/dist/types/src/types.d.ts +1 -0
  30. package/dist/types/src/utils/merge.d.ts +2 -0
  31. package/dist/vue.jsonc +170 -0
  32. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @quentinhsu/biome-config
2
+
3
+ A modular Biome configuration preset collection built with Rslib. Includes a comprehensive base configuration with framework-specific overlays (React, Next.js, Vue, Nuxt), ultimately generating `biome.jsonc` files in the `dist/` directory that can be directly extended in projects.
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm build
10
+ ```
11
+
12
+ The build process first compiles TypeScript modules to `dist/` via Rslib, then executes `dist/build.mjs` to output each preset as a JSONC file.
13
+
14
+ ## Available Presets
15
+
16
+ | Preset | Description | Export |
17
+ | --- | --- | --- |
18
+ | `.` (index) | Base configuration with recommended rules | `"extends": ["@quentinhsu/biome-config"]` |
19
+ | `./react` | React-specific rules and overrides | `"extends": ["@quentinhsu/biome-config/react"]` |
20
+ | `./next` | Next.js-specific rules and file exclusions | `"extends": ["@quentinhsu/biome-config/next"]` |
21
+ | `./vue` | Vue-specific rules and configuration | `"extends": ["@quentinhsu/biome-config/vue"]` |
22
+ | `./nuxt` | Nuxt-specific rules and configuration | `"extends": ["@quentinhsu/biome-config/nuxt"]` |
23
+
24
+ Each preset inherits from and extends the base configuration with framework-specific file patterns, globals, and linter rules.
25
+
26
+ ## Usage
27
+
28
+ ### Installation
29
+
30
+ Install this package in your project:
31
+
32
+ ```bash
33
+ pnpm add -D @quentinhsu/biome-config
34
+ ```
35
+
36
+ ### Basic Setup
37
+
38
+ Create or update your `biome.json` (or `biome.jsonc`) file to use the base configuration:
39
+
40
+ ```json
41
+ {
42
+ "extends": ["@quentinhsu/biome-config"]
43
+ }
44
+ ```
45
+
46
+ ### Framework-Specific Configuration
47
+
48
+ #### React Projects
49
+
50
+ ```json
51
+ {
52
+ "extends": ["@quentinhsu/biome-config/react"]
53
+ }
54
+ ```
55
+
56
+ The React preset includes:
57
+ - Fragment syntax enforcement
58
+ - JSX quote style configuration
59
+ - Test file rule overrides
60
+ - Storybook exclusion
61
+
62
+ #### Next.js Projects
63
+
64
+ ```json
65
+ {
66
+ "extends": ["@quentinhsu/biome-config/next"]
67
+ }
68
+ ```
69
+
70
+ The Next.js preset extends React rules with Next.js-specific patterns.
71
+
72
+ #### Vue Projects
73
+
74
+ ```json
75
+ {
76
+ "extends": ["@quentinhsu/biome-config/vue"]
77
+ }
78
+ ```
79
+
80
+ #### Nuxt Projects
81
+
82
+ ```json
83
+ {
84
+ "extends": ["@quentinhsu/biome-config/nuxt"]
85
+ }
86
+ ```
87
+
88
+ ### Customization
89
+
90
+ You can override or extend any preset rules in your own `biome.json`:
91
+
92
+ ```json
93
+ {
94
+ "extends": ["@quentinhsu/biome-config/react"],
95
+ "linter": {
96
+ "rules": {
97
+ "style": {
98
+ "useFragmentSyntax": "warn"
99
+ }
100
+ }
101
+ },
102
+ "formatter": {
103
+ "lineWidth": 120
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Base Configuration Features
109
+
110
+ The base configuration includes:
111
+
112
+ - **Formatter**: Space indentation, 140-character line width
113
+ - **Linter**: Recommended rules with strict complexity and correctness checks
114
+ - **JavaScript**: Single quotes, no arrow parentheses, trailing commas
115
+ - **Imports**: Organized by node modules, packages, aliases, and relative paths
116
+ - **VCS**: Git integration with `.gitignore` support
117
+ - **Files**: Ignores common output directories (`build`, `dist`, `.next`)
118
+
119
+ ### Running Biome
120
+
121
+ Once configured, you can use Biome commands:
122
+
123
+ ```bash
124
+ # Format files
125
+ pnpm biome format . --write
126
+
127
+ # Lint files
128
+ pnpm biome lint . --write
129
+
130
+ # Check formatting and linting
131
+ pnpm biome check .
132
+
133
+ # Fix lint issues automatically
134
+ pnpm biome lint . --fix
135
+ ```
136
+
137
+
138
+
package/dist/build.mjs ADDED
@@ -0,0 +1,381 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const isPlainObject = (value)=>null !== value && 'object' == typeof value && !Array.isArray(value);
5
+ const mergeArrays = (a, b)=>{
6
+ const result = [
7
+ ...a
8
+ ];
9
+ for (const item of b)if (!result.some((existing)=>deepEqual(existing, item))) result.push(item);
10
+ return result;
11
+ };
12
+ const deepEqual = (a, b)=>{
13
+ if (a === b) return true;
14
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, index)=>deepEqual(item, b[index]));
15
+ if (isPlainObject(a) && isPlainObject(b)) {
16
+ const keysA = Object.keys(a);
17
+ const keysB = Object.keys(b);
18
+ return keysA.length === keysB.length && keysA.every((key)=>deepEqual(a[key], b[key]));
19
+ }
20
+ return false;
21
+ };
22
+ const deepMerge = (target, source)=>{
23
+ for (const [key, value] of Object.entries(source)){
24
+ const current = target[key];
25
+ if (Array.isArray(value)) {
26
+ if (Array.isArray(current)) target[key] = mergeArrays(current, value);
27
+ else target[key] = [
28
+ ...value
29
+ ];
30
+ continue;
31
+ }
32
+ if (isPlainObject(value)) {
33
+ const nextTarget = isPlainObject(current) ? {
34
+ ...current
35
+ } : {};
36
+ target[key] = deepMerge(nextTarget, value);
37
+ continue;
38
+ }
39
+ target[key] = value;
40
+ }
41
+ return target;
42
+ };
43
+ const mergeConfigs = (...configs)=>{
44
+ const merged = configs.reduce((accumulator, config)=>deepMerge(accumulator, config), {});
45
+ return merged;
46
+ };
47
+ const BIOME_SCHEMA_URL = 'https://biomejs.dev/schemas/2.2.6/schema.json';
48
+ const indexConfig = {
49
+ $schema: BIOME_SCHEMA_URL,
50
+ root: true,
51
+ vcs: {
52
+ enabled: true,
53
+ clientKind: 'git',
54
+ useIgnoreFile: true,
55
+ defaultBranch: 'main'
56
+ },
57
+ files: {
58
+ ignoreUnknown: true,
59
+ includes: [
60
+ '**',
61
+ '!**/build',
62
+ '!**/dist',
63
+ '!**/.next'
64
+ ]
65
+ },
66
+ formatter: {
67
+ enabled: true,
68
+ indentStyle: 'space',
69
+ lineWidth: 140,
70
+ formatWithErrors: true
71
+ },
72
+ assist: {
73
+ actions: {
74
+ source: {
75
+ organizeImports: {
76
+ level: 'on',
77
+ options: {
78
+ groups: [
79
+ [
80
+ ':NODE:',
81
+ ':BUN:',
82
+ ':PACKAGE_WITH_PROTOCOL:',
83
+ ':PACKAGE:'
84
+ ],
85
+ ':BLANK_LINE:',
86
+ ':ALIAS:',
87
+ ':BLANK_LINE:',
88
+ ':PATH:'
89
+ ]
90
+ }
91
+ }
92
+ }
93
+ }
94
+ },
95
+ linter: {
96
+ enabled: true,
97
+ rules: {
98
+ recommended: true,
99
+ complexity: {
100
+ noUselessStringConcat: 'error',
101
+ noUselessUndefinedInitialization: 'error',
102
+ noVoid: 'error',
103
+ useDateNow: 'error'
104
+ },
105
+ correctness: {
106
+ noConstantMathMinMaxClamp: 'error',
107
+ noUndeclaredVariables: 'error',
108
+ noUnusedImports: 'error',
109
+ noUnusedFunctionParameters: 'error',
110
+ noUnusedPrivateClassMembers: 'error',
111
+ useExhaustiveDependencies: {
112
+ level: 'error',
113
+ options: {
114
+ reportUnnecessaryDependencies: false
115
+ }
116
+ },
117
+ noUnusedVariables: 'error'
118
+ },
119
+ style: {
120
+ noParameterProperties: 'error',
121
+ noYodaExpression: 'error',
122
+ useConsistentBuiltinInstantiation: 'error',
123
+ useFragmentSyntax: 'error',
124
+ useImportType: {
125
+ level: 'error',
126
+ fix: 'safe',
127
+ options: {
128
+ style: 'separatedType'
129
+ }
130
+ },
131
+ useSelfClosingElements: {
132
+ level: 'error',
133
+ fix: 'safe',
134
+ options: {}
135
+ },
136
+ useShorthandAssign: 'error',
137
+ useArrayLiterals: 'error'
138
+ },
139
+ nursery: {
140
+ useSortedClasses: {
141
+ level: 'error',
142
+ fix: 'safe',
143
+ options: {
144
+ functions: [
145
+ 'clsx',
146
+ 'cn'
147
+ ]
148
+ }
149
+ }
150
+ },
151
+ suspicious: {
152
+ useAwait: 'error',
153
+ noEvolvingTypes: 'error'
154
+ }
155
+ }
156
+ },
157
+ javascript: {
158
+ formatter: {
159
+ quoteStyle: 'single',
160
+ jsxQuoteStyle: 'single',
161
+ arrowParentheses: 'asNeeded',
162
+ trailingCommas: 'all'
163
+ }
164
+ },
165
+ overrides: [
166
+ {
167
+ includes: [
168
+ '**/*.jsx',
169
+ '**/*.tsx'
170
+ ],
171
+ linter: {
172
+ rules: {
173
+ style: {
174
+ noParameterAssign: 'error'
175
+ }
176
+ }
177
+ }
178
+ },
179
+ {
180
+ includes: [
181
+ '**/*.ts',
182
+ '**/*.tsx'
183
+ ],
184
+ linter: {
185
+ rules: {
186
+ correctness: {
187
+ noUnusedVariables: 'off'
188
+ }
189
+ }
190
+ }
191
+ }
192
+ ]
193
+ };
194
+ const reactOverlay = {
195
+ files: {
196
+ includes: [
197
+ '!**/.storybook'
198
+ ]
199
+ },
200
+ javascript: {
201
+ jsxRuntime: 'reactClassic'
202
+ },
203
+ linter: {
204
+ rules: {
205
+ style: {
206
+ useFragmentSyntax: 'error'
207
+ }
208
+ }
209
+ },
210
+ overrides: [
211
+ {
212
+ includes: [
213
+ '**/__tests__/**',
214
+ '**/*.{test,spec}.{ts,tsx,js,jsx}'
215
+ ],
216
+ linter: {
217
+ rules: {
218
+ correctness: {
219
+ noUnusedVariables: 'off'
220
+ }
221
+ }
222
+ }
223
+ }
224
+ ]
225
+ };
226
+ const reactConfig = mergeConfigs(indexConfig, reactOverlay);
227
+ const nextOverlay = {
228
+ files: {
229
+ includes: [
230
+ '!**/.next',
231
+ '!**/.vercel',
232
+ '!**/out'
233
+ ]
234
+ },
235
+ javascript: {
236
+ jsxRuntime: 'transparent'
237
+ },
238
+ linter: {
239
+ rules: {
240
+ correctness: {
241
+ useExhaustiveDependencies: {
242
+ level: 'error',
243
+ options: {
244
+ reportUnnecessaryDependencies: true
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+ };
251
+ const nextConfig = mergeConfigs(reactConfig, nextOverlay);
252
+ const vueOverlay = {
253
+ files: {
254
+ includes: [
255
+ '!**/.vitepress',
256
+ '!**/.output'
257
+ ]
258
+ },
259
+ javascript: {
260
+ parser: {
261
+ jsxEverywhere: false
262
+ }
263
+ },
264
+ html: {
265
+ formatter: {
266
+ indentScriptAndStyle: true,
267
+ selfCloseVoidElements: 'always'
268
+ }
269
+ },
270
+ overrides: [
271
+ {
272
+ includes: [
273
+ '**/*.vue'
274
+ ],
275
+ formatter: {
276
+ lineWidth: 120
277
+ },
278
+ javascript: {
279
+ formatter: {
280
+ quoteStyle: 'single'
281
+ }
282
+ }
283
+ }
284
+ ]
285
+ };
286
+ const vueConfig = mergeConfigs(indexConfig, vueOverlay);
287
+ const nuxtOverlay = {
288
+ files: {
289
+ includes: [
290
+ '!**/.nuxt',
291
+ '!**/.nitro',
292
+ '!**/.output'
293
+ ]
294
+ },
295
+ javascript: {
296
+ globals: [
297
+ 'defineNuxtConfig',
298
+ 'defineAppConfig',
299
+ 'defineNuxtPlugin',
300
+ 'defineNuxtRouteMiddleware',
301
+ 'defineNuxtServerPlugin',
302
+ 'defineNitroPlugin',
303
+ 'defineEventHandler',
304
+ 'defineLazyEventHandler',
305
+ 'definePayloadPlugin',
306
+ 'defineRouteRules',
307
+ 'definePageMeta',
308
+ 'useRuntimeConfig',
309
+ 'useNuxtApp',
310
+ 'useAsyncData',
311
+ 'useLazyAsyncData',
312
+ 'useFetch',
313
+ 'useLazyFetch',
314
+ 'useState',
315
+ 'useCookie',
316
+ 'useHead',
317
+ 'useSeoMeta',
318
+ 'useError',
319
+ 'clearError',
320
+ 'showError',
321
+ 'navigateTo',
322
+ 'abortNavigation',
323
+ 'refreshNuxtData',
324
+ 'onNuxtReady',
325
+ 'useRouter',
326
+ 'useRoute',
327
+ 'useRequestEvent',
328
+ 'useRequestHeaders'
329
+ ]
330
+ },
331
+ overrides: [
332
+ {
333
+ includes: [
334
+ '**/*.ts'
335
+ ],
336
+ linter: {
337
+ rules: {
338
+ correctness: {
339
+ noUndeclaredVariables: 'error'
340
+ }
341
+ }
342
+ }
343
+ }
344
+ ]
345
+ };
346
+ const nuxtConfig = mergeConfigs(vueConfig, nuxtOverlay);
347
+ const allPresets = Object.freeze({
348
+ index: indexConfig,
349
+ react: reactConfig,
350
+ next: nextConfig,
351
+ vue: vueConfig,
352
+ nuxt: nuxtConfig
353
+ });
354
+ const currentDir = dirname(fileURLToPath(import.meta.url));
355
+ const outputDir = currentDir;
356
+ const serialize = (config)=>`${JSON.stringify(config, null, 2)}\n`;
357
+ async function ensureDir(path) {
358
+ await mkdir(path, {
359
+ recursive: true
360
+ });
361
+ }
362
+ async function buildJsonPresets() {
363
+ await ensureDir(outputDir);
364
+ const tasks = Object.entries(allPresets).map(async ([name, config])=>{
365
+ const filePath = join(outputDir, `${name}.jsonc`);
366
+ const data = serialize(config);
367
+ await writeFile(filePath, data, 'utf8');
368
+ return filePath;
369
+ });
370
+ const writtenFiles = await Promise.all(tasks);
371
+ return writtenFiles;
372
+ }
373
+ (async ()=>{
374
+ try {
375
+ const files = await buildJsonPresets();
376
+ console.log(`Generated presets: ${files.join(', ')}`);
377
+ } catch (error) {
378
+ console.error('Failed to generate Biome presets', error);
379
+ process.exitCode = 1;
380
+ }
381
+ })();
@@ -0,0 +1,146 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.2.6/schema.json",
3
+ "root": true,
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": true,
8
+ "defaultBranch": "main"
9
+ },
10
+ "files": {
11
+ "ignoreUnknown": true,
12
+ "includes": [
13
+ "**",
14
+ "!**/build",
15
+ "!**/dist",
16
+ "!**/.next"
17
+ ]
18
+ },
19
+ "formatter": {
20
+ "enabled": true,
21
+ "indentStyle": "space",
22
+ "lineWidth": 140,
23
+ "formatWithErrors": true
24
+ },
25
+ "assist": {
26
+ "actions": {
27
+ "source": {
28
+ "organizeImports": {
29
+ "level": "on",
30
+ "options": {
31
+ "groups": [
32
+ [
33
+ ":NODE:",
34
+ ":BUN:",
35
+ ":PACKAGE_WITH_PROTOCOL:",
36
+ ":PACKAGE:"
37
+ ],
38
+ ":BLANK_LINE:",
39
+ ":ALIAS:",
40
+ ":BLANK_LINE:",
41
+ ":PATH:"
42
+ ]
43
+ }
44
+ }
45
+ }
46
+ }
47
+ },
48
+ "linter": {
49
+ "enabled": true,
50
+ "rules": {
51
+ "recommended": true,
52
+ "complexity": {
53
+ "noUselessStringConcat": "error",
54
+ "noUselessUndefinedInitialization": "error",
55
+ "noVoid": "error",
56
+ "useDateNow": "error"
57
+ },
58
+ "correctness": {
59
+ "noConstantMathMinMaxClamp": "error",
60
+ "noUndeclaredVariables": "error",
61
+ "noUnusedImports": "error",
62
+ "noUnusedFunctionParameters": "error",
63
+ "noUnusedPrivateClassMembers": "error",
64
+ "useExhaustiveDependencies": {
65
+ "level": "error",
66
+ "options": {
67
+ "reportUnnecessaryDependencies": false
68
+ }
69
+ },
70
+ "noUnusedVariables": "error"
71
+ },
72
+ "style": {
73
+ "noParameterProperties": "error",
74
+ "noYodaExpression": "error",
75
+ "useConsistentBuiltinInstantiation": "error",
76
+ "useFragmentSyntax": "error",
77
+ "useImportType": {
78
+ "level": "error",
79
+ "fix": "safe",
80
+ "options": {
81
+ "style": "separatedType"
82
+ }
83
+ },
84
+ "useSelfClosingElements": {
85
+ "level": "error",
86
+ "fix": "safe",
87
+ "options": {}
88
+ },
89
+ "useShorthandAssign": "error",
90
+ "useArrayLiterals": "error"
91
+ },
92
+ "nursery": {
93
+ "useSortedClasses": {
94
+ "level": "error",
95
+ "fix": "safe",
96
+ "options": {
97
+ "functions": [
98
+ "clsx",
99
+ "cn"
100
+ ]
101
+ }
102
+ }
103
+ },
104
+ "suspicious": {
105
+ "useAwait": "error",
106
+ "noEvolvingTypes": "error"
107
+ }
108
+ }
109
+ },
110
+ "javascript": {
111
+ "formatter": {
112
+ "quoteStyle": "single",
113
+ "jsxQuoteStyle": "single",
114
+ "arrowParentheses": "asNeeded",
115
+ "trailingCommas": "all"
116
+ }
117
+ },
118
+ "overrides": [
119
+ {
120
+ "includes": [
121
+ "**/*.jsx",
122
+ "**/*.tsx"
123
+ ],
124
+ "linter": {
125
+ "rules": {
126
+ "style": {
127
+ "noParameterAssign": "error"
128
+ }
129
+ }
130
+ }
131
+ },
132
+ {
133
+ "includes": [
134
+ "**/*.ts",
135
+ "**/*.tsx"
136
+ ],
137
+ "linter": {
138
+ "rules": {
139
+ "correctness": {
140
+ "noUnusedVariables": "off"
141
+ }
142
+ }
143
+ }
144
+ }
145
+ ]
146
+ }