@theholocron/pkg-utils 1.16.1-alpha.1 → 1.16.1-alpha.3

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
@@ -62,6 +62,32 @@ import { listWorkspacePackagesSync } from "@theholocron/pkg-utils";
62
62
  const publishable = listWorkspacePackagesSync(repoRoot).filter((p) => !p.manifest.private && p.name);
63
63
  ```
64
64
 
65
+ ### `discoverWorkspacePackages(repoRoot, options?)`
66
+
67
+ Every package the repo's `pnpm-workspace.yaml` declares, as `listWorkspacePackagesSync()` entries — in glob order, packages within a `<dir>/*` glob sorted by name. Two glob forms are understood: `<dir>/*` (each child of `<dir>` with a `package.json`) and a literal directory (that directory is the package). `!pattern` entries drop matching packages, the repo root (`.`) is skipped, and any other glob throws. Without a `pnpm-workspace.yaml`, or one with no `packages:`, `DEFAULT_WORKSPACE_ROOTS` (`packages`, `apps`, `plugins`) are scanned. `options.group` keeps one version group's folder, `options.excludeGroups` leaves some out, and `options.readText`, `options.readFile` and `options.listDirs` replace the disk access.
68
+
69
+ ```typescript
70
+ import { discoverWorkspacePackages } from "@theholocron/pkg-utils";
71
+
72
+ const names = discoverWorkspacePackages(repoRoot, { group: "clients" }).map((p) => p.name);
73
+ ```
74
+
75
+ ### `resolveWorkspaceGlobs(repoRoot, options?)` and `readWorkspaceGlobs(yaml)`
76
+
77
+ `readWorkspaceGlobs` reads the `packages:` list of a `pnpm-workspace.yaml` text into `{ include, exclude }` (a line-based read, not a YAML parse; quotes and trailing comments are stripped). `resolveWorkspaceGlobs` reads the file in `repoRoot` and falls back to the default roots as `<root>/*` when it lists none.
78
+
79
+ ### `parseCatalogs(text)`
80
+
81
+ The `catalog:` and `catalogs:` blocks of a `pnpm-workspace.yaml` as `{ default: {...}, <name>: {...} }`, package name to range. A line-based read of the flat format pnpm writes; entries outside a catalog are ignored.
82
+
83
+ ### `addWorkspaceMember(workspaceYaml, member)`
84
+
85
+ Adds `member` (for example `"."`) as the first quoted entry of the `packages:` list and returns `{ content, changed }`. A line-based edit that only adds a line, so the `catalog:` and `overrides:` blocks after it keep their formatting. Nothing changes when the member is already listed, or when there is no plain block-list `packages:`.
86
+
87
+ ### `bumpCatalogPins(content, fetchLatest, options?)`
88
+
89
+ Rewrites the pins of every catalog entry to the latest version `fetchLatest(name)` returns, keeping the range prefix (`^`, `~`) and the quoting. It never downgrades, leaves a package with no answer alone, and asks once per package. `options.include` limits which packages are looked up. Returns `{ content, bumps }`. `gt(a, b)` is the comparison it uses for `x.y.z` release versions.
90
+
65
91
  ### `PackageJson`
66
92
 
67
93
  The fields these helpers read, typed; any other field passes through untyped.
package/dist/index.d.mts CHANGED
@@ -66,6 +66,63 @@ export interface ListWorkspacePackagesOptions {
66
66
  * `readPackageJson` instead.
67
67
  */
68
68
  export declare function listWorkspacePackagesSync(repoRoot: string, options?: ListWorkspacePackagesOptions): WorkspacePackageEntry[];
69
+ /** The `packages:` entries of a `pnpm-workspace.yaml`, split by whether they include or exclude (`!pattern`) packages. */
70
+ export interface WorkspaceGlobs {
71
+ include: string[];
72
+ exclude: string[];
73
+ }
74
+ /**
75
+ * The `packages:` list of a `pnpm-workspace.yaml`. A line-based read of the
76
+ * plain block-list form, not a YAML parse -- the same approach (and for the
77
+ * same reason) as `ensureRootWorkspaceMember()`: the file routinely carries a
78
+ * `catalog:`/`catalogs:`/`overrides:` block after `packages:` that nothing
79
+ * here should have to round-trip. Quotes and trailing comments are stripped;
80
+ * a missing `packages:` key, or the inline `packages: [a, b]` form, reads as no
81
+ * globs (callers fall back to the default roots).
82
+ */
83
+ export declare function readWorkspaceGlobs(workspaceYaml: string): WorkspaceGlobs;
84
+ /** Where packages live when there is no `pnpm-workspace.yaml` (or it lists none): the layout before globs were read. */
85
+ export declare const DEFAULT_WORKSPACE_ROOTS: readonly string[];
86
+ export interface DiscoverWorkspacePackagesOptions extends Pick<ListWorkspacePackagesOptions, "listDirs" | "readFile"> {
87
+ /** Replaces the read of `pnpm-workspace.yaml`. Throw (`ENOENT`) when it is absent -- or leave it out to read the real file. */
88
+ readText?: (path: string) => string;
89
+ /**
90
+ * Only the packages of one version group: a folder of the repo (`clients`,
91
+ * `observability`). A package belongs when it is that folder or sits under it.
92
+ */
93
+ group?: string;
94
+ /**
95
+ * Leave out the packages of these version groups. A repo that releases some folders on
96
+ * their own line releases everything else together, and that release must not sweep the
97
+ * independent groups in.
98
+ */
99
+ excludeGroups?: readonly string[];
100
+ }
101
+ /**
102
+ * Every package the repo's `pnpm-workspace.yaml` declares, as
103
+ * `listWorkspacePackagesSync()` entries -- in glob order, packages within a
104
+ * `<dir>/*` glob sorted by name. Two forms of glob are understood:
105
+ *
106
+ * - `<dir>/*`: each child of `<dir>` that has a `package.json` (`clients/*`).
107
+ * - a literal directory: that directory is the package (`observability`,
108
+ * `docs/theme`).
109
+ *
110
+ * `!pattern` entries drop matching packages. The repo root (`.`) is not a
111
+ * workspace child and is skipped; callers that treat a root-only repo as a
112
+ * single package read the root manifest themselves. Any other glob throws
113
+ * rather than being silently skipped. Without a `pnpm-workspace.yaml`, or one
114
+ * with no `packages:`, the default roots are scanned.
115
+ *
116
+ * Nothing is filtered by `name`, `version` or `private`; that is the caller's
117
+ * call, as for `listWorkspacePackagesSync()`.
118
+ */
119
+ export declare function discoverWorkspacePackages(repoRoot: string, options?: DiscoverWorkspacePackagesOptions): WorkspacePackageEntry[];
120
+ /**
121
+ * The globs {@link discoverWorkspacePackages} scans: the repo's
122
+ * `pnpm-workspace.yaml` `packages:` list, or the {@link DEFAULT_WORKSPACE_ROOTS}
123
+ * as `<root>/*` when there is no such file or it lists none.
124
+ */
125
+ export declare function resolveWorkspaceGlobs(repoRoot: string, options?: Pick<DiscoverWorkspacePackagesOptions, "readText">): WorkspaceGlobs;
69
126
  /**
70
127
  * Parses `package.json` text. Returns `undefined` for anything that isn't a
71
128
  * JSON object — invalid JSON, or valid JSON of another type (an array, `null`,
@@ -78,6 +135,54 @@ export declare function listWorkspacePackagesSync(repoRoot: string, options?: Li
78
135
  * string-valued entries. Every other field passes through untouched.
79
136
  */
80
137
  export declare function parsePackageJson(text: string): PackageJson | undefined;
138
+ /** The `catalog:` and `catalogs:` of a `pnpm-workspace.yaml`: the default one under `default`, the named ones under their names. */
139
+ export type Catalogs = Record<string, Record<string, string>>;
140
+ /** The `packages:` key of a `pnpm-workspace.yaml`, as a line. */
141
+ export declare const PACKAGES_KEY: RegExp;
142
+ /** What an edit of a `pnpm-workspace.yaml` gives back: the new text, and whether it differs from what was passed in. */
143
+ export interface WorkspaceYamlEdit {
144
+ /** The file's text after the edit; the text passed in when nothing changed. */
145
+ content: string;
146
+ /** Whether `content` differs from the text passed in. */
147
+ changed: boolean;
148
+ }
149
+ /** A catalog pin rewritten by {@link bumpCatalogPins}. */
150
+ export interface CatalogBump {
151
+ /** The package whose pin was rewritten. */
152
+ pkg: string;
153
+ /** The version it was pinned to. */
154
+ from: string;
155
+ /** The version it is pinned to now. */
156
+ to: string;
157
+ }
158
+ /** Resolves a package's latest published version, or `null` when there is no answer. */
159
+ export type FetchLatest = (pkg: string) => Promise<string | null>;
160
+ /** What {@link bumpCatalogPins} may be told. */
161
+ export interface BumpCatalogPinsOptions {
162
+ /** Which pinned packages to look up. Default: all of them. */
163
+ include?: (pkg: string) => boolean;
164
+ }
165
+ /** Parse the `catalog:` and `catalogs:` blocks of `pnpm-workspace.yaml` (the generated, flat format). */
166
+ export declare function parseCatalogs(text: string): Catalogs;
167
+ /**
168
+ * Add `member` as the first entry of the `packages:` list, unless the list already has it. A targeted
169
+ * line-based edit, not a YAML round-trip: the file routinely carries a `catalog:`/`catalogs:`/`overrides:`
170
+ * block after `packages:`, and a parser would reformat content nobody asked to touch. It only ever adds a
171
+ * line, quoted like `"docs"`, and changes nothing when there is no `packages:` key or it is not the plain
172
+ * block-list form.
173
+ */
174
+ export declare function addWorkspaceMember(workspaceYaml: string, member: string): WorkspaceYamlEdit;
175
+ /** `a > b` for `x.y.z` release versions (prerelease tags are ignored). */
176
+ export declare function gt(a: string, b: string): boolean;
177
+ /**
178
+ * Rewrite the pins of the `catalog:` and every named `catalogs.*` entry to the latest published version,
179
+ * keeping the range prefix (`^`, `~`). Never downgrades; a package with no answer from `fetchLatest` is left
180
+ * alone. `fetchLatest` is called once per package.
181
+ */
182
+ export declare function bumpCatalogPins(content: string, fetchLatest: FetchLatest, options?: BumpCatalogPinsOptions): Promise<{
183
+ content: string;
184
+ bumps: CatalogBump[];
185
+ }>;
81
186
  export interface ReadPackageJsonOptions {
82
187
  /** Replaces the file read — for callers with an injected or in-memory filesystem. */
83
188
  readFile?: (path: string) => string;
package/dist/index.mjs CHANGED
@@ -158,6 +158,252 @@ function listWorkspacePackagesSync(repoRoot, options = {}) {
158
158
  }
159
159
  return entries;
160
160
  }
161
+ // src/pnpm-workspace.ts
162
+ /** The `packages:` key of a `pnpm-workspace.yaml`, as a line. */
163
+ const PACKAGES_KEY = /^packages:\s*(#.*)?$/;
164
+ /** Parse the `catalog:` and `catalogs:` blocks of `pnpm-workspace.yaml` (the generated, flat format). */
165
+ function parseCatalogs(text) {
166
+ const catalogs = { default: {} };
167
+ let current = null;
168
+ let inCatalogs = false;
169
+ for (const raw of text.split("\n")) {
170
+ if (raw.trim() === "" || raw.trimStart().startsWith("#")) continue;
171
+ if (/^catalog:\s*$/.test(raw)) {
172
+ current = "default";
173
+ inCatalogs = false;
174
+ continue;
175
+ }
176
+ if (/^catalogs:\s*$/.test(raw)) {
177
+ current = null;
178
+ inCatalogs = true;
179
+ continue;
180
+ }
181
+ if (/^\S/.test(raw)) {
182
+ current = null;
183
+ inCatalogs = false;
184
+ continue;
185
+ }
186
+ const named = inCatalogs ? raw.match(/^ {2}([A-Za-z0-9_-]+):\s*$/) : null;
187
+ if (named?.[1]) {
188
+ current = named[1];
189
+ catalogs[current] ??= {};
190
+ continue;
191
+ }
192
+ const entry = raw.match(/^\s+("[^"]+"|[^\s:][^:]*?):\s*(.+?)\s*$/);
193
+ if (entry?.[1] && entry[2] && current) (catalogs[current] ??= {})[entry[1].replace(/^"|"$/g, "")] = entry[2].replace(/^"|"$/g, "");
194
+ }
195
+ return catalogs;
196
+ }
197
+ /**
198
+ * Add `member` as the first entry of the `packages:` list, unless the list already has it. A targeted
199
+ * line-based edit, not a YAML round-trip: the file routinely carries a `catalog:`/`catalogs:`/`overrides:`
200
+ * block after `packages:`, and a parser would reformat content nobody asked to touch. It only ever adds a
201
+ * line, quoted like `"docs"`, and changes nothing when there is no `packages:` key or it is not the plain
202
+ * block-list form.
203
+ */
204
+ function addWorkspaceMember(workspaceYaml, member) {
205
+ const lines = workspaceYaml.split("\n");
206
+ const packagesLineIdx = lines.findIndex((l) => PACKAGES_KEY.test(l));
207
+ if (packagesLineIdx === -1) return {
208
+ content: workspaceYaml,
209
+ changed: false
210
+ };
211
+ const items = [];
212
+ let cursor = packagesLineIdx + 1;
213
+ while (cursor < lines.length && /^\s*-\s*/.test(lines[cursor])) {
214
+ items.push(lines[cursor]);
215
+ cursor++;
216
+ }
217
+ if (items.some((item) => {
218
+ const value = item.replace(/^\s*-\s*/, "").replace(/^["']|["']$/g, "").trim();
219
+ return value === member || member === "." && value === "";
220
+ })) return {
221
+ content: workspaceYaml,
222
+ changed: false
223
+ };
224
+ const indent = items[0]?.match(/^(\s*-\s*)/)?.[1] ?? " - ";
225
+ return {
226
+ content: [
227
+ ...lines.slice(0, packagesLineIdx + 1),
228
+ `${indent}"${member}"`,
229
+ ...lines.slice(packagesLineIdx + 1)
230
+ ].join("\n"),
231
+ changed: true
232
+ };
233
+ }
234
+ /** `a > b` for `x.y.z` release versions (prerelease tags are ignored). */
235
+ function gt(a, b) {
236
+ const pa = a.split(".").map((n) => parseInt(n, 10));
237
+ const pb = b.split(".").map((n) => parseInt(n, 10));
238
+ for (let i = 0; i < 3; i++) {
239
+ const x = pa[i] ?? 0;
240
+ const y = pb[i] ?? 0;
241
+ if (x !== y) return x > y;
242
+ }
243
+ return false;
244
+ }
245
+ const CATALOG_LINE = /^(\s*['"]?)((?:@[a-z0-9._-]+\/)?[a-z0-9._-]+)(['"]?\s*:\s*['"]?)([~^]?)(\d+\.\d+\.\d+[\w.-]*)(['"]?\s*)$/;
246
+ /**
247
+ * Rewrite the pins of the `catalog:` and every named `catalogs.*` entry to the latest published version,
248
+ * keeping the range prefix (`^`, `~`). Never downgrades; a package with no answer from `fetchLatest` is left
249
+ * alone. `fetchLatest` is called once per package.
250
+ */
251
+ async function bumpCatalogPins(content, fetchLatest, options = {}) {
252
+ const include = options.include ?? (() => true);
253
+ const lines = content.split("\n");
254
+ const wanted = /* @__PURE__ */ new Set();
255
+ for (const line of lines) {
256
+ const m = CATALOG_LINE.exec(line);
257
+ if (m && include(m[2])) wanted.add(m[2]);
258
+ }
259
+ const latest = /* @__PURE__ */ new Map();
260
+ await Promise.all([...wanted].map(async (pkg) => {
261
+ latest.set(pkg, await fetchLatest(pkg));
262
+ }));
263
+ const bumps = [];
264
+ return {
265
+ content: lines.map((line) => {
266
+ const m = CATALOG_LINE.exec(line);
267
+ if (!m) return line;
268
+ const [, pre, pkg, mid, prefix, current, post] = m;
269
+ const next = latest.get(pkg);
270
+ if (!next || next === current || !gt(next, current)) return line;
271
+ bumps.push({
272
+ pkg,
273
+ from: current,
274
+ to: next
275
+ });
276
+ return `${pre}${pkg}${mid}${prefix}${next}${post}`;
277
+ }).join("\n"),
278
+ bumps
279
+ };
280
+ }
281
+ // src/workspace-globs.ts
282
+ /**
283
+ * The `packages:` list of a `pnpm-workspace.yaml`. A line-based read of the
284
+ * plain block-list form, not a YAML parse -- the same approach (and for the
285
+ * same reason) as `ensureRootWorkspaceMember()`: the file routinely carries a
286
+ * `catalog:`/`catalogs:`/`overrides:` block after `packages:` that nothing
287
+ * here should have to round-trip. Quotes and trailing comments are stripped;
288
+ * a missing `packages:` key, or the inline `packages: [a, b]` form, reads as no
289
+ * globs (callers fall back to the default roots).
290
+ */
291
+ function readWorkspaceGlobs(workspaceYaml) {
292
+ const lines = workspaceYaml.split("\n");
293
+ const start = lines.findIndex((line) => PACKAGES_KEY.test(line));
294
+ const globs = {
295
+ include: [],
296
+ exclude: []
297
+ };
298
+ if (start === -1) return globs;
299
+ for (const line of lines.slice(start + 1)) {
300
+ if (line.trim() === "" || /^\s*#/.test(line)) continue;
301
+ const item = /^\s*-\s*(.*)$/.exec(line);
302
+ if (!item) break;
303
+ const value = item[1].replace(/(^|\s)#.*$/, "").trim().replace(/^["']|["']$/g, "").trim();
304
+ if (value === "") continue;
305
+ if (value.startsWith("!")) globs.exclude.push(normalizeGlob(value.slice(1)));
306
+ else globs.include.push(normalizeGlob(value));
307
+ }
308
+ return globs;
309
+ }
310
+ /** `./a/b/` -> `a/b`: the form {@link discoverWorkspacePackages} matches against `relativeDir`. */
311
+ function normalizeGlob(glob) {
312
+ return glob.replace(/^\.\//, "").replace(/\/+$/, "");
313
+ }
314
+ // src/discover-workspace-packages.ts
315
+ /** Where packages live when there is no `pnpm-workspace.yaml` (or it lists none): the layout before globs were read. */
316
+ const DEFAULT_WORKSPACE_ROOTS = [
317
+ "packages",
318
+ "apps",
319
+ "plugins"
320
+ ];
321
+ /** Characters pnpm globs treat specially. A literal directory has none of them. */
322
+ const GLOB_CHARS = /[*?[\]{}!]/;
323
+ /**
324
+ * Every package the repo's `pnpm-workspace.yaml` declares, as
325
+ * `listWorkspacePackagesSync()` entries -- in glob order, packages within a
326
+ * `<dir>/*` glob sorted by name. Two forms of glob are understood:
327
+ *
328
+ * - `<dir>/*`: each child of `<dir>` that has a `package.json` (`clients/*`).
329
+ * - a literal directory: that directory is the package (`observability`,
330
+ * `docs/theme`).
331
+ *
332
+ * `!pattern` entries drop matching packages. The repo root (`.`) is not a
333
+ * workspace child and is skipped; callers that treat a root-only repo as a
334
+ * single package read the root manifest themselves. Any other glob throws
335
+ * rather than being silently skipped. Without a `pnpm-workspace.yaml`, or one
336
+ * with no `packages:`, the default roots are scanned.
337
+ *
338
+ * Nothing is filtered by `name`, `version` or `private`; that is the caller's
339
+ * call, as for `listWorkspacePackagesSync()`.
340
+ */
341
+ function discoverWorkspacePackages(repoRoot, options = {}) {
342
+ const base = resolve(repoRoot);
343
+ const readFile = options.readFile ?? ((path) => readFileSync(path, "utf8"));
344
+ const { include, exclude } = resolveWorkspaceGlobs(base, options);
345
+ const entries = /* @__PURE__ */ new Map();
346
+ for (const glob of include) {
347
+ if (glob === "." || glob === "") continue;
348
+ for (const entry of expandGlob(base, glob, {
349
+ readFile,
350
+ listDirs: options.listDirs
351
+ })) if (!exclude.some((pattern) => matches(entry.relativeDir, pattern))) entries.set(entry.relativeDir, entry);
352
+ }
353
+ const { group, excludeGroups = [] } = options;
354
+ return [...entries.values()].filter(({ relativeDir }) => (group === void 0 || inGroup(relativeDir, group)) && !excludeGroups.some((excluded) => inGroup(relativeDir, excluded)));
355
+ }
356
+ /** `relativeDir` is the group's folder, or is under it. `clients` holds `clients/http`, not `clients-old/x`. */
357
+ function inGroup(relativeDir, group) {
358
+ const folder = group.replace(/^\.\//, "").replace(/\/+$/, "");
359
+ return relativeDir === folder || relativeDir.startsWith(`${folder}/`);
360
+ }
361
+ /**
362
+ * The globs {@link discoverWorkspacePackages} scans: the repo's
363
+ * `pnpm-workspace.yaml` `packages:` list, or the {@link DEFAULT_WORKSPACE_ROOTS}
364
+ * as `<root>/*` when there is no such file or it lists none.
365
+ */
366
+ function resolveWorkspaceGlobs(repoRoot, options = {}) {
367
+ const readText = options.readText ?? ((path) => readFileSync(path, "utf8"));
368
+ let workspaceYaml = "";
369
+ try {
370
+ workspaceYaml = readText(join(resolve(repoRoot), "pnpm-workspace.yaml"));
371
+ } catch {}
372
+ const { include, exclude } = readWorkspaceGlobs(workspaceYaml);
373
+ return {
374
+ include: include.length > 0 ? include : DEFAULT_WORKSPACE_ROOTS.map((root) => `${root}/*`),
375
+ exclude
376
+ };
377
+ }
378
+ function expandGlob(base, glob, options) {
379
+ if (glob.endsWith("/*") && !GLOB_CHARS.test(glob.slice(0, -2))) return listWorkspacePackagesSync(base, {
380
+ ...options,
381
+ roots: [glob.slice(0, -2)]
382
+ });
383
+ if (GLOB_CHARS.test(glob)) throw new TypeError(`unsupported workspace pattern ${JSON.stringify(glob)}: only "<dir>/*" and literal directories are supported`);
384
+ const rel = relative(base, resolve(base, glob));
385
+ if (isAbsolute(glob) || rel === ".." || rel.startsWith(`..${sep}`)) throw new TypeError(`workspace directory must be inside the repo, got ${JSON.stringify(glob)}`);
386
+ const dir = join(base, glob);
387
+ try {
388
+ const manifest = parsePackageJson(options.readFile(join(dir, "package.json")));
389
+ return manifest ? [{
390
+ name: manifest.name,
391
+ dir,
392
+ relativeDir: glob,
393
+ manifest
394
+ }] : [];
395
+ } catch {
396
+ return [];
397
+ }
398
+ }
399
+ /** `relativeDir` against an exclusion: the same directory, or a child of `<dir>/*`. */
400
+ function matches(relativeDir, pattern) {
401
+ if (pattern.endsWith("/*")) {
402
+ const parent = pattern.slice(0, -2);
403
+ return relativeDir.startsWith(`${parent}/`) && !relativeDir.slice(parent.length + 1).includes("/");
404
+ }
405
+ return relativeDir === pattern;
406
+ }
161
407
  // src/read-package-json.ts
162
408
  /**
163
409
  * The parsed `<dir>/package.json`, or `undefined` when it is missing,
@@ -179,4 +425,4 @@ function readPackageJsonSync(dir, options = {}) {
179
425
  return;
180
426
  }
181
427
  }
182
- export { dependencyNames, detectPackageManager, listWorkspacePackagesSync, parsePackageJson, readPackageJson, readPackageJsonSync };
428
+ export { DEFAULT_WORKSPACE_ROOTS, PACKAGES_KEY, addWorkspaceMember, bumpCatalogPins, dependencyNames, detectPackageManager, discoverWorkspacePackages, gt, listWorkspacePackagesSync, parseCatalogs, parsePackageJson, readPackageJson, readPackageJsonSync, readWorkspaceGlobs, resolveWorkspaceGlobs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/pkg-utils",
3
- "version": "1.16.1-alpha.1",
3
+ "version": "1.16.1-alpha.3",
4
4
  "description": "Read package.json, list its dependencies and detect the package manager.",
5
5
  "keywords": [
6
6
  "package.json",
@@ -31,9 +31,6 @@
31
31
  "dist"
32
32
  ],
33
33
  "devDependencies": {
34
- "@theholocron/eslint-config": "^8.8.2",
35
- "@theholocron/tsdown-config": "^8.8.2",
36
- "@theholocron/vitest-config": "^8.8.2",
37
34
  "@tsconfig/node-lts": "^24.0.1",
38
35
  "@types/node": "^26",
39
36
  "@vitest/coverage-v8": "^5.0.5",
@@ -43,7 +40,10 @@
43
40
  "globals": "^17.11.0",
44
41
  "tsdown": "^0.23.3",
45
42
  "typescript": "^5.9.3",
46
- "vitest": "^5.0.5"
43
+ "vitest": "^5.0.5",
44
+ "@theholocron/tsdown-config": "^8.9.0-alpha.3",
45
+ "@theholocron/vitest-config": "^8.9.0-alpha.3",
46
+ "@theholocron/eslint-config": "^8.9.0-alpha.3"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=22"