@theholocron/datapad 5.0.0-alpha.13 → 5.0.0-alpha.133

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
@@ -43,15 +43,41 @@ export const defineConfig = createDefineConfig<TasksConfig>();
43
43
 
44
44
  ## API
45
45
 
46
- ### `loadConfigFile<T>({ cwd, name, extensions? })`
47
-
48
- Loads the first `<name>.config.<ext>` found in `cwd`. Probe order is
49
- **TS-first** — `.ts` → `.js` → `.mjs` → `.cjs` → `.json` — overridable via
50
- `extensions`. Returns `{ config, filepath } | null`. `null` means "no such
51
- file"; a file that exists but cannot be parsed / loaded / has no default
52
- export throws `ConfigFileError`.
53
-
54
- ### `loadLayered<T>({ cwd, name, fallback?, extensions? })`
46
+ ### `loadConfigFile<T>({ cwd, name, extensions?, walkUp? })`
47
+
48
+ Loads the first `<name>.config.<ext>` found starting at `cwd`. Probe order
49
+ is **TS-first** — `.ts` → `.js` → `.mjs` → `.cjs` → `.json` — overridable
50
+ via `extensions`. Returns `{ config, filepath } | null`. `null` means "no
51
+ such file anywhere searched"; a file that exists but cannot be parsed /
52
+ loaded / has no default export throws `ConfigFileError`.
53
+
54
+ `walkUp: true` (off by default) adds the "nearest config" search
55
+ `cosmiconfig`/`postcss`/`stylelint`/ESLint's flat config all do: when `cwd`
56
+ has no match, check its parent, then its parent's parent, and so on —
57
+ stopping at the first directory containing a `.git` entry (that directory
58
+ is still searched before stopping) or the filesystem root. Never wanders
59
+ past a repo boundary into an unrelated enclosing directory. `loadLayered`
60
+ passes its own `walkUp` through to both the dedicated-file and
61
+ fallback-file lookups. Built on `@theholocron/fs-utils`'s generic
62
+ `findUpward`/`hasGitEntry` — not reimplemented here.
63
+
64
+ ### `loadConfigFromContent<T>({ dir, content, name, extension })`
65
+
66
+ Loads config from content that didn't come from a file already on disk —
67
+ writes it to `<dir>/<name>.config.<extension>` first, then loads it through
68
+ the exact same internals `loadConfigFile` uses for a file it discovered
69
+ itself. `dir` matters: it's what upward `node_modules` resolution sees, so
70
+ content that does `import { defineConfig } from "@theholocron/cli"` only
71
+ resolves if `dir` sits under a tree where that package is a real
72
+ dependency. Callers own creating and cleaning up `dir`. Returns
73
+ `{ config, filepath }`; throws `ConfigFileError` the same way
74
+ `loadConfigFile` does on a parse/load failure.
75
+
76
+ Built for `@theholocron/sentinel`'s `validateConfig()` — it fetches a
77
+ repo's `holocron.config.*` from GitHub's API, not a local checkout, but
78
+ still needs the fetched content to execute as a real module.
79
+
80
+ ### `loadLayered<T>({ cwd, name, fallback?, extensions?, walkUp? })`
55
81
 
56
82
  `<name>.config.*` layered over the `[fallback.key]` of
57
83
  `<fallback.file>.config.*`. Either, both, or neither may be present; the
@@ -63,7 +89,10 @@ paths actually merged, lowest priority first.
63
89
 
64
90
  Deep-merge, `vite`-style: plain objects merge recursively, arrays
65
91
  concatenate (`base` first), `undefined` in `override` is skipped, every
66
- other `override` value replaces `base`. Neither input is mutated.
92
+ other `override` value replaces `base`. Neither input is mutated. A
93
+ re-export of `@theholocron/object-utils`'s `deepMerge` under this
94
+ package's established name — the merge logic itself has no awareness of
95
+ configs at all, so it lives there instead of being duplicated here.
67
96
 
68
97
  ### `createDefineConfig<T>()`
69
98
 
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { deepMerge as mergeConfig } from "@theholocron/object-utils";
1
2
  //#region src/define.d.ts
2
3
  /**
3
4
  * Build a typed identity `defineConfig` for a specific config shape.
@@ -31,22 +32,43 @@ declare class ConfigFileError extends Error {
31
32
  //#endregion
32
33
  //#region src/load.d.ts
33
34
  /**
34
- * Discover and load `<name>.config.*` files. Holocron-agnostic — no
35
- * schema, no validation, no defaults. Consumers layer those on top.
35
+ * Discover and load `<name>.config.*` files, or load one from content that
36
+ * didn't come from a file at all ({@link loadConfigFromContent}).
37
+ * Holocron-agnostic — no schema, no validation, no defaults. Consumers
38
+ * layer those on top.
36
39
  *
37
40
  * Probe order is **TS-first**: `.ts` → `.js` → `.mjs` → `.cjs` → `.json`.
38
41
  * TS is loaded through `tsx`'s `tsImport` (a runtime dependency) so a
39
42
  * typed `defineConfig` file works with no build step.
43
+ *
44
+ * `walkUp` (off by default) adds the "nearest config" search
45
+ * cosmiconfig/postcss/stylelint/ESLint's flat config all do: when `cwd`
46
+ * itself has no match, check its parent, then its parent's parent, and so
47
+ * on — stopping at the first directory containing a `.git` entry (checked
48
+ * after that directory's own config probe, so the project root itself is
49
+ * still searched) or the filesystem root. Never wanders past a repo
50
+ * boundary into an unrelated enclosing directory. Built on
51
+ * `@theholocron/fs-utils`'s `findUpward`/`hasGitEntry` — the same
52
+ * upward-walk shape `@theholocron/sentinel`'s own `findPackageRoot` needs,
53
+ * only with a different predicate.
40
54
  */
41
55
  /** Default extension probe order, highest priority first. */
42
56
  declare const DEFAULT_EXTENSIONS: readonly ["ts", "js", "mjs", "cjs", "json"];
43
57
  interface LoadConfigFileOptions {
44
- /** Directory to look in. */
58
+ /** Directory to start looking in. */
45
59
  cwd: string;
46
60
  /** Base name — `"holocron"` resolves `holocron.config.{ts,js,mjs,cjs,json}`. */
47
61
  name: string;
48
62
  /** Override the extension probe order. */
49
63
  extensions?: readonly string[];
64
+ /**
65
+ * Walk upward through ancestor directories when `cwd` itself has no
66
+ * match, stopping at the first directory containing a `.git` entry (that
67
+ * directory is still searched before stopping) or the filesystem root.
68
+ * Off by default — exact-`cwd`-only, unchanged behavior for any existing
69
+ * caller that doesn't pass this.
70
+ */
71
+ walkUp?: boolean;
50
72
  }
51
73
  interface Loaded<T> {
52
74
  config: T;
@@ -54,9 +76,11 @@ interface Loaded<T> {
54
76
  filepath: string;
55
77
  }
56
78
  /**
57
- * Load the first `<name>.config.<ext>` that exists in `cwd`. Returns
58
- * `null` when none is present; throws {@link ConfigFileError} when a file
59
- * exists but cannot be loaded.
79
+ * Load the first `<name>.config.<ext>` found starting at `cwd` — just
80
+ * `cwd` itself by default, or walking up through ancestor directories when
81
+ * `walkUp` is set (see the module doc comment for the stop conditions).
82
+ * Returns `null` when none is present anywhere searched; throws
83
+ * {@link ConfigFileError} when a file exists but cannot be loaded.
60
84
  */
61
85
  declare function loadConfigFile<T>(opts: LoadConfigFileOptions): Promise<Loaded<T> | null>;
62
86
  interface LoadLayeredOptions extends LoadConfigFileOptions {
@@ -84,18 +108,33 @@ interface LayeredResult<T> {
84
108
  * when neither source resolves.
85
109
  */
86
110
  declare function loadLayered<T>(opts: LoadLayeredOptions): Promise<LayeredResult<T> | null>;
87
- //#endregion
88
- //#region src/merge.d.ts
111
+ interface LoadConfigFromContentOptions {
112
+ /**
113
+ * Directory to write `<name>.config.<extension>` into before loading —
114
+ * this is what upward `node_modules` resolution sees, so a config that
115
+ * does `import { defineConfig } from "@theholocron/cli"` only resolves
116
+ * if `dir` sits under a tree where that package is a real dependency.
117
+ * Callers own creating and cleaning up `dir` — this function only
118
+ * writes the one file into it.
119
+ */
120
+ dir: string;
121
+ /** Raw file content — not necessarily sourced from a local file at all (a network fetch, a git blob, …). */
122
+ content: string;
123
+ /** Base name — `"holocron"` writes `holocron.config.<extension>`. */
124
+ name: string;
125
+ /** Which of `DEFAULT_EXTENSIONS` `content` is — determines how it's interpreted (ts/js/mjs/cjs parsed as a module, json as `JSON.parse`). */
126
+ extension: string;
127
+ }
89
128
  /**
90
- * Deep-merge two config objects, `vite`-style:
91
- *
92
- * - plain objects merge recursively
93
- * - arrays concatenate (`base` first, then `override`)
94
- * - an `undefined` value in `override` is skipped (keeps `base`)
95
- * - every other `override` value replaces `base`
96
- *
97
- * Neither input is mutated.
129
+ * Load config from content that didn't come from a file already on disk —
130
+ * writes it to `<dir>/<name>.config.<extension>` first, then loads it
131
+ * through the exact same path {@link loadConfigFile} uses for a file it
132
+ * discovered itself. Exists because sourcing config content from somewhere
133
+ * other than the local filesystem (a GitHub API fetch, for one — see
134
+ * `@theholocron/sentinel`) doesn't change what loading it correctly means:
135
+ * a `.ts` config still needs real module resolution, `defineConfig` import
136
+ * included, not a re-implemented parser.
98
137
  */
99
- declare function mergeConfig<T>(base: T, override: unknown): T;
138
+ declare function loadConfigFromContent<T>(opts: LoadConfigFromContentOptions): Promise<Loaded<T>>;
100
139
  //#endregion
101
- export { ConfigFileError, DEFAULT_EXTENSIONS, type LayeredResult, type LoadConfigFileOptions, type LoadLayeredOptions, type Loaded, createDefineConfig, loadConfigFile, loadLayered, mergeConfig };
140
+ export { ConfigFileError, DEFAULT_EXTENSIONS, type LayeredResult, type LoadConfigFileOptions, type LoadConfigFromContentOptions, type LoadLayeredOptions, type Loaded, createDefineConfig, loadConfigFile, loadConfigFromContent, loadLayered, mergeConfig };
package/dist/index.mjs CHANGED
@@ -1,6 +1,9 @@
1
- import { readFile, stat } from "node:fs/promises";
1
+ import { readFile, stat, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
+ import { findUpward, hasGitEntry } from "@theholocron/fs-utils";
4
+ import { deepMerge, deepMerge as mergeConfig } from "@theholocron/object-utils";
3
5
  import { pathToFileURL } from "node:url";
6
+ import { errorMessage } from "@theholocron/misc-utils";
4
7
  //#region src/define.ts
5
8
  /**
6
9
  * Build a typed identity `defineConfig` for a specific config shape.
@@ -34,43 +37,97 @@ var ConfigFileError = class extends Error {
34
37
  }
35
38
  };
36
39
  //#endregion
37
- //#region src/merge.ts
40
+ //#region src/utils.ts
38
41
  /**
39
- * Deep-merge two config objects, `vite`-style:
40
- *
41
- * - plain objects merge recursively
42
- * - arrays concatenate (`base` first, then `override`)
43
- * - an `undefined` value in `override` is skipped (keeps `base`)
44
- * - every other `override` value replaces `base`
45
- *
46
- * Neither input is mutated.
42
+ * The actual file-loading mechanics `load.ts`'s exported functions share:
43
+ * JSON parsing, dynamic `import()` (TS via `tsx`'s persistent ESM loader,
44
+ * JS/MJS/CJS natively), and unwrapping the CJS/ESM default-export
45
+ * double-wrap `tsx`'s transform produces. Kept separate from `load.ts`'s
46
+ * own orchestration (which directory, which extension order, upward
47
+ * search) — this file only knows "given one exact filepath and
48
+ * extension, load it," always throwing {@link ConfigFileError} on failure.
49
+ */
50
+ async function loadFile(filepath, ext) {
51
+ if (ext === "json") return loadJson(filepath);
52
+ return extractDefault(filepath, ext === "ts" ? await importTs(filepath) : await importModule(filepath));
53
+ }
54
+ async function loadJson(filepath) {
55
+ const text = await readFile(filepath, "utf8");
56
+ try {
57
+ return JSON.parse(text);
58
+ } catch (err) {
59
+ throw new ConfigFileError(`${filepath} is not valid JSON: ${errorMessage(err)}`, filepath);
60
+ }
61
+ }
62
+ async function importModule(filepath) {
63
+ try {
64
+ return await import(pathToFileURL(filepath).href);
65
+ } catch (err) {
66
+ throw new ConfigFileError(`could not load ${filepath}: ${errorMessage(err)}`, filepath);
67
+ }
68
+ }
69
+ let tsRegistered;
70
+ async function registerTsx() {
71
+ const { register } = await import("tsx/esm/api");
72
+ register();
73
+ }
74
+ /**
75
+ * Load a `.ts` file. `tsx`'s ESM loader is registered once per process
76
+ * (lazily — only when a `.ts` file is actually loaded). `register()` is
77
+ * used over `tsImport()` because a single run may load several TS files
78
+ * (`loadLayered` reads a dedicated file *and* a parent file) and
79
+ * `tsImport`'s one-off register/unregister cycle is not reentrant.
47
80
  */
48
- function mergeConfig(base, override) {
49
- if (!isPlainObject(base) || !isPlainObject(override)) return override === void 0 ? base : override;
50
- const out = { ...base };
51
- for (const [key, value] of Object.entries(override)) {
52
- if (value === void 0) continue;
53
- const prev = out[key];
54
- if (Array.isArray(prev) && Array.isArray(value)) out[key] = [...prev, ...value];
55
- else if (isPlainObject(prev) && isPlainObject(value)) out[key] = mergeConfig(prev, value);
56
- else out[key] = value;
81
+ async function importTs(filepath) {
82
+ tsRegistered ??= registerTsx();
83
+ await tsRegistered;
84
+ try {
85
+ return await import(pathToFileURL(filepath).href);
86
+ } catch (err) {
87
+ throw new ConfigFileError(`could not load ${filepath}: ${errorMessage(err)}`, filepath);
57
88
  }
58
- return out;
59
89
  }
60
- function isPlainObject(value) {
61
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
62
- const proto = Object.getPrototypeOf(value);
63
- return proto === Object.prototype || proto === null;
90
+ /**
91
+ * Unwrap `export default`. `tsx` CJS-transforms `export default x` into
92
+ * `exports.default = x`, which dynamic import wraps as
93
+ * `{ default: { __esModule: true, default: x } }` — strip the extra layer
94
+ * when present so ESM and CJS outputs both resolve.
95
+ */
96
+ function extractDefault(filepath, mod) {
97
+ const outer = mod.default;
98
+ const raw = outer?.__esModule === true ? outer.default : outer;
99
+ if (raw === void 0 || raw === null) throw new ConfigFileError(`${filepath} must have a default export (use \`export default defineConfig({…})\`)`, filepath);
100
+ return raw;
101
+ }
102
+ async function isFile(path) {
103
+ try {
104
+ return (await stat(path)).isFile();
105
+ } catch {
106
+ return false;
107
+ }
64
108
  }
65
109
  //#endregion
66
110
  //#region src/load.ts
67
111
  /**
68
- * Discover and load `<name>.config.*` files. Holocron-agnostic — no
69
- * schema, no validation, no defaults. Consumers layer those on top.
112
+ * Discover and load `<name>.config.*` files, or load one from content that
113
+ * didn't come from a file at all ({@link loadConfigFromContent}).
114
+ * Holocron-agnostic — no schema, no validation, no defaults. Consumers
115
+ * layer those on top.
70
116
  *
71
117
  * Probe order is **TS-first**: `.ts` → `.js` → `.mjs` → `.cjs` → `.json`.
72
118
  * TS is loaded through `tsx`'s `tsImport` (a runtime dependency) so a
73
119
  * typed `defineConfig` file works with no build step.
120
+ *
121
+ * `walkUp` (off by default) adds the "nearest config" search
122
+ * cosmiconfig/postcss/stylelint/ESLint's flat config all do: when `cwd`
123
+ * itself has no match, check its parent, then its parent's parent, and so
124
+ * on — stopping at the first directory containing a `.git` entry (checked
125
+ * after that directory's own config probe, so the project root itself is
126
+ * still searched) or the filesystem root. Never wanders past a repo
127
+ * boundary into an unrelated enclosing directory. Built on
128
+ * `@theholocron/fs-utils`'s `findUpward`/`hasGitEntry` — the same
129
+ * upward-walk shape `@theholocron/sentinel`'s own `findPackageRoot` needs,
130
+ * only with a different predicate.
74
131
  */
75
132
  /** Default extension probe order, highest priority first. */
76
133
  const DEFAULT_EXTENSIONS = [
@@ -81,21 +138,34 @@ const DEFAULT_EXTENSIONS = [
81
138
  "json"
82
139
  ];
83
140
  /**
84
- * Load the first `<name>.config.<ext>` that exists in `cwd`. Returns
85
- * `null` when none is present; throws {@link ConfigFileError} when a file
86
- * exists but cannot be loaded.
141
+ * Probe `dir` for `<name>.config.<ext>` in extension-priority order,
142
+ * loading and returning the first match. The one piece of per-directory
143
+ * orchestration `loadConfigFile` needs on both its non-walking and
144
+ * walking paths — pulled out so it can be unit-tested directly instead of
145
+ * only through `loadConfigFile`'s full probe/walk behavior.
87
146
  */
88
- async function loadConfigFile(opts) {
89
- const extensions = opts.extensions ?? DEFAULT_EXTENSIONS;
147
+ async function probeExtensions(dir, name, extensions) {
90
148
  for (const ext of extensions) {
91
- const filepath = join(opts.cwd, `${opts.name}.config.${ext}`);
149
+ const filepath = join(dir, `${name}.config.${ext}`);
92
150
  if (!await isFile(filepath)) continue;
93
151
  return {
94
152
  config: await loadFile(filepath, ext),
95
153
  filepath
96
154
  };
97
155
  }
98
- return null;
156
+ }
157
+ /**
158
+ * Load the first `<name>.config.<ext>` found starting at `cwd` — just
159
+ * `cwd` itself by default, or walking up through ancestor directories when
160
+ * `walkUp` is set (see the module doc comment for the stop conditions).
161
+ * Returns `null` when none is present anywhere searched; throws
162
+ * {@link ConfigFileError} when a file exists but cannot be loaded.
163
+ */
164
+ async function loadConfigFile(opts) {
165
+ const extensions = opts.extensions ?? DEFAULT_EXTENSIONS;
166
+ const checkDir = (dir) => probeExtensions(dir, opts.name, extensions);
167
+ if (!opts.walkUp) return await checkDir(opts.cwd) ?? null;
168
+ return await findUpward(opts.cwd, checkDir, hasGitEntry) ?? null;
99
169
  }
100
170
  /**
101
171
  * `<name>.config.*` layered over the `[fallback.key]` of
@@ -111,7 +181,8 @@ async function loadLayered(opts) {
111
181
  const parent = await loadConfigFile({
112
182
  cwd: opts.cwd,
113
183
  name: opts.fallback.file,
114
- extensions: opts.extensions
184
+ extensions: opts.extensions,
185
+ walkUp: opts.walkUp
115
186
  });
116
187
  if (parent && parent.config[opts.fallback.key] !== void 0) {
117
188
  base = parent.config[opts.fallback.key];
@@ -119,7 +190,7 @@ async function loadLayered(opts) {
119
190
  }
120
191
  }
121
192
  if (!dedicated && base === void 0) return null;
122
- const config = mergeConfig(base, dedicated?.config);
193
+ const config = deepMerge(base, dedicated?.config);
123
194
  const sources = [baseFile, dedicated?.filepath ?? null].filter((s) => s !== null);
124
195
  return {
125
196
  config,
@@ -127,65 +198,23 @@ async function loadLayered(opts) {
127
198
  sources
128
199
  };
129
200
  }
130
- async function loadFile(filepath, ext) {
131
- if (ext === "json") return loadJson(filepath);
132
- return extractDefault(filepath, ext === "ts" ? await importTs(filepath) : await importModule(filepath));
133
- }
134
- async function loadJson(filepath) {
135
- const text = await readFile(filepath, "utf8");
136
- try {
137
- return JSON.parse(text);
138
- } catch (err) {
139
- throw new ConfigFileError(`${filepath} is not valid JSON: ${message(err)}`, filepath);
140
- }
141
- }
142
- async function importModule(filepath) {
143
- try {
144
- return await import(pathToFileURL(filepath).href);
145
- } catch (err) {
146
- throw new ConfigFileError(`could not load ${filepath}: ${message(err)}`, filepath);
147
- }
148
- }
149
- let tsRegistered;
150
- async function registerTsx() {
151
- const { register } = await import("tsx/esm/api");
152
- register();
153
- }
154
- /**
155
- * Load a `.ts` config. `tsx`'s ESM loader is registered once per process
156
- * (lazily — only when a `.ts` config is actually loaded). `register()` is
157
- * used over `tsImport()` because a single run may load several TS configs
158
- * (`loadLayered` reads a dedicated file *and* a parent file) and
159
- * `tsImport`'s one-off register/unregister cycle is not reentrant.
160
- */
161
- async function importTs(filepath) {
162
- tsRegistered ??= registerTsx();
163
- await tsRegistered;
164
- try {
165
- return await import(pathToFileURL(filepath).href);
166
- } catch (err) {
167
- throw new ConfigFileError(`could not load ${filepath}: ${message(err)}`, filepath);
168
- }
169
- }
170
201
  /**
171
- * Unwrap `export default`. `tsx` CJS-transforms `export default x` into
172
- * `exports.default = x`, which dynamic import wraps as
173
- * `{ default: { __esModule: true, default: x } }` — strip the extra layer
174
- * when present so ESM and CJS outputs both resolve.
202
+ * Load config from content that didn't come from a file already on disk —
203
+ * writes it to `<dir>/<name>.config.<extension>` first, then loads it
204
+ * through the exact same path {@link loadConfigFile} uses for a file it
205
+ * discovered itself. Exists because sourcing config content from somewhere
206
+ * other than the local filesystem (a GitHub API fetch, for one — see
207
+ * `@theholocron/sentinel`) doesn't change what loading it correctly means:
208
+ * a `.ts` config still needs real module resolution, `defineConfig` import
209
+ * included, not a re-implemented parser.
175
210
  */
176
- function extractDefault(filepath, mod) {
177
- const outer = mod.default;
178
- const raw = outer?.__esModule === true ? outer.default : outer;
179
- if (raw === void 0 || raw === null) throw new ConfigFileError(`${filepath} must have a default export (use \`export default defineConfig({…})\`)`, filepath);
180
- return raw;
181
- }
182
- async function isFile(path) {
183
- try {
184
- return (await stat(path)).isFile();
185
- } catch {
186
- return false;
187
- }
211
+ async function loadConfigFromContent(opts) {
212
+ const filepath = join(opts.dir, `${opts.name}.config.${opts.extension}`);
213
+ await writeFile(filepath, opts.content, "utf8");
214
+ return {
215
+ config: await loadFile(filepath, opts.extension),
216
+ filepath
217
+ };
188
218
  }
189
- const message = (err) => err instanceof Error ? err.message : String(err);
190
219
  //#endregion
191
- export { ConfigFileError, DEFAULT_EXTENSIONS, createDefineConfig, loadConfigFile, loadLayered, mergeConfig };
220
+ export { ConfigFileError, DEFAULT_EXTENSIONS, createDefineConfig, loadConfigFile, loadConfigFromContent, loadLayered, mergeConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/datapad",
3
- "version": "5.0.0-alpha.13",
3
+ "version": "5.0.0-alpha.133",
4
4
  "description": "Generic config-file loading for Holocron — discover, load (JSON/JS/TS/ESM/CJS), and merge <name>.config.* files with a typed defineConfig.",
5
5
  "keywords": [
6
6
  "config",
@@ -32,13 +32,16 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
+ "@theholocron/fs-utils": "^1.9.2",
36
+ "@theholocron/misc-utils": "^1.9.2",
37
+ "@theholocron/object-utils": "^1.9.2",
35
38
  "tsx": "4.23.12"
36
39
  },
37
40
  "devDependencies": {
38
- "@theholocron/eslint-config": "^8.0.0",
39
- "@theholocron/tsconfig": "^8.0.0",
40
- "@theholocron/tsdown-config": "^8.0.0",
41
- "@theholocron/vitest-config": "^8.0.0",
41
+ "@theholocron/eslint-config": "^8.7.1",
42
+ "@theholocron/tsconfig": "^8.7.1",
43
+ "@theholocron/tsdown-config": "^8.7.1",
44
+ "@theholocron/vitest-config": "^8.7.0",
42
45
  "@types/node": "^26",
43
46
  "@vitest/coverage-v8": "^4.1.11",
44
47
  "@vitest/eslint-plugin": "^1.6.27",