@luxfi/biome-config 1.0.3 → 1.0.4

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/src/processor.js DELETED
@@ -1,121 +0,0 @@
1
- const { mergeObjectValues, mergeArrayValues } = require('./merger')
2
- const {
3
- detectUniversePackages,
4
- generateUniversePackageOverrides,
5
- getGlobalRestrictedImportPatterns,
6
- } = require('./universePackages')
7
-
8
- /**
9
- * Processes the entire config, resolving markers in all overrides
10
- * @param {Object} baseConfig - The base configuration with overrides
11
- * @param {Map<string, any>} globalRules - Map of global rule paths to values
12
- * @returns {Object} Configuration with markers resolved
13
- */
14
- function processConfig(baseConfig, globalRules) {
15
- // Deep clone to avoid mutating original
16
- const config = structuredClone(baseConfig)
17
-
18
- // Process each override section
19
- if (Array.isArray(config.overrides)) {
20
- const processedOverrides = []
21
-
22
- // First pass: expand markers that generate multiple overrides
23
- for (const override of config.overrides) {
24
- if (override === '__AUTO_GENERATE_UNIVERSE_OVERRIDES__') {
25
- const generatedOverrides = expandUniverseOverridesMarker(config)
26
- processedOverrides.push(...generatedOverrides)
27
- } else {
28
- processedOverrides.push(override)
29
- }
30
- }
31
-
32
- // Second pass: process each override to resolve __INCLUDE_GLOBAL_VALUES__ markers
33
- config.overrides = processedOverrides.map((override) =>
34
- override.linter?.rules ? resolveIncludeGlobalValuesMarkers(override, globalRules) : override,
35
- )
36
- } else if (config.overrides) {
37
- throw new Error('`overrides` must be an array')
38
- }
39
-
40
- return config
41
- }
42
-
43
- /**
44
- * Expands __AUTO_GENERATE_UNIVERSE_OVERRIDES__ marker into actual override configurations
45
- * @param {Object} baseConfig - The base configuration (needed to extract global patterns)
46
- * @returns {Array<Object>} Array of generated override configurations
47
- */
48
- function expandUniverseOverridesMarker(baseConfig) {
49
- const universePackages = detectUniversePackages()
50
- const globalPatterns = getGlobalRestrictedImportPatterns(baseConfig)
51
- const generatedOverrides = generateUniversePackageOverrides(universePackages, globalPatterns)
52
-
53
- console.log(`✓ Auto-generated ${generatedOverrides.length} override(s) for @luxfi/* packages`)
54
-
55
- return generatedOverrides
56
- }
57
-
58
- /**
59
- * Resolves __INCLUDE_GLOBAL_VALUES__ markers in an override's rule options
60
- * @param {Object} override - Override configuration section
61
- * @param {Map<string, any>} globalRules - Map of global rule paths to values
62
- * @returns {Object} Processed override with markers resolved
63
- */
64
- function resolveIncludeGlobalValuesMarkers(override, globalRules) {
65
- // Deep clone to avoid mutation
66
- const processed = structuredClone(override)
67
-
68
- /**
69
- * Recursively walks override rules tree to find and resolve markers
70
- * @param {Object} obj - Current object being walked
71
- * @param {Array<string>} pathParts - Path components leading to this object
72
- */
73
- function walkAndMerge(obj, pathParts) {
74
- if (!obj || typeof obj !== 'object') {
75
- return
76
- }
77
-
78
- for (const [key, value] of Object.entries(obj)) {
79
- const currentPath = [...pathParts, key]
80
-
81
- // Check if this is a rule's options object with potential markers
82
- if (key === 'options' && value && typeof value === 'object') {
83
- const rulePath = pathParts.join('.')
84
- const globalRule = globalRules.get(rulePath)
85
-
86
- // Process each option key generically
87
- for (const [optionKey, optionValue] of Object.entries(value)) {
88
- const globalOptionValue = globalRule?.options?.[optionKey]
89
-
90
- // Check for marker in object-type options
91
- const isObjectWithMarker =
92
- optionValue &&
93
- typeof optionValue === 'object' &&
94
- !Array.isArray(optionValue) &&
95
- optionValue.__INCLUDE_GLOBAL_VALUES__
96
-
97
- if (isObjectWithMarker) {
98
- obj[key][optionKey] = mergeObjectValues(globalOptionValue || {}, optionValue)
99
- }
100
-
101
- // Check for marker in array-type options
102
- const isArrayWithMarker = Array.isArray(optionValue) && optionValue.includes('__INCLUDE_GLOBAL_VALUES__')
103
-
104
- if (isArrayWithMarker) {
105
- obj[key][optionKey] = mergeArrayValues(globalOptionValue || [], optionValue)
106
- }
107
- }
108
- }
109
-
110
- // Continue walking nested objects
111
- if (typeof value === 'object' && value !== null) {
112
- walkAndMerge(value, currentPath)
113
- }
114
- }
115
- }
116
-
117
- walkAndMerge(processed.linter.rules, ['linter', 'rules'])
118
- return processed
119
- }
120
-
121
- module.exports = { processConfig }
@@ -1,336 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
- import { parse as parseJsonc } from 'jsonc-parser'
5
- import { extractGlobalRuleValues } from './extractor.js'
6
- import { processConfig } from './processor.js'
7
-
8
- /**
9
- * Helper to load and process a fixture file
10
- */
11
- function processFixture(fixtureName) {
12
- const fixturePath = path.join(__dirname, 'fixtures', fixtureName)
13
- const content = fs.readFileSync(fixturePath, 'utf8')
14
- const config = parseJsonc(content)
15
- const globalRules = extractGlobalRuleValues(config)
16
- return processConfig(config, globalRules)
17
- }
18
-
19
- describe('Biome Config Processor', () => {
20
- describe('Object Marker Resolution', () => {
21
- test('should merge global paths with override paths', () => {
22
- const result = processFixture('simple-config.jsonc')
23
-
24
- const override = result.overrides[0]
25
- const paths = override.linter.rules.style.noRestrictedImports.options.paths
26
-
27
- // Should include both global and override paths
28
- expect(paths).toMatchObject({
29
- lodash: 'Use lodash-es instead',
30
- moment: 'Use date-fns instead',
31
- react: 'Use preact in tests',
32
- })
33
-
34
- // Should not include marker
35
- expect(paths.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
36
- })
37
-
38
- test('should handle "off" overrides correctly', () => {
39
- const result = processFixture('off-override-config.jsonc')
40
-
41
- const override = result.overrides[0]
42
- const paths = override.linter.rules.style.noRestrictedImports.options.paths
43
-
44
- // Should include global paths except the one turned off
45
- expect(paths.lodash).toBe('Use lodash-es instead')
46
- expect(paths.moment).toBe('Use date-fns instead')
47
-
48
- // Should not include the "off" path
49
- expect(paths.jquery).toBeUndefined()
50
-
51
- // Should include override-specific path
52
- expect(paths.axios).toBe('Use fetch instead')
53
-
54
- // Should not include marker
55
- expect(paths.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
56
- })
57
- })
58
-
59
- describe('Array Marker Resolution', () => {
60
- test('should merge global array with override array', () => {
61
- const result = processFixture('array-merge-config.jsonc')
62
-
63
- const override = result.overrides[0]
64
- const patterns = override.linter.rules.style.noRestrictedImports.options.patterns
65
-
66
- // Should include both global and override items
67
- expect(patterns).toEqual(
68
- expect.arrayContaining([
69
- {
70
- group: ['global/*'],
71
- message: 'Please do not import from global',
72
- },
73
- {
74
- group: ['localStorage/*'],
75
- message: 'Please do not import from localStorage',
76
- },
77
- {
78
- group: ['sessionStorage/*'],
79
- message: 'Please do not import from sessionStorage',
80
- },
81
- ]),
82
- )
83
-
84
- // Should not include marker
85
- expect(patterns).not.toContain('__INCLUDE_GLOBAL_VALUES__')
86
- })
87
-
88
- test('should deduplicate merged arrays', () => {
89
- const config = {
90
- linter: {
91
- rules: {
92
- style: {
93
- noRestrictedImports: {
94
- level: 'error',
95
- options: {
96
- patterns: [
97
- {
98
- group: ['event/*'],
99
- message: 'Do not import from event',
100
- },
101
- {
102
- group: ['name/*'],
103
- message: 'Do not import from name',
104
- },
105
- ],
106
- },
107
- },
108
- },
109
- },
110
- },
111
- overrides: [
112
- {
113
- include: ['src/**'],
114
- linter: {
115
- rules: {
116
- style: {
117
- noRestrictedImports: {
118
- level: 'error',
119
- options: {
120
- patterns: [
121
- '__INCLUDE_GLOBAL_VALUES__',
122
- {
123
- group: ['event/*'],
124
- message: 'Do not import from event',
125
- },
126
- {
127
- group: ['localStorage/*'],
128
- message: 'Do not import from localStorage',
129
- },
130
- ],
131
- },
132
- },
133
- },
134
- },
135
- },
136
- },
137
- ],
138
- }
139
-
140
- const globalRules = extractGlobalRuleValues(config)
141
- const result = processConfig(config, globalRules)
142
-
143
- const patterns = result.overrides[0].linter.rules.style.noRestrictedImports.options.patterns
144
-
145
- // Should not have duplicates
146
- const eventPatterns = patterns.filter((x) => JSON.stringify(x.group) === JSON.stringify(['event/*']))
147
- expect(eventPatterns).toHaveLength(1)
148
-
149
- // Should maintain override order (local items first)
150
- expect(patterns[0]).toMatchObject({
151
- group: ['event/*'],
152
- message: 'Do not import from event',
153
- })
154
- expect(patterns[1]).toMatchObject({
155
- group: ['localStorage/*'],
156
- message: 'Do not import from localStorage',
157
- })
158
- expect(patterns[2]).toMatchObject({
159
- group: ['name/*'],
160
- message: 'Do not import from name',
161
- })
162
- })
163
- })
164
-
165
- describe('No Markers', () => {
166
- test('should not modify overrides without markers', () => {
167
- const result = processFixture('no-markers-config.jsonc')
168
-
169
- const override = result.overrides[0]
170
- const paths = override.linter.rules.style.noRestrictedImports.options.paths
171
-
172
- // Should only have override path
173
- expect(paths).toMatchObject({
174
- react: 'Custom restriction',
175
- })
176
-
177
- // Should not include global paths
178
- expect(paths.lodash).toBeUndefined()
179
- })
180
- })
181
-
182
- describe('Edge Cases', () => {
183
- test('should handle config with no overrides', () => {
184
- const config = {
185
- linter: {
186
- rules: {
187
- style: {
188
- noRestrictedImports: {
189
- level: 'error',
190
- options: {
191
- paths: {
192
- lodash: 'Use lodash-es',
193
- },
194
- },
195
- },
196
- },
197
- },
198
- },
199
- }
200
-
201
- const globalRules = extractGlobalRuleValues(config)
202
- const result = processConfig(config, globalRules)
203
-
204
- // Should return unchanged
205
- expect(result).toEqual(config)
206
- })
207
-
208
- test('should handle override without linter rules', () => {
209
- const config = {
210
- linter: {
211
- rules: {
212
- style: {
213
- noRestrictedImports: {
214
- level: 'error',
215
- options: {
216
- paths: {
217
- lodash: 'Use lodash-es',
218
- },
219
- },
220
- },
221
- },
222
- },
223
- },
224
- overrides: [
225
- {
226
- include: ['src/**'],
227
- formatter: {
228
- enabled: false,
229
- },
230
- },
231
- ],
232
- }
233
-
234
- const globalRules = extractGlobalRuleValues(config)
235
- const result = processConfig(config, globalRules)
236
-
237
- // Should handle override without linter rules gracefully
238
- expect(result.overrides[0]).toMatchObject({
239
- include: ['src/**'],
240
- formatter: {
241
- enabled: false,
242
- },
243
- })
244
- })
245
-
246
- test('should handle empty global rules', () => {
247
- const config = {
248
- linter: {
249
- rules: {},
250
- },
251
- overrides: [
252
- {
253
- include: ['src/**'],
254
- linter: {
255
- rules: {
256
- style: {
257
- noRestrictedImports: {
258
- level: 'error',
259
- options: {
260
- paths: {
261
- __INCLUDE_GLOBAL_VALUES__: true,
262
- react: 'Custom restriction',
263
- },
264
- },
265
- },
266
- },
267
- },
268
- },
269
- },
270
- ],
271
- }
272
-
273
- const globalRules = extractGlobalRuleValues(config)
274
- const result = processConfig(config, globalRules)
275
-
276
- const paths = result.overrides[0].linter.rules.style.noRestrictedImports.options.paths
277
-
278
- // Should only include override paths
279
- expect(paths).toMatchObject({
280
- react: 'Custom restriction',
281
- })
282
-
283
- // Should remove marker
284
- expect(paths.__INCLUDE_GLOBAL_VALUES__).toBeUndefined()
285
- })
286
- })
287
-
288
- describe('Immutability', () => {
289
- test('should not mutate original config', () => {
290
- const originalConfig = {
291
- linter: {
292
- rules: {
293
- style: {
294
- noRestrictedImports: {
295
- level: 'error',
296
- options: {
297
- paths: {
298
- lodash: 'Use lodash-es',
299
- },
300
- },
301
- },
302
- },
303
- },
304
- },
305
- overrides: [
306
- {
307
- include: ['src/**'],
308
- linter: {
309
- rules: {
310
- style: {
311
- noRestrictedImports: {
312
- level: 'error',
313
- options: {
314
- paths: {
315
- __INCLUDE_GLOBAL_VALUES__: true,
316
- react: 'Custom restriction',
317
- },
318
- },
319
- },
320
- },
321
- },
322
- },
323
- },
324
- ],
325
- }
326
-
327
- const globalRules = extractGlobalRuleValues(originalConfig)
328
- processConfig(originalConfig, globalRules)
329
-
330
- // Original config should still have the marker
331
- expect(
332
- originalConfig.overrides[0].linter.rules.style.noRestrictedImports.options.paths.__INCLUDE_GLOBAL_VALUES__,
333
- ).toBe(true)
334
- })
335
- })
336
- })
@@ -1,144 +0,0 @@
1
- const { readCachedProjectGraph, readProjectsConfigurationFromProjectGraph } = require('@nx/devkit')
2
- const path = require('node:path')
3
-
4
- /**
5
- * Detects all @luxfi/* packages using NX project graph
6
- * @returns {Array<{dir: string, name: string, fullName: string}>} Array of package info objects
7
- */
8
- function detectUniversePackages() {
9
- try {
10
- // Read the NX project graph (cached for performance)
11
- const projectGraph = readCachedProjectGraph()
12
- const projectsConfig = readProjectsConfigurationFromProjectGraph(projectGraph)
13
-
14
- const universePackages = []
15
-
16
- // Iterate through all projects in the workspace
17
- for (const [projectName, config] of Object.entries(projectsConfig.projects)) {
18
- // NX project names are the same as package names (from package.json)
19
- if (projectName.startsWith('@luxfi/')) {
20
- // Extract the package name without @luxfi/ prefix
21
- const shortName = projectName.replace('@luxfi/', '')
22
-
23
- // Extract directory name from root path (e.g., "pkgs/api" -> "api")
24
- const dir = path.basename(config.root)
25
-
26
- universePackages.push({
27
- dir,
28
- name: shortName,
29
- fullName: projectName,
30
- })
31
- }
32
- }
33
-
34
- return universePackages
35
- } catch (error) {
36
- console.warn(`Warning: Could not read project graph: ${error.message}`)
37
- console.warn('Falling back to empty package list')
38
- return []
39
- }
40
- }
41
-
42
- /**
43
- * Generates override configurations for @luxfi/* packages
44
- * Each package gets an override that:
45
- * - Applies to files within that package
46
- * - Allows the package to deep-import into itself
47
- *
48
- * @param {Array<{dir: string, name: string, fullName: string}>} universePackages - Detected packages
49
- * @param {Array<Object>} globalPatterns - Global restriction patterns
50
- * @returns {Array<Object>} Array of override configurations
51
- */
52
- function generateUniversePackageOverrides(universePackages, globalPatterns) {
53
- const overrides = []
54
-
55
- for (const pkg of universePackages) {
56
- // For each @luxfi/* package, we need to:
57
- // 1. Allow deep imports to ITSELF (@luxfi/api can use @luxfi/api/src/...)
58
- // 2. Block deep imports to OTHER @luxfi/* packages
59
- //
60
- // Strategy: Replace the wildcard pattern @luxfi/*/src with explicit patterns
61
- // for all OTHER packages (excluding the current package)
62
-
63
- const filteredPatterns = globalPatterns
64
- .map((pattern) => {
65
- if (!pattern.group || !Array.isArray(pattern.group)) {
66
- return pattern // Keep non-group patterns as-is
67
- }
68
-
69
- // Check if this is the @luxfi/* wildcard pattern
70
- const hasUniverseWildcard = pattern.group.some((g) => g.includes('@luxfi/*/src') || g === '@luxfi/*/src/*')
71
-
72
- if (!hasUniverseWildcard) {
73
- return pattern // Keep other patterns unchanged
74
- }
75
-
76
- // Replace wildcard with explicit patterns for OTHER @luxfi/* packages
77
- const otherPackages = universePackages.filter((p) => p.name !== pkg.name)
78
- const explicitGroup = []
79
-
80
- for (const otherPkg of otherPackages) {
81
- explicitGroup.push(`@luxfi/${otherPkg.name}/src`)
82
- explicitGroup.push(`@luxfi/${otherPkg.name}/src/*`)
83
- }
84
-
85
- // Return new pattern with explicit group (or empty if no other packages)
86
- return explicitGroup.length > 0
87
- ? {
88
- ...pattern,
89
- group: explicitGroup,
90
- }
91
- : null // Will be filtered out below
92
- })
93
- .filter((p) => p !== null) // Remove null entries
94
-
95
- const override = {
96
- includes: [
97
- `pkgs/${pkg.dir}/**`,
98
- `!pkgs/${pkg.dir}/.eslintrc.js`,
99
- `!pkgs/${pkg.dir}/**/__generated__/**`,
100
- `!pkgs/${pkg.dir}/scripts/**`,
101
- ],
102
- linter: {
103
- rules: {
104
- style: {
105
- noRestrictedImports: {
106
- level: 'error',
107
- options: {
108
- paths: {
109
- __INCLUDE_GLOBAL_VALUES__: true,
110
- },
111
- patterns: filteredPatterns,
112
- },
113
- },
114
- },
115
- },
116
- },
117
- }
118
-
119
- overrides.push(override)
120
- }
121
-
122
- return overrides
123
- }
124
-
125
- /**
126
- * Gets the global noRestrictedImports patterns from the config
127
- * @param {Object} config - The base configuration
128
- * @returns {Array<Object>} Array of pattern objects
129
- */
130
- function getGlobalRestrictedImportPatterns(config) {
131
- const patterns = config?.linter?.rules?.style?.noRestrictedImports?.options?.patterns
132
-
133
- if (!Array.isArray(patterns)) {
134
- return []
135
- }
136
-
137
- return patterns
138
- }
139
-
140
- module.exports = {
141
- detectUniversePackages,
142
- generateUniversePackageOverrides,
143
- getGlobalRestrictedImportPatterns,
144
- }