load-oxfmt-config 0.4.1 → 0.5.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,13 +185,29 @@ 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
194
  - `options` - Optional configuration object
157
195
 
158
- **Returns:** `Promise<FormatOptions>` - Parsed and merged oxfmt configuration object. Returns empty object `{}` if no supported config file is found.
196
+ **Returns:** `Promise<OxfmtOptions>` - Parsed and merged oxfmt configuration object. Returns empty object `{}` if no supported config file is found.
197
+
198
+ ### `loadOxfmtConfigResult(options?)`
199
+
200
+ Load and parse oxfmt configuration files, merge supported `.editorconfig` fields, and return metadata for the resolved config file.
201
+
202
+ **Parameters:**
203
+
204
+ - `options` - Optional configuration object (same as `loadOxfmtConfig`)
205
+
206
+ **Returns:** `Promise<LoadOxfmtConfigResult>`
207
+
208
+ - `config` - Parsed and merged oxfmt configuration object
209
+ - `filepath` - Resolved config absolute path (undefined when not found)
210
+ - `dirname` - Config directory (undefined when not found)
159
211
 
160
212
  **Throws:** Error if config file exists but cannot be parsed.
161
213
 
@@ -170,6 +222,23 @@ Resolve the absolute path to oxfmt config file.
170
222
 
171
223
  **Returns:** `Promise<string | undefined>` - Absolute path to config file, or `undefined` if not found.
172
224
 
225
+ ### `isOxfmtIgnored(options)`
226
+
227
+ Resolve whether a file should be ignored with oxfmt CLI-like semantics.
228
+
229
+ **Returns:** `Promise<IsOxfmtIgnoredResult>`
230
+
231
+ `ignorePath` accepts either a single path (`string`) or multiple paths (`string[]`).
232
+
233
+ - `ignored` - Whether the file is ignored
234
+ - `reason` - One of:
235
+ - `default-dir`
236
+ - `lockfile`
237
+ - `gitignore`
238
+ - `prettierignore`
239
+ - `ignore-path`
240
+ - `config-ignore-patterns`
241
+
173
242
  ## Options
174
243
 
175
244
  All options are optional.
@@ -229,9 +298,7 @@ When `configPath` is not provided, the loader automatically searches for config
229
298
  2. **Supported filenames:**
230
299
  - `.oxfmtrc.json`
231
300
  - `.oxfmtrc.jsonc`
232
-
233
- - `oxfmt.config.ts`
234
-
301
+ - `oxfmt.config.ts`
235
302
  3. **Stops when:**
236
303
  - A valid config file is found
237
304
  - Reaches the filesystem root
@@ -278,6 +345,8 @@ export default {
278
345
  }
279
346
  ```
280
347
 
348
+ 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.
349
+
281
350
  ## `.editorconfig` Support
282
351
 
283
352
  The loader reads the nearest `.editorconfig` file and maps the subset of fields that oxfmt supports:
@@ -292,6 +361,32 @@ The loader reads the nearest `.editorconfig` file and maps the subset of fields
292
361
 
293
362
  Only `[*]` is treated as a global section to match oxfmt. Other sections such as `[**]` and `[*.ts]` are converted into returned `overrides` entries.
294
363
 
364
+ ## Ignore Strategy
365
+
366
+ `isOxfmtIgnored()` applies two layers:
367
+
368
+ 1. Global ignore
369
+ 2. Ignore patterns from the resolved oxfmt config (`ignorePatterns`)
370
+
371
+ Global ignore includes:
372
+
373
+ - Default ignored directories: `.git`, `.svn`, `.jj`, `node_modules`
374
+ - Default lockfiles: `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `bun.lockb`
375
+ - Ignore files:
376
+ - If `ignorePath` is provided: use those files only (multiple supported)
377
+ - If `ignorePath` is not provided: use `.gitignore` and `.prettierignore` from `cwd`
378
+
379
+ Notes:
380
+
381
+ - `node_modules` can be included by passing `withNodeModules: true`.
382
+ - This package does not read parent `.gitignore` files or global gitignore settings.
383
+ - 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.
384
+ - `ignorePatterns` are always interpreted relative to the resolved oxfmt config directory.
385
+ - Nested config behavior follows oxfmt semantics:
386
+ - default: nearest config from target file directory upward
387
+ - `disableNestedConfig: true`: resolve from `cwd` only
388
+ - `configPath`: also disables nested lookup (same intent as CLI `-c`)
389
+
295
390
  ## Precedence
296
391
 
297
392
  The merged result follows this order:
@@ -305,17 +400,18 @@ This means explicit oxfmt config values always win over `.editorconfig` fallback
305
400
 
306
401
  ## Limitations
307
402
 
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.
403
+ `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
404
 
310
405
  ## Error Handling
311
406
 
312
407
  ```ts
313
- import { loadOxfmtConfig } from 'load-oxfmt-config'
408
+ import { loadOxfmtConfigResult } from 'load-oxfmt-config'
314
409
 
315
410
  try {
316
- const config = await loadOxfmtConfig()
411
+ const result = await loadOxfmtConfigResult()
412
+ console.log(result.config)
317
413
  } catch (error) {
318
- // Thrown when config file exists but contains invalid JSON
414
+ // Thrown when config file exists but contains invalid JSON/JSONC
319
415
  console.error('Failed to parse oxfmt config:', error.message)
320
416
  }
321
417
  ```
package/dist/index.d.mts CHANGED
@@ -77,35 +77,153 @@ 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
+ */
123
+ withNodeModules?: boolean;
124
+ /**
125
+ * Disable nested config lookup.
126
+ */
127
+ disableNestedConfig?: boolean;
128
+ /**
129
+ * Whether to use in-memory cache.
130
+ */
131
+ useCache?: boolean;
132
+ }
133
+ /**
134
+ * Ignore resolution result.
135
+ */
136
+ interface IsOxfmtIgnoredResult {
137
+ /**
138
+ * Whether the file should be ignored.
139
+ */
140
+ ignored: boolean;
141
+ /**
142
+ * Matched ignore source.
143
+ */
144
+ reason?: 'default-dir' | 'lockfile' | 'gitignore' | 'prettierignore' | 'ignore-path' | 'config-ignore-patterns';
145
+ }
80
146
  /**
81
147
  * @deprecated Use `OxfmtConfigOverride` instead
82
148
  */
83
149
  type FormatOptionOverride = OxfmtConfigOverride;
84
150
  //#endregion
85
- //#region src/core.d.ts
151
+ //#region src/config-file.d.ts
86
152
  /**
87
153
  * 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.
154
+ *
155
+ * - If `configPath` is provided, absolute paths are returned as-is;
156
+ * relative paths are joined to `cwd`.
157
+ * - Otherwise, walk upward from `cwd` to find known filenames.
90
158
  *
91
159
  * @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.
160
+ * @param configPath - Optional explicit path.
161
+ * @returns Absolute path to config file, or undefined when not found.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * import { resolveOxfmtrcPath } from 'load-oxfmt-config'
166
+ *
167
+ * const path = await resolveOxfmtrcPath(process.cwd())
168
+ * ```
94
169
  */
95
170
  declare function resolveOxfmtrcPath(cwd: string, configPath?: string): Promise<string | undefined>;
171
+ //#endregion
172
+ //#region src/core.d.ts
96
173
  /**
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.
174
+ * Resolve config + editorconfig and return merged config with metadata.
99
175
  *
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.
176
+ * @param options - Loader settings.
177
+ * @returns Merged config and optional resolved config metadata.
103
178
  *
104
179
  * @example
105
180
  * ```ts
106
- * const config = await loadOxfmtConfig({ cwd: '/project' })
181
+ * import { loadOxfmtConfigResult } from 'load-oxfmt-config'
182
+ *
183
+ * const result = await loadOxfmtConfigResult({ cwd: process.cwd() })
184
+ * console.log(result.config)
185
+ * ```
186
+ */
187
+ declare function loadOxfmtConfigResult(options?: Options): Promise<LoadOxfmtConfigResult>;
188
+ //#endregion
189
+ //#region src/ignore.d.ts
190
+ /**
191
+ * Resolve whether a file should be ignored using oxfmt-like CLI semantics.
192
+ *
193
+ * @param options - Ignore resolution options.
194
+ * @returns Ignore status with optional reason.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * import { isOxfmtIgnored } from 'load-oxfmt-config'
199
+ *
200
+ * const result = await isOxfmtIgnored({
201
+ * cwd: process.cwd(),
202
+ * filepath: 'src/generated/file.ts',
203
+ * })
204
+ * ```
205
+ */
206
+ declare function isOxfmtIgnored(options: IsOxfmtIgnoredOptions): Promise<IsOxfmtIgnoredResult>;
207
+ //#endregion
208
+ //#region src/legacy.d.ts
209
+ /**
210
+ * Legacy API that returns only the merged config object.
211
+ *
212
+ * Prefer using `loadOxfmtConfigResult` when you also need resolved config metadata.
213
+ *
214
+ * @deprecated Prefer `loadOxfmtConfigResult`.
215
+ *
216
+ * @param options - Loader options.
217
+ * @returns Parsed and merged oxfmt options.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * import { loadOxfmtConfig } from 'load-oxfmt-config'
222
+ *
223
+ * // Deprecated: prefer loadOxfmtConfigResult.
224
+ * const config = await loadOxfmtConfig({ cwd: process.cwd() })
107
225
  * ```
108
226
  */
109
227
  declare function loadOxfmtConfig(options?: Options): Promise<OxfmtOptions>;
110
228
  //#endregion
111
- export { EditorconfigOption, FormatOptionOverride, Options, OxfmtConfigOverride, OxfmtOptions, loadOxfmtConfig, resolveOxfmtrcPath };
229
+ 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,230 @@ 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 cwd = resolve(options.cwd ?? process.cwd());
558
+ const filepath = isAbsolute(options.filepath) ? resolve(options.filepath) : resolve(cwd, options.filepath);
559
+ if (isDefaultIgnoredDir(filepath, options.withNodeModules ? { withNodeModules: true } : {})) return {
560
+ ignored: true,
561
+ reason: "default-dir"
562
+ };
563
+ if (isLockfile(filepath)) return {
564
+ ignored: true,
565
+ reason: "lockfile"
566
+ };
567
+ const explicitIgnorePaths = (typeof options.ignorePath === "string" ? [options.ignorePath] : options.ignorePath)?.map((path) => resolveIgnoreFilePath(path, cwd));
568
+ if (explicitIgnorePaths && explicitIgnorePaths.length > 0) {
569
+ for (const ignoreFilePath of explicitIgnorePaths) if (await matchIgnoreFile(filepath, ignoreFilePath, useCache)) return {
570
+ ignored: true,
571
+ reason: "ignore-path"
344
572
  };
573
+ } else for (const ignoreFile of DEFAULT_IGNORE_FILES) if (await matchIgnoreFile(filepath, resolve(cwd, ignoreFile), useCache)) return {
574
+ ignored: true,
575
+ reason: ignoreFile === ".gitignore" ? "gitignore" : "prettierignore"
345
576
  };
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);
577
+ const configResult = await loadOxfmtConfigResult({
578
+ cwd: options.configPath || options.disableNestedConfig ? cwd : dirname(filepath),
579
+ ...options.configPath ? { configPath: options.configPath } : {},
580
+ editorconfig: false,
581
+ useCache
582
+ });
583
+ if (configResult.dirname && configResult.config.ignorePatterns && configResult.config.ignorePatterns.length > 0 && matchConfigIgnorePatterns(filepath, configResult.dirname, configResult.config.ignorePatterns)) return {
584
+ ignored: true,
585
+ reason: "config-ignore-patterns"
586
+ };
587
+ return { ignored: false };
588
+ }
589
+ //#endregion
590
+ //#region src/legacy.ts
591
+ /**
592
+ * Legacy API that returns only the merged config object.
593
+ *
594
+ * Prefer using `loadOxfmtConfigResult` when you also need resolved config metadata.
595
+ *
596
+ * @deprecated Prefer `loadOxfmtConfigResult`.
597
+ *
598
+ * @param options - Loader options.
599
+ * @returns Parsed and merged oxfmt options.
600
+ *
601
+ * @example
602
+ * ```ts
603
+ * import { loadOxfmtConfig } from 'load-oxfmt-config'
604
+ *
605
+ * // Deprecated: prefer loadOxfmtConfigResult.
606
+ * const config = await loadOxfmtConfig({ cwd: process.cwd() })
607
+ * ```
608
+ */
609
+ async function loadOxfmtConfig(options = {}) {
610
+ return (await loadOxfmtConfigResult(options)).config;
352
611
  }
353
612
  //#endregion
354
- export { loadOxfmtConfig, resolveOxfmtrcPath };
613
+ 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.5.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
  },