@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/README.md +34 -6
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +394 -120
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
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
|
|
5
|
-
import { join as
|
|
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.
|
|
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: "
|
|
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.
|
|
270
|
-
"@pierre/diffs": "^1.
|
|
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.
|
|
274
|
+
zod: "^4.6.5"
|
|
274
275
|
},
|
|
275
276
|
devDependencies: {
|
|
276
|
-
"@biomejs/biome": "^2.5.
|
|
277
|
-
"@commitlint/cli": "^21.2.
|
|
278
|
-
"@commitlint/config-conventional": "^21.2.
|
|
279
|
-
"@evilmartians/lefthook": "^2.1.
|
|
280
|
-
"@types/node": "
|
|
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.
|
|
285
|
+
tsx: "^4.23.15",
|
|
285
286
|
typescript: "^6.0.3",
|
|
286
|
-
vitest: "^4.1.
|
|
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
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const
|
|
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 =
|
|
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(` ${
|
|
582
|
+
console.info(` ${icon} ${file.path}${dateInfo}${options.suffix?.(file) ?? ""}`);
|
|
584
583
|
}
|
|
585
|
-
if (
|
|
586
|
-
console.info(colors_default.dim(` ... + ${
|
|
584
|
+
if (files.length > maxLines) {
|
|
585
|
+
console.info(colors_default.dim(` ... + ${files.length - maxLines} more`));
|
|
587
586
|
}
|
|
588
|
-
if (options
|
|
587
|
+
if (options.hint) {
|
|
589
588
|
console.info();
|
|
590
|
-
|
|
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
|
-
|
|
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
|
|
1830
|
-
if (forkChanged &&
|
|
1904
|
+
const upstreamChanged2 = upstreamHash !== baseHash;
|
|
1905
|
+
if (forkChanged && upstreamChanged2) {
|
|
1831
1906
|
status = fileIsPinned ? "pinned" : "diverged";
|
|
1832
|
-
} else if (forkChanged && !
|
|
1907
|
+
} else if (forkChanged && !upstreamChanged2) {
|
|
1833
1908
|
status = fileIsPinned ? "ahead" : predicates.hard ? "behind" : "drifted";
|
|
1834
|
-
} else if (!forkChanged &&
|
|
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
|
-
|
|
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
|
|
2782
|
-
const relativePath = path2.relative(forkPath,
|
|
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://${
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3711
|
-
} else if (
|
|
3905
|
+
} else if (forkValue === upstreamValue) {
|
|
3906
|
+
} else if (base[name] === forkValue) {
|
|
3712
3907
|
merged[name] = upstreamValue;
|
|
3713
|
-
|
|
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
|
-
|
|
3718
|
-
}
|
|
3719
|
-
|
|
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 =
|
|
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
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
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 (!
|
|
3996
|
+
if (!existsSync9(filePath)) return null;
|
|
3777
3997
|
try {
|
|
3778
|
-
return JSON.parse(
|
|
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
|
-
|
|
3788
|
-
|
|
4007
|
+
function packageJsonPath(relativePath) {
|
|
4008
|
+
return relativePath ? `${relativePath}/package.json` : "package.json";
|
|
4009
|
+
}
|
|
4010
|
+
async function readPackageJsonAtRef(forkPath, ref, relativePath) {
|
|
3789
4011
|
try {
|
|
3790
|
-
|
|
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
|
-
|
|
3798
|
-
|
|
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(
|
|
4029
|
+
async function syncPackageJson(config, baseRef, usage, relativePath, keysToSync) {
|
|
4030
|
+
const { forkPath, upstreamRef } = config;
|
|
3801
4031
|
const changes = [];
|
|
3802
|
-
const
|
|
4032
|
+
const kept = [];
|
|
4033
|
+
const pkgRelPath = packageJsonPath(relativePath);
|
|
4034
|
+
const pkgPath = join15(forkPath, pkgRelPath);
|
|
3803
4035
|
const forkPkg = readPackageJson(pkgPath);
|
|
3804
|
-
const
|
|
3805
|
-
|
|
3806
|
-
|
|
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}:
|
|
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
|
|
3836
|
-
|
|
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
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
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
|
-
|
|
4113
|
+
const reports = [];
|
|
3858
4114
|
const skipped = [];
|
|
3859
4115
|
for (const location of locations) {
|
|
3860
|
-
const pkgRelPath = location
|
|
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
|
|
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
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
4896
|
+
if (!existsSync11(join18(forkPath, ".git"))) {
|
|
4623
4897
|
throw new Error(`not a git repository: ${forkPath}`);
|
|
4624
4898
|
}
|
|
4625
4899
|
}
|