@cellajs/cli 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env tsx
2
2
 
3
3
  // src/cella-cli.ts
4
- import { existsSync as existsSync10, statSync as statSync2 } from "fs";
5
- import { join as join17, resolve as resolve6 } from "path";
4
+ import { existsSync as existsSync11, statSync as statSync3 } from "fs";
5
+ import { join as join18, resolve as resolve6 } from "path";
6
6
  import process9 from "process";
7
7
 
8
8
  // src/cli.ts
@@ -75,6 +75,7 @@ var cellaConfigSchema = z.object({
75
75
  "engines",
76
76
  "packageManager",
77
77
  "overrides",
78
+ "exports",
78
79
  "pnpm"
79
80
  ])
80
81
  ).optional(),
@@ -215,7 +216,7 @@ import process4 from "process";
215
216
  // package.json
216
217
  var package_default = {
217
218
  name: "@cellajs/cli",
218
- version: "0.2.0",
219
+ version: "0.2.2",
219
220
  private: false,
220
221
  license: "MIT",
221
222
  description: "CLI for managing your monorepo and syncing with upstream Cella.",
@@ -226,7 +227,7 @@ var package_default = {
226
227
  homepage: "https://cellajs.com",
227
228
  author: "CellaJS <info@cellajs.com>",
228
229
  engines: {
229
- node: "24.x"
230
+ node: "26.x"
230
231
  },
231
232
  type: "module",
232
233
  main: "./dist/index.js",
@@ -266,24 +267,24 @@ var package_default = {
266
267
  },
267
268
  dependencies: {
268
269
  "@inquirer/core": "^11.2.1",
269
- "@inquirer/prompts": "^8.5.2",
270
- "@pierre/diffs": "^1.2.12",
270
+ "@inquirer/prompts": "^8.7.2",
271
+ "@pierre/diffs": "^1.4.3",
271
272
  commander: "^15.0.0",
272
273
  "ts-morph": "^28.0.0",
273
- zod: "^4.4.3"
274
+ zod: "^4.6.5"
274
275
  },
275
276
  devDependencies: {
276
- "@biomejs/biome": "^2.5.2",
277
- "@commitlint/cli": "^21.2.0",
278
- "@commitlint/config-conventional": "^21.2.0",
279
- "@evilmartians/lefthook": "^2.1.9",
280
- "@types/node": "24.10.13",
277
+ "@biomejs/biome": "^2.5.14",
278
+ "@commitlint/cli": "^21.2.3",
279
+ "@commitlint/config-conventional": "^21.2.3",
280
+ "@evilmartians/lefthook": "^2.1.14",
281
+ "@types/node": "26.5.0",
281
282
  "@typescript/native-preview": "7.0.0-dev.20260702.3",
282
283
  rimraf: "^6.1.3",
283
284
  tsup: "^8.5.1",
284
- tsx: "^4.22.4",
285
+ tsx: "^4.23.15",
285
286
  typescript: "^6.0.3",
286
- vitest: "^4.1.9"
287
+ vitest: "^4.1.11"
287
288
  },
288
289
  publishConfig: {
289
290
  access: "public"
@@ -567,37 +568,74 @@ function printSummary(summary, title = "summary") {
567
568
  console.info();
568
569
  printLine("behind", summary.behind);
569
570
  }
570
- function printFileGroup(files, status, linkOptions, options) {
571
- const filtered = files.filter((f) => f.status === status && !isManagedFile(f.path));
572
- if (filtered.length === 0) return;
573
- const config = statusConfig[status];
574
- const title = options?.title ?? `${config.icon} ${config.label}`;
575
- printSectionHeader(`${title} ${colors_default.dim(`\xB7 ${filtered.length} files`)}`);
576
- const useUpstream = options?.dateSource === "upstream";
571
+ function printFileSection(files, title, linkOptions, options) {
572
+ if (files.length === 0) return;
573
+ printSectionHeader(`${title} ${colors_default.dim(`\xB7 ${files.length} files`)}`);
574
+ const useUpstream = options.dateSource === "upstream";
577
575
  const maxLines = 100;
578
- const shown = filtered.length > maxLines ? filtered.slice(0, maxLines) : filtered;
576
+ const shown = files.length > maxLines ? files.slice(0, maxLines) : files;
579
577
  for (const file of shown) {
578
+ const icon = typeof options.icon === "string" ? options.icon : options.icon(file);
580
579
  const commit = useUpstream ? file.upstreamCommit : file.changedCommit;
581
580
  const date = useUpstream ? file.upstreamChangedAt : file.changedAt;
582
581
  const dateInfo = formatFileDateInfo(file.path, commit, date, linkOptions);
583
- console.info(` ${config.icon} ${file.path}${dateInfo}`);
582
+ console.info(` ${icon} ${file.path}${dateInfo}${options.suffix?.(file) ?? ""}`);
584
583
  }
585
- if (filtered.length > maxLines) {
586
- console.info(colors_default.dim(` ... + ${filtered.length - maxLines} more`));
584
+ if (files.length > maxLines) {
585
+ console.info(colors_default.dim(` ... + ${files.length - maxLines} more`));
587
586
  }
588
- if (options?.hint) {
587
+ if (options.hint) {
589
588
  console.info();
590
- console.info(colors_default.dim(` ${options.hint}`));
589
+ for (const line of Array.isArray(options.hint) ? options.hint : [options.hint]) {
590
+ console.info(colors_default.dim(` ${line}`));
591
+ }
591
592
  }
592
593
  }
594
+ function printFileGroup(files, status, linkOptions, options) {
595
+ const filtered = files.filter((f) => f.status === status && !isManagedFile(f.path));
596
+ const config = statusConfig[status];
597
+ const title = options?.title ?? `${config.icon} ${config.label}`;
598
+ printFileSection(filtered, title, linkOptions, { ...options, icon: config.icon });
599
+ }
600
+ function findProtectedBehind(files) {
601
+ return files.filter((file) => file.upstreamChanged && !isManagedFile(file.path));
602
+ }
603
+ var PROTECTED_BEHIND_HINT = "pinned/ignored files where upstream also changed since the last sync; the fork side wins on conflict, so diff each against upstream (analyze --open-diff <path>) and adopt what you need.";
604
+ function protectionIcon(file) {
605
+ return file.isIgnored ? statusConfig.ignored.icon : statusConfig.pinned.icon;
606
+ }
607
+ function formatUpstreamChangedLines(file) {
608
+ if (file.upstreamChangedLines === void 0) return "";
609
+ const n = file.upstreamChangedLines;
610
+ return colors_default.yellow(` \xB7 ${n} ${n === 1 ? "line" : "lines"} changed upstream`);
611
+ }
612
+ function formatUpstreamLinesAbsent(file) {
613
+ const n = file.upstreamLinesAbsent ?? 0;
614
+ return n > 0 ? colors_default.yellow(` \xB7 ${n} upstream ${n === 1 ? "line" : "lines"} absent`) : "";
615
+ }
593
616
  function printAnalysisFileGroups(files, linkOptions) {
594
617
  printFileGroup(files, "behind", linkOptions, {
595
618
  title: colors_default.cyan("\u2193 behind on upstream")
596
619
  });
597
620
  printFileGroup(files, "ahead", linkOptions, {
598
621
  title: `${colors_default.blue("\u2191 protected in fork")}`,
599
- hint: "these files have fork changes but are protected (pinned)."
622
+ suffix: formatUpstreamLinesAbsent,
623
+ hint: [
624
+ "these files have fork changes but are protected (pinned); upstream did not change them since the last sync.",
625
+ "pinned files keep the fork side on every conflict; a count here is upstream content the fork never received, deliberately or not: diff and decide."
626
+ ]
600
627
  });
628
+ printFileSection(
629
+ findProtectedBehind(files),
630
+ `${warningMark} ${colors_default.yellow("protected but behind upstream")}`,
631
+ linkOptions,
632
+ {
633
+ icon: protectionIcon,
634
+ suffix: formatUpstreamChangedLines,
635
+ dateSource: "upstream",
636
+ hint: PROTECTED_BEHIND_HINT
637
+ }
638
+ );
601
639
  printFileGroup(files, "drifted", linkOptions, {
602
640
  title: `${warningMark} ${colors_default.yellow("drifted from upstream")}`,
603
641
  hint: "these files have fork changes but are not pinned or ignored."
@@ -607,11 +645,6 @@ function printAnalysisFileGroups(files, linkOptions) {
607
645
  hint: "both fork and upstream changed.",
608
646
  dateSource: "upstream"
609
647
  });
610
- printFileGroup(files, "pinned", linkOptions, {
611
- title: `${colors_default.green("\u2A00 pinned")}`,
612
- hint: "both changed, fork wins (pinned in cella/cella.config.ts).",
613
- dateSource: "upstream"
614
- });
615
648
  }
616
649
  function writeLogFile(forkPath, files) {
617
650
  const logPath = join3(forkPath, "cella-sync.log");
@@ -652,11 +685,29 @@ function printSyncComplete(result, options = {}) {
652
685
  console.info(`${colors_default.green("\u2713")} sync complete`);
653
686
  console.info(colors_default.dim(` ${updated} files updated, ${merged} auto-merged, ${conflicts} conflicts`));
654
687
  }
688
+ printProtectedConflicts(result);
689
+ console.info();
690
+ }
691
+ function printProtectedConflicts(result) {
692
+ const paths = result.protectedConflicts ?? [];
693
+ if (paths.length === 0) return;
694
+ const byPath = new Map(result.files.map((file) => [file.path, file]));
655
695
  console.info();
696
+ console.info(
697
+ `${warningMark} ${colors_default.yellow(
698
+ `${paths.length} protected ${paths.length === 1 ? "file kept" : "files kept"} the fork version, dropping upstream changes:`
699
+ )}`
700
+ );
701
+ for (const path4 of paths) {
702
+ const file = byPath.get(path4);
703
+ const icon = file ? protectionIcon(file) : statusConfig.pinned.icon;
704
+ console.info(` ${icon} ${path4}${file ? formatUpstreamChangedLines(file) : ""}`);
705
+ }
706
+ console.info(colors_default.dim(` ${PROTECTED_BEHIND_HINT}`));
656
707
  }
657
708
  function findMaskingPins(files) {
658
709
  return files.filter(
659
- (file) => file.isPinned && file.status === "behind" && file.existsInFork && file.existsInUpstream
710
+ (file) => file.isPinned && !isManagedFile(file.path) && file.status === "behind" && file.existsInFork && file.existsInUpstream
660
711
  );
661
712
  }
662
713
  function printMaskingPinWarning(files) {
@@ -1377,6 +1428,23 @@ async function getFileChanges(cwd, baseRef, targetRef) {
1377
1428
  }
1378
1429
  return changes;
1379
1430
  }
1431
+ async function getDiffStat(cwd, fromRef, toRef, paths = []) {
1432
+ const args = ["diff", "--numstat", "--no-renames", fromRef, toRef];
1433
+ if (paths.length > 0) args.push("--", ...paths);
1434
+ const out = await git(args, cwd, { ignoreErrors: true });
1435
+ const stat = /* @__PURE__ */ new Map();
1436
+ for (const line of out.split("\n")) {
1437
+ if (!line.trim()) continue;
1438
+ const [a, d, ...rest] = line.split(" ");
1439
+ const path4 = rest.join(" ");
1440
+ if (!path4) continue;
1441
+ stat.set(path4, {
1442
+ additions: a === "-" ? null : Number(a),
1443
+ deletions: d === "-" ? null : Number(d)
1444
+ });
1445
+ }
1446
+ return stat;
1447
+ }
1380
1448
  async function getFileHashesAtRef(cwd, ref) {
1381
1449
  const output = await git(["ls-tree", "-r", ref], cwd);
1382
1450
  const hashes = /* @__PURE__ */ new Map();
@@ -1747,6 +1815,7 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
1747
1815
  );
1748
1816
  const analyzedFiles = [];
1749
1817
  const handledOldPaths = /* @__PURE__ */ new Set();
1818
+ const renamedProtected = /* @__PURE__ */ new Set();
1750
1819
  let processed = 0;
1751
1820
  for (const filePath of allFiles) {
1752
1821
  if (handledOldPaths.has(filePath)) continue;
@@ -1785,8 +1854,13 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
1785
1854
  handledOldPaths.add(oldPath);
1786
1855
  const forkModifiedOld = forkOldHash !== baseOldHash;
1787
1856
  const oldPathPinned = predicates.isPinned(oldPath);
1857
+ let upstreamChanged2;
1788
1858
  if (fileIsPinned || oldPathPinned || predicates.isIgnored(oldPath)) {
1789
1859
  status = "pinned";
1860
+ if (forkModifiedOld && upstreamHash !== baseOldHash) {
1861
+ upstreamChanged2 = true;
1862
+ renamedProtected.add(filePath);
1863
+ }
1790
1864
  } else if (!oldPathInFork) {
1791
1865
  status = "behind";
1792
1866
  } else if (forkModifiedOld) {
@@ -1803,7 +1877,8 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
1803
1877
  isPinned: fileIsPinned,
1804
1878
  existsInFork: inFork,
1805
1879
  existsInUpstream: true,
1806
- renamedFrom
1880
+ renamedFrom,
1881
+ upstreamChanged: upstreamChanged2
1807
1882
  });
1808
1883
  continue;
1809
1884
  }
@@ -1826,26 +1901,58 @@ async function analyzeRefs(repoPath, localRef, incomingRef, mergeBaseRef, predic
1826
1901
  status = "identical";
1827
1902
  } else {
1828
1903
  const forkChanged = forkHash !== baseHash;
1829
- const upstreamChanged = upstreamHash !== baseHash;
1830
- if (forkChanged && upstreamChanged) {
1904
+ const upstreamChanged2 = upstreamHash !== baseHash;
1905
+ if (forkChanged && upstreamChanged2) {
1831
1906
  status = fileIsPinned ? "pinned" : "diverged";
1832
- } else if (forkChanged && !upstreamChanged) {
1907
+ } else if (forkChanged && !upstreamChanged2) {
1833
1908
  status = fileIsPinned ? "ahead" : predicates.hard ? "behind" : "drifted";
1834
- } else if (!forkChanged && upstreamChanged) {
1909
+ } else if (!forkChanged && upstreamChanged2) {
1835
1910
  status = "behind";
1836
1911
  } else {
1837
1912
  status = "identical";
1838
1913
  }
1839
1914
  }
1915
+ const upstreamChanged = (fileIsPinned || fileIsIgnored) && inUpstream && upstreamHash !== baseHash && forkHash !== baseHash;
1840
1916
  analyzedFiles.push({
1841
1917
  path: filePath,
1842
1918
  status,
1843
1919
  isIgnored: fileIsIgnored,
1844
1920
  isPinned: fileIsPinned,
1845
1921
  existsInFork: inFork,
1846
- existsInUpstream: inUpstream
1922
+ existsInUpstream: inUpstream,
1923
+ upstreamChanged: upstreamChanged || void 0
1847
1924
  });
1848
1925
  }
1926
+ const flagged = analyzedFiles.filter((file) => file.upstreamChanged && !renamedProtected.has(file.path));
1927
+ if (flagged.length > 0) {
1928
+ const stat = await getDiffStat(
1929
+ repoPath,
1930
+ mergeBaseRef,
1931
+ incomingRef,
1932
+ flagged.map((file) => file.path)
1933
+ );
1934
+ for (const file of flagged) {
1935
+ const entry = stat.get(file.path);
1936
+ if (entry && entry.additions !== null && entry.deletions !== null) {
1937
+ file.upstreamChangedLines = entry.additions + entry.deletions;
1938
+ }
1939
+ }
1940
+ }
1941
+ const stale = analyzedFiles.filter(
1942
+ (file) => file.status === "ahead" && file.isPinned && !file.isIgnored && file.existsInFork && file.existsInUpstream && forkHashes.get(file.path) !== upstreamHashes.get(file.path)
1943
+ );
1944
+ if (stale.length > 0) {
1945
+ const stat = await getDiffStat(
1946
+ repoPath,
1947
+ incomingRef,
1948
+ localRef,
1949
+ stale.map((file) => file.path)
1950
+ );
1951
+ for (const file of stale) {
1952
+ const deletions = stat.get(file.path)?.deletions;
1953
+ if (deletions !== null && deletions !== void 0) file.upstreamLinesAbsent = deletions;
1954
+ }
1955
+ }
1849
1956
  return analyzedFiles;
1850
1957
  }
1851
1958
  async function enrichChangeInfo(repoPath, files, mergeBaseRef, localRef, incomingRef) {
@@ -1867,7 +1974,7 @@ async function enrichChangeInfo(repoPath, files, mergeBaseRef, localRef, incomin
1867
1974
  file.changedTs = info.timestamp;
1868
1975
  file.changedCommit = info.hash;
1869
1976
  }
1870
- } else if (file.status === "diverged" || file.status === "pinned") {
1977
+ } else if (file.status === "diverged" || file.status === "pinned" || file.upstreamChanged) {
1871
1978
  const forkFileInfo = forkInfo.get(file.path);
1872
1979
  const upstreamFileInfo = upstreamInfo.get(file.path);
1873
1980
  if (forkFileInfo) {
@@ -2013,6 +2120,7 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
2013
2120
  }
2014
2121
  }
2015
2122
  }
2123
+ const protectedConflicts = analyzedFiles.filter((file) => file.upstreamChanged).map((file) => file.path);
2016
2124
  const gitConflicts = await getConflictedFiles(forkPath);
2017
2125
  for (const filePath of gitConflicts) {
2018
2126
  const isProtected = isIgnored(filePath, config) || isPinnedForSync(filePath, config, config.unpinned);
@@ -2020,6 +2128,7 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
2020
2128
  if (await fileExistsAtRef(forkPath, "HEAD", filePath)) {
2021
2129
  onProgress?.(`\u2192 ${filePath}: keeping fork (protected conflict)`);
2022
2130
  await restoreToHead(forkPath, filePath);
2131
+ if (!protectedConflicts.includes(filePath)) protectedConflicts.push(filePath);
2023
2132
  } else {
2024
2133
  onProgress?.(`\u2192 ${filePath}: removing (protected conflict, not in fork)`);
2025
2134
  await removeFileFully(forkPath, filePath);
@@ -2037,7 +2146,7 @@ async function applyDirectMerge(forkPath, upstreamRef, mergeBaseRef, config, onP
2037
2146
  await removeFileFromWorktree(forkPath, filePath);
2038
2147
  }
2039
2148
  }
2040
- return { remainingConflicts, analyzedFiles, autoMergedFiles };
2149
+ return { remainingConflicts, analyzedFiles, autoMergedFiles, protectedConflicts };
2041
2150
  }
2042
2151
  function calculateSummary(files) {
2043
2152
  const summary = {
@@ -2130,7 +2239,7 @@ function resultMeta(config, ctx) {
2130
2239
  async function runSyncMerge(config, ctx, onProgress, onStep) {
2131
2240
  const { forkPath } = config;
2132
2241
  const { upstreamRef, releaseTag, mergeBase, upstreamGitHubUrl, upstreamCommit } = ctx;
2133
- const { remainingConflicts, analyzedFiles, autoMergedFiles } = await applyDirectMerge(
2242
+ const { remainingConflicts, analyzedFiles, autoMergedFiles, protectedConflicts } = await applyDirectMerge(
2134
2243
  forkPath,
2135
2244
  upstreamRef,
2136
2245
  mergeBase,
@@ -2177,6 +2286,7 @@ async function runSyncMerge(config, ctx, onProgress, onStep) {
2177
2286
  summary,
2178
2287
  conflicts: remainingConflicts,
2179
2288
  autoMergedFiles,
2289
+ protectedConflicts,
2180
2290
  ...resultMeta(config, ctx)
2181
2291
  };
2182
2292
  }
@@ -2213,6 +2323,7 @@ async function runAnalyzePreview(config, ctx, onProgress, onStep) {
2213
2323
  summary: calculateSummary(analyzedFiles),
2214
2324
  // For analyze mode, count diverged files as potential conflicts
2215
2325
  conflicts: analyzedFiles.filter((f) => f.status === "diverged").map((f) => f.path),
2326
+ protectedConflicts: analyzedFiles.filter((f) => f.upstreamChanged).map((f) => f.path),
2216
2327
  ...resultMeta(config, ctx)
2217
2328
  };
2218
2329
  }
@@ -2239,7 +2350,8 @@ var scopeStatuses = {
2239
2350
  };
2240
2351
  function filterByScope(files, scope) {
2241
2352
  const statuses = scopeStatuses[scope];
2242
- return files.filter((f) => statuses.has(f.status));
2353
+ const includeProtectedBehind = scope !== "risk";
2354
+ return files.filter((f) => statuses.has(f.status) || includeProtectedBehind && f.upstreamChanged === true);
2243
2355
  }
2244
2356
  function findTargetFile(files, targetPath) {
2245
2357
  const exact = files.find((f) => f.path === targetPath);
@@ -2280,7 +2392,10 @@ async function runAnalyze(config) {
2280
2392
  changedAt: f.changedAt ?? null,
2281
2393
  changedCommit: f.changedCommit ?? null,
2282
2394
  upstreamChangedAt: f.upstreamChangedAt ?? null,
2283
- upstreamCommit: f.upstreamCommit ?? null
2395
+ upstreamCommit: f.upstreamCommit ?? null,
2396
+ upstreamChanged: f.upstreamChanged ?? false,
2397
+ upstreamChangedLines: f.upstreamChangedLines ?? null,
2398
+ upstreamLinesAbsent: f.upstreamLinesAbsent ?? null
2284
2399
  }));
2285
2400
  writeStdout(JSON.stringify(out, null, 2));
2286
2401
  return result;
@@ -2778,11 +2893,11 @@ function printOutdatedResults(enhancedPackages, dependentNameCache, forkPath) {
2778
2893
  const sortedDependents = [...dependentMap.entries()].sort((a, b) => a[0].localeCompare(b[0]));
2779
2894
  const maxDependentNameLen = Math.max(0, ...sortedDependents.map(([name]) => name.length));
2780
2895
  for (const [dependentName, { location, count }] of sortedDependents) {
2781
- const packageJsonPath = path2.join(location, "package.json");
2782
- const relativePath = path2.relative(forkPath, packageJsonPath);
2896
+ const packageJsonPath2 = path2.join(location, "package.json");
2897
+ const relativePath = path2.relative(forkPath, packageJsonPath2);
2783
2898
  const paddedName = dependentName.padEnd(maxDependentNameLen);
2784
2899
  console.info(
2785
- ` ${colors_default.dim("\u2022")} ${paddedName} ${hyperlink(relativePath, `file://${packageJsonPath}`)} ${colors_default.dim(`${count}`)}`
2900
+ ` ${colors_default.dim("\u2022")} ${paddedName} ${hyperlink(relativePath, `file://${packageJsonPath2}`)} ${colors_default.dim(`${count}`)}`
2786
2901
  );
2787
2902
  }
2788
2903
  }
@@ -3379,21 +3494,6 @@ async function forkRefMeta(cellaPath, forkRef) {
3379
3494
  const [sha = forkRef.slice(0, 7), date = "unknown"] = out.split(" ");
3380
3495
  return { sha, date };
3381
3496
  }
3382
- async function diffStat(cellaPath, baseRef, ref) {
3383
- const out = await git(["diff", "--numstat", `${baseRef}..${ref}`], cellaPath, { ignoreErrors: true });
3384
- const stat = /* @__PURE__ */ new Map();
3385
- for (const line of out.split("\n")) {
3386
- if (!line.trim()) continue;
3387
- const [a, d, ...rest] = line.split(" ");
3388
- const path4 = rest.join(" ");
3389
- if (!path4) continue;
3390
- stat.set(path4, {
3391
- additions: a === "-" ? null : Number(a),
3392
- deletions: d === "-" ? null : Number(d)
3393
- });
3394
- }
3395
- return stat;
3396
- }
3397
3497
  async function runContributions(config) {
3398
3498
  const forks = config.forks ?? [];
3399
3499
  if (forks.length === 0) {
@@ -3468,7 +3568,7 @@ async function runContributions(config) {
3468
3568
  if (countDetection(detection) > 0) {
3469
3569
  const { branch, appliedFiles } = await buildContribBranch(config.forkPath, baseRef, forkRef, detection, forkName);
3470
3570
  if (appliedFiles.length > 0) {
3471
- const stat = await diffStat(config.forkPath, baseRef, branch);
3571
+ const stat = await getDiffStat(config.forkPath, baseRef, branch);
3472
3572
  const metaByPath = new Map(detection.files.map((f) => [f.path, f]));
3473
3573
  for (const path4 of [...detection.modified, ...detection.created]) {
3474
3574
  const fileMeta = metaByPath.get(path4);
@@ -3668,8 +3768,93 @@ function closePr(cwd, ref) {
3668
3768
  }
3669
3769
 
3670
3770
  // src/services/packages.ts
3671
- import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
3771
+ import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3772
+ import { join as join15 } from "path";
3773
+
3774
+ // src/services/package-usage.ts
3775
+ import { existsSync as existsSync8, readFileSync as readFileSync5, statSync } from "fs";
3672
3776
  import { join as join14 } from "path";
3777
+ var MAX_FILE_BYTES = 1024 * 1024;
3778
+ function escapeRegExp(value) {
3779
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3780
+ }
3781
+ function readSearchable(filePath) {
3782
+ try {
3783
+ if (statSync(filePath).size > MAX_FILE_BYTES) return "";
3784
+ const text = readFileSync5(filePath, "utf-8");
3785
+ return text.includes("\0") ? "" : text;
3786
+ } catch {
3787
+ return "";
3788
+ }
3789
+ }
3790
+ function fileSource(forkPath, path4) {
3791
+ let text;
3792
+ return { path: path4, label: path4, text: () => text ??= readSearchable(join14(forkPath, path4)) };
3793
+ }
3794
+ async function readScriptsAtRef(forkPath, ref, path4) {
3795
+ try {
3796
+ return JSON.parse(await git(["show", `${ref}:${path4}`], forkPath)).scripts ?? {};
3797
+ } catch {
3798
+ return {};
3799
+ }
3800
+ }
3801
+ async function scriptSources(forkPath, upstreamRef, baseRef, path4) {
3802
+ let forkScripts;
3803
+ try {
3804
+ forkScripts = JSON.parse(readFileSync5(join14(forkPath, path4), "utf-8")).scripts ?? {};
3805
+ } catch {
3806
+ return [];
3807
+ }
3808
+ const baseScripts = await readScriptsAtRef(forkPath, baseRef, path4);
3809
+ const upstreamScripts = await readScriptsAtRef(forkPath, upstreamRef, path4);
3810
+ return Object.entries(forkScripts).filter(([name, value]) => value !== baseScripts[name] && value !== upstreamScripts[name]).map(([name, value]) => ({ path: path4, label: `${path4} (scripts.${name})`, text: () => value }));
3811
+ }
3812
+ function binNames(forkPath, location, name) {
3813
+ const bareName = name.split("/").pop();
3814
+ for (const dir of [join14(forkPath, location), forkPath]) {
3815
+ const manifest = join14(dir, "node_modules", name, "package.json");
3816
+ if (!existsSync8(manifest)) continue;
3817
+ try {
3818
+ const { bin } = JSON.parse(readFileSync5(manifest, "utf-8"));
3819
+ if (typeof bin === "string") return [bareName];
3820
+ return bin && typeof bin === "object" ? Object.keys(bin) : [];
3821
+ } catch {
3822
+ return [];
3823
+ }
3824
+ }
3825
+ return [bareName];
3826
+ }
3827
+ function packagePatterns(forkPath, location, name) {
3828
+ const specifiers = [name];
3829
+ const typed = name.match(/^@types\/(.+)$/);
3830
+ if (typed) specifiers.push(typed[1].includes("__") ? `@${typed[1].replace("__", "/")}` : typed[1]);
3831
+ const patterns = specifiers.map((specifier) => new RegExp(`(['"\`])${escapeRegExp(specifier)}(?:/[^'"\`\\s]*)?\\1`));
3832
+ for (const bin of binNames(forkPath, location, name)) {
3833
+ patterns.push(new RegExp(`(?:^|[\\s'"\`(;&|=])${escapeRegExp(bin)}(?=$|[\\s'"\`);&|])`, "m"));
3834
+ }
3835
+ return patterns;
3836
+ }
3837
+ function scriptPattern(name) {
3838
+ return new RegExp(`\\b(?:pnpm|npm|yarn|bun)\\s+(?:[^\\n;&|]*?\\s)?${escapeRegExp(name)}(?=$|[\\s'"\`);&|])`, "m");
3839
+ }
3840
+ async function loadForkUsage(forkPath, upstreamRef, baseRef) {
3841
+ const diff = await git(["diff", "--name-only", "-z", "--diff-filter=d", upstreamRef], forkPath);
3842
+ const sources = [];
3843
+ for (const path4 of diff.split("\0").filter(Boolean)) {
3844
+ if (isPackageJson(path4)) sources.push(...await scriptSources(forkPath, upstreamRef, baseRef, path4));
3845
+ else if (!isManagedFile(path4)) sources.push(fileSource(forkPath, path4));
3846
+ }
3847
+ const findIn = (scoped, patterns) => scoped.find((source) => patterns.some((pattern) => pattern.test(source.text())))?.label;
3848
+ return {
3849
+ findPackage: (location, name) => findIn(
3850
+ location ? sources.filter((source) => isUnderAnyFolder(source.path, [location])) : sources,
3851
+ packagePatterns(forkPath, location, name)
3852
+ ),
3853
+ findScript: (name) => findIn(sources, [scriptPattern(name)])
3854
+ };
3855
+ }
3856
+
3857
+ // src/services/packages.ts
3673
3858
  function parseComparableVersion(version) {
3674
3859
  const trimmed = version.trim();
3675
3860
  if (trimmed === "" || trimmed === "*" || trimmed.includes("workspace:") || trimmed.includes("catalog:") || trimmed.includes("file:") || trimmed.includes("link:") || trimmed.includes("git+") || trimmed.includes("github:") || trimmed.includes("http://") || trimmed.includes("https://") || trimmed.includes("||")) {
@@ -3697,33 +3882,71 @@ function isHigherVersion(upstreamVersion, forkVersion) {
3697
3882
  if (!upCoerced || !forkCoerced) return false;
3698
3883
  return compareVersions(upCoerced, forkCoerced) > 0;
3699
3884
  }
3700
- function safeMergeRecord(forkRecord, upstreamRecord, addOnly = false) {
3885
+ var DEPENDENCY_KEYS = [
3886
+ "dependencies",
3887
+ "devDependencies",
3888
+ "peerDependencies",
3889
+ "optionalDependencies"
3890
+ ];
3891
+ function mergeRecord(forkRecord, upstreamRecord, baseRecord, { bumpHigher = true, findUse } = {}) {
3701
3892
  if (!upstreamRecord) return void 0;
3702
3893
  const merged = { ...forkRecord || {} };
3894
+ const base = baseRecord || {};
3703
3895
  const added = [];
3704
- let changed = false;
3896
+ const updated = [];
3897
+ const removed = [];
3898
+ const kept = [];
3705
3899
  for (const [name, upstreamValue] of Object.entries(upstreamRecord)) {
3706
3900
  const forkValue = merged[name];
3707
3901
  if (forkValue === void 0) {
3902
+ if (name in base) continue;
3708
3903
  merged[name] = upstreamValue;
3709
3904
  added.push(name);
3710
- changed = true;
3711
- } else if (!addOnly && forkValue !== upstreamValue && isHigherVersion(upstreamValue, forkValue)) {
3905
+ } else if (forkValue === upstreamValue) {
3906
+ } else if (base[name] === forkValue) {
3712
3907
  merged[name] = upstreamValue;
3713
- changed = true;
3908
+ updated.push(name);
3909
+ } else if (bumpHigher && isHigherVersion(upstreamValue, forkValue)) {
3910
+ merged[name] = upstreamValue;
3911
+ updated.push(name);
3912
+ }
3913
+ }
3914
+ for (const [name, forkValue] of Object.entries(merged)) {
3915
+ if (name in upstreamRecord) continue;
3916
+ if (base[name] === void 0 || base[name] !== forkValue) continue;
3917
+ const usedAt = findUse?.(name);
3918
+ if (usedAt) {
3919
+ kept.push({ name, usedAt });
3920
+ continue;
3714
3921
  }
3922
+ delete merged[name];
3923
+ removed.push(name);
3715
3924
  }
3716
3925
  const sorted = Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)));
3717
- return { merged: sorted, changed, added };
3718
- }
3719
- function safeMergePnpm(forkPnpm, upstreamPnpm) {
3926
+ const changed = added.length + updated.length + removed.length > 0;
3927
+ return { merged: sorted, changed, added, updated, removed, kept };
3928
+ }
3929
+ function isSubpathMap(value) {
3930
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
3931
+ return Object.keys(value).every((name) => name.startsWith("."));
3932
+ }
3933
+ function safeMergeExports(forkExports, upstreamExports) {
3934
+ if (!isSubpathMap(upstreamExports)) return void 0;
3935
+ if (forkExports !== void 0 && !isSubpathMap(forkExports)) return void 0;
3936
+ const merged = { ...forkExports || {} };
3937
+ const added = Object.keys(upstreamExports).filter((name) => !(name in merged));
3938
+ for (const name of added) merged[name] = upstreamExports[name];
3939
+ return { merged, changed: added.length > 0, added };
3940
+ }
3941
+ function safeMergePnpm(forkPnpm, upstreamPnpm, basePnpm) {
3720
3942
  if (!upstreamPnpm) return void 0;
3721
3943
  const merged = { ...forkPnpm || {} };
3722
3944
  let changed = false;
3723
3945
  if (upstreamPnpm.overrides) {
3724
- const result = safeMergeRecord(
3946
+ const result = mergeRecord(
3725
3947
  merged.overrides,
3726
- upstreamPnpm.overrides
3948
+ upstreamPnpm.overrides,
3949
+ basePnpm?.overrides
3727
3950
  );
3728
3951
  if (result?.changed) {
3729
3952
  merged.overrides = result.merged;
@@ -3731,18 +3954,15 @@ function safeMergePnpm(forkPnpm, upstreamPnpm) {
3731
3954
  }
3732
3955
  }
3733
3956
  if (upstreamPnpm.patchedDependencies) {
3734
- const forkPatched = merged.patchedDependencies || {};
3735
- const upstreamPatched = upstreamPnpm.patchedDependencies;
3736
- for (const [name, value] of Object.entries(upstreamPatched)) {
3737
- if (!(name in forkPatched)) {
3738
- forkPatched[name] = value;
3739
- changed = true;
3740
- }
3741
- }
3742
- if (changed || !merged.patchedDependencies) {
3743
- merged.patchedDependencies = Object.fromEntries(
3744
- Object.entries(forkPatched).sort(([a], [b]) => a.localeCompare(b))
3745
- );
3957
+ const result = mergeRecord(
3958
+ merged.patchedDependencies,
3959
+ upstreamPnpm.patchedDependencies,
3960
+ basePnpm?.patchedDependencies,
3961
+ { bumpHigher: false }
3962
+ );
3963
+ if (result?.changed) {
3964
+ merged.patchedDependencies = result.merged;
3965
+ changed = true;
3746
3966
  }
3747
3967
  }
3748
3968
  if (upstreamPnpm.packageExtensions) {
@@ -3773,9 +3993,9 @@ function safeMergePnpm(forkPnpm, upstreamPnpm) {
3773
3993
  return { merged, changed };
3774
3994
  }
3775
3995
  function readPackageJson(filePath) {
3776
- if (!existsSync8(filePath)) return null;
3996
+ if (!existsSync9(filePath)) return null;
3777
3997
  try {
3778
- return JSON.parse(readFileSync5(filePath, "utf-8"));
3998
+ return JSON.parse(readFileSync6(filePath, "utf-8"));
3779
3999
  } catch {
3780
4000
  return null;
3781
4001
  }
@@ -3784,31 +4004,50 @@ function writePackageJson(filePath, data) {
3784
4004
  writeFileSync4(filePath, `${JSON.stringify(data, null, 2)}
3785
4005
  `, "utf-8");
3786
4006
  }
3787
- async function getUpstreamPackageJson(forkPath, upstreamRef, relativePath) {
3788
- const filePath = relativePath ? `${relativePath}/package.json` : "package.json";
4007
+ function packageJsonPath(relativePath) {
4008
+ return relativePath ? `${relativePath}/package.json` : "package.json";
4009
+ }
4010
+ async function readPackageJsonAtRef(forkPath, ref, relativePath) {
3789
4011
  try {
3790
- return JSON.parse(await git(["show", `${upstreamRef}:${filePath}`], forkPath));
4012
+ const raw = await git(["show", `${ref}:${packageJsonPath(relativePath)}`], forkPath);
4013
+ return { raw, data: JSON.parse(raw) };
3791
4014
  } catch {
3792
4015
  return null;
3793
4016
  }
3794
4017
  }
3795
4018
  async function discoverPackageLocations(forkPath, upstreamRef) {
3796
4019
  const stdout = await git(["ls-tree", "-r", "--name-only", upstreamRef], forkPath, { ignoreErrors: true });
3797
- const upstreamPaths = stdout.split("\n").filter((p) => p.endsWith("/package.json") || p === "package.json").map((p) => p === "package.json" ? "" : p.replace("/package.json", ""));
3798
- return upstreamPaths.filter((loc) => existsSync8(join14(forkPath, loc, "package.json")));
4020
+ return stdout.split("\n").filter((p) => p.endsWith("/package.json") || p === "package.json").map((p) => p === "package.json" ? "" : p.replace("/package.json", ""));
4021
+ }
4022
+ async function resolveMergeBase(forkPath, upstreamRef) {
4023
+ try {
4024
+ return (await getEffectiveMergeBase(forkPath, "HEAD", upstreamRef)).base || null;
4025
+ } catch {
4026
+ return null;
4027
+ }
3799
4028
  }
3800
- async function syncPackageJson(forkPath, upstreamRef, relativePath, keysToSync) {
4029
+ async function syncPackageJson(config, baseRef, usage, relativePath, keysToSync) {
4030
+ const { forkPath, upstreamRef } = config;
3801
4031
  const changes = [];
3802
- const pkgPath = join14(forkPath, relativePath, "package.json");
4032
+ const kept = [];
4033
+ const pkgRelPath = packageJsonPath(relativePath);
4034
+ const pkgPath = join15(forkPath, pkgRelPath);
3803
4035
  const forkPkg = readPackageJson(pkgPath);
3804
- const upstreamPkg = await getUpstreamPackageJson(forkPath, upstreamRef, relativePath);
3805
- if (!forkPkg || !upstreamPkg) {
3806
- return { updated: false, changes };
4036
+ const upstream = await readPackageJsonAtRef(forkPath, upstreamRef, relativePath);
4037
+ const basePkg = baseRef ? (await readPackageJsonAtRef(forkPath, baseRef, relativePath))?.data : void 0;
4038
+ if (!upstream) return { updated: false, changes, kept };
4039
+ if (!forkPkg) {
4040
+ const workspaceArrived = relativePath !== "" && existsSync9(join15(forkPath, relativePath));
4041
+ if (basePkg || !workspaceArrived || isIgnored(pkgRelPath, config)) return { updated: false, changes, kept };
4042
+ writeFileSync4(pkgPath, upstream.raw.endsWith("\n") ? upstream.raw : `${upstream.raw}
4043
+ `, "utf-8");
4044
+ return { updated: true, changes: ["copied from upstream (new workspace)"], kept };
3807
4045
  }
4046
+ const upstreamPkg = upstream.data;
3808
4047
  let updated = false;
3809
4048
  for (const key of keysToSync) {
3810
4049
  if (key === "pnpm") {
3811
- const result2 = safeMergePnpm(forkPkg.pnpm, upstreamPkg.pnpm);
4050
+ const result2 = safeMergePnpm(forkPkg.pnpm, upstreamPkg.pnpm, basePkg?.pnpm);
3812
4051
  if (result2?.changed) {
3813
4052
  forkPkg.pnpm = result2.merged;
3814
4053
  updated = true;
@@ -3819,59 +4058,88 @@ async function syncPackageJson(forkPath, upstreamRef, relativePath, keysToSync)
3819
4058
  if (key === "packageManager") {
3820
4059
  const upstreamValue2 = upstreamPkg[key];
3821
4060
  const forkValue2 = forkPkg[key];
4061
+ const baseValue2 = basePkg?.[key];
3822
4062
  if (upstreamValue2 && !forkValue2) {
4063
+ if (baseValue2) continue;
3823
4064
  forkPkg[key] = upstreamValue2;
3824
4065
  updated = true;
3825
4066
  changes.push(`${key}: added`);
3826
- } else if (upstreamValue2 && forkValue2 && upstreamValue2 !== forkValue2 && isHigherVersion(upstreamValue2, forkValue2)) {
4067
+ } else if (upstreamValue2 && forkValue2 && upstreamValue2 !== forkValue2 && (forkValue2 === baseValue2 || isHigherVersion(upstreamValue2, forkValue2))) {
3827
4068
  forkPkg[key] = upstreamValue2;
3828
4069
  updated = true;
3829
- changes.push(`${key}: bumped`);
4070
+ changes.push(`${key}: updated`);
4071
+ }
4072
+ continue;
4073
+ }
4074
+ if (key === "exports") {
4075
+ const result2 = safeMergeExports(forkPkg.exports, upstreamPkg.exports);
4076
+ if (result2?.changed) {
4077
+ forkPkg.exports = result2.merged;
4078
+ updated = true;
4079
+ changes.push(...result2.added.map((name) => `exports.${name}: added`));
3830
4080
  }
3831
4081
  continue;
3832
4082
  }
3833
4083
  const forkValue = forkPkg[key];
3834
4084
  const upstreamValue = upstreamPkg[key];
3835
- const addOnly = key === "scripts";
3836
- const result = safeMergeRecord(forkValue, upstreamValue, addOnly);
4085
+ const baseValue = basePkg?.[key];
4086
+ let findUse;
4087
+ if (usage && key === "scripts") findUse = (name) => usage.findScript(name);
4088
+ else if (usage && DEPENDENCY_KEYS.includes(key)) findUse = (name) => usage.findPackage(relativePath, name);
4089
+ const result = mergeRecord(forkValue, upstreamValue, baseValue, { bumpHigher: key !== "scripts", findUse });
4090
+ kept.push(...(result?.kept ?? []).map(({ name, usedAt }) => `${key}.${name}: used in ${usedAt}`));
3837
4091
  if (result?.changed) {
3838
4092
  forkPkg[key] = result.merged;
3839
4093
  updated = true;
3840
- if (addOnly) {
3841
- changes.push(...result.added.map((name) => `scripts.${name}: added`));
3842
- } else {
3843
- changes.push(`${key}: merged`);
3844
- }
4094
+ changes.push(
4095
+ ...result.added.map((name) => `${key}.${name}: added`),
4096
+ ...result.updated.map((name) => `${key}.${name}: updated`),
4097
+ ...result.removed.map((name) => `${key}.${name}: removed`)
4098
+ );
3845
4099
  }
3846
4100
  }
3847
4101
  if (updated) {
3848
4102
  writePackageJson(pkgPath, forkPkg);
3849
4103
  }
3850
- return { updated, changes };
4104
+ return { updated, changes, kept };
3851
4105
  }
3852
4106
  async function runPackages(config, options = {}) {
3853
4107
  createSpinner("syncing package.json files...");
3854
4108
  const keysToSync = config.settings.packageJsonSync || ["dependencies", "devDependencies"];
3855
4109
  const conflictedSet = new Set(options.conflictedFiles ?? []);
4110
+ const baseRef = await resolveMergeBase(config.forkPath, config.upstreamRef);
4111
+ const usage = baseRef ? await loadForkUsage(config.forkPath, config.upstreamRef, baseRef) : null;
3856
4112
  const locations = await discoverPackageLocations(config.forkPath, config.upstreamRef);
3857
- let changedCount = 0;
4113
+ const reports = [];
3858
4114
  const skipped = [];
3859
4115
  for (const location of locations) {
3860
- const pkgRelPath = location ? `${location}/package.json` : "package.json";
4116
+ const pkgRelPath = packageJsonPath(location);
3861
4117
  if (conflictedSet.has(pkgRelPath)) {
3862
4118
  skipped.push(pkgRelPath);
3863
4119
  continue;
3864
4120
  }
3865
4121
  spinnerText(`syncing ${location || "root"}/package.json...`);
3866
- const { updated } = await syncPackageJson(config.forkPath, config.upstreamRef, location, keysToSync);
3867
- if (updated) {
3868
- changedCount += 1;
3869
- }
4122
+ const { updated, changes, kept: kept2 } = await syncPackageJson(config, baseRef, usage, location, keysToSync);
4123
+ if (updated || kept2.length > 0) reports.push({ path: pkgRelPath, changes, kept: kept2 });
3870
4124
  }
4125
+ const changedCount = reports.filter(({ changes }) => changes.length > 0).length;
3871
4126
  spinnerSuccess(
3872
4127
  "package sync complete",
3873
4128
  `${changedCount} package.json${changedCount === 1 ? " was" : "s were"} changed`
3874
4129
  );
4130
+ for (const { path: path4, changes } of reports) {
4131
+ if (changes.length === 0) continue;
4132
+ console.info(` ${path4}`);
4133
+ for (const change of changes) console.info(` ${colors_default.dim(change)}`);
4134
+ }
4135
+ const kept = reports.flatMap(({ path: path4, kept: kept2 }) => kept2.map((entry) => `${path4} ${entry}`));
4136
+ if (kept.length > 0) {
4137
+ console.info();
4138
+ console.warn(
4139
+ `${warningMark} kept ${kept.length} ${kept.length === 1 ? "entry" : "entries"} upstream dropped, the fork still uses ${kept.length === 1 ? "it" : "them"}:`
4140
+ );
4141
+ for (const entry of kept) console.warn(` ${colors_default.dim("\u2192")} ${entry}`);
4142
+ }
3875
4143
  if (skipped.length > 0) {
3876
4144
  console.info();
3877
4145
  console.warn(
@@ -4356,7 +4624,7 @@ async function runForks(config) {
4356
4624
 
4357
4625
  // src/services/stats.ts
4358
4626
  import { readFile as readFile2 } from "fs/promises";
4359
- import { join as join16 } from "path";
4627
+ import { join as join17 } from "path";
4360
4628
 
4361
4629
  // src/utils/coverage-utils.ts
4362
4630
  import { spawnSync as spawnSync4 } from "child_process";
@@ -4367,6 +4635,7 @@ function summaryPath(forkPath) {
4367
4635
  return path3.join(forkPath, ".coverage", "coverage-summary.json");
4368
4636
  }
4369
4637
  function colorPct(pct) {
4638
+ if (typeof pct !== "number" || !Number.isFinite(pct)) return colors_default.dim("n/a");
4370
4639
  const label = `${pct.toFixed(2)}%`;
4371
4640
  if (pct >= 80) return colors_default.green(label);
4372
4641
  if (pct >= 50) return colors_default.yellow(label);
@@ -4429,6 +4698,11 @@ function printCoverageSummary(forkPath, options = {}) {
4429
4698
  if (stale) {
4430
4699
  console.info(colors_default.yellow(" \u26A0 data is older than 7 days \u2014 run `pnpm test` to refresh"));
4431
4700
  }
4701
+ if (!Object.keys(summary).some((key) => key !== "total")) {
4702
+ console.info(colors_default.yellow(" no coverage data found \u2014 run `pnpm test` to regenerate"));
4703
+ console.info();
4704
+ return;
4705
+ }
4432
4706
  const total = summary.total;
4433
4707
  console.info(
4434
4708
  ` ${colors_default.bold("overall")} lines ${colorPct(total.lines.pct)} ${colors_default.dim(`(${total.lines.covered}/${total.lines.total})`)} funcs ${colorPct(total.functions.pct)} branches ${colorPct(total.branches.pct)}`
@@ -4444,7 +4718,7 @@ function printCoverageSummary(forkPath, options = {}) {
4444
4718
  // src/services/stats.ts
4445
4719
  var sourceExtensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mjs", "cjs", "json", "jsonc", "css", "html"]);
4446
4720
  async function getWorkspacePackages(forkPath) {
4447
- const content = await readFile2(join16(forkPath, "pnpm-workspace.yaml"), "utf-8");
4721
+ const content = await readFile2(join17(forkPath, "pnpm-workspace.yaml"), "utf-8");
4448
4722
  return parseYamlBlockList(content, "packages").map((pattern) => {
4449
4723
  if (pattern.endsWith("/*")) {
4450
4724
  const base = pattern.slice(0, -2);
@@ -4607,7 +4881,7 @@ function getForkPath() {
4607
4881
  const envPath = getEnv("CELLA_FORK_PATH");
4608
4882
  if (envPath) {
4609
4883
  const resolved = resolve6(envPath);
4610
- if (!existsSync10(resolved) || !statSync2(resolved).isDirectory()) {
4884
+ if (!existsSync11(resolved) || !statSync3(resolved).isDirectory()) {
4611
4885
  throw new Error(`CELLA_FORK_PATH is not a valid directory: ${resolved}`);
4612
4886
  }
4613
4887
  return resolved;
@@ -4619,7 +4893,7 @@ function getForkPath() {
4619
4893
  return cwd;
4620
4894
  }
4621
4895
  async function preflight(forkPath) {
4622
- if (!existsSync10(join17(forkPath, ".git"))) {
4896
+ if (!existsSync11(join18(forkPath, ".git"))) {
4623
4897
  throw new Error(`not a git repository: ${forkPath}`);
4624
4898
  }
4625
4899
  }