knarr 0.0.1

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/add-ODK52RZI.mjs +3 -0
  4. package/dist/bell-YD6IWNXO.mjs +2 -0
  5. package/dist/check-YVEJEI2G.mjs +6 -0
  6. package/dist/chokidar-LVDD2IK4.mjs +4 -0
  7. package/dist/chunk-23HXXAGG.mjs +5 -0
  8. package/dist/chunk-2EZDTBUU.mjs +3 -0
  9. package/dist/chunk-2GDRDQA5.mjs +3 -0
  10. package/dist/chunk-2QPLXLJW.mjs +3 -0
  11. package/dist/chunk-2VCW5RWI.mjs +3 -0
  12. package/dist/chunk-3KNUBUPH.mjs +3 -0
  13. package/dist/chunk-7DZPDPP6.mjs +3 -0
  14. package/dist/chunk-7HVPEBK5.mjs +7 -0
  15. package/dist/chunk-7JG555TZ.mjs +3 -0
  16. package/dist/chunk-B3DZ5HVQ.mjs +3 -0
  17. package/dist/chunk-BS4VKVYH.mjs +3 -0
  18. package/dist/chunk-FU7FCNTW.mjs +3 -0
  19. package/dist/chunk-FUINO5RD.mjs +3 -0
  20. package/dist/chunk-HQ7NKBQW.mjs +4 -0
  21. package/dist/chunk-IM555H3S.mjs +4 -0
  22. package/dist/chunk-KXLQGVT2.mjs +13 -0
  23. package/dist/chunk-MBKCCWSD.mjs +3 -0
  24. package/dist/chunk-NBSJGM2X.mjs +3 -0
  25. package/dist/chunk-OLUZ7T7G.mjs +3 -0
  26. package/dist/chunk-OXI2KGCW.mjs +14 -0
  27. package/dist/chunk-SFLWVTJC.mjs +3 -0
  28. package/dist/chunk-SYADAYF4.mjs +3 -0
  29. package/dist/chunk-TEFMLGCB.mjs +3 -0
  30. package/dist/chunk-U5ZZAYNU.mjs +13 -0
  31. package/dist/chunk-V2ED74ZQ.mjs +3 -0
  32. package/dist/chunk-XQPVRRTN.mjs +3 -0
  33. package/dist/chunk-XQVMCMO7.mjs +7 -0
  34. package/dist/chunk-YZCBBQCH.mjs +19 -0
  35. package/dist/chunk-Z22BYXWQ.mjs +3 -0
  36. package/dist/clean-XMLDIZDZ.mjs +3 -0
  37. package/dist/cli.mjs +10 -0
  38. package/dist/dev-7L35BV6M.mjs +3 -0
  39. package/dist/doctor-4TTAYNGW.mjs +4 -0
  40. package/dist/fs-35635IS7.mjs +2 -0
  41. package/dist/history-XUZSDCNE.mjs +2 -0
  42. package/dist/index.d.ts +434 -0
  43. package/dist/index.mjs +3530 -0
  44. package/dist/init-OBJFQ6OB.mjs +7 -0
  45. package/dist/list-AQKUBZ2I.mjs +5 -0
  46. package/dist/migrate-7B7ACQHY.mjs +8 -0
  47. package/dist/preflight-TVJFHRI2.mjs +2 -0
  48. package/dist/publish-Q4JYQPQP.mjs +3 -0
  49. package/dist/push-NHCPN6MO.mjs +3 -0
  50. package/dist/remove-PDERBH66.mjs +2 -0
  51. package/dist/reset-3FXWAAPQ.mjs +3 -0
  52. package/dist/restore-YGPO42W4.mjs +11 -0
  53. package/dist/rollback-FKNGLGFC.mjs +3 -0
  54. package/dist/status-3VUPPR5D.mjs +4 -0
  55. package/dist/tailwind-source-ND5FE6PQ.mjs +5 -0
  56. package/dist/topo-sort-WEIVPJKN.mjs +2 -0
  57. package/dist/tracker-R4ZZIDJV.mjs +2 -0
  58. package/dist/update-QPBWYDSG.mjs +3 -0
  59. package/dist/use-NKLXGPIZ.mjs +3 -0
  60. package/dist/vite-config-URP2SYRQ.mjs +2 -0
  61. package/dist/vite-plugin.d.ts +5 -0
  62. package/dist/vite-plugin.mjs +215 -0
  63. package/dist/watch-orchestrator-F6S5WQQX.mjs +3 -0
  64. package/dist/watcher-PTPUN2HE.mjs +3 -0
  65. package/dist/webpack-plugin.d.ts +47 -0
  66. package/dist/webpack-plugin.mjs +143 -0
  67. package/dist/workspace-L5CGPK7U.mjs +2 -0
  68. package/dist/xxhash-wasm-DTW44IIQ.mjs +3 -0
  69. package/package.json +126 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,3530 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/utils/console.ts
12
+ import pc from "picocolors";
13
+ import { createInterface } from "readline/promises";
14
+ import { stdin, stdout } from "process";
15
+ async function prompt(message, options) {
16
+ const rl = createInterface({ input: stdin, output: stdout });
17
+ try {
18
+ if (options.type === "confirm") {
19
+ const hint = options.initial !== false ? "Y/n" : "y/N";
20
+ const answer2 = await rl.question(
21
+ `${pc.cyan("?")} ${message} ${pc.dim(`(${hint})`)} `
22
+ );
23
+ const val = answer2.trim().toLowerCase();
24
+ if (val === "") return options.initial !== false;
25
+ return val === "y" || val === "yes";
26
+ }
27
+ if (options.type === "text") {
28
+ const hint = options.default ? pc.dim(` (${options.default})`) : "";
29
+ const answer2 = await rl.question(`${pc.cyan("?")} ${message}${hint} `);
30
+ return answer2.trim() || options.default || "";
31
+ }
32
+ console.log(`${pc.cyan("?")} ${message}`);
33
+ for (let i = 0; i < options.options.length; i++) {
34
+ console.log(` ${pc.cyan(`${i + 1}.`)} ${options.options[i].label}`);
35
+ }
36
+ const answer = await rl.question(`${pc.dim("Enter number:")} `);
37
+ const idx = parseInt(answer, 10) - 1;
38
+ if (idx >= 0 && idx < options.options.length) {
39
+ return options.options[idx].value;
40
+ }
41
+ return options.options[0].value;
42
+ } finally {
43
+ rl.close();
44
+ }
45
+ }
46
+ var _level, consola;
47
+ var init_console = __esm({
48
+ "src/utils/console.ts"() {
49
+ "use strict";
50
+ _level = 3;
51
+ consola = {
52
+ get level() {
53
+ return _level;
54
+ },
55
+ set level(n) {
56
+ _level = n;
57
+ },
58
+ info(msg, ...args) {
59
+ if (_level >= 3) console.log(pc.cyan("i"), msg, ...args);
60
+ },
61
+ success(msg, ...args) {
62
+ if (_level >= 3) console.log(pc.green("OK"), msg, ...args);
63
+ },
64
+ warn(msg, ...args) {
65
+ if (_level >= 2) console.warn(pc.yellow("!"), msg, ...args);
66
+ },
67
+ error(msg, ...args) {
68
+ if (_level >= 1) console.error(pc.red("x"), msg, ...args);
69
+ },
70
+ start(msg, ...args) {
71
+ if (_level >= 3) console.log(pc.cyan("..."), msg, ...args);
72
+ },
73
+ debug(msg, ...args) {
74
+ if (_level >= 4) console.debug(pc.dim("D"), msg, ...args);
75
+ },
76
+ /** Plain log (no icon) that still respects level suppression */
77
+ log(msg, ...args) {
78
+ if (_level >= 3) console.log(msg, ...args);
79
+ },
80
+ prompt
81
+ };
82
+ }
83
+ });
84
+
85
+ // src/utils/concurrency.ts
86
+ function pLimit(concurrency) {
87
+ let active = 0;
88
+ const queue = [];
89
+ let head = 0;
90
+ const next = () => {
91
+ if (head < queue.length && active < concurrency) {
92
+ active++;
93
+ queue[head++]();
94
+ }
95
+ if (head > 0 && head === queue.length) {
96
+ queue.length = 0;
97
+ head = 0;
98
+ }
99
+ };
100
+ return (fn) => new Promise((resolve7, reject) => {
101
+ const run = () => {
102
+ fn().then(
103
+ (val) => {
104
+ active--;
105
+ resolve7(val);
106
+ next();
107
+ },
108
+ (err) => {
109
+ active--;
110
+ reject(err);
111
+ next();
112
+ }
113
+ );
114
+ };
115
+ if (active < concurrency) {
116
+ active++;
117
+ run();
118
+ } else {
119
+ queue.push(run);
120
+ }
121
+ });
122
+ }
123
+ var init_concurrency = __esm({
124
+ "src/utils/concurrency.ts"() {
125
+ "use strict";
126
+ }
127
+ });
128
+
129
+ // src/utils/paths.ts
130
+ import { homedir } from "os";
131
+ import { join } from "path";
132
+ function getKnarrHome() {
133
+ return process.env.KNARR_HOME || join(homedir(), ".knarr");
134
+ }
135
+ function getStorePath() {
136
+ return join(getKnarrHome(), "store");
137
+ }
138
+ function getConsumersPath() {
139
+ return join(getKnarrHome(), "consumers.json");
140
+ }
141
+ function encodePackageName(name) {
142
+ return name.replace(/\//g, "+");
143
+ }
144
+ function decodePackageName(encoded) {
145
+ if (encoded.startsWith("@")) {
146
+ const plusIdx = encoded.indexOf("+");
147
+ if (plusIdx !== -1) {
148
+ return encoded.slice(0, plusIdx) + "/" + encoded.slice(plusIdx + 1);
149
+ }
150
+ }
151
+ return encoded;
152
+ }
153
+ function getStoreEntryPath(name, version) {
154
+ return join(getStorePath(), `${encodePackageName(name)}@${version}`);
155
+ }
156
+ function getStorePackagePath(name, version) {
157
+ return join(getStoreEntryPath(name, version), "package");
158
+ }
159
+ function getStoreMetaPath(name, version) {
160
+ return join(getStoreEntryPath(name, version), ".knarr-meta.json");
161
+ }
162
+ function getStoreHistoryPath(name, version) {
163
+ return join(getStoreEntryPath(name, version), "history");
164
+ }
165
+ function getHistoryEntryPath(name, version, buildId) {
166
+ return join(getStoreHistoryPath(name, version), buildId);
167
+ }
168
+ function getConsumerKnarrDir(consumerPath) {
169
+ return join(consumerPath, ".knarr");
170
+ }
171
+ function getConsumerStatePath(consumerPath) {
172
+ return join(consumerPath, ".knarr", "state.json");
173
+ }
174
+ function getConsumerBackupPath(consumerPath, packageName) {
175
+ return join(consumerPath, ".knarr", "backups", encodePackageName(packageName));
176
+ }
177
+ function normalizePath(p) {
178
+ return p.replace(/\\/g, "/");
179
+ }
180
+ function getNodeModulesPackagePath(consumerPath, packageName) {
181
+ return join(consumerPath, "node_modules", packageName);
182
+ }
183
+ var init_paths = __esm({
184
+ "src/utils/paths.ts"() {
185
+ "use strict";
186
+ }
187
+ });
188
+
189
+ // src/utils/logger.ts
190
+ function isDryRun() {
191
+ return _dryRun;
192
+ }
193
+ function isJsonOutput() {
194
+ return _jsonOutput;
195
+ }
196
+ function verbose(msg, ...args) {
197
+ if (_verbose) consola.debug(msg, ...args);
198
+ }
199
+ var _verbose, _dryRun, _jsonOutput;
200
+ var init_logger = __esm({
201
+ "src/utils/logger.ts"() {
202
+ "use strict";
203
+ init_console();
204
+ _verbose = false;
205
+ _dryRun = false;
206
+ _jsonOutput = false;
207
+ }
208
+ });
209
+
210
+ // src/utils/hash.ts
211
+ import { createHash } from "crypto";
212
+ import { readFile, stat } from "fs/promises";
213
+ import { relative } from "path";
214
+ import { availableParallelism } from "os";
215
+ function getXXHash() {
216
+ if (!_xxhash) {
217
+ _xxhash = import("xxhash-wasm").then((mod) => mod.default()).catch((err) => {
218
+ _xxhash = null;
219
+ throw err;
220
+ });
221
+ }
222
+ return _xxhash;
223
+ }
224
+ async function computeContentHash(files, baseDir) {
225
+ const sorted = [...files].sort((a, b) => {
226
+ const relA = normalizePath(relative(baseDir, a));
227
+ const relB = normalizePath(relative(baseDir, b));
228
+ return relA.localeCompare(relB);
229
+ });
230
+ const currentFiles = new Set(sorted);
231
+ let cacheHits = 0;
232
+ const contents = await Promise.all(
233
+ sorted.map(
234
+ (file) => limit(async () => {
235
+ const rel = normalizePath(relative(baseDir, file));
236
+ const s = await stat(file);
237
+ const cached = _contentCache.get(file);
238
+ if (cached && cached.mtimeMs === s.mtimeMs && cached.size === s.size) {
239
+ cacheHits++;
240
+ return { rel, content: cached.content };
241
+ }
242
+ const content = await readFile(file);
243
+ _contentCache.set(file, { mtimeMs: s.mtimeMs, size: s.size, content });
244
+ return { rel, content };
245
+ })
246
+ )
247
+ );
248
+ for (const key of _contentCache.keys()) {
249
+ if (!currentFiles.has(key)) _contentCache.delete(key);
250
+ }
251
+ verbose(`[hash] Computing content hash for ${files.length} files (${cacheHits} cached)`);
252
+ const hash = createHash("sha256");
253
+ const lenBuf = Buffer.alloc(4);
254
+ for (const { rel, content } of contents) {
255
+ hash.update(rel);
256
+ hash.update("\0");
257
+ lenBuf.writeUInt32LE(content.length);
258
+ hash.update(lenBuf);
259
+ hash.update(content);
260
+ }
261
+ const result = "sha256v2:" + hash.digest("hex");
262
+ verbose(`[hash] Result: ${result.slice(0, 20)}...`);
263
+ return result;
264
+ }
265
+ async function hashFile(filePath, knownSize) {
266
+ const size = knownSize ?? (await stat(filePath)).size;
267
+ const xx = await getXXHash();
268
+ if (size > STREAM_THRESHOLD) {
269
+ return hashFileStream(filePath, xx);
270
+ }
271
+ const content = await readFile(filePath);
272
+ return xx.h64Raw(content).toString(16);
273
+ }
274
+ async function hashFileStream(filePath, xx) {
275
+ const { createReadStream } = await import("fs");
276
+ const hasher = xx.create64();
277
+ return new Promise((resolve7, reject) => {
278
+ const stream = createReadStream(filePath);
279
+ stream.on("data", (chunk) => hasher.update(chunk));
280
+ stream.on("end", () => resolve7(hasher.digest().toString(16)));
281
+ stream.on("error", reject);
282
+ });
283
+ }
284
+ var _xxhash, STREAM_THRESHOLD, limit, _contentCache;
285
+ var init_hash = __esm({
286
+ "src/utils/hash.ts"() {
287
+ "use strict";
288
+ init_concurrency();
289
+ init_logger();
290
+ init_paths();
291
+ _xxhash = null;
292
+ STREAM_THRESHOLD = 1024 * 1024;
293
+ limit = pLimit(Math.max(availableParallelism(), 8));
294
+ _contentCache = /* @__PURE__ */ new Map();
295
+ }
296
+ });
297
+
298
+ // src/utils/dry-run.ts
299
+ function recordMutation(mutation) {
300
+ mutations.push(mutation);
301
+ }
302
+ function printDryRunReport() {
303
+ if (mutations.length === 0) {
304
+ consola.info("[dry-run] No mutations would be performed");
305
+ return;
306
+ }
307
+ if (isJsonOutput()) {
308
+ console.log(JSON.stringify({ dryRun: true, mutations }, null, 2));
309
+ return;
310
+ }
311
+ const grouped = /* @__PURE__ */ new Map();
312
+ for (const m of mutations) {
313
+ let list = grouped.get(m.type);
314
+ if (!list) {
315
+ list = [];
316
+ grouped.set(m.type, list);
317
+ }
318
+ list.push(m);
319
+ }
320
+ consola.info(`
321
+ [dry-run] ${mutations.length} mutation(s) would be performed:
322
+ `);
323
+ const labels = {
324
+ copy: "Copy",
325
+ remove: "Remove",
326
+ move: "Move",
327
+ mkdir: "Create directory",
328
+ write: "Write file",
329
+ "bin-link": "Create bin link",
330
+ "bin-unlink": "Remove bin link",
331
+ "cache-invalidate": "Invalidate cache",
332
+ "lock-skip": "Skip lock",
333
+ "lifecycle-skip": "Skip lifecycle hook"
334
+ };
335
+ for (const [type, items] of grouped) {
336
+ consola.info(` ${labels[type]} (${items.length}):`);
337
+ for (const item of items.slice(0, 20)) {
338
+ const dest = item.dest ? ` \u2192 ${item.dest}` : "";
339
+ const detail = item.detail ? ` (${item.detail})` : "";
340
+ consola.info(` ${item.path}${dest}${detail}`);
341
+ }
342
+ if (items.length > 20) {
343
+ consola.info(` ... and ${items.length - 20} more`);
344
+ }
345
+ }
346
+ }
347
+ function resetMutations() {
348
+ mutations.length = 0;
349
+ }
350
+ var mutations;
351
+ var init_dry_run = __esm({
352
+ "src/utils/dry-run.ts"() {
353
+ "use strict";
354
+ init_logger();
355
+ init_console();
356
+ mutations = [];
357
+ }
358
+ });
359
+
360
+ // src/utils/fs.ts
361
+ var fs_exports = {};
362
+ __export(fs_exports, {
363
+ atomicWriteFile: () => atomicWriteFile,
364
+ collectFiles: () => collectFiles,
365
+ copyDir: () => copyDir,
366
+ copyWithCoW: () => copyWithCoW,
367
+ dirSize: () => dirSize,
368
+ ensureDir: () => ensureDir,
369
+ ensurePrivateDir: () => ensurePrivateDir,
370
+ exists: () => exists,
371
+ incrementalCopy: () => incrementalCopy,
372
+ isNodeError: () => isNodeError,
373
+ moveDir: () => moveDir,
374
+ removeDir: () => removeDir
375
+ });
376
+ import {
377
+ copyFile,
378
+ cp,
379
+ mkdir,
380
+ readdir,
381
+ rename,
382
+ rm,
383
+ stat as stat2,
384
+ utimes,
385
+ writeFile,
386
+ constants
387
+ } from "fs/promises";
388
+ import { join as join2, dirname, relative as relative2, parse as parsePath } from "path";
389
+ import { availableParallelism as availableParallelism2 } from "os";
390
+ function isNodeError(err) {
391
+ return err instanceof Error && "code" in err;
392
+ }
393
+ function volumeRoot(filePath) {
394
+ const { root } = parsePath(filePath);
395
+ return root || "/";
396
+ }
397
+ async function copyWithCoW(src, dest, options) {
398
+ if (isDryRun()) {
399
+ verbose(`[dry-run] would copy ${src} \u2192 ${dest}`);
400
+ recordMutation({ type: "copy", path: src, dest });
401
+ return;
402
+ }
403
+ if (options?.ensureParent !== false) {
404
+ await mkdir(dirname(dest), { recursive: true });
405
+ }
406
+ const root = volumeRoot(dest);
407
+ const supportsReflink = reflinkSupported.get(root);
408
+ if (supportsReflink === false) {
409
+ await copyFile(src, dest);
410
+ return;
411
+ }
412
+ if (supportsReflink === true) {
413
+ await copyFile(src, dest, constants.COPYFILE_FICLONE);
414
+ return;
415
+ }
416
+ try {
417
+ await copyFile(src, dest, constants.COPYFILE_FICLONE_FORCE);
418
+ reflinkSupported.set(root, true);
419
+ } catch {
420
+ reflinkSupported.set(root, false);
421
+ verbose(`[copy] reflink not supported on ${root}, using plain copy`);
422
+ await copyFile(src, dest);
423
+ }
424
+ }
425
+ async function collectFiles(dir) {
426
+ const entries = await readdir(dir, { recursive: true, withFileTypes: true });
427
+ return entries.filter((e) => e.isFile()).map((e) => join2(e.parentPath, e.name));
428
+ }
429
+ async function incrementalCopy(srcDir, destDir, options = {}) {
430
+ const srcFilesPromise = collectFiles(srcDir);
431
+ const destFilesPromise = collectFiles(destDir).catch((err) => {
432
+ if (isNodeError(err) && err.code === "ENOENT") return [];
433
+ throw err;
434
+ });
435
+ const [srcFiles, destFiles] = await Promise.all([srcFilesPromise, destFilesPromise]);
436
+ let copied = 0;
437
+ let removed = 0;
438
+ let skipped = 0;
439
+ const results = await Promise.all(
440
+ srcFiles.map(
441
+ (srcFile) => ioLimit(async () => {
442
+ const rel = relative2(srcDir, srcFile);
443
+ const destFile = join2(destDir, rel);
444
+ let needsCopy = true;
445
+ let srcTimes = null;
446
+ if (options.force) {
447
+ verbose(`[copy] ${rel} (forced)`);
448
+ } else {
449
+ try {
450
+ const [srcStat, destStat] = await Promise.all([
451
+ stat2(srcFile),
452
+ stat2(destFile)
453
+ ]);
454
+ srcTimes = { atime: srcStat.atime, mtime: srcStat.mtime };
455
+ if (srcStat.size !== destStat.size) {
456
+ verbose(`[copy] ${rel} (size differs: ${srcStat.size} vs ${destStat.size})`);
457
+ } else if (srcStat.mtimeMs === destStat.mtimeMs) {
458
+ needsCopy = false;
459
+ verbose(`[skip] ${rel} (size+mtime match)`);
460
+ } else {
461
+ const [srcHash, destHash] = await Promise.all([
462
+ hashFile(srcFile, srcStat.size),
463
+ hashFile(destFile, destStat.size)
464
+ ]);
465
+ if (srcHash === destHash) {
466
+ needsCopy = false;
467
+ verbose(`[skip] ${rel} (unchanged)`);
468
+ } else {
469
+ verbose(`[copy] ${rel} (hash differs)`);
470
+ }
471
+ }
472
+ } catch (err) {
473
+ if (isNodeError(err) && err.code === "ENOENT") {
474
+ verbose(`[copy] ${rel} (new file)`);
475
+ } else {
476
+ throw err;
477
+ }
478
+ }
479
+ }
480
+ if (needsCopy) {
481
+ await copyWithCoW(srcFile, destFile);
482
+ if (!srcTimes) {
483
+ const s = await stat2(srcFile);
484
+ srcTimes = { atime: s.atime, mtime: s.mtime };
485
+ }
486
+ await utimes(destFile, srcTimes.atime, srcTimes.mtime);
487
+ return "copied";
488
+ }
489
+ return "skipped";
490
+ })
491
+ )
492
+ );
493
+ for (const r of results) {
494
+ if (r === "copied") copied++;
495
+ else skipped++;
496
+ }
497
+ const srcRelPaths = new Set(srcFiles.map((f) => relative2(srcDir, f)));
498
+ const filesToRemove = destFiles.filter(
499
+ (f) => !srcRelPaths.has(relative2(destDir, f))
500
+ );
501
+ await Promise.all(
502
+ filesToRemove.map(
503
+ (destFile) => ioLimit(async () => {
504
+ verbose(`[remove] ${relative2(destDir, destFile)} (no longer in source)`);
505
+ if (isDryRun()) {
506
+ recordMutation({ type: "remove", path: destFile });
507
+ } else {
508
+ await rm(destFile);
509
+ }
510
+ })
511
+ )
512
+ );
513
+ removed = filesToRemove.length;
514
+ return { copied, removed, skipped };
515
+ }
516
+ async function moveDir(src, dest) {
517
+ if (isDryRun()) {
518
+ verbose(`[dry-run] would move ${src} \u2192 ${dest}`);
519
+ recordMutation({ type: "move", path: src, dest });
520
+ return;
521
+ }
522
+ try {
523
+ await rename(src, dest);
524
+ } catch (err) {
525
+ if (isNodeError(err) && err.code === "EXDEV") {
526
+ await cp(src, dest, { recursive: true });
527
+ await rm(src, { recursive: true, force: true });
528
+ } else {
529
+ throw err;
530
+ }
531
+ }
532
+ }
533
+ async function removeDir(dir) {
534
+ if (isDryRun()) {
535
+ verbose(`[dry-run] would remove ${dir}`);
536
+ recordMutation({ type: "remove", path: dir });
537
+ return;
538
+ }
539
+ await rm(dir, { recursive: true, force: true });
540
+ }
541
+ async function ensureDir(dir) {
542
+ if (isDryRun()) {
543
+ verbose(`[dry-run] would ensure dir ${dir}`);
544
+ recordMutation({ type: "mkdir", path: dir });
545
+ return;
546
+ }
547
+ await mkdir(dir, { recursive: true });
548
+ }
549
+ async function ensurePrivateDir(dir) {
550
+ if (isDryRun()) {
551
+ verbose(`[dry-run] would ensure private dir ${dir}`);
552
+ recordMutation({ type: "mkdir", path: dir });
553
+ return;
554
+ }
555
+ await mkdir(dir, { recursive: true, mode: 448 });
556
+ }
557
+ async function exists(path) {
558
+ try {
559
+ await stat2(path);
560
+ return true;
561
+ } catch {
562
+ return false;
563
+ }
564
+ }
565
+ async function atomicWriteFile(filePath, data) {
566
+ if (isDryRun()) {
567
+ verbose(`[dry-run] would write ${filePath}`);
568
+ recordMutation({ type: "write", path: filePath });
569
+ return;
570
+ }
571
+ const tmpPath = filePath + `.tmp-${process.pid}-${Date.now()}`;
572
+ await writeFile(tmpPath, data);
573
+ await rename(tmpPath, filePath);
574
+ }
575
+ async function dirSize(dir) {
576
+ try {
577
+ const files = await collectFiles(dir);
578
+ const stats = await Promise.all(
579
+ files.map((f) => stat2(f).then((s) => s.size).catch(() => 0))
580
+ );
581
+ return stats.reduce((sum, s) => sum + s, 0);
582
+ } catch {
583
+ return 0;
584
+ }
585
+ }
586
+ async function copyDir(src, dest) {
587
+ await cp(src, dest, { recursive: true });
588
+ }
589
+ var ioLimit, reflinkSupported;
590
+ var init_fs = __esm({
591
+ "src/utils/fs.ts"() {
592
+ "use strict";
593
+ init_concurrency();
594
+ init_hash();
595
+ init_logger();
596
+ init_logger();
597
+ init_dry_run();
598
+ ioLimit = pLimit(Math.max(availableParallelism2(), 8));
599
+ reflinkSupported = /* @__PURE__ */ new Map();
600
+ }
601
+ });
602
+
603
+ // src/utils/pack-list.ts
604
+ import { readFile as readFile2, readdir as readdir2, stat as stat3 } from "fs/promises";
605
+ import { join as join3, relative as relative3, resolve, sep } from "path";
606
+ import picomatch from "picomatch";
607
+ async function resolvePackFiles(packageDir, pkg) {
608
+ const files = [];
609
+ const absDir = resolve(packageDir);
610
+ files.push(join3(absDir, "package.json"));
611
+ const allFiles = await collectAllFiles(absDir, absDir);
612
+ const allRelPaths = allFiles.map((f) => normalizePath(relative3(absDir, f)));
613
+ if (pkg.files && pkg.files.length > 0) {
614
+ for (const pattern of pkg.files) {
615
+ const target = join3(absDir, pattern);
616
+ const resolved = resolve(target);
617
+ if (!resolved.startsWith(absDir + sep) && resolved !== absDir) {
618
+ consola.warn(`files pattern "${pattern}" escapes package directory, skipping`);
619
+ continue;
620
+ }
621
+ let matched = false;
622
+ try {
623
+ const s = await stat3(target);
624
+ if (s.isDirectory()) {
625
+ const prefix = normalizePath(relative3(absDir, target)) + "/";
626
+ for (let i = 0; i < allRelPaths.length; i++) {
627
+ if (allRelPaths[i].startsWith(prefix)) {
628
+ files.push(allFiles[i]);
629
+ }
630
+ }
631
+ matched = true;
632
+ } else {
633
+ files.push(target);
634
+ matched = true;
635
+ }
636
+ } catch (err) {
637
+ if (isNodeError(err) && err.code !== "ENOENT") {
638
+ throw err;
639
+ }
640
+ }
641
+ if (!matched) {
642
+ const isMatch = picomatch(pattern, { dot: true });
643
+ let globMatched = 0;
644
+ for (let i = 0; i < allRelPaths.length; i++) {
645
+ if (isMatch(allRelPaths[i])) {
646
+ const absFile = resolve(allFiles[i]);
647
+ if (!absFile.startsWith(absDir + sep) && absFile !== absDir) continue;
648
+ files.push(allFiles[i]);
649
+ globMatched++;
650
+ }
651
+ }
652
+ if (globMatched === 0) {
653
+ consola.warn(`files pattern "${pattern}" matched no files`);
654
+ }
655
+ }
656
+ }
657
+ } else {
658
+ const ignoreMatchers = await loadNpmIgnore(absDir);
659
+ for (let i = 0; i < allRelPaths.length; i++) {
660
+ if (!shouldIgnore(allRelPaths[i], ignoreMatchers)) {
661
+ files.push(allFiles[i]);
662
+ }
663
+ }
664
+ }
665
+ const fileSet = new Set(files);
666
+ const allFileSet = new Set(allFiles);
667
+ for (const name of ["README.md", "README", "LICENSE", "LICENCE", "CHANGELOG.md"]) {
668
+ const p = join3(absDir, name);
669
+ if (fileSet.has(p)) continue;
670
+ if (!allFileSet.has(p)) continue;
671
+ files.push(p);
672
+ fileSet.add(p);
673
+ }
674
+ return [...fileSet];
675
+ }
676
+ function shouldIgnore(relPath, matchers) {
677
+ for (const isMatch of matchers.negations) {
678
+ if (isMatch(relPath)) return false;
679
+ }
680
+ const parts = relPath.split(/[\\/]/);
681
+ for (const part of parts) {
682
+ if (DEFAULT_IGNORES.has(part)) return true;
683
+ if (matchers.literals.has(part)) return true;
684
+ }
685
+ if (matchers.literals.has(relPath)) return true;
686
+ for (const isMatch of matchers.patterns) {
687
+ if (isMatch(relPath)) return true;
688
+ }
689
+ return false;
690
+ }
691
+ async function loadNpmIgnore(dir) {
692
+ const matchers = { literals: /* @__PURE__ */ new Set(), patterns: [], negations: [] };
693
+ try {
694
+ const content = await readFile2(join3(dir, ".npmignore"), "utf-8");
695
+ for (const line of content.split("\n")) {
696
+ const trimmed = line.trim();
697
+ if (!trimmed || trimmed.startsWith("#")) continue;
698
+ if (trimmed.startsWith("!")) {
699
+ const pat = trimmed.slice(1);
700
+ if (hasGlobChars(pat)) {
701
+ matchers.negations.push(picomatch(pat, { dot: true }));
702
+ } else {
703
+ matchers.negations.push(picomatch(pat, { dot: true }));
704
+ }
705
+ } else if (hasGlobChars(trimmed)) {
706
+ matchers.patterns.push(picomatch(trimmed, { dot: true }));
707
+ } else {
708
+ matchers.literals.add(trimmed.replace(/\/$/, ""));
709
+ }
710
+ }
711
+ } catch (err) {
712
+ if (isNodeError(err) && err.code !== "ENOENT") {
713
+ throw err;
714
+ }
715
+ }
716
+ return matchers;
717
+ }
718
+ function hasGlobChars(pattern) {
719
+ return /[*?[\]{}()]/.test(pattern);
720
+ }
721
+ async function collectAllFiles(dir, rootDir) {
722
+ const results = [];
723
+ try {
724
+ const entries = await readdir2(dir, { withFileTypes: true });
725
+ for (const entry of entries) {
726
+ const full = join3(dir, entry.name);
727
+ if (entry.isDirectory()) {
728
+ if (entry.name === ".git") continue;
729
+ if (dir === rootDir && entry.name === "node_modules") continue;
730
+ if (entry.isSymbolicLink()) continue;
731
+ results.push(...await collectAllFiles(full, rootDir));
732
+ } else if (!entry.isSymbolicLink()) {
733
+ results.push(full);
734
+ }
735
+ }
736
+ } catch (err) {
737
+ if (isNodeError(err) && err.code === "ENOENT") {
738
+ return [];
739
+ }
740
+ throw err;
741
+ }
742
+ return results;
743
+ }
744
+ var DEFAULT_IGNORES;
745
+ var init_pack_list = __esm({
746
+ "src/utils/pack-list.ts"() {
747
+ "use strict";
748
+ init_console();
749
+ init_fs();
750
+ init_paths();
751
+ DEFAULT_IGNORES = /* @__PURE__ */ new Set([
752
+ "node_modules",
753
+ ".git",
754
+ ".svn",
755
+ ".hg",
756
+ ".DS_Store",
757
+ ".npmrc",
758
+ ".knarr",
759
+ "test",
760
+ "tests",
761
+ "__tests__",
762
+ ".github",
763
+ ".vscode",
764
+ ".idea",
765
+ "coverage",
766
+ ".nyc_output",
767
+ "tsconfig.json",
768
+ "tsconfig.build.json",
769
+ ".eslintrc",
770
+ ".eslintrc.js",
771
+ ".eslintrc.json",
772
+ ".prettierrc",
773
+ ".prettierrc.js",
774
+ "jest.config.js",
775
+ "jest.config.ts",
776
+ "vitest.config.ts",
777
+ "vitest.config.js"
778
+ ]);
779
+ }
780
+ });
781
+
782
+ // src/utils/validators.ts
783
+ import { readFile as readFile3 } from "fs/promises";
784
+ import { join as join4 } from "path";
785
+ function isKnarrMeta(value) {
786
+ if (typeof value !== "object" || value === null) return false;
787
+ const v = value;
788
+ return typeof v.contentHash === "string" && typeof v.publishedAt === "string" && typeof v.sourcePath === "string" && (v.buildId === void 0 || typeof v.buildId === "string") && (v.schemaVersion === void 0 || typeof v.schemaVersion === "number");
789
+ }
790
+ function isLinkEntry(value) {
791
+ if (typeof value !== "object" || value === null) return false;
792
+ const v = value;
793
+ return typeof v.version === "string" && typeof v.contentHash === "string" && typeof v.linkedAt === "string" && typeof v.sourcePath === "string" && typeof v.backupExists === "boolean" && typeof v.packageManager === "string" && ["npm", "pnpm", "yarn", "bun"].includes(v.packageManager) && (v.buildId === void 0 || typeof v.buildId === "string");
794
+ }
795
+ function isConsumerState(value) {
796
+ if (typeof value !== "object" || value === null) return false;
797
+ const v = value;
798
+ if (v.version !== "1") return false;
799
+ if (typeof v.links !== "object" || v.links === null) return false;
800
+ const links = v.links;
801
+ for (const entry of Object.values(links)) {
802
+ if (!isLinkEntry(entry)) return false;
803
+ }
804
+ return true;
805
+ }
806
+ function isConsumersRegistry(value) {
807
+ if (typeof value !== "object" || value === null) return false;
808
+ const v = value;
809
+ for (const val of Object.values(v)) {
810
+ if (!Array.isArray(val)) return false;
811
+ for (const item of val) {
812
+ if (typeof item !== "string") return false;
813
+ }
814
+ }
815
+ return true;
816
+ }
817
+ var init_validators = __esm({
818
+ "src/utils/validators.ts"() {
819
+ "use strict";
820
+ }
821
+ });
822
+
823
+ // src/core/store.ts
824
+ import { readFile as readFile4, readdir as readdir3 } from "fs/promises";
825
+ import "path";
826
+ async function readMeta(name, version) {
827
+ const metaPath = getStoreMetaPath(name, version);
828
+ try {
829
+ const content = await readFile4(metaPath, "utf-8");
830
+ const parsed = JSON.parse(content);
831
+ if (!isKnarrMeta(parsed)) {
832
+ consola.warn(`Invalid metadata for ${name}@${version}, ignoring`);
833
+ return null;
834
+ }
835
+ return parsed;
836
+ } catch (err) {
837
+ if (isNodeError(err) && err.code !== "ENOENT") {
838
+ consola.warn(`Failed to read metadata for ${name}@${version}: ${err instanceof Error ? err.message : String(err)}`);
839
+ }
840
+ return null;
841
+ }
842
+ }
843
+ async function getStoreEntry(name, version) {
844
+ const packageDir = getStorePackagePath(name, version);
845
+ const meta = await readMeta(name, version);
846
+ if (!meta) return null;
847
+ if (!await exists(packageDir)) return null;
848
+ return { name, version, packageDir, meta };
849
+ }
850
+ async function findStoreEntry(name) {
851
+ const storePath = getStorePath();
852
+ if (!await exists(storePath)) return null;
853
+ const encodedPrefix = encodePackageName(name) + "@";
854
+ const dirs = await readdir3(storePath, { withFileTypes: true });
855
+ const candidates = dirs.filter(
856
+ (d) => d.isDirectory() && d.name.startsWith(encodedPrefix)
857
+ );
858
+ const results = await Promise.all(
859
+ candidates.map(async (dir) => {
860
+ const version = dir.name.slice(encodedPrefix.length);
861
+ const meta = await readMeta(name, version);
862
+ if (!meta) return null;
863
+ return {
864
+ name,
865
+ version,
866
+ packageDir: getStorePackagePath(name, version),
867
+ meta
868
+ };
869
+ })
870
+ );
871
+ const matching = results.filter((r) => r !== null);
872
+ if (matching.length === 0) return null;
873
+ return matching.reduce(
874
+ (latest, entry) => new Date(entry.meta.publishedAt).getTime() > new Date(latest.meta.publishedAt).getTime() ? entry : latest
875
+ );
876
+ }
877
+ async function listStoreEntries() {
878
+ const storePath = getStorePath();
879
+ if (!await exists(storePath)) return [];
880
+ const dirs = await readdir3(storePath, { withFileTypes: true });
881
+ const candidates = dirs.filter((d) => {
882
+ if (!d.isDirectory()) return false;
883
+ const atIdx = d.name.lastIndexOf("@");
884
+ return atIdx > 0;
885
+ });
886
+ const results = await Promise.all(
887
+ candidates.map(async (dir) => {
888
+ const atIdx = dir.name.lastIndexOf("@");
889
+ const encodedName = dir.name.slice(0, atIdx);
890
+ const version = dir.name.slice(atIdx + 1);
891
+ const name = decodePackageName(encodedName);
892
+ const meta = await readMeta(name, version);
893
+ if (!meta) return null;
894
+ return {
895
+ name,
896
+ version,
897
+ packageDir: getStorePackagePath(name, version),
898
+ meta
899
+ };
900
+ })
901
+ );
902
+ return results.filter((r) => r !== null);
903
+ }
904
+ var init_store = __esm({
905
+ "src/core/store.ts"() {
906
+ "use strict";
907
+ init_console();
908
+ init_paths();
909
+ init_fs();
910
+ init_validators();
911
+ }
912
+ });
913
+
914
+ // src/utils/lockfile.ts
915
+ import { mkdir as mkdir2, stat as stat4, rm as rm2 } from "fs/promises";
916
+ import { dirname as dirname2 } from "path";
917
+ import { setTimeout as sleep } from "timers/promises";
918
+ async function withFileLock(filePath, fn, lockOptions) {
919
+ if (isDryRun()) {
920
+ recordMutation({ type: "lock-skip", path: filePath });
921
+ return fn();
922
+ }
923
+ await mkdir2(dirname2(filePath), { recursive: true });
924
+ const lockDir = filePath + ".lk";
925
+ const stale = lockOptions?.stale ?? DEFAULTS.stale;
926
+ let acquired = false;
927
+ for (let attempt = 0; attempt <= DEFAULTS.retries; attempt++) {
928
+ try {
929
+ await mkdir2(lockDir);
930
+ acquired = true;
931
+ break;
932
+ } catch (err) {
933
+ if (isNodeError(err) && err.code === "EEXIST") {
934
+ try {
935
+ const s = await stat4(lockDir);
936
+ if (Date.now() - s.mtimeMs > stale) {
937
+ await rm2(lockDir, { recursive: true, force: true });
938
+ continue;
939
+ }
940
+ } catch {
941
+ continue;
942
+ }
943
+ if (attempt < DEFAULTS.retries) {
944
+ const delay = Math.min(
945
+ DEFAULTS.minTimeout * DEFAULTS.factor ** attempt,
946
+ DEFAULTS.maxTimeout
947
+ );
948
+ await sleep(delay);
949
+ }
950
+ } else {
951
+ throw err;
952
+ }
953
+ }
954
+ }
955
+ if (!acquired) {
956
+ throw new Error(
957
+ `Failed to acquire lock after ${DEFAULTS.retries} attempts. Another Knarr process may be running. If this persists, delete ${lockDir} and retry.`
958
+ );
959
+ }
960
+ try {
961
+ return await fn();
962
+ } finally {
963
+ await rm2(lockDir, { recursive: true, force: true });
964
+ }
965
+ }
966
+ var DEFAULTS;
967
+ var init_lockfile = __esm({
968
+ "src/utils/lockfile.ts"() {
969
+ "use strict";
970
+ init_fs();
971
+ init_logger();
972
+ init_dry_run();
973
+ DEFAULTS = {
974
+ retries: 5,
975
+ minTimeout: 100,
976
+ maxTimeout: 1e3,
977
+ factor: 2,
978
+ stale: 3e4
979
+ };
980
+ }
981
+ });
982
+
983
+ // src/core/history.ts
984
+ var history_exports = {};
985
+ __export(history_exports, {
986
+ captureHistory: () => captureHistory,
987
+ clearHistory: () => clearHistory,
988
+ getHistoryEntry: () => getHistoryEntry,
989
+ listHistory: () => listHistory,
990
+ pruneHistory: () => pruneHistory,
991
+ resolveHistoryLimit: () => resolveHistoryLimit,
992
+ restoreHistoryEntry: () => restoreHistoryEntry
993
+ });
994
+ import { readdir as readdir4, readFile as readFile5, rename as rename2, writeFile as writeFile2 } from "fs/promises";
995
+ import { join as join6 } from "path";
996
+ async function captureHistory(name, version, oldEntryDir, historyLimit) {
997
+ const metaPath = join6(oldEntryDir, ".knarr-meta.json");
998
+ let meta;
999
+ try {
1000
+ meta = JSON.parse(await readFile5(metaPath, "utf-8"));
1001
+ } catch {
1002
+ verbose(`[history] Could not read meta from ${metaPath}, skipping history capture`);
1003
+ return;
1004
+ }
1005
+ const buildId = meta.buildId;
1006
+ if (!buildId) {
1007
+ verbose(`[history] No buildId in meta, skipping history capture`);
1008
+ return;
1009
+ }
1010
+ const historyDir = getStoreHistoryPath(name, version);
1011
+ const entryDir = getHistoryEntryPath(name, version, buildId);
1012
+ if (await exists(entryDir)) {
1013
+ verbose(`[history] Build ${buildId} already in history, skipping`);
1014
+ return;
1015
+ }
1016
+ await ensureDir(historyDir);
1017
+ const tmpHistoryEntry = entryDir + `.tmp-${process.pid}`;
1018
+ try {
1019
+ await ensureDir(tmpHistoryEntry);
1020
+ const oldPkgDir = join6(oldEntryDir, "package");
1021
+ if (await exists(oldPkgDir)) {
1022
+ await moveDir(oldPkgDir, join6(tmpHistoryEntry, "package"));
1023
+ }
1024
+ await writeFile2(
1025
+ join6(tmpHistoryEntry, ".knarr-meta.json"),
1026
+ JSON.stringify(meta, null, 2)
1027
+ );
1028
+ await rename2(tmpHistoryEntry, entryDir);
1029
+ verbose(`[history] Captured build ${buildId} to history`);
1030
+ } catch (err) {
1031
+ verbose(`[history] Failed to capture history: ${err instanceof Error ? err.message : String(err)}`);
1032
+ await removeDir(tmpHistoryEntry);
1033
+ }
1034
+ const limit2 = historyLimit ?? DEFAULT_HISTORY_LIMIT;
1035
+ await pruneHistory(name, version, limit2);
1036
+ }
1037
+ async function listHistory(name, version) {
1038
+ const historyDir = getStoreHistoryPath(name, version);
1039
+ if (!await exists(historyDir)) return [];
1040
+ let entries;
1041
+ try {
1042
+ entries = await readdir4(historyDir);
1043
+ } catch {
1044
+ return [];
1045
+ }
1046
+ const result = [];
1047
+ for (const buildId of entries) {
1048
+ const entryDir = join6(historyDir, buildId);
1049
+ const metaPath = join6(entryDir, ".knarr-meta.json");
1050
+ try {
1051
+ const meta = JSON.parse(await readFile5(metaPath, "utf-8"));
1052
+ result.push({
1053
+ buildId: meta.buildId ?? buildId,
1054
+ contentHash: meta.contentHash,
1055
+ publishedAt: meta.publishedAt,
1056
+ sourcePath: meta.sourcePath,
1057
+ packageDir: join6(entryDir, "package")
1058
+ });
1059
+ } catch {
1060
+ }
1061
+ }
1062
+ result.sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime());
1063
+ return result;
1064
+ }
1065
+ async function getHistoryEntry(name, version, buildId) {
1066
+ const entryDir = getHistoryEntryPath(name, version, buildId);
1067
+ const metaPath = join6(entryDir, ".knarr-meta.json");
1068
+ try {
1069
+ const meta = JSON.parse(await readFile5(metaPath, "utf-8"));
1070
+ return {
1071
+ buildId: meta.buildId ?? buildId,
1072
+ contentHash: meta.contentHash,
1073
+ publishedAt: meta.publishedAt,
1074
+ sourcePath: meta.sourcePath,
1075
+ packageDir: join6(entryDir, "package")
1076
+ };
1077
+ } catch {
1078
+ return null;
1079
+ }
1080
+ }
1081
+ async function restoreHistoryEntry(name, version, buildId, historyLimit) {
1082
+ const entry = await getHistoryEntry(name, version, buildId);
1083
+ if (!entry) return null;
1084
+ const storeEntryDir = getStoreEntryPath(name, version);
1085
+ const historyEntryDir = getHistoryEntryPath(name, version, buildId);
1086
+ const historyPkg = join6(historyEntryDir, "package");
1087
+ const historyMeta = join6(historyEntryDir, ".knarr-meta.json");
1088
+ const metaContent = await readFile5(historyMeta, "utf-8");
1089
+ if (await exists(storeEntryDir)) {
1090
+ await captureHistory(name, version, storeEntryDir, historyLimit);
1091
+ }
1092
+ const storePkg = join6(storeEntryDir, "package");
1093
+ const storeMeta = join6(storeEntryDir, ".knarr-meta.json");
1094
+ if (await exists(storePkg)) await removeDir(storePkg);
1095
+ if (await exists(historyPkg)) {
1096
+ await moveDir(historyPkg, storePkg);
1097
+ }
1098
+ await writeFile2(storeMeta, metaContent);
1099
+ await removeDir(historyEntryDir);
1100
+ verbose(`[history] Restored build ${buildId} as current`);
1101
+ return entry;
1102
+ }
1103
+ async function pruneHistory(name, version, limit2) {
1104
+ if (limit2 < 0) return 0;
1105
+ const entries = await listHistory(name, version);
1106
+ if (entries.length <= limit2) return 0;
1107
+ const toRemove = entries.slice(limit2);
1108
+ let removed = 0;
1109
+ for (const entry of toRemove) {
1110
+ const entryDir = getHistoryEntryPath(name, version, entry.buildId);
1111
+ try {
1112
+ await removeDir(entryDir);
1113
+ removed++;
1114
+ verbose(`[history] Pruned old build ${entry.buildId}`);
1115
+ } catch {
1116
+ }
1117
+ }
1118
+ return removed;
1119
+ }
1120
+ async function clearHistory(name, version) {
1121
+ const historyDir = getStoreHistoryPath(name, version);
1122
+ if (await exists(historyDir)) {
1123
+ await removeDir(historyDir);
1124
+ }
1125
+ }
1126
+ function resolveHistoryLimit(configValue) {
1127
+ return configValue ?? DEFAULT_HISTORY_LIMIT;
1128
+ }
1129
+ var DEFAULT_HISTORY_LIMIT;
1130
+ var init_history = __esm({
1131
+ "src/core/history.ts"() {
1132
+ "use strict";
1133
+ init_fs();
1134
+ init_paths();
1135
+ init_logger();
1136
+ DEFAULT_HISTORY_LIMIT = 3;
1137
+ }
1138
+ });
1139
+
1140
+ // src/utils/workspace.ts
1141
+ var workspace_exports = {};
1142
+ __export(workspace_exports, {
1143
+ buildReverseAdjacency: () => buildReverseAdjacency,
1144
+ buildWorkspaceGraph: () => buildWorkspaceGraph,
1145
+ findWorkspacePackages: () => findWorkspacePackages,
1146
+ findWorkspaceRoot: () => findWorkspaceRoot,
1147
+ parseCatalogs: () => parseCatalogs
1148
+ });
1149
+ import { readFile as readFile6, readdir as readdir5 } from "fs/promises";
1150
+ import { join as join7, dirname as dirname3, resolve as resolve2, relative as relative4 } from "path";
1151
+ import picomatch2 from "picomatch";
1152
+ async function findWorkspaceRoot(startDir) {
1153
+ let dir = startDir;
1154
+ for (; ; ) {
1155
+ if (await exists(join7(dir, "pnpm-workspace.yaml"))) {
1156
+ return dir;
1157
+ }
1158
+ const parent = dirname3(dir);
1159
+ if (parent === dir) return null;
1160
+ dir = parent;
1161
+ }
1162
+ }
1163
+ async function parseCatalogs(workspaceRoot) {
1164
+ const result = { default: {}, named: {} };
1165
+ const filePath = join7(workspaceRoot, "pnpm-workspace.yaml");
1166
+ let content;
1167
+ try {
1168
+ content = await readFile6(filePath, "utf-8");
1169
+ } catch {
1170
+ return result;
1171
+ }
1172
+ const lines = content.split(/\r?\n/);
1173
+ let state = "top";
1174
+ let currentNamedCatalog = "";
1175
+ for (const line of lines) {
1176
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
1177
+ const indent = line.length - line.trimStart().length;
1178
+ if (indent === 0) {
1179
+ if (line.startsWith("catalog:")) {
1180
+ state = "default-catalog";
1181
+ continue;
1182
+ }
1183
+ if (line.startsWith("catalogs:")) {
1184
+ state = "named-catalogs";
1185
+ continue;
1186
+ }
1187
+ state = "top";
1188
+ continue;
1189
+ }
1190
+ if (state === "default-catalog" && indent >= 2) {
1191
+ const kv = parseKeyValue(line);
1192
+ if (kv) result.default[kv[0]] = kv[1];
1193
+ continue;
1194
+ }
1195
+ if (state === "named-catalogs" && indent >= 2 && indent < 4) {
1196
+ const trimmed = line.trim();
1197
+ if (trimmed.endsWith(":")) {
1198
+ currentNamedCatalog = trimmed.slice(0, -1);
1199
+ result.named[currentNamedCatalog] = {};
1200
+ state = "named-catalog-entries";
1201
+ }
1202
+ continue;
1203
+ }
1204
+ if (state === "named-catalog-entries" && indent >= 4) {
1205
+ const kv = parseKeyValue(line);
1206
+ if (kv && currentNamedCatalog) {
1207
+ result.named[currentNamedCatalog][kv[0]] = kv[1];
1208
+ }
1209
+ continue;
1210
+ }
1211
+ if (state === "named-catalog-entries" && indent >= 2 && indent < 4) {
1212
+ const trimmed = line.trim();
1213
+ if (trimmed.endsWith(":")) {
1214
+ currentNamedCatalog = trimmed.slice(0, -1);
1215
+ result.named[currentNamedCatalog] = {};
1216
+ } else {
1217
+ state = "named-catalogs";
1218
+ }
1219
+ continue;
1220
+ }
1221
+ }
1222
+ return result;
1223
+ }
1224
+ async function findWorkspacePackages(startDir) {
1225
+ const pnpmRoot = await findWorkspaceRoot(startDir);
1226
+ if (pnpmRoot) {
1227
+ const allPatterns = await parsePnpmWorkspacePackages(pnpmRoot);
1228
+ const positive = allPatterns.filter((p) => !p.startsWith("!"));
1229
+ const negations = allPatterns.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
1230
+ if (positive.length > 0) {
1231
+ return resolveWorkspaceGlobs(pnpmRoot, positive, negations);
1232
+ }
1233
+ }
1234
+ const rootDir = pnpmRoot ?? await findPackageJsonWorkspaceRoot(startDir);
1235
+ if (!rootDir) return [];
1236
+ try {
1237
+ const rootPkg = JSON.parse(await readFile6(join7(rootDir, "package.json"), "utf-8"));
1238
+ const workspaces = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages ?? [];
1239
+ if (workspaces.length === 0) return [];
1240
+ const positive = workspaces.filter((p) => !p.startsWith("!"));
1241
+ const negations = workspaces.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
1242
+ return resolveWorkspaceGlobs(rootDir, positive, negations);
1243
+ } catch {
1244
+ return [];
1245
+ }
1246
+ }
1247
+ async function parsePnpmWorkspacePackages(workspaceRoot) {
1248
+ const filePath = join7(workspaceRoot, "pnpm-workspace.yaml");
1249
+ let content;
1250
+ try {
1251
+ content = await readFile6(filePath, "utf-8");
1252
+ } catch {
1253
+ return [];
1254
+ }
1255
+ const patterns = [];
1256
+ let inPackages = false;
1257
+ for (const line of content.split(/\r?\n/)) {
1258
+ const trimmed = line.trim();
1259
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
1260
+ const indent = line.length - line.trimStart().length;
1261
+ if (indent === 0) {
1262
+ inPackages = trimmed === "packages:";
1263
+ continue;
1264
+ }
1265
+ if (inPackages && indent >= 2) {
1266
+ const match = trimmed.match(/^-\s+["']?([^"']+)["']?$/);
1267
+ if (match) {
1268
+ patterns.push(match[1]);
1269
+ }
1270
+ }
1271
+ }
1272
+ return patterns;
1273
+ }
1274
+ async function findPackageJsonWorkspaceRoot(startDir) {
1275
+ let dir = startDir;
1276
+ for (; ; ) {
1277
+ try {
1278
+ const pkg = JSON.parse(await readFile6(join7(dir, "package.json"), "utf-8"));
1279
+ if (pkg.workspaces) return dir;
1280
+ } catch {
1281
+ }
1282
+ const parent = dirname3(dir);
1283
+ if (parent === dir) return null;
1284
+ dir = parent;
1285
+ }
1286
+ }
1287
+ async function resolveWorkspaceGlobs(rootDir, patterns, negations = []) {
1288
+ const results = [];
1289
+ for (const pattern of patterns) {
1290
+ if (pattern.includes("*")) {
1291
+ const parts = pattern.split("/");
1292
+ let staticPrefix = rootDir;
1293
+ const globParts = [];
1294
+ let foundGlob = false;
1295
+ for (const part of parts) {
1296
+ if (foundGlob || part.includes("*")) {
1297
+ foundGlob = true;
1298
+ globParts.push(part);
1299
+ } else {
1300
+ staticPrefix = join7(staticPrefix, part);
1301
+ }
1302
+ }
1303
+ if (globParts.length === 1 && globParts[0] === "*") {
1304
+ try {
1305
+ const entries = await readdir5(staticPrefix, { withFileTypes: true });
1306
+ for (const entry of entries) {
1307
+ if (entry.isDirectory()) {
1308
+ const pkgDir = join7(staticPrefix, entry.name);
1309
+ if (await exists(join7(pkgDir, "package.json"))) {
1310
+ results.push(pkgDir);
1311
+ }
1312
+ }
1313
+ }
1314
+ } catch {
1315
+ }
1316
+ } else {
1317
+ const isMatch = picomatch2(pattern);
1318
+ const candidates = await collectDirs(rootDir, 8);
1319
+ for (const candidate of candidates) {
1320
+ const rel = normalizePath(relative4(rootDir, candidate));
1321
+ if (isMatch(rel) && await exists(join7(candidate, "package.json"))) {
1322
+ results.push(candidate);
1323
+ }
1324
+ }
1325
+ }
1326
+ } else {
1327
+ const pkgDir = resolve2(rootDir, pattern);
1328
+ if (await exists(join7(pkgDir, "package.json"))) {
1329
+ results.push(pkgDir);
1330
+ }
1331
+ }
1332
+ }
1333
+ const unique = [...new Set(results)];
1334
+ if (negations.length === 0) return unique;
1335
+ const isExcluded = picomatch2(negations);
1336
+ return unique.filter((dir) => {
1337
+ const rel = normalizePath(relative4(rootDir, dir));
1338
+ return !isExcluded(rel);
1339
+ });
1340
+ }
1341
+ async function collectDirs(dir, maxDepth) {
1342
+ if (maxDepth <= 0) return [];
1343
+ const results = [];
1344
+ try {
1345
+ const entries = await readdir5(dir, { withFileTypes: true });
1346
+ for (const entry of entries) {
1347
+ if (!entry.isDirectory() || entry.name === "node_modules" || entry.name === ".git") continue;
1348
+ const full = join7(dir, entry.name);
1349
+ results.push(full);
1350
+ results.push(...await collectDirs(full, maxDepth - 1));
1351
+ }
1352
+ } catch {
1353
+ }
1354
+ return results;
1355
+ }
1356
+ async function buildWorkspaceGraph(startDir) {
1357
+ const dirs = await findWorkspacePackages(startDir);
1358
+ const packages = [];
1359
+ for (const dir of dirs) {
1360
+ try {
1361
+ const pkg = JSON.parse(
1362
+ await readFile6(join7(dir, "package.json"), "utf-8")
1363
+ );
1364
+ if (pkg.name && pkg.version) {
1365
+ packages.push({ name: pkg.name, version: pkg.version, dir, pkg });
1366
+ }
1367
+ } catch {
1368
+ }
1369
+ }
1370
+ const wsNames = new Set(packages.map((p) => p.name));
1371
+ const adjacency = /* @__PURE__ */ new Map();
1372
+ for (const wp of packages) {
1373
+ const deps = /* @__PURE__ */ new Set();
1374
+ for (const field of ["dependencies", "devDependencies"]) {
1375
+ const depMap = wp.pkg[field];
1376
+ if (!depMap) continue;
1377
+ for (const depName of Object.keys(depMap)) {
1378
+ if (wsNames.has(depName)) {
1379
+ deps.add(depName);
1380
+ }
1381
+ }
1382
+ }
1383
+ adjacency.set(wp.name, deps);
1384
+ }
1385
+ return { packages, adjacency };
1386
+ }
1387
+ function buildReverseAdjacency(adjacency) {
1388
+ const reverse = /* @__PURE__ */ new Map();
1389
+ for (const name of adjacency.keys()) {
1390
+ reverse.set(name, /* @__PURE__ */ new Set());
1391
+ }
1392
+ for (const [name, deps] of adjacency) {
1393
+ for (const dep of deps) {
1394
+ let set = reverse.get(dep);
1395
+ if (!set) {
1396
+ set = /* @__PURE__ */ new Set();
1397
+ reverse.set(dep, set);
1398
+ }
1399
+ set.add(name);
1400
+ }
1401
+ }
1402
+ return reverse;
1403
+ }
1404
+ function parseKeyValue(line) {
1405
+ const trimmed = line.trim();
1406
+ const colonIdx = trimmed.indexOf(":");
1407
+ if (colonIdx <= 0) return null;
1408
+ const key = trimmed.slice(0, colonIdx).trim();
1409
+ const value = trimmed.slice(colonIdx + 1).trim();
1410
+ if (!key || !value) return null;
1411
+ const unquoted = value.replace(/^["']|["']$/g, "");
1412
+ return [key, unquoted];
1413
+ }
1414
+ var init_workspace = __esm({
1415
+ "src/utils/workspace.ts"() {
1416
+ "use strict";
1417
+ init_fs();
1418
+ init_paths();
1419
+ }
1420
+ });
1421
+
1422
+ // src/core/publisher.ts
1423
+ import { readFile as readFile7, writeFile as writeFile3, rename as rename3, stat as stat5 } from "fs/promises";
1424
+ import { join as join8, relative as relative5, dirname as dirname4, resolve as resolve3 } from "path";
1425
+ import { spawn } from "child_process";
1426
+ import { platform } from "os";
1427
+ import { availableParallelism as availableParallelism3 } from "os";
1428
+ async function publish(packageDir, options = {}) {
1429
+ const pkgPath = join8(packageDir, "package.json");
1430
+ let pkgContent;
1431
+ try {
1432
+ pkgContent = await readFile7(pkgPath, "utf-8");
1433
+ } catch {
1434
+ throw new Error(`No package.json found in ${packageDir}`);
1435
+ }
1436
+ const pkg = JSON.parse(pkgContent);
1437
+ if (!pkg.name) throw new Error("package.json missing 'name' field");
1438
+ if (!pkg.version) throw new Error("package.json missing 'version' field");
1439
+ if (pkg.private && !options.allowPrivate) {
1440
+ throw new Error(
1441
+ `Package "${pkg.name}" is private. Use --private flag to publish private packages.`
1442
+ );
1443
+ }
1444
+ await runLifecycleHook(packageDir, pkg, "preknarr");
1445
+ if (options.runScripts !== false) {
1446
+ await runLifecycleHook(packageDir, pkg, "prepack");
1447
+ }
1448
+ let publishDir = packageDir;
1449
+ if (pkg.publishConfig?.directory) {
1450
+ publishDir = resolve3(packageDir, pkg.publishConfig.directory);
1451
+ try {
1452
+ const s = await stat5(publishDir);
1453
+ if (!s.isDirectory()) {
1454
+ throw new Error(`publishConfig.directory "${pkg.publishConfig.directory}" is not a directory`);
1455
+ }
1456
+ } catch (err) {
1457
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1458
+ throw new Error(`publishConfig.directory "${pkg.publishConfig.directory}" does not exist`);
1459
+ }
1460
+ throw err;
1461
+ }
1462
+ verbose(`[publish] Using publishConfig.directory: ${publishDir}`);
1463
+ }
1464
+ const filePkg = publishDir !== packageDir ? JSON.parse(await readFile7(join8(publishDir, "package.json"), "utf-8").catch(() => JSON.stringify(pkg))) : pkg;
1465
+ const files = await resolvePackFiles(publishDir, filePkg);
1466
+ if (files.length === 0) {
1467
+ throw new Error("No publishable files found");
1468
+ }
1469
+ verbose(`[publish] Resolved ${files.length} files for ${pkg.name}@${pkg.version}`);
1470
+ const contentHash = await computeContentHash(files, publishDir);
1471
+ await preloadWorkspaceVersions(pkg, packageDir);
1472
+ await preloadCatalogs(pkg, packageDir);
1473
+ if (!options.force) {
1474
+ const existingMeta = await readMeta(pkg.name, pkg.version);
1475
+ if (existingMeta && existingMeta.contentHash === contentHash) {
1476
+ consola.info(`${pkg.name}@${pkg.version} already up to date (no changes since last publish)`);
1477
+ return {
1478
+ name: pkg.name,
1479
+ version: pkg.version,
1480
+ fileCount: files.length,
1481
+ skipped: true,
1482
+ contentHash,
1483
+ buildId: existingMeta.buildId ?? ""
1484
+ };
1485
+ }
1486
+ }
1487
+ const storeEntryDir = getStoreEntryPath(pkg.name, pkg.version);
1488
+ const result = await withFileLock(
1489
+ storeEntryDir + ".lock",
1490
+ async () => {
1491
+ if (!options.force) {
1492
+ const metaUnderLock = await readMeta(pkg.name, pkg.version);
1493
+ if (metaUnderLock && metaUnderLock.contentHash === contentHash) {
1494
+ consola.info(`${pkg.name}@${pkg.version} already up to date (no changes since last publish)`);
1495
+ return {
1496
+ name: pkg.name,
1497
+ version: pkg.version,
1498
+ fileCount: files.length,
1499
+ skipped: true,
1500
+ contentHash,
1501
+ buildId: metaUnderLock.buildId ?? ""
1502
+ };
1503
+ }
1504
+ }
1505
+ const tmpDir = storeEntryDir + `.tmp-${process.pid}-${Date.now()}`;
1506
+ const tmpPackageDir = join8(tmpDir, "package");
1507
+ const buildId = contentHash.slice(9, 17);
1508
+ try {
1509
+ await ensurePrivateDir(tmpPackageDir);
1510
+ let processedPkg = rewriteProtocolVersions(pkg);
1511
+ processedPkg = applyPublishConfig(processedPkg);
1512
+ verbose(`[publish] Copying files to temp store...`);
1513
+ const uniqueDirs = new Set(
1514
+ files.map((file) => dirname4(join8(tmpPackageDir, relative5(publishDir, file))))
1515
+ );
1516
+ await Promise.all([...uniqueDirs].map((d) => ensureDir(d)));
1517
+ await Promise.all(
1518
+ files.map(
1519
+ (file) => copyLimit(async () => {
1520
+ const rel = relative5(publishDir, file);
1521
+ const dest = join8(tmpPackageDir, rel);
1522
+ if (rel === "package.json" && processedPkg !== pkg) {
1523
+ await writeFile3(dest, JSON.stringify(processedPkg, null, 2));
1524
+ } else {
1525
+ await copyWithCoW(file, dest, { ensureParent: false });
1526
+ }
1527
+ })
1528
+ )
1529
+ );
1530
+ if (publishDir !== packageDir) {
1531
+ await writeFile3(
1532
+ join8(tmpPackageDir, "package.json"),
1533
+ JSON.stringify(processedPkg, null, 2)
1534
+ );
1535
+ }
1536
+ const meta = {
1537
+ schemaVersion: 1,
1538
+ contentHash,
1539
+ publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1540
+ sourcePath: packageDir,
1541
+ buildId
1542
+ };
1543
+ await writeFile3(
1544
+ join8(tmpDir, ".knarr-meta.json"),
1545
+ JSON.stringify(meta, null, 2)
1546
+ );
1547
+ const hadOld = await exists(storeEntryDir);
1548
+ const oldDir = storeEntryDir + ".old-" + Date.now();
1549
+ if (hadOld) await rename3(storeEntryDir, oldDir);
1550
+ await moveDir(tmpDir, storeEntryDir);
1551
+ if (hadOld) {
1552
+ try {
1553
+ const { captureHistory: captureHistory2 } = await Promise.resolve().then(() => (init_history(), history_exports));
1554
+ await captureHistory2(pkg.name, pkg.version, oldDir, options.historyLimit);
1555
+ } catch (err) {
1556
+ verbose(`[publish] History capture failed: ${err instanceof Error ? err.message : String(err)}`);
1557
+ }
1558
+ await removeDir(oldDir);
1559
+ }
1560
+ verbose(`[publish] Stored at ${storeEntryDir}`);
1561
+ } catch (err) {
1562
+ await removeDir(tmpDir);
1563
+ throw err;
1564
+ }
1565
+ return {
1566
+ name: pkg.name,
1567
+ version: pkg.version,
1568
+ fileCount: files.length,
1569
+ skipped: false,
1570
+ contentHash,
1571
+ buildId
1572
+ };
1573
+ },
1574
+ { stale: 6e4 }
1575
+ );
1576
+ if (result.skipped) return result;
1577
+ if (options.runScripts !== false) {
1578
+ await runLifecycleHook(packageDir, pkg, "postpack");
1579
+ }
1580
+ await runLifecycleHook(packageDir, pkg, "postknarr");
1581
+ consola.success(
1582
+ `Published ${pkg.name}@${pkg.version} (${files.length} files) [${result.buildId}]`
1583
+ );
1584
+ return result;
1585
+ }
1586
+ async function runLifecycleHook(packageDir, pkg, hookName) {
1587
+ const script = pkg.scripts?.[hookName];
1588
+ if (!script) return;
1589
+ if (isDryRun()) {
1590
+ recordMutation({
1591
+ type: "lifecycle-skip",
1592
+ path: packageDir,
1593
+ detail: `${hookName}: ${script}`
1594
+ });
1595
+ return;
1596
+ }
1597
+ verbose(`[lifecycle] Running ${hookName}: ${script}`);
1598
+ return new Promise((resolve7, reject) => {
1599
+ const isWin = platform() === "win32";
1600
+ const shell = isWin ? "cmd" : "sh";
1601
+ const shellFlag = isWin ? "/c" : "-c";
1602
+ const child = spawn(shell, [shellFlag, script], {
1603
+ cwd: packageDir,
1604
+ stdio: "inherit"
1605
+ });
1606
+ const timer = setTimeout(() => {
1607
+ child.kill("SIGTERM");
1608
+ reject(new Error(`${hookName} script timed out after ${HOOK_TIMEOUT / 1e3}s. Increase KNARR_HOOK_TIMEOUT env var if the script needs more time.`));
1609
+ }, HOOK_TIMEOUT);
1610
+ child.on("close", (code) => {
1611
+ clearTimeout(timer);
1612
+ if (code === 0) {
1613
+ resolve7();
1614
+ } else {
1615
+ reject(new Error(`${hookName} script failed with exit code ${code}`));
1616
+ }
1617
+ });
1618
+ child.on("error", (err) => {
1619
+ clearTimeout(timer);
1620
+ reject(new Error(`${hookName} script error: ${err.message}`));
1621
+ });
1622
+ });
1623
+ }
1624
+ function applyPublishConfig(pkg) {
1625
+ if (!pkg.publishConfig) return pkg;
1626
+ const result = { ...pkg };
1627
+ for (const field of PUBLISH_CONFIG_OVERRIDES) {
1628
+ if (field in pkg.publishConfig) {
1629
+ result[field] = pkg.publishConfig[field];
1630
+ }
1631
+ }
1632
+ delete result.publishConfig;
1633
+ return result;
1634
+ }
1635
+ function rewriteProtocolVersions(pkg) {
1636
+ let changed = false;
1637
+ const result = { ...pkg };
1638
+ let catalogs = null;
1639
+ let catalogsLoaded = false;
1640
+ for (const depField of [
1641
+ "dependencies",
1642
+ "devDependencies",
1643
+ "peerDependencies",
1644
+ "optionalDependencies"
1645
+ ]) {
1646
+ const deps = pkg[depField];
1647
+ if (!deps) continue;
1648
+ let fieldChanged = false;
1649
+ const newDeps = { ...deps };
1650
+ for (const [name, version] of Object.entries(deps)) {
1651
+ if (version.startsWith("workspace:")) {
1652
+ const versionPart = version.slice("workspace:".length);
1653
+ if (versionPart === "*" || versionPart === "^" || versionPart === "~") {
1654
+ const depVersion = _cachedWorkspaceVersions?.versions.get(name) ?? pkg.version;
1655
+ newDeps[name] = versionPart === "*" ? depVersion : versionPart + depVersion;
1656
+ } else {
1657
+ newDeps[name] = versionPart;
1658
+ }
1659
+ fieldChanged = true;
1660
+ changed = true;
1661
+ } else if (version.startsWith("catalog:")) {
1662
+ if (!catalogsLoaded) {
1663
+ catalogs = loadCatalogsFromCache();
1664
+ catalogsLoaded = true;
1665
+ }
1666
+ if (catalogs) {
1667
+ const resolved = resolveCatalogVersion(version, name, catalogs);
1668
+ if (resolved) {
1669
+ newDeps[name] = resolved;
1670
+ fieldChanged = true;
1671
+ changed = true;
1672
+ } else {
1673
+ consola.warn(`catalog: specifier for "${name}" could not be resolved \u2014 published package.json will contain "${version}" which may cause install failures`);
1674
+ }
1675
+ } else {
1676
+ verbose(`[publish] No pnpm-workspace.yaml found, cannot resolve catalog: for "${name}"`);
1677
+ }
1678
+ }
1679
+ }
1680
+ if (fieldChanged) {
1681
+ result[depField] = newDeps;
1682
+ }
1683
+ }
1684
+ return changed ? result : pkg;
1685
+ }
1686
+ function resolveCatalogVersion(specifier, depName, catalogs) {
1687
+ const catalogRef = specifier.slice("catalog:".length);
1688
+ if (catalogRef === "" || catalogRef === "default") {
1689
+ return catalogs.default[depName] ?? null;
1690
+ }
1691
+ return catalogs.named[catalogRef]?.[depName] ?? null;
1692
+ }
1693
+ async function getWorkspaceRoot(packageDir) {
1694
+ if (_cachedWorkspaceRoot?.dir === packageDir) return _cachedWorkspaceRoot.root;
1695
+ const { findWorkspaceRoot: findWorkspaceRoot2 } = await Promise.resolve().then(() => (init_workspace(), workspace_exports));
1696
+ const root = await findWorkspaceRoot2(packageDir);
1697
+ _cachedWorkspaceRoot = { dir: packageDir, root };
1698
+ return root;
1699
+ }
1700
+ async function preloadWorkspaceVersions(pkg, packageDir) {
1701
+ const hasWorkspace = [
1702
+ "dependencies",
1703
+ "devDependencies",
1704
+ "peerDependencies",
1705
+ "optionalDependencies"
1706
+ ].some((field) => {
1707
+ const deps = pkg[field];
1708
+ return deps && Object.values(deps).some((v) => v.startsWith("workspace:"));
1709
+ });
1710
+ if (!hasWorkspace) return;
1711
+ const root = await getWorkspaceRoot(packageDir);
1712
+ if (!root) {
1713
+ _cachedWorkspaceVersions = null;
1714
+ return;
1715
+ }
1716
+ if (_cachedWorkspaceVersions?.root === root) return;
1717
+ const { findWorkspacePackages: findWorkspacePackages2 } = await Promise.resolve().then(() => (init_workspace(), workspace_exports));
1718
+ const pkgDirs = await findWorkspacePackages2(root);
1719
+ const versions = /* @__PURE__ */ new Map();
1720
+ await Promise.all(
1721
+ pkgDirs.map(async (dir) => {
1722
+ try {
1723
+ const depPkg = JSON.parse(
1724
+ await readFile7(join8(dir, "package.json"), "utf-8")
1725
+ );
1726
+ if (depPkg.name && depPkg.version) {
1727
+ versions.set(depPkg.name, depPkg.version);
1728
+ }
1729
+ } catch {
1730
+ }
1731
+ })
1732
+ );
1733
+ _cachedWorkspaceVersions = { root, versions };
1734
+ }
1735
+ function loadCatalogsFromCache() {
1736
+ return _cachedCatalogs?.catalogs ?? null;
1737
+ }
1738
+ async function preloadCatalogs(pkg, packageDir) {
1739
+ const hasCatalog = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"].some(
1740
+ (field) => {
1741
+ const deps = pkg[field];
1742
+ return deps && Object.values(deps).some((v) => v.startsWith("catalog:"));
1743
+ }
1744
+ );
1745
+ if (!hasCatalog) return;
1746
+ const root = await getWorkspaceRoot(packageDir);
1747
+ if (!root) {
1748
+ _cachedCatalogs = null;
1749
+ return;
1750
+ }
1751
+ const workspaceFile = join8(root, "pnpm-workspace.yaml");
1752
+ const mtimeMs = (await stat5(workspaceFile).catch(() => null))?.mtimeMs ?? 0;
1753
+ if (_cachedCatalogs?.root === root && _cachedCatalogs.mtimeMs === mtimeMs) return;
1754
+ const { parseCatalogs: parseCatalogs2 } = await Promise.resolve().then(() => (init_workspace(), workspace_exports));
1755
+ const catalogs = await parseCatalogs2(root);
1756
+ _cachedCatalogs = { root, mtimeMs, catalogs };
1757
+ }
1758
+ var copyLimit, HOOK_TIMEOUT, PUBLISH_CONFIG_OVERRIDES, _cachedWorkspaceRoot, _cachedWorkspaceVersions, _cachedCatalogs;
1759
+ var init_publisher = __esm({
1760
+ "src/core/publisher.ts"() {
1761
+ "use strict";
1762
+ init_console();
1763
+ init_concurrency();
1764
+ init_paths();
1765
+ init_pack_list();
1766
+ init_hash();
1767
+ init_fs();
1768
+ init_store();
1769
+ init_lockfile();
1770
+ init_logger();
1771
+ init_dry_run();
1772
+ copyLimit = pLimit(Math.max(availableParallelism3(), 8));
1773
+ HOOK_TIMEOUT = parseInt(process.env.KNARR_HOOK_TIMEOUT ?? "30000", 10);
1774
+ PUBLISH_CONFIG_OVERRIDES = [
1775
+ "main",
1776
+ "module",
1777
+ "exports",
1778
+ "types",
1779
+ "typings",
1780
+ "browser",
1781
+ "bin"
1782
+ ];
1783
+ _cachedWorkspaceRoot = null;
1784
+ _cachedWorkspaceVersions = null;
1785
+ _cachedCatalogs = null;
1786
+ }
1787
+ });
1788
+
1789
+ // src/utils/bin-linker.ts
1790
+ import { mkdir as mkdir3, symlink, writeFile as writeFile4, chmod, rm as rm3 } from "fs/promises";
1791
+ import { join as join9, relative as relative6, resolve as resolve4, sep as sep2 } from "path";
1792
+ import { platform as platform2 } from "os";
1793
+ function resolveBinEntries(pkg) {
1794
+ if (!pkg.bin) return {};
1795
+ if (typeof pkg.bin === "string") {
1796
+ const binName = pkg.name.startsWith("@") ? pkg.name.split("/")[1] : pkg.name;
1797
+ return { [binName]: pkg.bin };
1798
+ }
1799
+ return pkg.bin;
1800
+ }
1801
+ async function createBinLinks(consumerPath, packageName, pkg) {
1802
+ const entries = resolveBinEntries(pkg);
1803
+ if (Object.keys(entries).length === 0) return 0;
1804
+ if (isDryRun()) {
1805
+ for (const binName of Object.keys(entries)) {
1806
+ recordMutation({ type: "bin-link", path: join9(consumerPath, "node_modules", ".bin", binName), detail: packageName });
1807
+ }
1808
+ verbose(`[dry-run] would create ${Object.keys(entries).length} bin link(s) for ${packageName}`);
1809
+ return Object.keys(entries).length;
1810
+ }
1811
+ const binDir = join9(consumerPath, "node_modules", ".bin");
1812
+ await mkdir3(binDir, { recursive: true });
1813
+ const isWindows = platform2() === "win32";
1814
+ let count = 0;
1815
+ for (const [binName, binPath] of Object.entries(entries)) {
1816
+ const packageRoot = join9(consumerPath, "node_modules", packageName);
1817
+ const targetAbsolute = join9(packageRoot, binPath);
1818
+ const resolvedTarget = resolve4(targetAbsolute);
1819
+ if (!resolvedTarget.startsWith(resolve4(packageRoot) + sep2) && resolvedTarget !== resolve4(packageRoot)) {
1820
+ consola.warn(`bin "${binName}" points outside package directory, skipping`);
1821
+ continue;
1822
+ }
1823
+ const targetRelative = normalizePath(relative6(binDir, targetAbsolute));
1824
+ if (isWindows) {
1825
+ const cmdPath = join9(binDir, `${binName}.cmd`);
1826
+ const targetWindows = targetRelative.replace(/\//g, "\\");
1827
+ const cmdContent = `@ECHO off\r
1828
+ GOTO start\r
1829
+ :find_dp0\r
1830
+ SET dp0=%~dp0\r
1831
+ EXIT /b\r
1832
+ :start\r
1833
+ CALL :find_dp0\r
1834
+ "%dp0%\\${targetWindows}" %*\r
1835
+ `;
1836
+ await writeFile4(cmdPath, cmdContent);
1837
+ const ps1Path = join9(binDir, `${binName}.ps1`);
1838
+ const ps1Content = `#!/usr/bin/env pwsh
1839
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
1840
+ & node "$basedir/${targetRelative}" $args
1841
+ exit $LASTEXITCODE
1842
+ `;
1843
+ await writeFile4(ps1Path, ps1Content);
1844
+ const shPath = join9(binDir, binName);
1845
+ const shContent = `#!/bin/sh
1846
+ exec node "${targetRelative}" "$@"
1847
+ `;
1848
+ await writeFile4(shPath, shContent);
1849
+ } else {
1850
+ const linkPath = join9(binDir, binName);
1851
+ try {
1852
+ await rm3(linkPath, { force: true });
1853
+ } catch {
1854
+ }
1855
+ try {
1856
+ await symlink(targetRelative, linkPath);
1857
+ await chmod(targetAbsolute, 493);
1858
+ } catch (err) {
1859
+ if (isNodeError(err) && (err.code === "EPERM" || err.code === "EACCES")) {
1860
+ verbose(`[bin-linker] Symlink failed (${err.code}), using shell wrapper for ${binName}`);
1861
+ const shContent = `#!/bin/sh
1862
+ exec node "${targetRelative}" "$@"
1863
+ `;
1864
+ await writeFile4(linkPath, shContent);
1865
+ await chmod(linkPath, 493);
1866
+ } else {
1867
+ throw err;
1868
+ }
1869
+ }
1870
+ }
1871
+ count++;
1872
+ }
1873
+ return count;
1874
+ }
1875
+ async function removeBinLinks(consumerPath, pkg) {
1876
+ const entries = resolveBinEntries(pkg);
1877
+ if (isDryRun()) {
1878
+ for (const binName of Object.keys(entries)) {
1879
+ recordMutation({ type: "bin-unlink", path: join9(consumerPath, "node_modules", ".bin", binName) });
1880
+ }
1881
+ verbose(`[dry-run] would remove ${Object.keys(entries).length} bin link(s)`);
1882
+ return;
1883
+ }
1884
+ const binDir = join9(consumerPath, "node_modules", ".bin");
1885
+ const isWindows = platform2() === "win32";
1886
+ for (const binName of Object.keys(entries)) {
1887
+ try {
1888
+ await rm3(join9(binDir, binName), { force: true });
1889
+ if (isWindows) {
1890
+ await rm3(join9(binDir, `${binName}.cmd`), { force: true });
1891
+ await rm3(join9(binDir, `${binName}.ps1`), { force: true });
1892
+ }
1893
+ } catch {
1894
+ }
1895
+ }
1896
+ }
1897
+ var init_bin_linker = __esm({
1898
+ "src/utils/bin-linker.ts"() {
1899
+ "use strict";
1900
+ init_console();
1901
+ init_fs();
1902
+ init_logger();
1903
+ init_paths();
1904
+ init_dry_run();
1905
+ }
1906
+ });
1907
+
1908
+ // src/utils/pm-detect.ts
1909
+ import { readFile as readFile8, stat as stat6 } from "fs/promises";
1910
+ import { join as join10, dirname as dirname5 } from "path";
1911
+ async function readPackageManagerField(dir) {
1912
+ try {
1913
+ const raw = await readFile8(join10(dir, "package.json"), "utf-8");
1914
+ const pkg = JSON.parse(raw);
1915
+ if (typeof pkg.packageManager !== "string") return null;
1916
+ const name = pkg.packageManager.split("@")[0];
1917
+ return VALID_PMS.has(name) ? name : null;
1918
+ } catch {
1919
+ return null;
1920
+ }
1921
+ }
1922
+ async function detectPackageManager(projectDir) {
1923
+ let dir = projectDir;
1924
+ for (; ; ) {
1925
+ const fromField = await readPackageManagerField(dir);
1926
+ if (fromField) return fromField;
1927
+ const results = await Promise.all(
1928
+ LOCKFILES.map(async ([lockfile, pm]) => {
1929
+ try {
1930
+ await stat6(join10(dir, lockfile));
1931
+ return pm;
1932
+ } catch {
1933
+ return null;
1934
+ }
1935
+ })
1936
+ );
1937
+ const found = results.find((pm) => pm !== null);
1938
+ if (found) return found;
1939
+ const parent = dirname5(dir);
1940
+ if (parent === dir) return "npm";
1941
+ dir = parent;
1942
+ }
1943
+ }
1944
+ async function detectYarnNodeLinker(projectDir) {
1945
+ let dir = projectDir;
1946
+ for (; ; ) {
1947
+ let content;
1948
+ try {
1949
+ content = await readFile8(join10(dir, ".yarnrc.yml"), "utf-8");
1950
+ } catch {
1951
+ const parent = dirname5(dir);
1952
+ if (parent === dir) return null;
1953
+ dir = parent;
1954
+ continue;
1955
+ }
1956
+ for (const line of content.split("\n")) {
1957
+ const trimmed = line.trim();
1958
+ if (trimmed.startsWith("#") || !trimmed.includes("nodeLinker")) continue;
1959
+ const match = trimmed.match(/^nodeLinker:\s*(.+)$/);
1960
+ if (match) {
1961
+ const value = match[1].trim().replace(/^["']|["']$/g, "");
1962
+ if (value === "node-modules" || value === "pnpm" || value === "pnp") {
1963
+ return value;
1964
+ }
1965
+ }
1966
+ }
1967
+ return null;
1968
+ }
1969
+ }
1970
+ var VALID_PMS, LOCKFILES;
1971
+ var init_pm_detect = __esm({
1972
+ "src/utils/pm-detect.ts"() {
1973
+ "use strict";
1974
+ VALID_PMS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
1975
+ LOCKFILES = [
1976
+ ["pnpm-lock.yaml", "pnpm"],
1977
+ ["bun.lockb", "bun"],
1978
+ ["bun.lock", "bun"],
1979
+ ["yarn.lock", "yarn"],
1980
+ ["package-lock.json", "npm"]
1981
+ ];
1982
+ }
1983
+ });
1984
+
1985
+ // src/utils/bundler-detect.ts
1986
+ import { join as join11 } from "path";
1987
+ async function detectAllBundlers(projectDir) {
1988
+ const checks = BUNDLER_CONFIGS.flatMap(
1989
+ ([type, configFiles]) => configFiles.map(async (configFile) => ({
1990
+ type,
1991
+ configFile: join11(projectDir, configFile),
1992
+ found: await exists(join11(projectDir, configFile))
1993
+ }))
1994
+ );
1995
+ const all = await Promise.all(checks);
1996
+ const seen = /* @__PURE__ */ new Set();
1997
+ const results = [];
1998
+ for (const { type, configFile, found } of all) {
1999
+ if (found && !seen.has(type)) {
2000
+ seen.add(type);
2001
+ results.push({ type, configFile });
2002
+ }
2003
+ }
2004
+ return results;
2005
+ }
2006
+ var BUNDLER_CONFIGS;
2007
+ var init_bundler_detect = __esm({
2008
+ "src/utils/bundler-detect.ts"() {
2009
+ "use strict";
2010
+ init_fs();
2011
+ BUNDLER_CONFIGS = [
2012
+ ["vite", ["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"]],
2013
+ ["next", ["next.config.js", "next.config.ts", "next.config.mjs"]],
2014
+ ["webpack", ["webpack.config.js", "webpack.config.ts"]],
2015
+ ["turbo", ["turbo.json"]],
2016
+ ["rollup", ["rollup.config.js", "rollup.config.ts", "rollup.config.mjs"]]
2017
+ ];
2018
+ }
2019
+ });
2020
+
2021
+ // src/utils/bundler-cache.ts
2022
+ import { join as join12 } from "path";
2023
+ async function invalidateBundlerCache(consumerPath) {
2024
+ if (isDryRun()) {
2025
+ verbose(`[dry-run] would invalidate bundler caches for ${consumerPath}`);
2026
+ recordMutation({ type: "cache-invalidate", path: consumerPath });
2027
+ return;
2028
+ }
2029
+ let bundlers = bundlerCache.get(consumerPath);
2030
+ if (!bundlers) {
2031
+ bundlers = await detectAllBundlers(consumerPath);
2032
+ bundlerCache.set(consumerPath, bundlers);
2033
+ }
2034
+ for (const bundler of bundlers) {
2035
+ if (!bundler.type) continue;
2036
+ const dirs = CACHE_DIRS[bundler.type];
2037
+ if (!dirs) continue;
2038
+ for (const dir of dirs) {
2039
+ const cacheDir = join12(consumerPath, dir);
2040
+ if (await exists(cacheDir)) {
2041
+ try {
2042
+ await removeDir(cacheDir);
2043
+ verbose(`[inject] Invalidated ${bundler.type} cache: ${dir}`);
2044
+ } catch {
2045
+ verbose(`[inject] Could not clear ${bundler.type} cache: ${dir} (locked?)`);
2046
+ }
2047
+ }
2048
+ }
2049
+ }
2050
+ }
2051
+ var CACHE_DIRS, bundlerCache;
2052
+ var init_bundler_cache = __esm({
2053
+ "src/utils/bundler-cache.ts"() {
2054
+ "use strict";
2055
+ init_fs();
2056
+ init_bundler_detect();
2057
+ init_logger();
2058
+ init_dry_run();
2059
+ CACHE_DIRS = {
2060
+ next: [".next/cache"],
2061
+ webpack: ["node_modules/.cache"]
2062
+ };
2063
+ bundlerCache = /* @__PURE__ */ new Map();
2064
+ }
2065
+ });
2066
+
2067
+ // src/core/injector.ts
2068
+ import { readFile as readFile9, readdir as readdir6, realpath, stat as stat7 } from "fs/promises";
2069
+ import { join as join13, resolve as resolve5 } from "path";
2070
+ async function inject(storeEntry, consumerPath, pm, options = {}) {
2071
+ const targetDir = await resolveTargetDir(
2072
+ consumerPath,
2073
+ storeEntry.name,
2074
+ pm,
2075
+ storeEntry.version
2076
+ );
2077
+ verbose(`[inject] ${storeEntry.name}@${storeEntry.version} \u2192 ${targetDir}`);
2078
+ await ensureDir(targetDir);
2079
+ const { copied, removed, skipped } = await incrementalCopy(
2080
+ storeEntry.packageDir,
2081
+ targetDir,
2082
+ { force: options.force }
2083
+ );
2084
+ verbose(`[inject] ${copied} copied, ${removed} removed, ${skipped} skipped`);
2085
+ if (copied > 0 || removed > 0) {
2086
+ await invalidateBundlerCache(consumerPath);
2087
+ }
2088
+ const pkg = await readPackageJson(storeEntry.packageDir);
2089
+ const binLinks = pkg ? await createBinLinks(consumerPath, storeEntry.name, pkg) : 0;
2090
+ if (binLinks > 0) {
2091
+ verbose(`[inject] Created ${binLinks} bin link(s)`);
2092
+ }
2093
+ return { copied, removed, skipped, binLinks };
2094
+ }
2095
+ async function backupExisting(consumerPath, packageName, pm) {
2096
+ const installedDir = await resolveTargetDir(consumerPath, packageName, pm);
2097
+ if (!await exists(installedDir)) return false;
2098
+ const backupDir = getConsumerBackupPath(consumerPath, packageName);
2099
+ await removeDir(backupDir);
2100
+ await copyDir(installedDir, backupDir);
2101
+ return true;
2102
+ }
2103
+ async function restoreBackup(consumerPath, packageName, pm) {
2104
+ const backupDir = getConsumerBackupPath(consumerPath, packageName);
2105
+ if (!await exists(backupDir)) return false;
2106
+ const targetDir = await resolveTargetDir(consumerPath, packageName, pm);
2107
+ await removeDir(targetDir);
2108
+ await copyDir(backupDir, targetDir);
2109
+ await removeDir(backupDir);
2110
+ return true;
2111
+ }
2112
+ async function removeInjected(consumerPath, packageName, pm) {
2113
+ const targetDir = await resolveTargetDir(consumerPath, packageName, pm);
2114
+ const pkg = await readPackageJson(targetDir);
2115
+ if (pkg) {
2116
+ await removeBinLinks(consumerPath, pkg);
2117
+ }
2118
+ await removeDir(targetDir);
2119
+ }
2120
+ async function checkMissingDeps(storeEntry, consumerPath) {
2121
+ const pkg = await readPackageJson(storeEntry.packageDir);
2122
+ if (!pkg) return [];
2123
+ const allDeps = {
2124
+ ...pkg.dependencies,
2125
+ ...Object.fromEntries(
2126
+ Object.entries(pkg.peerDependencies ?? {}).filter(
2127
+ ([name]) => !pkg.peerDependenciesMeta?.[name]?.optional
2128
+ )
2129
+ )
2130
+ };
2131
+ if (Object.keys(allDeps).length === 0) return [];
2132
+ const depNames = Object.keys(allDeps);
2133
+ const results = await Promise.all(
2134
+ depNames.map(async (dep) => ({
2135
+ dep,
2136
+ installed: await exists(join13(consumerPath, "node_modules", dep))
2137
+ }))
2138
+ );
2139
+ return results.filter((r) => !r.installed).map((r) => r.dep);
2140
+ }
2141
+ async function resolveTargetDir(consumerPath, packageName, pm, version) {
2142
+ const directPath = getNodeModulesPackagePath(consumerPath, packageName);
2143
+ const needsSymlinkResolution = pm === "pnpm" || pm === "yarn" && await detectYarnNodeLinker(consumerPath) === "pnpm";
2144
+ if (!needsSymlinkResolution) {
2145
+ return directPath;
2146
+ }
2147
+ try {
2148
+ const realPath = await resolveRealPath(directPath);
2149
+ if (realPath !== resolve5(directPath)) {
2150
+ verbose(`[inject] pnpm: resolved symlink \u2192 ${realPath}`);
2151
+ return realPath;
2152
+ }
2153
+ } catch (err) {
2154
+ if (isNodeError(err) && err.code !== "ENOENT") {
2155
+ consola.debug(`pnpm symlink resolution error: ${err instanceof Error ? err.message : String(err)}`);
2156
+ }
2157
+ }
2158
+ const pnpmDir = join13(consumerPath, "node_modules", ".pnpm");
2159
+ if (await exists(pnpmDir)) {
2160
+ verbose(`[inject] pnpm: scanning .pnpm/ for ${packageName}`);
2161
+ const encodedName = packageName.replaceAll("/", "+");
2162
+ if (version) {
2163
+ const exactEntry = `${encodedName}@${version}`;
2164
+ const candidate = join13(pnpmDir, exactEntry, "node_modules", packageName);
2165
+ if (await exists(candidate)) {
2166
+ verbose(`[inject] pnpm: exact version match in .pnpm/ \u2192 ${candidate}`);
2167
+ return candidate;
2168
+ }
2169
+ }
2170
+ const entries = await readdir6(pnpmDir);
2171
+ for (const entry of entries) {
2172
+ if (entry.startsWith(encodedName + "@")) {
2173
+ const candidate = join13(
2174
+ pnpmDir,
2175
+ entry,
2176
+ "node_modules",
2177
+ packageName
2178
+ );
2179
+ if (await exists(candidate)) {
2180
+ verbose(`[inject] pnpm: found in .pnpm/ \u2192 ${candidate}`);
2181
+ return candidate;
2182
+ }
2183
+ }
2184
+ }
2185
+ }
2186
+ consola.warn(
2187
+ `pnpm: Could not find ${packageName} in .pnpm/ virtual store, using direct node_modules path. If this causes issues, run 'pnpm install' to rebuild the virtual store, then 'knarr add' again.`
2188
+ );
2189
+ return directPath;
2190
+ }
2191
+ async function resolveRealPath(linkPath) {
2192
+ try {
2193
+ await stat7(linkPath);
2194
+ return await realpath(linkPath);
2195
+ } catch (err) {
2196
+ if (isNodeError(err) && err.code === "ENOENT") {
2197
+ return resolve5(linkPath);
2198
+ }
2199
+ throw err;
2200
+ }
2201
+ }
2202
+ async function readPackageJson(dir) {
2203
+ try {
2204
+ const content = await readFile9(join13(dir, "package.json"), "utf-8");
2205
+ return JSON.parse(content);
2206
+ } catch (err) {
2207
+ if (isNodeError(err) && err.code !== "ENOENT") {
2208
+ consola.warn(`Failed to read package.json in ${dir}: ${err instanceof Error ? err.message : String(err)}`);
2209
+ }
2210
+ return null;
2211
+ }
2212
+ }
2213
+ var init_injector = __esm({
2214
+ "src/core/injector.ts"() {
2215
+ "use strict";
2216
+ init_console();
2217
+ init_paths();
2218
+ init_fs();
2219
+ init_bin_linker();
2220
+ init_logger();
2221
+ init_pm_detect();
2222
+ init_bundler_cache();
2223
+ }
2224
+ });
2225
+
2226
+ // src/core/tracker.ts
2227
+ import { readFile as readFile10 } from "fs/promises";
2228
+ import { dirname as dirname6 } from "path";
2229
+ async function readConsumerState(consumerPath) {
2230
+ const { state } = await readConsumerStateSafe(consumerPath);
2231
+ return state;
2232
+ }
2233
+ async function readConsumerStateSafe(consumerPath) {
2234
+ const statePath = getConsumerStatePath(consumerPath);
2235
+ try {
2236
+ const content = await readFile10(statePath, "utf-8");
2237
+ const parsed = JSON.parse(content);
2238
+ if (!isConsumerState(parsed)) {
2239
+ consola.warn(`Invalid consumer state in ${statePath}, using defaults`);
2240
+ return { state: { version: "1", links: {} }, reliable: false };
2241
+ }
2242
+ return { state: parsed, reliable: true };
2243
+ } catch (err) {
2244
+ if (isNodeError(err) && err.code === "ENOENT") {
2245
+ return { state: { version: "1", links: {} }, reliable: true };
2246
+ }
2247
+ consola.warn(`Failed to read consumer state: ${err instanceof Error ? err.message : String(err)}`);
2248
+ return { state: { version: "1", links: {} }, reliable: false };
2249
+ }
2250
+ }
2251
+ async function writeConsumerState(consumerPath, state) {
2252
+ await ensureDir(getConsumerKnarrDir(consumerPath));
2253
+ const statePath = getConsumerStatePath(consumerPath);
2254
+ await atomicWriteFile(statePath, JSON.stringify(state, null, 2));
2255
+ }
2256
+ async function addLink(consumerPath, packageName, entry) {
2257
+ const statePath = getConsumerStatePath(consumerPath);
2258
+ await withFileLock(statePath, async () => {
2259
+ const { state, reliable } = await readConsumerStateSafe(consumerPath);
2260
+ if (!reliable) {
2261
+ throw new Error(
2262
+ `Consumer state in ${statePath} is corrupt \u2014 refusing to write to avoid destroying existing links. Delete .knarr/state.json and re-run 'knarr add' for each package.`
2263
+ );
2264
+ }
2265
+ state.links[packageName] = entry;
2266
+ await writeConsumerState(consumerPath, state);
2267
+ });
2268
+ }
2269
+ async function removeLink(consumerPath, packageName) {
2270
+ const statePath = getConsumerStatePath(consumerPath);
2271
+ await withFileLock(statePath, async () => {
2272
+ const { state, reliable } = await readConsumerStateSafe(consumerPath);
2273
+ if (!reliable) {
2274
+ throw new Error(
2275
+ `Consumer state in ${statePath} is corrupt \u2014 refusing to write to avoid destroying existing links. Delete .knarr/state.json and re-run 'knarr add' for each package.`
2276
+ );
2277
+ }
2278
+ delete state.links[packageName];
2279
+ await writeConsumerState(consumerPath, state);
2280
+ });
2281
+ }
2282
+ async function getLink(consumerPath, packageName) {
2283
+ const state = await readConsumerState(consumerPath);
2284
+ return state.links[packageName] ?? null;
2285
+ }
2286
+ async function readConsumersRegistry() {
2287
+ const regPath = getConsumersPath();
2288
+ try {
2289
+ const content = await readFile10(regPath, "utf-8");
2290
+ const parsed = JSON.parse(content);
2291
+ if (!isConsumersRegistry(parsed)) {
2292
+ consola.warn(`Invalid consumers registry, using empty registry`);
2293
+ return {};
2294
+ }
2295
+ return parsed;
2296
+ } catch (err) {
2297
+ if (isNodeError(err) && err.code !== "ENOENT") {
2298
+ consola.warn(`Failed to read consumers registry: ${err instanceof Error ? err.message : String(err)}`);
2299
+ }
2300
+ return {};
2301
+ }
2302
+ }
2303
+ async function writeConsumersRegistry(registry) {
2304
+ const regPath = getConsumersPath();
2305
+ await ensurePrivateDir(dirname6(getConsumersPath()));
2306
+ await atomicWriteFile(regPath, JSON.stringify(registry, null, 2));
2307
+ }
2308
+ async function registerConsumer(packageName, consumerPath) {
2309
+ const regPath = getConsumersPath();
2310
+ await withFileLock(regPath, async () => {
2311
+ const registry = await readConsumersRegistry();
2312
+ if (!registry[packageName]) {
2313
+ registry[packageName] = [];
2314
+ }
2315
+ const normalized = normalizePath(consumerPath);
2316
+ if (!registry[packageName].includes(normalized)) {
2317
+ registry[packageName].push(normalized);
2318
+ }
2319
+ await writeConsumersRegistry(registry);
2320
+ });
2321
+ }
2322
+ async function unregisterConsumer(packageName, consumerPath) {
2323
+ const regPath = getConsumersPath();
2324
+ await withFileLock(regPath, async () => {
2325
+ const registry = await readConsumersRegistry();
2326
+ if (!registry[packageName]) return;
2327
+ const normalized = normalizePath(consumerPath);
2328
+ registry[packageName] = registry[packageName].filter(
2329
+ (p) => p !== normalized
2330
+ );
2331
+ if (registry[packageName].length === 0) {
2332
+ delete registry[packageName];
2333
+ }
2334
+ await writeConsumersRegistry(registry);
2335
+ });
2336
+ }
2337
+ async function getConsumers(packageName) {
2338
+ const registry = await readConsumersRegistry();
2339
+ return registry[packageName] ?? [];
2340
+ }
2341
+ async function cleanStaleConsumers() {
2342
+ const regPath = getConsumersPath();
2343
+ let removedConsumers = 0;
2344
+ let removedPackages = 0;
2345
+ await withFileLock(regPath, async () => {
2346
+ const registry = await readConsumersRegistry();
2347
+ const updated = {};
2348
+ for (const [pkgName, consumers] of Object.entries(registry)) {
2349
+ const results = await Promise.all(
2350
+ consumers.map(async (consumerPath) => ({
2351
+ consumerPath,
2352
+ valid: await exists(consumerPath)
2353
+ }))
2354
+ );
2355
+ const validConsumers = results.filter((r) => r.valid).map((r) => r.consumerPath);
2356
+ removedConsumers += consumers.length - validConsumers.length;
2357
+ if (validConsumers.length > 0) {
2358
+ updated[pkgName] = validConsumers;
2359
+ } else {
2360
+ removedPackages++;
2361
+ }
2362
+ }
2363
+ await writeConsumersRegistry(updated);
2364
+ });
2365
+ return { removedConsumers, removedPackages };
2366
+ }
2367
+ var init_tracker = __esm({
2368
+ "src/core/tracker.ts"() {
2369
+ "use strict";
2370
+ init_console();
2371
+ init_paths();
2372
+ init_fs();
2373
+ init_lockfile();
2374
+ init_validators();
2375
+ }
2376
+ });
2377
+
2378
+ // src/utils/build-detect.ts
2379
+ import { readFile as readFile11 } from "fs/promises";
2380
+ import { join as join14 } from "path";
2381
+ async function detectBuildCommand(packageDir, pm) {
2382
+ const runPrefix = pm === "npm" ? "npm run " : `${pm} `;
2383
+ try {
2384
+ const pkg = JSON.parse(
2385
+ await readFile11(join14(packageDir, "package.json"), "utf-8")
2386
+ );
2387
+ const scripts = pkg.scripts || {};
2388
+ for (const name of ["build", "compile", "bundle", "tsc"]) {
2389
+ if (scripts[name]) {
2390
+ return `${runPrefix}${name}`;
2391
+ }
2392
+ }
2393
+ } catch {
2394
+ }
2395
+ return null;
2396
+ }
2397
+ var init_build_detect = __esm({
2398
+ "src/utils/build-detect.ts"() {
2399
+ "use strict";
2400
+ }
2401
+ });
2402
+
2403
+ // src/utils/config.ts
2404
+ import { readFile as readFile12 } from "fs/promises";
2405
+ import { join as join15 } from "path";
2406
+ async function loadKnarrConfig(projectDir) {
2407
+ try {
2408
+ const raw = await readFile12(join15(projectDir, "package.json"), "utf-8");
2409
+ const pkg = JSON.parse(raw);
2410
+ const source = pkg.knarr;
2411
+ if (!source || typeof source !== "object") return {};
2412
+ const config = {};
2413
+ const p = source;
2414
+ if (typeof p.buildCmd === "string") config.buildCmd = p.buildCmd;
2415
+ if (Array.isArray(p.watchPatterns)) {
2416
+ config.watchPatterns = p.watchPatterns.filter(
2417
+ (v) => typeof v === "string"
2418
+ );
2419
+ }
2420
+ if (typeof p.debounce === "number" && Number.isFinite(p.debounce)) {
2421
+ config.debounce = p.debounce;
2422
+ }
2423
+ if (typeof p.cooldown === "number" && Number.isFinite(p.cooldown)) {
2424
+ config.cooldown = p.cooldown;
2425
+ }
2426
+ if (typeof p.historyLimit === "number" && Number.isFinite(p.historyLimit)) {
2427
+ config.historyLimit = Math.max(0, Math.floor(p.historyLimit));
2428
+ }
2429
+ if (typeof p.notify === "boolean") {
2430
+ config.notify = p.notify;
2431
+ }
2432
+ verbose(`[config] Loaded knarr config from package.json: ${JSON.stringify(config)}`);
2433
+ return config;
2434
+ } catch {
2435
+ return {};
2436
+ }
2437
+ }
2438
+ var init_config = __esm({
2439
+ "src/utils/config.ts"() {
2440
+ "use strict";
2441
+ init_logger();
2442
+ }
2443
+ });
2444
+
2445
+ // src/utils/timer.ts
2446
+ var Timer;
2447
+ var init_timer = __esm({
2448
+ "src/utils/timer.ts"() {
2449
+ "use strict";
2450
+ Timer = class {
2451
+ start = performance.now();
2452
+ /** Return elapsed time in ms */
2453
+ elapsedMs() {
2454
+ return performance.now() - this.start;
2455
+ }
2456
+ /** Return human-readable elapsed time (e.g., "1.2s" or "150ms") */
2457
+ elapsed() {
2458
+ const ms = this.elapsedMs();
2459
+ if (ms >= 1e3) {
2460
+ return `${(ms / 1e3).toFixed(1)}s`;
2461
+ }
2462
+ return `${Math.round(ms)}ms`;
2463
+ }
2464
+ };
2465
+ }
2466
+ });
2467
+
2468
+ // src/utils/output.ts
2469
+ function output(data) {
2470
+ if (isJsonOutput()) {
2471
+ console.log(JSON.stringify(data, null, 2));
2472
+ }
2473
+ }
2474
+ var init_output = __esm({
2475
+ "src/utils/output.ts"() {
2476
+ "use strict";
2477
+ init_logger();
2478
+ }
2479
+ });
2480
+
2481
+ // src/utils/errors.ts
2482
+ import pc2 from "picocolors";
2483
+ function errorWithSuggestion(message) {
2484
+ consola.error(message);
2485
+ for (const { pattern, message: suggestion } of SUGGESTIONS) {
2486
+ if (pattern.test(message)) {
2487
+ consola.info(`${pc2.dim("Suggestion:")} ${suggestion}`);
2488
+ break;
2489
+ }
2490
+ }
2491
+ }
2492
+ var SUGGESTIONS;
2493
+ var init_errors = __esm({
2494
+ "src/utils/errors.ts"() {
2495
+ "use strict";
2496
+ init_console();
2497
+ SUGGESTIONS = [
2498
+ {
2499
+ pattern: /not found in store/i,
2500
+ message: "Run 'knarr publish' in the package directory first, or use --from <path>."
2501
+ },
2502
+ {
2503
+ pattern: /is not linked/i,
2504
+ message: "Run 'knarr add <package>' to link it first."
2505
+ },
2506
+ {
2507
+ pattern: /No package\.json/i,
2508
+ message: "Make sure you're in a valid package directory with a package.json."
2509
+ },
2510
+ {
2511
+ pattern: /missing 'name'/i,
2512
+ message: "Add a 'name' field to your package.json."
2513
+ },
2514
+ {
2515
+ pattern: /missing 'version'/i,
2516
+ message: "Add a 'version' field to your package.json."
2517
+ },
2518
+ {
2519
+ pattern: /store entry missing/i,
2520
+ message: "Re-publish the package with 'knarr publish'."
2521
+ },
2522
+ {
2523
+ pattern: /EACCES|EPERM/i,
2524
+ message: "Permission denied. Try running with elevated privileges or check file ownership."
2525
+ },
2526
+ {
2527
+ pattern: /ENOSPC/i,
2528
+ message: "Disk is full. Free up some space and try again."
2529
+ },
2530
+ {
2531
+ pattern: /No publishable files/i,
2532
+ message: "Check the 'files' field in package.json, or ensure the build output exists."
2533
+ },
2534
+ {
2535
+ pattern: /private.*package/i,
2536
+ message: "Use --private flag to publish private packages."
2537
+ },
2538
+ {
2539
+ pattern: /Failed to read store entry/i,
2540
+ message: "The store may be corrupted. Try 'knarr clean' then re-publish."
2541
+ },
2542
+ {
2543
+ pattern: /Failed to acquire lock/i,
2544
+ message: "Another Knarr process may be running. Wait or delete stale .lk directories."
2545
+ },
2546
+ {
2547
+ pattern: /not a directory/i,
2548
+ message: "Check that the path exists and is a directory."
2549
+ },
2550
+ {
2551
+ pattern: /timed out/i,
2552
+ message: "Set KNARR_HOOK_TIMEOUT to a higher value (in ms) if your scripts need more time."
2553
+ },
2554
+ {
2555
+ pattern: /state.*corrupt|corrupt.*state/i,
2556
+ message: "Delete .knarr/state.json and re-run 'knarr add' for each package."
2557
+ },
2558
+ {
2559
+ pattern: /EBUSY/i,
2560
+ message: "A file is locked by another process (e.g. dev server). Stop the process and retry."
2561
+ },
2562
+ {
2563
+ pattern: /Invalid package name/i,
2564
+ message: "Package names must be non-empty. Use format: package-name or @scope/package-name."
2565
+ }
2566
+ ];
2567
+ }
2568
+ });
2569
+
2570
+ // src/core/push-engine.ts
2571
+ import { readFile as readFile13 } from "fs/promises";
2572
+ import { join as join16 } from "path";
2573
+ async function doPush(packageDir, options = {}) {
2574
+ const timer = new Timer();
2575
+ const result = await publish(packageDir, {
2576
+ runScripts: options.runScripts,
2577
+ force: options.force,
2578
+ historyLimit: options.historyLimit
2579
+ });
2580
+ if (result.skipped) {
2581
+ consola.info("No changes to push");
2582
+ return;
2583
+ }
2584
+ const entry = await getStoreEntry(result.name, result.version);
2585
+ if (!entry) {
2586
+ errorWithSuggestion(
2587
+ `Failed to read store entry for ${result.name}@${result.version} after publish`
2588
+ );
2589
+ return;
2590
+ }
2591
+ const consumers = await getConsumers(result.name);
2592
+ if (consumers.length === 0) {
2593
+ consola.success(
2594
+ `Published ${result.name}@${result.version} to store`
2595
+ );
2596
+ consola.info(
2597
+ "No consumers registered yet. Run 'knarr add " + result.name + "' in a consumer project to start receiving pushes."
2598
+ );
2599
+ output({
2600
+ name: result.name,
2601
+ version: result.version,
2602
+ buildId: result.buildId,
2603
+ consumers: 0,
2604
+ failedConsumers: 0,
2605
+ copied: 0,
2606
+ skipped: 0,
2607
+ elapsed: timer.elapsedMs()
2608
+ });
2609
+ return;
2610
+ }
2611
+ let totalCopied = 0;
2612
+ let totalSkipped = 0;
2613
+ let pushCount = 0;
2614
+ let failedCount = 0;
2615
+ const results = await Promise.all(
2616
+ consumers.map(
2617
+ (consumerPath) => consumerLimit(async () => {
2618
+ const link = await getLink(consumerPath, result.name);
2619
+ if (!link) {
2620
+ verbose(
2621
+ `[push] No link found for ${result.name} in ${consumerPath}, skipping`
2622
+ );
2623
+ return null;
2624
+ }
2625
+ try {
2626
+ const injectResult = await inject(
2627
+ entry,
2628
+ consumerPath,
2629
+ link.packageManager,
2630
+ { force: options.force }
2631
+ );
2632
+ await addLink(consumerPath, result.name, {
2633
+ ...link,
2634
+ contentHash: entry.meta.contentHash,
2635
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString(),
2636
+ buildId: entry.meta.buildId ?? ""
2637
+ });
2638
+ return injectResult;
2639
+ } catch (err) {
2640
+ consola.warn(
2641
+ `Failed to push to ${consumerPath}: ${err instanceof Error ? err.message : String(err)}`
2642
+ );
2643
+ return null;
2644
+ }
2645
+ })
2646
+ )
2647
+ );
2648
+ for (const r of results) {
2649
+ if (r) {
2650
+ totalCopied += r.copied;
2651
+ totalSkipped += r.skipped;
2652
+ pushCount++;
2653
+ } else {
2654
+ failedCount++;
2655
+ }
2656
+ }
2657
+ const buildTag = result.buildId ? ` [${result.buildId}]` : "";
2658
+ consola.success(
2659
+ `Pushed ${result.name}@${result.version}${buildTag} to ${pushCount} consumer(s) in ${timer.elapsed()} (${totalCopied} files changed, ${totalSkipped} unchanged)`
2660
+ );
2661
+ output({
2662
+ name: result.name,
2663
+ version: result.version,
2664
+ buildId: result.buildId,
2665
+ consumers: pushCount,
2666
+ failedConsumers: failedCount,
2667
+ copied: totalCopied,
2668
+ skipped: totalSkipped,
2669
+ elapsed: timer.elapsedMs()
2670
+ });
2671
+ }
2672
+ async function resolveWatchConfig(packageDir, args, config) {
2673
+ let buildCmd = args.build;
2674
+ let patterns = config?.watchPatterns;
2675
+ if (args.build) {
2676
+ } else if (args["skip-build"]) {
2677
+ } else if (config?.buildCmd) {
2678
+ buildCmd = config.buildCmd;
2679
+ consola.info(`Using build command from config: ${buildCmd}`);
2680
+ } else {
2681
+ const pm = await detectPackageManager(packageDir);
2682
+ const detected = await detectBuildCommand(packageDir, pm);
2683
+ if (detected) {
2684
+ buildCmd = detected;
2685
+ consola.info(`Auto-detected build command: ${detected}`);
2686
+ }
2687
+ }
2688
+ if (buildCmd) {
2689
+ const { exists: exists3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
2690
+ const candidates = ["src", "lib", "source", "app", "pages", "components"];
2691
+ const existing = (await Promise.all(
2692
+ candidates.map(async (dir) => ({
2693
+ dir,
2694
+ exists: await exists3(join16(packageDir, dir))
2695
+ }))
2696
+ )).filter((c) => c.exists).map((c) => c.dir);
2697
+ patterns = existing.length > 0 ? existing : ["src", "lib"];
2698
+ verbose(`[watch] Using source patterns with build command: ${patterns.join(", ")}`);
2699
+ } else {
2700
+ consola.info("No build command detected \u2014 watching output directories directly");
2701
+ try {
2702
+ const pkg = JSON.parse(
2703
+ await readFile13(join16(packageDir, "package.json"), "utf-8")
2704
+ );
2705
+ if (pkg.files && pkg.files.length > 0) {
2706
+ patterns = pkg.files;
2707
+ consola.info(`Watching from package.json "files": ${patterns.join(", ")}`);
2708
+ } else {
2709
+ consola.warn(
2710
+ `No "files" field in package.json \u2014 falling back to watching src/ and lib/. Add a "files" field or use --build to specify a build command.`
2711
+ );
2712
+ }
2713
+ } catch (err) {
2714
+ verbose(
2715
+ `[watch] Could not read package.json: ${err instanceof Error ? err.message : String(err)}`
2716
+ );
2717
+ }
2718
+ }
2719
+ return { buildCmd, patterns };
2720
+ }
2721
+ var consumerLimit;
2722
+ var init_push_engine = __esm({
2723
+ "src/core/push-engine.ts"() {
2724
+ "use strict";
2725
+ init_concurrency();
2726
+ init_publisher();
2727
+ init_store();
2728
+ init_injector();
2729
+ init_tracker();
2730
+ init_build_detect();
2731
+ init_pm_detect();
2732
+ init_timer();
2733
+ init_output();
2734
+ init_errors();
2735
+ init_logger();
2736
+ init_console();
2737
+ consumerLimit = pLimit(4);
2738
+ }
2739
+ });
2740
+
2741
+ // src/utils/topo-sort.ts
2742
+ var topo_sort_exports = {};
2743
+ __export(topo_sort_exports, {
2744
+ CycleError: () => CycleError,
2745
+ topoSort: () => topoSort
2746
+ });
2747
+ function topoSort(graph) {
2748
+ const inDegree = /* @__PURE__ */ new Map();
2749
+ const dependents = /* @__PURE__ */ new Map();
2750
+ for (const node of graph.keys()) {
2751
+ if (!inDegree.has(node)) inDegree.set(node, 0);
2752
+ if (!dependents.has(node)) dependents.set(node, []);
2753
+ }
2754
+ for (const [node, deps] of graph) {
2755
+ for (const dep of deps) {
2756
+ if (!graph.has(dep)) continue;
2757
+ inDegree.set(node, (inDegree.get(node) ?? 0) + 1);
2758
+ const list = dependents.get(dep);
2759
+ if (list) list.push(node);
2760
+ else dependents.set(dep, [node]);
2761
+ }
2762
+ }
2763
+ const queue = [];
2764
+ for (const [node, degree] of inDegree) {
2765
+ if (degree === 0) queue.push(node);
2766
+ }
2767
+ const sorted = [];
2768
+ while (queue.length > 0) {
2769
+ const node = queue.shift();
2770
+ sorted.push(node);
2771
+ for (const dependent of dependents.get(node) ?? []) {
2772
+ const newDegree = (inDegree.get(dependent) ?? 1) - 1;
2773
+ inDegree.set(dependent, newDegree);
2774
+ if (newDegree === 0) queue.push(dependent);
2775
+ }
2776
+ }
2777
+ if (sorted.length !== graph.size) {
2778
+ const remaining = [...graph.keys()].filter((n) => !sorted.includes(n));
2779
+ throw new CycleError(remaining);
2780
+ }
2781
+ return sorted;
2782
+ }
2783
+ var CycleError;
2784
+ var init_topo_sort = __esm({
2785
+ "src/utils/topo-sort.ts"() {
2786
+ "use strict";
2787
+ CycleError = class extends Error {
2788
+ cycle;
2789
+ constructor(cycle) {
2790
+ super(`Dependency cycle detected: ${cycle.join(" \u2192 ")}`);
2791
+ this.name = "CycleError";
2792
+ this.cycle = cycle;
2793
+ }
2794
+ };
2795
+ }
2796
+ });
2797
+
2798
+ // src/utils/bell.ts
2799
+ var bell_exports = {};
2800
+ __export(bell_exports, {
2801
+ ringBell: () => ringBell
2802
+ });
2803
+ function ringBell(enabled) {
2804
+ if (enabled) {
2805
+ process.stderr.write("\x07");
2806
+ }
2807
+ }
2808
+ var init_bell = __esm({
2809
+ "src/utils/bell.ts"() {
2810
+ "use strict";
2811
+ }
2812
+ });
2813
+
2814
+ // src/core/watcher.ts
2815
+ var watcher_exports = {};
2816
+ __export(watcher_exports, {
2817
+ killActiveBuild: () => killActiveBuild,
2818
+ runBuildCommand: () => runBuildCommand,
2819
+ startWatcher: () => startWatcher
2820
+ });
2821
+ import { spawn as spawn2 } from "child_process";
2822
+ import { readdir as readdir7, stat as stat8 } from "fs/promises";
2823
+ import { platform as platform3 } from "os";
2824
+ import { join as join17 } from "path";
2825
+ function killActiveBuild() {
2826
+ if (activeChild && !activeChild.killed) {
2827
+ activeChild.kill("SIGTERM");
2828
+ activeChild = null;
2829
+ }
2830
+ }
2831
+ async function walkDir(dir, snapshot) {
2832
+ let entries;
2833
+ try {
2834
+ entries = await readdir7(dir, { withFileTypes: true });
2835
+ } catch {
2836
+ return;
2837
+ }
2838
+ for (const entry of entries) {
2839
+ if (IGNORED_DIRS.has(entry.name)) continue;
2840
+ const fullPath = join17(dir, entry.name);
2841
+ if (entry.isDirectory()) {
2842
+ await walkDir(fullPath, snapshot);
2843
+ } else {
2844
+ try {
2845
+ const st = await stat8(fullPath);
2846
+ snapshot.set(fullPath, st.mtimeMs);
2847
+ } catch {
2848
+ }
2849
+ }
2850
+ }
2851
+ }
2852
+ async function buildSnapshot(watchPaths) {
2853
+ const snapshot = /* @__PURE__ */ new Map();
2854
+ for (const p of watchPaths) {
2855
+ try {
2856
+ const st = await stat8(p);
2857
+ if (st.isDirectory()) {
2858
+ await walkDir(p, snapshot);
2859
+ } else {
2860
+ snapshot.set(p, st.mtimeMs);
2861
+ }
2862
+ } catch {
2863
+ }
2864
+ }
2865
+ return snapshot;
2866
+ }
2867
+ async function startWatcher(watchDir, options, onChange) {
2868
+ const { watch } = await import("chokidar");
2869
+ const patterns = options.patterns ?? ["src", "lib"];
2870
+ const watchPaths = patterns.map(
2871
+ (p) => p.startsWith("/") || p.includes(":") ? p : `${watchDir}/${p}`
2872
+ );
2873
+ const debounceMs = options.debounce ?? 500;
2874
+ const cooldownMs = options.cooldown ?? 500;
2875
+ let debounceTimer = null;
2876
+ let closed = false;
2877
+ let running = false;
2878
+ let lastBuildEndTime = 0;
2879
+ let hasPendingChanges = false;
2880
+ const doBuild = async () => {
2881
+ if (closed || running) return;
2882
+ const timeSinceLastBuild = Date.now() - lastBuildEndTime;
2883
+ if (lastBuildEndTime > 0 && timeSinceLastBuild < cooldownMs) {
2884
+ const remaining = cooldownMs - timeSinceLastBuild;
2885
+ if (debounceTimer) clearTimeout(debounceTimer);
2886
+ debounceTimer = setTimeout(() => {
2887
+ debounceTimer = null;
2888
+ doBuild();
2889
+ }, remaining);
2890
+ return;
2891
+ }
2892
+ running = true;
2893
+ hasPendingChanges = false;
2894
+ try {
2895
+ if (options.buildCmd) {
2896
+ const success = await runBuildCommand(options.buildCmd, watchDir);
2897
+ if (!success) {
2898
+ consola.warn("Build failed (see output above), skipping push");
2899
+ if (options.notify) {
2900
+ const { ringBell: ringBell2 } = await Promise.resolve().then(() => (init_bell(), bell_exports));
2901
+ ringBell2(true);
2902
+ }
2903
+ return;
2904
+ }
2905
+ }
2906
+ await onChange();
2907
+ if (options.notify) ringBell(true);
2908
+ } catch (err) {
2909
+ consola.error(`Push failed: ${err instanceof Error ? err.message : String(err)}`);
2910
+ if (options.notify) ringBell(true);
2911
+ } finally {
2912
+ running = false;
2913
+ lastBuildEndTime = Date.now();
2914
+ if (hasPendingChanges && !closed) {
2915
+ hasPendingChanges = false;
2916
+ debounceTimer = setTimeout(() => {
2917
+ debounceTimer = null;
2918
+ doBuild();
2919
+ }, cooldownMs);
2920
+ }
2921
+ }
2922
+ };
2923
+ const onFileEvent = (path) => {
2924
+ if (closed) return;
2925
+ if (running) {
2926
+ hasPendingChanges = true;
2927
+ return;
2928
+ }
2929
+ const timeSinceLastBuild = Date.now() - lastBuildEndTime;
2930
+ if (lastBuildEndTime > 0 && timeSinceLastBuild < cooldownMs) {
2931
+ if (debounceTimer) clearTimeout(debounceTimer);
2932
+ const remainingCooldown = cooldownMs - timeSinceLastBuild;
2933
+ debounceTimer = setTimeout(() => {
2934
+ debounceTimer = null;
2935
+ doBuild();
2936
+ }, remainingCooldown + debounceMs);
2937
+ return;
2938
+ }
2939
+ if (debounceTimer) {
2940
+ clearTimeout(debounceTimer);
2941
+ }
2942
+ debounceTimer = setTimeout(() => {
2943
+ debounceTimer = null;
2944
+ doBuild();
2945
+ }, debounceMs);
2946
+ };
2947
+ const awfOption = options.buildCmd ? false : options.awaitWriteFinish ?? {
2948
+ stabilityThreshold: 200,
2949
+ pollInterval: 50
2950
+ };
2951
+ const usePolling = !!process.versions?.webcontainer;
2952
+ const watcher = watch(watchPaths, {
2953
+ ignoreInitial: true,
2954
+ ignored: [
2955
+ /[/\\]node_modules[/\\]/,
2956
+ /[/\\]\.git[/\\]/,
2957
+ /[/\\]\.knarr[/\\]/
2958
+ ],
2959
+ awaitWriteFinish: awfOption,
2960
+ usePolling,
2961
+ ...usePolling && { interval: 1e3 }
2962
+ });
2963
+ watcher.on("change", onFileEvent);
2964
+ watcher.on("add", onFileEvent);
2965
+ watcher.on("unlink", onFileEvent);
2966
+ watcher.on("error", (err) => {
2967
+ consola.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`);
2968
+ });
2969
+ let pollInterval = null;
2970
+ if (usePolling) {
2971
+ let lastSnapshot = await buildSnapshot(watchPaths);
2972
+ pollInterval = setInterval(async () => {
2973
+ if (closed) return;
2974
+ try {
2975
+ const current = await buildSnapshot(watchPaths);
2976
+ for (const [path, mtime] of current) {
2977
+ const prev = lastSnapshot.get(path);
2978
+ if (prev === void 0 || prev !== mtime) {
2979
+ onFileEvent(path);
2980
+ break;
2981
+ }
2982
+ }
2983
+ if (!closed) {
2984
+ for (const path of lastSnapshot.keys()) {
2985
+ if (!current.has(path)) {
2986
+ onFileEvent(path);
2987
+ break;
2988
+ }
2989
+ }
2990
+ }
2991
+ lastSnapshot = current;
2992
+ } catch {
2993
+ }
2994
+ }, 1e3);
2995
+ }
2996
+ const watcherHandle = {
2997
+ close: async () => {
2998
+ closed = true;
2999
+ if (pollInterval) clearInterval(pollInterval);
3000
+ if (debounceTimer) clearTimeout(debounceTimer);
3001
+ killActiveBuild();
3002
+ await watcher.close();
3003
+ activeWatcher = null;
3004
+ }
3005
+ };
3006
+ activeWatcher = watcherHandle;
3007
+ consola.info(`Watching for changes in: ${patterns.join(", ")}`);
3008
+ return watcherHandle;
3009
+ }
3010
+ function runBuildCommand(cmd, cwd) {
3011
+ return new Promise((resolve7) => {
3012
+ const isWin = platform3() === "win32";
3013
+ const shell = isWin ? "cmd" : "sh";
3014
+ const shellFlag = isWin ? "/c" : "-c";
3015
+ consola.start(`Running: ${cmd}`);
3016
+ const child = spawn2(shell, [shellFlag, cmd], {
3017
+ cwd,
3018
+ stdio: "inherit"
3019
+ });
3020
+ activeChild = child;
3021
+ child.on("close", (code) => {
3022
+ activeChild = null;
3023
+ if (code === 0) {
3024
+ consola.success("Build succeeded");
3025
+ resolve7(true);
3026
+ } else {
3027
+ consola.error(`Build failed with code ${code}`);
3028
+ resolve7(false);
3029
+ }
3030
+ });
3031
+ child.on("error", (err) => {
3032
+ activeChild = null;
3033
+ consola.error(`Build error: ${err.message}`);
3034
+ resolve7(false);
3035
+ });
3036
+ });
3037
+ }
3038
+ var activeChild, activeWatcher, IGNORED_DIRS;
3039
+ var init_watcher = __esm({
3040
+ "src/core/watcher.ts"() {
3041
+ "use strict";
3042
+ init_console();
3043
+ init_bell();
3044
+ activeChild = null;
3045
+ activeWatcher = null;
3046
+ IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".knarr"]);
3047
+ }
3048
+ });
3049
+
3050
+ // src/core/watch-orchestrator.ts
3051
+ var cascadeLimit, WatchOrchestrator;
3052
+ var init_watch_orchestrator = __esm({
3053
+ "src/core/watch-orchestrator.ts"() {
3054
+ "use strict";
3055
+ init_concurrency();
3056
+ init_console();
3057
+ init_logger();
3058
+ init_config();
3059
+ init_push_engine();
3060
+ cascadeLimit = pLimit(2);
3061
+ WatchOrchestrator = class {
3062
+ packages = /* @__PURE__ */ new Map();
3063
+ dependents = /* @__PURE__ */ new Map();
3064
+ cascade;
3065
+ pushOptions = {};
3066
+ constructor(cascade) {
3067
+ this.cascade = cascade;
3068
+ }
3069
+ async start(startDir, args, pushOptions) {
3070
+ this.pushOptions = pushOptions;
3071
+ const { buildWorkspaceGraph: buildWorkspaceGraph2, buildReverseAdjacency: buildReverseAdjacency2 } = await Promise.resolve().then(() => (init_workspace(), workspace_exports));
3072
+ const { topoSort: topoSort2, CycleError: CycleError2 } = await Promise.resolve().then(() => (init_topo_sort(), topo_sort_exports));
3073
+ const { startWatcher: startWatcher2 } = await Promise.resolve().then(() => (init_watcher(), watcher_exports));
3074
+ const graph = await buildWorkspaceGraph2(startDir);
3075
+ if (graph.packages.length === 0) {
3076
+ consola.warn("No workspace packages found");
3077
+ return;
3078
+ }
3079
+ let ordered;
3080
+ try {
3081
+ ordered = topoSort2(graph.adjacency);
3082
+ } catch (err) {
3083
+ if (err instanceof CycleError2) {
3084
+ consola.error(`Cannot watch: ${err.message}`);
3085
+ return;
3086
+ }
3087
+ throw err;
3088
+ }
3089
+ if (this.cascade) {
3090
+ this.dependents = buildReverseAdjacency2(graph.adjacency);
3091
+ consola.info("Cascade mode enabled");
3092
+ }
3093
+ const nameToDir = new Map(graph.packages.map((p) => [p.name, p.dir]));
3094
+ for (const name of ordered) {
3095
+ const dir = nameToDir.get(name);
3096
+ if (!dir) continue;
3097
+ const config = await loadKnarrConfig(dir);
3098
+ const { buildCmd, patterns } = await resolveWatchConfig(dir, args, config);
3099
+ const notify = args.notify ?? config.notify ?? false;
3100
+ const wrappedOnChange = async () => {
3101
+ await doPush(dir, pushOptions);
3102
+ await this.onPackagePushed(name);
3103
+ };
3104
+ const parseMs = (v) => {
3105
+ if (!v) return void 0;
3106
+ const n = parseInt(v, 10);
3107
+ return Number.isFinite(n) ? n : void 0;
3108
+ };
3109
+ const watcher = await startWatcher2(
3110
+ dir,
3111
+ {
3112
+ patterns,
3113
+ buildCmd,
3114
+ debounce: parseMs(args.debounce) ?? config.debounce,
3115
+ cooldown: parseMs(args.cooldown) ?? config.cooldown,
3116
+ notify
3117
+ },
3118
+ wrappedOnChange
3119
+ );
3120
+ this.packages.set(name, { dir, state: "idle", watcher });
3121
+ }
3122
+ consola.info(`Watching ${this.packages.size} workspace packages`);
3123
+ await new Promise((resolve7) => {
3124
+ const cleanup = async () => {
3125
+ consola.info("Stopping watchers...");
3126
+ await this.close();
3127
+ resolve7();
3128
+ };
3129
+ process.once("SIGINT", cleanup);
3130
+ process.once("SIGTERM", cleanup);
3131
+ });
3132
+ }
3133
+ async onPackagePushed(name) {
3134
+ if (!this.cascade) return;
3135
+ const deps = this.dependents.get(name);
3136
+ if (!deps || deps.size === 0) return;
3137
+ verbose(`[cascade] ${name} pushed, triggering dependents: ${[...deps].join(", ")}`);
3138
+ const tasks = [...deps].map(
3139
+ (depName) => cascadeLimit(() => this.requestRebuild(depName))
3140
+ );
3141
+ await Promise.all(tasks);
3142
+ }
3143
+ async requestRebuild(name) {
3144
+ const entry = this.packages.get(name);
3145
+ if (!entry) return;
3146
+ if (entry.state === "queued") {
3147
+ verbose(`[cascade] ${name} already queued, skipping`);
3148
+ return;
3149
+ }
3150
+ if (entry.state === "building") {
3151
+ verbose(`[cascade] ${name} is building, marking as queued`);
3152
+ entry.state = "queued";
3153
+ return;
3154
+ }
3155
+ entry.state = "building";
3156
+ verbose(`[cascade] Rebuilding ${name}`);
3157
+ try {
3158
+ const config = await loadKnarrConfig(entry.dir);
3159
+ const buildCmd = config.buildCmd;
3160
+ if (buildCmd) {
3161
+ const { runBuildCommand: runBuildCommand2 } = await Promise.resolve().then(() => (init_watcher(), watcher_exports));
3162
+ const success = await runBuildCommand2(buildCmd, entry.dir);
3163
+ if (!success) {
3164
+ consola.warn(`[cascade] Build failed for ${name}, skipping dependents`);
3165
+ entry.state = "idle";
3166
+ return;
3167
+ }
3168
+ }
3169
+ await doPush(entry.dir, this.pushOptions);
3170
+ await this.onPackagePushed(name);
3171
+ } catch (err) {
3172
+ consola.warn(
3173
+ `[cascade] Push failed for ${name}: ${err instanceof Error ? err.message : String(err)}`
3174
+ );
3175
+ } finally {
3176
+ const wasQueued = entry.state === "queued";
3177
+ entry.state = "idle";
3178
+ if (wasQueued) {
3179
+ await this.requestRebuild(name);
3180
+ }
3181
+ }
3182
+ }
3183
+ async close() {
3184
+ await Promise.all(
3185
+ [...this.packages.values()].map((p) => p.watcher.close())
3186
+ );
3187
+ this.packages.clear();
3188
+ }
3189
+ };
3190
+ }
3191
+ });
3192
+
3193
+ // src/index.ts
3194
+ init_publisher();
3195
+ init_injector();
3196
+ init_store();
3197
+ init_tracker();
3198
+ init_push_engine();
3199
+
3200
+ // src/core/batch-push.ts
3201
+ init_console();
3202
+ init_workspace();
3203
+ init_topo_sort();
3204
+ init_push_engine();
3205
+ init_timer();
3206
+ init_logger();
3207
+ async function doPushAll(startDir, options = {}) {
3208
+ const timer = new Timer();
3209
+ const graph = await buildWorkspaceGraph(startDir);
3210
+ if (graph.packages.length === 0) {
3211
+ consola.warn("No workspace packages found");
3212
+ return;
3213
+ }
3214
+ let ordered;
3215
+ try {
3216
+ ordered = topoSort(graph.adjacency);
3217
+ } catch (err) {
3218
+ if (err instanceof CycleError) {
3219
+ consola.error(`Cannot push: ${err.message}`);
3220
+ return;
3221
+ }
3222
+ throw err;
3223
+ }
3224
+ const nameToDir = new Map(graph.packages.map((p) => [p.name, p.dir]));
3225
+ consola.info(`Pushing ${ordered.length} packages in dependency order`);
3226
+ verbose(`[batch-push] Order: ${ordered.join(" \u2192 ")}`);
3227
+ let success = 0;
3228
+ let failed = 0;
3229
+ for (const name of ordered) {
3230
+ const dir = nameToDir.get(name);
3231
+ if (!dir) continue;
3232
+ try {
3233
+ await doPush(dir, options);
3234
+ success++;
3235
+ } catch (err) {
3236
+ consola.warn(
3237
+ `Failed to push ${name}: ${err instanceof Error ? err.message : String(err)}`
3238
+ );
3239
+ failed++;
3240
+ }
3241
+ }
3242
+ consola.success(
3243
+ `Pushed ${success}/${ordered.length} packages in ${timer.elapsed()}${failed > 0 ? ` (${failed} failed)` : ""}`
3244
+ );
3245
+ }
3246
+
3247
+ // src/index.ts
3248
+ init_history();
3249
+ init_watcher();
3250
+ init_watch_orchestrator();
3251
+ init_pm_detect();
3252
+ init_config();
3253
+ init_topo_sort();
3254
+ init_workspace();
3255
+
3256
+ // src/utils/preflight.ts
3257
+ init_logger();
3258
+ import { readFile as readFile14, stat as stat9 } from "fs/promises";
3259
+ import { join as join18, resolve as resolve6 } from "path";
3260
+ async function runPreflightChecks(packageDir) {
3261
+ const issues = [];
3262
+ let pkgContent;
3263
+ try {
3264
+ pkgContent = await readFile14(join18(packageDir, "package.json"), "utf-8");
3265
+ } catch {
3266
+ issues.push({
3267
+ code: "NO_PACKAGE_JSON",
3268
+ severity: "error",
3269
+ message: "No package.json found"
3270
+ });
3271
+ return issues;
3272
+ }
3273
+ let pkg;
3274
+ try {
3275
+ pkg = JSON.parse(pkgContent);
3276
+ } catch {
3277
+ issues.push({
3278
+ code: "INVALID_PACKAGE_JSON",
3279
+ severity: "error",
3280
+ message: "package.json is not valid JSON"
3281
+ });
3282
+ return issues;
3283
+ }
3284
+ const files = pkg.files;
3285
+ if (!files || files.length === 0) {
3286
+ issues.push({
3287
+ code: "EMPTY_FILES",
3288
+ severity: "warn",
3289
+ message: 'No "files" field in package.json \u2014 npm will include almost everything. Consider adding a "files" field to control what gets published.'
3290
+ });
3291
+ }
3292
+ if (typeof pkg.main === "string") {
3293
+ await checkPath(packageDir, pkg.main, "main", issues);
3294
+ }
3295
+ if (typeof pkg.module === "string") {
3296
+ await checkPath(packageDir, pkg.module, "module", issues);
3297
+ }
3298
+ if (typeof pkg.types === "string") {
3299
+ await checkPath(packageDir, pkg.types, "types", issues);
3300
+ } else if (typeof pkg.typings === "string") {
3301
+ await checkPath(packageDir, pkg.typings, "typings", issues);
3302
+ }
3303
+ if (typeof pkg.exports === "string") {
3304
+ await checkPath(packageDir, pkg.exports, "exports", issues);
3305
+ } else if (pkg.exports && typeof pkg.exports === "object") {
3306
+ await checkExports(packageDir, pkg.exports, issues);
3307
+ }
3308
+ if (typeof pkg.bin === "string") {
3309
+ await checkPath(packageDir, pkg.bin, "bin", issues);
3310
+ } else if (pkg.bin && typeof pkg.bin === "object") {
3311
+ for (const [name, binPath] of Object.entries(pkg.bin)) {
3312
+ if (typeof binPath === "string") {
3313
+ await checkPath(packageDir, binPath, `bin.${name}`, issues);
3314
+ }
3315
+ }
3316
+ }
3317
+ verbose(`[preflight] ${issues.length} issue(s) found in ${packageDir}`);
3318
+ return issues;
3319
+ }
3320
+ async function fileExists(filePath) {
3321
+ try {
3322
+ const s = await stat9(filePath);
3323
+ return s.isFile();
3324
+ } catch {
3325
+ return false;
3326
+ }
3327
+ }
3328
+ async function checkPath(packageDir, filePath, field, issues) {
3329
+ const resolved = resolve6(packageDir, filePath);
3330
+ if (!await fileExists(resolved)) {
3331
+ issues.push({
3332
+ code: "MISSING_PATH",
3333
+ severity: "warn",
3334
+ message: `"${field}" points to "${filePath}" which does not exist`
3335
+ });
3336
+ }
3337
+ }
3338
+ async function checkExports(packageDir, exports, issues) {
3339
+ for (const [key, value] of Object.entries(exports)) {
3340
+ if (typeof value === "string") {
3341
+ if (value.startsWith(".")) {
3342
+ const resolved = resolve6(packageDir, value);
3343
+ if (!await fileExists(resolved)) {
3344
+ issues.push({
3345
+ code: "EXPORTS_PATH_MISSING",
3346
+ severity: "warn",
3347
+ message: `exports["${key}"] points to "${value}" which does not exist`
3348
+ });
3349
+ }
3350
+ }
3351
+ } else if (value && typeof value === "object") {
3352
+ await checkExportsConditions(
3353
+ packageDir,
3354
+ key,
3355
+ value,
3356
+ issues
3357
+ );
3358
+ }
3359
+ }
3360
+ }
3361
+ async function checkExportsConditions(packageDir, exportKey, conditions, issues) {
3362
+ const keys = Object.keys(conditions);
3363
+ const typesIdx = keys.indexOf("types");
3364
+ const importIdx = keys.indexOf("import");
3365
+ const requireIdx = keys.indexOf("require");
3366
+ const defaultIdx = keys.indexOf("default");
3367
+ if (typesIdx !== -1) {
3368
+ const firstCodeIdx = Math.min(
3369
+ importIdx === -1 ? Infinity : importIdx,
3370
+ requireIdx === -1 ? Infinity : requireIdx,
3371
+ defaultIdx === -1 ? Infinity : defaultIdx
3372
+ );
3373
+ if (typesIdx > firstCodeIdx && firstCodeIdx !== Infinity) {
3374
+ issues.push({
3375
+ code: "TYPES_CONDITION_ORDER",
3376
+ severity: "warn",
3377
+ message: `exports["${exportKey}"].types should come before import/require/default for TypeScript to resolve it correctly`
3378
+ });
3379
+ }
3380
+ }
3381
+ for (const [condition, value] of Object.entries(conditions)) {
3382
+ if (typeof value === "string" && value.startsWith(".")) {
3383
+ const resolved = resolve6(packageDir, value);
3384
+ if (!await fileExists(resolved)) {
3385
+ issues.push({
3386
+ code: "EXPORTS_PATH_MISSING",
3387
+ severity: "warn",
3388
+ message: `exports["${exportKey}"].${condition} points to "${value}" which does not exist`
3389
+ });
3390
+ }
3391
+ } else if (value && typeof value === "object") {
3392
+ await checkExportsConditions(
3393
+ packageDir,
3394
+ exportKey,
3395
+ value,
3396
+ issues
3397
+ );
3398
+ }
3399
+ }
3400
+ }
3401
+
3402
+ // src/index.ts
3403
+ init_bell();
3404
+ init_timer();
3405
+ init_fs();
3406
+ init_paths();
3407
+ init_dry_run();
3408
+
3409
+ // src/utils/vite-config.ts
3410
+ import { readFile as readFile15 } from "fs/promises";
3411
+ function defineConfigUsesTernary(content) {
3412
+ const callRegex = /(^|[^A-Za-z0-9_$])defineConfig\s*\(/g;
3413
+ let match;
3414
+ while ((match = callRegex.exec(content)) !== null) {
3415
+ const parenStart = match.index + match[0].length - 1;
3416
+ let depth = 1;
3417
+ let i = parenStart + 1;
3418
+ let inString = false;
3419
+ let escaped = false;
3420
+ while (i < content.length && depth > 0) {
3421
+ const ch = content[i];
3422
+ if (escaped) {
3423
+ escaped = false;
3424
+ i++;
3425
+ continue;
3426
+ }
3427
+ if (ch === "\\") {
3428
+ escaped = true;
3429
+ i++;
3430
+ continue;
3431
+ }
3432
+ if (inString) {
3433
+ if (ch === inString) inString = false;
3434
+ i++;
3435
+ continue;
3436
+ }
3437
+ if (ch === '"' || ch === "'" || ch === "`") {
3438
+ inString = ch;
3439
+ i++;
3440
+ continue;
3441
+ }
3442
+ if (ch === "/" && content[i + 1] === "/") {
3443
+ const newline = content.indexOf("\n", i);
3444
+ i = newline === -1 ? content.length : newline + 1;
3445
+ continue;
3446
+ }
3447
+ if (ch === "/" && content[i + 1] === "*") {
3448
+ const end = content.indexOf("*/", i + 2);
3449
+ i = end === -1 ? content.length : end + 2;
3450
+ continue;
3451
+ }
3452
+ if (ch === "?") {
3453
+ const next = content[i + 1];
3454
+ if (next === "." || next === "?") {
3455
+ i += 2;
3456
+ continue;
3457
+ }
3458
+ return true;
3459
+ }
3460
+ if (ch === "(") depth++;
3461
+ if (ch === ")") depth--;
3462
+ i++;
3463
+ }
3464
+ callRegex.lastIndex = i;
3465
+ }
3466
+ return false;
3467
+ }
3468
+ function isComplexConfig(content) {
3469
+ if (defineConfigUsesTernary(content)) {
3470
+ return { complex: true, reason: "conditional defineConfig" };
3471
+ }
3472
+ if (/export\s+default\s+function/.test(content)) {
3473
+ return { complex: true, reason: "dynamic export (function)" };
3474
+ }
3475
+ if (/plugins\s*:\s*\[\s*\.\.\./.test(content)) {
3476
+ return { complex: true, reason: "spread operator in plugins array" };
3477
+ }
3478
+ if (/defineConfig\s*\(\s*async/.test(content) || /mergeConfig\s*\(/.test(content)) {
3479
+ return { complex: true, reason: "async or merged config" };
3480
+ }
3481
+ return { complex: false };
3482
+ }
3483
+ export {
3484
+ CycleError,
3485
+ Timer,
3486
+ WatchOrchestrator,
3487
+ addLink,
3488
+ backupExisting,
3489
+ buildReverseAdjacency,
3490
+ buildWorkspaceGraph,
3491
+ captureHistory,
3492
+ checkMissingDeps,
3493
+ cleanStaleConsumers,
3494
+ clearHistory,
3495
+ detectPackageManager,
3496
+ doPush,
3497
+ doPushAll,
3498
+ findStoreEntry,
3499
+ getConsumers,
3500
+ getHistoryEntry,
3501
+ getLink,
3502
+ getStoreEntry,
3503
+ inject,
3504
+ isComplexConfig,
3505
+ isNodeError,
3506
+ killActiveBuild,
3507
+ listHistory,
3508
+ listStoreEntries,
3509
+ loadKnarrConfig,
3510
+ normalizePath,
3511
+ printDryRunReport,
3512
+ pruneHistory,
3513
+ publish,
3514
+ readConsumerState,
3515
+ readConsumerStateSafe,
3516
+ recordMutation,
3517
+ registerConsumer,
3518
+ removeInjected,
3519
+ removeLink,
3520
+ resetMutations,
3521
+ resolveHistoryLimit,
3522
+ restoreBackup,
3523
+ restoreHistoryEntry,
3524
+ ringBell,
3525
+ runBuildCommand,
3526
+ runPreflightChecks,
3527
+ startWatcher,
3528
+ topoSort,
3529
+ unregisterConsumer
3530
+ };