load-oxfmt-config 0.2.0 → 0.3.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 +104 -8
- package/dist/index.d.mts +32 -6
- package/dist/index.mjs +149 -11
- package/package.json +14 -10
package/README.md
CHANGED
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/load-oxfmt-config)
|
|
6
6
|
[](https://github.com/ntnyq/load-oxfmt-config/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
> Load and resolve
|
|
8
|
+
> Load and resolve oxfmt configuration files 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
|
|
13
|
+
- 📦 **Multiple formats** - Supports `.oxfmtrc.json`, `.oxfmtrc.jsonc`, and `oxfmt.config.ts`
|
|
14
|
+
- 🧩 **EditorConfig fallback** - Merges supported `.editorconfig` fields into the returned oxfmt config result
|
|
14
15
|
- ⚡ **Built-in caching** - Caches both file resolution and parsed configs for optimal performance
|
|
15
16
|
- 🎯 **TypeScript support** - Fully typed with comprehensive type definitions
|
|
16
17
|
- 🛠️ **Flexible API** - Support explicit config paths or automatic discovery
|
|
@@ -38,11 +39,29 @@ Load config from current directory or parent directories:
|
|
|
38
39
|
```ts
|
|
39
40
|
import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
40
41
|
|
|
41
|
-
// Automatically searches for
|
|
42
|
+
// Automatically searches for oxfmt config files and the nearest .editorconfig
|
|
42
43
|
const config = await loadOxfmtConfig()
|
|
43
44
|
console.log(config) // { printWidth: 80, ... }
|
|
44
45
|
```
|
|
45
46
|
|
|
47
|
+
### Merge With `.editorconfig`
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
51
|
+
|
|
52
|
+
const config = await loadOxfmtConfig({ cwd: '/path/to/project' })
|
|
53
|
+
|
|
54
|
+
// Returns one merged static config object
|
|
55
|
+
console.log(config)
|
|
56
|
+
// {
|
|
57
|
+
// tabWidth: 2,
|
|
58
|
+
// printWidth: 100,
|
|
59
|
+
// overrides: [
|
|
60
|
+
// { files: ['src/**/*.ts'], options: { printWidth: 120 } }
|
|
61
|
+
// ]
|
|
62
|
+
// }
|
|
63
|
+
```
|
|
64
|
+
|
|
46
65
|
### Specify Working Directory
|
|
47
66
|
|
|
48
67
|
```ts
|
|
@@ -70,6 +89,28 @@ const config = await loadOxfmtConfig({
|
|
|
70
89
|
})
|
|
71
90
|
```
|
|
72
91
|
|
|
92
|
+
### Disable `.editorconfig`
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
96
|
+
|
|
97
|
+
// Skip .editorconfig reading entirely
|
|
98
|
+
const config = await loadOxfmtConfig({
|
|
99
|
+
editorconfig: false,
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Limit `.editorconfig` to `cwd`
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { loadOxfmtConfig } from 'load-oxfmt-config'
|
|
107
|
+
|
|
108
|
+
// Only look in the cwd directory itself, no upward traversal
|
|
109
|
+
const config = await loadOxfmtConfig({
|
|
110
|
+
editorconfig: { onlyCwd: true },
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
73
114
|
### Disable Caching
|
|
74
115
|
|
|
75
116
|
```ts
|
|
@@ -95,13 +136,13 @@ console.log(configPath) // '/path/to/.oxfmtrc.json' or undefined
|
|
|
95
136
|
|
|
96
137
|
### `loadOxfmtConfig(options?)`
|
|
97
138
|
|
|
98
|
-
Load and parse oxfmt configuration
|
|
139
|
+
Load and parse oxfmt configuration files, then merge supported `.editorconfig` fields into the returned result.
|
|
99
140
|
|
|
100
141
|
**Parameters:**
|
|
101
142
|
|
|
102
143
|
- `options` - Optional configuration object
|
|
103
144
|
|
|
104
|
-
**Returns:** `Promise<FormatOptions>` - Parsed oxfmt configuration object. Returns empty object `{}` if no config file is found.
|
|
145
|
+
**Returns:** `Promise<FormatOptions>` - Parsed and merged oxfmt configuration object. Returns empty object `{}` if no supported config file is found.
|
|
105
146
|
|
|
106
147
|
**Throws:** Error if config file exists but cannot be parsed.
|
|
107
148
|
|
|
@@ -151,6 +192,21 @@ Enable in-memory caching for both path resolution and parsed config contents. Wh
|
|
|
151
192
|
|
|
152
193
|
Set to `false` to force reload from disk on every call.
|
|
153
194
|
|
|
195
|
+
### `editorconfig`
|
|
196
|
+
|
|
197
|
+
- **Type:** `boolean | EditorconfigOption`
|
|
198
|
+
- **Default:** `true`
|
|
199
|
+
|
|
200
|
+
Control how `.editorconfig` files are read and merged:
|
|
201
|
+
|
|
202
|
+
- **`true`** — Read and merge the nearest `.editorconfig`, walking up from the config file's directory (or `cwd` when no config path is given).
|
|
203
|
+
- **`false`** — Disable `.editorconfig` reading entirely.
|
|
204
|
+
- **`EditorconfigOption`** — Enable with additional settings:
|
|
205
|
+
|
|
206
|
+
| Property | Type | Default | Description |
|
|
207
|
+
| --------- | --------- | ------- | --------------------------------------------------------------------------------- |
|
|
208
|
+
| `onlyCwd` | `boolean` | `false` | When `true`, only look for `.editorconfig` in `cwd` itself — no upward traversal. |
|
|
209
|
+
|
|
154
210
|
## Config File Discovery
|
|
155
211
|
|
|
156
212
|
When `configPath` is not provided, the loader automatically searches for config files:
|
|
@@ -159,10 +215,14 @@ When `configPath` is not provided, the loader automatically searches for config
|
|
|
159
215
|
2. **Supported filenames:**
|
|
160
216
|
- `.oxfmtrc.json`
|
|
161
217
|
- `.oxfmtrc.jsonc`
|
|
218
|
+
|
|
219
|
+
- `oxfmt.config.ts`
|
|
220
|
+
|
|
162
221
|
3. **Stops when:**
|
|
163
222
|
- A valid config file is found
|
|
164
223
|
- Reaches the filesystem root
|
|
165
|
-
4. **
|
|
224
|
+
4. **EditorConfig:** The nearest `.editorconfig` is also resolved and merged into the returned result
|
|
225
|
+
5. **Returns:** Empty object `{}` if no config file is found
|
|
166
226
|
|
|
167
227
|
## Supported Config Formats
|
|
168
228
|
|
|
@@ -191,10 +251,46 @@ JSON with comments support:
|
|
|
191
251
|
/* Code style preferences */
|
|
192
252
|
"useTabs": false,
|
|
193
253
|
"semi": true,
|
|
194
|
-
"singleQuote": true
|
|
254
|
+
"singleQuote": true,
|
|
195
255
|
}
|
|
196
256
|
```
|
|
197
257
|
|
|
258
|
+
### TypeScript (`oxfmt.config.ts`)
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
export default {
|
|
262
|
+
printWidth: 100,
|
|
263
|
+
tabWidth: 2,
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## `.editorconfig` Support
|
|
268
|
+
|
|
269
|
+
The loader reads the nearest `.editorconfig` file and maps the subset of fields that oxfmt supports:
|
|
270
|
+
|
|
271
|
+
- `end_of_line` → `endOfLine`
|
|
272
|
+
- `indent_style` → `useTabs`
|
|
273
|
+
- `indent_size` / `tab_width` → `tabWidth`
|
|
274
|
+
- `max_line_length` → `printWidth`
|
|
275
|
+
- `insert_final_newline` → `insertFinalNewline`
|
|
276
|
+
|
|
277
|
+
Glob sections such as `[*.ts]` are converted into returned `overrides` entries.
|
|
278
|
+
|
|
279
|
+
## Precedence
|
|
280
|
+
|
|
281
|
+
The merged result follows this order:
|
|
282
|
+
|
|
283
|
+
1. Root-level values from the nearest `.editorconfig`
|
|
284
|
+
2. Root-level values from `.oxfmtrc.json`, `.oxfmtrc.jsonc`, or `oxfmt.config.ts`
|
|
285
|
+
3. Overrides generated from `.editorconfig` sections
|
|
286
|
+
4. Overrides declared directly in the oxfmt config file
|
|
287
|
+
|
|
288
|
+
This means explicit oxfmt config values always win over `.editorconfig` fallback values.
|
|
289
|
+
|
|
290
|
+
## Limitations
|
|
291
|
+
|
|
292
|
+
`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.
|
|
293
|
+
|
|
198
294
|
## Error Handling
|
|
199
295
|
|
|
200
296
|
```ts
|
|
@@ -218,7 +314,7 @@ The caching system maintains two separate caches:
|
|
|
218
314
|
**Cache keys are based on:**
|
|
219
315
|
|
|
220
316
|
- `cwd` + `configPath` for path resolution
|
|
221
|
-
- Resolved
|
|
317
|
+
- Resolved oxfmt path and resolved `.editorconfig` path for config content
|
|
222
318
|
|
|
223
319
|
**Cache invalidation:**
|
|
224
320
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FormatConfig } from "oxfmt";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Object-form editorconfig option, enabling fine-grained control.
|
|
6
|
+
*/
|
|
7
|
+
interface EditorconfigOption {
|
|
8
|
+
/**
|
|
9
|
+
* When `true`, only look for `.editorconfig` in the `cwd` directory itself
|
|
10
|
+
* (no upward traversal).
|
|
11
|
+
*
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
onlyCwd?: boolean;
|
|
15
|
+
}
|
|
4
16
|
/**
|
|
5
17
|
* Format option override for a single matching rule
|
|
6
18
|
*/
|
|
7
|
-
interface
|
|
19
|
+
interface OxfmtConfigOverride {
|
|
8
20
|
/**
|
|
9
21
|
* Glob patterns to match files
|
|
10
22
|
*/
|
|
@@ -16,7 +28,7 @@ interface FormatOptionOverride {
|
|
|
16
28
|
/**
|
|
17
29
|
* Format options to apply
|
|
18
30
|
*/
|
|
19
|
-
options?:
|
|
31
|
+
options?: FormatConfig;
|
|
20
32
|
}
|
|
21
33
|
interface Options {
|
|
22
34
|
/**
|
|
@@ -31,11 +43,21 @@ interface Options {
|
|
|
31
43
|
* Whether to use cache
|
|
32
44
|
*/
|
|
33
45
|
useCache?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Control `.editorconfig` reading.
|
|
48
|
+
* - `true` (default): read and merge `.editorconfig`, walking up from the config
|
|
49
|
+
* file's directory (or `cwd` when no config path is given).
|
|
50
|
+
* - `false`: disable `.editorconfig` reading entirely.
|
|
51
|
+
* - `EditorconfigOption`: enable with additional settings (e.g. `onlyCwd`).
|
|
52
|
+
*
|
|
53
|
+
* @default true
|
|
54
|
+
*/
|
|
55
|
+
editorconfig?: boolean | EditorconfigOption;
|
|
34
56
|
}
|
|
35
57
|
/**
|
|
36
58
|
* Final oxfmt options (including overrides)
|
|
37
59
|
*/
|
|
38
|
-
interface OxfmtOptions extends
|
|
60
|
+
interface OxfmtOptions extends FormatConfig {
|
|
39
61
|
/**
|
|
40
62
|
* Ignore files matching these glob patterns
|
|
41
63
|
* Patterns are based on the location of the Oxfmt configuration file
|
|
@@ -44,8 +66,12 @@ interface OxfmtOptions extends FormatOptions {
|
|
|
44
66
|
/**
|
|
45
67
|
* Array of format option overrides
|
|
46
68
|
*/
|
|
47
|
-
overrides?:
|
|
69
|
+
overrides?: OxfmtConfigOverride[];
|
|
48
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* @deprecated Use `OxfmtConfigOverride` instead
|
|
73
|
+
*/
|
|
74
|
+
type FormatOptionOverride = OxfmtConfigOverride;
|
|
49
75
|
//#endregion
|
|
50
76
|
//#region src/core.d.ts
|
|
51
77
|
/**
|
|
@@ -73,4 +99,4 @@ declare function resolveOxfmtrcPath(cwd: string, configPath?: string): Promise<s
|
|
|
73
99
|
*/
|
|
74
100
|
declare function loadOxfmtConfig(options?: Options): Promise<OxfmtOptions>;
|
|
75
101
|
//#endregion
|
|
76
|
-
export { FormatOptionOverride, Options, OxfmtOptions, loadOxfmtConfig, resolveOxfmtrcPath };
|
|
102
|
+
export { EditorconfigOption, FormatOptionOverride, Options, OxfmtConfigOverride, OxfmtOptions, loadOxfmtConfig, resolveOxfmtrcPath };
|
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,128 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
|
-
import { isAbsolute, join } from "node:path";
|
|
2
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
+
import { interopDefault } from "@ntnyq/utils";
|
|
4
5
|
import { parse } from "jsonc-parser";
|
|
6
|
+
import { parseBuffer } from "editorconfig";
|
|
5
7
|
//#region src/constants.ts
|
|
6
8
|
/**
|
|
7
9
|
* Supported configuration files for oxfmt.
|
|
8
10
|
* @see https://oxc.rs/docs/guide/usage/formatter/config.html#oxfmtrc-json-c
|
|
9
11
|
*/
|
|
10
|
-
const OXFMT_CONFIG_FILES = [
|
|
12
|
+
const OXFMT_CONFIG_FILES = [
|
|
13
|
+
".oxfmtrc.json",
|
|
14
|
+
".oxfmtrc.jsonc",
|
|
15
|
+
"oxfmt.config.ts"
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Supported EditorConfig filename.
|
|
19
|
+
*/
|
|
20
|
+
const EDITORCONFIG_FILE = ".editorconfig";
|
|
21
|
+
/**
|
|
22
|
+
* Sections that apply globally and can be merged into root-level oxfmt options.
|
|
23
|
+
*/
|
|
24
|
+
const EDITORCONFIG_GLOBAL_SECTION_NAMES = ["*", "**"];
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/editorconfig.ts
|
|
27
|
+
function getEditorconfigResolveCacheKey(resolveKey) {
|
|
28
|
+
return `editorconfig::${resolveKey}`;
|
|
29
|
+
}
|
|
30
|
+
function getEditorconfigSearchDir(cwd, configPath) {
|
|
31
|
+
if (!configPath) return cwd;
|
|
32
|
+
return dirname(isAbsolute(configPath) ? configPath : join(cwd, configPath));
|
|
33
|
+
}
|
|
34
|
+
function mergeRootOptions(oxfmtConfig, editorconfigRootOptions) {
|
|
35
|
+
return {
|
|
36
|
+
...editorconfigRootOptions,
|
|
37
|
+
...oxfmtConfig
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function mergeOverrides(oxfmtOverrides, editorconfigOverrides) {
|
|
41
|
+
const mergedOverrides = [...editorconfigOverrides, ...oxfmtOverrides || []];
|
|
42
|
+
return mergedOverrides.length > 0 ? mergedOverrides : void 0;
|
|
43
|
+
}
|
|
44
|
+
async function resolveEditorconfigPath(startDir, onlyCwd = false) {
|
|
45
|
+
let currentDir = startDir;
|
|
46
|
+
while (true) {
|
|
47
|
+
const editorconfigPath = join(currentDir, EDITORCONFIG_FILE);
|
|
48
|
+
try {
|
|
49
|
+
if ((await stat(editorconfigPath)).isFile()) return editorconfigPath;
|
|
50
|
+
} catch {}
|
|
51
|
+
if (onlyCwd) break;
|
|
52
|
+
const parentDir = join(currentDir, "..");
|
|
53
|
+
if (parentDir === currentDir) break;
|
|
54
|
+
currentDir = parentDir;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function toPosixPath(path) {
|
|
58
|
+
return path.replaceAll("\\", "/");
|
|
59
|
+
}
|
|
60
|
+
function isEditorconfigGlobalSection(sectionName) {
|
|
61
|
+
return Boolean(sectionName && EDITORCONFIG_GLOBAL_SECTION_NAMES.includes(sectionName));
|
|
62
|
+
}
|
|
63
|
+
function parseEditorconfigBoolean(value) {
|
|
64
|
+
if (value === "true") return true;
|
|
65
|
+
if (value === "false") return false;
|
|
66
|
+
}
|
|
67
|
+
function parseEditorconfigEndOfLine(value) {
|
|
68
|
+
if (value === "lf" || value === "crlf" || value === "cr") return value;
|
|
69
|
+
}
|
|
70
|
+
function parseEditorconfigTabWidth(section) {
|
|
71
|
+
const indentSize = section["indent_size"];
|
|
72
|
+
const tabWidth = section["tab_width"];
|
|
73
|
+
if (indentSize && indentSize !== "unset" && indentSize !== "tab") {
|
|
74
|
+
const parsedIndentSize = Number(indentSize);
|
|
75
|
+
if (Number.isFinite(parsedIndentSize)) return parsedIndentSize;
|
|
76
|
+
}
|
|
77
|
+
if (tabWidth && tabWidth !== "unset") {
|
|
78
|
+
const parsedTabWidth = Number(tabWidth);
|
|
79
|
+
if (Number.isFinite(parsedTabWidth)) return parsedTabWidth;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function mapEditorconfigSectionToOptions(section) {
|
|
83
|
+
const options = {};
|
|
84
|
+
const endOfLine = parseEditorconfigEndOfLine(section["end_of_line"]);
|
|
85
|
+
if (endOfLine) options.endOfLine = endOfLine;
|
|
86
|
+
if (section["indent_style"] === "tab") options.useTabs = true;
|
|
87
|
+
else if (section["indent_style"] === "space") options.useTabs = false;
|
|
88
|
+
const tabWidth = parseEditorconfigTabWidth(section);
|
|
89
|
+
if (typeof tabWidth === "number") options.tabWidth = tabWidth;
|
|
90
|
+
if (section["max_line_length"] && section["max_line_length"] !== "unset") {
|
|
91
|
+
const parsedPrintWidth = Number(section["max_line_length"]);
|
|
92
|
+
if (Number.isFinite(parsedPrintWidth)) options.printWidth = parsedPrintWidth;
|
|
93
|
+
}
|
|
94
|
+
const insertFinalNewline = parseEditorconfigBoolean(section["insert_final_newline"]);
|
|
95
|
+
if (typeof insertFinalNewline === "boolean") options.insertFinalNewline = insertFinalNewline;
|
|
96
|
+
return options;
|
|
97
|
+
}
|
|
98
|
+
function rebaseEditorconfigPattern(pattern, editorconfigDir, anchorDir) {
|
|
99
|
+
const relativePrefix = toPosixPath(relative(anchorDir, editorconfigDir));
|
|
100
|
+
if (!relativePrefix || relativePrefix === ".") return pattern;
|
|
101
|
+
return `${relativePrefix}/${pattern.replace(/^\//, "")}`;
|
|
102
|
+
}
|
|
103
|
+
async function readEditorconfigFromFile(editorconfigPath, anchorDir) {
|
|
104
|
+
const parsedSections = parseBuffer(await readFile(editorconfigPath));
|
|
105
|
+
const editorconfigDir = dirname(editorconfigPath);
|
|
106
|
+
const rootOptions = {};
|
|
107
|
+
const overrides = [];
|
|
108
|
+
for (const [sectionName, sectionBody] of parsedSections) {
|
|
109
|
+
if (!sectionName) continue;
|
|
110
|
+
const mappedOptions = mapEditorconfigSectionToOptions(sectionBody);
|
|
111
|
+
if (Object.keys(mappedOptions).length === 0) continue;
|
|
112
|
+
if (isEditorconfigGlobalSection(sectionName)) {
|
|
113
|
+
Object.assign(rootOptions, mappedOptions);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
overrides.push({
|
|
117
|
+
files: [rebaseEditorconfigPattern(sectionName, editorconfigDir, anchorDir)],
|
|
118
|
+
options: mappedOptions
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
overrides,
|
|
123
|
+
rootOptions
|
|
124
|
+
};
|
|
125
|
+
}
|
|
11
126
|
//#endregion
|
|
12
127
|
//#region src/core.ts
|
|
13
128
|
const resolveCache = /* @__PURE__ */ new Map();
|
|
@@ -34,11 +149,12 @@ function cachePromise(cache, key, factory) {
|
|
|
34
149
|
* Build a cache key for config content; prefixes missing entries with `missing:`.
|
|
35
150
|
*
|
|
36
151
|
* @param resolvedPath - Resolved config path or undefined when missing.
|
|
152
|
+
* @param editorconfigPath - Resolved editorconfig path or undefined when missing.
|
|
37
153
|
* @param resolveKey - Key used for path resolution caching.
|
|
38
154
|
* @returns Cache key for config content.
|
|
39
155
|
*/
|
|
40
|
-
function getConfigCacheKey(resolvedPath, resolveKey) {
|
|
41
|
-
return resolvedPath || `missing:${resolveKey}`;
|
|
156
|
+
function getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey) {
|
|
157
|
+
return `${resolvedPath || `missing-oxfmt:${resolveKey}`}::${editorconfigPath || `missing-editorconfig:${resolveKey}`}`;
|
|
42
158
|
}
|
|
43
159
|
/**
|
|
44
160
|
* Resolve the oxfmt config file path.
|
|
@@ -75,12 +191,16 @@ function getResolveCacheKey(cwd, configPath) {
|
|
|
75
191
|
return `${cwd}::${configPath || ""}`;
|
|
76
192
|
}
|
|
77
193
|
/**
|
|
78
|
-
* Read and parse config file, supporting JSON and
|
|
194
|
+
* Read and parse config file, supporting JSON, JSONC, and TypeScript/JavaScript.
|
|
79
195
|
*
|
|
80
196
|
* @param resolvedPath - Absolute path to the config file.
|
|
81
197
|
* @returns Parsed OxfmtOptions object.
|
|
82
198
|
*/
|
|
83
199
|
async function readConfigFromFile(resolvedPath) {
|
|
200
|
+
if (resolvedPath.endsWith(".ts")) {
|
|
201
|
+
const mod = await (await interopDefault(import("jiti")))(import.meta.url).import(resolvedPath);
|
|
202
|
+
return mod["default"] ?? mod;
|
|
203
|
+
}
|
|
84
204
|
const content = await readFile(resolvedPath, "utf8");
|
|
85
205
|
if (resolvedPath.endsWith(".jsonc")) return parse(content);
|
|
86
206
|
return JSON.parse(content);
|
|
@@ -101,17 +221,35 @@ async function readConfigFromFile(resolvedPath) {
|
|
|
101
221
|
async function loadOxfmtConfig(options = {}) {
|
|
102
222
|
const useCache = options.useCache !== false;
|
|
103
223
|
const cwd = options.cwd || process.cwd();
|
|
224
|
+
const editorconfig = options.editorconfig ?? true;
|
|
225
|
+
const useEditorconfig = editorconfig !== false;
|
|
226
|
+
const onlyCwd = useEditorconfig && typeof editorconfig === "object" ? editorconfig.onlyCwd ?? false : false;
|
|
104
227
|
const resolveKey = getResolveCacheKey(cwd, options.configPath);
|
|
228
|
+
const editorconfigSearchDir = getEditorconfigSearchDir(cwd, options.configPath);
|
|
229
|
+
const editorconfigResolveKey = getEditorconfigResolveCacheKey(resolveKey);
|
|
105
230
|
const resolvedPath = useCache ? await cachePromise(resolveCache, resolveKey, () => resolveOxfmtrcPath(cwd, options.configPath)) : await resolveOxfmtrcPath(cwd, options.configPath);
|
|
106
|
-
|
|
231
|
+
const editorconfigPath = useEditorconfig ? await (useCache ? cachePromise(resolveCache, editorconfigResolveKey, () => resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : resolveEditorconfigPath(editorconfigSearchDir, onlyCwd)) : void 0;
|
|
232
|
+
const anchorDir = dirname(resolvedPath || editorconfigPath || cwd);
|
|
233
|
+
const loadTask = async () => {
|
|
234
|
+
const oxfmtConfig = resolvedPath ? await readConfigFromFile(resolvedPath).catch((error) => {
|
|
235
|
+
throw new Error(`Failed to parse oxfmt configuration file at ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
236
|
+
}) : {};
|
|
237
|
+
if (!editorconfigPath) return oxfmtConfig;
|
|
238
|
+
const editorconfigData = await readEditorconfigFromFile(editorconfigPath, anchorDir);
|
|
239
|
+
const mergedConfig = mergeRootOptions(oxfmtConfig, editorconfigData.rootOptions);
|
|
240
|
+
const mergedOverrides = mergeOverrides(oxfmtConfig.overrides, editorconfigData.overrides);
|
|
241
|
+
if (!mergedOverrides) return mergedConfig;
|
|
242
|
+
return {
|
|
243
|
+
...mergedConfig,
|
|
244
|
+
overrides: mergedOverrides
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
if (!resolvedPath && !editorconfigPath) {
|
|
107
248
|
if (!useCache) return {};
|
|
108
|
-
return cachePromise(configCache, getConfigCacheKey(resolvedPath, resolveKey), () => Promise.resolve({}));
|
|
249
|
+
return cachePromise(configCache, getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey), () => Promise.resolve({}));
|
|
109
250
|
}
|
|
110
|
-
const loadTask = () => readConfigFromFile(resolvedPath).catch((error) => {
|
|
111
|
-
throw new Error(`Failed to parse oxfmt configuration file at ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
112
|
-
});
|
|
113
251
|
if (!useCache) return loadTask();
|
|
114
|
-
return cachePromise(configCache, getConfigCacheKey(resolvedPath, resolveKey), loadTask);
|
|
252
|
+
return cachePromise(configCache, getConfigCacheKey(resolvedPath, editorconfigPath, resolveKey), loadTask);
|
|
115
253
|
}
|
|
116
254
|
//#endregion
|
|
117
255
|
export { loadOxfmtConfig, resolveOxfmtrcPath };
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "load-oxfmt-config",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Load
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Load oxfmt config files and merge supported .editorconfig options.",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"editorconfig",
|
|
6
7
|
"jsonc-parser",
|
|
7
8
|
"load-config",
|
|
8
9
|
"load-oxfmt-config",
|
|
@@ -38,23 +39,26 @@
|
|
|
38
39
|
"access": "public"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
42
|
+
"@ntnyq/utils": "^0.11.2",
|
|
43
|
+
"editorconfig": "^3.0.2",
|
|
44
|
+
"jiti": "^2.6.1",
|
|
41
45
|
"jsonc-parser": "^3.3.1"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
44
48
|
"@ntnyq/tsconfig": "^3.1.0",
|
|
45
|
-
"@types/node": "^25.
|
|
46
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
47
|
-
"bumpp": "^
|
|
49
|
+
"@types/node": "^25.5.0",
|
|
50
|
+
"@typescript/native-preview": "^7.0.0-dev.20260326.1",
|
|
51
|
+
"bumpp": "^11.0.1",
|
|
48
52
|
"husky": "^9.1.7",
|
|
49
53
|
"nano-staged": "^0.9.0",
|
|
50
54
|
"npm-run-all2": "^8.0.4",
|
|
51
|
-
"oxfmt": "^0.
|
|
52
|
-
"oxlint": "^1.
|
|
53
|
-
"tsdown": "^0.21.
|
|
54
|
-
"vitest": "^4.
|
|
55
|
+
"oxfmt": "^0.42.0",
|
|
56
|
+
"oxlint": "^1.57.0",
|
|
57
|
+
"tsdown": "^0.21.5",
|
|
58
|
+
"vitest": "^4.1.2"
|
|
55
59
|
},
|
|
56
60
|
"peerDependencies": {
|
|
57
|
-
"oxfmt": "0.
|
|
61
|
+
"oxfmt": ">=0.41.0"
|
|
58
62
|
},
|
|
59
63
|
"nano-staged": {
|
|
60
64
|
"*.{js,ts,mjs,tsx}": "oxlint --fix",
|