load-oxfmt-config 0.4.1 → 0.6.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 +250 -41
- package/dist/index.d.mts +138 -12
- package/dist/index.mjs +364 -104
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- 🔍 **Auto-discovery** - Automatically searches for config files in current and parent directories
|
|
13
13
|
- 📦 **Multiple formats** - Supports `.oxfmtrc.json`, `.oxfmtrc.jsonc`, and `oxfmt.config.ts`
|
|
14
14
|
- 🧩 **EditorConfig fallback** - Merges supported `.editorconfig` fields into the returned oxfmt config result
|
|
15
|
+
- 🚫 **Ignore resolution** - Resolves ignore status with oxfmt CLI-like global + config-scoped semantics
|
|
15
16
|
- ⚡ **Built-in caching** - Caches both file resolution and parsed configs for optimal performance
|
|
16
17
|
- 🎯 **TypeScript support** - Fully typed with comprehensive type definitions
|
|
17
18
|
- 🛠️ **Flexible API** - Support explicit config paths or automatic discovery
|
|
@@ -37,22 +38,22 @@ pnpm add load-oxfmt-config
|
|
|
37
38
|
Load config from current directory or parent directories:
|
|
38
39
|
|
|
39
40
|
```ts
|
|
40
|
-
import {
|
|
41
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
41
42
|
|
|
42
43
|
// Automatically searches for oxfmt config files and the nearest .editorconfig
|
|
43
|
-
const
|
|
44
|
-
console.log(config) // { printWidth: 80, ... }
|
|
44
|
+
const result = await loadOxfmtConfigResult()
|
|
45
|
+
console.log(result.config) // { printWidth: 80, ... }
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
### Merge With `.editorconfig`
|
|
48
49
|
|
|
49
50
|
```ts
|
|
50
|
-
import {
|
|
51
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
51
52
|
|
|
52
|
-
const
|
|
53
|
+
const result = await loadOxfmtConfigResult({ cwd: '/path/to/project' })
|
|
53
54
|
|
|
54
55
|
// Returns one merged static config object
|
|
55
|
-
console.log(config)
|
|
56
|
+
console.log(result.config)
|
|
56
57
|
// {
|
|
57
58
|
// tabWidth: 2,
|
|
58
59
|
// printWidth: 100,
|
|
@@ -65,26 +66,52 @@ console.log(config)
|
|
|
65
66
|
### Specify Working Directory
|
|
66
67
|
|
|
67
68
|
```ts
|
|
68
|
-
import {
|
|
69
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
69
70
|
|
|
70
|
-
const
|
|
71
|
+
const result = await loadOxfmtConfigResult({
|
|
71
72
|
cwd: '/path/to/project',
|
|
72
73
|
})
|
|
73
74
|
```
|
|
74
75
|
|
|
76
|
+
### Get Config Metadata
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
80
|
+
|
|
81
|
+
const result = await loadOxfmtConfigResult({ cwd: '/path/to/project' })
|
|
82
|
+
|
|
83
|
+
console.log(result.config) // merged oxfmt options
|
|
84
|
+
console.log(result.filepath) // /path/to/project/.oxfmtrc.json (or undefined)
|
|
85
|
+
console.log(result.dirname) // /path/to/project (or undefined)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Resolve Ignore Status
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { isOxfmtIgnored } from 'load-oxfmt-config'
|
|
92
|
+
|
|
93
|
+
const result = await isOxfmtIgnored({
|
|
94
|
+
cwd: '/path/to/project',
|
|
95
|
+
filepath: '/path/to/project/src/generated/foo.ts',
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
console.log(result)
|
|
99
|
+
// { ignored: true, reason: 'config-ignore-patterns' }
|
|
100
|
+
```
|
|
101
|
+
|
|
75
102
|
### Explicit Config Path
|
|
76
103
|
|
|
77
104
|
```ts
|
|
78
|
-
import {
|
|
105
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
79
106
|
|
|
80
107
|
// Relative path (resolved relative to cwd)
|
|
81
|
-
const
|
|
108
|
+
const result = await loadOxfmtConfigResult({
|
|
82
109
|
configPath: 'configs/.oxfmtrc.json',
|
|
83
110
|
cwd: '/path/to/project',
|
|
84
111
|
})
|
|
85
112
|
|
|
86
113
|
// Absolute path
|
|
87
|
-
const
|
|
114
|
+
const absoluteResult = await loadOxfmtConfigResult({
|
|
88
115
|
configPath: '/absolute/path/to/.oxfmtrc.json',
|
|
89
116
|
})
|
|
90
117
|
```
|
|
@@ -92,10 +119,10 @@ const config = await loadOxfmtConfig({
|
|
|
92
119
|
### Disable `.editorconfig`
|
|
93
120
|
|
|
94
121
|
```ts
|
|
95
|
-
import {
|
|
122
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
96
123
|
|
|
97
124
|
// Skip .editorconfig reading entirely
|
|
98
|
-
const
|
|
125
|
+
const result = await loadOxfmtConfigResult({
|
|
99
126
|
editorconfig: false,
|
|
100
127
|
})
|
|
101
128
|
```
|
|
@@ -103,10 +130,10 @@ const config = await loadOxfmtConfig({
|
|
|
103
130
|
### Limit `.editorconfig` to `cwd`
|
|
104
131
|
|
|
105
132
|
```ts
|
|
106
|
-
import {
|
|
133
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
107
134
|
|
|
108
135
|
// Only look in the cwd directory itself, no upward traversal
|
|
109
|
-
const
|
|
136
|
+
const result = await loadOxfmtConfigResult({
|
|
110
137
|
editorconfig: { onlyCwd: true },
|
|
111
138
|
})
|
|
112
139
|
```
|
|
@@ -114,10 +141,10 @@ const config = await loadOxfmtConfig({
|
|
|
114
141
|
### Override `.editorconfig` Search Directory
|
|
115
142
|
|
|
116
143
|
```ts
|
|
117
|
-
import {
|
|
144
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
118
145
|
|
|
119
146
|
// Search .editorconfig from a custom directory instead of the config file's directory
|
|
120
|
-
const
|
|
147
|
+
const result = await loadOxfmtConfigResult({
|
|
121
148
|
editorconfig: {
|
|
122
149
|
cwd: '/path/to/editorconfig-dir',
|
|
123
150
|
},
|
|
@@ -127,14 +154,23 @@ const config = await loadOxfmtConfig({
|
|
|
127
154
|
### Disable Caching
|
|
128
155
|
|
|
129
156
|
```ts
|
|
130
|
-
import {
|
|
157
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
131
158
|
|
|
132
159
|
// Force reload from disk, bypassing cache
|
|
133
|
-
const
|
|
160
|
+
const result = await loadOxfmtConfigResult({
|
|
134
161
|
useCache: false,
|
|
135
162
|
})
|
|
136
163
|
```
|
|
137
164
|
|
|
165
|
+
### Legacy API (Deprecated)
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
169
|
+
|
|
170
|
+
// Deprecated: prefer loadOxfmtConfigResult
|
|
171
|
+
const config = await loadOxfmtConfig({ cwd: '/path/to/project' })
|
|
172
|
+
```
|
|
173
|
+
|
|
138
174
|
### Path Resolution Only
|
|
139
175
|
|
|
140
176
|
```ts
|
|
@@ -149,39 +185,83 @@ console.log(configPath) // '/path/to/.oxfmtrc.json' or undefined
|
|
|
149
185
|
|
|
150
186
|
### `loadOxfmtConfig(options?)`
|
|
151
187
|
|
|
188
|
+
> Deprecated. Prefer `loadOxfmtConfigResult(options?)`.
|
|
189
|
+
|
|
152
190
|
Load and parse oxfmt configuration files, then merge supported `.editorconfig` fields into the returned result.
|
|
153
191
|
|
|
154
192
|
**Parameters:**
|
|
155
193
|
|
|
156
|
-
- `options` - Optional configuration object
|
|
194
|
+
- `options` - Optional configuration object (`Options`)
|
|
157
195
|
|
|
158
|
-
|
|
196
|
+
Option fields:
|
|
159
197
|
|
|
160
|
-
|
|
198
|
+
#### `cwd`
|
|
161
199
|
|
|
162
|
-
|
|
200
|
+
- **Type:** `string`
|
|
201
|
+
- **Default:** `process.cwd()`
|
|
163
202
|
|
|
164
|
-
|
|
203
|
+
Current working directory to start searching for config files. The loader will walk up from this directory to find a config file.
|
|
165
204
|
|
|
166
|
-
|
|
205
|
+
#### `configPath`
|
|
167
206
|
|
|
168
|
-
- `
|
|
169
|
-
- `
|
|
207
|
+
- **Type:** `string`
|
|
208
|
+
- **Default:** `undefined`
|
|
170
209
|
|
|
171
|
-
|
|
210
|
+
Explicit path to the config file:
|
|
211
|
+
|
|
212
|
+
- **Relative path:** Resolved relative to `cwd`
|
|
213
|
+
- **Absolute path:** Used as-is
|
|
214
|
+
- **When provided:** Skips auto-discovery and uses this path directly
|
|
215
|
+
|
|
216
|
+
#### `useCache`
|
|
217
|
+
|
|
218
|
+
- **Type:** `boolean`
|
|
219
|
+
- **Default:** `true`
|
|
220
|
+
|
|
221
|
+
Enable in-memory caching for both path resolution and parsed config contents. When enabled:
|
|
222
|
+
|
|
223
|
+
- Config file paths are cached to avoid repeated filesystem lookups
|
|
224
|
+
- Parsed config objects are cached to avoid re-parsing
|
|
225
|
+
- Subsequent calls with the same parameters return cached results instantly
|
|
172
226
|
|
|
173
|
-
|
|
227
|
+
Set to `false` to force reload from disk on every call.
|
|
228
|
+
|
|
229
|
+
#### `editorconfig`
|
|
230
|
+
|
|
231
|
+
- **Type:** `boolean | EditorconfigOption`
|
|
232
|
+
- **Default:** `true`
|
|
174
233
|
|
|
175
|
-
|
|
234
|
+
Control how `.editorconfig` files are read and merged:
|
|
235
|
+
|
|
236
|
+
- **`true`** — Read and merge the nearest `.editorconfig`, walking up from the config file's directory (or `cwd` when no config path is given).
|
|
237
|
+
- **`false`** — Disable `.editorconfig` reading entirely.
|
|
238
|
+
- **`EditorconfigOption`** — Enable with additional settings:
|
|
176
239
|
|
|
177
|
-
|
|
240
|
+
| Property | Type | Default | Description |
|
|
241
|
+
| --------- | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
242
|
+
| `onlyCwd` | `boolean` | `false` | When `true`, only look for `.editorconfig` in `cwd` itself — no upward traversal. |
|
|
243
|
+
| `cwd` | `string` | `undefined` | Override the directory from which `.editorconfig` resolution starts, instead of the config file's directory or `cwd`. |
|
|
244
|
+
|
|
245
|
+
**Returns:** `Promise<OxfmtOptions>` - Parsed and merged oxfmt configuration object. Returns empty object `{}` if no supported config file is found.
|
|
246
|
+
|
|
247
|
+
### `loadOxfmtConfigResult(options?)`
|
|
248
|
+
|
|
249
|
+
Load and parse oxfmt configuration files, merge supported `.editorconfig` fields, and return metadata for the resolved config file.
|
|
250
|
+
|
|
251
|
+
**Parameters:**
|
|
252
|
+
|
|
253
|
+
- `options` - Optional configuration object (`Options`)
|
|
254
|
+
|
|
255
|
+
Option fields:
|
|
256
|
+
|
|
257
|
+
#### `cwd`
|
|
178
258
|
|
|
179
259
|
- **Type:** `string`
|
|
180
260
|
- **Default:** `process.cwd()`
|
|
181
261
|
|
|
182
262
|
Current working directory to start searching for config files. The loader will walk up from this directory to find a config file.
|
|
183
263
|
|
|
184
|
-
|
|
264
|
+
#### `configPath`
|
|
185
265
|
|
|
186
266
|
- **Type:** `string`
|
|
187
267
|
- **Default:** `undefined`
|
|
@@ -192,7 +272,7 @@ Explicit path to the config file:
|
|
|
192
272
|
- **Absolute path:** Used as-is
|
|
193
273
|
- **When provided:** Skips auto-discovery and uses this path directly
|
|
194
274
|
|
|
195
|
-
|
|
275
|
+
#### `useCache`
|
|
196
276
|
|
|
197
277
|
- **Type:** `boolean`
|
|
198
278
|
- **Default:** `true`
|
|
@@ -205,7 +285,7 @@ Enable in-memory caching for both path resolution and parsed config contents. Wh
|
|
|
205
285
|
|
|
206
286
|
Set to `false` to force reload from disk on every call.
|
|
207
287
|
|
|
208
|
-
|
|
288
|
+
#### `editorconfig`
|
|
209
289
|
|
|
210
290
|
- **Type:** `boolean | EditorconfigOption`
|
|
211
291
|
- **Default:** `true`
|
|
@@ -221,6 +301,105 @@ Control how `.editorconfig` files are read and merged:
|
|
|
221
301
|
| `onlyCwd` | `boolean` | `false` | When `true`, only look for `.editorconfig` in `cwd` itself — no upward traversal. |
|
|
222
302
|
| `cwd` | `string` | `undefined` | Override the directory from which `.editorconfig` resolution starts, instead of the config file's directory or `cwd`. |
|
|
223
303
|
|
|
304
|
+
**Returns:** `Promise<LoadOxfmtConfigResult>`
|
|
305
|
+
|
|
306
|
+
- `config` - Parsed and merged oxfmt configuration object
|
|
307
|
+
- `filepath` - Resolved config absolute path (undefined when not found)
|
|
308
|
+
- `dirname` - Config directory (undefined when not found)
|
|
309
|
+
|
|
310
|
+
**Throws:** Error if config file exists but cannot be parsed.
|
|
311
|
+
|
|
312
|
+
### `resolveOxfmtrcPath(cwd, configPath?)`
|
|
313
|
+
|
|
314
|
+
Resolve the absolute path to oxfmt config file.
|
|
315
|
+
|
|
316
|
+
**Parameters:**
|
|
317
|
+
|
|
318
|
+
- `cwd` - Starting directory for resolution
|
|
319
|
+
- `configPath` - Optional explicit path (absolute or relative to cwd)
|
|
320
|
+
|
|
321
|
+
**Returns:** `Promise<string | undefined>` - Absolute path to config file, or `undefined` if not found.
|
|
322
|
+
|
|
323
|
+
### `isOxfmtIgnored(options)`
|
|
324
|
+
|
|
325
|
+
Resolve whether a file should be ignored with oxfmt CLI-like semantics.
|
|
326
|
+
|
|
327
|
+
**Parameters:**
|
|
328
|
+
|
|
329
|
+
- `options` - Required configuration object (`IsOxfmtIgnoredOptions`)
|
|
330
|
+
|
|
331
|
+
Option fields:
|
|
332
|
+
|
|
333
|
+
#### `cwd`
|
|
334
|
+
|
|
335
|
+
- **Type:** `string`
|
|
336
|
+
- **Default:** `process.cwd()`
|
|
337
|
+
|
|
338
|
+
Current working directory.
|
|
339
|
+
Also the base directory for default `.gitignore`/`.prettierignore` lookup.
|
|
340
|
+
|
|
341
|
+
#### `filepath`
|
|
342
|
+
|
|
343
|
+
- **Type:** `string`
|
|
344
|
+
- **Default:** required
|
|
345
|
+
|
|
346
|
+
File path to test.
|
|
347
|
+
|
|
348
|
+
#### `configPath`
|
|
349
|
+
|
|
350
|
+
- **Type:** `string`
|
|
351
|
+
- **Default:** `undefined`
|
|
352
|
+
|
|
353
|
+
Explicit oxfmt config path.
|
|
354
|
+
When provided, nested config lookup is disabled (same as oxfmt CLI `-c`).
|
|
355
|
+
|
|
356
|
+
#### `ignorePath`
|
|
357
|
+
|
|
358
|
+
- **Type:** `string | string[]`
|
|
359
|
+
- **Default:** `undefined`
|
|
360
|
+
|
|
361
|
+
Ignore files to use instead of cwd `.gitignore`/`.prettierignore`.
|
|
362
|
+
Can be passed multiple times in CLI style.
|
|
363
|
+
|
|
364
|
+
#### `withNodeModules`
|
|
365
|
+
|
|
366
|
+
- **Type:** `boolean`
|
|
367
|
+
- **Default:** `true`
|
|
368
|
+
|
|
369
|
+
Whether `node_modules` should be included.
|
|
370
|
+
|
|
371
|
+
#### `disableNestedConfig`
|
|
372
|
+
|
|
373
|
+
- **Type:** `boolean`
|
|
374
|
+
- **Default:** `false`
|
|
375
|
+
|
|
376
|
+
Disable nested config lookup.
|
|
377
|
+
|
|
378
|
+
#### `useCache`
|
|
379
|
+
|
|
380
|
+
- **Type:** `boolean`
|
|
381
|
+
- **Default:** `true`
|
|
382
|
+
|
|
383
|
+
Whether to use in-memory cache.
|
|
384
|
+
|
|
385
|
+
#### `includeConfigIgnorePatterns`
|
|
386
|
+
|
|
387
|
+
- **Type:** `boolean`
|
|
388
|
+
- **Default:** `true`
|
|
389
|
+
|
|
390
|
+
Whether to include ignore patterns defined in the resolved config file.
|
|
391
|
+
|
|
392
|
+
**Returns:** `Promise<IsOxfmtIgnoredResult>`
|
|
393
|
+
|
|
394
|
+
- `ignored` - Whether the file is ignored
|
|
395
|
+
- `reason` - One of:
|
|
396
|
+
- `default-dir`
|
|
397
|
+
- `lockfile`
|
|
398
|
+
- `gitignore`
|
|
399
|
+
- `prettierignore`
|
|
400
|
+
- `ignore-path`
|
|
401
|
+
- `config-ignore-patterns`
|
|
402
|
+
|
|
224
403
|
## Config File Discovery
|
|
225
404
|
|
|
226
405
|
When `configPath` is not provided, the loader automatically searches for config files:
|
|
@@ -229,9 +408,7 @@ When `configPath` is not provided, the loader automatically searches for config
|
|
|
229
408
|
2. **Supported filenames:**
|
|
230
409
|
- `.oxfmtrc.json`
|
|
231
410
|
- `.oxfmtrc.jsonc`
|
|
232
|
-
|
|
233
|
-
- `oxfmt.config.ts`
|
|
234
|
-
|
|
411
|
+
- `oxfmt.config.ts`
|
|
235
412
|
3. **Stops when:**
|
|
236
413
|
- A valid config file is found
|
|
237
414
|
- Reaches the filesystem root
|
|
@@ -278,6 +455,8 @@ export default {
|
|
|
278
455
|
}
|
|
279
456
|
```
|
|
280
457
|
|
|
458
|
+
When `configPath` is passed explicitly, extensions `.json`, `.jsonc`, `.ts`, `.mts`, `.cts`, `.js`, `.mjs`, and `.cjs` are supported. Extensionless paths are also accepted and parsed as JSON.
|
|
459
|
+
|
|
281
460
|
## `.editorconfig` Support
|
|
282
461
|
|
|
283
462
|
The loader reads the nearest `.editorconfig` file and maps the subset of fields that oxfmt supports:
|
|
@@ -292,6 +471,35 @@ The loader reads the nearest `.editorconfig` file and maps the subset of fields
|
|
|
292
471
|
|
|
293
472
|
Only `[*]` is treated as a global section to match oxfmt. Other sections such as `[**]` and `[*.ts]` are converted into returned `overrides` entries.
|
|
294
473
|
|
|
474
|
+
## Ignore Strategy
|
|
475
|
+
|
|
476
|
+
`isOxfmtIgnored()` applies two layers:
|
|
477
|
+
|
|
478
|
+
1. Global ignore
|
|
479
|
+
2. Ignore patterns from the resolved oxfmt config (`ignorePatterns`)
|
|
480
|
+
|
|
481
|
+
Set `includeConfigIgnorePatterns: false` to keep only global ignore behavior (skip config `ignorePatterns`).
|
|
482
|
+
|
|
483
|
+
Global ignore includes:
|
|
484
|
+
|
|
485
|
+
- Default ignored directories: `.git`, `.svn`, `.jj`, `node_modules`
|
|
486
|
+
- Default lockfiles: `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `bun.lockb`
|
|
487
|
+
- Ignore files:
|
|
488
|
+
- If `ignorePath` is provided: use those files only (multiple supported)
|
|
489
|
+
- If `ignorePath` is not provided: use `.gitignore` and `.prettierignore` from `cwd`
|
|
490
|
+
|
|
491
|
+
Notes:
|
|
492
|
+
|
|
493
|
+
- `node_modules` can be included by passing `withNodeModules: true`.
|
|
494
|
+
- This package does not read parent `.gitignore` files or global gitignore settings.
|
|
495
|
+
- The default lockfile list mirrors oxfmt documentation intent (`package-lock.json`, `pnpm-lock.yaml`, etc.) and common ecosystem lockfiles. It is not guaranteed to be a complete internal oxfmt list.
|
|
496
|
+
- `ignorePatterns` are always interpreted relative to the resolved oxfmt config directory.
|
|
497
|
+
- `includeConfigIgnorePatterns` defaults to `true` to preserve current behavior.
|
|
498
|
+
- Nested config behavior follows oxfmt semantics:
|
|
499
|
+
- default: nearest config from target file directory upward
|
|
500
|
+
- `disableNestedConfig: true`: resolve from `cwd` only
|
|
501
|
+
- `configPath`: also disables nested lookup (same intent as CLI `-c`)
|
|
502
|
+
|
|
295
503
|
## Precedence
|
|
296
504
|
|
|
297
505
|
The merged result follows this order:
|
|
@@ -305,17 +513,18 @@ This means explicit oxfmt config values always win over `.editorconfig` fallback
|
|
|
305
513
|
|
|
306
514
|
## Limitations
|
|
307
515
|
|
|
308
|
-
`loadOxfmtConfig()` returns a static `OxfmtOptions`
|
|
516
|
+
`loadOxfmtConfigResult()` (and the deprecated `loadOxfmtConfig()`) returns a static merged `OxfmtOptions` shape. That means `.editorconfig` support is represented as merged root + overrides config data, not as per-file runtime evaluation. In practice this works well for common root settings and section-based overrides, but it is not a full replacement for oxfmt's own file-by-file config resolution.
|
|
309
517
|
|
|
310
518
|
## Error Handling
|
|
311
519
|
|
|
312
520
|
```ts
|
|
313
|
-
import {
|
|
521
|
+
import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
314
522
|
|
|
315
523
|
try {
|
|
316
|
-
const
|
|
524
|
+
const result = await loadOxfmtConfigResult()
|
|
525
|
+
console.log(result.config)
|
|
317
526
|
} catch (error) {
|
|
318
|
-
// Thrown when config file exists but contains invalid JSON
|
|
527
|
+
// Thrown when config file exists but contains invalid JSON/JSONC
|
|
319
528
|
console.error('Failed to parse oxfmt config:', error.message)
|
|
320
529
|
}
|
|
321
530
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -77,35 +77,161 @@ interface OxfmtOptions extends FormatConfig {
|
|
|
77
77
|
*/
|
|
78
78
|
overrides?: OxfmtConfigOverride[];
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Result object with metadata about resolved oxfmt config.
|
|
82
|
+
*/
|
|
83
|
+
interface LoadOxfmtConfigResult {
|
|
84
|
+
/**
|
|
85
|
+
* Final merged config (oxfmt + optional editorconfig mapping)
|
|
86
|
+
*/
|
|
87
|
+
config: OxfmtOptions;
|
|
88
|
+
/**
|
|
89
|
+
* Absolute path of resolved config file
|
|
90
|
+
*/
|
|
91
|
+
filepath?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Directory of resolved config file
|
|
94
|
+
*/
|
|
95
|
+
dirname?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Options for resolving whether a single file should be ignored.
|
|
99
|
+
*/
|
|
100
|
+
interface IsOxfmtIgnoredOptions {
|
|
101
|
+
/**
|
|
102
|
+
* Current working directory.
|
|
103
|
+
* Also the base directory for default .gitignore/.prettierignore lookup.
|
|
104
|
+
*/
|
|
105
|
+
cwd?: string;
|
|
106
|
+
/**
|
|
107
|
+
* File path to test.
|
|
108
|
+
*/
|
|
109
|
+
filepath: string;
|
|
110
|
+
/**
|
|
111
|
+
* Explicit oxfmt config path.
|
|
112
|
+
* When provided, nested config lookup is disabled (same as oxfmt CLI -c).
|
|
113
|
+
*/
|
|
114
|
+
configPath?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Ignore files to use instead of cwd .gitignore/.prettierignore.
|
|
117
|
+
* Can be passed multiple times in CLI style.
|
|
118
|
+
*/
|
|
119
|
+
ignorePath?: string | string[];
|
|
120
|
+
/**
|
|
121
|
+
* Whether node_modules should be included.
|
|
122
|
+
* @default true
|
|
123
|
+
*/
|
|
124
|
+
withNodeModules?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Disable nested config lookup.
|
|
127
|
+
* @default false
|
|
128
|
+
*/
|
|
129
|
+
disableNestedConfig?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Whether to use in-memory cache.
|
|
132
|
+
* @default true
|
|
133
|
+
*/
|
|
134
|
+
useCache?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Whether to include ignore patterns defined in the config file.
|
|
137
|
+
* @default true
|
|
138
|
+
*/
|
|
139
|
+
includeConfigIgnorePatterns?: boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Ignore resolution result.
|
|
143
|
+
*/
|
|
144
|
+
interface IsOxfmtIgnoredResult {
|
|
145
|
+
/**
|
|
146
|
+
* Whether the file should be ignored.
|
|
147
|
+
*/
|
|
148
|
+
ignored: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Matched ignore source.
|
|
151
|
+
*/
|
|
152
|
+
reason?: 'default-dir' | 'lockfile' | 'gitignore' | 'prettierignore' | 'ignore-path' | 'config-ignore-patterns';
|
|
153
|
+
}
|
|
80
154
|
/**
|
|
81
155
|
* @deprecated Use `OxfmtConfigOverride` instead
|
|
82
156
|
*/
|
|
83
157
|
type FormatOptionOverride = OxfmtConfigOverride;
|
|
84
158
|
//#endregion
|
|
85
|
-
//#region src/
|
|
159
|
+
//#region src/config-file.d.ts
|
|
86
160
|
/**
|
|
87
161
|
* Resolve the oxfmt config file path.
|
|
88
|
-
*
|
|
89
|
-
* -
|
|
162
|
+
*
|
|
163
|
+
* - If `configPath` is provided, absolute paths are returned as-is;
|
|
164
|
+
* relative paths are joined to `cwd`.
|
|
165
|
+
* - Otherwise, walk upward from `cwd` to find known filenames.
|
|
90
166
|
*
|
|
91
167
|
* @param cwd - Starting directory for resolution.
|
|
92
|
-
* @param configPath - Optional explicit path
|
|
93
|
-
* @returns Absolute path to
|
|
168
|
+
* @param configPath - Optional explicit path.
|
|
169
|
+
* @returns Absolute path to config file, or undefined when not found.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { resolveOxfmtrcPath } from 'load-oxfmt-config'
|
|
174
|
+
*
|
|
175
|
+
* const path = await resolveOxfmtrcPath(process.cwd())
|
|
176
|
+
* ```
|
|
94
177
|
*/
|
|
95
178
|
declare function resolveOxfmtrcPath(cwd: string, configPath?: string): Promise<string | undefined>;
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/core.d.ts
|
|
181
|
+
/**
|
|
182
|
+
* Resolve config + editorconfig and return merged config with metadata.
|
|
183
|
+
*
|
|
184
|
+
* @param options - Loader settings.
|
|
185
|
+
* @returns Merged config and optional resolved config metadata.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
190
|
+
*
|
|
191
|
+
* const result = await loadOxfmtConfigResult({ cwd: process.cwd() })
|
|
192
|
+
* console.log(result.config)
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
declare function loadOxfmtConfigResult(options?: Options): Promise<LoadOxfmtConfigResult>;
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/ignore.d.ts
|
|
198
|
+
/**
|
|
199
|
+
* Resolve whether a file should be ignored using oxfmt-like CLI semantics.
|
|
200
|
+
*
|
|
201
|
+
* @param options - Ignore resolution options.
|
|
202
|
+
* @returns Ignore status with optional reason.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* import { isOxfmtIgnored } from 'load-oxfmt-config'
|
|
207
|
+
*
|
|
208
|
+
* const result = await isOxfmtIgnored({
|
|
209
|
+
* cwd: process.cwd(),
|
|
210
|
+
* filepath: 'src/generated/file.ts',
|
|
211
|
+
* })
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
declare function isOxfmtIgnored(options: IsOxfmtIgnoredOptions): Promise<IsOxfmtIgnoredResult>;
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/legacy.d.ts
|
|
96
217
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
218
|
+
* Legacy API that returns only the merged config object.
|
|
219
|
+
*
|
|
220
|
+
* Prefer using `loadOxfmtConfigResult` when you also need resolved config metadata.
|
|
99
221
|
*
|
|
100
|
-
* @
|
|
101
|
-
*
|
|
102
|
-
* @
|
|
222
|
+
* @deprecated Prefer `loadOxfmtConfigResult`.
|
|
223
|
+
*
|
|
224
|
+
* @param options - Loader options.
|
|
225
|
+
* @returns Parsed and merged oxfmt options.
|
|
103
226
|
*
|
|
104
227
|
* @example
|
|
105
228
|
* ```ts
|
|
106
|
-
*
|
|
229
|
+
* import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
230
|
+
*
|
|
231
|
+
* // Deprecated: prefer loadOxfmtConfigResult.
|
|
232
|
+
* const config = await loadOxfmtConfig({ cwd: process.cwd() })
|
|
107
233
|
* ```
|
|
108
234
|
*/
|
|
109
235
|
declare function loadOxfmtConfig(options?: Options): Promise<OxfmtOptions>;
|
|
110
236
|
//#endregion
|
|
111
|
-
export { EditorconfigOption, FormatOptionOverride, Options, OxfmtConfigOverride, OxfmtOptions, loadOxfmtConfig, resolveOxfmtrcPath };
|
|
237
|
+
export { EditorconfigOption, FormatOptionOverride, IsOxfmtIgnoredOptions, IsOxfmtIgnoredResult, LoadOxfmtConfigResult, Options, OxfmtConfigOverride, OxfmtOptions, isOxfmtIgnored, loadOxfmtConfig, loadOxfmtConfigResult, resolveOxfmtrcPath };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
1
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
2
|
import process from "node:process";
|
|
4
3
|
import { interopDefault, isBoolean, isNumber, isObject } from "@ntnyq/utils";
|
|
5
|
-
import {
|
|
4
|
+
import { readFile, stat } from "node:fs/promises";
|
|
5
|
+
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
6
6
|
import { parseBuffer } from "editorconfig";
|
|
7
|
+
import ignore from "ignore";
|
|
8
|
+
import picomatch from "picomatch";
|
|
7
9
|
//#region src/constants.ts
|
|
8
10
|
/**
|
|
9
11
|
* Supported configuration files for oxfmt.
|
|
@@ -15,6 +17,46 @@ const OXFMT_CONFIG_FILES = [
|
|
|
15
17
|
"oxfmt.config.ts"
|
|
16
18
|
];
|
|
17
19
|
/**
|
|
20
|
+
* Supported extensions for explicit config paths.
|
|
21
|
+
*/
|
|
22
|
+
const OXFMT_EXPLICIT_CONFIG_EXTENSIONS = [
|
|
23
|
+
".json",
|
|
24
|
+
".jsonc",
|
|
25
|
+
".ts",
|
|
26
|
+
".mts",
|
|
27
|
+
".cts",
|
|
28
|
+
".js",
|
|
29
|
+
".mjs",
|
|
30
|
+
".cjs"
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Default ignored directories used by oxfmt CLI semantics.
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_IGNORED_DIRS = [
|
|
36
|
+
".git",
|
|
37
|
+
".svn",
|
|
38
|
+
".jj",
|
|
39
|
+
"node_modules"
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Common lockfiles ignored by default.
|
|
43
|
+
*
|
|
44
|
+
* Note: oxfmt docs only list package-lock.json and pnpm-lock.yaml explicitly,
|
|
45
|
+
* then mention "etc.". This list mirrors common ecosystem lockfiles.
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_IGNORED_LOCKFILES = [
|
|
48
|
+
"package-lock.json",
|
|
49
|
+
"npm-shrinkwrap.json",
|
|
50
|
+
"pnpm-lock.yaml",
|
|
51
|
+
"yarn.lock",
|
|
52
|
+
"bun.lock",
|
|
53
|
+
"bun.lockb"
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Default ignore files loaded from cwd when --ignore-path is not provided.
|
|
57
|
+
*/
|
|
58
|
+
const DEFAULT_IGNORE_FILES = [".gitignore", ".prettierignore"];
|
|
59
|
+
/**
|
|
18
60
|
* Supported EditorConfig filename.
|
|
19
61
|
*/
|
|
20
62
|
const EDITORCONFIG_FILE = ".editorconfig";
|
|
@@ -23,6 +65,131 @@ const EDITORCONFIG_FILE = ".editorconfig";
|
|
|
23
65
|
*/
|
|
24
66
|
const EDITORCONFIG_GLOBAL_SECTION_NAMES = ["*"];
|
|
25
67
|
//#endregion
|
|
68
|
+
//#region src/config-file.ts
|
|
69
|
+
/**
|
|
70
|
+
* Build a cache key for path resolution (cwd + configPath).
|
|
71
|
+
*
|
|
72
|
+
* @param cwd - Current working directory.
|
|
73
|
+
* @param configPath - Optional config path.
|
|
74
|
+
* @returns Cache key for resolve cache.
|
|
75
|
+
*/
|
|
76
|
+
function getResolveCacheKey(cwd, configPath) {
|
|
77
|
+
return `${cwd}::${configPath || ""}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build a cache key for config content; prefixes missing entries.
|
|
81
|
+
*
|
|
82
|
+
* @param resolvedPath - Resolved oxfmt config path.
|
|
83
|
+
* @param editorconfigPath - Resolved editorconfig path.
|
|
84
|
+
* @param resolveKey - Resolution cache key.
|
|
85
|
+
* @returns Cache key for config content cache.
|
|
86
|
+
*/
|
|
87
|
+
function getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey) {
|
|
88
|
+
return `${resolvedPath || `missing-oxfmt:${resolveKey}`}::${editorconfigPath || `missing-editorconfig:${resolveKey}`}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the oxfmt config file path.
|
|
92
|
+
*
|
|
93
|
+
* - If `configPath` is provided, absolute paths are returned as-is;
|
|
94
|
+
* relative paths are joined to `cwd`.
|
|
95
|
+
* - Otherwise, walk upward from `cwd` to find known filenames.
|
|
96
|
+
*
|
|
97
|
+
* @param cwd - Starting directory for resolution.
|
|
98
|
+
* @param configPath - Optional explicit path.
|
|
99
|
+
* @returns Absolute path to config file, or undefined when not found.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { resolveOxfmtrcPath } from 'load-oxfmt-config'
|
|
104
|
+
*
|
|
105
|
+
* const path = await resolveOxfmtrcPath(process.cwd())
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
async function resolveOxfmtrcPath(cwd, configPath) {
|
|
109
|
+
const resolvedCwd = resolve(cwd);
|
|
110
|
+
if (configPath) return isAbsolute(configPath) ? configPath : join(resolvedCwd, configPath);
|
|
111
|
+
let currentDir = resolvedCwd;
|
|
112
|
+
while (true) {
|
|
113
|
+
for (const filename of OXFMT_CONFIG_FILES) {
|
|
114
|
+
const configFilePath = join(currentDir, filename);
|
|
115
|
+
try {
|
|
116
|
+
if ((await stat(configFilePath)).isFile()) return configFilePath;
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
const parentDir = join(currentDir, "..");
|
|
120
|
+
if (parentDir === currentDir) break;
|
|
121
|
+
currentDir = parentDir;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Read and parse an oxfmt config file.
|
|
126
|
+
*
|
|
127
|
+
* @param resolvedPath - Absolute path to config file.
|
|
128
|
+
* @returns Parsed config object.
|
|
129
|
+
*/
|
|
130
|
+
async function readConfigFromFile(resolvedPath) {
|
|
131
|
+
const extension = extname(resolvedPath);
|
|
132
|
+
if (extension === ".ts" || extension === ".mts" || extension === ".cts" || extension === ".js" || extension === ".mjs" || extension === ".cjs") {
|
|
133
|
+
const mod = await (await interopDefault(import("jiti")))(import.meta.url).import(resolvedPath);
|
|
134
|
+
return mod["default"] ?? mod;
|
|
135
|
+
}
|
|
136
|
+
const content = await readFile(resolvedPath, "utf8");
|
|
137
|
+
if (extension === ".jsonc") {
|
|
138
|
+
const parseErrors = [];
|
|
139
|
+
const parsed = parse(content, parseErrors);
|
|
140
|
+
if (parseErrors.length > 0) {
|
|
141
|
+
const firstError = parseErrors[0];
|
|
142
|
+
const errorCode = firstError === void 0 ? "Unknown" : printParseErrorCode(firstError.error);
|
|
143
|
+
throw new Error(`Invalid JSONC syntax: ${errorCode}`);
|
|
144
|
+
}
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
if (extension === ".json") return JSON.parse(content);
|
|
148
|
+
if (!extension) return JSON.parse(content);
|
|
149
|
+
if (!OXFMT_EXPLICIT_CONFIG_EXTENSIONS.includes(extension)) throw new Error(`Unsupported oxfmt config extension "${extension}" at ${resolvedPath}`);
|
|
150
|
+
return JSON.parse(content);
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/utils.ts
|
|
154
|
+
/**
|
|
155
|
+
* Return a cached promise by key, creating and storing it on miss.
|
|
156
|
+
*
|
|
157
|
+
* If the promise rejects, the cache entry is removed so future calls can retry.
|
|
158
|
+
*
|
|
159
|
+
* @param cache - Map used to store inflight/resolved promises.
|
|
160
|
+
* @param key - Cache key.
|
|
161
|
+
* @param factory - Factory to create the promise when missing.
|
|
162
|
+
* @returns Cached or newly created promise.
|
|
163
|
+
*/
|
|
164
|
+
function cachePromise(cache, key, factory) {
|
|
165
|
+
const cached = cache.get(key);
|
|
166
|
+
if (cached) return cached;
|
|
167
|
+
const task = factory().catch((error) => {
|
|
168
|
+
cache.delete(key);
|
|
169
|
+
throw error;
|
|
170
|
+
});
|
|
171
|
+
cache.set(key, task);
|
|
172
|
+
return task;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Normalize a filesystem path to POSIX-style separators.
|
|
176
|
+
*
|
|
177
|
+
* @param path - Original path.
|
|
178
|
+
* @returns Path using `/` as separator.
|
|
179
|
+
*/
|
|
180
|
+
function toPosixPath(path) {
|
|
181
|
+
return path.replaceAll("\\", "/");
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Split a path into non-empty segments.
|
|
185
|
+
*
|
|
186
|
+
* @param path - Original path.
|
|
187
|
+
* @returns Path segments.
|
|
188
|
+
*/
|
|
189
|
+
function splitPathSegments(path) {
|
|
190
|
+
return path.split(/[\\/]+/u).filter(Boolean);
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
26
193
|
//#region src/editorconfig.ts
|
|
27
194
|
/**
|
|
28
195
|
* Builds the cache key used for resolved EditorConfig lookups.
|
|
@@ -89,15 +256,6 @@ async function resolveEditorconfigPath(startDir, onlyCwd = false) {
|
|
|
89
256
|
}
|
|
90
257
|
}
|
|
91
258
|
/**
|
|
92
|
-
* Normalizes a file path to POSIX separators for glob compatibility.
|
|
93
|
-
*
|
|
94
|
-
* @param path - The path to normalize.
|
|
95
|
-
* @returns The path with forward slashes.
|
|
96
|
-
*/
|
|
97
|
-
function toPosixPath(path) {
|
|
98
|
-
return path.replaceAll("\\", "/");
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
259
|
* Checks whether an EditorConfig section should be treated as a global section.
|
|
102
260
|
*
|
|
103
261
|
* @param sectionName - The section name parsed from .editorconfig.
|
|
@@ -189,7 +347,7 @@ function mapEditorconfigSectionToOptions(section) {
|
|
|
189
347
|
function rebaseEditorconfigPattern(pattern, editorconfigDir, anchorDir) {
|
|
190
348
|
const relativePrefix = toPosixPath(relative(anchorDir, editorconfigDir));
|
|
191
349
|
if (!relativePrefix || relativePrefix === ".") return pattern;
|
|
192
|
-
return `${relativePrefix}/${pattern.replace(
|
|
350
|
+
return `${relativePrefix}/${pattern.replace(/^\//u, "")}`;
|
|
193
351
|
}
|
|
194
352
|
/**
|
|
195
353
|
* Reads a .editorconfig file and converts it into root options and override entries.
|
|
@@ -226,129 +384,231 @@ async function readEditorconfigFromFile(editorconfigPath, anchorDir) {
|
|
|
226
384
|
const resolveCache = /* @__PURE__ */ new Map();
|
|
227
385
|
const configCache = /* @__PURE__ */ new Map();
|
|
228
386
|
/**
|
|
229
|
-
*
|
|
387
|
+
* Resolve config + editorconfig and return merged config with metadata.
|
|
230
388
|
*
|
|
231
|
-
* @param
|
|
232
|
-
* @
|
|
233
|
-
*
|
|
234
|
-
* @
|
|
389
|
+
* @param options - Loader settings.
|
|
390
|
+
* @returns Merged config and optional resolved config metadata.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* import { loadOxfmtConfigResult } from 'load-oxfmt-config'
|
|
395
|
+
*
|
|
396
|
+
* const result = await loadOxfmtConfigResult({ cwd: process.cwd() })
|
|
397
|
+
* console.log(result.config)
|
|
398
|
+
* ```
|
|
235
399
|
*/
|
|
236
|
-
function
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
400
|
+
async function loadOxfmtConfigResult(options = {}) {
|
|
401
|
+
const useCache = options.useCache !== false;
|
|
402
|
+
const cwd = resolve(options.cwd || process.cwd());
|
|
403
|
+
const editorconfig = options.editorconfig ?? true;
|
|
404
|
+
const useEditorconfig = editorconfig !== false;
|
|
405
|
+
const isEditorconfigOptionsObject = useEditorconfig && isObject(editorconfig);
|
|
406
|
+
const onlyCwd = isEditorconfigOptionsObject ? editorconfig.onlyCwd ?? false : false;
|
|
407
|
+
const editorconfigCwd = isEditorconfigOptionsObject && editorconfig.cwd ? resolve(editorconfig.cwd) : void 0;
|
|
408
|
+
const resolveKey = getResolveCacheKey(cwd, options.configPath);
|
|
409
|
+
const editorconfigSearchDir = editorconfigCwd || getEditorconfigSearchDir(cwd, options.configPath);
|
|
410
|
+
const editorconfigResolveKey = editorconfigCwd ? getEditorconfigResolveCacheKey(`${editorconfigCwd}::${options.configPath || ""}`) : getEditorconfigResolveCacheKey(resolveKey);
|
|
411
|
+
const resolvedPath = useCache ? await cachePromise(resolveCache, resolveKey, () => resolveOxfmtrcPath(cwd, options.configPath)) : await resolveOxfmtrcPath(cwd, options.configPath);
|
|
412
|
+
const editorconfigPath = useEditorconfig ? await (useCache ? cachePromise(resolveCache, editorconfigResolveKey, () => resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : void 0;
|
|
413
|
+
const anchorDir = dirname(resolvedPath || editorconfigPath || cwd);
|
|
414
|
+
const loadTask = async () => {
|
|
415
|
+
const oxfmtConfig = resolvedPath ? await readConfigFromFile(resolvedPath).catch((error) => {
|
|
416
|
+
throw new Error(`Failed to parse oxfmt configuration file at ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
417
|
+
}) : {};
|
|
418
|
+
if (!editorconfigPath) return oxfmtConfig;
|
|
419
|
+
const editorconfigData = await readEditorconfigFromFile(editorconfigPath, anchorDir);
|
|
420
|
+
const mergedConfig = mergeRootOptions(oxfmtConfig, editorconfigData.rootOptions);
|
|
421
|
+
const mergedOverrides = mergeOverrides(oxfmtConfig.overrides, editorconfigData.overrides);
|
|
422
|
+
if (!mergedOverrides) return mergedConfig;
|
|
423
|
+
return {
|
|
424
|
+
...mergedConfig,
|
|
425
|
+
overrides: mergedOverrides
|
|
426
|
+
};
|
|
427
|
+
};
|
|
428
|
+
const hasNoConfigSources = !resolvedPath && !editorconfigPath;
|
|
429
|
+
return {
|
|
430
|
+
config: await (async () => {
|
|
431
|
+
if (hasNoConfigSources) return useCache ? await cachePromise(configCache, getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey), () => Promise.resolve({})) : {};
|
|
432
|
+
return useCache ? await cachePromise(configCache, getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey), loadTask) : await loadTask();
|
|
433
|
+
})(),
|
|
434
|
+
...resolvedPath ? {
|
|
435
|
+
filepath: resolvedPath,
|
|
436
|
+
dirname: dirname(resolvedPath)
|
|
437
|
+
} : {}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/ignore.ts
|
|
442
|
+
const ignoreMatcherCache = /* @__PURE__ */ new Map();
|
|
443
|
+
/**
|
|
444
|
+
* Check whether a file is under oxfmt's default ignored directories.
|
|
445
|
+
*
|
|
446
|
+
* @param filepath - Absolute file path to test.
|
|
447
|
+
* @param options - Matching options.
|
|
448
|
+
* @returns True when the path should be ignored by default directory rules.
|
|
449
|
+
*/
|
|
450
|
+
function isDefaultIgnoredDir(filepath, options) {
|
|
451
|
+
const directories = options.withNodeModules ? DEFAULT_IGNORED_DIRS.filter((dir) => dir !== "node_modules") : DEFAULT_IGNORED_DIRS;
|
|
452
|
+
const segments = splitPathSegments(dirname(filepath));
|
|
453
|
+
return directories.some((dir) => segments.includes(dir));
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Check whether a file is a default ignored lockfile.
|
|
457
|
+
*
|
|
458
|
+
* @param filepath - Absolute file path.
|
|
459
|
+
* @returns True when the basename matches a default lockfile name.
|
|
460
|
+
*/
|
|
461
|
+
function isLockfile(filepath) {
|
|
462
|
+
return DEFAULT_IGNORED_LOCKFILES.includes(basename(filepath));
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Read and parse an ignore file into an `ignore` matcher.
|
|
466
|
+
*
|
|
467
|
+
* @param ignoreFilePath - Ignore file path.
|
|
468
|
+
* @param useCache - Whether to cache matcher instances.
|
|
469
|
+
* @returns Parsed matcher, or undefined when file does not exist.
|
|
470
|
+
*/
|
|
471
|
+
function loadIgnoreMatcher(ignoreFilePath, useCache) {
|
|
472
|
+
const loadTask = () => readFile(ignoreFilePath, "utf8").then((content) => {
|
|
473
|
+
const ig = ignore();
|
|
474
|
+
ig.add(content);
|
|
475
|
+
return ig;
|
|
476
|
+
}).catch((error) => {
|
|
477
|
+
const code = error.code;
|
|
478
|
+
if (code === "ENOENT" || code === "ENOTDIR") return;
|
|
241
479
|
throw error;
|
|
242
480
|
});
|
|
243
|
-
|
|
244
|
-
return
|
|
481
|
+
if (!useCache) return loadTask();
|
|
482
|
+
return cachePromise(ignoreMatcherCache, ignoreFilePath, loadTask);
|
|
245
483
|
}
|
|
246
484
|
/**
|
|
247
|
-
* Build a
|
|
485
|
+
* Build a POSIX relative path.
|
|
248
486
|
*
|
|
249
|
-
* @param
|
|
250
|
-
* @param
|
|
251
|
-
* @
|
|
252
|
-
* @returns Cache key for config content.
|
|
487
|
+
* @param from - Base directory.
|
|
488
|
+
* @param to - Target path.
|
|
489
|
+
* @returns POSIX relative path.
|
|
253
490
|
*/
|
|
254
|
-
function
|
|
255
|
-
return
|
|
491
|
+
function relativeSafe(from, to) {
|
|
492
|
+
return toPosixPath(relative(from, to));
|
|
256
493
|
}
|
|
257
494
|
/**
|
|
258
|
-
*
|
|
259
|
-
* - If `configPath` is provided, absolute paths are returned as-is; relative paths are joined to cwd.
|
|
260
|
-
* - Otherwise, walk upward from cwd to find known filenames.
|
|
495
|
+
* Match file path against one ignore file.
|
|
261
496
|
*
|
|
262
|
-
* @param
|
|
263
|
-
* @param
|
|
264
|
-
* @
|
|
497
|
+
* @param filepath - Absolute file path.
|
|
498
|
+
* @param ignoreFilePath - Ignore file path.
|
|
499
|
+
* @param useCache - Whether to use matcher cache.
|
|
500
|
+
* @returns True when ignored by this file.
|
|
265
501
|
*/
|
|
266
|
-
async function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
if ((await stat(configFilePath)).isFile()) return configFilePath;
|
|
274
|
-
} catch {}
|
|
275
|
-
}
|
|
276
|
-
const parentDir = join(currentDir, "..");
|
|
277
|
-
if (parentDir === currentDir) break;
|
|
278
|
-
currentDir = parentDir;
|
|
279
|
-
}
|
|
502
|
+
async function matchIgnoreFile(filepath, ignoreFilePath, useCache) {
|
|
503
|
+
const matcher = await loadIgnoreMatcher(ignoreFilePath, useCache);
|
|
504
|
+
if (!matcher) return false;
|
|
505
|
+
const relativeToIgnore = relativeSafe(dirname(ignoreFilePath), filepath);
|
|
506
|
+
if (relativeToIgnore === ".." || relativeToIgnore.startsWith("../")) return false;
|
|
507
|
+
return matcher.ignores(relativeToIgnore);
|
|
280
508
|
}
|
|
281
509
|
/**
|
|
282
|
-
*
|
|
510
|
+
* Resolve an ignore file path against cwd when needed.
|
|
283
511
|
*
|
|
512
|
+
* @param path - Absolute or relative path.
|
|
284
513
|
* @param cwd - Current working directory.
|
|
285
|
-
* @
|
|
286
|
-
* @returns Cache key for resolve cache.
|
|
514
|
+
* @returns Absolute ignore file path.
|
|
287
515
|
*/
|
|
288
|
-
function
|
|
289
|
-
return
|
|
516
|
+
function resolveIgnoreFilePath(path, cwd) {
|
|
517
|
+
return isAbsolute(path) ? path : resolve(cwd, path);
|
|
290
518
|
}
|
|
291
519
|
/**
|
|
292
|
-
*
|
|
520
|
+
* Match `ignorePatterns` from config with support for negated patterns.
|
|
293
521
|
*
|
|
294
|
-
* @param
|
|
295
|
-
* @
|
|
522
|
+
* @param filepath - Absolute file path.
|
|
523
|
+
* @param configDir - Resolved config directory.
|
|
524
|
+
* @param patterns - Config ignore patterns.
|
|
525
|
+
* @returns True when patterns mark the file as ignored.
|
|
296
526
|
*/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
527
|
+
function matchConfigIgnorePatterns(filepath, configDir, patterns) {
|
|
528
|
+
const relativeFile = relativeSafe(configDir, filepath);
|
|
529
|
+
let ignored = false;
|
|
530
|
+
for (const rawPattern of patterns) {
|
|
531
|
+
if (!rawPattern) continue;
|
|
532
|
+
const isNegative = rawPattern.startsWith("!");
|
|
533
|
+
const pattern = isNegative ? rawPattern.slice(1) : rawPattern;
|
|
534
|
+
if (!pattern) continue;
|
|
535
|
+
if (picomatch(pattern, { dot: true })(relativeFile)) ignored = !isNegative;
|
|
301
536
|
}
|
|
302
|
-
|
|
303
|
-
if (resolvedPath.endsWith(".jsonc")) return parse(content);
|
|
304
|
-
return JSON.parse(content);
|
|
537
|
+
return ignored;
|
|
305
538
|
}
|
|
306
539
|
/**
|
|
307
|
-
*
|
|
308
|
-
* Caching is enabled by default; pass `useCache: false` to force a re-read.
|
|
540
|
+
* Resolve whether a file should be ignored using oxfmt-like CLI semantics.
|
|
309
541
|
*
|
|
310
|
-
* @param options -
|
|
311
|
-
* @returns
|
|
312
|
-
* @throws {Error} when the config file exists but cannot be parsed.
|
|
542
|
+
* @param options - Ignore resolution options.
|
|
543
|
+
* @returns Ignore status with optional reason.
|
|
313
544
|
*
|
|
314
545
|
* @example
|
|
315
546
|
* ```ts
|
|
316
|
-
*
|
|
547
|
+
* import { isOxfmtIgnored } from 'load-oxfmt-config'
|
|
548
|
+
*
|
|
549
|
+
* const result = await isOxfmtIgnored({
|
|
550
|
+
* cwd: process.cwd(),
|
|
551
|
+
* filepath: 'src/generated/file.ts',
|
|
552
|
+
* })
|
|
317
553
|
* ```
|
|
318
554
|
*/
|
|
319
|
-
async function
|
|
555
|
+
async function isOxfmtIgnored(options) {
|
|
320
556
|
const useCache = options.useCache !== false;
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const editorconfigData = await readEditorconfigFromFile(editorconfigPath, anchorDir);
|
|
338
|
-
const mergedConfig = mergeRootOptions(oxfmtConfig, editorconfigData.rootOptions);
|
|
339
|
-
const mergedOverrides = mergeOverrides(oxfmtConfig.overrides, editorconfigData.overrides);
|
|
340
|
-
if (!mergedOverrides) return mergedConfig;
|
|
341
|
-
return {
|
|
342
|
-
...mergedConfig,
|
|
343
|
-
overrides: mergedOverrides
|
|
557
|
+
const includeConfigIgnorePatterns = options.includeConfigIgnorePatterns !== false;
|
|
558
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
559
|
+
const filepath = isAbsolute(options.filepath) ? resolve(options.filepath) : resolve(cwd, options.filepath);
|
|
560
|
+
if (isDefaultIgnoredDir(filepath, options.withNodeModules ? { withNodeModules: true } : {})) return {
|
|
561
|
+
ignored: true,
|
|
562
|
+
reason: "default-dir"
|
|
563
|
+
};
|
|
564
|
+
if (isLockfile(filepath)) return {
|
|
565
|
+
ignored: true,
|
|
566
|
+
reason: "lockfile"
|
|
567
|
+
};
|
|
568
|
+
const explicitIgnorePaths = (typeof options.ignorePath === "string" ? [options.ignorePath] : options.ignorePath)?.map((path) => resolveIgnoreFilePath(path, cwd));
|
|
569
|
+
if (explicitIgnorePaths && explicitIgnorePaths.length > 0) {
|
|
570
|
+
for (const ignoreFilePath of explicitIgnorePaths) if (await matchIgnoreFile(filepath, ignoreFilePath, useCache)) return {
|
|
571
|
+
ignored: true,
|
|
572
|
+
reason: "ignore-path"
|
|
344
573
|
};
|
|
574
|
+
} else for (const ignoreFile of DEFAULT_IGNORE_FILES) if (await matchIgnoreFile(filepath, resolve(cwd, ignoreFile), useCache)) return {
|
|
575
|
+
ignored: true,
|
|
576
|
+
reason: ignoreFile === ".gitignore" ? "gitignore" : "prettierignore"
|
|
345
577
|
};
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
578
|
+
const configResult = await loadOxfmtConfigResult({
|
|
579
|
+
cwd: options.configPath || options.disableNestedConfig ? cwd : dirname(filepath),
|
|
580
|
+
...options.configPath ? { configPath: options.configPath } : {},
|
|
581
|
+
editorconfig: false,
|
|
582
|
+
useCache
|
|
583
|
+
});
|
|
584
|
+
if (includeConfigIgnorePatterns && configResult.dirname && configResult.config.ignorePatterns && configResult.config.ignorePatterns.length > 0 && matchConfigIgnorePatterns(filepath, configResult.dirname, configResult.config.ignorePatterns)) return {
|
|
585
|
+
ignored: true,
|
|
586
|
+
reason: "config-ignore-patterns"
|
|
587
|
+
};
|
|
588
|
+
return { ignored: false };
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/legacy.ts
|
|
592
|
+
/**
|
|
593
|
+
* Legacy API that returns only the merged config object.
|
|
594
|
+
*
|
|
595
|
+
* Prefer using `loadOxfmtConfigResult` when you also need resolved config metadata.
|
|
596
|
+
*
|
|
597
|
+
* @deprecated Prefer `loadOxfmtConfigResult`.
|
|
598
|
+
*
|
|
599
|
+
* @param options - Loader options.
|
|
600
|
+
* @returns Parsed and merged oxfmt options.
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```ts
|
|
604
|
+
* import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
605
|
+
*
|
|
606
|
+
* // Deprecated: prefer loadOxfmtConfigResult.
|
|
607
|
+
* const config = await loadOxfmtConfig({ cwd: process.cwd() })
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
async function loadOxfmtConfig(options = {}) {
|
|
611
|
+
return (await loadOxfmtConfigResult(options)).config;
|
|
352
612
|
}
|
|
353
613
|
//#endregion
|
|
354
|
-
export { loadOxfmtConfig, resolveOxfmtrcPath };
|
|
614
|
+
export { isOxfmtIgnored, loadOxfmtConfig, loadOxfmtConfigResult, resolveOxfmtrcPath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "load-oxfmt-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Load oxfmt config files and merge supported .editorconfig options.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"editorconfig",
|
|
@@ -41,19 +41,22 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@ntnyq/utils": "^0.13.2",
|
|
43
43
|
"editorconfig": "^3.0.2",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
44
|
+
"ignore": "^7.0.5",
|
|
45
|
+
"jiti": "^2.7.0",
|
|
46
|
+
"jsonc-parser": "^3.3.1",
|
|
47
|
+
"picomatch": "^4.0.4"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"@ntnyq/tsconfig": "^3.1.0",
|
|
49
51
|
"@types/node": "^25.6.0",
|
|
50
|
-
"@
|
|
52
|
+
"@types/picomatch": "^4.0.3",
|
|
53
|
+
"@typescript/native-preview": "^7.0.0-dev.20260506.1",
|
|
51
54
|
"bumpp": "^11.0.1",
|
|
52
55
|
"husky": "^9.1.7",
|
|
53
56
|
"nano-staged": "^1.0.2",
|
|
54
57
|
"npm-run-all2": "^8.0.4",
|
|
55
|
-
"oxfmt": "^0.
|
|
56
|
-
"oxlint": "^1.
|
|
58
|
+
"oxfmt": "^0.48.0",
|
|
59
|
+
"oxlint": "^1.63.0",
|
|
57
60
|
"tsdown": "^0.21.10",
|
|
58
61
|
"vitest": "^4.1.5"
|
|
59
62
|
},
|