load-oxfmt-config 0.13.0 → 0.14.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/dist/index.d.mts CHANGED
@@ -187,27 +187,6 @@ interface IsOxfmtIgnoredResult {
187
187
  reason?: 'default-dir' | 'lockfile' | 'gitignore' | 'git-info-exclude' | 'prettierignore' | 'ignore-path' | 'config-ignore-patterns';
188
188
  }
189
189
  //#endregion
190
- //#region src/config-file.d.ts
191
- /**
192
- * Resolve the oxfmt config file path.
193
- *
194
- * - If `configPath` is provided, absolute paths are returned as-is;
195
- * relative paths are joined to `cwd`.
196
- * - Otherwise, walk upward from `cwd` to find known filenames.
197
- *
198
- * @param cwd - Starting directory for resolution.
199
- * @param configPath - Optional explicit path.
200
- * @returns Absolute path to config file, or undefined when not found.
201
- *
202
- * @example
203
- * ```ts
204
- * import { resolveOxfmtrcPath } from 'load-oxfmt-config'
205
- *
206
- * const path = await resolveOxfmtrcPath(process.cwd())
207
- * ```
208
- */
209
- declare function resolveOxfmtrcPath(cwd: string, configPath?: string): Promise<string | undefined>;
210
- //#endregion
211
190
  //#region src/core.d.ts
212
191
  /**
213
192
  * Resolve config + editorconfig and return merged config with metadata.
@@ -244,4 +223,52 @@ declare function loadOxfmtConfig(options?: LoadOxfmtConfigOptions): Promise<Load
244
223
  */
245
224
  declare function isOxfmtIgnored(options: IsOxfmtIgnoredOptions): Promise<IsOxfmtIgnoredResult>;
246
225
  //#endregion
247
- export { EditorconfigOption, IsOxfmtIgnoredOptions, IsOxfmtIgnoredResult, LoadOxfmtConfigOptions, LoadOxfmtConfigResult, OxfmtConfigOverride, OxfmtOptions, isOxfmtIgnored, loadOxfmtConfig, resolveOxfmtrcPath };
226
+ //#region src/config.d.ts
227
+ /**
228
+ * Build a cache key for path resolution (cwd + configPath).
229
+ *
230
+ * @param cwd - Current working directory.
231
+ * @param configPath - Optional config path.
232
+ * @returns Cache key for resolve cache.
233
+ */
234
+ declare function getResolveCacheKey(cwd: string, configPath?: string): string;
235
+ /**
236
+ * Build a cache key for config content; prefixes missing entries.
237
+ *
238
+ * @param resolvedPath - Resolved oxfmt config path.
239
+ * @param editorconfigPath - Resolved editorconfig path.
240
+ * @param resolveKey - Resolution cache key.
241
+ * @returns Cache key for config content cache.
242
+ */
243
+ declare function getConfigCacheKey(resolvedPath: string | undefined, editorconfigPath: string | undefined, resolveKey: string): string;
244
+ /**
245
+ * Resolve the oxfmt config file path.
246
+ *
247
+ * - If `configPath` is provided, absolute paths are returned as-is;
248
+ * relative paths are joined to `cwd`.
249
+ * - Otherwise, walk upward from `cwd` to find known filenames.
250
+ *
251
+ * @param cwd - Starting directory for resolution.
252
+ * @param configPath - Optional explicit path.
253
+ * @returns Absolute path to config file, or undefined when not found.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * import { resolveOxfmtrcPath } from 'load-oxfmt-config'
258
+ *
259
+ * const path = await resolveOxfmtrcPath(process.cwd())
260
+ * ```
261
+ */
262
+ declare function resolveOxfmtrcPath(cwd: string, configPath?: string): Promise<string | undefined>;
263
+ /**
264
+ * Read and parse an oxfmt config file.
265
+ *
266
+ * @param resolvedPath - Absolute path to config file.
267
+ * @param options - Config loading options.
268
+ * @returns Parsed config object.
269
+ */
270
+ declare function readConfigFromFile(resolvedPath: string, options?: {
271
+ useCache?: boolean;
272
+ }): Promise<OxfmtOptions>;
273
+ //#endregion
274
+ export { EditorconfigOption, IsOxfmtIgnoredOptions, IsOxfmtIgnoredResult, LoadOxfmtConfigOptions, LoadOxfmtConfigResult, OxfmtConfigOverride, OxfmtOptions, getConfigCacheKey, getResolveCacheKey, isOxfmtIgnored, loadOxfmtConfig, readConfigFromFile, resolveOxfmtrcPath };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import process from "node:process";
4
- import { interopDefault, isBoolean, isNumber, isObject } from "@ntnyq/utils";
4
+ import { hasOwn, interopDefault, isBoolean, isNumber, isObject, isRecord, isString, isUndefined } from "@ntnyq/utils";
5
5
  import { readFile, stat } from "node:fs/promises";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import { parse, printParseErrorCode } from "jsonc-parser";
@@ -75,7 +75,7 @@ const EDITORCONFIG_FILE = ".editorconfig";
75
75
  */
76
76
  const EDITORCONFIG_GLOBAL_SECTION_NAMES = ["*"];
77
77
  //#endregion
78
- //#region src/config-file.ts
78
+ //#region src/config.ts
79
79
  /**
80
80
  * CommonJS require scoped to this ESM module for loading `.cjs` config files.
81
81
  */
@@ -99,7 +99,18 @@ const syntaxFallbackMessages = [
99
99
  * @returns True when the value can be treated as an oxfmt options object.
100
100
  */
101
101
  function isConfigObject(value) {
102
- return typeof value === "object" && value !== null && !Array.isArray(value);
102
+ return isRecord(value);
103
+ }
104
+ /**
105
+ * Read a parsed JSON-like config value as an oxfmt config object.
106
+ *
107
+ * @param config - Runtime config value.
108
+ * @returns Oxfmt config object.
109
+ * @throws When the config value is not an object.
110
+ */
111
+ function readParsedConfigObject(config) {
112
+ if (!isConfigObject(config)) throw new Error("Configuration file must be an object.");
113
+ return config;
103
114
  }
104
115
  /**
105
116
  * Read the default export from a JavaScript or TypeScript config module.
@@ -109,7 +120,7 @@ function isConfigObject(value) {
109
120
  * @throws When the module has no default export or the default export is not an object.
110
121
  */
111
122
  function readConfigDefaultExport(mod) {
112
- if (!Object.hasOwn(mod, "default")) throw new Error("Configuration file has no default export.");
123
+ if (!hasOwn(mod, "default")) throw new Error("Configuration file has no default export.");
113
124
  const config = mod["default"];
114
125
  if (!isConfigObject(config)) throw new Error("Configuration file must have a default export that is an object.");
115
126
  return config;
@@ -155,7 +166,7 @@ async function getFreshImportCacheKey(resolvedPath) {
155
166
  * @returns Error code when present.
156
167
  */
157
168
  function getErrorCode(error) {
158
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
169
+ return isObject(error) && "code" in error && isString(error.code) ? error.code : void 0;
159
170
  }
160
171
  /**
161
172
  * Check whether a loader error should continue the JS config fallback chain.
@@ -321,15 +332,15 @@ async function readConfigFromFile(resolvedPath, options = {}) {
321
332
  });
322
333
  if (parseErrors.length > 0) {
323
334
  const firstError = parseErrors[0];
324
- const errorCode = firstError === void 0 ? "Unknown" : printParseErrorCode(firstError.error);
335
+ const errorCode = isUndefined(firstError) ? "Unknown" : printParseErrorCode(firstError.error);
325
336
  throw new Error(`Invalid JSONC syntax: ${errorCode}`);
326
337
  }
327
- return parsed ?? {};
338
+ return readParsedConfigObject(isUndefined(parsed) && content.trim() === "" ? {} : parsed);
328
339
  }
329
- if (extension === ".json") return JSON.parse(content);
330
- if (!extension) return JSON.parse(content);
340
+ if (extension === ".json") return readParsedConfigObject(JSON.parse(content));
341
+ if (!extension) return readParsedConfigObject(JSON.parse(content));
331
342
  if (!OXFMT_EXPLICIT_CONFIG_EXTENSIONS.includes(extension)) throw new Error(`Unsupported oxfmt config extension "${extension}" at ${resolvedPath}`);
332
- return JSON.parse(content);
343
+ return readParsedConfigObject(JSON.parse(content));
333
344
  }
334
345
  //#endregion
335
346
  //#region src/utils.ts
@@ -757,6 +768,29 @@ async function hasGitEntry(dir) {
757
768
  }
758
769
  }
759
770
  /**
771
+ * Resolve the git info exclude file from a repo root.
772
+ *
773
+ * Worktrees and submodules store `.git` as a file containing a `gitdir:` pointer.
774
+ *
775
+ * @param repoRoot - Git repository working tree root.
776
+ * @returns Absolute path to the git info exclude file, or undefined when unavailable.
777
+ */
778
+ async function resolveGitInfoExcludePath(repoRoot) {
779
+ const gitEntryPath = join(repoRoot, ".git");
780
+ try {
781
+ const stats = await stat(gitEntryPath);
782
+ if (stats.isDirectory()) return join(gitEntryPath, "info", "exclude");
783
+ if (!stats.isFile()) return;
784
+ const gitdir = (await readFile(gitEntryPath, "utf8")).split(/\r?\n/u).find((line) => line.startsWith("gitdir:"))?.slice(7).trim();
785
+ if (!gitdir) return;
786
+ return join(isAbsolute(gitdir) ? gitdir : resolve(repoRoot, gitdir), "info", "exclude");
787
+ } catch (error) {
788
+ const code = error.code;
789
+ if (code === "ENOENT" || code === "ENOTDIR") return;
790
+ throw error;
791
+ }
792
+ }
793
+ /**
760
794
  * Find the nearest git repo root by walking up from a start directory.
761
795
  *
762
796
  * @param fromDir - Directory to start from.
@@ -851,7 +885,8 @@ async function isOxfmtIgnored(options) {
851
885
  reason: "gitignore"
852
886
  };
853
887
  if (repoRoot) {
854
- if (await matchIgnoreFile(filepath, join(repoRoot, ".git", "info", "exclude"), useCache, repoRoot)) return {
888
+ const infoExcludePath = await resolveGitInfoExcludePath(repoRoot);
889
+ if (infoExcludePath && await matchIgnoreFile(filepath, infoExcludePath, useCache, repoRoot)) return {
855
890
  ignored: true,
856
891
  reason: "git-info-exclude"
857
892
  };
@@ -881,4 +916,4 @@ async function isOxfmtIgnored(options) {
881
916
  return { ignored: false };
882
917
  }
883
918
  //#endregion
884
- export { isOxfmtIgnored, loadOxfmtConfig, resolveOxfmtrcPath };
919
+ export { getConfigCacheKey, getResolveCacheKey, isOxfmtIgnored, loadOxfmtConfig, readConfigFromFile, resolveOxfmtrcPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "load-oxfmt-config",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
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",
@@ -50,19 +50,19 @@
50
50
  },
51
51
  "devDependencies": {
52
52
  "@ntnyq/tsconfig": "^3.1.0",
53
- "@types/node": "^26.0.1",
54
- "@typescript/native-preview": "^7.0.0-dev.20260629.1",
53
+ "@types/node": "^26.1.0",
54
+ "@typescript/native-preview": "^7.0.0-dev.20260706.1",
55
55
  "bumpp": "^11.1.0",
56
56
  "husky": "^9.1.7",
57
57
  "nano-staged": "^1.0.2",
58
58
  "npm-run-all2": "^9.0.2",
59
- "oxfmt": "^0.57.0",
60
- "oxlint": "^1.72.0",
59
+ "oxfmt": "^0.58.0",
60
+ "oxlint": "^1.73.0",
61
61
  "tsdown": "^0.22.3",
62
- "vitest": "^4.1.9"
62
+ "vitest": "^4.1.10"
63
63
  },
64
64
  "peerDependencies": {
65
- "oxfmt": ">=0.57.0"
65
+ "oxfmt": ">=0.58.0"
66
66
  },
67
67
  "nano-staged": {
68
68
  "*.{js,ts,mjs,tsx}": "oxlint --fix",