load-oxfmt-config 0.7.2 → 0.9.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
@@ -5,12 +5,12 @@
5
5
  [![NPM DOWNLOADS](https://img.shields.io/npm/dy/load-oxfmt-config.svg)](https://www.npmjs.com/package/load-oxfmt-config)
6
6
  [![LICENSE](https://img.shields.io/github/license/ntnyq/load-oxfmt-config.svg)](https://github.com/ntnyq/load-oxfmt-config/blob/main/LICENSE)
7
7
 
8
- > Load and resolve oxfmt configuration files and merge supported `.editorconfig` settings for [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html).
8
+ > Load and resolve oxfmt configuration files, including explicit JS/TS config paths, and merge supported `.editorconfig` settings for [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html).
9
9
 
10
10
  ## Features
11
11
 
12
12
  - 🔍 **Auto-discovery** - Automatically searches for config files in current and parent directories
13
- - 📦 **Multiple formats** - Supports `.oxfmtrc.json`, `.oxfmtrc.jsonc`, and `oxfmt.config.ts`
13
+ - 📦 **Multiple formats** - Auto-discovers `.oxfmtrc.json`, `.oxfmtrc.jsonc`, and `oxfmt.config.ts`, and also supports explicit `.js` / `.mjs` / `.cjs` / `.mts` / `.cts` config paths
14
14
  - 🧩 **EditorConfig fallback** - Merges supported `.editorconfig` fields into the returned oxfmt config result
15
15
  - 🚫 **Ignore resolution** - Resolves ignore status with oxfmt CLI-like global + config-scoped semantics
16
16
  - ⚡ **Built-in caching** - Caches both file resolution and parsed configs for optimal performance
@@ -31,6 +31,8 @@ yarn add load-oxfmt-config
31
31
  pnpm add load-oxfmt-config
32
32
  ```
33
33
 
34
+ > `oxfmt` is a peer dependency and should be installed alongside this package.
35
+
34
36
  ## Usage
35
37
 
36
38
  ### Basic Usage
@@ -38,19 +40,19 @@ pnpm add load-oxfmt-config
38
40
  Load config from current directory or parent directories:
39
41
 
40
42
  ```ts
41
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
43
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
42
44
 
43
45
  // Automatically searches for oxfmt config files and the nearest .editorconfig
44
- const result = await loadOxfmtConfigResult()
46
+ const result = await loadOxfmtConfig()
45
47
  console.log(result.config) // { printWidth: 80, ... }
46
48
  ```
47
49
 
48
50
  ### Merge With `.editorconfig`
49
51
 
50
52
  ```ts
51
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
53
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
52
54
 
53
- const result = await loadOxfmtConfigResult({ cwd: '/path/to/project' })
55
+ const result = await loadOxfmtConfig({ cwd: '/path/to/project' })
54
56
 
55
57
  // Returns one merged static config object
56
58
  console.log(result.config)
@@ -66,9 +68,9 @@ console.log(result.config)
66
68
  ### Specify Working Directory
67
69
 
68
70
  ```ts
69
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
71
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
70
72
 
71
- const result = await loadOxfmtConfigResult({
73
+ const result = await loadOxfmtConfig({
72
74
  cwd: '/path/to/project',
73
75
  })
74
76
  ```
@@ -76,9 +78,9 @@ const result = await loadOxfmtConfigResult({
76
78
  ### Get Config Metadata
77
79
 
78
80
  ```ts
79
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
81
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
80
82
 
81
- const result = await loadOxfmtConfigResult({ cwd: '/path/to/project' })
83
+ const result = await loadOxfmtConfig({ cwd: '/path/to/project' })
82
84
 
83
85
  console.log(result.config) // merged oxfmt options
84
86
  console.log(result.filepath) // /path/to/project/.oxfmtrc.json (or undefined)
@@ -102,16 +104,16 @@ console.log(result)
102
104
  ### Explicit Config Path
103
105
 
104
106
  ```ts
105
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
107
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
106
108
 
107
109
  // Relative path (resolved relative to cwd)
108
- const result = await loadOxfmtConfigResult({
110
+ const result = await loadOxfmtConfig({
109
111
  configPath: 'configs/.oxfmtrc.json',
110
112
  cwd: '/path/to/project',
111
113
  })
112
114
 
113
115
  // Absolute path
114
- const absoluteResult = await loadOxfmtConfigResult({
116
+ const absoluteResult = await loadOxfmtConfig({
115
117
  configPath: '/absolute/path/to/.oxfmtrc.json',
116
118
  })
117
119
  ```
@@ -119,10 +121,10 @@ const absoluteResult = await loadOxfmtConfigResult({
119
121
  ### Disable `.editorconfig`
120
122
 
121
123
  ```ts
122
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
124
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
123
125
 
124
126
  // Skip .editorconfig reading entirely
125
- const result = await loadOxfmtConfigResult({
127
+ const result = await loadOxfmtConfig({
126
128
  editorconfig: false,
127
129
  })
128
130
  ```
@@ -130,10 +132,10 @@ const result = await loadOxfmtConfigResult({
130
132
  ### Limit `.editorconfig` to `cwd`
131
133
 
132
134
  ```ts
133
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
135
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
134
136
 
135
137
  // Only look in the cwd directory itself, no upward traversal
136
- const result = await loadOxfmtConfigResult({
138
+ const result = await loadOxfmtConfig({
137
139
  editorconfig: { onlyCwd: true },
138
140
  })
139
141
  ```
@@ -141,10 +143,10 @@ const result = await loadOxfmtConfigResult({
141
143
  ### Override `.editorconfig` Search Directory
142
144
 
143
145
  ```ts
144
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
146
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
145
147
 
146
148
  // Search .editorconfig from a custom directory instead of the config file's directory
147
- const result = await loadOxfmtConfigResult({
149
+ const result = await loadOxfmtConfig({
148
150
  editorconfig: {
149
151
  cwd: '/path/to/editorconfig-dir',
150
152
  },
@@ -154,23 +156,14 @@ const result = await loadOxfmtConfigResult({
154
156
  ### Disable Caching
155
157
 
156
158
  ```ts
157
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
159
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
158
160
 
159
161
  // Force reload from disk, bypassing cache
160
- const result = await loadOxfmtConfigResult({
162
+ const result = await loadOxfmtConfig({
161
163
  useCache: false,
162
164
  })
163
165
  ```
164
166
 
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
-
174
167
  ### Path Resolution Only
175
168
 
176
169
  ```ts
@@ -185,9 +178,7 @@ console.log(configPath) // '/path/to/.oxfmtrc.json' or undefined
185
178
 
186
179
  ### `loadOxfmtConfig(options?)`
187
180
 
188
- > Deprecated. Prefer `loadOxfmtConfigResult(options?)`.
189
-
190
- Load and parse oxfmt configuration files, then merge supported `.editorconfig` fields into the returned result.
181
+ Load and parse oxfmt configuration files, merge supported `.editorconfig` fields, and return metadata for the resolved config file.
191
182
 
192
183
  **Parameters:**
193
184
 
@@ -200,66 +191,16 @@ Option fields:
200
191
  - **Type:** `string`
201
192
  - **Default:** `process.cwd()`
202
193
 
203
- Current working directory to start searching for config files. The loader will walk up from this directory to find a config file.
194
+ Current working directory for config resolution.
195
+ By default config discovery starts here, unless `filepath` is provided.
204
196
 
205
- #### `configPath`
197
+ #### `filepath`
206
198
 
207
199
  - **Type:** `string`
208
200
  - **Default:** `undefined`
209
201
 
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
226
-
227
- Set to `false` to force reload from disk on every call.
228
-
229
- #### `editorconfig`
230
-
231
- - **Type:** `boolean | EditorconfigOption`
232
- - **Default:** `true`
233
-
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:
239
-
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 (`LoadOxfmtConfigOptions`)
254
-
255
- Option fields:
256
-
257
- #### `cwd`
258
-
259
- - **Type:** `string`
260
- - **Default:** `process.cwd()`
261
-
262
- Current working directory to start searching for config files. The loader will walk up from this directory to find a config file.
202
+ Target file path for nested config resolution.
203
+ When provided (and `configPath` is not set), config discovery starts from `dirname(filepath)` and walks upward to match oxfmt per-file semantics.
263
204
 
264
205
  #### `configPath`
265
206
 
@@ -272,6 +213,14 @@ Explicit path to the config file:
272
213
  - **Absolute path:** Used as-is
273
214
  - **When provided:** Skips auto-discovery and uses this path directly
274
215
 
216
+ #### `disableNestedConfig`
217
+
218
+ - **Type:** `boolean`
219
+ - **Default:** `false`
220
+
221
+ Disable nested config lookup.
222
+ When `true`, config discovery is anchored to `cwd` (or explicit `configPath`) instead of `filepath`.
223
+
275
224
  #### `useCache`
276
225
 
277
226
  - **Type:** `boolean`
@@ -292,7 +241,9 @@ Set to `false` to force reload from disk on every call.
292
241
 
293
242
  Control how `.editorconfig` files are read and merged:
294
243
 
295
- - **`true`** — Read and merge the nearest `.editorconfig`, walking up from the config file's directory (or `cwd` when no config path is given).
244
+ - **`true`** — Read and merge the nearest `.editorconfig`, walking up from the config-discovery start directory:
245
+ - `dirname(filepath)` when nested lookup is enabled and `filepath` is provided
246
+ - otherwise `cwd`
296
247
  - **`false`** — Disable `.editorconfig` reading entirely.
297
248
  - **`EditorconfigOption`** — Enable with additional settings:
298
249
 
@@ -336,7 +287,7 @@ Option fields:
336
287
  - **Default:** `process.cwd()`
337
288
 
338
289
  Current working directory.
339
- Also the base directory for default `.gitignore`/`.prettierignore` lookup.
290
+ Also the base directory for default `.prettierignore` lookup.
340
291
 
341
292
  #### `filepath`
342
293
 
@@ -358,7 +309,7 @@ When provided, nested config lookup is disabled (same as oxfmt CLI `-c`).
358
309
  - **Type:** `string | string[]`
359
310
  - **Default:** `undefined`
360
311
 
361
- Ignore files to use instead of cwd `.gitignore`/`.prettierignore`.
312
+ Ignore files to use instead of default `.gitignore` hierarchy + cwd `.prettierignore`.
362
313
  Can be passed multiple times in CLI style.
363
314
 
364
315
  #### `withNodeModules`
@@ -404,6 +355,7 @@ When `false`, `isOxfmtIgnored()` only applies global ignore and skips config loa
404
355
  - `default-dir`
405
356
  - `lockfile`
406
357
  - `gitignore`
358
+ - `git-info-exclude`
407
359
  - `prettierignore`
408
360
  - `ignore-path`
409
361
  - `config-ignore-patterns`
@@ -412,7 +364,7 @@ When `false`, `isOxfmtIgnored()` only applies global ignore and skips config loa
412
364
 
413
365
  When `configPath` is not provided, the loader automatically searches for config files:
414
366
 
415
- 1. **Search order:** Starts from `cwd` and walks up to parent directories
367
+ 1. **Search order:** Starts from `dirname(filepath)` when `filepath` is provided **and nested lookup is enabled**; otherwise starts from `cwd`, then walks up to parent directories
416
368
  2. **Supported filenames:**
417
369
  - `.oxfmtrc.json`
418
370
  - `.oxfmtrc.jsonc`
@@ -420,7 +372,7 @@ When `configPath` is not provided, the loader automatically searches for config
420
372
  3. **Stops when:**
421
373
  - A valid config file is found
422
374
  - Reaches the filesystem root
423
- 4. **EditorConfig:** The nearest `.editorconfig` is also resolved and merged into the returned result
375
+ 4. **EditorConfig:** The nearest `.editorconfig` is resolved from the same start directory and merged into the returned result
424
376
  5. **Returns:** Empty object `{}` if no config file is found
425
377
 
426
378
  ## Supported Config Formats
@@ -495,16 +447,15 @@ Global ignore includes:
495
447
  - Default lockfiles: `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `bun.lockb`
496
448
  - Ignore files:
497
449
  - If `ignorePath` is provided: use those files only (multiple supported)
498
- - If `ignorePath` is not provided: use `.gitignore` and `.prettierignore` from `cwd`
450
+ - If `ignorePath` is not provided:
451
+ - Read `.gitignore` from the file's directory upward until the git repo boundary
452
+ - Read `<repo>/.git/info/exclude` when inside a git repo
453
+ - Read `.prettierignore` from `cwd`
499
454
 
500
455
  Notes:
501
456
 
502
457
  - `node_modules` can be included by passing `withNodeModules: true`.
503
458
 
504
- Type compatibility note:
505
-
506
- - `Options` is still exported as a deprecated alias of `LoadOxfmtConfigOptions`.
507
- - This package does not read parent `.gitignore` files or global gitignore settings.
508
459
  - 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.
509
460
  - `ignorePatterns` are always interpreted relative to the resolved oxfmt config directory.
510
461
  - `includeConfigIgnorePatterns` defaults to `true` to preserve current behavior.
@@ -513,6 +464,7 @@ Type compatibility note:
513
464
  - default: nearest config from target file directory upward
514
465
  - `disableNestedConfig: true`: resolve from `cwd` only
515
466
  - `configPath`: also disables nested lookup (same intent as CLI `-c`)
467
+ - invalid nested config only fails files that resolve to that config (no project-wide pre-scan)
516
468
 
517
469
  ## Precedence
518
470
 
@@ -527,18 +479,18 @@ This means explicit oxfmt config values always win over `.editorconfig` fallback
527
479
 
528
480
  ## Limitations
529
481
 
530
- `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.
482
+ `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.
531
483
 
532
484
  ## Error Handling
533
485
 
534
486
  ```ts
535
- import { loadOxfmtConfigResult } from 'load-oxfmt-config'
487
+ import { loadOxfmtConfig } from 'load-oxfmt-config'
536
488
 
537
489
  try {
538
- const result = await loadOxfmtConfigResult()
490
+ const result = await loadOxfmtConfig()
539
491
  console.log(result.config)
540
492
  } catch (error) {
541
- // Thrown when config file exists but contains invalid JSON/JSONC
493
+ // Thrown when a resolved config file cannot be parsed or loaded
542
494
  console.error('Failed to parse oxfmt config:', error.message)
543
495
  }
544
496
  ```
@@ -553,6 +505,7 @@ The caching system maintains two separate caches:
553
505
  **Cache keys are based on:**
554
506
 
555
507
  - `cwd` + `configPath` for path resolution
508
+ - or `dirname(filepath)` + `configPath` when `filepath` is provided and nested lookup is enabled
556
509
  - Resolved oxfmt path and resolved `.editorconfig` path for config content
557
510
 
558
511
  **Cache invalidation:**
package/dist/index.d.mts CHANGED
@@ -48,6 +48,19 @@ interface LoadOxfmtConfigOptions {
48
48
  * Current working directory
49
49
  */
50
50
  cwd?: string;
51
+ /**
52
+ * File path being formatted. When provided (and `configPath` is not set),
53
+ * config discovery starts from this file's directory to match oxfmt nested
54
+ * config behavior.
55
+ */
56
+ filepath?: string;
57
+ /**
58
+ * Disable nested config lookup.
59
+ * When true, config discovery is anchored to `cwd` (or explicit `configPath`).
60
+ *
61
+ * @default false
62
+ */
63
+ disableNestedConfig?: boolean;
51
64
  /**
52
65
  * Whether to use cache
53
66
  */
@@ -100,7 +113,7 @@ interface LoadOxfmtConfigResult {
100
113
  interface IsOxfmtIgnoredOptions {
101
114
  /**
102
115
  * Current working directory.
103
- * Also the base directory for default .gitignore/.prettierignore lookup.
116
+ * Also the base directory for default `.prettierignore` lookup.
104
117
  */
105
118
  cwd?: string;
106
119
  /**
@@ -113,7 +126,7 @@ interface IsOxfmtIgnoredOptions {
113
126
  */
114
127
  configPath?: string;
115
128
  /**
116
- * Ignore files to use instead of cwd .gitignore/.prettierignore.
129
+ * Ignore files to use instead of default `.gitignore` hierarchy + cwd `.prettierignore`.
117
130
  * Can be passed multiple times in CLI style.
118
131
  */
119
132
  ignorePath?: string | string[];
@@ -155,16 +168,8 @@ interface IsOxfmtIgnoredResult {
155
168
  /**
156
169
  * Matched ignore source.
157
170
  */
158
- reason?: 'default-dir' | 'lockfile' | 'gitignore' | 'prettierignore' | 'ignore-path' | 'config-ignore-patterns';
171
+ reason?: 'default-dir' | 'lockfile' | 'gitignore' | 'git-info-exclude' | 'prettierignore' | 'ignore-path' | 'config-ignore-patterns';
159
172
  }
160
- /**
161
- * @deprecated Use `OxfmtConfigOverride` instead
162
- */
163
- type FormatOptionOverride = OxfmtConfigOverride;
164
- /**
165
- * @deprecated Use `LoadOxfmtConfigOptions` instead.
166
- */
167
- type Options = LoadOxfmtConfigOptions;
168
173
  //#endregion
169
174
  //#region src/config-file.d.ts
170
175
  /**
@@ -196,13 +201,13 @@ declare function resolveOxfmtrcPath(cwd: string, configPath?: string): Promise<s
196
201
  *
197
202
  * @example
198
203
  * ```ts
199
- * import { loadOxfmtConfigResult } from 'load-oxfmt-config'
204
+ * import { loadOxfmtConfig } from 'load-oxfmt-config'
200
205
  *
201
- * const result = await loadOxfmtConfigResult({ cwd: process.cwd() })
206
+ * const result = await loadOxfmtConfig({ cwd: process.cwd() })
202
207
  * console.log(result.config)
203
208
  * ```
204
209
  */
205
- declare function loadOxfmtConfigResult(options?: LoadOxfmtConfigOptions): Promise<LoadOxfmtConfigResult>;
210
+ declare function loadOxfmtConfig(options?: LoadOxfmtConfigOptions): Promise<LoadOxfmtConfigResult>;
206
211
  //#endregion
207
212
  //#region src/ignore.d.ts
208
213
  /**
@@ -223,25 +228,4 @@ declare function loadOxfmtConfigResult(options?: LoadOxfmtConfigOptions): Promis
223
228
  */
224
229
  declare function isOxfmtIgnored(options: IsOxfmtIgnoredOptions): Promise<IsOxfmtIgnoredResult>;
225
230
  //#endregion
226
- //#region src/legacy.d.ts
227
- /**
228
- * Legacy API that returns only the merged config object.
229
- *
230
- * Prefer using `loadOxfmtConfigResult` when you also need resolved config metadata.
231
- *
232
- * @deprecated Prefer `loadOxfmtConfigResult`.
233
- *
234
- * @param options - Loader options.
235
- * @returns Parsed and merged oxfmt options.
236
- *
237
- * @example
238
- * ```ts
239
- * import { loadOxfmtConfig } from 'load-oxfmt-config'
240
- *
241
- * // Deprecated: prefer loadOxfmtConfigResult.
242
- * const config = await loadOxfmtConfig({ cwd: process.cwd() })
243
- * ```
244
- */
245
- declare function loadOxfmtConfig(options?: LoadOxfmtConfigOptions): Promise<OxfmtOptions>;
246
- //#endregion
247
- export { EditorconfigOption, FormatOptionOverride, IsOxfmtIgnoredOptions, IsOxfmtIgnoredResult, LoadOxfmtConfigOptions, LoadOxfmtConfigResult, Options, OxfmtConfigOverride, OxfmtOptions, isOxfmtIgnored, loadOxfmtConfig, loadOxfmtConfigResult, resolveOxfmtrcPath };
231
+ export { EditorconfigOption, IsOxfmtIgnoredOptions, IsOxfmtIgnoredResult, LoadOxfmtConfigOptions, LoadOxfmtConfigResult, OxfmtConfigOverride, OxfmtOptions, isOxfmtIgnored, loadOxfmtConfig, resolveOxfmtrcPath };
package/dist/index.mjs CHANGED
@@ -53,10 +53,6 @@ const DEFAULT_IGNORED_LOCKFILES = [
53
53
  "bun.lockb"
54
54
  ];
55
55
  /**
56
- * Default ignore files loaded from cwd when --ignore-path is not provided.
57
- */
58
- const DEFAULT_IGNORE_FILES = [".gitignore", ".prettierignore"];
59
- /**
60
56
  * Supported EditorConfig filename.
61
57
  */
62
58
  const EDITORCONFIG_FILE = ".editorconfig";
@@ -394,24 +390,26 @@ const configCache = /* @__PURE__ */ new Map();
394
390
  *
395
391
  * @example
396
392
  * ```ts
397
- * import { loadOxfmtConfigResult } from 'load-oxfmt-config'
393
+ * import { loadOxfmtConfig } from 'load-oxfmt-config'
398
394
  *
399
- * const result = await loadOxfmtConfigResult({ cwd: process.cwd() })
395
+ * const result = await loadOxfmtConfig({ cwd: process.cwd() })
400
396
  * console.log(result.config)
401
397
  * ```
402
398
  */
403
- async function loadOxfmtConfigResult(options = {}) {
399
+ async function loadOxfmtConfig(options = {}) {
404
400
  const useCache = options.useCache !== false;
405
401
  const cwd = resolve(options.cwd || process.cwd());
402
+ const filepath = options.filepath ? resolve(cwd, options.filepath) : void 0;
403
+ const configLookupCwd = options.configPath || options.disableNestedConfig || !filepath ? cwd : dirname(filepath);
406
404
  const editorconfig = options.editorconfig ?? true;
407
405
  const useEditorconfig = editorconfig !== false;
408
406
  const isEditorconfigOptionsObject = useEditorconfig && isObject(editorconfig);
409
407
  const onlyCwd = isEditorconfigOptionsObject ? editorconfig.onlyCwd ?? false : false;
410
408
  const editorconfigCwd = isEditorconfigOptionsObject && editorconfig.cwd ? resolve(editorconfig.cwd) : void 0;
411
- const resolveKey = getResolveCacheKey(cwd, options.configPath);
412
- const editorconfigSearchDir = editorconfigCwd || getEditorconfigSearchDir(cwd, options.configPath);
409
+ const resolveKey = getResolveCacheKey(configLookupCwd, options.configPath);
410
+ const editorconfigSearchDir = editorconfigCwd || getEditorconfigSearchDir(configLookupCwd, options.configPath);
413
411
  const editorconfigResolveKey = editorconfigCwd ? getEditorconfigResolveCacheKey(`${editorconfigCwd}::${options.configPath || ""}`) : getEditorconfigResolveCacheKey(resolveKey);
414
- const resolvedPath = useCache ? await cachePromise(resolveCache, resolveKey, () => resolveOxfmtrcPath(cwd, options.configPath)) : await resolveOxfmtrcPath(cwd, options.configPath);
412
+ const resolvedPath = useCache ? await cachePromise(resolveCache, resolveKey, () => resolveOxfmtrcPath(configLookupCwd, options.configPath)) : await resolveOxfmtrcPath(configLookupCwd, options.configPath);
415
413
  const editorconfigPath = useEditorconfig ? await (useCache ? cachePromise(resolveCache, editorconfigResolveKey, () => resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : void 0;
416
414
  const anchorDir = dirname(resolvedPath || editorconfigPath || cwd);
417
415
  const loadTask = async () => {
@@ -500,12 +498,13 @@ function relativeSafe(from, to) {
500
498
  * @param filepath - Absolute file path.
501
499
  * @param ignoreFilePath - Ignore file path.
502
500
  * @param useCache - Whether to use matcher cache.
501
+ * @param baseDir - Base directory for relative path calculation, defaults to ignore file directory.
503
502
  * @returns True when ignored by this file.
504
503
  */
505
- async function matchIgnoreFile(filepath, ignoreFilePath, useCache) {
504
+ async function matchIgnoreFile(filepath, ignoreFilePath, useCache, baseDir) {
506
505
  const matcher = await loadIgnoreMatcher(ignoreFilePath, useCache);
507
506
  if (!matcher) return false;
508
- const relativeToIgnore = relativeSafe(dirname(ignoreFilePath), filepath);
507
+ const relativeToIgnore = relativeSafe(baseDir ?? dirname(ignoreFilePath), filepath);
509
508
  if (relativeToIgnore === ".." || relativeToIgnore.startsWith("../")) return false;
510
509
  return matcher.ignores(relativeToIgnore);
511
510
  }
@@ -520,6 +519,62 @@ function resolveIgnoreFilePath(path, cwd) {
520
519
  return isAbsolute(path) ? path : resolve(cwd, path);
521
520
  }
522
521
  /**
522
+ * Check whether a directory looks like a git repo root.
523
+ *
524
+ * A `.git` entry can be either a directory (regular repo) or a file (worktree/submodule).
525
+ *
526
+ * @param dir - Directory to inspect.
527
+ * @returns True when `.git` exists under the directory.
528
+ */
529
+ async function hasGitEntry(dir) {
530
+ try {
531
+ await stat(join(dir, ".git"));
532
+ return true;
533
+ } catch (error) {
534
+ const code = error.code;
535
+ if (code === "ENOENT" || code === "ENOTDIR") return false;
536
+ throw error;
537
+ }
538
+ }
539
+ /**
540
+ * Find the nearest git repo root by walking up from a start directory.
541
+ *
542
+ * @param fromDir - Directory to start from.
543
+ * @returns Repo root directory, or undefined when no git boundary is found.
544
+ */
545
+ async function findGitRepoRoot(fromDir) {
546
+ let current = fromDir;
547
+ while (true) {
548
+ if (await hasGitEntry(current)) return current;
549
+ const parent = dirname(current);
550
+ if (parent === current) return;
551
+ current = parent;
552
+ }
553
+ }
554
+ /**
555
+ * Collect `.gitignore` files from file directory up to git repo boundary.
556
+ *
557
+ * @param filepath - Absolute file path to test.
558
+ * @returns Collected ignore files and repo root when found.
559
+ */
560
+ async function collectGitignorePaths(filepath) {
561
+ const fileDir = dirname(filepath);
562
+ const repoRoot = await findGitRepoRoot(fileDir);
563
+ const paths = [];
564
+ let current = fileDir;
565
+ while (true) {
566
+ paths.push(join(current, ".gitignore"));
567
+ if (repoRoot && current === repoRoot) break;
568
+ const parent = dirname(current);
569
+ if (parent === current) break;
570
+ current = parent;
571
+ }
572
+ return {
573
+ paths,
574
+ repoRoot
575
+ };
576
+ }
577
+ /**
523
578
  * Match `ignorePatterns` from config with support for negated patterns.
524
579
  *
525
580
  * @param filepath - Absolute file path.
@@ -575,13 +630,28 @@ async function isOxfmtIgnored(options) {
575
630
  ignored: true,
576
631
  reason: "ignore-path"
577
632
  };
578
- } else for (const ignoreFile of DEFAULT_IGNORE_FILES) if (await matchIgnoreFile(filepath, resolve(cwd, ignoreFile), useCache)) return {
579
- ignored: true,
580
- reason: ignoreFile === ".gitignore" ? "gitignore" : "prettierignore"
581
- };
633
+ } else {
634
+ const { paths: gitignorePaths, repoRoot } = await collectGitignorePaths(filepath);
635
+ for (const ignorePath of gitignorePaths) if (await matchIgnoreFile(filepath, ignorePath, useCache)) return {
636
+ ignored: true,
637
+ reason: "gitignore"
638
+ };
639
+ if (repoRoot) {
640
+ if (await matchIgnoreFile(filepath, join(repoRoot, ".git", "info", "exclude"), useCache, repoRoot)) return {
641
+ ignored: true,
642
+ reason: "git-info-exclude"
643
+ };
644
+ }
645
+ if (await matchIgnoreFile(filepath, resolve(cwd, ".prettierignore"), useCache)) return {
646
+ ignored: true,
647
+ reason: "prettierignore"
648
+ };
649
+ }
582
650
  if (!loadConfigForIgnorePatterns) return { ignored: false };
583
- const configResult = await loadOxfmtConfigResult({
584
- cwd: options.configPath || options.disableNestedConfig ? cwd : dirname(filepath),
651
+ const configResult = await loadOxfmtConfig({
652
+ cwd,
653
+ filepath,
654
+ ...options.disableNestedConfig === true ? { disableNestedConfig: true } : {},
585
655
  ...options.configPath ? { configPath: options.configPath } : {},
586
656
  editorconfig: false,
587
657
  useCache
@@ -593,27 +663,4 @@ async function isOxfmtIgnored(options) {
593
663
  return { ignored: false };
594
664
  }
595
665
  //#endregion
596
- //#region src/legacy.ts
597
- /**
598
- * Legacy API that returns only the merged config object.
599
- *
600
- * Prefer using `loadOxfmtConfigResult` when you also need resolved config metadata.
601
- *
602
- * @deprecated Prefer `loadOxfmtConfigResult`.
603
- *
604
- * @param options - Loader options.
605
- * @returns Parsed and merged oxfmt options.
606
- *
607
- * @example
608
- * ```ts
609
- * import { loadOxfmtConfig } from 'load-oxfmt-config'
610
- *
611
- * // Deprecated: prefer loadOxfmtConfigResult.
612
- * const config = await loadOxfmtConfig({ cwd: process.cwd() })
613
- * ```
614
- */
615
- async function loadOxfmtConfig(options = {}) {
616
- return (await loadOxfmtConfigResult(options)).config;
617
- }
618
- //#endregion
619
- export { isOxfmtIgnored, loadOxfmtConfig, loadOxfmtConfigResult, resolveOxfmtrcPath };
666
+ export { isOxfmtIgnored, loadOxfmtConfig, resolveOxfmtrcPath };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "load-oxfmt-config",
3
- "version": "0.7.2",
4
- "description": "Load oxfmt config files and merge supported .editorconfig options.",
3
+ "version": "0.9.0",
4
+ "description": "Load and resolve oxfmt configuration files, including explicit JS/TS config paths, and merge supported `.editorconfig` settings for Oxfmt.",
5
5
  "keywords": [
6
6
  "editorconfig",
7
7
  "jsonc-parser",
@@ -48,20 +48,20 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@ntnyq/tsconfig": "^3.1.0",
51
- "@types/node": "^25.6.2",
51
+ "@types/node": "^25.8.0",
52
52
  "@types/picomatch": "^4.0.3",
53
- "@typescript/native-preview": "^7.0.0-dev.20260508.1",
53
+ "@typescript/native-preview": "^7.0.0-dev.20260517.1",
54
54
  "bumpp": "^11.1.0",
55
55
  "husky": "^9.1.7",
56
56
  "nano-staged": "^1.0.2",
57
57
  "npm-run-all2": "^8.0.4",
58
- "oxfmt": "^0.48.0",
59
- "oxlint": "^1.63.0",
58
+ "oxfmt": "^0.50.0",
59
+ "oxlint": "^1.65.0",
60
60
  "tsdown": "^0.22.0",
61
- "vitest": "^4.1.5"
61
+ "vitest": "^4.1.6"
62
62
  },
63
63
  "peerDependencies": {
64
- "oxfmt": ">=0.41.0"
64
+ "oxfmt": ">=0.50.0"
65
65
  },
66
66
  "nano-staged": {
67
67
  "*.{js,ts,mjs,tsx}": "oxlint --fix",