@junctionpanel/server 0.1.28 → 0.1.29
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/server/client/daemon-client.d.ts +39 -4
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +83 -3
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +1 -39
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts +4 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.js +36 -8
- package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
- package/dist/server/server/agent/providers/image-attachments.d.ts +8 -0
- package/dist/server/server/agent/providers/image-attachments.d.ts.map +1 -0
- package/dist/server/server/agent/providers/image-attachments.js +47 -0
- package/dist/server/server/agent/providers/image-attachments.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +3 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/daemon-doctor.d.ts +39 -0
- package/dist/server/server/daemon-doctor.d.ts.map +1 -0
- package/dist/server/server/daemon-doctor.js +260 -0
- package/dist/server/server/daemon-doctor.js.map +1 -0
- package/dist/server/server/daemon-provider-settings.d.ts +42 -0
- package/dist/server/server/daemon-provider-settings.d.ts.map +1 -0
- package/dist/server/server/daemon-provider-settings.js +207 -0
- package/dist/server/server/daemon-provider-settings.js.map +1 -0
- package/dist/server/server/file-explorer/service.d.ts +4 -2
- package/dist/server/server/file-explorer/service.d.ts.map +1 -1
- package/dist/server/server/file-explorer/service.js +104 -2
- package/dist/server/server/file-explorer/service.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +24 -24
- package/dist/server/server/session.d.ts +10 -1
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +421 -60
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/worktree-bootstrap.d.ts +1 -0
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
- package/dist/server/server/worktree-bootstrap.js +4 -0
- package/dist/server/server/worktree-bootstrap.js.map +1 -1
- package/dist/server/shared/messages.d.ts +3673 -22
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +151 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +23 -4
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +298 -79
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/directory-suggestions.d.ts +4 -0
- package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
- package/dist/server/utils/directory-suggestions.js +83 -5
- package/dist/server/utils/directory-suggestions.js.map +1 -1
- package/dist/server/utils/workspace-ref-files.d.ts +31 -0
- package/dist/server/utils/workspace-ref-files.d.ts.map +1 -0
- package/dist/server/utils/workspace-ref-files.js +207 -0
- package/dist/server/utils/workspace-ref-files.js.map +1 -0
- package/dist/server/utils/worktree.d.ts +6 -3
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +46 -45
- package/dist/server/utils/worktree.js.map +1 -1
- package/package.json +2 -2
|
@@ -12,10 +12,16 @@ const READ_ONLY_GIT_ENV = {
|
|
|
12
12
|
...process.env,
|
|
13
13
|
GIT_OPTIONAL_LOCKS: "0",
|
|
14
14
|
};
|
|
15
|
+
const SAFE_GIT_REMOTE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
15
16
|
const SMALL_OUTPUT_MAX_BUFFER = 20 * 1024 * 1024; // 20MB
|
|
16
17
|
async function execGit(command, options) {
|
|
17
18
|
return execAsync(command, { ...options, maxBuffer: SMALL_OUTPUT_MAX_BUFFER });
|
|
18
19
|
}
|
|
20
|
+
function assertSafeRemoteName(remoteName) {
|
|
21
|
+
if (!SAFE_GIT_REMOTE_NAME_PATTERN.test(remoteName)) {
|
|
22
|
+
throw new Error(`Invalid remote name: ${remoteName}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
19
25
|
async function spawnLimitedText(params) {
|
|
20
26
|
const accept = new Set(params.acceptExitCodes ?? [0]);
|
|
21
27
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
@@ -72,7 +78,7 @@ async function spawnLimitedText(params) {
|
|
|
72
78
|
});
|
|
73
79
|
});
|
|
74
80
|
}
|
|
75
|
-
function normalizeBranchSuggestionName(raw) {
|
|
81
|
+
function normalizeBranchSuggestionName(raw, source) {
|
|
76
82
|
const trimmed = raw.trim();
|
|
77
83
|
if (!trimmed) {
|
|
78
84
|
return null;
|
|
@@ -84,8 +90,12 @@ function normalizeBranchSuggestionName(raw) {
|
|
|
84
90
|
else if (normalized.startsWith("refs/remotes/")) {
|
|
85
91
|
normalized = normalized.slice("refs/remotes/".length);
|
|
86
92
|
}
|
|
87
|
-
if (
|
|
88
|
-
|
|
93
|
+
if (source === "remote") {
|
|
94
|
+
const slashIndex = normalized.indexOf("/");
|
|
95
|
+
if (slashIndex <= 0) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
normalized = normalized.slice(slashIndex + 1);
|
|
89
99
|
}
|
|
90
100
|
if (!normalized || normalized === "HEAD") {
|
|
91
101
|
return null;
|
|
@@ -128,13 +138,19 @@ export async function listBranchSuggestions(cwd, options) {
|
|
|
128
138
|
const requestedLimit = options?.limit ?? 50;
|
|
129
139
|
const limit = Math.max(1, Math.min(200, requestedLimit));
|
|
130
140
|
const query = options?.query?.trim().toLowerCase() ?? "";
|
|
141
|
+
if (options?.remoteName?.trim()) {
|
|
142
|
+
assertSafeRemoteName(options.remoteName.trim());
|
|
143
|
+
}
|
|
144
|
+
const remoteRefPrefix = options?.remoteName?.trim()
|
|
145
|
+
? `refs/remotes/${options.remoteName.trim()}`
|
|
146
|
+
: "refs/remotes";
|
|
131
147
|
const [localRefs, remoteRefs] = await Promise.all([
|
|
132
148
|
listGitRefs(cwd, "refs/heads"),
|
|
133
|
-
listGitRefs(cwd,
|
|
149
|
+
listGitRefs(cwd, remoteRefPrefix),
|
|
134
150
|
]);
|
|
135
151
|
const merged = new Map();
|
|
136
152
|
for (const localRef of localRefs) {
|
|
137
|
-
const normalized = normalizeBranchSuggestionName(localRef);
|
|
153
|
+
const normalized = normalizeBranchSuggestionName(localRef, "local");
|
|
138
154
|
if (!normalized) {
|
|
139
155
|
continue;
|
|
140
156
|
}
|
|
@@ -143,7 +159,7 @@ export async function listBranchSuggestions(cwd, options) {
|
|
|
143
159
|
merged.set(normalized, origins);
|
|
144
160
|
}
|
|
145
161
|
for (const remoteRef of remoteRefs) {
|
|
146
|
-
const normalized = normalizeBranchSuggestionName(remoteRef);
|
|
162
|
+
const normalized = normalizeBranchSuggestionName(remoteRef, "remote");
|
|
147
163
|
if (!normalized) {
|
|
148
164
|
continue;
|
|
149
165
|
}
|
|
@@ -482,9 +498,10 @@ async function isWorkingTreeDirty(cwd) {
|
|
|
482
498
|
});
|
|
483
499
|
return stdout.trim().length > 0;
|
|
484
500
|
}
|
|
485
|
-
async function
|
|
501
|
+
async function getRemoteUrl(cwd, remoteName) {
|
|
502
|
+
assertSafeRemoteName(remoteName);
|
|
486
503
|
try {
|
|
487
|
-
const { stdout } = await execAsync(
|
|
504
|
+
const { stdout } = await execAsync(`git config --get remote.${remoteName}.url`, {
|
|
488
505
|
cwd,
|
|
489
506
|
env: READ_ONLY_GIT_ENV,
|
|
490
507
|
});
|
|
@@ -495,20 +512,171 @@ async function getOriginRemoteUrl(cwd) {
|
|
|
495
512
|
return null;
|
|
496
513
|
}
|
|
497
514
|
}
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
515
|
+
function parseSymbolicHeadRef(ref) {
|
|
516
|
+
const trimmed = ref.trim();
|
|
517
|
+
if (!trimmed.startsWith("ref: ")) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
const headRef = trimmed.slice("ref: ".length).trim();
|
|
521
|
+
const match = /^refs\/heads\/(.+)$/.exec(headRef);
|
|
522
|
+
return match?.[1] ?? null;
|
|
501
523
|
}
|
|
502
|
-
|
|
524
|
+
async function getLocalRemoteHeadBranch(remoteUrl) {
|
|
525
|
+
let localPath = null;
|
|
503
526
|
try {
|
|
504
|
-
|
|
505
|
-
|
|
527
|
+
if (remoteUrl.startsWith("file://")) {
|
|
528
|
+
localPath = realpathSync(new URL(remoteUrl));
|
|
529
|
+
}
|
|
530
|
+
else if (remoteUrl.startsWith("/")) {
|
|
531
|
+
localPath = realpathSync(remoteUrl);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
localPath = null;
|
|
536
|
+
}
|
|
537
|
+
if (!localPath) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
for (const candidateHeadPath of [`${localPath}/HEAD`, `${localPath}/.git/HEAD`]) {
|
|
541
|
+
try {
|
|
542
|
+
const headFile = await openFile(candidateHeadPath, "r");
|
|
543
|
+
try {
|
|
544
|
+
const contents = await headFile.readFile("utf8");
|
|
545
|
+
const branch = parseSymbolicHeadRef(contents);
|
|
546
|
+
if (branch) {
|
|
547
|
+
return branch;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
finally {
|
|
551
|
+
await headFile.close();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
// try next location
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
async function listRemoteNames(cwd) {
|
|
561
|
+
const { stdout } = await execAsync("git remote", {
|
|
562
|
+
cwd,
|
|
563
|
+
env: READ_ONLY_GIT_ENV,
|
|
564
|
+
});
|
|
565
|
+
return stdout
|
|
566
|
+
.split("\n")
|
|
567
|
+
.map((line) => line.trim())
|
|
568
|
+
.filter((line) => line.length > 0)
|
|
569
|
+
.sort((left, right) => left.localeCompare(right));
|
|
570
|
+
}
|
|
571
|
+
async function getRemoteHeadBranch(cwd, remoteName) {
|
|
572
|
+
assertSafeRemoteName(remoteName);
|
|
573
|
+
try {
|
|
574
|
+
const { stdout } = await execAsync(`git symbolic-ref --quiet refs/remotes/${remoteName}/HEAD`, {
|
|
575
|
+
cwd,
|
|
506
576
|
env: READ_ONLY_GIT_ENV,
|
|
507
577
|
});
|
|
508
578
|
const ref = stdout.trim();
|
|
579
|
+
if (!ref) {
|
|
580
|
+
const remoteUrl = await getRemoteUrl(cwd, remoteName);
|
|
581
|
+
return remoteUrl ? getLocalRemoteHeadBranch(remoteUrl) : null;
|
|
582
|
+
}
|
|
583
|
+
const shortRef = ref.replace(/^refs\/remotes\//, "");
|
|
584
|
+
return shortRef.startsWith(`${remoteName}/`)
|
|
585
|
+
? shortRef.slice(`${remoteName}/`.length)
|
|
586
|
+
: shortRef;
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
try {
|
|
590
|
+
const { stdout } = await execAsync(`git ls-remote --symref ${remoteName} HEAD`, {
|
|
591
|
+
cwd,
|
|
592
|
+
env: READ_ONLY_GIT_ENV,
|
|
593
|
+
});
|
|
594
|
+
const headLine = stdout
|
|
595
|
+
.split("\n")
|
|
596
|
+
.map((line) => line.trim())
|
|
597
|
+
.find((line) => line.startsWith("ref: refs/heads/") && line.endsWith("\tHEAD"));
|
|
598
|
+
if (!headLine) {
|
|
599
|
+
const remoteUrl = await getRemoteUrl(cwd, remoteName);
|
|
600
|
+
return remoteUrl ? getLocalRemoteHeadBranch(remoteUrl) : null;
|
|
601
|
+
}
|
|
602
|
+
const match = /^ref:\s+refs\/heads\/(.+)\tHEAD$/.exec(headLine);
|
|
603
|
+
return match?.[1] ?? null;
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
const remoteUrl = await getRemoteUrl(cwd, remoteName);
|
|
607
|
+
return remoteUrl ? getLocalRemoteHeadBranch(remoteUrl) : null;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
export async function listGitRemotes(cwd) {
|
|
612
|
+
await requireGitRepo(cwd);
|
|
613
|
+
const remoteNames = await listRemoteNames(cwd);
|
|
614
|
+
const remotes = await Promise.all(remoteNames.map(async (name) => {
|
|
615
|
+
const [url, headBranch] = await Promise.all([
|
|
616
|
+
getRemoteUrl(cwd, name),
|
|
617
|
+
getRemoteHeadBranch(cwd, name),
|
|
618
|
+
]);
|
|
619
|
+
return {
|
|
620
|
+
name,
|
|
621
|
+
url,
|
|
622
|
+
headBranch,
|
|
623
|
+
isGitHub: typeof url === "string" && parseGitHubRepoFromRemoteUrl(url) !== null,
|
|
624
|
+
};
|
|
625
|
+
}));
|
|
626
|
+
return remotes;
|
|
627
|
+
}
|
|
628
|
+
async function resolveEffectiveRemoteName(cwd, requestedRemoteName) {
|
|
629
|
+
const remotes = await listRemoteNames(cwd);
|
|
630
|
+
if (remotes.length === 0) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
const trimmedRequested = requestedRemoteName?.trim();
|
|
634
|
+
if (trimmedRequested && remotes.includes(trimmedRequested)) {
|
|
635
|
+
assertSafeRemoteName(trimmedRequested);
|
|
636
|
+
return trimmedRequested;
|
|
637
|
+
}
|
|
638
|
+
if (remotes.includes("origin")) {
|
|
639
|
+
return "origin";
|
|
640
|
+
}
|
|
641
|
+
return remotes[0] ?? null;
|
|
642
|
+
}
|
|
643
|
+
export async function resolveBaseRef(repoRoot, options) {
|
|
644
|
+
const remoteName = await resolveEffectiveRemoteName(repoRoot, options?.remoteName);
|
|
645
|
+
if (remoteName) {
|
|
646
|
+
try {
|
|
647
|
+
const { stdout } = await execAsync(`git symbolic-ref --quiet refs/remotes/${remoteName}/HEAD`, {
|
|
648
|
+
cwd: repoRoot,
|
|
649
|
+
env: READ_ONLY_GIT_ENV,
|
|
650
|
+
});
|
|
651
|
+
const ref = stdout.trim();
|
|
652
|
+
if (ref) {
|
|
653
|
+
const remoteShort = ref.replace(/^refs\/remotes\//, "");
|
|
654
|
+
const localName = remoteShort.startsWith(`${remoteName}/`)
|
|
655
|
+
? remoteShort.slice(`${remoteName}/`.length)
|
|
656
|
+
: remoteShort;
|
|
657
|
+
try {
|
|
658
|
+
await execAsync(`git show-ref --verify --quiet refs/heads/${localName}`, {
|
|
659
|
+
cwd: repoRoot,
|
|
660
|
+
env: READ_ONLY_GIT_ENV,
|
|
661
|
+
});
|
|
662
|
+
return localName;
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
return remoteShort;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
// ignore
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
const fallbackOriginRef = await execAsync("git symbolic-ref --quiet refs/remotes/origin/HEAD", {
|
|
675
|
+
cwd: repoRoot,
|
|
676
|
+
env: READ_ONLY_GIT_ENV,
|
|
677
|
+
});
|
|
678
|
+
const ref = fallbackOriginRef.stdout.trim();
|
|
509
679
|
if (ref) {
|
|
510
|
-
// Prefer a local branch name (e.g. "main") over the remote-tracking ref (e.g. "origin/main")
|
|
511
|
-
// so that status/diff/merge all operate against the same base ref.
|
|
512
680
|
const remoteShort = ref.replace(/^refs\/remotes\//, "");
|
|
513
681
|
const localName = remoteShort.startsWith("origin/")
|
|
514
682
|
? remoteShort.slice("origin/".length)
|
|
@@ -545,7 +713,15 @@ export async function resolveBaseRef(repoRoot) {
|
|
|
545
713
|
return null;
|
|
546
714
|
}
|
|
547
715
|
function normalizeLocalBranchRefName(input) {
|
|
548
|
-
|
|
716
|
+
if (input.startsWith("origin/")) {
|
|
717
|
+
return input.slice("origin/".length);
|
|
718
|
+
}
|
|
719
|
+
if (input.startsWith("refs/remotes/")) {
|
|
720
|
+
const stripped = input.slice("refs/remotes/".length);
|
|
721
|
+
const slashIndex = stripped.indexOf("/");
|
|
722
|
+
return slashIndex > 0 ? stripped.slice(slashIndex + 1) : stripped;
|
|
723
|
+
}
|
|
724
|
+
return input;
|
|
549
725
|
}
|
|
550
726
|
async function doesGitRefExist(cwd, fullRef) {
|
|
551
727
|
try {
|
|
@@ -559,28 +735,34 @@ async function doesGitRefExist(cwd, fullRef) {
|
|
|
559
735
|
return false;
|
|
560
736
|
}
|
|
561
737
|
}
|
|
562
|
-
async function resolveBestBaseRefForMerge(cwd, normalizedBaseRef) {
|
|
738
|
+
async function resolveBestBaseRefForMerge(cwd, normalizedBaseRef, options) {
|
|
739
|
+
const remoteName = await resolveEffectiveRemoteName(cwd, options?.remoteName);
|
|
740
|
+
const remoteRef = remoteName ? `refs/remotes/${remoteName}/${normalizedBaseRef}` : null;
|
|
563
741
|
const [hasLocal, hasOrigin] = await Promise.all([
|
|
564
742
|
doesGitRefExist(cwd, `refs/heads/${normalizedBaseRef}`),
|
|
565
|
-
doesGitRefExist(cwd,
|
|
743
|
+
remoteRef ? doesGitRefExist(cwd, remoteRef) : Promise.resolve(false),
|
|
566
744
|
]);
|
|
567
745
|
if (hasLocal && !hasOrigin) {
|
|
568
746
|
return normalizedBaseRef;
|
|
569
747
|
}
|
|
570
748
|
if (!hasLocal && hasOrigin) {
|
|
571
|
-
return
|
|
749
|
+
return `${remoteName}/${normalizedBaseRef}`;
|
|
572
750
|
}
|
|
573
751
|
if (!hasLocal && !hasOrigin) {
|
|
574
|
-
|
|
752
|
+
const remoteLabel = remoteName ?? "any remote";
|
|
753
|
+
throw new Error(`Base branch not found locally or on ${remoteLabel}: ${normalizedBaseRef}`);
|
|
575
754
|
}
|
|
576
755
|
// Both exist: choose the ref with more unique commits compared to the other.
|
|
577
756
|
try {
|
|
578
|
-
|
|
757
|
+
if (!remoteName) {
|
|
758
|
+
return normalizedBaseRef;
|
|
759
|
+
}
|
|
760
|
+
const { stdout } = await execAsync(`git rev-list --left-right --count ${normalizedBaseRef}...${remoteName}/${normalizedBaseRef}`, { cwd, env: READ_ONLY_GIT_ENV });
|
|
579
761
|
const [localOnlyRaw, originOnlyRaw] = stdout.trim().split(/\s+/);
|
|
580
762
|
const localOnly = Number.parseInt(localOnlyRaw ?? "0", 10);
|
|
581
763
|
const originOnly = Number.parseInt(originOnlyRaw ?? "0", 10);
|
|
582
764
|
if (!Number.isNaN(localOnly) && !Number.isNaN(originOnly) && originOnly > localOnly) {
|
|
583
|
-
return
|
|
765
|
+
return `${remoteName}/${normalizedBaseRef}`;
|
|
584
766
|
}
|
|
585
767
|
}
|
|
586
768
|
catch {
|
|
@@ -634,11 +816,12 @@ async function inspectCheckoutContext(cwd, context) {
|
|
|
634
816
|
if (!root) {
|
|
635
817
|
return null;
|
|
636
818
|
}
|
|
637
|
-
const [currentBranch,
|
|
819
|
+
const [currentBranch, configured, effectiveRemoteName] = await Promise.all([
|
|
638
820
|
getCurrentBranch(cwd),
|
|
639
|
-
getOriginRemoteUrl(cwd),
|
|
640
821
|
getConfiguredBaseRefForCwd(cwd, context),
|
|
822
|
+
resolveEffectiveRemoteName(cwd),
|
|
641
823
|
]);
|
|
824
|
+
const remoteUrl = effectiveRemoteName ? await getRemoteUrl(cwd, effectiveRemoteName) : null;
|
|
642
825
|
return {
|
|
643
826
|
worktreeRoot: root,
|
|
644
827
|
currentBranch,
|
|
@@ -1130,7 +1313,7 @@ export async function mergeFromBase(cwd, options = {}, context) {
|
|
|
1130
1313
|
throw new Error("Unable to determine current branch for merge");
|
|
1131
1314
|
}
|
|
1132
1315
|
const configured = await getConfiguredBaseRefForCwd(cwd, context);
|
|
1133
|
-
const baseRef = configured.baseRef ?? options.baseRef ?? (await resolveBaseRef(cwd));
|
|
1316
|
+
const baseRef = configured.baseRef ?? options.baseRef ?? (await resolveBaseRef(cwd, { remoteName: options.remoteName }));
|
|
1134
1317
|
if (!baseRef) {
|
|
1135
1318
|
throw new Error("Unable to determine base branch for merge");
|
|
1136
1319
|
}
|
|
@@ -1148,7 +1331,9 @@ export async function mergeFromBase(cwd, options = {}, context) {
|
|
|
1148
1331
|
}
|
|
1149
1332
|
}
|
|
1150
1333
|
const normalizedBaseRef = normalizeLocalBranchRefName(baseRef);
|
|
1151
|
-
const bestBaseRef = await resolveBestBaseRefForMerge(cwd, normalizedBaseRef
|
|
1334
|
+
const bestBaseRef = await resolveBestBaseRefForMerge(cwd, normalizedBaseRef, {
|
|
1335
|
+
remoteName: options.remoteName,
|
|
1336
|
+
});
|
|
1152
1337
|
if (bestBaseRef === currentBranch) {
|
|
1153
1338
|
return;
|
|
1154
1339
|
}
|
|
@@ -1207,17 +1392,17 @@ export async function mergeFromBase(cwd, options = {}, context) {
|
|
|
1207
1392
|
throw error;
|
|
1208
1393
|
}
|
|
1209
1394
|
}
|
|
1210
|
-
export async function pushCurrentBranch(cwd) {
|
|
1395
|
+
export async function pushCurrentBranch(cwd, options) {
|
|
1211
1396
|
await requireGitRepo(cwd);
|
|
1212
1397
|
const currentBranch = await getCurrentBranch(cwd);
|
|
1213
1398
|
if (!currentBranch || currentBranch === "HEAD") {
|
|
1214
1399
|
throw new Error("Unable to determine current branch for push");
|
|
1215
1400
|
}
|
|
1216
|
-
const
|
|
1217
|
-
if (!
|
|
1218
|
-
throw new Error("
|
|
1401
|
+
const remoteName = await resolveEffectiveRemoteName(cwd, options?.remoteName);
|
|
1402
|
+
if (!remoteName) {
|
|
1403
|
+
throw new Error("No git remotes are configured.");
|
|
1219
1404
|
}
|
|
1220
|
-
await execAsync(`git push -u
|
|
1405
|
+
await execAsync(`git push -u ${remoteName} ${currentBranch}`, { cwd });
|
|
1221
1406
|
}
|
|
1222
1407
|
const GH_JSON_MAX_BYTES = 512 * 1024;
|
|
1223
1408
|
const GH_FAILED_LOG_MAX_BYTES = 200 * 1024;
|
|
@@ -1241,54 +1426,65 @@ function getCommandErrorText(error) {
|
|
|
1241
1426
|
function isGhAuthError(error) {
|
|
1242
1427
|
const text = getCommandErrorText(error);
|
|
1243
1428
|
return (text.includes("gh auth login") ||
|
|
1429
|
+
text.includes("gh auth status") ||
|
|
1244
1430
|
text.includes("not logged into any github hosts") ||
|
|
1431
|
+
text.includes("not logged into github.com") ||
|
|
1245
1432
|
text.includes("authentication failed") ||
|
|
1246
1433
|
text.includes("authentication required") ||
|
|
1247
1434
|
text.includes("bad credentials") ||
|
|
1248
1435
|
text.includes("http 401"));
|
|
1249
1436
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
cleaned = cleaned.slice(
|
|
1437
|
+
function parseGitHubRepoFromRemoteUrl(url) {
|
|
1438
|
+
let cleaned = url.trim();
|
|
1439
|
+
if (!cleaned) {
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
if (cleaned.startsWith("git@github.com:")) {
|
|
1443
|
+
cleaned = cleaned.slice("git@github.com:".length);
|
|
1444
|
+
}
|
|
1445
|
+
else if (cleaned.startsWith("https://github.com/")) {
|
|
1446
|
+
cleaned = cleaned.slice("https://github.com/".length);
|
|
1447
|
+
}
|
|
1448
|
+
else if (cleaned.startsWith("http://github.com/")) {
|
|
1449
|
+
cleaned = cleaned.slice("http://github.com/".length);
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
const marker = "github.com/";
|
|
1453
|
+
const index = cleaned.indexOf(marker);
|
|
1454
|
+
if (index !== -1) {
|
|
1455
|
+
cleaned = cleaned.slice(index + marker.length);
|
|
1269
1456
|
}
|
|
1270
1457
|
else {
|
|
1271
|
-
const marker = "github.com/";
|
|
1272
|
-
const index = cleaned.indexOf(marker);
|
|
1273
|
-
if (index !== -1) {
|
|
1274
|
-
cleaned = cleaned.slice(index + marker.length);
|
|
1275
|
-
}
|
|
1276
|
-
else {
|
|
1277
|
-
return null;
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
if (cleaned.endsWith(".git")) {
|
|
1281
|
-
cleaned = cleaned.slice(0, -".git".length);
|
|
1282
|
-
}
|
|
1283
|
-
if (!cleaned.includes("/")) {
|
|
1284
1458
|
return null;
|
|
1285
1459
|
}
|
|
1286
|
-
return cleaned;
|
|
1287
1460
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1461
|
+
if (cleaned.endsWith(".git")) {
|
|
1462
|
+
cleaned = cleaned.slice(0, -".git".length);
|
|
1463
|
+
}
|
|
1464
|
+
if (!cleaned.includes("/")) {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
return cleaned;
|
|
1468
|
+
}
|
|
1469
|
+
async function resolveGitHubRepo(cwd, options) {
|
|
1470
|
+
const remoteName = await resolveEffectiveRemoteName(cwd, options?.remoteName);
|
|
1471
|
+
if (!remoteName) {
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
const remoteUrl = await getRemoteUrl(cwd, remoteName);
|
|
1475
|
+
return remoteUrl ? parseGitHubRepoFromRemoteUrl(remoteUrl) : null;
|
|
1476
|
+
}
|
|
1477
|
+
async function hasAuthenticatedGitHubCliSession(cwd) {
|
|
1478
|
+
try {
|
|
1479
|
+
await execFileAsync("gh", ["auth", "status", "--hostname", "github.com"], { cwd });
|
|
1480
|
+
return true;
|
|
1481
|
+
}
|
|
1482
|
+
catch (error) {
|
|
1483
|
+
if (isGhAuthError(error)) {
|
|
1484
|
+
return false;
|
|
1485
|
+
}
|
|
1486
|
+
throw error;
|
|
1290
1487
|
}
|
|
1291
|
-
return null;
|
|
1292
1488
|
}
|
|
1293
1489
|
async function listPullRequestsForHead(options) {
|
|
1294
1490
|
const { stdout } = await execFileAsync("gh", [
|
|
@@ -1666,11 +1862,10 @@ function appendBoundedText(parts, next, currentBytes) {
|
|
|
1666
1862
|
parts.push(`${truncated}\n\n[combined log output truncated]`);
|
|
1667
1863
|
return GH_FAILED_LOG_TOTAL_MAX_BYTES;
|
|
1668
1864
|
}
|
|
1669
|
-
async function loadCurrentPullRequest(cwd) {
|
|
1865
|
+
async function loadCurrentPullRequest(cwd, options) {
|
|
1670
1866
|
await requireGitRepo(cwd);
|
|
1671
|
-
const repo = await resolveGitHubRepo(cwd);
|
|
1672
1867
|
const head = await getCurrentBranch(cwd);
|
|
1673
|
-
if (!
|
|
1868
|
+
if (!head) {
|
|
1674
1869
|
return {
|
|
1675
1870
|
status: null,
|
|
1676
1871
|
githubFeaturesEnabled: false,
|
|
@@ -1689,6 +1884,26 @@ async function loadCurrentPullRequest(cwd) {
|
|
|
1689
1884
|
headSha: null,
|
|
1690
1885
|
};
|
|
1691
1886
|
}
|
|
1887
|
+
const remoteName = await resolveEffectiveRemoteName(cwd, options?.remoteName);
|
|
1888
|
+
const remoteUrl = remoteName ? await getRemoteUrl(cwd, remoteName) : null;
|
|
1889
|
+
if (!remoteUrl) {
|
|
1890
|
+
const hasAuthenticatedGhSession = await hasAuthenticatedGitHubCliSession(cwd);
|
|
1891
|
+
return {
|
|
1892
|
+
status: null,
|
|
1893
|
+
githubFeaturesEnabled: hasAuthenticatedGhSession,
|
|
1894
|
+
checks: [],
|
|
1895
|
+
headSha: null,
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
const repo = parseGitHubRepoFromRemoteUrl(remoteUrl);
|
|
1899
|
+
if (!repo) {
|
|
1900
|
+
return {
|
|
1901
|
+
status: null,
|
|
1902
|
+
githubFeaturesEnabled: false,
|
|
1903
|
+
checks: [],
|
|
1904
|
+
headSha: null,
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1692
1907
|
const owner = repo.split("/")[0];
|
|
1693
1908
|
let openPulls;
|
|
1694
1909
|
try {
|
|
@@ -1794,13 +2009,14 @@ async function loadCurrentPullRequest(cwd) {
|
|
|
1794
2009
|
export async function createPullRequest(cwd, options) {
|
|
1795
2010
|
await requireGitRepo(cwd);
|
|
1796
2011
|
await ensureGhAvailable(cwd);
|
|
1797
|
-
const
|
|
2012
|
+
const remoteName = await resolveEffectiveRemoteName(cwd, options.remoteName);
|
|
2013
|
+
const repo = await resolveGitHubRepo(cwd, { remoteName });
|
|
1798
2014
|
if (!repo) {
|
|
1799
2015
|
throw new Error("Unable to determine GitHub repo from git remote");
|
|
1800
2016
|
}
|
|
1801
2017
|
const head = options.head ?? (await getCurrentBranch(cwd));
|
|
1802
2018
|
const configured = await getConfiguredBaseRefForCwd(cwd);
|
|
1803
|
-
const base = configured.baseRef ?? options.base ?? (await resolveBaseRef(cwd));
|
|
2019
|
+
const base = configured.baseRef ?? options.base ?? (await resolveBaseRef(cwd, { remoteName }));
|
|
1804
2020
|
if (!head) {
|
|
1805
2021
|
throw new Error("Unable to determine head branch for PR");
|
|
1806
2022
|
}
|
|
@@ -1811,7 +2027,10 @@ export async function createPullRequest(cwd, options) {
|
|
|
1811
2027
|
if (configured.isJunctionOwnedWorktree && options.base && options.base !== base) {
|
|
1812
2028
|
throw new Error(`Base ref mismatch: expected ${base}, got ${options.base}`);
|
|
1813
2029
|
}
|
|
1814
|
-
|
|
2030
|
+
if (!remoteName) {
|
|
2031
|
+
throw new Error("No git remotes are configured.");
|
|
2032
|
+
}
|
|
2033
|
+
await execAsync(`git push -u ${remoteName} ${head}`, { cwd });
|
|
1815
2034
|
const args = ["api", "-X", "POST", `repos/${repo}/pulls`, "-f", `title=${options.title}`];
|
|
1816
2035
|
args.push("-f", `head=${head}`);
|
|
1817
2036
|
args.push("-f", `base=${normalizedBase}`);
|
|
@@ -1825,15 +2044,15 @@ export async function createPullRequest(cwd, options) {
|
|
|
1825
2044
|
}
|
|
1826
2045
|
return { url: parsed.url, number: parsed.number };
|
|
1827
2046
|
}
|
|
1828
|
-
export async function getPullRequestStatus(cwd) {
|
|
1829
|
-
const lookup = await loadCurrentPullRequest(cwd);
|
|
2047
|
+
export async function getPullRequestStatus(cwd, options) {
|
|
2048
|
+
const lookup = await loadCurrentPullRequest(cwd, options);
|
|
1830
2049
|
return {
|
|
1831
2050
|
status: lookup.status,
|
|
1832
2051
|
githubFeaturesEnabled: lookup.githubFeaturesEnabled,
|
|
1833
2052
|
};
|
|
1834
2053
|
}
|
|
1835
|
-
export async function getPullRequestFailureLogs(cwd) {
|
|
1836
|
-
const lookup = await loadCurrentPullRequest(cwd);
|
|
2054
|
+
export async function getPullRequestFailureLogs(cwd, options) {
|
|
2055
|
+
const lookup = await loadCurrentPullRequest(cwd, options);
|
|
1837
2056
|
if (!lookup.githubFeaturesEnabled) {
|
|
1838
2057
|
return {
|
|
1839
2058
|
logs: null,
|
|
@@ -1910,7 +2129,7 @@ export async function mergePullRequest(cwd, options = {}) {
|
|
|
1910
2129
|
if (method !== "squash") {
|
|
1911
2130
|
throw new Error("Only squash merge is supported");
|
|
1912
2131
|
}
|
|
1913
|
-
const statusResult = await getPullRequestStatus(cwd);
|
|
2132
|
+
const statusResult = await getPullRequestStatus(cwd, { remoteName: options.remoteName });
|
|
1914
2133
|
if (!statusResult.githubFeaturesEnabled) {
|
|
1915
2134
|
throw new Error("GitHub CLI (gh) is not available or not authenticated");
|
|
1916
2135
|
}
|