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 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 { loadOxfmtConfig } from 'load-oxfmt-config'
41
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
41
42
 
42
43
  // Automatically searches for oxfmt config files and the nearest .editorconfig
43
- const config = await loadOxfmtConfig()
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 { loadOxfmtConfig } from 'load-oxfmt-config'
51
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
51
52
 
52
- const config = await loadOxfmtConfig({ cwd: '/path/to/project' })
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 { loadOxfmtConfig } from 'load-oxfmtConfig'
69
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
69
70
 
70
- const config = await loadOxfmtConfig({
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 { loadOxfmtConfig } from 'load-oxfmt-config'
105
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
79
106
 
80
107
  // Relative path (resolved relative to cwd)
81
- const config = await loadOxfmtConfig({
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 config = await loadOxfmtConfig({
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 { loadOxfmtConfig } from 'load-oxfmt-config'
122
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
96
123
 
97
124
  // Skip .editorconfig reading entirely
98
- const config = await loadOxfmtConfig({
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 { loadOxfmtConfig } from 'load-oxfmt-config'
133
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
107
134
 
108
135
  // Only look in the cwd directory itself, no upward traversal
109
- const config = await loadOxfmtConfig({
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 { loadOxfmtConfig } from 'load-oxfmt-config'
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 config = await loadOxfmtConfig({
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 { loadOxfmtConfig } from 'load-oxfmt-config'
157
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
131
158
 
132
159
  // Force reload from disk, bypassing cache
133
- const config = await loadOxfmtConfig({
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
- **Returns:** `Promise<FormatOptions>` - Parsed and merged oxfmt configuration object. Returns empty object `{}` if no supported config file is found.
196
+ Option fields:
159
197
 
160
- **Throws:** Error if config file exists but cannot be parsed.
198
+ #### `cwd`
161
199
 
162
- ### `resolveOxfmtrcPath(cwd, configPath?)`
200
+ - **Type:** `string`
201
+ - **Default:** `process.cwd()`
163
202
 
164
- Resolve the absolute path to oxfmt config file.
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
- **Parameters:**
205
+ #### `configPath`
167
206
 
168
- - `cwd` - Starting directory for resolution
169
- - `configPath` - Optional explicit path (absolute or relative to cwd)
207
+ - **Type:** `string`
208
+ - **Default:** `undefined`
170
209
 
171
- **Returns:** `Promise<string | undefined>` - Absolute path to config file, or `undefined` if not found.
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
- ## Options
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
- All options are optional.
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
- ### `cwd`
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
- ### `configPath`
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
- ### `useCache`
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
- ### `editorconfig`
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` object. That means `.editorconfig` support is represented as a merged config shape, 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.
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 { loadOxfmtConfig } from 'load-oxfmt-config'
521
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
314
522
 
315
523
  try {
316
- const config = await loadOxfmtConfig()
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/core.d.ts
159
+ //#region src/config-file.d.ts
86
160
  /**
87
161
  * Resolve the oxfmt config file path.
88
- * - If `configPath` is provided, absolute paths are returned as-is; relative paths are joined to cwd.
89
- * - Otherwise, walk upward from cwd to find known filenames.
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 (absolute or relative to cwd).
93
- * @returns Absolute path to the config file, or undefined when not found.
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
- * Load oxfmt configuration: resolve the file path, then read and parse it.
98
- * Caching is enabled by default; pass `useCache: false` to force a re-read.
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
- * @param options - Optional loader settings (cwd, configPath, useCache).
101
- * @returns Parsed oxfmt OxfmtOptions or an empty object when missing.
102
- * @throws {Error} when the config file exists but cannot be parsed.
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
- * const config = await loadOxfmtConfig({ cwd: '/project' })
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 { readFile, stat } from "node:fs/promises";
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 { parse } from "jsonc-parser";
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
- * Return a cached promise by key, creating and storing it on miss; failures clear the entry.
387
+ * Resolve config + editorconfig and return merged config with metadata.
230
388
  *
231
- * @param cache - Map used to store inflight/resolved promises.
232
- * @param key - Cache key.
233
- * @param factory - Factory to create the promise when missing.
234
- * @returns Cached or newly created promise.
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 cachePromise(cache, key, factory) {
237
- const cached = cache.get(key);
238
- if (cached) return cached;
239
- const task = factory().catch((error) => {
240
- cache.delete(key);
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
- cache.set(key, task);
244
- return task;
481
+ if (!useCache) return loadTask();
482
+ return cachePromise(ignoreMatcherCache, ignoreFilePath, loadTask);
245
483
  }
246
484
  /**
247
- * Build a cache key for config content; prefixes missing entries with `missing:`.
485
+ * Build a POSIX relative path.
248
486
  *
249
- * @param resolvedPath - Resolved config path or undefined when missing.
250
- * @param editorconfigPath - Resolved editorconfig path or undefined when missing.
251
- * @param resolveKey - Key used for path resolution caching.
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 getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey) {
255
- return `${resolvedPath || `missing-oxfmt:${resolveKey}`}::${editorconfigPath || `missing-editorconfig:${resolveKey}`}`;
491
+ function relativeSafe(from, to) {
492
+ return toPosixPath(relative(from, to));
256
493
  }
257
494
  /**
258
- * Resolve the oxfmt config file path.
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 cwd - Starting directory for resolution.
263
- * @param configPath - Optional explicit path (absolute or relative to cwd).
264
- * @returns Absolute path to the config file, or undefined when not found.
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 resolveOxfmtrcPath(cwd, configPath) {
267
- if (configPath) return isAbsolute(configPath) ? configPath : join(cwd, configPath);
268
- let currentDir = cwd;
269
- while (true) {
270
- for (const filename of OXFMT_CONFIG_FILES) {
271
- const configFilePath = join(currentDir, filename);
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
- * Build a cache key for path resolution (cwd + configPath).
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
- * @param configPath - Optional config path.
286
- * @returns Cache key for resolve cache.
514
+ * @returns Absolute ignore file path.
287
515
  */
288
- function getResolveCacheKey(cwd, configPath) {
289
- return `${cwd}::${configPath || ""}`;
516
+ function resolveIgnoreFilePath(path, cwd) {
517
+ return isAbsolute(path) ? path : resolve(cwd, path);
290
518
  }
291
519
  /**
292
- * Read and parse config file, supporting JSON, JSONC, and TypeScript/JavaScript.
520
+ * Match `ignorePatterns` from config with support for negated patterns.
293
521
  *
294
- * @param resolvedPath - Absolute path to the config file.
295
- * @returns Parsed OxfmtOptions object.
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
- async function readConfigFromFile(resolvedPath) {
298
- if (resolvedPath.endsWith(".ts")) {
299
- const mod = await (await interopDefault(import("jiti")))(import.meta.url).import(resolvedPath);
300
- return mod["default"] ?? mod;
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
- const content = await readFile(resolvedPath, "utf8");
303
- if (resolvedPath.endsWith(".jsonc")) return parse(content);
304
- return JSON.parse(content);
537
+ return ignored;
305
538
  }
306
539
  /**
307
- * Load oxfmt configuration: resolve the file path, then read and parse it.
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 - Optional loader settings (cwd, configPath, useCache).
311
- * @returns Parsed oxfmt OxfmtOptions or an empty object when missing.
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
- * const config = await loadOxfmtConfig({ cwd: '/project' })
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 loadOxfmtConfig(options = {}) {
555
+ async function isOxfmtIgnored(options) {
320
556
  const useCache = options.useCache !== false;
321
- const cwd = options.cwd || process.cwd();
322
- const editorconfig = options.editorconfig ?? true;
323
- const useEditorconfig = editorconfig !== false;
324
- const onlyCwd = useEditorconfig && isObject(editorconfig) ? editorconfig.onlyCwd ?? false : false;
325
- const editorconfigCwd = useEditorconfig && isObject(editorconfig) ? editorconfig.cwd : void 0;
326
- const resolveKey = getResolveCacheKey(cwd, options.configPath);
327
- const editorconfigSearchDir = editorconfigCwd || getEditorconfigSearchDir(cwd, options.configPath);
328
- const editorconfigResolveKey = editorconfigCwd ? getEditorconfigResolveCacheKey(`${editorconfigCwd}::${options.configPath || ""}`) : getEditorconfigResolveCacheKey(resolveKey);
329
- const resolvedPath = useCache ? await cachePromise(resolveCache, resolveKey, () => resolveOxfmtrcPath(cwd, options.configPath)) : await resolveOxfmtrcPath(cwd, options.configPath);
330
- const editorconfigPath = useEditorconfig ? await (useCache ? cachePromise(resolveCache, editorconfigResolveKey, () => resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : void 0;
331
- const anchorDir = dirname(resolvedPath || editorconfigPath || cwd);
332
- const loadTask = async () => {
333
- const oxfmtConfig = resolvedPath ? await readConfigFromFile(resolvedPath).catch((error) => {
334
- throw new Error(`Failed to parse oxfmt configuration file at ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
335
- }) : {};
336
- if (!editorconfigPath) return oxfmtConfig;
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
- if (!resolvedPath && !editorconfigPath) {
347
- if (!useCache) return {};
348
- return cachePromise(configCache, getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey), () => Promise.resolve({}));
349
- }
350
- if (!useCache) return loadTask();
351
- return cachePromise(configCache, getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey), loadTask);
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.4.1",
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
- "jiti": "^2.6.1",
45
- "jsonc-parser": "^3.3.1"
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
- "@typescript/native-preview": "^7.0.0-dev.20260426.1",
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.46.0",
56
- "oxlint": "^1.61.0",
58
+ "oxfmt": "^0.48.0",
59
+ "oxlint": "^1.63.0",
57
60
  "tsdown": "^0.21.10",
58
61
  "vitest": "^4.1.5"
59
62
  },