@cellajs/cli 0.3.0 → 0.3.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.
- package/README.md +25 -2
- package/dist/config.d.ts +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +200 -53
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cella-cli.ts
|
|
4
4
|
import { existsSync as existsSync13, statSync as statSync3 } from "fs";
|
|
5
|
-
import { join as join21, resolve as
|
|
5
|
+
import { join as join21, resolve as resolve8 } from "path";
|
|
6
6
|
import process9 from "process";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
@@ -220,7 +220,7 @@ import process4 from "process";
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@cellajs/cli",
|
|
223
|
-
version: "0.3.
|
|
223
|
+
version: "0.3.1",
|
|
224
224
|
private: false,
|
|
225
225
|
license: "MIT",
|
|
226
226
|
description: "CLI for managing your monorepo and syncing with upstream Cella.",
|
|
@@ -287,7 +287,7 @@ var package_default = {
|
|
|
287
287
|
rimraf: "^6.1.3",
|
|
288
288
|
tsup: "^8.5.1",
|
|
289
289
|
tsx: "^4.23.15",
|
|
290
|
-
typescript: "
|
|
290
|
+
typescript: "6.0.3",
|
|
291
291
|
vitest: "^5.0.3"
|
|
292
292
|
},
|
|
293
293
|
publishConfig: {
|
|
@@ -812,7 +812,7 @@ import { dirname as dirname3, join as join6 } from "path";
|
|
|
812
812
|
import { execFile } from "child_process";
|
|
813
813
|
import { existsSync as existsSync3 } from "fs";
|
|
814
814
|
import { mkdir as mkdir2, readdir, rmdir, unlink } from "fs/promises";
|
|
815
|
-
import { dirname as dirname2, join as join5 } from "path";
|
|
815
|
+
import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
|
|
816
816
|
import { promisify } from "util";
|
|
817
817
|
|
|
818
818
|
// src/utils/manifest.ts
|
|
@@ -872,8 +872,26 @@ async function getCurrentBranch(cwd) {
|
|
|
872
872
|
async function switchBranch(cwd, branch) {
|
|
873
873
|
await git(["switch", branch], cwd);
|
|
874
874
|
}
|
|
875
|
+
async function switchDetached(cwd, ref) {
|
|
876
|
+
await git(["switch", "--detach", ref], cwd);
|
|
877
|
+
}
|
|
875
878
|
async function createBranchFrom(cwd, branch, startPoint) {
|
|
876
|
-
await git(["switch", "-c", branch, startPoint], cwd);
|
|
879
|
+
await git(["switch", "-c", branch, "--no-track", startPoint], cwd);
|
|
880
|
+
}
|
|
881
|
+
async function branchExists(cwd, branch) {
|
|
882
|
+
return await git(["rev-parse", "-q", "--verify", `refs/heads/${branch}`], cwd, { ignoreErrors: true }) !== "";
|
|
883
|
+
}
|
|
884
|
+
async function getBranchWorktree(cwd, branch) {
|
|
885
|
+
const output = await git(["worktree", "list", "--porcelain"], cwd, { ignoreErrors: true });
|
|
886
|
+
let worktree = null;
|
|
887
|
+
for (const line of output.split("\n")) {
|
|
888
|
+
if (line.startsWith("worktree ")) worktree = line.slice("worktree ".length);
|
|
889
|
+
else if (line === `branch refs/heads/${branch}`) return worktree;
|
|
890
|
+
}
|
|
891
|
+
return null;
|
|
892
|
+
}
|
|
893
|
+
async function fastForwardBranch(cwd, branch, ref) {
|
|
894
|
+
await git(["fetch", "--quiet", ".", `${ref}:refs/heads/${branch}`], cwd);
|
|
877
895
|
}
|
|
878
896
|
async function deleteBranch(cwd, branch) {
|
|
879
897
|
await git(["branch", "-D", branch], cwd, { ignoreErrors: true });
|
|
@@ -881,14 +899,21 @@ async function deleteBranch(cwd, branch) {
|
|
|
881
899
|
async function pullFastForward(cwd) {
|
|
882
900
|
await git(["pull", "--ff-only"], cwd, { ignoreErrors: true, skipEditor: true });
|
|
883
901
|
}
|
|
884
|
-
async function
|
|
885
|
-
const upstream = await git(["rev-parse", "--abbrev-ref", "--symbolic-full-name",
|
|
902
|
+
async function getBranchUpstream(cwd, branch = "") {
|
|
903
|
+
const upstream = await git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", `${branch}@{upstream}`], cwd, {
|
|
886
904
|
ignoreErrors: true
|
|
887
905
|
});
|
|
906
|
+
return upstream || null;
|
|
907
|
+
}
|
|
908
|
+
async function getUpstreamStatus(cwd, branch) {
|
|
909
|
+
const local = branch ? `refs/heads/${branch}` : "HEAD";
|
|
910
|
+
const upstream = await getBranchUpstream(cwd, branch);
|
|
888
911
|
if (!upstream) return { upstream: null, ahead: 0, behind: 0 };
|
|
889
912
|
const remote = upstream.split("/")[0];
|
|
890
913
|
await git(["fetch", remote], cwd, { ignoreErrors: true, skipEditor: true });
|
|
891
|
-
const counts = await git(["rev-list", "--left-right", "--count",
|
|
914
|
+
const counts = await git(["rev-list", "--left-right", "--count", `${local}...${upstream}`], cwd, {
|
|
915
|
+
ignoreErrors: true
|
|
916
|
+
});
|
|
892
917
|
const [ahead, behind] = counts.split(/\s+/).map((n) => Number.parseInt(n, 10) || 0);
|
|
893
918
|
return { upstream, ahead: ahead ?? 0, behind: behind ?? 0 };
|
|
894
919
|
}
|
|
@@ -907,16 +932,19 @@ async function flattenBranch(cwd, baseRef, message) {
|
|
|
907
932
|
}
|
|
908
933
|
async function commitSquash(cwd, message) {
|
|
909
934
|
for (const file of ["MERGE_HEAD", "MERGE_MSG", "MERGE_MODE"]) {
|
|
910
|
-
const path4 =
|
|
935
|
+
const path4 = await gitPath(cwd, file);
|
|
911
936
|
if (existsSync3(path4)) await unlink(path4);
|
|
912
937
|
}
|
|
913
938
|
await git(["commit", "-m", message], cwd, { skipEditor: true });
|
|
914
939
|
}
|
|
940
|
+
async function gitPath(cwd, file) {
|
|
941
|
+
return resolve3(cwd, await git(["rev-parse", "--git-path", file], cwd));
|
|
942
|
+
}
|
|
915
943
|
async function stageAll(cwd) {
|
|
916
944
|
await git(["add", "-A"], cwd);
|
|
917
945
|
}
|
|
918
|
-
function mergeInProgress(cwd) {
|
|
919
|
-
return
|
|
946
|
+
async function mergeInProgress(cwd) {
|
|
947
|
+
return await git(["rev-parse", "-q", "--verify", "MERGE_HEAD"], cwd, { ignoreErrors: true }) !== "";
|
|
920
948
|
}
|
|
921
949
|
async function getShortSha(cwd, ref) {
|
|
922
950
|
return git(["rev-parse", "--short", ref], cwd);
|
|
@@ -957,6 +985,33 @@ async function resolveLatestReleaseTag(cwd, remoteName) {
|
|
|
957
985
|
}
|
|
958
986
|
return null;
|
|
959
987
|
}
|
|
988
|
+
async function resolveUpstreamCommit(cwd, remoteName, ref) {
|
|
989
|
+
if (ref.startsWith("-")) return null;
|
|
990
|
+
const tagRef = `refs/${remoteName}/tags/${ref}`;
|
|
991
|
+
for (const candidate of [`refs/remotes/${remoteName}/${ref}`, tagRef, ref]) {
|
|
992
|
+
const sha = await git(["rev-parse", "-q", "--verify", `${candidate}^{commit}`], cwd, { ignoreErrors: true });
|
|
993
|
+
if (!sha) continue;
|
|
994
|
+
const release = candidate === tagRef && /^v\d[\w.+-]*$/.test(ref) ? { tag: ref, ref: tagRef } : void 0;
|
|
995
|
+
return { sha, release };
|
|
996
|
+
}
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
async function isPublishedUpstream(cwd, remoteName, branchRef, sha) {
|
|
1000
|
+
const containing = await git(
|
|
1001
|
+
[
|
|
1002
|
+
"for-each-ref",
|
|
1003
|
+
"--count=1",
|
|
1004
|
+
"--format=%(refname)",
|
|
1005
|
+
"--contains",
|
|
1006
|
+
sha,
|
|
1007
|
+
`refs/remotes/${branchRef}`,
|
|
1008
|
+
`refs/${remoteName}/tags/v*`
|
|
1009
|
+
],
|
|
1010
|
+
cwd,
|
|
1011
|
+
{ ignoreErrors: true }
|
|
1012
|
+
);
|
|
1013
|
+
return containing !== "";
|
|
1014
|
+
}
|
|
960
1015
|
async function getCommitInfo(cwd, ref) {
|
|
961
1016
|
const format = "%H%n%s%n%ar";
|
|
962
1017
|
const output = await git(["log", "-1", `--format=${format}`, ref], cwd);
|
|
@@ -1586,6 +1641,7 @@ function readOptions(opts) {
|
|
|
1586
1641
|
hard: opts.hard === true,
|
|
1587
1642
|
unpinned: opts.unpinned === true,
|
|
1588
1643
|
track: opts.track === "release" || opts.track === "branch" ? opts.track : void 0,
|
|
1644
|
+
ref: typeof opts.ref === "string" && opts.ref ? opts.ref : void 0,
|
|
1589
1645
|
force: opts.force === true,
|
|
1590
1646
|
checkOverrides: opts.checkOverrides === true,
|
|
1591
1647
|
coverage: opts.coverage === true,
|
|
@@ -1605,6 +1661,7 @@ var serviceDefinitions = [
|
|
|
1605
1661
|
{ flags: "--json", description: "machine-readable output for tooling/agents" },
|
|
1606
1662
|
{ flags: "--scope <scope>", description: "analyze scope for --list/--json: all|risk|protected" },
|
|
1607
1663
|
{ flags: "--track <mode>", description: "override upstream tracking for this run: release|branch" },
|
|
1664
|
+
{ flags: "--ref <ref>", description: "pin the upstream commit for this run: sha, release tag or branch" },
|
|
1608
1665
|
{ flags: "--diff <path>", description: "print unified diff for one file, then exit" },
|
|
1609
1666
|
{ flags: "--open-diff <path>", description: "open a browser diff for one file, then exit" }
|
|
1610
1667
|
],
|
|
@@ -1617,7 +1674,8 @@ var serviceDefinitions = [
|
|
|
1617
1674
|
{ flags: "--log", description: "write complete file list to cella-sync.log" },
|
|
1618
1675
|
{ flags: "--hard", description: "overwrite drifted files with upstream version (aggressive realignment)" },
|
|
1619
1676
|
{ flags: "--unpinned", description: "ignore pinned files (except package.json) to resurface upstream changes" },
|
|
1620
|
-
{ flags: "--track <mode>", description: "override upstream tracking for this run: release|branch" }
|
|
1677
|
+
{ flags: "--track <mode>", description: "override upstream tracking for this run: release|branch" },
|
|
1678
|
+
{ flags: "--ref <ref>", description: "pin the upstream commit for this run: sha, release tag or branch" }
|
|
1621
1679
|
],
|
|
1622
1680
|
includeInMenu: (context) => !context.isUpstreamRepo,
|
|
1623
1681
|
menuDescription: () => "merge upstream changes + sync package.json"
|
|
@@ -1716,6 +1774,7 @@ function buildProgram(setSelection) {
|
|
|
1716
1774
|
" $ cella sync --hard",
|
|
1717
1775
|
" $ cella sync --unpinned",
|
|
1718
1776
|
" $ cella sync --track branch",
|
|
1777
|
+
" $ cella sync --ref 4f7d87c",
|
|
1719
1778
|
" $ cella migrate --mark 20261002T0614-config-switch",
|
|
1720
1779
|
" $ cella audit --check-overrides",
|
|
1721
1780
|
" $ cella contributions --fork raak --json"
|
|
@@ -1894,14 +1953,14 @@ import { join as join11 } from "path";
|
|
|
1894
1953
|
import { createHash } from "crypto";
|
|
1895
1954
|
import { existsSync as existsSync6, rmSync } from "fs";
|
|
1896
1955
|
import { tmpdir as tmpdir2 } from "os";
|
|
1897
|
-
import { basename as basename3, join as join10, resolve as
|
|
1956
|
+
import { basename as basename3, join as join10, resolve as resolve4 } from "path";
|
|
1898
1957
|
import process7 from "process";
|
|
1899
1958
|
var WORKTREE_PREFIXES = {
|
|
1900
1959
|
sync: "cella-sync-",
|
|
1901
1960
|
view: "cella-view-"
|
|
1902
1961
|
};
|
|
1903
1962
|
function buildWorktreePath(kind, repoPath) {
|
|
1904
|
-
const key = createHash("sha256").update(
|
|
1963
|
+
const key = createHash("sha256").update(resolve4(repoPath)).digest("hex").slice(0, 8);
|
|
1905
1964
|
return join10(tmpdir2(), `${WORKTREE_PREFIXES[kind]}${basename3(repoPath)}-${key}`);
|
|
1906
1965
|
}
|
|
1907
1966
|
function legacyWorktreePath(kind, repoPath) {
|
|
@@ -2511,6 +2570,20 @@ function formatLocalCheckoutDetail(forkPath, branch, headSha, changeCount, upstr
|
|
|
2511
2570
|
const workingTree = changeCount === 0 ? " working tree clean" : ` ${changeCount} uncommitted change${changeCount === 1 ? "" : "s"} in working tree, not included in analysis`;
|
|
2512
2571
|
return [`${forkPath}`, ` ${branch} @ ${headSha}${tracking}`, workingTree].join("\n");
|
|
2513
2572
|
}
|
|
2573
|
+
async function resolvePinnedRef(forkPath, config, remoteName, branchRef, ref) {
|
|
2574
|
+
const resolved = await resolveUpstreamCommit(forkPath, remoteName, ref);
|
|
2575
|
+
if (!resolved) {
|
|
2576
|
+
throw new Error(
|
|
2577
|
+
`--ref '${ref}' does not resolve to a commit. Pass a commit sha, a release tag (v*) or a branch on ${config.settings.upstreamUrl}.`
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
if (!await isPublishedUpstream(forkPath, remoteName, branchRef, resolved.sha)) {
|
|
2581
|
+
throw new Error(
|
|
2582
|
+
`--ref '${ref}' (${resolved.sha.slice(0, 7)}) is not on ${branchRef} or in an upstream release (v*). Sync only to commits upstream has published there.`
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
return resolved.release ? { ref: resolved.release.ref, releaseTag: resolved.release.tag } : { ref: resolved.sha };
|
|
2586
|
+
}
|
|
2514
2587
|
async function prepareUpstream(config, onProgress, onStep) {
|
|
2515
2588
|
const { forkPath } = config;
|
|
2516
2589
|
onProgress?.("checking local checkout...");
|
|
@@ -2530,7 +2603,12 @@ async function prepareUpstream(config, onProgress, onStep) {
|
|
|
2530
2603
|
await fetch2(forkPath, remoteName);
|
|
2531
2604
|
let upstreamRef = branchRef;
|
|
2532
2605
|
let releaseTag;
|
|
2533
|
-
if (
|
|
2606
|
+
if (config.ref) {
|
|
2607
|
+
await fetchUpstreamTags(forkPath, remoteName);
|
|
2608
|
+
const pinned = await resolvePinnedRef(forkPath, config, remoteName, branchRef, config.ref);
|
|
2609
|
+
upstreamRef = pinned.ref;
|
|
2610
|
+
releaseTag = pinned.releaseTag;
|
|
2611
|
+
} else if (track === "release") {
|
|
2534
2612
|
await fetchUpstreamTags(forkPath, remoteName);
|
|
2535
2613
|
const latest = await resolveLatestReleaseTag(forkPath, remoteName);
|
|
2536
2614
|
if (!latest) {
|
|
@@ -2541,8 +2619,9 @@ async function prepareUpstream(config, onProgress, onStep) {
|
|
|
2541
2619
|
upstreamRef = latest.ref;
|
|
2542
2620
|
releaseTag = latest.tag;
|
|
2543
2621
|
}
|
|
2622
|
+
const upstreamLabel = releaseTag ?? (config.ref ? `--ref ${config.ref}` : upstreamRef);
|
|
2544
2623
|
config.upstreamRef = upstreamRef;
|
|
2545
|
-
onStep?.("remote configured", `${
|
|
2624
|
+
onStep?.("remote configured", `${upstreamLabel} \u2192 ${config.settings.upstreamUrl}`);
|
|
2546
2625
|
const cliRange = await readUpstreamCliRange(forkPath, upstreamRef);
|
|
2547
2626
|
const cliMismatch = cliRange ? cliVersionMismatch(VERSION, cliRange) : null;
|
|
2548
2627
|
if (cliMismatch) throw new Error(cliMismatch);
|
|
@@ -2550,8 +2629,20 @@ async function prepareUpstream(config, onProgress, onStep) {
|
|
|
2550
2629
|
await ensureSyncBase(forkPath, "HEAD", upstreamRef);
|
|
2551
2630
|
const upstreamGitHubUrl = getGitHubBaseUrl(config.settings.upstreamUrl) ?? void 0;
|
|
2552
2631
|
const aggressive = config.hard === true || config.unpinned === true;
|
|
2553
|
-
const
|
|
2632
|
+
const syncPoint = (await getEffectiveMergeBase(forkPath, "HEAD", upstreamRef)).base;
|
|
2633
|
+
const mergeBase = aggressive ? await getMergeBase(forkPath, "HEAD", upstreamRef) : syncPoint;
|
|
2554
2634
|
const upstreamCommit = await getCommitInfo(forkPath, upstreamRef);
|
|
2635
|
+
if (upstreamCommit.hash !== syncPoint && await isAncestor(forkPath, upstreamCommit.hash, syncPoint)) {
|
|
2636
|
+
const [upstreamShort, syncShort] = await Promise.all([
|
|
2637
|
+
getShortSha(forkPath, upstreamCommit.hash),
|
|
2638
|
+
getShortSha(forkPath, syncPoint)
|
|
2639
|
+
]);
|
|
2640
|
+
const hint = config.ref ? "Pick a newer --ref." : releaseTag ? `Nothing to sync until a release past ${syncShort}; to follow the tip instead, run with --track branch.` : `Nothing to sync until '${branchRef}' moves past ${syncShort}.`;
|
|
2641
|
+
throw new Error(
|
|
2642
|
+
`upstream ${upstreamLabel} (${upstreamShort}) is behind the last sync point ${syncShort}: the fork already has it, and syncing it would revert the upstream changes after it.
|
|
2643
|
+
${hint}`
|
|
2644
|
+
);
|
|
2645
|
+
}
|
|
2555
2646
|
const commitCount = await countCommitsBetween(forkPath, mergeBase, upstreamRef);
|
|
2556
2647
|
const commitListMax = 50;
|
|
2557
2648
|
const commitSkip = commitCount > commitListMax ? commitCount - commitListMax : 0;
|
|
@@ -3776,7 +3867,7 @@ import {
|
|
|
3776
3867
|
import { select as select2 } from "@inquirer/prompts";
|
|
3777
3868
|
|
|
3778
3869
|
// src/services/contrib-core.ts
|
|
3779
|
-
import { dirname as dirname5, join as join15, resolve as
|
|
3870
|
+
import { dirname as dirname5, join as join15, resolve as resolve5 } from "path";
|
|
3780
3871
|
async function listFilesAtRef(repoPath, ref) {
|
|
3781
3872
|
const raw = await git(["ls-tree", "-r", "--name-only", ref], repoPath);
|
|
3782
3873
|
return new Set(raw.split("\n").filter(Boolean));
|
|
@@ -3839,7 +3930,7 @@ async function readBlobInfo(repoPath, ref, filePath) {
|
|
|
3839
3930
|
}
|
|
3840
3931
|
async function buildContribBranch(repoPath, baseRef, forkRef, detection, forkName) {
|
|
3841
3932
|
const branch = `contrib/${forkName}`;
|
|
3842
|
-
const gitDir =
|
|
3933
|
+
const gitDir = resolve5(repoPath, await git(["rev-parse", "--git-dir"], repoPath));
|
|
3843
3934
|
const tmpIndex = join15(gitDir, `tmp-contrib-index-${forkName.replace(/[^\w.-]/g, "_")}`);
|
|
3844
3935
|
const indexEnv = { GIT_INDEX_FILE: tmpIndex };
|
|
3845
3936
|
try {
|
|
@@ -3884,14 +3975,14 @@ ${commitBody}`;
|
|
|
3884
3975
|
|
|
3885
3976
|
// src/services/fork-utils.ts
|
|
3886
3977
|
import { existsSync as existsSync9 } from "fs";
|
|
3887
|
-
import { dirname as dirname6, resolve as
|
|
3978
|
+
import { dirname as dirname6, resolve as resolve6 } from "path";
|
|
3888
3979
|
async function resolveForkBasePath(repoPath) {
|
|
3889
3980
|
const commonDir = await git(["rev-parse", "--git-common-dir"], repoPath, { ignoreErrors: true });
|
|
3890
|
-
return commonDir ? dirname6(
|
|
3981
|
+
return commonDir ? dirname6(resolve6(repoPath, commonDir)) : repoPath;
|
|
3891
3982
|
}
|
|
3892
3983
|
function validateForkPath(fork, basePath, requireConfig = false) {
|
|
3893
|
-
const resolvedPath =
|
|
3894
|
-
if (!resolvedPath.startsWith(
|
|
3984
|
+
const resolvedPath = resolve6(basePath, fork.localPath);
|
|
3985
|
+
if (!resolvedPath.startsWith(resolve6(basePath, ".."))) {
|
|
3895
3986
|
return { fork, resolvedPath, valid: false, error: "path resolves outside parent directory" };
|
|
3896
3987
|
}
|
|
3897
3988
|
if (!existsSync9(resolvedPath)) return { fork, resolvedPath, valid: false, error: "path does not exist" };
|
|
@@ -4259,7 +4350,7 @@ async function runContributions(config) {
|
|
|
4259
4350
|
}
|
|
4260
4351
|
|
|
4261
4352
|
// src/services/forks.ts
|
|
4262
|
-
import { resolve as
|
|
4353
|
+
import { resolve as resolve7 } from "path";
|
|
4263
4354
|
import process8 from "process";
|
|
4264
4355
|
import { Separator as Separator2, select as select4 } from "@inquirer/prompts";
|
|
4265
4356
|
|
|
@@ -4517,6 +4608,26 @@ function safeMergePnpm(forkPnpm, upstreamPnpm, basePnpm) {
|
|
|
4517
4608
|
}
|
|
4518
4609
|
return { merged, changed };
|
|
4519
4610
|
}
|
|
4611
|
+
function mergeModuleType(forkType, upstreamType, baseType) {
|
|
4612
|
+
const asString = (value) => typeof value === "string" ? value : void 0;
|
|
4613
|
+
const fork = asString(forkType);
|
|
4614
|
+
const upstream = asString(upstreamType);
|
|
4615
|
+
if (fork === upstream) return null;
|
|
4616
|
+
if (fork === void 0) return { value: upstream, change: "added" };
|
|
4617
|
+
if (fork !== asString(baseType)) return null;
|
|
4618
|
+
return { value: upstream, change: upstream === void 0 ? "removed" : "updated" };
|
|
4619
|
+
}
|
|
4620
|
+
function setTopLevelKey(pkg, key, value) {
|
|
4621
|
+
if (key in pkg) {
|
|
4622
|
+
pkg[key] = value;
|
|
4623
|
+
return;
|
|
4624
|
+
}
|
|
4625
|
+
const entries = Object.entries(pkg);
|
|
4626
|
+
const after = Math.max(...["name", "version", "private"].map((name) => entries.findIndex(([k]) => k === name)));
|
|
4627
|
+
entries.splice(after + 1, 0, [key, value]);
|
|
4628
|
+
for (const name of Object.keys(pkg)) delete pkg[name];
|
|
4629
|
+
Object.assign(pkg, Object.fromEntries(entries));
|
|
4630
|
+
}
|
|
4520
4631
|
function readPackageJson(filePath) {
|
|
4521
4632
|
if (!existsSync11(filePath)) return null;
|
|
4522
4633
|
try {
|
|
@@ -4570,6 +4681,13 @@ async function syncPackageJson(config, baseRef, usage, relativePath, keysToSync)
|
|
|
4570
4681
|
}
|
|
4571
4682
|
const upstreamPkg = upstream.data;
|
|
4572
4683
|
let updated = false;
|
|
4684
|
+
const type = mergeModuleType(forkPkg.type, upstreamPkg.type, basePkg?.type);
|
|
4685
|
+
if (type) {
|
|
4686
|
+
if (type.value === void 0) delete forkPkg.type;
|
|
4687
|
+
else setTopLevelKey(forkPkg, "type", type.value);
|
|
4688
|
+
updated = true;
|
|
4689
|
+
changes.push(`type: ${type.change}`);
|
|
4690
|
+
}
|
|
4573
4691
|
for (const key of keysToSync) {
|
|
4574
4692
|
if (key === "pnpm") {
|
|
4575
4693
|
const result2 = safeMergePnpm(forkPkg.pnpm, upstreamPkg.pnpm, basePkg?.pnpm);
|
|
@@ -4677,11 +4795,17 @@ async function runPackages(config, options = {}) {
|
|
|
4677
4795
|
}
|
|
4678
4796
|
|
|
4679
4797
|
// src/services/sync.ts
|
|
4680
|
-
async function
|
|
4681
|
-
|
|
4798
|
+
async function resolveCycleStart(forkPath, base, currentBranch) {
|
|
4799
|
+
if (!await branchExists(forkPath, base)) {
|
|
4800
|
+
await fetch2(forkPath, "origin").catch(() => {
|
|
4801
|
+
});
|
|
4802
|
+
console.info(colors_default.dim(`'${base}' has no local branch \u2014 cutting from 'origin/${base}'.`));
|
|
4803
|
+
return `origin/${base}`;
|
|
4804
|
+
}
|
|
4805
|
+
const { upstream, ahead, behind } = await getUpstreamStatus(forkPath, base);
|
|
4682
4806
|
if (!upstream) {
|
|
4683
4807
|
console.info(colors_default.dim(`'${base}' has no upstream \u2014 skipping the up-to-date check.`));
|
|
4684
|
-
return;
|
|
4808
|
+
return base;
|
|
4685
4809
|
}
|
|
4686
4810
|
if (ahead > 0 && behind > 0) {
|
|
4687
4811
|
throw new Error(
|
|
@@ -4690,26 +4814,53 @@ Reconcile '${base}' with '${upstream}' first (e.g. rebase or reset it), then re-
|
|
|
4690
4814
|
);
|
|
4691
4815
|
}
|
|
4692
4816
|
if (behind > 0) {
|
|
4817
|
+
const worktree = currentBranch === base ? null : await getBranchWorktree(forkPath, base);
|
|
4818
|
+
if (worktree) {
|
|
4819
|
+
console.info(
|
|
4820
|
+
colors_default.dim(`'${base}' is ${behind} behind '${upstream}' but checked out at ${worktree} \u2014 leaving it as it is.`)
|
|
4821
|
+
);
|
|
4822
|
+
return upstream;
|
|
4823
|
+
}
|
|
4693
4824
|
console.info(colors_default.dim(`fast-forwarding '${base}' to '${upstream}' (${behind} behind)...`));
|
|
4694
|
-
await pullFastForward(forkPath);
|
|
4695
|
-
|
|
4825
|
+
if (currentBranch === base) await pullFastForward(forkPath);
|
|
4826
|
+
else await fastForwardBranch(forkPath, base, upstream);
|
|
4827
|
+
return base;
|
|
4696
4828
|
}
|
|
4697
4829
|
if (ahead > 0) {
|
|
4698
4830
|
console.info(colors_default.dim(`'${base}' is ${ahead} commit(s) ahead of '${upstream}' (unpushed) \u2014 continuing.`));
|
|
4699
4831
|
}
|
|
4832
|
+
return base;
|
|
4700
4833
|
}
|
|
4701
4834
|
async function setupTemporarySyncBranch(config) {
|
|
4702
4835
|
const { forkPath, settings } = config;
|
|
4703
4836
|
const base = resolveReleaseBase(settings);
|
|
4704
4837
|
const startBranch = await getCurrentBranch(forkPath);
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
await
|
|
4838
|
+
const startCommit = (await getCommitInfo(forkPath, "HEAD")).hash;
|
|
4839
|
+
console.info(colors_default.dim(`updating '${base}'...`));
|
|
4840
|
+
const startPoint = await resolveCycleStart(forkPath, base, startBranch);
|
|
4708
4841
|
const temporaryBranch = buildTemporarySyncBranch();
|
|
4709
|
-
console.info(colors_default.dim(`creating temporary sync branch '${temporaryBranch}' from '${
|
|
4842
|
+
console.info(colors_default.dim(`creating temporary sync branch '${temporaryBranch}' from '${startPoint}'...`));
|
|
4710
4843
|
console.info();
|
|
4711
|
-
await createBranchFrom(forkPath, temporaryBranch,
|
|
4712
|
-
return { temporaryBranch, base, startBranch };
|
|
4844
|
+
await createBranchFrom(forkPath, temporaryBranch, startPoint);
|
|
4845
|
+
return { temporaryBranch, base, startBranch, startCommit };
|
|
4846
|
+
}
|
|
4847
|
+
async function resolveCutBase(forkPath, base) {
|
|
4848
|
+
if (!await branchExists(forkPath, base)) return `origin/${base}`;
|
|
4849
|
+
const upstream = await getBranchUpstream(forkPath, base);
|
|
4850
|
+
return upstream && await isAncestor(forkPath, base, upstream) ? upstream : base;
|
|
4851
|
+
}
|
|
4852
|
+
async function returnToBase(forkPath, base) {
|
|
4853
|
+
const worktree = await getBranchWorktree(forkPath, base);
|
|
4854
|
+
if (worktree) {
|
|
4855
|
+
console.info(colors_default.dim(`'${base}' is checked out at ${worktree} \u2014 detaching at '${base}' here instead...`));
|
|
4856
|
+
await switchDetached(forkPath, base);
|
|
4857
|
+
return `detached at '${base}'`;
|
|
4858
|
+
}
|
|
4859
|
+
console.info(colors_default.dim(`switching back to '${base}'...`));
|
|
4860
|
+
await switchBranch(forkPath, base);
|
|
4861
|
+
await pullFastForward(forkPath).catch(() => {
|
|
4862
|
+
});
|
|
4863
|
+
return `back on '${base}'`;
|
|
4713
4864
|
}
|
|
4714
4865
|
async function runSync(config, options) {
|
|
4715
4866
|
createSpinner("starting sync...");
|
|
@@ -4850,7 +5001,8 @@ function indentLines(text) {
|
|
|
4850
5001
|
async function shipSyncBranch(config, branch) {
|
|
4851
5002
|
const { forkPath, settings } = config;
|
|
4852
5003
|
const base = resolveReleaseBase(settings);
|
|
4853
|
-
const
|
|
5004
|
+
const cutBase = await resolveCutBase(forkPath, base);
|
|
5005
|
+
const flattened = await flattenSyncBranch(forkPath, branch, cutBase);
|
|
4854
5006
|
let prUrl;
|
|
4855
5007
|
let prOpened = false;
|
|
4856
5008
|
const headSubject = (await getCommitInfo(forkPath, "HEAD").catch(() => null))?.message;
|
|
@@ -4866,7 +5018,7 @@ async function shipSyncBranch(config, branch) {
|
|
|
4866
5018
|
}
|
|
4867
5019
|
if (ghAvailable()) {
|
|
4868
5020
|
console.info(colors_default.dim("opening a pull request..."));
|
|
4869
|
-
const prBody = await buildSyncPrBodyForBranch(forkPath,
|
|
5021
|
+
const prBody = await buildSyncPrBodyForBranch(forkPath, cutBase);
|
|
4870
5022
|
const bodyArgs = prBody ? ["--body", prBody] : ["--fill"];
|
|
4871
5023
|
const pr = spawnSync3("gh", ["pr", "create", "--base", base, "--head", branch, "--title", prTitle, ...bodyArgs], {
|
|
4872
5024
|
cwd: forkPath,
|
|
@@ -4885,17 +5037,14 @@ ${pr.stderr ?? ""}`);
|
|
|
4885
5037
|
console.info(colors_default.yellow("`gh` not found \u2014 open the PR manually:"));
|
|
4886
5038
|
printPrCreateStep(branch, base, prTitle);
|
|
4887
5039
|
}
|
|
4888
|
-
|
|
4889
|
-
await switchBranch(forkPath, base);
|
|
4890
|
-
await pullFastForward(forkPath).catch(() => {
|
|
4891
|
-
});
|
|
5040
|
+
const position = await returnToBase(forkPath, base);
|
|
4892
5041
|
console.info();
|
|
4893
5042
|
if (prUrl) {
|
|
4894
5043
|
console.info(`${colors_default.green("\u2713")} Sync pull request ${prOpened ? "opened" : "ready"}`);
|
|
4895
|
-
console.info(colors_default.dim(` ${prUrl} \xB7 branch pushed,
|
|
5044
|
+
console.info(colors_default.dim(` ${prUrl} \xB7 branch pushed, ${position}`));
|
|
4896
5045
|
} else {
|
|
4897
5046
|
console.info(`${colors_default.green("\u2713")} Sync branch pushed`);
|
|
4898
|
-
console.info(colors_default.dim(` '${branch}' is on origin,
|
|
5047
|
+
console.info(colors_default.dim(` '${branch}' is on origin, ${position}`));
|
|
4899
5048
|
}
|
|
4900
5049
|
await printMigrationNotesLine(config);
|
|
4901
5050
|
}
|
|
@@ -4915,8 +5064,9 @@ async function runSyncCycle(config) {
|
|
|
4915
5064
|
await runPackages(config, { conflictedFiles: result.conflicts });
|
|
4916
5065
|
}
|
|
4917
5066
|
if (result.conflicts.length > 0) return { status: "conflicts", branch };
|
|
4918
|
-
if (!mergeInProgress(forkPath)) {
|
|
4919
|
-
|
|
5067
|
+
if (!await mergeInProgress(forkPath)) {
|
|
5068
|
+
if (branch.startBranch === "HEAD") await switchDetached(forkPath, branch.startCommit);
|
|
5069
|
+
else await switchBranch(forkPath, branch.startBranch);
|
|
4920
5070
|
await deleteBranch(forkPath, branch.temporaryBranch);
|
|
4921
5071
|
return { status: "noop" };
|
|
4922
5072
|
}
|
|
@@ -4974,9 +5124,6 @@ async function mergeOpenSyncPrs(config, open) {
|
|
|
4974
5124
|
closePr(forkPath, pr.number);
|
|
4975
5125
|
await deleteBranch(forkPath, pr.headRefName);
|
|
4976
5126
|
}
|
|
4977
|
-
console.info(colors_default.dim(`switching to '${base}' and fast-forwarding...`));
|
|
4978
|
-
await switchBranch(forkPath, base);
|
|
4979
|
-
await pullFastForward(forkPath);
|
|
4980
5127
|
return "continue";
|
|
4981
5128
|
}
|
|
4982
5129
|
async function guardAgainstOpenSyncPr(config) {
|
|
@@ -5015,7 +5162,7 @@ async function runSyncCommand(config) {
|
|
|
5015
5162
|
const { forkPath } = config;
|
|
5016
5163
|
const currentBranch = await getCurrentBranch(forkPath);
|
|
5017
5164
|
const onSyncBranch = isTemporarySyncBranch(currentBranch);
|
|
5018
|
-
if (onSyncBranch && mergeInProgress(forkPath)) {
|
|
5165
|
+
if (onSyncBranch && await mergeInProgress(forkPath)) {
|
|
5019
5166
|
await commitSyncMerge(config, currentBranch);
|
|
5020
5167
|
return;
|
|
5021
5168
|
}
|
|
@@ -5138,7 +5285,7 @@ async function runForks(config) {
|
|
|
5138
5285
|
console.error(colors_default.red(`fork '${config.fork}' not found in config`));
|
|
5139
5286
|
return;
|
|
5140
5287
|
}
|
|
5141
|
-
const resolvedPath =
|
|
5288
|
+
const resolvedPath = resolve7(await resolveForkBasePath(config.forkPath), match.localPath);
|
|
5142
5289
|
await syncFork(config, match, resolvedPath);
|
|
5143
5290
|
return;
|
|
5144
5291
|
}
|
|
@@ -5154,7 +5301,7 @@ async function runForks(config) {
|
|
|
5154
5301
|
if (selectedPath === "_exit") {
|
|
5155
5302
|
process8.exit(0);
|
|
5156
5303
|
}
|
|
5157
|
-
const resolvedForkPath =
|
|
5304
|
+
const resolvedForkPath = resolve7(forkBasePath, selectedPath);
|
|
5158
5305
|
const selectedFork = forks.find((f) => f.localPath === selectedPath);
|
|
5159
5306
|
if (!selectedFork) {
|
|
5160
5307
|
console.error(colors_default.red(`fork '${selectedPath}' not found in config`));
|
|
@@ -5424,7 +5571,7 @@ async function runStats(forkPath, options = {}) {
|
|
|
5424
5571
|
function getForkPath() {
|
|
5425
5572
|
const envPath = getEnv("CELLA_FORK_PATH");
|
|
5426
5573
|
if (envPath) {
|
|
5427
|
-
const resolved =
|
|
5574
|
+
const resolved = resolve8(envPath);
|
|
5428
5575
|
if (!existsSync13(resolved) || !statSync3(resolved).isDirectory()) {
|
|
5429
5576
|
throw new Error(`CELLA_FORK_PATH is not a valid directory: ${resolved}`);
|
|
5430
5577
|
}
|
|
@@ -5432,7 +5579,7 @@ function getForkPath() {
|
|
|
5432
5579
|
}
|
|
5433
5580
|
let cwd = process9.cwd();
|
|
5434
5581
|
if (cwd.endsWith("/cli/cella") || cwd.endsWith("\\cli\\cella")) {
|
|
5435
|
-
cwd =
|
|
5582
|
+
cwd = resolve8(cwd, "../..");
|
|
5436
5583
|
}
|
|
5437
5584
|
return cwd;
|
|
5438
5585
|
}
|