@opentig/cli 0.2.0 → 0.2.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/dist/bin.mjs +1 -1
- package/dist/chunks/{cli-Bi6ET5UT.mjs → cli-B6JTojx_.mjs} +1 -1
- package/dist/client/assets/App-Dfm5F199.js +74 -0
- package/dist/client/assets/CreatePullRequestDialog-CAeykZ72.js +2 -0
- package/dist/client/assets/{DiffWorkspace-RDG_Y70N.js → DiffWorkspace-BRicWHvw.js} +13 -13
- package/dist/client/assets/{EditableFileViewer-CsbifqBO.js → EditableFileViewer-DWeSDnZn.js} +1 -1
- package/dist/client/assets/{File-CHjvYpb2.js → File-D65shwRR.js} +1 -1
- package/dist/client/assets/FilesView-EfoU7sF-.js +1 -0
- package/dist/client/assets/GitHubAvatar-C8R3iNoM.js +1 -0
- package/dist/client/assets/HistoryView-C6T2dsBC.js +1 -0
- package/dist/client/assets/{HtmlFileViewer-h8ULU8Gy.js → HtmlFileViewer-JXLHTNPm.js} +1 -1
- package/dist/client/assets/{IconEdit-Cg8S2wqW.js → IconEdit-3fgBTCQ4.js} +1 -1
- package/dist/client/assets/{IconFolderSymlink-BZSLX1ez.js → IconFolderSymlink-C4ydD1eS.js} +1 -1
- package/dist/client/assets/{ImageFileViewer-Cjw58ckv.js → ImageFileViewer-YH2xhkkk.js} +1 -1
- package/dist/client/assets/LocalRefsDialog--1NiYKYj.js +1 -0
- package/dist/client/assets/{MarkdownFileViewer-kLiduag6.js → MarkdownFileViewer-BsB8WkIH.js} +2 -2
- package/dist/client/assets/PairingPage-DXRyAYHn.js +1 -0
- package/dist/client/assets/{PierreDiffViewer-eAcugmoF.js → PierreDiffViewer-DoV3V0IW.js} +1 -1
- package/dist/client/assets/{PierreEditBoundary-DlGd1pqO.js → PierreEditBoundary-BnYZbHfi.js} +1 -1
- package/dist/client/assets/{PierreWorkerPool-Col4k7qz.js → PierreWorkerPool-Cg9zdxiI.js} +1 -1
- package/dist/client/assets/PullRequestDiff-D6QG5Anx.js +1 -0
- package/dist/client/assets/PullRequestViewer-jC3LVmiO.js +2 -0
- package/dist/client/assets/PullRequestsView-GStKxG7k.js +1 -0
- package/dist/client/assets/{QuickOpenDialog-BFDDZVCL.js → QuickOpenDialog-HvslpGQl.js} +1 -1
- package/dist/client/assets/{RepositoryProjectsDialog-CvB8KcFp.js → RepositoryProjectsDialog-Biqv5GO_.js} +1 -1
- package/dist/client/assets/SearchView-D5XB6wdu.js +1 -0
- package/dist/client/assets/SettingsDialog-DWAunOQB.js +72 -0
- package/dist/client/assets/{Viewer-o1hDU33O.js → Viewer-Gfy3Ejyo.js} +3 -3
- package/dist/client/assets/{abnfDiagram-N423BO3Z-BvrnK_R-.js → abnfDiagram-N423BO3Z-DdAfHevw.js} +1 -1
- package/dist/client/assets/{arc-DLa3_7ua.js → arc-C43uoeEi.js} +1 -1
- package/dist/client/assets/{architectureDiagram-T3A2C74G-BexwhnyX.js → architectureDiagram-T3A2C74G-C7q0hhRO.js} +1 -1
- package/dist/client/assets/{blockDiagram-VBNYF7ZC-CoOttUUo.js → blockDiagram-VBNYF7ZC-C1x9Iap4.js} +1 -1
- package/dist/client/assets/{c4Diagram-5PPSVZJV-Bgp6ppUh.js → c4Diagram-5PPSVZJV-BmKaVW6q.js} +1 -1
- package/dist/client/assets/channel-Diwpee6E.js +1 -0
- package/dist/client/assets/checkbox-XfIUFPxh.js +1 -0
- package/dist/client/assets/{chunk-2GRJ4B5K-C_kzp38w.js → chunk-2GRJ4B5K-kzy_H3BQ.js} +1 -1
- package/dist/client/assets/{chunk-2Q5K7J3B-CysYnRkJ.js → chunk-2Q5K7J3B-BMj-FT4X.js} +1 -1
- package/dist/client/assets/{chunk-5RXB4S5H-oMS6x_ag.js → chunk-5RXB4S5H-DmPGiCCb.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-C5-dW5wf.js → chunk-5VM5RSS4-DYFL8__L.js} +1 -1
- package/dist/client/assets/{chunk-6Q2QTUOP-DxahInnA.js → chunk-6Q2QTUOP-CI98oVdT.js} +1 -1
- package/dist/client/assets/{chunk-GF5L2VYU-D7V-3eRg.js → chunk-GF5L2VYU-D9JiiMlX.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-BrLmjbVP.js → chunk-JWPE2WC7-D0THYpZl.js} +1 -1
- package/dist/client/assets/{chunk-KBJHAD2P-CyOvijwM.js → chunk-KBJHAD2P-Z1tJfyAr.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-BvEjVjQs.js → chunk-RYQCIY6F-31aS4cD3.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-C_IY9A64.js → chunk-XXDRQBXY-ClWDHmEp.js} +1 -1
- package/dist/client/assets/classDiagram-JCYQIIEL-BDuo9kiS.js +1 -0
- package/dist/client/assets/classDiagram-v2-OCEON4UE-BDuo9kiS.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Cu7qZi7h.js → cose-bilkent-JH36ORCC-Btg2MYLh.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-B4M1t9lL.js → cynefin-VYW2F7L2-Cbh4Pk6j.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-MW4NZA55-CAosOLmT.js → cynefinDiagram-MW4NZA55-BBqorl0u.js} +1 -1
- package/dist/client/assets/{dagre-VZM6K2ZE-q354V538.js → dagre-VZM6K2ZE-DugEItZ0.js} +1 -1
- package/dist/client/assets/{diagram-7IWD3JNH-C_CzkBPt.js → diagram-7IWD3JNH-Biu7dmH8.js} +1 -1
- package/dist/client/assets/{diagram-B4RE2ZJO-VvvYHE9c.js → diagram-B4RE2ZJO-UDm4DXFS.js} +1 -1
- package/dist/client/assets/{diagram-LBJQPF4R-D8vRQwb5.js → diagram-LBJQPF4R-Bcku4gb-.js} +1 -1
- package/dist/client/assets/{diagram-Q27KOJAE-Bul4aDKg.js → diagram-Q27KOJAE-DFRjglV-.js} +1 -1
- package/dist/client/assets/{diagram-UB23O5K3-DNadEE58.js → diagram-UB23O5K3-CgfMWtd0.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-BXEA7PRR-Bf-68MMZ.js → ebnfDiagram-BXEA7PRR-CZzYOqs0.js} +1 -1
- package/dist/client/assets/{erDiagram-JOGREHBK-CJ-jHFsf.js → erDiagram-JOGREHBK-Cdlbxgs6.js} +1 -1
- package/dist/client/assets/{flowDiagram-UKHOOZJN-DMOdinBU.js → flowDiagram-UKHOOZJN-BgImDwpP.js} +1 -1
- package/dist/client/assets/{ganttDiagram-PKOTCBZU-DzezS3Zy.js → ganttDiagram-PKOTCBZU-SFhtiyL4.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-Dftpvns8.js → gitGraphDiagram-DS77QQ5N-Bkm5ueys.js} +1 -1
- package/dist/client/assets/{index-DAUK3rnz.js → index-B5RC4THs.js} +4 -4
- package/dist/client/assets/index-Dc9xVluy.css +1 -0
- package/dist/client/assets/{infoDiagram-6WML65LV-DmgUBq17.js → infoDiagram-6WML65LV-pCYc-VX2.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-CL0PQhpQ.js → ishikawaDiagram-WSZJBQD7-BtNbG3Y6.js} +1 -1
- package/dist/client/assets/{journeyDiagram-NVQOT4AX-Ck4pQx_A.js → journeyDiagram-NVQOT4AX-CPW2qT3y.js} +1 -1
- package/dist/client/assets/{kanban-definition-27J2QSJJ-DuRZxBql.js → kanban-definition-27J2QSJJ-wD6JXQFs.js} +1 -1
- package/dist/client/assets/{linear-C2JDCaz4.js → linear-sj8MOkmS.js} +1 -1
- package/dist/client/assets/{mermaid.core-9dt2ugQA.js → mermaid.core-CGJtfmwb.js} +4 -4
- package/dist/client/assets/{mindmap-definition-FAOFIHXS-vy0fdDF0.js → mindmap-definition-FAOFIHXS-BQwLHBVs.js} +1 -1
- package/dist/client/assets/{pegDiagram-VL7TDLO6-DjVJy-Gm.js → pegDiagram-VL7TDLO6-DtDsb-Z8.js} +1 -1
- package/dist/client/assets/{pieDiagram-7S7Q4E2Y-ATbd_8vw.js → pieDiagram-7S7Q4E2Y-B3WhqNuS.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-C8o6ofcl.js → quadrantDiagram-CIZ2JOQS-CPi38pp_.js} +1 -1
- package/dist/client/assets/{railroadDiagram-AXF67PYL-Qvij9Ubo.js → railroadDiagram-AXF67PYL-Btac44Ux.js} +1 -1
- package/dist/client/assets/{render-markdown-BtCybfvQ.js → render-markdown-hAIWRfn0.js} +6 -6
- package/dist/client/assets/{requirementDiagram-LRYGKXZP-yLfrba2h.js → requirementDiagram-LRYGKXZP-CHJgDNdg.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-W5VNT64P-BTXRtTyG.js → sankeyDiagram-W5VNT64P-OYJHfmtd.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-SI44F4Z6-BN1CCZIX.js → sequenceDiagram-SI44F4Z6-9ypa9W08.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-BfXjBBYo.js → sizeCapture-X5ZJPWSS-5ZDUTERI.js} +1 -1
- package/dist/client/assets/{stateDiagram-OKZ733FA-DMAWiBsP.js → stateDiagram-OKZ733FA-nGHE1hN7.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-UEYNNEHI-CkCpE1Yu.js +1 -0
- package/dist/client/assets/{swimlanes-SLNWSIFB-BD20m5cw.js → swimlanes-SLNWSIFB-BZFv9Zp8.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-Dxlb7bju.js +8 -0
- package/dist/client/assets/tanstack-CcfAJoQ7.js +1 -0
- package/dist/client/assets/{timeline-definition-Z64GVDOM-gtc0-AHB.js → timeline-definition-Z64GVDOM-pUIcaCIE.js} +1 -1
- package/dist/client/assets/{vennDiagram-T6HMQDX7-Cdr4AXTp.js → vennDiagram-T6HMQDX7-D6dEIJmP.js} +1 -1
- package/dist/client/assets/{viewer-tabs-D-T0xQaq.js → viewer-tabs-BjG3oKQ9.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-T6FBY63Y-BcU4vxO8.js → wardleyDiagram-T6FBY63Y-DZ8ro_-y.js} +1 -1
- package/dist/client/assets/{xychartDiagram-ELKLHX3M-B3Zuadj8.js → xychartDiagram-ELKLHX3M-WJ2Zfmlk.js} +1 -1
- package/dist/client/index.html +3 -3
- package/dist/dev.mjs +2 -2
- package/dist/manifest.json +2 -2
- package/dist/server.mjs +1616 -473
- package/dist/utility.mjs +11 -5
- package/package.json +1 -1
- package/dist/client/assets/App-BXZLGT86.js +0 -74
- package/dist/client/assets/CreatePullRequestDialog-DFonMYEx.js +0 -2
- package/dist/client/assets/FilesView-Dl8QFbFA.js +0 -1
- package/dist/client/assets/GitHubAvatar-CnBaloV0.js +0 -1
- package/dist/client/assets/HistoryView-BgCZU2JO.js +0 -1
- package/dist/client/assets/LocalRefsDialog-DNgrd7Qp.js +0 -1
- package/dist/client/assets/PairingPage-C97sv-8s.js +0 -1
- package/dist/client/assets/PullRequestDiff-DhKTXyIF.js +0 -1
- package/dist/client/assets/PullRequestViewer-BgI8HFMe.js +0 -2
- package/dist/client/assets/PullRequestsView-PYWLy8We.js +0 -1
- package/dist/client/assets/SearchView-BwF_MsxK.js +0 -1
- package/dist/client/assets/SettingsDialog-DIcLmXSE.js +0 -9
- package/dist/client/assets/channel-1k1AYpyV.js +0 -1
- package/dist/client/assets/checkbox-BRH0ZNIC.js +0 -1
- package/dist/client/assets/classDiagram-JCYQIIEL-ChyNFpay.js +0 -1
- package/dist/client/assets/classDiagram-v2-OCEON4UE-ChyNFpay.js +0 -1
- package/dist/client/assets/index-BfA4tAc1.css +0 -1
- package/dist/client/assets/stateDiagram-v2-UEYNNEHI-D_ofZeaa.js +0 -1
- package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-CF9m5JSB.js +0 -8
- package/dist/client/assets/tanstack-B0Sf7wNU.js +0 -1
package/dist/server.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
2
|
import path, { extname, delimiter, resolve, join, isAbsolute, dirname } from "node:path";
|
|
3
|
-
import { randomUUID, randomBytes,
|
|
3
|
+
import { randomUUID, randomBytes, createHmac, createHash, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { stat, access, realpath, mkdir, open, rename, chmod, rm, appendFile, readFile, mkdtemp, writeFile, lstat, readdir, cp } from "node:fs/promises";
|
|
5
5
|
import os, { constants, homedir, networkInterfaces } from "node:os";
|
|
6
6
|
import { ChildProcess, execFile, spawnSync, spawn } from "node:child_process";
|
|
@@ -19,6 +19,9 @@ import path$1 from "node:path/win32";
|
|
|
19
19
|
import { createServer, isIP } from "node:net";
|
|
20
20
|
import { createServer as createServer$1 } from "node:http";
|
|
21
21
|
import WebSocket, { WebSocketServer } from "ws";
|
|
22
|
+
function aiExecutablePathsKey(paths) {
|
|
23
|
+
return JSON.stringify(["codex", "claude", "opencode", "grok"].map((id) => paths[id] ?? ""));
|
|
24
|
+
}
|
|
22
25
|
function isPlainObject$1(value) {
|
|
23
26
|
if (typeof value !== "object" || value === null) {
|
|
24
27
|
return false;
|
|
@@ -5709,7 +5712,7 @@ const createReadable = ({ subprocess, concurrentStreams, encoding }, { from, bin
|
|
|
5709
5712
|
const binary = binaryOption || BINARY_ENCODINGS.has(encoding);
|
|
5710
5713
|
const { subprocessStdout, waitReadableDestroy } = getSubprocessStdout(subprocess, from, concurrentStreams);
|
|
5711
5714
|
const { readableEncoding, readableObjectMode, readableHighWaterMark } = getReadableOptions(subprocessStdout, binary);
|
|
5712
|
-
const { read, onStdoutDataDone } = getReadableMethods({
|
|
5715
|
+
const { read: read2, onStdoutDataDone } = getReadableMethods({
|
|
5713
5716
|
subprocessStdout,
|
|
5714
5717
|
subprocess,
|
|
5715
5718
|
binary,
|
|
@@ -5717,7 +5720,7 @@ const createReadable = ({ subprocess, concurrentStreams, encoding }, { from, bin
|
|
|
5717
5720
|
preserveNewlines
|
|
5718
5721
|
});
|
|
5719
5722
|
const readable2 = new Readable({
|
|
5720
|
-
read,
|
|
5723
|
+
read: read2,
|
|
5721
5724
|
destroy: callbackify(onReadableDestroy.bind(void 0, { subprocessStdout, subprocess, waitReadableDestroy })),
|
|
5722
5725
|
highWaterMark: readableHighWaterMark,
|
|
5723
5726
|
objectMode: readableObjectMode,
|
|
@@ -6555,7 +6558,7 @@ const createDuplex = ({ subprocess, concurrentStreams, encoding }, { from, to, b
|
|
|
6555
6558
|
const { subprocessStdout, waitReadableDestroy } = getSubprocessStdout(subprocess, from, concurrentStreams);
|
|
6556
6559
|
const { subprocessStdin, waitWritableFinal, waitWritableDestroy } = getSubprocessStdin(subprocess, to, concurrentStreams);
|
|
6557
6560
|
const { readableEncoding, readableObjectMode, readableHighWaterMark } = getReadableOptions(subprocessStdout, binary);
|
|
6558
|
-
const { read, onStdoutDataDone } = getReadableMethods({
|
|
6561
|
+
const { read: read2, onStdoutDataDone } = getReadableMethods({
|
|
6559
6562
|
subprocessStdout,
|
|
6560
6563
|
subprocess,
|
|
6561
6564
|
binary,
|
|
@@ -6563,7 +6566,7 @@ const createDuplex = ({ subprocess, concurrentStreams, encoding }, { from, to, b
|
|
|
6563
6566
|
preserveNewlines
|
|
6564
6567
|
});
|
|
6565
6568
|
const duplex2 = new Duplex({
|
|
6566
|
-
read,
|
|
6569
|
+
read: read2,
|
|
6567
6570
|
...getWritableMethods(subprocessStdin, subprocess, waitWritableFinal),
|
|
6568
6571
|
destroy: callbackify(onDuplexDestroy.bind(void 0, {
|
|
6569
6572
|
subprocessStdout,
|
|
@@ -6900,9 +6903,9 @@ function pathDirectories(value, platform2) {
|
|
|
6900
6903
|
});
|
|
6901
6904
|
}
|
|
6902
6905
|
class CliEnvironment {
|
|
6903
|
-
constructor(host = currentCliHost(),
|
|
6906
|
+
constructor(host = currentCliHost(), read2 = readUserEnvironment) {
|
|
6904
6907
|
this.host = host;
|
|
6905
|
-
this.read =
|
|
6908
|
+
this.read = read2;
|
|
6906
6909
|
}
|
|
6907
6910
|
host;
|
|
6908
6911
|
read;
|
|
@@ -6947,7 +6950,7 @@ async function readUserEnvironment(host) {
|
|
|
6947
6950
|
env: host.env,
|
|
6948
6951
|
extendEnv: false,
|
|
6949
6952
|
input: "",
|
|
6950
|
-
timeout: 5e3,
|
|
6953
|
+
timeout: host.platform === "win32" ? 15e3 : 5e3,
|
|
6951
6954
|
maxBuffer: 128 * 1024,
|
|
6952
6955
|
cleanup: true,
|
|
6953
6956
|
killDescendants: true,
|
|
@@ -6961,7 +6964,8 @@ async function readUserEnvironment(host) {
|
|
|
6961
6964
|
const shell2 = path.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
6962
6965
|
const script = '$ErrorActionPreference="Stop"; $r=@{}; foreach($scope in @("Machine","User")){ $r[$scope]=[Environment]::GetEnvironmentVariables($scope) }; ConvertTo-Json -InputObject $r -Compress';
|
|
6963
6966
|
const result = await execa(shell2, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], options);
|
|
6964
|
-
if (result.
|
|
6967
|
+
if (result.timedOut) throw new Error("Environment query timed out");
|
|
6968
|
+
if (result.exitCode !== 0 || result.isMaxBuffer) throw new Error("Environment query failed");
|
|
6965
6969
|
return windowsLocations(JSON.parse(result.stdout), host.env);
|
|
6966
6970
|
}
|
|
6967
6971
|
const shell = host.shell;
|
|
@@ -7529,14 +7533,14 @@ const NUMBER_FORMAT_RANGES = {
|
|
|
7529
7533
|
float32: [-34028234663852886e22, 34028234663852886e22],
|
|
7530
7534
|
float64: [-Number.MAX_VALUE, Number.MAX_VALUE]
|
|
7531
7535
|
};
|
|
7532
|
-
function pick(
|
|
7533
|
-
const currDef =
|
|
7536
|
+
function pick(schema2, mask) {
|
|
7537
|
+
const currDef = schema2._zod.def;
|
|
7534
7538
|
const checks = currDef.checks;
|
|
7535
7539
|
const hasChecks = checks && checks.length > 0;
|
|
7536
7540
|
if (hasChecks) {
|
|
7537
7541
|
throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
7538
7542
|
}
|
|
7539
|
-
const def = mergeDefs(
|
|
7543
|
+
const def = mergeDefs(schema2._zod.def, {
|
|
7540
7544
|
get shape() {
|
|
7541
7545
|
const newShape = {};
|
|
7542
7546
|
for (const key in mask) {
|
|
@@ -7552,18 +7556,18 @@ function pick(schema, mask) {
|
|
|
7552
7556
|
},
|
|
7553
7557
|
checks: []
|
|
7554
7558
|
});
|
|
7555
|
-
return clone(
|
|
7559
|
+
return clone(schema2, def);
|
|
7556
7560
|
}
|
|
7557
|
-
function omit(
|
|
7558
|
-
const currDef =
|
|
7561
|
+
function omit(schema2, mask) {
|
|
7562
|
+
const currDef = schema2._zod.def;
|
|
7559
7563
|
const checks = currDef.checks;
|
|
7560
7564
|
const hasChecks = checks && checks.length > 0;
|
|
7561
7565
|
if (hasChecks) {
|
|
7562
7566
|
throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
7563
7567
|
}
|
|
7564
|
-
const def = mergeDefs(
|
|
7568
|
+
const def = mergeDefs(schema2._zod.def, {
|
|
7565
7569
|
get shape() {
|
|
7566
|
-
const newShape = { ...
|
|
7570
|
+
const newShape = { ...schema2._zod.def.shape };
|
|
7567
7571
|
for (const key in mask) {
|
|
7568
7572
|
if (!(key in currDef.shape)) {
|
|
7569
7573
|
throw new Error(`Unrecognized key: "${key}"`);
|
|
@@ -7577,43 +7581,43 @@ function omit(schema, mask) {
|
|
|
7577
7581
|
},
|
|
7578
7582
|
checks: []
|
|
7579
7583
|
});
|
|
7580
|
-
return clone(
|
|
7584
|
+
return clone(schema2, def);
|
|
7581
7585
|
}
|
|
7582
|
-
function extend(
|
|
7586
|
+
function extend(schema2, shape) {
|
|
7583
7587
|
if (!isPlainObject(shape)) {
|
|
7584
7588
|
throw new Error("Invalid input to extend: expected a plain object");
|
|
7585
7589
|
}
|
|
7586
|
-
const checks =
|
|
7590
|
+
const checks = schema2._zod.def.checks;
|
|
7587
7591
|
const hasChecks = checks && checks.length > 0;
|
|
7588
7592
|
if (hasChecks) {
|
|
7589
|
-
const existingShape =
|
|
7593
|
+
const existingShape = schema2._zod.def.shape;
|
|
7590
7594
|
for (const key in shape) {
|
|
7591
7595
|
if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) {
|
|
7592
7596
|
throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
|
|
7593
7597
|
}
|
|
7594
7598
|
}
|
|
7595
7599
|
}
|
|
7596
|
-
const def = mergeDefs(
|
|
7600
|
+
const def = mergeDefs(schema2._zod.def, {
|
|
7597
7601
|
get shape() {
|
|
7598
|
-
const _shape = { ...
|
|
7602
|
+
const _shape = { ...schema2._zod.def.shape, ...shape };
|
|
7599
7603
|
assignProp(this, "shape", _shape);
|
|
7600
7604
|
return _shape;
|
|
7601
7605
|
}
|
|
7602
7606
|
});
|
|
7603
|
-
return clone(
|
|
7607
|
+
return clone(schema2, def);
|
|
7604
7608
|
}
|
|
7605
|
-
function safeExtend(
|
|
7609
|
+
function safeExtend(schema2, shape) {
|
|
7606
7610
|
if (!isPlainObject(shape)) {
|
|
7607
7611
|
throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
7608
7612
|
}
|
|
7609
|
-
const def = mergeDefs(
|
|
7613
|
+
const def = mergeDefs(schema2._zod.def, {
|
|
7610
7614
|
get shape() {
|
|
7611
|
-
const _shape = { ...
|
|
7615
|
+
const _shape = { ...schema2._zod.def.shape, ...shape };
|
|
7612
7616
|
assignProp(this, "shape", _shape);
|
|
7613
7617
|
return _shape;
|
|
7614
7618
|
}
|
|
7615
7619
|
});
|
|
7616
|
-
return clone(
|
|
7620
|
+
return clone(schema2, def);
|
|
7617
7621
|
}
|
|
7618
7622
|
function merge(a2, b) {
|
|
7619
7623
|
if (a2._zod.def.checks?.length) {
|
|
@@ -7632,16 +7636,16 @@ function merge(a2, b) {
|
|
|
7632
7636
|
});
|
|
7633
7637
|
return clone(a2, def);
|
|
7634
7638
|
}
|
|
7635
|
-
function partial(Class,
|
|
7636
|
-
const currDef =
|
|
7639
|
+
function partial(Class, schema2, mask) {
|
|
7640
|
+
const currDef = schema2._zod.def;
|
|
7637
7641
|
const checks = currDef.checks;
|
|
7638
7642
|
const hasChecks = checks && checks.length > 0;
|
|
7639
7643
|
if (hasChecks) {
|
|
7640
7644
|
throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
7641
7645
|
}
|
|
7642
|
-
const def = mergeDefs(
|
|
7646
|
+
const def = mergeDefs(schema2._zod.def, {
|
|
7643
7647
|
get shape() {
|
|
7644
|
-
const oldShape =
|
|
7648
|
+
const oldShape = schema2._zod.def.shape;
|
|
7645
7649
|
const shape = { ...oldShape };
|
|
7646
7650
|
if (mask) {
|
|
7647
7651
|
for (const key in mask) {
|
|
@@ -7668,12 +7672,12 @@ function partial(Class, schema, mask) {
|
|
|
7668
7672
|
},
|
|
7669
7673
|
checks: []
|
|
7670
7674
|
});
|
|
7671
|
-
return clone(
|
|
7675
|
+
return clone(schema2, def);
|
|
7672
7676
|
}
|
|
7673
|
-
function required(Class,
|
|
7674
|
-
const def = mergeDefs(
|
|
7677
|
+
function required(Class, schema2, mask) {
|
|
7678
|
+
const def = mergeDefs(schema2._zod.def, {
|
|
7675
7679
|
get shape() {
|
|
7676
|
-
const oldShape =
|
|
7680
|
+
const oldShape = schema2._zod.def.shape;
|
|
7677
7681
|
const shape = { ...oldShape };
|
|
7678
7682
|
if (mask) {
|
|
7679
7683
|
for (const key in mask) {
|
|
@@ -7699,7 +7703,7 @@ function required(Class, schema, mask) {
|
|
|
7699
7703
|
return shape;
|
|
7700
7704
|
}
|
|
7701
7705
|
});
|
|
7702
|
-
return clone(
|
|
7706
|
+
return clone(schema2, def);
|
|
7703
7707
|
}
|
|
7704
7708
|
function aborted(x, startIndex = 0) {
|
|
7705
7709
|
if (x.aborted === true)
|
|
@@ -7828,9 +7832,9 @@ function formatError(error, mapper = (issue2) => issue2.message) {
|
|
|
7828
7832
|
processError(error);
|
|
7829
7833
|
return fieldErrors;
|
|
7830
7834
|
}
|
|
7831
|
-
const _parse = (_Err) => (
|
|
7835
|
+
const _parse = (_Err) => (schema2, value, _ctx, _params) => {
|
|
7832
7836
|
const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
|
|
7833
|
-
const result =
|
|
7837
|
+
const result = schema2._zod.run({ value, issues: [] }, ctx);
|
|
7834
7838
|
if (result instanceof Promise) {
|
|
7835
7839
|
throw new $ZodAsyncError();
|
|
7836
7840
|
}
|
|
@@ -7841,9 +7845,9 @@ const _parse = (_Err) => (schema, value, _ctx, _params) => {
|
|
|
7841
7845
|
}
|
|
7842
7846
|
return result.value;
|
|
7843
7847
|
};
|
|
7844
|
-
const _parseAsync = (_Err) => async (
|
|
7848
|
+
const _parseAsync = (_Err) => async (schema2, value, _ctx, params) => {
|
|
7845
7849
|
const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
7846
|
-
let result =
|
|
7850
|
+
let result = schema2._zod.run({ value, issues: [] }, ctx);
|
|
7847
7851
|
if (result instanceof Promise)
|
|
7848
7852
|
result = await result;
|
|
7849
7853
|
if (result.issues.length) {
|
|
@@ -7853,9 +7857,9 @@ const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
|
|
|
7853
7857
|
}
|
|
7854
7858
|
return result.value;
|
|
7855
7859
|
};
|
|
7856
|
-
const _safeParse = (_Err) => (
|
|
7860
|
+
const _safeParse = (_Err) => (schema2, value, _ctx) => {
|
|
7857
7861
|
const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
|
|
7858
|
-
const result =
|
|
7862
|
+
const result = schema2._zod.run({ value, issues: [] }, ctx);
|
|
7859
7863
|
if (result instanceof Promise) {
|
|
7860
7864
|
throw new $ZodAsyncError();
|
|
7861
7865
|
}
|
|
@@ -7865,9 +7869,9 @@ const _safeParse = (_Err) => (schema, value, _ctx) => {
|
|
|
7865
7869
|
} : { success: true, data: result.value };
|
|
7866
7870
|
};
|
|
7867
7871
|
const safeParse$1 = /* @__PURE__ */ _safeParse($ZodRealError);
|
|
7868
|
-
const _safeParseAsync = (_Err) => async (
|
|
7872
|
+
const _safeParseAsync = (_Err) => async (schema2, value, _ctx) => {
|
|
7869
7873
|
const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
7870
|
-
let result =
|
|
7874
|
+
let result = schema2._zod.run({ value, issues: [] }, ctx);
|
|
7871
7875
|
if (result instanceof Promise)
|
|
7872
7876
|
result = await result;
|
|
7873
7877
|
return result.issues.length ? {
|
|
@@ -7876,33 +7880,33 @@ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
|
|
|
7876
7880
|
} : { success: true, data: result.value };
|
|
7877
7881
|
};
|
|
7878
7882
|
const safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync($ZodRealError);
|
|
7879
|
-
const _encode = (_Err) => (
|
|
7883
|
+
const _encode = (_Err) => (schema2, value, _ctx) => {
|
|
7880
7884
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
7881
|
-
return _parse(_Err)(
|
|
7885
|
+
return _parse(_Err)(schema2, value, ctx);
|
|
7882
7886
|
};
|
|
7883
|
-
const _decode = (_Err) => (
|
|
7884
|
-
return _parse(_Err)(
|
|
7887
|
+
const _decode = (_Err) => (schema2, value, _ctx) => {
|
|
7888
|
+
return _parse(_Err)(schema2, value, _ctx);
|
|
7885
7889
|
};
|
|
7886
|
-
const _encodeAsync = (_Err) => async (
|
|
7890
|
+
const _encodeAsync = (_Err) => async (schema2, value, _ctx) => {
|
|
7887
7891
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
7888
|
-
return _parseAsync(_Err)(
|
|
7892
|
+
return _parseAsync(_Err)(schema2, value, ctx);
|
|
7889
7893
|
};
|
|
7890
|
-
const _decodeAsync = (_Err) => async (
|
|
7891
|
-
return _parseAsync(_Err)(
|
|
7894
|
+
const _decodeAsync = (_Err) => async (schema2, value, _ctx) => {
|
|
7895
|
+
return _parseAsync(_Err)(schema2, value, _ctx);
|
|
7892
7896
|
};
|
|
7893
|
-
const _safeEncode = (_Err) => (
|
|
7897
|
+
const _safeEncode = (_Err) => (schema2, value, _ctx) => {
|
|
7894
7898
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
7895
|
-
return _safeParse(_Err)(
|
|
7899
|
+
return _safeParse(_Err)(schema2, value, ctx);
|
|
7896
7900
|
};
|
|
7897
|
-
const _safeDecode = (_Err) => (
|
|
7898
|
-
return _safeParse(_Err)(
|
|
7901
|
+
const _safeDecode = (_Err) => (schema2, value, _ctx) => {
|
|
7902
|
+
return _safeParse(_Err)(schema2, value, _ctx);
|
|
7899
7903
|
};
|
|
7900
|
-
const _safeEncodeAsync = (_Err) => async (
|
|
7904
|
+
const _safeEncodeAsync = (_Err) => async (schema2, value, _ctx) => {
|
|
7901
7905
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
7902
|
-
return _safeParseAsync(_Err)(
|
|
7906
|
+
return _safeParseAsync(_Err)(schema2, value, ctx);
|
|
7903
7907
|
};
|
|
7904
|
-
const _safeDecodeAsync = (_Err) => async (
|
|
7905
|
-
return _safeParseAsync(_Err)(
|
|
7908
|
+
const _safeDecodeAsync = (_Err) => async (schema2, value, _ctx) => {
|
|
7909
|
+
return _safeParseAsync(_Err)(schema2, value, _ctx);
|
|
7906
7910
|
};
|
|
7907
7911
|
const cuid = /^[cC][0-9a-z]{6,}$/;
|
|
7908
7912
|
const cuid2 = /^[0-9a-z]+$/;
|
|
@@ -9072,9 +9076,9 @@ const $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def)
|
|
|
9072
9076
|
for (const key of normalized.keys) {
|
|
9073
9077
|
const id = ids[key];
|
|
9074
9078
|
const k = esc(key);
|
|
9075
|
-
const
|
|
9076
|
-
const isOptionalIn =
|
|
9077
|
-
const isOptionalOut =
|
|
9079
|
+
const schema2 = shape[key];
|
|
9080
|
+
const isOptionalIn = schema2?._zod?.optin === "optional";
|
|
9081
|
+
const isOptionalOut = schema2?._zod?.optout === "optional";
|
|
9078
9082
|
doc.write(`const ${id} = ${parseStr(key)};`);
|
|
9079
9083
|
if (isOptionalIn && isOptionalOut) {
|
|
9080
9084
|
doc.write(`
|
|
@@ -9243,6 +9247,73 @@ const $ZodUnion = /* @__PURE__ */ $constructor("$ZodUnion", (inst, def) => {
|
|
|
9243
9247
|
});
|
|
9244
9248
|
};
|
|
9245
9249
|
});
|
|
9250
|
+
const $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnion", (inst, def) => {
|
|
9251
|
+
def.inclusive = false;
|
|
9252
|
+
$ZodUnion.init(inst, def);
|
|
9253
|
+
const _super = inst._zod.parse;
|
|
9254
|
+
defineLazy(inst._zod, "propValues", () => {
|
|
9255
|
+
const propValues = {};
|
|
9256
|
+
for (const option of def.options) {
|
|
9257
|
+
const pv = option._zod.propValues;
|
|
9258
|
+
if (!pv || Object.keys(pv).length === 0)
|
|
9259
|
+
throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
|
|
9260
|
+
for (const [k, v] of Object.entries(pv)) {
|
|
9261
|
+
if (!propValues[k])
|
|
9262
|
+
propValues[k] = /* @__PURE__ */ new Set();
|
|
9263
|
+
for (const val of v) {
|
|
9264
|
+
propValues[k].add(val);
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
}
|
|
9268
|
+
return propValues;
|
|
9269
|
+
});
|
|
9270
|
+
const disc = cached(() => {
|
|
9271
|
+
const opts = def.options;
|
|
9272
|
+
const map = /* @__PURE__ */ new Map();
|
|
9273
|
+
for (const o2 of opts) {
|
|
9274
|
+
const values = o2._zod.propValues?.[def.discriminator];
|
|
9275
|
+
if (!values || values.size === 0)
|
|
9276
|
+
throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o2)}"`);
|
|
9277
|
+
for (const v of values) {
|
|
9278
|
+
if (map.has(v)) {
|
|
9279
|
+
throw new Error(`Duplicate discriminator value "${String(v)}"`);
|
|
9280
|
+
}
|
|
9281
|
+
map.set(v, o2);
|
|
9282
|
+
}
|
|
9283
|
+
}
|
|
9284
|
+
return map;
|
|
9285
|
+
});
|
|
9286
|
+
inst._zod.parse = (payload, ctx) => {
|
|
9287
|
+
const input = payload.value;
|
|
9288
|
+
if (!isObject(input)) {
|
|
9289
|
+
payload.issues.push({
|
|
9290
|
+
code: "invalid_type",
|
|
9291
|
+
expected: "object",
|
|
9292
|
+
input,
|
|
9293
|
+
inst
|
|
9294
|
+
});
|
|
9295
|
+
return payload;
|
|
9296
|
+
}
|
|
9297
|
+
const opt = disc.value.get(input?.[def.discriminator]);
|
|
9298
|
+
if (opt) {
|
|
9299
|
+
return opt._zod.run(payload, ctx);
|
|
9300
|
+
}
|
|
9301
|
+
if (def.unionFallback || ctx.direction === "backward") {
|
|
9302
|
+
return _super(payload, ctx);
|
|
9303
|
+
}
|
|
9304
|
+
payload.issues.push({
|
|
9305
|
+
code: "invalid_union",
|
|
9306
|
+
errors: [],
|
|
9307
|
+
note: "No matching discriminator",
|
|
9308
|
+
discriminator: def.discriminator,
|
|
9309
|
+
options: Array.from(disc.value.keys()),
|
|
9310
|
+
input,
|
|
9311
|
+
path: [def.discriminator],
|
|
9312
|
+
inst
|
|
9313
|
+
});
|
|
9314
|
+
return payload;
|
|
9315
|
+
};
|
|
9316
|
+
});
|
|
9246
9317
|
const $ZodIntersection = /* @__PURE__ */ $constructor("$ZodIntersection", (inst, def) => {
|
|
9247
9318
|
$ZodType.init(inst, def);
|
|
9248
9319
|
inst._zod.parse = (payload, ctx) => {
|
|
@@ -9487,6 +9558,28 @@ const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
|
9487
9558
|
return payload;
|
|
9488
9559
|
};
|
|
9489
9560
|
});
|
|
9561
|
+
const $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
9562
|
+
$ZodType.init(inst, def);
|
|
9563
|
+
if (def.values.length === 0) {
|
|
9564
|
+
throw new Error("Cannot create literal schema with no valid values");
|
|
9565
|
+
}
|
|
9566
|
+
const values = new Set(def.values);
|
|
9567
|
+
inst._zod.values = values;
|
|
9568
|
+
inst._zod.pattern = new RegExp(`^(${def.values.map((o2) => typeof o2 === "string" ? escapeRegex(o2) : o2 ? escapeRegex(o2.toString()) : String(o2)).join("|")})$`);
|
|
9569
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
9570
|
+
const input = payload.value;
|
|
9571
|
+
if (values.has(input)) {
|
|
9572
|
+
return payload;
|
|
9573
|
+
}
|
|
9574
|
+
payload.issues.push({
|
|
9575
|
+
code: "invalid_value",
|
|
9576
|
+
values: def.values,
|
|
9577
|
+
input,
|
|
9578
|
+
inst
|
|
9579
|
+
});
|
|
9580
|
+
return payload;
|
|
9581
|
+
};
|
|
9582
|
+
});
|
|
9490
9583
|
const $ZodTransform = /* @__PURE__ */ $constructor("$ZodTransform", (inst, def) => {
|
|
9491
9584
|
$ZodType.init(inst, def);
|
|
9492
9585
|
inst._zod.optin = "optional";
|
|
@@ -9761,11 +9854,11 @@ class $ZodRegistry {
|
|
|
9761
9854
|
this._map = /* @__PURE__ */ new WeakMap();
|
|
9762
9855
|
this._idmap = /* @__PURE__ */ new Map();
|
|
9763
9856
|
}
|
|
9764
|
-
add(
|
|
9857
|
+
add(schema2, ..._meta) {
|
|
9765
9858
|
const meta = _meta[0];
|
|
9766
|
-
this._map.set(
|
|
9859
|
+
this._map.set(schema2, meta);
|
|
9767
9860
|
if (meta && typeof meta === "object" && "id" in meta) {
|
|
9768
|
-
this._idmap.set(meta.id,
|
|
9861
|
+
this._idmap.set(meta.id, schema2);
|
|
9769
9862
|
}
|
|
9770
9863
|
return this;
|
|
9771
9864
|
}
|
|
@@ -9774,26 +9867,26 @@ class $ZodRegistry {
|
|
|
9774
9867
|
this._idmap = /* @__PURE__ */ new Map();
|
|
9775
9868
|
return this;
|
|
9776
9869
|
}
|
|
9777
|
-
remove(
|
|
9778
|
-
const meta = this._map.get(
|
|
9870
|
+
remove(schema2) {
|
|
9871
|
+
const meta = this._map.get(schema2);
|
|
9779
9872
|
if (meta && typeof meta === "object" && "id" in meta) {
|
|
9780
9873
|
this._idmap.delete(meta.id);
|
|
9781
9874
|
}
|
|
9782
|
-
this._map.delete(
|
|
9875
|
+
this._map.delete(schema2);
|
|
9783
9876
|
return this;
|
|
9784
9877
|
}
|
|
9785
|
-
get(
|
|
9786
|
-
const p =
|
|
9878
|
+
get(schema2) {
|
|
9879
|
+
const p = schema2._zod.parent;
|
|
9787
9880
|
if (p) {
|
|
9788
9881
|
const pm = { ...this.get(p) ?? {} };
|
|
9789
9882
|
delete pm.id;
|
|
9790
|
-
const f = { ...pm, ...this._map.get(
|
|
9883
|
+
const f = { ...pm, ...this._map.get(schema2) };
|
|
9791
9884
|
return Object.keys(f).length ? f : void 0;
|
|
9792
9885
|
}
|
|
9793
|
-
return this._map.get(
|
|
9886
|
+
return this._map.get(schema2);
|
|
9794
9887
|
}
|
|
9795
|
-
has(
|
|
9796
|
-
return this._map.has(
|
|
9888
|
+
has(schema2) {
|
|
9889
|
+
return this._map.has(schema2);
|
|
9797
9890
|
}
|
|
9798
9891
|
}
|
|
9799
9892
|
function registry() {
|
|
@@ -10270,13 +10363,13 @@ function _array(Class, element, params) {
|
|
|
10270
10363
|
}
|
|
10271
10364
|
// @__NO_SIDE_EFFECTS__
|
|
10272
10365
|
function _refine(Class, fn, _params) {
|
|
10273
|
-
const
|
|
10366
|
+
const schema2 = new Class({
|
|
10274
10367
|
type: "custom",
|
|
10275
10368
|
check: "custom",
|
|
10276
10369
|
fn,
|
|
10277
10370
|
...normalizeParams(_params)
|
|
10278
10371
|
});
|
|
10279
|
-
return
|
|
10372
|
+
return schema2;
|
|
10280
10373
|
}
|
|
10281
10374
|
// @__NO_SIDE_EFFECTS__
|
|
10282
10375
|
function _superRefine(fn, params) {
|
|
@@ -10329,40 +10422,40 @@ function initializeContext(params) {
|
|
|
10329
10422
|
external: params?.external ?? void 0
|
|
10330
10423
|
};
|
|
10331
10424
|
}
|
|
10332
|
-
function process$1(
|
|
10425
|
+
function process$1(schema2, ctx, _params = { path: [], schemaPath: [] }) {
|
|
10333
10426
|
var _a2;
|
|
10334
|
-
const def =
|
|
10335
|
-
const seen = ctx.seen.get(
|
|
10427
|
+
const def = schema2._zod.def;
|
|
10428
|
+
const seen = ctx.seen.get(schema2);
|
|
10336
10429
|
if (seen) {
|
|
10337
10430
|
seen.count++;
|
|
10338
|
-
const isCycle = _params.schemaPath.includes(
|
|
10431
|
+
const isCycle = _params.schemaPath.includes(schema2);
|
|
10339
10432
|
if (isCycle) {
|
|
10340
10433
|
seen.cycle = _params.path;
|
|
10341
10434
|
}
|
|
10342
10435
|
return seen.schema;
|
|
10343
10436
|
}
|
|
10344
10437
|
const result = { schema: {}, count: 1, cycle: void 0, path: _params.path };
|
|
10345
|
-
ctx.seen.set(
|
|
10346
|
-
const overrideSchema =
|
|
10438
|
+
ctx.seen.set(schema2, result);
|
|
10439
|
+
const overrideSchema = schema2._zod.toJSONSchema?.();
|
|
10347
10440
|
if (overrideSchema) {
|
|
10348
10441
|
result.schema = overrideSchema;
|
|
10349
10442
|
} else {
|
|
10350
10443
|
const params = {
|
|
10351
10444
|
..._params,
|
|
10352
|
-
schemaPath: [..._params.schemaPath,
|
|
10445
|
+
schemaPath: [..._params.schemaPath, schema2],
|
|
10353
10446
|
path: _params.path
|
|
10354
10447
|
};
|
|
10355
|
-
if (
|
|
10356
|
-
|
|
10448
|
+
if (schema2._zod.processJSONSchema) {
|
|
10449
|
+
schema2._zod.processJSONSchema(ctx, result.schema, params);
|
|
10357
10450
|
} else {
|
|
10358
10451
|
const _json = result.schema;
|
|
10359
10452
|
const processor = ctx.processors[def.type];
|
|
10360
10453
|
if (!processor) {
|
|
10361
10454
|
throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);
|
|
10362
10455
|
}
|
|
10363
|
-
processor(
|
|
10456
|
+
processor(schema2, ctx, _json, params);
|
|
10364
10457
|
}
|
|
10365
|
-
const parent =
|
|
10458
|
+
const parent = schema2._zod.parent;
|
|
10366
10459
|
if (parent) {
|
|
10367
10460
|
if (!result.ref)
|
|
10368
10461
|
result.ref = parent;
|
|
@@ -10370,21 +10463,21 @@ function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
|
|
|
10370
10463
|
ctx.seen.get(parent).isParent = true;
|
|
10371
10464
|
}
|
|
10372
10465
|
}
|
|
10373
|
-
const meta = ctx.metadataRegistry.get(
|
|
10466
|
+
const meta = ctx.metadataRegistry.get(schema2);
|
|
10374
10467
|
if (meta)
|
|
10375
10468
|
Object.assign(result.schema, meta);
|
|
10376
|
-
if (ctx.io === "input" && isTransforming(
|
|
10469
|
+
if (ctx.io === "input" && isTransforming(schema2)) {
|
|
10377
10470
|
delete result.schema.examples;
|
|
10378
10471
|
delete result.schema.default;
|
|
10379
10472
|
}
|
|
10380
10473
|
if (ctx.io === "input" && "_prefault" in result.schema)
|
|
10381
10474
|
(_a2 = result.schema).default ?? (_a2.default = result.schema._prefault);
|
|
10382
10475
|
delete result.schema._prefault;
|
|
10383
|
-
const _result = ctx.seen.get(
|
|
10476
|
+
const _result = ctx.seen.get(schema2);
|
|
10384
10477
|
return _result.schema;
|
|
10385
10478
|
}
|
|
10386
|
-
function extractDefs(ctx,
|
|
10387
|
-
const root = ctx.seen.get(
|
|
10479
|
+
function extractDefs(ctx, schema2) {
|
|
10480
|
+
const root = ctx.seen.get(schema2);
|
|
10388
10481
|
if (!root)
|
|
10389
10482
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
10390
10483
|
const idToSchema = /* @__PURE__ */ new Map();
|
|
@@ -10427,11 +10520,11 @@ function extractDefs(ctx, schema) {
|
|
|
10427
10520
|
seen.def = { ...seen.schema };
|
|
10428
10521
|
if (defId)
|
|
10429
10522
|
seen.defId = defId;
|
|
10430
|
-
const
|
|
10431
|
-
for (const key in
|
|
10432
|
-
delete
|
|
10523
|
+
const schema3 = seen.schema;
|
|
10524
|
+
for (const key in schema3) {
|
|
10525
|
+
delete schema3[key];
|
|
10433
10526
|
}
|
|
10434
|
-
|
|
10527
|
+
schema3.$ref = ref;
|
|
10435
10528
|
};
|
|
10436
10529
|
if (ctx.cycles === "throw") {
|
|
10437
10530
|
for (const entry of ctx.seen.entries()) {
|
|
@@ -10445,13 +10538,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10445
10538
|
}
|
|
10446
10539
|
for (const entry of ctx.seen.entries()) {
|
|
10447
10540
|
const seen = entry[1];
|
|
10448
|
-
if (
|
|
10541
|
+
if (schema2 === entry[0]) {
|
|
10449
10542
|
extractToDef(entry);
|
|
10450
10543
|
continue;
|
|
10451
10544
|
}
|
|
10452
10545
|
if (ctx.external) {
|
|
10453
10546
|
const ext = ctx.external.registry.get(entry[0])?.id;
|
|
10454
|
-
if (
|
|
10547
|
+
if (schema2 !== entry[0] && ext) {
|
|
10455
10548
|
extractToDef(entry);
|
|
10456
10549
|
continue;
|
|
10457
10550
|
}
|
|
@@ -10473,16 +10566,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10473
10566
|
}
|
|
10474
10567
|
}
|
|
10475
10568
|
}
|
|
10476
|
-
function finalize(ctx,
|
|
10477
|
-
const root = ctx.seen.get(
|
|
10569
|
+
function finalize(ctx, schema2) {
|
|
10570
|
+
const root = ctx.seen.get(schema2);
|
|
10478
10571
|
if (!root)
|
|
10479
10572
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
10480
10573
|
const flattenRef = (zodSchema) => {
|
|
10481
10574
|
const seen = ctx.seen.get(zodSchema);
|
|
10482
10575
|
if (seen.ref === null)
|
|
10483
10576
|
return;
|
|
10484
|
-
const
|
|
10485
|
-
const _cached = { ...
|
|
10577
|
+
const schema3 = seen.def ?? seen.schema;
|
|
10578
|
+
const _cached = { ...schema3 };
|
|
10486
10579
|
const ref = seen.ref;
|
|
10487
10580
|
seen.ref = null;
|
|
10488
10581
|
if (ref) {
|
|
@@ -10490,28 +10583,28 @@ function finalize(ctx, schema) {
|
|
|
10490
10583
|
const refSeen = ctx.seen.get(ref);
|
|
10491
10584
|
const refSchema = refSeen.schema;
|
|
10492
10585
|
if (refSchema.$ref && (ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0")) {
|
|
10493
|
-
|
|
10494
|
-
|
|
10586
|
+
schema3.allOf = schema3.allOf ?? [];
|
|
10587
|
+
schema3.allOf.push(refSchema);
|
|
10495
10588
|
} else {
|
|
10496
|
-
Object.assign(
|
|
10589
|
+
Object.assign(schema3, refSchema);
|
|
10497
10590
|
}
|
|
10498
|
-
Object.assign(
|
|
10591
|
+
Object.assign(schema3, _cached);
|
|
10499
10592
|
const isParentRef = zodSchema._zod.parent === ref;
|
|
10500
10593
|
if (isParentRef) {
|
|
10501
|
-
for (const key in
|
|
10594
|
+
for (const key in schema3) {
|
|
10502
10595
|
if (key === "$ref" || key === "allOf")
|
|
10503
10596
|
continue;
|
|
10504
10597
|
if (!(key in _cached)) {
|
|
10505
|
-
delete
|
|
10598
|
+
delete schema3[key];
|
|
10506
10599
|
}
|
|
10507
10600
|
}
|
|
10508
10601
|
}
|
|
10509
10602
|
if (refSchema.$ref && refSeen.def) {
|
|
10510
|
-
for (const key in
|
|
10603
|
+
for (const key in schema3) {
|
|
10511
10604
|
if (key === "$ref" || key === "allOf")
|
|
10512
10605
|
continue;
|
|
10513
|
-
if (key in refSeen.def && JSON.stringify(
|
|
10514
|
-
delete
|
|
10606
|
+
if (key in refSeen.def && JSON.stringify(schema3[key]) === JSON.stringify(refSeen.def[key])) {
|
|
10607
|
+
delete schema3[key];
|
|
10515
10608
|
}
|
|
10516
10609
|
}
|
|
10517
10610
|
}
|
|
@@ -10521,13 +10614,13 @@ function finalize(ctx, schema) {
|
|
|
10521
10614
|
flattenRef(parent);
|
|
10522
10615
|
const parentSeen = ctx.seen.get(parent);
|
|
10523
10616
|
if (parentSeen?.schema.$ref) {
|
|
10524
|
-
|
|
10617
|
+
schema3.$ref = parentSeen.schema.$ref;
|
|
10525
10618
|
if (parentSeen.def) {
|
|
10526
|
-
for (const key in
|
|
10619
|
+
for (const key in schema3) {
|
|
10527
10620
|
if (key === "$ref" || key === "allOf")
|
|
10528
10621
|
continue;
|
|
10529
|
-
if (key in parentSeen.def && JSON.stringify(
|
|
10530
|
-
delete
|
|
10622
|
+
if (key in parentSeen.def && JSON.stringify(schema3[key]) === JSON.stringify(parentSeen.def[key])) {
|
|
10623
|
+
delete schema3[key];
|
|
10531
10624
|
}
|
|
10532
10625
|
}
|
|
10533
10626
|
}
|
|
@@ -10535,7 +10628,7 @@ function finalize(ctx, schema) {
|
|
|
10535
10628
|
}
|
|
10536
10629
|
ctx.override({
|
|
10537
10630
|
zodSchema,
|
|
10538
|
-
jsonSchema:
|
|
10631
|
+
jsonSchema: schema3,
|
|
10539
10632
|
path: seen.path ?? []
|
|
10540
10633
|
});
|
|
10541
10634
|
};
|
|
@@ -10552,13 +10645,13 @@ function finalize(ctx, schema) {
|
|
|
10552
10645
|
} else if (ctx.target === "openapi-3.0") ;
|
|
10553
10646
|
else ;
|
|
10554
10647
|
if (ctx.external?.uri) {
|
|
10555
|
-
const id = ctx.external.registry.get(
|
|
10648
|
+
const id = ctx.external.registry.get(schema2)?.id;
|
|
10556
10649
|
if (!id)
|
|
10557
10650
|
throw new Error("Schema is missing an `id` property");
|
|
10558
10651
|
result.$id = ctx.external.uri(id);
|
|
10559
10652
|
}
|
|
10560
10653
|
Object.assign(result, root.def ?? root.schema);
|
|
10561
|
-
const rootMetaId = ctx.metadataRegistry.get(
|
|
10654
|
+
const rootMetaId = ctx.metadataRegistry.get(schema2)?.id;
|
|
10562
10655
|
if (rootMetaId !== void 0 && result.id === rootMetaId)
|
|
10563
10656
|
delete result.id;
|
|
10564
10657
|
const defs = ctx.external?.defs ?? {};
|
|
@@ -10584,10 +10677,10 @@ function finalize(ctx, schema) {
|
|
|
10584
10677
|
const finalized = JSON.parse(JSON.stringify(result));
|
|
10585
10678
|
Object.defineProperty(finalized, "~standard", {
|
|
10586
10679
|
value: {
|
|
10587
|
-
...
|
|
10680
|
+
...schema2["~standard"],
|
|
10588
10681
|
jsonSchema: {
|
|
10589
|
-
input: createStandardJSONSchemaMethod(
|
|
10590
|
-
output: createStandardJSONSchemaMethod(
|
|
10682
|
+
input: createStandardJSONSchemaMethod(schema2, "input", ctx.processors),
|
|
10683
|
+
output: createStandardJSONSchemaMethod(schema2, "output", ctx.processors)
|
|
10591
10684
|
}
|
|
10592
10685
|
},
|
|
10593
10686
|
enumerable: false,
|
|
@@ -10651,18 +10744,18 @@ function isTransforming(_schema, _ctx) {
|
|
|
10651
10744
|
}
|
|
10652
10745
|
return false;
|
|
10653
10746
|
}
|
|
10654
|
-
const createToJSONSchemaMethod = (
|
|
10747
|
+
const createToJSONSchemaMethod = (schema2, processors = {}) => (params) => {
|
|
10655
10748
|
const ctx = initializeContext({ ...params, processors });
|
|
10656
|
-
process$1(
|
|
10657
|
-
extractDefs(ctx,
|
|
10658
|
-
return finalize(ctx,
|
|
10749
|
+
process$1(schema2, ctx);
|
|
10750
|
+
extractDefs(ctx, schema2);
|
|
10751
|
+
return finalize(ctx, schema2);
|
|
10659
10752
|
};
|
|
10660
|
-
const createStandardJSONSchemaMethod = (
|
|
10753
|
+
const createStandardJSONSchemaMethod = (schema2, io, processors = {}) => (params) => {
|
|
10661
10754
|
const { libraryOptions, target } = params ?? {};
|
|
10662
10755
|
const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
|
|
10663
|
-
process$1(
|
|
10664
|
-
extractDefs(ctx,
|
|
10665
|
-
return finalize(ctx,
|
|
10756
|
+
process$1(schema2, ctx);
|
|
10757
|
+
extractDefs(ctx, schema2);
|
|
10758
|
+
return finalize(ctx, schema2);
|
|
10666
10759
|
};
|
|
10667
10760
|
const formatMap = {
|
|
10668
10761
|
guid: "uuid",
|
|
@@ -10672,10 +10765,10 @@ const formatMap = {
|
|
|
10672
10765
|
regex: ""
|
|
10673
10766
|
// do not set
|
|
10674
10767
|
};
|
|
10675
|
-
const stringProcessor = (
|
|
10768
|
+
const stringProcessor = (schema2, ctx, _json, _params) => {
|
|
10676
10769
|
const json2 = _json;
|
|
10677
10770
|
json2.type = "string";
|
|
10678
|
-
const { minimum, maximum, format: format2, patterns, contentEncoding } =
|
|
10771
|
+
const { minimum, maximum, format: format2, patterns, contentEncoding } = schema2._zod.bag;
|
|
10679
10772
|
if (typeof minimum === "number")
|
|
10680
10773
|
json2.minLength = minimum;
|
|
10681
10774
|
if (typeof maximum === "number")
|
|
@@ -10704,9 +10797,9 @@ const stringProcessor = (schema, ctx, _json, _params) => {
|
|
|
10704
10797
|
}
|
|
10705
10798
|
}
|
|
10706
10799
|
};
|
|
10707
|
-
const numberProcessor = (
|
|
10800
|
+
const numberProcessor = (schema2, ctx, _json, _params) => {
|
|
10708
10801
|
const json2 = _json;
|
|
10709
|
-
const { minimum, maximum, format: format2, multipleOf, exclusiveMaximum, exclusiveMinimum } =
|
|
10802
|
+
const { minimum, maximum, format: format2, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema2._zod.bag;
|
|
10710
10803
|
if (typeof format2 === "string" && format2.includes("int"))
|
|
10711
10804
|
json2.type = "integer";
|
|
10712
10805
|
else
|
|
@@ -10781,8 +10874,8 @@ const dateProcessor = (_schema, ctx, _json, _params) => {
|
|
|
10781
10874
|
throw new Error("Date cannot be represented in JSON Schema");
|
|
10782
10875
|
}
|
|
10783
10876
|
};
|
|
10784
|
-
const enumProcessor = (
|
|
10785
|
-
const def =
|
|
10877
|
+
const enumProcessor = (schema2, _ctx, json2, _params) => {
|
|
10878
|
+
const def = schema2._zod.def;
|
|
10786
10879
|
const values = getEnumValues(def.entries);
|
|
10787
10880
|
if (values.every((v) => typeof v === "number"))
|
|
10788
10881
|
json2.type = "number";
|
|
@@ -10790,8 +10883,8 @@ const enumProcessor = (schema, _ctx, json2, _params) => {
|
|
|
10790
10883
|
json2.type = "string";
|
|
10791
10884
|
json2.enum = values;
|
|
10792
10885
|
};
|
|
10793
|
-
const literalProcessor = (
|
|
10794
|
-
const def =
|
|
10886
|
+
const literalProcessor = (schema2, ctx, json2, _params) => {
|
|
10887
|
+
const def = schema2._zod.def;
|
|
10795
10888
|
const vals = [];
|
|
10796
10889
|
for (const val of def.values) {
|
|
10797
10890
|
if (val === void 0) {
|
|
@@ -10834,22 +10927,22 @@ const nanProcessor = (_schema, ctx, _json, _params) => {
|
|
|
10834
10927
|
throw new Error("NaN cannot be represented in JSON Schema");
|
|
10835
10928
|
}
|
|
10836
10929
|
};
|
|
10837
|
-
const templateLiteralProcessor = (
|
|
10930
|
+
const templateLiteralProcessor = (schema2, _ctx, json2, _params) => {
|
|
10838
10931
|
const _json = json2;
|
|
10839
|
-
const pattern =
|
|
10932
|
+
const pattern = schema2._zod.pattern;
|
|
10840
10933
|
if (!pattern)
|
|
10841
10934
|
throw new Error("Pattern not found in template literal");
|
|
10842
10935
|
_json.type = "string";
|
|
10843
10936
|
_json.pattern = pattern.source;
|
|
10844
10937
|
};
|
|
10845
|
-
const fileProcessor = (
|
|
10938
|
+
const fileProcessor = (schema2, _ctx, json2, _params) => {
|
|
10846
10939
|
const _json = json2;
|
|
10847
10940
|
const file = {
|
|
10848
10941
|
type: "string",
|
|
10849
10942
|
format: "binary",
|
|
10850
10943
|
contentEncoding: "binary"
|
|
10851
10944
|
};
|
|
10852
|
-
const { minimum, maximum, mime } =
|
|
10945
|
+
const { minimum, maximum, mime } = schema2._zod.bag;
|
|
10853
10946
|
if (minimum !== void 0)
|
|
10854
10947
|
file.minLength = minimum;
|
|
10855
10948
|
if (maximum !== void 0)
|
|
@@ -10894,10 +10987,10 @@ const setProcessor = (_schema, ctx, _json, _params) => {
|
|
|
10894
10987
|
throw new Error("Set cannot be represented in JSON Schema");
|
|
10895
10988
|
}
|
|
10896
10989
|
};
|
|
10897
|
-
const arrayProcessor = (
|
|
10990
|
+
const arrayProcessor = (schema2, ctx, _json, params) => {
|
|
10898
10991
|
const json2 = _json;
|
|
10899
|
-
const def =
|
|
10900
|
-
const { minimum, maximum } =
|
|
10992
|
+
const def = schema2._zod.def;
|
|
10993
|
+
const { minimum, maximum } = schema2._zod.bag;
|
|
10901
10994
|
if (typeof minimum === "number")
|
|
10902
10995
|
json2.minItems = minimum;
|
|
10903
10996
|
if (typeof maximum === "number")
|
|
@@ -10908,9 +11001,9 @@ const arrayProcessor = (schema, ctx, _json, params) => {
|
|
|
10908
11001
|
path: [...params.path, "items"]
|
|
10909
11002
|
});
|
|
10910
11003
|
};
|
|
10911
|
-
const objectProcessor = (
|
|
11004
|
+
const objectProcessor = (schema2, ctx, _json, params) => {
|
|
10912
11005
|
const json2 = _json;
|
|
10913
|
-
const def =
|
|
11006
|
+
const def = schema2._zod.def;
|
|
10914
11007
|
json2.type = "object";
|
|
10915
11008
|
json2.properties = {};
|
|
10916
11009
|
const shape = def.shape;
|
|
@@ -10944,8 +11037,8 @@ const objectProcessor = (schema, ctx, _json, params) => {
|
|
|
10944
11037
|
});
|
|
10945
11038
|
}
|
|
10946
11039
|
};
|
|
10947
|
-
const unionProcessor = (
|
|
10948
|
-
const def =
|
|
11040
|
+
const unionProcessor = (schema2, ctx, json2, params) => {
|
|
11041
|
+
const def = schema2._zod.def;
|
|
10949
11042
|
const isExclusive = def.inclusive === false;
|
|
10950
11043
|
const options = def.options.map((x, i2) => process$1(x, ctx, {
|
|
10951
11044
|
...params,
|
|
@@ -10957,8 +11050,8 @@ const unionProcessor = (schema, ctx, json2, params) => {
|
|
|
10957
11050
|
json2.anyOf = options;
|
|
10958
11051
|
}
|
|
10959
11052
|
};
|
|
10960
|
-
const intersectionProcessor = (
|
|
10961
|
-
const def =
|
|
11053
|
+
const intersectionProcessor = (schema2, ctx, json2, params) => {
|
|
11054
|
+
const def = schema2._zod.def;
|
|
10962
11055
|
const a2 = process$1(def.left, ctx, {
|
|
10963
11056
|
...params,
|
|
10964
11057
|
path: [...params.path, "allOf", 0]
|
|
@@ -10974,9 +11067,9 @@ const intersectionProcessor = (schema, ctx, json2, params) => {
|
|
|
10974
11067
|
];
|
|
10975
11068
|
json2.allOf = allOf;
|
|
10976
11069
|
};
|
|
10977
|
-
const tupleProcessor = (
|
|
11070
|
+
const tupleProcessor = (schema2, ctx, _json, params) => {
|
|
10978
11071
|
const json2 = _json;
|
|
10979
|
-
const def =
|
|
11072
|
+
const def = schema2._zod.def;
|
|
10980
11073
|
json2.type = "array";
|
|
10981
11074
|
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
10982
11075
|
const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
@@ -11010,15 +11103,15 @@ const tupleProcessor = (schema, ctx, _json, params) => {
|
|
|
11010
11103
|
json2.additionalItems = rest;
|
|
11011
11104
|
}
|
|
11012
11105
|
}
|
|
11013
|
-
const { minimum, maximum } =
|
|
11106
|
+
const { minimum, maximum } = schema2._zod.bag;
|
|
11014
11107
|
if (typeof minimum === "number")
|
|
11015
11108
|
json2.minItems = minimum;
|
|
11016
11109
|
if (typeof maximum === "number")
|
|
11017
11110
|
json2.maxItems = maximum;
|
|
11018
11111
|
};
|
|
11019
|
-
const recordProcessor = (
|
|
11112
|
+
const recordProcessor = (schema2, ctx, _json, params) => {
|
|
11020
11113
|
const json2 = _json;
|
|
11021
|
-
const def =
|
|
11114
|
+
const def = schema2._zod.def;
|
|
11022
11115
|
json2.type = "object";
|
|
11023
11116
|
const keyType = def.keyType;
|
|
11024
11117
|
const keyBag = keyType._zod.bag;
|
|
@@ -11052,10 +11145,10 @@ const recordProcessor = (schema, ctx, _json, params) => {
|
|
|
11052
11145
|
}
|
|
11053
11146
|
}
|
|
11054
11147
|
};
|
|
11055
|
-
const nullableProcessor = (
|
|
11056
|
-
const def =
|
|
11148
|
+
const nullableProcessor = (schema2, ctx, json2, params) => {
|
|
11149
|
+
const def = schema2._zod.def;
|
|
11057
11150
|
const inner = process$1(def.innerType, ctx, params);
|
|
11058
|
-
const seen = ctx.seen.get(
|
|
11151
|
+
const seen = ctx.seen.get(schema2);
|
|
11059
11152
|
if (ctx.target === "openapi-3.0") {
|
|
11060
11153
|
seen.ref = def.innerType;
|
|
11061
11154
|
json2.nullable = true;
|
|
@@ -11063,31 +11156,31 @@ const nullableProcessor = (schema, ctx, json2, params) => {
|
|
|
11063
11156
|
json2.anyOf = [inner, { type: "null" }];
|
|
11064
11157
|
}
|
|
11065
11158
|
};
|
|
11066
|
-
const nonoptionalProcessor = (
|
|
11067
|
-
const def =
|
|
11159
|
+
const nonoptionalProcessor = (schema2, ctx, _json, params) => {
|
|
11160
|
+
const def = schema2._zod.def;
|
|
11068
11161
|
process$1(def.innerType, ctx, params);
|
|
11069
|
-
const seen = ctx.seen.get(
|
|
11162
|
+
const seen = ctx.seen.get(schema2);
|
|
11070
11163
|
seen.ref = def.innerType;
|
|
11071
11164
|
};
|
|
11072
|
-
const defaultProcessor = (
|
|
11073
|
-
const def =
|
|
11165
|
+
const defaultProcessor = (schema2, ctx, json2, params) => {
|
|
11166
|
+
const def = schema2._zod.def;
|
|
11074
11167
|
process$1(def.innerType, ctx, params);
|
|
11075
|
-
const seen = ctx.seen.get(
|
|
11168
|
+
const seen = ctx.seen.get(schema2);
|
|
11076
11169
|
seen.ref = def.innerType;
|
|
11077
11170
|
json2.default = JSON.parse(JSON.stringify(def.defaultValue));
|
|
11078
11171
|
};
|
|
11079
|
-
const prefaultProcessor = (
|
|
11080
|
-
const def =
|
|
11172
|
+
const prefaultProcessor = (schema2, ctx, json2, params) => {
|
|
11173
|
+
const def = schema2._zod.def;
|
|
11081
11174
|
process$1(def.innerType, ctx, params);
|
|
11082
|
-
const seen = ctx.seen.get(
|
|
11175
|
+
const seen = ctx.seen.get(schema2);
|
|
11083
11176
|
seen.ref = def.innerType;
|
|
11084
11177
|
if (ctx.io === "input")
|
|
11085
11178
|
json2._prefault = JSON.parse(JSON.stringify(def.defaultValue));
|
|
11086
11179
|
};
|
|
11087
|
-
const catchProcessor = (
|
|
11088
|
-
const def =
|
|
11180
|
+
const catchProcessor = (schema2, ctx, json2, params) => {
|
|
11181
|
+
const def = schema2._zod.def;
|
|
11089
11182
|
process$1(def.innerType, ctx, params);
|
|
11090
|
-
const seen = ctx.seen.get(
|
|
11183
|
+
const seen = ctx.seen.get(schema2);
|
|
11091
11184
|
seen.ref = def.innerType;
|
|
11092
11185
|
let catchValue;
|
|
11093
11186
|
try {
|
|
@@ -11097,37 +11190,37 @@ const catchProcessor = (schema, ctx, json2, params) => {
|
|
|
11097
11190
|
}
|
|
11098
11191
|
json2.default = catchValue;
|
|
11099
11192
|
};
|
|
11100
|
-
const pipeProcessor = (
|
|
11101
|
-
const def =
|
|
11193
|
+
const pipeProcessor = (schema2, ctx, _json, params) => {
|
|
11194
|
+
const def = schema2._zod.def;
|
|
11102
11195
|
const inIsTransform = def.in._zod.traits.has("$ZodTransform");
|
|
11103
11196
|
const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
|
|
11104
11197
|
process$1(innerType, ctx, params);
|
|
11105
|
-
const seen = ctx.seen.get(
|
|
11198
|
+
const seen = ctx.seen.get(schema2);
|
|
11106
11199
|
seen.ref = innerType;
|
|
11107
11200
|
};
|
|
11108
|
-
const readonlyProcessor = (
|
|
11109
|
-
const def =
|
|
11201
|
+
const readonlyProcessor = (schema2, ctx, json2, params) => {
|
|
11202
|
+
const def = schema2._zod.def;
|
|
11110
11203
|
process$1(def.innerType, ctx, params);
|
|
11111
|
-
const seen = ctx.seen.get(
|
|
11204
|
+
const seen = ctx.seen.get(schema2);
|
|
11112
11205
|
seen.ref = def.innerType;
|
|
11113
11206
|
json2.readOnly = true;
|
|
11114
11207
|
};
|
|
11115
|
-
const promiseProcessor = (
|
|
11116
|
-
const def =
|
|
11208
|
+
const promiseProcessor = (schema2, ctx, _json, params) => {
|
|
11209
|
+
const def = schema2._zod.def;
|
|
11117
11210
|
process$1(def.innerType, ctx, params);
|
|
11118
|
-
const seen = ctx.seen.get(
|
|
11211
|
+
const seen = ctx.seen.get(schema2);
|
|
11119
11212
|
seen.ref = def.innerType;
|
|
11120
11213
|
};
|
|
11121
|
-
const optionalProcessor = (
|
|
11122
|
-
const def =
|
|
11214
|
+
const optionalProcessor = (schema2, ctx, _json, params) => {
|
|
11215
|
+
const def = schema2._zod.def;
|
|
11123
11216
|
process$1(def.innerType, ctx, params);
|
|
11124
|
-
const seen = ctx.seen.get(
|
|
11217
|
+
const seen = ctx.seen.get(schema2);
|
|
11125
11218
|
seen.ref = def.innerType;
|
|
11126
11219
|
};
|
|
11127
|
-
const lazyProcessor = (
|
|
11128
|
-
const innerType =
|
|
11220
|
+
const lazyProcessor = (schema2, ctx, _json, params) => {
|
|
11221
|
+
const innerType = schema2._zod.innerType;
|
|
11129
11222
|
process$1(innerType, ctx, params);
|
|
11130
|
-
const seen = ctx.seen.get(
|
|
11223
|
+
const seen = ctx.seen.get(schema2);
|
|
11131
11224
|
seen.ref = innerType;
|
|
11132
11225
|
};
|
|
11133
11226
|
const allProcessors = {
|
|
@@ -11177,8 +11270,8 @@ function toJSONSchema(input, params) {
|
|
|
11177
11270
|
const ctx2 = initializeContext({ ...params, processors: allProcessors });
|
|
11178
11271
|
const defs = {};
|
|
11179
11272
|
for (const entry of registry2._idmap.entries()) {
|
|
11180
|
-
const [_,
|
|
11181
|
-
process$1(
|
|
11273
|
+
const [_, schema2] = entry;
|
|
11274
|
+
process$1(schema2, ctx2);
|
|
11182
11275
|
}
|
|
11183
11276
|
const schemas = {};
|
|
11184
11277
|
const external = {
|
|
@@ -11188,9 +11281,9 @@ function toJSONSchema(input, params) {
|
|
|
11188
11281
|
};
|
|
11189
11282
|
ctx2.external = external;
|
|
11190
11283
|
for (const entry of registry2._idmap.entries()) {
|
|
11191
|
-
const [key,
|
|
11192
|
-
extractDefs(ctx2,
|
|
11193
|
-
schemas[key] = finalize(ctx2,
|
|
11284
|
+
const [key, schema2] = entry;
|
|
11285
|
+
extractDefs(ctx2, schema2);
|
|
11286
|
+
schemas[key] = finalize(ctx2, schema2);
|
|
11194
11287
|
}
|
|
11195
11288
|
if (Object.keys(defs).length > 0) {
|
|
11196
11289
|
const defsSegment = ctx2.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
@@ -11814,6 +11907,18 @@ function union(options, params) {
|
|
|
11814
11907
|
...normalizeParams(params)
|
|
11815
11908
|
});
|
|
11816
11909
|
}
|
|
11910
|
+
const ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("ZodDiscriminatedUnion", (inst, def) => {
|
|
11911
|
+
ZodUnion.init(inst, def);
|
|
11912
|
+
$ZodDiscriminatedUnion.init(inst, def);
|
|
11913
|
+
});
|
|
11914
|
+
function discriminatedUnion(discriminator, options, params) {
|
|
11915
|
+
return new ZodDiscriminatedUnion({
|
|
11916
|
+
type: "union",
|
|
11917
|
+
options,
|
|
11918
|
+
discriminator,
|
|
11919
|
+
...normalizeParams(params)
|
|
11920
|
+
});
|
|
11921
|
+
}
|
|
11817
11922
|
const ZodIntersection = /* @__PURE__ */ $constructor("ZodIntersection", (inst, def) => {
|
|
11818
11923
|
$ZodIntersection.init(inst, def);
|
|
11819
11924
|
ZodType.init(inst, def);
|
|
@@ -11895,6 +12000,27 @@ function _enum(values, params) {
|
|
|
11895
12000
|
...normalizeParams(params)
|
|
11896
12001
|
});
|
|
11897
12002
|
}
|
|
12003
|
+
const ZodLiteral = /* @__PURE__ */ $constructor("ZodLiteral", (inst, def) => {
|
|
12004
|
+
$ZodLiteral.init(inst, def);
|
|
12005
|
+
ZodType.init(inst, def);
|
|
12006
|
+
inst._zod.processJSONSchema = (ctx, json2, params) => literalProcessor(inst, ctx, json2);
|
|
12007
|
+
inst.values = new Set(def.values);
|
|
12008
|
+
Object.defineProperty(inst, "value", {
|
|
12009
|
+
get() {
|
|
12010
|
+
if (def.values.length > 1) {
|
|
12011
|
+
throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
|
|
12012
|
+
}
|
|
12013
|
+
return def.values[0];
|
|
12014
|
+
}
|
|
12015
|
+
});
|
|
12016
|
+
});
|
|
12017
|
+
function literal(value, params) {
|
|
12018
|
+
return new ZodLiteral({
|
|
12019
|
+
type: "literal",
|
|
12020
|
+
values: Array.isArray(value) ? value : [value],
|
|
12021
|
+
...normalizeParams(params)
|
|
12022
|
+
});
|
|
12023
|
+
}
|
|
11898
12024
|
const ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
|
|
11899
12025
|
$ZodTransform.init(inst, def);
|
|
11900
12026
|
ZodType.init(inst, def);
|
|
@@ -12460,51 +12586,71 @@ function cleanText(value, maxLength) {
|
|
|
12460
12586
|
}
|
|
12461
12587
|
const DEFAULT_MODEL$1 = { id: "default", label: "Default (CLI)" };
|
|
12462
12588
|
class CommitMessageService {
|
|
12463
|
-
constructor(operations, providers, log, invalidateDiscovery) {
|
|
12589
|
+
constructor(operations, providers, log, invalidateDiscovery, statusPersistence) {
|
|
12464
12590
|
this.operations = operations;
|
|
12465
12591
|
this.log = log;
|
|
12466
12592
|
this.invalidateDiscovery = invalidateDiscovery;
|
|
12593
|
+
this.statusPersistence = statusPersistence;
|
|
12467
12594
|
for (const provider of providers) this.providers.set(provider.id, provider);
|
|
12468
12595
|
}
|
|
12469
12596
|
operations;
|
|
12470
12597
|
log;
|
|
12471
12598
|
invalidateDiscovery;
|
|
12599
|
+
statusPersistence;
|
|
12472
12600
|
providers = /* @__PURE__ */ new Map();
|
|
12473
12601
|
active = /* @__PURE__ */ new Map();
|
|
12474
12602
|
statusCache = null;
|
|
12603
|
+
statusInvalidated = false;
|
|
12475
12604
|
statusGeneration = 0;
|
|
12476
12605
|
pendingStatus;
|
|
12477
12606
|
invalidateStatuses() {
|
|
12478
12607
|
this.statusGeneration++;
|
|
12479
12608
|
this.statusCache = null;
|
|
12609
|
+
this.statusInvalidated = true;
|
|
12480
12610
|
this.pendingStatus = void 0;
|
|
12481
12611
|
this.invalidateDiscovery?.();
|
|
12482
12612
|
}
|
|
12483
12613
|
async statuses(forceRefresh = false) {
|
|
12484
|
-
|
|
12485
|
-
if (
|
|
12614
|
+
const key = this.statusPersistence?.key() ?? "";
|
|
12615
|
+
if (!forceRefresh && this.statusCache?.key === key) return this.statusCache.value;
|
|
12616
|
+
if (this.pendingStatus?.key === key && (!forceRefresh || this.pendingStatus.force)) return this.pendingStatus.promise;
|
|
12486
12617
|
const generation = ++this.statusGeneration;
|
|
12487
|
-
const promise =
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12618
|
+
const promise = (async () => {
|
|
12619
|
+
if (!forceRefresh && !this.statusInvalidated && this.statusPersistence) {
|
|
12620
|
+
const saved = await this.statusPersistence.store.load(key).catch(() => null);
|
|
12621
|
+
if (saved?.length === this.providers.size && saved.every((status) => this.providers.has(status.id))) {
|
|
12622
|
+
if (generation === this.statusGeneration) this.statusCache = { key, value: saved };
|
|
12623
|
+
return saved;
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12626
|
+
const value = await Promise.all([...this.providers.values()].map(async (provider) => {
|
|
12627
|
+
try {
|
|
12628
|
+
return { ...await provider.status(forceRefresh), checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
12629
|
+
} catch {
|
|
12630
|
+
return {
|
|
12631
|
+
id: provider.id,
|
|
12632
|
+
label: { codex: "Codex", claude: "Claude Code", opencode: "OpenCode", grok: "Grok Build" }[provider.id],
|
|
12633
|
+
installed: false,
|
|
12634
|
+
availability: "error",
|
|
12635
|
+
authStatus: "unknown",
|
|
12636
|
+
installationStatus: "inspection-failed",
|
|
12637
|
+
message: "Could not inspect this CLI. Check again or review its executable path.",
|
|
12638
|
+
models: [DEFAULT_MODEL$1],
|
|
12639
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12640
|
+
};
|
|
12641
|
+
}
|
|
12642
|
+
}));
|
|
12643
|
+
if (generation === this.statusGeneration) {
|
|
12644
|
+
this.statusCache = { key, value };
|
|
12645
|
+
this.statusInvalidated = false;
|
|
12646
|
+
try {
|
|
12647
|
+
await this.statusPersistence?.store.save(key, value);
|
|
12648
|
+
} catch {
|
|
12649
|
+
}
|
|
12502
12650
|
}
|
|
12503
|
-
})).then((value) => {
|
|
12504
|
-
if (generation === this.statusGeneration) this.statusCache = { at: Date.now(), value };
|
|
12505
12651
|
return value;
|
|
12506
|
-
});
|
|
12507
|
-
this.pendingStatus = { force: forceRefresh, promise };
|
|
12652
|
+
})();
|
|
12653
|
+
this.pendingStatus = { force: forceRefresh, key, promise };
|
|
12508
12654
|
try {
|
|
12509
12655
|
return await promise;
|
|
12510
12656
|
} finally {
|
|
@@ -12896,8 +13042,8 @@ function label(harness) {
|
|
|
12896
13042
|
function stripAnsi(value) {
|
|
12897
13043
|
return value.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
|
|
12898
13044
|
}
|
|
12899
|
-
function toCodexOutputSchema(
|
|
12900
|
-
return strictify(structuredClone(
|
|
13045
|
+
function toCodexOutputSchema(schema2) {
|
|
13046
|
+
return strictify(structuredClone(schema2));
|
|
12901
13047
|
}
|
|
12902
13048
|
function strictify(value) {
|
|
12903
13049
|
if (!value || typeof value !== "object") return value;
|
|
@@ -13051,9 +13197,9 @@ class CodexProvider {
|
|
|
13051
13197
|
const failure2 = detectionFailure(detected, { id: this.id, label: "Codex", models: [DEFAULT_MODEL$1], checkedAt });
|
|
13052
13198
|
if (failure2) return failure2;
|
|
13053
13199
|
const executable = requireCandidate(detected, this.id);
|
|
13054
|
-
const
|
|
13055
|
-
if (
|
|
13056
|
-
${
|
|
13200
|
+
const login2 = await runCandidate(this.runner, executable, ["login", "status"]);
|
|
13201
|
+
if (login2.exitCode !== 0 || !/logged in/i.test(`${login2.stdout}
|
|
13202
|
+
${login2.stderr}`)) {
|
|
13057
13203
|
return { ...detectionFields(detected), id: this.id, label: "Codex", availability: "error", installed: true, authStatus: "unauthenticated", version: detected.version ?? "", message: "Run codex login.", models: [DEFAULT_MODEL$1], checkedAt };
|
|
13058
13204
|
}
|
|
13059
13205
|
const catalog = await runCandidate(this.runner, executable, ["debug", "models", "--bundled"]);
|
|
@@ -13547,11 +13693,10 @@ class OpenCodeProvider {
|
|
|
13547
13693
|
const executable = requireCandidate(detected, this.id);
|
|
13548
13694
|
const version2 = detected.version ?? "";
|
|
13549
13695
|
const cliName = openCodeCliName(executable.executable);
|
|
13550
|
-
const [auth,
|
|
13696
|
+
const [auth, models] = await Promise.all([
|
|
13551
13697
|
runCandidate(this.runner, executable, ["auth", "list", "--format", "json", "--standalone"]),
|
|
13552
|
-
|
|
13698
|
+
this.loadModels(executable)
|
|
13553
13699
|
]);
|
|
13554
|
-
const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
|
|
13555
13700
|
let authStatus = parseOpenCodeAuthList(auth.stdout, auth.exitCode);
|
|
13556
13701
|
if (authStatus === "unauthenticated" && models.length > 1) authStatus = "unknown";
|
|
13557
13702
|
const confirmed = authStatus === "authenticated";
|
|
@@ -13566,9 +13711,20 @@ class OpenCodeProvider {
|
|
|
13566
13711
|
version: version2,
|
|
13567
13712
|
models,
|
|
13568
13713
|
checkedAt,
|
|
13569
|
-
...authStatus === "unauthenticated" ? { message: `Run ${openCodeLoginCommand(cliName)}.` } : !confirmed ? { message: "Could not confirm authentication for the selected provider." } : {}
|
|
13714
|
+
...authStatus === "unauthenticated" ? { message: `Run ${openCodeLoginCommand(cliName)}.` } : models.length === 1 ? { message: `Could not load the OpenCode model catalog. Check again or verify that ${cliName} models lists your models in a terminal.` } : !confirmed ? { message: "Could not confirm authentication for the selected provider." } : {}
|
|
13570
13715
|
};
|
|
13571
13716
|
}
|
|
13717
|
+
async loadModels(executable) {
|
|
13718
|
+
for (const args of [["models", "--standalone"], ["models"]]) {
|
|
13719
|
+
try {
|
|
13720
|
+
const catalog = await runCandidate(this.runner, executable, args, { timeoutMs: 3e4 });
|
|
13721
|
+
const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
|
|
13722
|
+
if (models.length > 1) return models;
|
|
13723
|
+
} catch {
|
|
13724
|
+
}
|
|
13725
|
+
}
|
|
13726
|
+
return [DEFAULT_MODEL$1];
|
|
13727
|
+
}
|
|
13572
13728
|
async generate(input) {
|
|
13573
13729
|
const detected = await this.detect(false, input.signal);
|
|
13574
13730
|
const executable = requireCandidate(detected, this.id);
|
|
@@ -15140,6 +15296,313 @@ function parseWorktrees(buffer) {
|
|
|
15140
15296
|
if (current) worktrees.push(current);
|
|
15141
15297
|
return worktrees;
|
|
15142
15298
|
}
|
|
15299
|
+
const commitAuthorshipInputSchema = object({
|
|
15300
|
+
name: string().trim().min(1).max(120).regex(/^[^<>]+$/).refine(noControls),
|
|
15301
|
+
email: string().trim().max(254).regex(/^[^\s<>@]+@[^\s<>@]+$/).refine(noControls),
|
|
15302
|
+
expectedRevision: string().regex(/^[a-f0-9]{64}$/)
|
|
15303
|
+
});
|
|
15304
|
+
function noControls(value) {
|
|
15305
|
+
return [...value].every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127);
|
|
15306
|
+
}
|
|
15307
|
+
const environmentKeys = ["GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", "EMAIL"];
|
|
15308
|
+
const identityPattern = "^(user|author|committer)\\.(name|email)$";
|
|
15309
|
+
class GitCommitAuthorship {
|
|
15310
|
+
constructor(git, repositories) {
|
|
15311
|
+
this.git = git;
|
|
15312
|
+
this.repositories = repositories;
|
|
15313
|
+
}
|
|
15314
|
+
git;
|
|
15315
|
+
repositories;
|
|
15316
|
+
revisionKey = randomBytes(32);
|
|
15317
|
+
async get(repositoryId) {
|
|
15318
|
+
const repository = this.repositories.get(repositoryId);
|
|
15319
|
+
return this.git.runWriteTask(repository.path, () => this.read(repositoryId), repository.commonDir);
|
|
15320
|
+
}
|
|
15321
|
+
async set(repositoryId, input) {
|
|
15322
|
+
const parsed = commitAuthorshipInputSchema.safeParse(input);
|
|
15323
|
+
if (!parsed.success) throw invalid$1("Enter a valid name, email and reviewed identity.");
|
|
15324
|
+
const { name, email: email2, expectedRevision } = parsed.data;
|
|
15325
|
+
const repository = this.repositories.get(repositoryId);
|
|
15326
|
+
const configPath = path.join(repository.commonDir, "config");
|
|
15327
|
+
const lockPath = `${configPath}.lock`;
|
|
15328
|
+
return this.git.runWriteTask(repository.path, async (run) => {
|
|
15329
|
+
const current = await this.read(repositoryId);
|
|
15330
|
+
if (!current.editable) throw invalid$1(current.blockers.join(" "));
|
|
15331
|
+
if (current.revision !== expectedRevision) throw invalid$1("Git identity or repository settings changed. Reopen the editor and review them again.");
|
|
15332
|
+
let locked = false;
|
|
15333
|
+
try {
|
|
15334
|
+
const file = await open(lockPath, "wx", 384);
|
|
15335
|
+
locked = true;
|
|
15336
|
+
try {
|
|
15337
|
+
await file.writeFile(await readFile(configPath));
|
|
15338
|
+
} finally {
|
|
15339
|
+
await file.close();
|
|
15340
|
+
}
|
|
15341
|
+
const reviewed = await this.read(repositoryId);
|
|
15342
|
+
if (!reviewed.editable || reviewed.revision !== expectedRevision) throw invalid$1("Git settings changed before saving. Reopen the editor and review them again.");
|
|
15343
|
+
const mode = (await lstat(configPath)).mode & 511;
|
|
15344
|
+
await run(["config", "--file", lockPath, "--replace-all", "--", "user.name", name], { operation: "commit-authorship-set" });
|
|
15345
|
+
await run(["config", "--file", lockPath, "--replace-all", "--", "user.email", email2], { operation: "commit-authorship-set" });
|
|
15346
|
+
const candidate = await this.identities(repository.path, ["-c", `include.path=${lockPath}`]);
|
|
15347
|
+
if (candidate.author?.name !== name || candidate.author.email !== email2 || candidate.committer?.name !== name || candidate.committer.email !== email2) {
|
|
15348
|
+
throw invalid$1("An included Git setting overrides this identity. Edit that setting outside OpenTig before applying a repository identity.");
|
|
15349
|
+
}
|
|
15350
|
+
const latest = await this.read(repositoryId);
|
|
15351
|
+
if (!latest.editable || latest.revision !== expectedRevision) throw invalid$1("Git settings changed while saving. Reopen the editor and review them again.");
|
|
15352
|
+
await chmod(lockPath, mode);
|
|
15353
|
+
await rename(lockPath, configPath);
|
|
15354
|
+
locked = false;
|
|
15355
|
+
} catch (error) {
|
|
15356
|
+
if (error.code === "EEXIST") {
|
|
15357
|
+
throw invalid$1("Another Git process is editing repository settings. Wait for it to finish and try again.");
|
|
15358
|
+
}
|
|
15359
|
+
throw error;
|
|
15360
|
+
} finally {
|
|
15361
|
+
if (locked) await rm(lockPath, { force: true });
|
|
15362
|
+
}
|
|
15363
|
+
return this.read(repositoryId);
|
|
15364
|
+
}, repository.commonDir);
|
|
15365
|
+
}
|
|
15366
|
+
async read(repositoryId) {
|
|
15367
|
+
const repository = this.repositories.get(repositoryId);
|
|
15368
|
+
const configPath = path.join(repository.commonDir, "config");
|
|
15369
|
+
const [entries, identities, config2, info] = await Promise.all([
|
|
15370
|
+
this.config(repository.path),
|
|
15371
|
+
this.identities(repository.path),
|
|
15372
|
+
readFile(configPath),
|
|
15373
|
+
lstat(configPath)
|
|
15374
|
+
]);
|
|
15375
|
+
const environment = environmentKeys.filter((key) => process.env[key] !== void 0);
|
|
15376
|
+
const custom = entries.some((entry) => entry.key.startsWith("author.") || entry.key.startsWith("committer."));
|
|
15377
|
+
const worktree = entries.some((entry) => entry.scope === "worktree");
|
|
15378
|
+
const command2 = entries.some((entry) => entry.scope === "command");
|
|
15379
|
+
const blockers = [];
|
|
15380
|
+
if (environment.length) blockers.push("Backend environment variables override Git identity. Manage them outside OpenTig before editing repository authorship.");
|
|
15381
|
+
if (custom) blockers.push("Git author.* or committer.* settings override user.name/email. Manage those settings outside OpenTig first.");
|
|
15382
|
+
if (worktree) blockers.push("This worktree has its own identity. Manage its worktree settings outside OpenTig first.");
|
|
15383
|
+
if (command2) blockers.push("Backend command configuration overrides Git identity. Manage it outside OpenTig first.");
|
|
15384
|
+
if (!info.isFile() || info.nlink !== 1) blockers.push("The repository config is linked or shared. Edit it outside OpenTig to avoid changing another repository.");
|
|
15385
|
+
const scopes = ["user.name", "user.email"].map((key) => entries.filter((entry) => entry.key === key).at(-1)?.scope ?? "inferred");
|
|
15386
|
+
const source = environment.length || command2 ? "environment" : custom ? "custom" : worktree ? "worktree" : !identities.author ? "unset" : scopes[0] !== scopes[1] ? "mixed" : scopes[0] === "local" || scopes[0] === "global" || scopes[0] === "system" ? scopes[0] : "inferred";
|
|
15387
|
+
const revision2 = createHmac("sha256", this.revisionKey).update(repository.path).update(config2).update(JSON.stringify({ entries, identities, environment, mode: info.mode, linked: info.nlink, blockers })).digest("hex");
|
|
15388
|
+
return { ...identities, source, editable: blockers.length === 0, blockers, revision: revision2 };
|
|
15389
|
+
}
|
|
15390
|
+
async config(cwd) {
|
|
15391
|
+
try {
|
|
15392
|
+
const output = await this.git.run(cwd, ["config", "--null", "--show-scope", "--get-regexp", identityPattern], { operation: "commit-authorship-read", readOnly: true });
|
|
15393
|
+
const fields = output.stdout.toString("utf8").split("\0");
|
|
15394
|
+
const entries = [];
|
|
15395
|
+
for (let index = 0; index + 1 < fields.length; index += 2) {
|
|
15396
|
+
const field = fields[index + 1];
|
|
15397
|
+
const separator = field.indexOf("\n");
|
|
15398
|
+
if (separator >= 0) entries.push({ scope: fields[index], key: field.slice(0, separator), value: field.slice(separator + 1) });
|
|
15399
|
+
}
|
|
15400
|
+
return entries;
|
|
15401
|
+
} catch (error) {
|
|
15402
|
+
if (error instanceof GitOperationError && error.detail.exitCode === 1) return [];
|
|
15403
|
+
throw error;
|
|
15404
|
+
}
|
|
15405
|
+
}
|
|
15406
|
+
async identities(cwd, prefix = []) {
|
|
15407
|
+
const [author, committer] = await Promise.all(["GIT_AUTHOR_IDENT", "GIT_COMMITTER_IDENT"].map(async (variable) => {
|
|
15408
|
+
try {
|
|
15409
|
+
const output = await this.git.run(cwd, [...prefix, "var", variable], { operation: "commit-authorship-read", readOnly: true });
|
|
15410
|
+
const match = /^(.*?) <([^<>]*)> \d+ [+-]\d{4}$/.exec(output.stdout.toString("utf8").trim());
|
|
15411
|
+
if (!match) throw invalid$1("Git returned an unreadable commit identity.");
|
|
15412
|
+
return { name: match[1], email: match[2] };
|
|
15413
|
+
} catch (error) {
|
|
15414
|
+
if (error instanceof GitOperationError && error.detail.exitCode === 128 && /identity unknown|unable to auto-detect|empty ident|no email was given|no name was given/i.test(error.detail.stderr ?? "")) return null;
|
|
15415
|
+
throw error;
|
|
15416
|
+
}
|
|
15417
|
+
}));
|
|
15418
|
+
return { author, committer };
|
|
15419
|
+
}
|
|
15420
|
+
}
|
|
15421
|
+
function invalid$1(message) {
|
|
15422
|
+
return new GitOperationError({ code: "INVALID_ARGUMENT", operation: "commit-authorship-set", message });
|
|
15423
|
+
}
|
|
15424
|
+
const read = { operation: "remote-branches", readOnly: true, maxOutputBytes: 4 * 1024 * 1024 };
|
|
15425
|
+
const network = { ...read, timeoutMs: 6e4 };
|
|
15426
|
+
class GitRemoteBranches {
|
|
15427
|
+
constructor(git, repositories) {
|
|
15428
|
+
this.git = git;
|
|
15429
|
+
this.repositories = repositories;
|
|
15430
|
+
}
|
|
15431
|
+
git;
|
|
15432
|
+
repositories;
|
|
15433
|
+
reader(root) {
|
|
15434
|
+
return (args, options) => this.git.run(root, args, options);
|
|
15435
|
+
}
|
|
15436
|
+
async remotes(run) {
|
|
15437
|
+
const names = (await run(["remote"], read)).stdout.toString("utf8").trim().split(/\r?\n/).filter(Boolean);
|
|
15438
|
+
return Promise.all(names.map(async (name) => ({ name, refspecs: (await this.config(run, `remote.${name}.fetch`)).split(/\r?\n/).filter(Boolean) })));
|
|
15439
|
+
}
|
|
15440
|
+
async config(run, key) {
|
|
15441
|
+
try {
|
|
15442
|
+
return (await run(["config", "--get-all", key], read)).stdout.toString("utf8").trim();
|
|
15443
|
+
} catch (error) {
|
|
15444
|
+
if (error instanceof GitOperationError && error.detail.exitCode === 1) return "";
|
|
15445
|
+
throw error;
|
|
15446
|
+
}
|
|
15447
|
+
}
|
|
15448
|
+
mappings(fullName, remotes) {
|
|
15449
|
+
return remotes.flatMap((remote) => remote.refspecs.flatMap((spec) => {
|
|
15450
|
+
if (spec.startsWith("^")) return [];
|
|
15451
|
+
const [source, destination] = spec.replace(/^\+/, "").split(":");
|
|
15452
|
+
if (!source?.startsWith("refs/heads/") || !destination?.startsWith("refs/remotes/")) return [];
|
|
15453
|
+
const match = refspecMatch(destination, fullName);
|
|
15454
|
+
if (match === null) return [];
|
|
15455
|
+
const sourceRef = source.replace("*", match);
|
|
15456
|
+
if (remote.refspecs.some((excluded) => excluded.startsWith("^") && refspecMatch(excluded.slice(1), sourceRef) !== null)) return [];
|
|
15457
|
+
return [{ remote: remote.name, sourceRef }];
|
|
15458
|
+
}));
|
|
15459
|
+
}
|
|
15460
|
+
async snapshotBranches(repositoryId, branches) {
|
|
15461
|
+
const root = this.repositories.get(repositoryId).path;
|
|
15462
|
+
const run = this.reader(root);
|
|
15463
|
+
const remotes = await this.remotes(run);
|
|
15464
|
+
const aliases = new Set((await run(["for-each-ref", "--format=%(refname)%09%(symref)", "refs/remotes"], read)).stdout.toString("utf8").split(/\r?\n/).filter((line) => line.split(" ")[1]).map((line) => line.split(" ")[0]));
|
|
15465
|
+
return { remotes: remotes.map((remote) => remote.name), branches: branches.filter((branch) => !aliases.has(branch.fullName)).map((branch) => {
|
|
15466
|
+
if (!branch.remote) return branch;
|
|
15467
|
+
const mappings = this.mappings(branch.fullName, remotes);
|
|
15468
|
+
if (mappings.length !== 1) return branch;
|
|
15469
|
+
return { ...branch, remoteName: mappings[0].remote, remoteBranchName: mappings[0].sourceRef.slice("refs/heads/".length) };
|
|
15470
|
+
}) };
|
|
15471
|
+
}
|
|
15472
|
+
async target(fullName, run) {
|
|
15473
|
+
const branches = parseRefs((await run(["for-each-ref", `--format=${REF_FORMAT}`, "refs/remotes"], read)).stdout);
|
|
15474
|
+
const branch = branches.find((item) => item.fullName === fullName);
|
|
15475
|
+
if (!branch) return null;
|
|
15476
|
+
const aliases = (await run(["for-each-ref", "--format=%(refname)%09%(symref)", fullName], read)).stdout.toString("utf8").split(/\r?\n/).some((line) => line.split(" ")[0] === fullName && Boolean(line.split(" ")[1]));
|
|
15477
|
+
const mappings = this.mappings(fullName, await this.remotes(run));
|
|
15478
|
+
if (aliases || mappings.length !== 1) throw new GitOperationError({
|
|
15479
|
+
code: "INVALID_ARGUMENT",
|
|
15480
|
+
operation: "remote-branch",
|
|
15481
|
+
message: "This reference does not map to one configured remote branch. Check the remote fetch settings."
|
|
15482
|
+
});
|
|
15483
|
+
return { branch, ...mappings[0], branchName: mappings[0].sourceRef.slice("refs/heads/".length) };
|
|
15484
|
+
}
|
|
15485
|
+
async destination(target, run) {
|
|
15486
|
+
const fetchUrls = (await run(["remote", "get-url", "--all", "--", target.remote], read)).stdout.toString("utf8").trim().split(/\r?\n/);
|
|
15487
|
+
const pushUrls = (await run(["remote", "get-url", "--push", "--all", "--", target.remote], read)).stdout.toString("utf8").trim().split(/\r?\n/);
|
|
15488
|
+
const id = createHash("sha256").update(JSON.stringify([target.remote, target.sourceRef, fetchUrls, pushUrls])).digest("hex");
|
|
15489
|
+
const blocked = fetchUrls.length !== 1 || pushUrls.length !== 1 || fetchUrls[0] !== pushUrls[0] ? "Fetch and push destinations differ or have multiple URLs. Manage remote deletion outside OpenTig." : null;
|
|
15490
|
+
return { id, blocked, url: fetchUrls[0] };
|
|
15491
|
+
}
|
|
15492
|
+
async advertised(target, run, url) {
|
|
15493
|
+
const lines = (await run(["ls-remote", "--symref", "--", url, "HEAD", target.sourceRef], network)).stdout.toString("utf8").split(/\r?\n/);
|
|
15494
|
+
const defaultRef = lines.find((line) => line.startsWith("ref: refs/heads/") && line.endsWith(" HEAD"))?.split(" ")[0]?.slice(5) ?? null;
|
|
15495
|
+
const oid = lines.find((line) => line.split(" ")[1] === target.sourceRef)?.split(" ")[0] ?? null;
|
|
15496
|
+
return { defaultRef, oid };
|
|
15497
|
+
}
|
|
15498
|
+
async details(repositoryId, fullName) {
|
|
15499
|
+
const run = this.reader(this.repositories.get(repositoryId).path);
|
|
15500
|
+
const target = await this.target(fullName, run);
|
|
15501
|
+
if (!target) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "remote-branch-details", message: "The remote reference no longer exists. Fetch branches again." });
|
|
15502
|
+
const destination = await this.destination(target, run);
|
|
15503
|
+
let blocked = destination.blocked;
|
|
15504
|
+
let defaultRef = null;
|
|
15505
|
+
let remoteState = "unchecked";
|
|
15506
|
+
if (!blocked) {
|
|
15507
|
+
try {
|
|
15508
|
+
const state = await this.advertised(target, run, destination.url);
|
|
15509
|
+
defaultRef = state.defaultRef;
|
|
15510
|
+
remoteState = !state.oid ? "missing" : state.oid === target.branch.oid ? "current" : "changed";
|
|
15511
|
+
blocked = !state.oid ? "The branch no longer exists on the remote. Fetch to remove the obsolete reference." : state.oid !== target.branch.oid ? "The remote branch changed since the last fetch. Fetch before deleting." : !state.defaultRef ? "The remote does not identify its default branch. Deletion is unavailable here." : state.defaultRef === target.sourceRef ? "This is the remote’s default branch and cannot be deleted here." : null;
|
|
15512
|
+
} catch {
|
|
15513
|
+
blocked = "Could not check the remote. Fetch again or check your connection and Git permissions.";
|
|
15514
|
+
}
|
|
15515
|
+
}
|
|
15516
|
+
const upstreams = (await run(["for-each-ref", "--format=%(refname)%00%(refname:short)%00%(upstream)%00%(worktreepath)", "refs/heads"], read)).stdout.toString("utf8");
|
|
15517
|
+
const localBranches = upstreams.split(/\r?\n/).map((line) => line.split("\0")).filter((fields) => fields[2] === fullName).map(([ref, name, , worktreePath]) => ({ fullName: ref, name, worktreePath: worktreePath || null }));
|
|
15518
|
+
return {
|
|
15519
|
+
...target.branch,
|
|
15520
|
+
remote: target.remote,
|
|
15521
|
+
branchName: target.branchName,
|
|
15522
|
+
localBranches,
|
|
15523
|
+
defaultBranch: defaultRef?.slice("refs/heads/".length) ?? null,
|
|
15524
|
+
remoteState,
|
|
15525
|
+
destinationId: destination.id,
|
|
15526
|
+
deletionBlockedReason: blocked
|
|
15527
|
+
};
|
|
15528
|
+
}
|
|
15529
|
+
async create(request) {
|
|
15530
|
+
const repository = this.repositories.get(request.repositoryId);
|
|
15531
|
+
return this.git.runWriteTask(repository.path, async (run) => {
|
|
15532
|
+
const target = await this.target(request.fullName, run);
|
|
15533
|
+
if (!target) return { status: "missing" };
|
|
15534
|
+
if (target.branch.oid !== request.expectedOid) return { status: "stale" };
|
|
15535
|
+
if (request.localName.startsWith("-")) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "create-tracking-branch", message: "Invalid local branch name." });
|
|
15536
|
+
await run(["check-ref-format", `refs/heads/${request.localName}`], read);
|
|
15537
|
+
const localRef = `refs/heads/${request.localName}`;
|
|
15538
|
+
const existing = (await run(["for-each-ref", "--format=%(refname)", "refs/heads"], read)).stdout.toString("utf8").trim().split(/\r?\n/);
|
|
15539
|
+
if (existing.includes(localRef)) return { status: "exists" };
|
|
15540
|
+
await run(["branch", "--track", "--", request.localName, target.branch.fullName], { operation: "create-tracking-branch" });
|
|
15541
|
+
return { status: "created", fullName: localRef };
|
|
15542
|
+
}, repository.commonDir);
|
|
15543
|
+
}
|
|
15544
|
+
async delete(request) {
|
|
15545
|
+
const repository = this.repositories.get(request.repositoryId);
|
|
15546
|
+
return this.git.runWriteTask(repository.path, async (run) => {
|
|
15547
|
+
const target = await this.target(request.fullName, run);
|
|
15548
|
+
if (!target) return { status: "missing" };
|
|
15549
|
+
if (target.branch.oid !== request.expectedOid) return { status: "stale" };
|
|
15550
|
+
const destination = await this.destination(target, run);
|
|
15551
|
+
if (destination.id !== request.destinationId) return { status: "destination-changed" };
|
|
15552
|
+
if (destination.blocked) return { status: "rejected", message: destination.blocked };
|
|
15553
|
+
try {
|
|
15554
|
+
const state = await this.advertised(target, run, destination.url);
|
|
15555
|
+
if (!state.oid) return { status: "missing" };
|
|
15556
|
+
if (!state.defaultRef) return { status: "rejected", message: "Could not identify the remote default branch. Nothing was deleted." };
|
|
15557
|
+
if (state.defaultRef === target.sourceRef) return { status: "default" };
|
|
15558
|
+
if (state.oid !== request.expectedOid) return { status: "stale" };
|
|
15559
|
+
await run(
|
|
15560
|
+
[
|
|
15561
|
+
"-c",
|
|
15562
|
+
`remote.${target.remote}.mirror=false`,
|
|
15563
|
+
"push",
|
|
15564
|
+
"--porcelain",
|
|
15565
|
+
"--no-follow-tags",
|
|
15566
|
+
`--force-with-lease=${target.sourceRef}:${request.expectedOid}`,
|
|
15567
|
+
"--",
|
|
15568
|
+
destination.url,
|
|
15569
|
+
`:${target.sourceRef}`
|
|
15570
|
+
],
|
|
15571
|
+
{ operation: "delete-remote-branch", timeoutMs: 12e4, maxOutputBytes: 4 * 1024 * 1024 }
|
|
15572
|
+
);
|
|
15573
|
+
return { status: "deleted" };
|
|
15574
|
+
} catch {
|
|
15575
|
+
return { status: "rejected", message: "Git refused the remote deletion. Check permissions or branch protection, fetch, and try again. No local branch or worktree was removed." };
|
|
15576
|
+
}
|
|
15577
|
+
}, repository.commonDir);
|
|
15578
|
+
}
|
|
15579
|
+
async fetch(repositoryId) {
|
|
15580
|
+
const repository = this.repositories.get(repositoryId);
|
|
15581
|
+
try {
|
|
15582
|
+
await this.git.runWriteTask(repository.path, async (run) => {
|
|
15583
|
+
for (const remote of await this.remotes(run)) {
|
|
15584
|
+
const specs = remote.refspecs.filter((spec) => spec.startsWith("^refs/heads/") || /^\+?refs\/heads\/[^:]+:refs\/remotes\//.test(spec));
|
|
15585
|
+
if (!specs.some((spec) => !spec.startsWith("^"))) continue;
|
|
15586
|
+
await run(
|
|
15587
|
+
["-c", "fetch.pruneTags=false", "fetch", "--prune", "--no-prune-tags", "--no-tags", "--no-recurse-submodules", "--", remote.name, ...specs],
|
|
15588
|
+
{ operation: "fetch-branches", timeoutMs: 12e4, maxOutputBytes: 4 * 1024 * 1024 }
|
|
15589
|
+
);
|
|
15590
|
+
}
|
|
15591
|
+
}, repository.commonDir);
|
|
15592
|
+
} catch {
|
|
15593
|
+
return { status: "failed", message: "Could not fetch all remote branches. Check your connection and Git credentials, then fetch again." };
|
|
15594
|
+
}
|
|
15595
|
+
const status = await this.repositories.status(repositoryId, false);
|
|
15596
|
+
return { status: "success", ahead: status.ahead, behind: status.behind };
|
|
15597
|
+
}
|
|
15598
|
+
}
|
|
15599
|
+
function refspecMatch(pattern, ref) {
|
|
15600
|
+
const star = pattern.indexOf("*");
|
|
15601
|
+
if (star < 0) return pattern === ref ? "" : null;
|
|
15602
|
+
const prefix = pattern.slice(0, star);
|
|
15603
|
+
const suffix = pattern.slice(star + 1);
|
|
15604
|
+
return ref.startsWith(prefix) && ref.endsWith(suffix) && ref.length >= prefix.length + suffix.length ? ref.slice(prefix.length, suffix ? -suffix.length : void 0) : null;
|
|
15605
|
+
}
|
|
15143
15606
|
const AI_PATCH_BUDGET = 4e5;
|
|
15144
15607
|
const AI_SUMMARY_BUDGET = 24e3;
|
|
15145
15608
|
class GitRepositoryOperations {
|
|
@@ -15147,11 +15610,33 @@ class GitRepositoryOperations {
|
|
|
15147
15610
|
this.git = git;
|
|
15148
15611
|
this.repositories = repositories;
|
|
15149
15612
|
this.files = files;
|
|
15613
|
+
this.authorship = new GitCommitAuthorship(git, repositories);
|
|
15614
|
+
this.remoteBranches = new GitRemoteBranches(git, repositories);
|
|
15150
15615
|
}
|
|
15151
15616
|
git;
|
|
15152
15617
|
repositories;
|
|
15153
15618
|
files;
|
|
15154
15619
|
knownOids = /* @__PURE__ */ new Map();
|
|
15620
|
+
authorship;
|
|
15621
|
+
remoteBranches;
|
|
15622
|
+
remoteBranchDetails(repositoryId, fullName) {
|
|
15623
|
+
return this.remoteBranches.details(repositoryId, fullName);
|
|
15624
|
+
}
|
|
15625
|
+
createTrackingBranch(request) {
|
|
15626
|
+
return this.remoteBranches.create(request);
|
|
15627
|
+
}
|
|
15628
|
+
deleteRemoteBranch(request) {
|
|
15629
|
+
return this.remoteBranches.delete(request);
|
|
15630
|
+
}
|
|
15631
|
+
fetchBranches(repositoryId) {
|
|
15632
|
+
return this.remoteBranches.fetch(repositoryId);
|
|
15633
|
+
}
|
|
15634
|
+
getCommitAuthorship(repositoryId) {
|
|
15635
|
+
return this.authorship.get(repositoryId);
|
|
15636
|
+
}
|
|
15637
|
+
setCommitAuthorship(repositoryId, input) {
|
|
15638
|
+
return this.authorship.set(repositoryId, input);
|
|
15639
|
+
}
|
|
15155
15640
|
async diff(request) {
|
|
15156
15641
|
const repository = this.repositories.get(request.repositoryId);
|
|
15157
15642
|
const paths = this.repositories.validatePaths(request.repositoryId, [request.path]);
|
|
@@ -15736,15 +16221,16 @@ ${base}
|
|
|
15736
16221
|
return this.repositories.openPath(worktree.path);
|
|
15737
16222
|
}
|
|
15738
16223
|
/**
|
|
15739
|
-
* Cheap enumeration for the
|
|
16224
|
+
* Cheap enumeration for the refs manager. Deliberately runs no status
|
|
15740
16225
|
* scan per worktree so ordinary refreshes stay as fast as the selectors.
|
|
15741
16226
|
*/
|
|
15742
16227
|
async localRefsSnapshot(repositoryId) {
|
|
15743
16228
|
const repository = this.repositories.get(repositoryId);
|
|
15744
16229
|
const [branches, worktrees] = await Promise.all([this.branches(repositoryId), this.worktrees(repositoryId)]);
|
|
15745
|
-
const
|
|
16230
|
+
const managed = await this.remoteBranches.snapshotBranches(repositoryId, branches);
|
|
15746
16231
|
return {
|
|
15747
|
-
branches:
|
|
16232
|
+
branches: managed.branches.sort(compareBranches),
|
|
16233
|
+
remotes: managed.remotes,
|
|
15748
16234
|
worktrees: await Promise.all(worktrees.map(async (worktree) => ({ ...worktree, current: await samePath(worktree.path, repository.path) })))
|
|
15749
16235
|
};
|
|
15750
16236
|
}
|
|
@@ -16692,72 +17178,14 @@ function revision(bytes) {
|
|
|
16692
17178
|
function invalidReplacement(message) {
|
|
16693
17179
|
return new GitOperationError({ code: "INVALID_ARGUMENT", operation: "replace-search", message });
|
|
16694
17180
|
}
|
|
16695
|
-
const
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
}
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
pull_requests: array(object({
|
|
16704
|
-
number: positive,
|
|
16705
|
-
title: string().optional(),
|
|
16706
|
-
draft: boolean().optional(),
|
|
16707
|
-
head: object({ ref: string().min(1) }),
|
|
16708
|
-
state: _enum(["open", "closed", "merged"]),
|
|
16709
|
-
merged_at: string().nullable().optional()
|
|
16710
|
-
})).min(1)
|
|
16711
|
-
});
|
|
16712
|
-
function invalid() {
|
|
16713
|
-
return new GhOperationError({ code: "GH_INVALID_OUTPUT", operation: "gh-pr-stack", message: "GitHub returned unreadable stack information." });
|
|
16714
|
-
}
|
|
16715
|
-
function json(raw) {
|
|
16716
|
-
try {
|
|
16717
|
-
return JSON.parse(raw);
|
|
16718
|
-
} catch {
|
|
16719
|
-
throw invalid();
|
|
16720
|
-
}
|
|
16721
|
-
}
|
|
16722
|
-
function stackMembershipQuery(numbers) {
|
|
16723
|
-
if (!numbers.length || numbers.some((number2) => !positive.safeParse(number2).success)) throw invalid();
|
|
16724
|
-
return `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){${numbers.map((number2) => `pr${number2}:pullRequest(number:${number2}){stack{number size baseRefName} stackEntry{position}}`).join(" ")}}}`;
|
|
16725
|
-
}
|
|
16726
|
-
function parseStackMemberships(raw, numbers) {
|
|
16727
|
-
const envelope = object({ data: object({ repository: record(string(), membershipSchema.nullable()) }), errors: array(unknown()).optional() }).safeParse(json(raw));
|
|
16728
|
-
if (!envelope.success || envelope.data.errors?.length) throw invalid();
|
|
16729
|
-
const result = /* @__PURE__ */ new Map();
|
|
16730
|
-
for (const number2 of numbers) {
|
|
16731
|
-
const entry = envelope.data.data.repository[`pr${number2}`];
|
|
16732
|
-
if (entry === void 0) throw invalid();
|
|
16733
|
-
if (!entry?.stack && !entry?.stackEntry) continue;
|
|
16734
|
-
if (!entry?.stack || !entry.stackEntry || entry.stackEntry.position > entry.stack.size) throw invalid();
|
|
16735
|
-
result.set(number2, { number: entry.stack.number, size: entry.stack.size, position: entry.stackEntry.position, base: entry.stack.baseRefName });
|
|
16736
|
-
}
|
|
16737
|
-
return result;
|
|
16738
|
-
}
|
|
16739
|
-
function parsePullRequestStack(raw, prNumber, listing = false) {
|
|
16740
|
-
let value = json(raw);
|
|
16741
|
-
if (listing) {
|
|
16742
|
-
if (!Array.isArray(value) || value.length > 1) throw invalid();
|
|
16743
|
-
if (!value.length) return null;
|
|
16744
|
-
value = value[0];
|
|
16745
|
-
}
|
|
16746
|
-
const parsed = stackSchema.safeParse(value);
|
|
16747
|
-
if (!parsed.success) throw invalid();
|
|
16748
|
-
const stack = parsed.data;
|
|
16749
|
-
if (!stack.pull_requests.some((pr) => pr.number === prNumber) || new Set(stack.pull_requests.map((pr) => pr.number)).size !== stack.pull_requests.length) throw invalid();
|
|
16750
|
-
return {
|
|
16751
|
-
number: stack.number,
|
|
16752
|
-
base: typeof stack.base === "string" ? stack.base : stack.base.ref,
|
|
16753
|
-
layers: stack.pull_requests.map((pr) => ({
|
|
16754
|
-
number: pr.number,
|
|
16755
|
-
title: pr.title || pr.head.ref,
|
|
16756
|
-
headRefName: pr.head.ref,
|
|
16757
|
-
state: pr.merged_at || pr.state === "merged" ? "MERGED" : pr.state === "closed" ? "CLOSED" : "OPEN",
|
|
16758
|
-
isDraft: pr.draft ?? false
|
|
16759
|
-
}))
|
|
16760
|
-
};
|
|
17181
|
+
const githubAccountSelectionSchema = discriminatedUnion("mode", [
|
|
17182
|
+
object({ mode: literal("auto") }),
|
|
17183
|
+
object({ mode: literal("account"), host: literal("github.com"), login: string().regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/) })
|
|
17184
|
+
]);
|
|
17185
|
+
const AUTOMATIC_GITHUB_ACCOUNT = { mode: "auto" };
|
|
17186
|
+
function githubRepositoryKey(commonDir, platform2 = process.platform) {
|
|
17187
|
+
const key = commonDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
17188
|
+
return platform2 === "win32" ? key.toLowerCase() : key;
|
|
16761
17189
|
}
|
|
16762
17190
|
const OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
|
|
16763
17191
|
const REPO_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
@@ -16781,15 +17209,466 @@ function parseGitHubRemote(url) {
|
|
|
16781
17209
|
if (!OWNER_PATTERN.test(owner) || !repo || repo === "." || repo === ".." || !REPO_PATTERN.test(repo)) return null;
|
|
16782
17210
|
return { nameWithOwner: `${owner}/${repo}` };
|
|
16783
17211
|
}
|
|
16784
|
-
|
|
16785
|
-
const
|
|
16786
|
-
const
|
|
16787
|
-
const
|
|
16788
|
-
const
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
17212
|
+
function parseSshRemote(url) {
|
|
17213
|
+
const trimmed = url.trim();
|
|
17214
|
+
const ssh = /^ssh:\/\/(?:(\w[\w.-]*)@)?([A-Za-z0-9][A-Za-z0-9._-]*)(?::(\d+))?\/(.+)$/i.exec(trimmed);
|
|
17215
|
+
const scp = /^(?:(\w[\w.-]*)@)?([A-Za-z0-9][A-Za-z0-9._-]*):(.+)$/.exec(trimmed);
|
|
17216
|
+
const match = ssh ?? scp;
|
|
17217
|
+
if (!match) return null;
|
|
17218
|
+
const parsed = parseGitHubRemote(`https://github.com/${ssh ? match[4] : match[3]}`);
|
|
17219
|
+
if (!parsed) return null;
|
|
17220
|
+
return { destination: `${match[1] ? `${match[1]}@` : ""}${match[2]}`, port: ssh ? match[3] ?? null : null, ...parsed };
|
|
17221
|
+
}
|
|
17222
|
+
const GITHUB_LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
|
|
17223
|
+
function parseGitHubAuthStatus(raw) {
|
|
17224
|
+
const input = JSON.parse(raw);
|
|
17225
|
+
if (!input || typeof input !== "object" || !("hosts" in input) || !input.hosts || typeof input.hosts !== "object") throw new Error("Invalid gh status");
|
|
17226
|
+
const entries = input.hosts["github.com"] ?? [];
|
|
17227
|
+
if (!Array.isArray(entries) || entries.length > 100) throw new Error("Invalid gh accounts");
|
|
17228
|
+
const accounts = [];
|
|
17229
|
+
for (const entry of entries) {
|
|
17230
|
+
if (!entry || typeof entry !== "object" || typeof entry.login !== "string" || !GITHUB_LOGIN.test(entry.login)) continue;
|
|
17231
|
+
accounts.push({
|
|
17232
|
+
host: "github.com",
|
|
17233
|
+
login: entry.login,
|
|
17234
|
+
active: entry.active === true,
|
|
17235
|
+
state: entry.state === "success" ? "authenticated" : entry.state === "error" ? "invalid" : "unknown",
|
|
17236
|
+
storage: entry.tokenSource === "keyring" ? "keyring" : typeof entry.tokenSource === "string" && (entry.tokenSource === "oauth_token" || /(?:^|[/\\])hosts\.yml$/.test(entry.tokenSource)) ? "file" : "unknown",
|
|
17237
|
+
...typeof entry.scopes === "string" && /^[A-Za-z0-9_:, -]{0,500}$/.test(entry.scopes) ? { scopes: entry.scopes } : {}
|
|
17238
|
+
});
|
|
17239
|
+
}
|
|
17240
|
+
return { accounts, activeLogin: accounts.find((account) => account.active)?.login ?? null };
|
|
17241
|
+
}
|
|
17242
|
+
const GH_ENV = { GH_HOST: "github.com", GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1", GH_PAGER: "cat", NO_COLOR: "1" };
|
|
17243
|
+
const GH_PRIVATE_ENV = ["GITHUB_TOKEN", "GH_DEBUG", "DEBUG"];
|
|
17244
|
+
async function identifySshAccount(remote, runner) {
|
|
17245
|
+
const ssh = parseSshRemote(remote);
|
|
17246
|
+
if (!ssh) return null;
|
|
17247
|
+
try {
|
|
17248
|
+
const config2 = await runner.run("ssh", ["-G", ...ssh.port ? ["-p", ssh.port] : [], "--", ssh.destination], {
|
|
17249
|
+
cwd: os.homedir(),
|
|
17250
|
+
timeoutMs: 5e3,
|
|
17251
|
+
maxOutputBytes: 64 * 1024
|
|
17252
|
+
});
|
|
17253
|
+
const hostname = config2.stdout.split(/\r?\n/).find((line) => /^hostname\s/i.test(line))?.trim().split(/\s+/)[1];
|
|
17254
|
+
if (config2.exitCode !== 0 || hostname?.toLowerCase() !== "github.com") return null;
|
|
17255
|
+
const result = await runner.run("ssh", [
|
|
17256
|
+
"-T",
|
|
17257
|
+
"-o",
|
|
17258
|
+
"BatchMode=yes",
|
|
17259
|
+
"-o",
|
|
17260
|
+
"StrictHostKeyChecking=yes",
|
|
17261
|
+
"-o",
|
|
17262
|
+
"ConnectTimeout=5",
|
|
17263
|
+
"-o",
|
|
17264
|
+
"ConnectionAttempts=1",
|
|
17265
|
+
"-o",
|
|
17266
|
+
"RemoteCommand=none",
|
|
17267
|
+
"-o",
|
|
17268
|
+
"UpdateHostKeys=no",
|
|
17269
|
+
...ssh.port ? ["-p", ssh.port] : [],
|
|
17270
|
+
"--",
|
|
17271
|
+
ssh.destination
|
|
17272
|
+
], { cwd: os.homedir(), timeoutMs: 1e4, maxOutputBytes: 64 * 1024 });
|
|
17273
|
+
if (result.exitCode !== 0 && result.exitCode !== 1) return null;
|
|
17274
|
+
const login2 = /^Hi ([A-Za-z0-9-]+)! You've successfully authenticated, but GitHub does not provide shell access\.\s*$/m.exec(`${result.stdout}
|
|
17275
|
+
${result.stderr}`)?.[1];
|
|
17276
|
+
return login2 && GITHUB_LOGIN.test(login2) ? login2 : null;
|
|
17277
|
+
} catch {
|
|
17278
|
+
return null;
|
|
17279
|
+
}
|
|
17280
|
+
}
|
|
17281
|
+
async function accountAuthentication(login2, executable, runner, operation) {
|
|
17282
|
+
try {
|
|
17283
|
+
const result = await runner.run(executable.executable, ["auth", "token", "--hostname", "github.com", ...login2 ? ["--user", login2] : []], {
|
|
17284
|
+
cwd: os.homedir(),
|
|
17285
|
+
env: { ...executable.env, ...GH_ENV },
|
|
17286
|
+
removeEnv: login2 ? ["GH_TOKEN", ...GH_PRIVATE_ENV] : ["GH_DEBUG", "DEBUG"],
|
|
17287
|
+
timeoutMs: 1e4,
|
|
17288
|
+
maxOutputBytes: 8 * 1024
|
|
17289
|
+
});
|
|
17290
|
+
const token = result.stdout.trim();
|
|
17291
|
+
if (result.exitCode === 0 && /^[A-Za-z0-9_]{20,4096}$/.test(token)) return { env: { GH_TOKEN: token }, removeEnv: GH_PRIVATE_ENV };
|
|
17292
|
+
if (/unknown flag.*user/i.test(result.stderr)) throw new GhOperationError({
|
|
17293
|
+
code: "GH_CLI_INCOMPATIBLE",
|
|
17294
|
+
operation,
|
|
17295
|
+
message: "Update GitHub CLI to a version supporting multiple accounts and auth token --user."
|
|
17296
|
+
});
|
|
17297
|
+
} catch (error) {
|
|
17298
|
+
if (error instanceof GhOperationError) throw error;
|
|
17299
|
+
}
|
|
17300
|
+
throw new GhOperationError({
|
|
17301
|
+
code: login2 ? "GH_ACCOUNT_MISSING" : "GH_AUTH_REQUIRED",
|
|
17302
|
+
operation,
|
|
17303
|
+
message: login2 ? `GitHub CLI has no usable saved credentials for @${login2}. Sign in as ${login2} on the backend host and check Settings → GitHub again. SSH and GitHub CLI authentication are separate.` : "GitHub CLI has no usable credentials. Sign in on the backend host and check Settings → GitHub again."
|
|
17304
|
+
});
|
|
17305
|
+
}
|
|
17306
|
+
function redactCommandToken(value, auth) {
|
|
17307
|
+
const token = auth.env?.GH_TOKEN;
|
|
17308
|
+
return token ? value.split(token).join("[redacted]") : value;
|
|
17309
|
+
}
|
|
17310
|
+
const CACHE_MS = 6e4;
|
|
17311
|
+
const emptyStatus = () => ({
|
|
17312
|
+
installationStatus: "unchecked",
|
|
17313
|
+
accounts: [],
|
|
17314
|
+
activeLogin: null,
|
|
17315
|
+
environment: { present: false, login: null, state: "unknown" },
|
|
17316
|
+
checkedAt: null
|
|
17317
|
+
});
|
|
17318
|
+
class GitHubAccountsService {
|
|
17319
|
+
constructor(resolver, runner, git, settings, store, changed = () => void 0) {
|
|
17320
|
+
this.resolver = resolver;
|
|
17321
|
+
this.runner = runner;
|
|
17322
|
+
this.git = git;
|
|
17323
|
+
this.settings = settings;
|
|
17324
|
+
this.store = store;
|
|
17325
|
+
this.changed = changed;
|
|
17326
|
+
}
|
|
17327
|
+
resolver;
|
|
17328
|
+
runner;
|
|
17329
|
+
git;
|
|
17330
|
+
settings;
|
|
17331
|
+
store;
|
|
17332
|
+
changed;
|
|
17333
|
+
inventory = null;
|
|
17334
|
+
loading = null;
|
|
17335
|
+
checking = null;
|
|
17336
|
+
detection = null;
|
|
17337
|
+
revision = 0;
|
|
17338
|
+
get authRevision() {
|
|
17339
|
+
return this.revision;
|
|
17340
|
+
}
|
|
17341
|
+
contexts = /* @__PURE__ */ new Map();
|
|
17342
|
+
ssh = /* @__PURE__ */ new Map();
|
|
17343
|
+
identities = /* @__PURE__ */ new Map();
|
|
17344
|
+
status(force = false) {
|
|
17345
|
+
if (force) {
|
|
17346
|
+
this.checking ??= this.check().finally(() => {
|
|
17347
|
+
this.checking = null;
|
|
17348
|
+
});
|
|
17349
|
+
return this.checking.then((value) => structuredClone(value));
|
|
17350
|
+
}
|
|
17351
|
+
this.loading ??= (async () => {
|
|
17352
|
+
this.inventory ??= await this.store?.load() ?? emptyStatus();
|
|
17353
|
+
return this.inventory;
|
|
17354
|
+
})();
|
|
17355
|
+
return this.loading.then(() => structuredClone(this.inventory ?? emptyStatus()));
|
|
17356
|
+
}
|
|
17357
|
+
selection(repository) {
|
|
17358
|
+
return this.settings?.githubAccount(repository.commonDir) ?? { ...AUTOMATIC_GITHUB_ACCOUNT };
|
|
17359
|
+
}
|
|
17360
|
+
context(repository, remote, nameWithOwner) {
|
|
17361
|
+
const cached2 = this.contexts.get(repository.id);
|
|
17362
|
+
if (cached2?.remote === remote && cached2.value.revision === this.revision) return structuredClone(cached2.value);
|
|
17363
|
+
const selection = this.selection(repository);
|
|
17364
|
+
return {
|
|
17365
|
+
selection,
|
|
17366
|
+
nameWithOwner,
|
|
17367
|
+
login: selection.mode === "account" ? selection.login : null,
|
|
17368
|
+
source: selection.mode === "account" ? "explicit" : null,
|
|
17369
|
+
state: "unchecked",
|
|
17370
|
+
checkedAt: null,
|
|
17371
|
+
revision: this.revision
|
|
17372
|
+
};
|
|
17373
|
+
}
|
|
17374
|
+
async setSelection(repository, selection) {
|
|
17375
|
+
if (!this.settings) throw new Error("GitHub account settings are unavailable.");
|
|
17376
|
+
await this.settings.setGitHubAccount(repository.commonDir, selection);
|
|
17377
|
+
this.invalidate();
|
|
17378
|
+
}
|
|
17379
|
+
invalidate() {
|
|
17380
|
+
this.revision++;
|
|
17381
|
+
this.contexts.clear();
|
|
17382
|
+
this.ssh.clear();
|
|
17383
|
+
this.identities.clear();
|
|
17384
|
+
this.changed();
|
|
17385
|
+
}
|
|
17386
|
+
refreshAuthentication() {
|
|
17387
|
+
this.ssh.clear();
|
|
17388
|
+
this.identities.clear();
|
|
17389
|
+
}
|
|
17390
|
+
async detect(force = false) {
|
|
17391
|
+
if (force || !this.detection || Date.now() - this.detection.at > CACHE_MS) {
|
|
17392
|
+
this.detection = { at: Date.now(), promise: selectCli(this.resolver, this.runner, "gh", {
|
|
17393
|
+
forceRefresh: force,
|
|
17394
|
+
runOptions: { cwd: os.homedir(), env: { ...GH_ENV }, removeEnv: ["GH_DEBUG", "DEBUG"] }
|
|
17395
|
+
}) };
|
|
17396
|
+
}
|
|
17397
|
+
return this.detection.promise;
|
|
17398
|
+
}
|
|
17399
|
+
async authenticate(repository, remote, nameWithOwner, operation) {
|
|
17400
|
+
const revision2 = this.revision;
|
|
17401
|
+
const selection = this.selection(repository);
|
|
17402
|
+
let login2 = selection.mode === "account" ? selection.login : null;
|
|
17403
|
+
let source = selection.mode === "account" ? "explicit" : null;
|
|
17404
|
+
try {
|
|
17405
|
+
const detection = await this.detect();
|
|
17406
|
+
if (detection.state !== "available" || !detection.candidate) throw new GhOperationError({
|
|
17407
|
+
code: detection.state === "not-found" ? "GH_CLI_NOT_FOUND" : "GH_PROCESS_FAILED",
|
|
17408
|
+
operation,
|
|
17409
|
+
message: detection.state === "not-found" ? "GitHub CLI is not installed on the backend host." : "GitHub CLI could not run. Check Settings → GitHub."
|
|
17410
|
+
});
|
|
17411
|
+
const executable = detection.candidate;
|
|
17412
|
+
if (selection.mode === "auto" && parseSshRemote(remote)) {
|
|
17413
|
+
const override = executable.env.GIT_SSH_COMMAND || executable.env.GIT_SSH || process.env.GIT_SSH_COMMAND || process.env.GIT_SSH;
|
|
17414
|
+
const configured = await this.git.run(repository.path, ["config", "--default", "", "--get", "core.sshCommand"], {
|
|
17415
|
+
operation: "github-ssh-configuration",
|
|
17416
|
+
readOnly: true,
|
|
17417
|
+
maxOutputBytes: 8 * 1024
|
|
17418
|
+
});
|
|
17419
|
+
if (override || configured.stdout.toString("utf8").trim()) throw new GhOperationError({
|
|
17420
|
+
code: "GH_ACCOUNT_UNRESOLVED",
|
|
17421
|
+
operation,
|
|
17422
|
+
message: "This repository uses a custom SSH command. Choose an explicit account in Settings → GitHub."
|
|
17423
|
+
});
|
|
17424
|
+
const key = `${repository.path}\0${remote}`;
|
|
17425
|
+
let cached2 = this.ssh.get(key);
|
|
17426
|
+
if (!cached2 || Date.now() - cached2.at > CACHE_MS) {
|
|
17427
|
+
cached2 = { at: Date.now(), promise: identifySshAccount(remote, this.runner) };
|
|
17428
|
+
this.ssh.set(key, cached2);
|
|
17429
|
+
}
|
|
17430
|
+
login2 = await cached2.promise;
|
|
17431
|
+
source = "ssh";
|
|
17432
|
+
if (!login2) throw new GhOperationError({
|
|
17433
|
+
code: "GH_ACCOUNT_UNRESOLVED",
|
|
17434
|
+
operation,
|
|
17435
|
+
message: "SSH could not verify a GitHub user account. Check again or choose an explicit account in Settings → GitHub. Deploy keys do not identify user accounts."
|
|
17436
|
+
});
|
|
17437
|
+
}
|
|
17438
|
+
if (!source) source = executable.env.GH_TOKEN || executable.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN ? "environment" : "global";
|
|
17439
|
+
const auth = await accountAuthentication(login2, executable, this.runner, operation);
|
|
17440
|
+
const verified = await this.identity(executable, auth, operation);
|
|
17441
|
+
if (login2 && login2.toLowerCase() !== verified.toLowerCase()) throw new GhOperationError({
|
|
17442
|
+
code: "GH_AUTH_REQUIRED",
|
|
17443
|
+
operation,
|
|
17444
|
+
message: `The saved credentials for @${login2} authenticate a different account. Authenticate @${login2} again and check Settings → GitHub.`
|
|
17445
|
+
});
|
|
17446
|
+
login2 = verified;
|
|
17447
|
+
const context = { selection, nameWithOwner, login: login2, source, state: "ready", checkedAt: (/* @__PURE__ */ new Date()).toISOString(), revision: revision2 };
|
|
17448
|
+
if (revision2 === this.revision) {
|
|
17449
|
+
const previous = this.contexts.get(repository.id)?.value;
|
|
17450
|
+
if (previous?.state === "ready" && (previous.login?.toLowerCase() !== login2.toLowerCase() || previous.source !== source)) {
|
|
17451
|
+
this.invalidate();
|
|
17452
|
+
context.revision = this.revision;
|
|
17453
|
+
}
|
|
17454
|
+
this.contexts.set(repository.id, { remote, value: context });
|
|
17455
|
+
}
|
|
17456
|
+
return { auth, executable, context };
|
|
17457
|
+
} catch (error) {
|
|
17458
|
+
const message = error instanceof GhOperationError ? error.message : "Could not inspect GitHub authentication. Check Settings → GitHub again.";
|
|
17459
|
+
if (revision2 === this.revision) this.contexts.set(repository.id, { remote, value: {
|
|
17460
|
+
selection,
|
|
17461
|
+
nameWithOwner,
|
|
17462
|
+
login: login2,
|
|
17463
|
+
source,
|
|
17464
|
+
state: "error",
|
|
17465
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17466
|
+
message,
|
|
17467
|
+
revision: revision2
|
|
17468
|
+
} });
|
|
17469
|
+
if (error instanceof GhOperationError) throw error;
|
|
17470
|
+
throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message, retryable: true });
|
|
17471
|
+
}
|
|
17472
|
+
}
|
|
17473
|
+
identity(executable, auth, operation) {
|
|
17474
|
+
const fingerprint = createHash("sha256").update(auth.env?.GH_TOKEN ?? "").digest("hex");
|
|
17475
|
+
let cached2 = this.identities.get(fingerprint);
|
|
17476
|
+
if (!cached2 || Date.now() - cached2.at > CACHE_MS) {
|
|
17477
|
+
const promise = (async () => {
|
|
17478
|
+
const result = await this.runner.run(executable.executable, ["api", "user", "--jq", ".login"], {
|
|
17479
|
+
cwd: os.homedir(),
|
|
17480
|
+
env: { ...executable.env, ...GH_ENV, ...auth.env },
|
|
17481
|
+
...auth.removeEnv ? { removeEnv: auth.removeEnv } : {},
|
|
17482
|
+
timeoutMs: 15e3,
|
|
17483
|
+
maxOutputBytes: 8 * 1024
|
|
17484
|
+
});
|
|
17485
|
+
const login2 = result.stdout.trim();
|
|
17486
|
+
if (result.exitCode === 0 && GITHUB_LOGIN.test(login2)) return login2;
|
|
17487
|
+
const invalid2 = /401|bad credentials|requires authentication/i.test(result.stderr);
|
|
17488
|
+
throw new GhOperationError({
|
|
17489
|
+
code: invalid2 ? "GH_AUTH_REQUIRED" : "GH_PROCESS_FAILED",
|
|
17490
|
+
operation,
|
|
17491
|
+
message: invalid2 ? "GitHub rejected these credentials. They may be expired or revoked. Authenticate this account again and check Settings → GitHub." : "Could not verify this GitHub account. Check your connection and try again.",
|
|
17492
|
+
retryable: !invalid2
|
|
17493
|
+
});
|
|
17494
|
+
})();
|
|
17495
|
+
cached2 = { at: Date.now(), promise };
|
|
17496
|
+
this.identities.set(fingerprint, cached2);
|
|
17497
|
+
void promise.catch(() => {
|
|
17498
|
+
if (this.identities.get(fingerprint)?.promise === promise) this.identities.delete(fingerprint);
|
|
17499
|
+
});
|
|
17500
|
+
}
|
|
17501
|
+
return cached2.promise;
|
|
17502
|
+
}
|
|
17503
|
+
async check() {
|
|
17504
|
+
await this.status();
|
|
17505
|
+
this.refreshAuthentication();
|
|
17506
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17507
|
+
const detection = await this.detect(true);
|
|
17508
|
+
const result = {
|
|
17509
|
+
...emptyStatus(),
|
|
17510
|
+
installationStatus: detection.state,
|
|
17511
|
+
checkedAt,
|
|
17512
|
+
...detection.version ? { version: detection.version } : {}
|
|
17513
|
+
};
|
|
17514
|
+
if (detection.state === "available" && detection.candidate) {
|
|
17515
|
+
const executable = detection.candidate;
|
|
17516
|
+
try {
|
|
17517
|
+
const status = await this.runner.run(executable.executable, ["auth", "status", "--hostname", "github.com", "--json", "hosts"], {
|
|
17518
|
+
cwd: os.homedir(),
|
|
17519
|
+
env: { ...executable.env, ...GH_ENV },
|
|
17520
|
+
removeEnv: ["GH_TOKEN", ...GH_PRIVATE_ENV],
|
|
17521
|
+
timeoutMs: 6e4,
|
|
17522
|
+
maxOutputBytes: 256 * 1024
|
|
17523
|
+
});
|
|
17524
|
+
if (status.exitCode !== 0) {
|
|
17525
|
+
if (/unknown flag.*json/i.test(status.stderr)) result.installationStatus = "incompatible";
|
|
17526
|
+
throw new Error("Status failed");
|
|
17527
|
+
}
|
|
17528
|
+
Object.assign(result, parseGitHubAuthStatus(status.stdout));
|
|
17529
|
+
const envToken = executable.env.GH_TOKEN || executable.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
17530
|
+
if (envToken) {
|
|
17531
|
+
result.environment.present = true;
|
|
17532
|
+
try {
|
|
17533
|
+
const auth = await accountAuthentication(null, executable, this.runner, "gh-auth-status");
|
|
17534
|
+
result.environment.login = await this.identity(executable, auth, "gh-auth-status");
|
|
17535
|
+
result.environment.state = "authenticated";
|
|
17536
|
+
} catch (error) {
|
|
17537
|
+
result.environment.state = error instanceof GhOperationError && error.detail.code === "GH_AUTH_REQUIRED" ? "invalid" : "unknown";
|
|
17538
|
+
}
|
|
17539
|
+
}
|
|
17540
|
+
} catch {
|
|
17541
|
+
result.message = result.installationStatus === "incompatible" ? "Update GitHub CLI to a version supporting auth status --json and multiple accounts." : "Could not check the saved GitHub accounts. Check your connection and CLI configuration, then try again.";
|
|
17542
|
+
result.accounts = this.inventory?.accounts.map((account) => ({ ...account, state: "unknown" })) ?? [];
|
|
17543
|
+
result.activeLogin = this.inventory?.activeLogin ?? null;
|
|
17544
|
+
}
|
|
17545
|
+
} else result.message = detection.state === "not-found" ? "Install GitHub CLI on the backend host." : "GitHub CLI was found but could not run. Check its installation and dependencies.";
|
|
17546
|
+
this.inventory = result;
|
|
17547
|
+
try {
|
|
17548
|
+
await this.store?.save(result);
|
|
17549
|
+
} finally {
|
|
17550
|
+
this.invalidate();
|
|
17551
|
+
}
|
|
17552
|
+
return result;
|
|
17553
|
+
}
|
|
17554
|
+
}
|
|
17555
|
+
const authState = _enum(["authenticated", "invalid", "unknown"]);
|
|
17556
|
+
const login = string().regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/);
|
|
17557
|
+
const schema = object({
|
|
17558
|
+
installationStatus: _enum(["unchecked", "available", "not-found", "not-executable", "incompatible", "inspection-failed"]),
|
|
17559
|
+
version: string().max(200).optional(),
|
|
17560
|
+
accounts: array(object({
|
|
17561
|
+
host: literal("github.com"),
|
|
17562
|
+
login,
|
|
17563
|
+
active: boolean(),
|
|
17564
|
+
state: authState,
|
|
17565
|
+
storage: _enum(["keyring", "file", "unknown"]),
|
|
17566
|
+
scopes: string().max(500).optional()
|
|
17567
|
+
})).max(100),
|
|
17568
|
+
activeLogin: login.nullable(),
|
|
17569
|
+
environment: object({ present: boolean(), login: login.nullable(), state: authState }),
|
|
17570
|
+
checkedAt: string().datetime().nullable(),
|
|
17571
|
+
message: string().max(500).optional()
|
|
17572
|
+
});
|
|
17573
|
+
class GitHubStatusStore {
|
|
17574
|
+
constructor(filePath) {
|
|
17575
|
+
this.filePath = filePath;
|
|
17576
|
+
}
|
|
17577
|
+
filePath;
|
|
17578
|
+
pending = Promise.resolve();
|
|
17579
|
+
async load() {
|
|
17580
|
+
await this.pending;
|
|
17581
|
+
try {
|
|
17582
|
+
if ((await stat(this.filePath)).size > 256 * 1024) return null;
|
|
17583
|
+
const parsed = schema.safeParse(JSON.parse(await readFile(this.filePath, "utf8")));
|
|
17584
|
+
return parsed.success ? parsed.data : null;
|
|
17585
|
+
} catch {
|
|
17586
|
+
return null;
|
|
17587
|
+
}
|
|
17588
|
+
}
|
|
17589
|
+
save(value) {
|
|
17590
|
+
const snapshot = JSON.stringify(schema.parse(value));
|
|
17591
|
+
const write = this.pending.then(() => writeFileAtomically(this.filePath, snapshot, { parseJson: true }));
|
|
17592
|
+
this.pending = write.catch(() => void 0);
|
|
17593
|
+
return write;
|
|
17594
|
+
}
|
|
17595
|
+
}
|
|
17596
|
+
const positive = number().int().positive().safe();
|
|
17597
|
+
const membershipSchema = object({
|
|
17598
|
+
stack: object({ number: positive, size: positive, baseRefName: string().min(1) }).nullable(),
|
|
17599
|
+
stackEntry: object({ position: positive }).nullable()
|
|
17600
|
+
});
|
|
17601
|
+
const stackSchema = object({
|
|
17602
|
+
number: positive,
|
|
17603
|
+
base: union([string().min(1), object({ ref: string().min(1) })]),
|
|
17604
|
+
pull_requests: array(object({
|
|
17605
|
+
number: positive,
|
|
17606
|
+
title: string().optional(),
|
|
17607
|
+
draft: boolean().optional(),
|
|
17608
|
+
head: object({ ref: string().min(1) }),
|
|
17609
|
+
state: _enum(["open", "closed", "merged"]),
|
|
17610
|
+
merged_at: string().nullable().optional()
|
|
17611
|
+
})).min(1)
|
|
17612
|
+
});
|
|
17613
|
+
function invalid() {
|
|
17614
|
+
return new GhOperationError({ code: "GH_INVALID_OUTPUT", operation: "gh-pr-stack", message: "GitHub returned unreadable stack information." });
|
|
17615
|
+
}
|
|
17616
|
+
function json(raw) {
|
|
17617
|
+
try {
|
|
17618
|
+
return JSON.parse(raw);
|
|
17619
|
+
} catch {
|
|
17620
|
+
throw invalid();
|
|
17621
|
+
}
|
|
17622
|
+
}
|
|
17623
|
+
function stackMembershipQuery(numbers) {
|
|
17624
|
+
if (!numbers.length || numbers.some((number2) => !positive.safeParse(number2).success)) throw invalid();
|
|
17625
|
+
return `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){${numbers.map((number2) => `pr${number2}:pullRequest(number:${number2}){stack{number size baseRefName} stackEntry{position}}`).join(" ")}}}`;
|
|
17626
|
+
}
|
|
17627
|
+
function parseStackMemberships(raw, numbers) {
|
|
17628
|
+
const envelope = object({ data: object({ repository: record(string(), membershipSchema.nullable()) }), errors: array(unknown()).optional() }).safeParse(json(raw));
|
|
17629
|
+
if (!envelope.success || envelope.data.errors?.length) throw invalid();
|
|
17630
|
+
const result = /* @__PURE__ */ new Map();
|
|
17631
|
+
for (const number2 of numbers) {
|
|
17632
|
+
const entry = envelope.data.data.repository[`pr${number2}`];
|
|
17633
|
+
if (entry === void 0) throw invalid();
|
|
17634
|
+
if (!entry?.stack && !entry?.stackEntry) continue;
|
|
17635
|
+
if (!entry?.stack || !entry.stackEntry || entry.stackEntry.position > entry.stack.size) throw invalid();
|
|
17636
|
+
result.set(number2, { number: entry.stack.number, size: entry.stack.size, position: entry.stackEntry.position, base: entry.stack.baseRefName });
|
|
17637
|
+
}
|
|
17638
|
+
return result;
|
|
17639
|
+
}
|
|
17640
|
+
function parsePullRequestStack(raw, prNumber, listing = false) {
|
|
17641
|
+
let value = json(raw);
|
|
17642
|
+
if (listing) {
|
|
17643
|
+
if (!Array.isArray(value) || value.length > 1) throw invalid();
|
|
17644
|
+
if (!value.length) return null;
|
|
17645
|
+
value = value[0];
|
|
17646
|
+
}
|
|
17647
|
+
const parsed = stackSchema.safeParse(value);
|
|
17648
|
+
if (!parsed.success) throw invalid();
|
|
17649
|
+
const stack = parsed.data;
|
|
17650
|
+
if (!stack.pull_requests.some((pr) => pr.number === prNumber) || new Set(stack.pull_requests.map((pr) => pr.number)).size !== stack.pull_requests.length) throw invalid();
|
|
17651
|
+
return {
|
|
17652
|
+
number: stack.number,
|
|
17653
|
+
base: typeof stack.base === "string" ? stack.base : stack.base.ref,
|
|
17654
|
+
layers: stack.pull_requests.map((pr) => ({
|
|
17655
|
+
number: pr.number,
|
|
17656
|
+
title: pr.title || pr.head.ref,
|
|
17657
|
+
headRefName: pr.head.ref,
|
|
17658
|
+
state: pr.merged_at || pr.state === "merged" ? "MERGED" : pr.state === "closed" ? "CLOSED" : "OPEN",
|
|
17659
|
+
isDraft: pr.draft ?? false
|
|
17660
|
+
}))
|
|
17661
|
+
};
|
|
17662
|
+
}
|
|
17663
|
+
const PR_SUMMARY_FIELDS = "number,title,state,isDraft,author,headRefName,baseRefName,updatedAt,url,reviewDecision,additions,deletions,statusCheckRollup";
|
|
17664
|
+
const PR_DETAIL_FIELDS = `${PR_SUMMARY_FIELDS},body,changedFiles,labels,commits`;
|
|
17665
|
+
const tolerantRecordSchema = looseObject({});
|
|
17666
|
+
const pullRequestArraySchema = array(unknown());
|
|
17667
|
+
const summaryRecordSchema = looseObject({
|
|
17668
|
+
number: number().safe().int().positive(),
|
|
17669
|
+
state: string().transform((value) => value.toUpperCase()).pipe(_enum(["OPEN", "CLOSED", "MERGED"]))
|
|
17670
|
+
});
|
|
17671
|
+
function parsePullRequestList(raw) {
|
|
16793
17672
|
const parsed = parseJson(raw, "gh-pr-list");
|
|
16794
17673
|
const list = pullRequestArraySchema.safeParse(parsed);
|
|
16795
17674
|
if (!list.success) throw invalidOutput("gh-pr-list");
|
|
@@ -16876,14 +17755,14 @@ function parseCommits(value) {
|
|
|
16876
17755
|
if (typeof record2.oid !== "string" || !record2.oid) return [];
|
|
16877
17756
|
const authors = Array.isArray(record2.authors) ? record2.authors : [];
|
|
16878
17757
|
const authorRecord = authors.map((author) => tolerantRecordSchema.safeParse(author)).find((author) => author.success)?.data;
|
|
16879
|
-
const
|
|
16880
|
-
const name = typeof authorRecord?.name === "string" && authorRecord.name ? authorRecord.name :
|
|
17758
|
+
const login2 = typeof authorRecord?.login === "string" ? authorRecord.login : null;
|
|
17759
|
+
const name = typeof authorRecord?.name === "string" && authorRecord.name ? authorRecord.name : login2 ?? "unknown";
|
|
16881
17760
|
return [{
|
|
16882
17761
|
oid: record2.oid,
|
|
16883
17762
|
messageHeadline: typeof record2.messageHeadline === "string" ? record2.messageHeadline : "",
|
|
16884
17763
|
authoredAt: typeof record2.authoredDate === "string" ? record2.authoredDate : "",
|
|
16885
17764
|
author: name,
|
|
16886
|
-
authorAvatarUrl:
|
|
17765
|
+
authorAvatarUrl: login2 ? avatarUrl(login2, login2.endsWith("[bot]")) : null
|
|
16887
17766
|
}];
|
|
16888
17767
|
});
|
|
16889
17768
|
}
|
|
@@ -16902,11 +17781,11 @@ function parseChecksState(value) {
|
|
|
16902
17781
|
}
|
|
16903
17782
|
return pending ? "PENDING" : "PASSING";
|
|
16904
17783
|
}
|
|
16905
|
-
function avatarUrl(
|
|
16906
|
-
if (!
|
|
16907
|
-
if (
|
|
16908
|
-
const botName =
|
|
16909
|
-
const path2 = isBot ||
|
|
17784
|
+
function avatarUrl(login2, isBot) {
|
|
17785
|
+
if (!login2 || login2 === "unknown") return null;
|
|
17786
|
+
if (login2 === "app/dependabot" || login2 === "dependabot[bot]") return "https://avatars.githubusercontent.com/in/29110?s=40&v=4";
|
|
17787
|
+
const botName = login2.startsWith("app/") ? login2.slice(4) : login2.replace(/\[bot\]$/i, "");
|
|
17788
|
+
const path2 = isBot || login2.startsWith("app/") ? botName : login2;
|
|
16910
17789
|
return `https://github.com/${encodeURIComponent(path2)}.png?size=40`;
|
|
16911
17790
|
}
|
|
16912
17791
|
function parseJson(raw, operation) {
|
|
@@ -16926,70 +17805,112 @@ function validTimestamp(value) {
|
|
|
16926
17805
|
function invalidOutput(operation) {
|
|
16927
17806
|
return new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "GitHub CLI returned an unexpected response." });
|
|
16928
17807
|
}
|
|
16929
|
-
const GH_ENV = { GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1", GH_PAGER: "cat", NO_COLOR: "1" };
|
|
16930
17808
|
const NETWORK_TIMEOUT_MS = 6e4;
|
|
16931
17809
|
class GitHubService {
|
|
16932
|
-
constructor(resolver, runner, git, repositories) {
|
|
16933
|
-
this.resolver = resolver;
|
|
17810
|
+
constructor(resolver, runner, git, repositories, accounts) {
|
|
16934
17811
|
this.runner = runner;
|
|
16935
17812
|
this.git = git;
|
|
16936
17813
|
this.repositories = repositories;
|
|
17814
|
+
this.accounts = accounts ?? new GitHubAccountsService(resolver, runner, git);
|
|
16937
17815
|
}
|
|
16938
|
-
resolver;
|
|
16939
17816
|
runner;
|
|
16940
17817
|
git;
|
|
16941
17818
|
repositories;
|
|
16942
|
-
|
|
17819
|
+
accounts;
|
|
16943
17820
|
async status(forceRefresh = false) {
|
|
16944
|
-
if (
|
|
16945
|
-
const
|
|
16946
|
-
|
|
16947
|
-
|
|
17821
|
+
if (forceRefresh) this.accounts.refreshAuthentication();
|
|
17822
|
+
const detected = await this.accounts.detect(forceRefresh);
|
|
17823
|
+
return {
|
|
17824
|
+
installed: !!detected.candidate,
|
|
17825
|
+
availability: detected.state === "available" ? "ready" : "error",
|
|
17826
|
+
authStatus: "unknown",
|
|
17827
|
+
...detected.version ? { version: detected.version } : {},
|
|
17828
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17829
|
+
};
|
|
17830
|
+
}
|
|
17831
|
+
async repositoryAccount(repositoryId, forceRefresh = false) {
|
|
17832
|
+
const repository = this.repositories.get(repositoryId);
|
|
17833
|
+
const info = await this.repositoryInfo(repositoryId);
|
|
17834
|
+
const remote = await this.remote(repositoryId);
|
|
17835
|
+
if (forceRefresh && info.nameWithOwner) {
|
|
17836
|
+
this.accounts.refreshAuthentication();
|
|
17837
|
+
try {
|
|
17838
|
+
await this.accounts.authenticate(repository, remote, info.nameWithOwner, "gh-repository-account");
|
|
17839
|
+
} catch {
|
|
17840
|
+
}
|
|
17841
|
+
}
|
|
17842
|
+
return this.accounts.context(repository, remote, info.nameWithOwner);
|
|
17843
|
+
}
|
|
17844
|
+
async setRepositoryAccount(repositoryId, selection) {
|
|
17845
|
+
await this.accounts.setSelection(this.repositories.get(repositoryId), selection);
|
|
17846
|
+
return this.repositoryAccount(repositoryId);
|
|
17847
|
+
}
|
|
17848
|
+
async remote(repositoryId) {
|
|
17849
|
+
const repository = this.repositories.get(repositoryId);
|
|
17850
|
+
try {
|
|
17851
|
+
const output = await this.git.run(repository.path, ["config", "--get", "remote.origin.url"], { operation: "github-remote-url", readOnly: true, maxOutputBytes: 64 * 1024 });
|
|
17852
|
+
return output.stdout.toString("utf8").trim();
|
|
17853
|
+
} catch {
|
|
17854
|
+
return "";
|
|
17855
|
+
}
|
|
16948
17856
|
}
|
|
16949
17857
|
async repositoryInfo(repositoryId) {
|
|
16950
17858
|
const repository = this.repositories.get(repositoryId);
|
|
16951
17859
|
try {
|
|
16952
17860
|
const output = await this.git.run(repository.path, ["config", "--get", "remote.origin.url"], { operation: "github-remote-url", readOnly: true, maxOutputBytes: 64 * 1024 });
|
|
16953
|
-
const
|
|
17861
|
+
const url = output.stdout.toString("utf8");
|
|
17862
|
+
let parsed = parseGitHubRemote(url);
|
|
17863
|
+
if (!parsed) {
|
|
17864
|
+
const ssh = parseSshRemote(url);
|
|
17865
|
+
if (ssh) {
|
|
17866
|
+
const config2 = await this.runner.run("ssh", ["-G", ...ssh.port ? ["-p", ssh.port] : [], "--", ssh.destination], {
|
|
17867
|
+
cwd: os.homedir(),
|
|
17868
|
+
timeoutMs: 5e3,
|
|
17869
|
+
maxOutputBytes: 64 * 1024
|
|
17870
|
+
});
|
|
17871
|
+
const hostname = config2.stdout.split(/\r?\n/).find((line) => /^hostname\s/i.test(line))?.trim().split(/\s+/)[1];
|
|
17872
|
+
if (config2.exitCode === 0 && hostname?.toLowerCase() === "github.com") parsed = { nameWithOwner: ssh.nameWithOwner };
|
|
17873
|
+
}
|
|
17874
|
+
}
|
|
16954
17875
|
return parsed ? { isGitHub: true, nameWithOwner: parsed.nameWithOwner } : { isGitHub: false, nameWithOwner: null };
|
|
16955
17876
|
} catch {
|
|
16956
17877
|
return { isGitHub: false, nameWithOwner: null };
|
|
16957
17878
|
}
|
|
16958
17879
|
}
|
|
16959
17880
|
async listPullRequests(repositoryId, states) {
|
|
16960
|
-
const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-list");
|
|
17881
|
+
const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-list");
|
|
16961
17882
|
const results = await Promise.all(states.map((state) => this.runGh(
|
|
16962
17883
|
["pr", "list", "-R", nameWithOwner, "--state", state.toLowerCase(), "--json", PR_SUMMARY_FIELDS, "--limit", "50"],
|
|
16963
|
-
{ cwd: repository.path, operation: "gh-pr-list", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
|
|
17884
|
+
{ auth, executable, cwd: repository.path, operation: "gh-pr-list", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
|
|
16964
17885
|
)));
|
|
16965
|
-
return this.withStackMembership(repository.path, nameWithOwner, selectPullRequestsNewestFirst(results.flatMap((result) => parsePullRequestList(result.stdout)), states));
|
|
17886
|
+
return this.withStackMembership(repository.path, nameWithOwner, auth, executable, selectPullRequestsNewestFirst(results.flatMap((result) => parsePullRequestList(result.stdout)), states));
|
|
16966
17887
|
}
|
|
16967
17888
|
async findPullRequestForBranch(repositoryId, branchName) {
|
|
16968
17889
|
const operation = "gh-pr-for-branch";
|
|
16969
|
-
const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
|
|
17890
|
+
const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
|
|
16970
17891
|
const branch = validateRef(branchName, operation);
|
|
16971
17892
|
const openResult = await this.runGh(
|
|
16972
17893
|
["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "open", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
|
|
16973
|
-
{ cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
|
|
17894
|
+
{ auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
|
|
16974
17895
|
);
|
|
16975
17896
|
const openPull = sortPullRequestsNewestFirst(parsePullRequestList(openResult.stdout)).find((pull) => pull.state === "OPEN");
|
|
16976
17897
|
if (openPull) return openPull;
|
|
16977
17898
|
const result = await this.runGh(
|
|
16978
17899
|
["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "all", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
|
|
16979
|
-
{ cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
|
|
17900
|
+
{ auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
|
|
16980
17901
|
);
|
|
16981
17902
|
return sortPullRequestsNewestFirst(parsePullRequestList(result.stdout))[0] ?? null;
|
|
16982
17903
|
}
|
|
16983
17904
|
async getPullRequest(repositoryId, prNumber) {
|
|
16984
|
-
const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-view");
|
|
17905
|
+
const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-view");
|
|
16985
17906
|
const result = await this.runGh(
|
|
16986
17907
|
["pr", "view", String(prNumber), "-R", nameWithOwner, "--json", PR_DETAIL_FIELDS],
|
|
16987
|
-
{ cwd: repository.path, operation: "gh-pr-view", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
|
|
17908
|
+
{ auth, executable, cwd: repository.path, operation: "gh-pr-view", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
|
|
16988
17909
|
);
|
|
16989
17910
|
const details = parsePullRequestDetails(result.stdout);
|
|
16990
|
-
return (await this.withStackMembership(repository.path, nameWithOwner, [details]))[0];
|
|
17911
|
+
return (await this.withStackMembership(repository.path, nameWithOwner, auth, executable, [details]))[0];
|
|
16991
17912
|
}
|
|
16992
|
-
async withStackMembership(cwd, nameWithOwner, pulls) {
|
|
17913
|
+
async withStackMembership(cwd, nameWithOwner, auth, executable, pulls) {
|
|
16993
17914
|
const [owner, name] = nameWithOwner.split("/");
|
|
16994
17915
|
const enriched = [];
|
|
16995
17916
|
for (let start = 0; start < pulls.length; start += 25) {
|
|
@@ -16997,6 +17918,8 @@ class GitHubService {
|
|
|
16997
17918
|
try {
|
|
16998
17919
|
const numbers = batch.map((pr) => pr.number);
|
|
16999
17920
|
const result = await this.runGh(["api", "graphql", "-f", `owner=${owner}`, "-f", `name=${name}`, "-f", `query=${stackMembershipQuery(numbers)}`], {
|
|
17921
|
+
auth,
|
|
17922
|
+
executable,
|
|
17000
17923
|
cwd,
|
|
17001
17924
|
operation: "gh-pr-stack-membership",
|
|
17002
17925
|
timeoutMs: 1e4,
|
|
@@ -17015,28 +17938,28 @@ class GitHubService {
|
|
|
17015
17938
|
}
|
|
17016
17939
|
async getPullRequestStack(repositoryId, prNumber) {
|
|
17017
17940
|
const operation = "gh-pr-stack";
|
|
17018
|
-
const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
|
|
17019
|
-
const options = { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 };
|
|
17020
|
-
const
|
|
17941
|
+
const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
|
|
17942
|
+
const options = { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 };
|
|
17943
|
+
const read2 = async (endpoint) => this.runGh(["api", endpoint], options);
|
|
17021
17944
|
let listing;
|
|
17022
17945
|
try {
|
|
17023
|
-
listing = await
|
|
17946
|
+
listing = await read2(`repos/${nameWithOwner}/stacks?pull_request=${prNumber}`);
|
|
17024
17947
|
} catch (error) {
|
|
17025
17948
|
if (error instanceof GhOperationError && error.detail.code === "GH_PROCESS_FAILED" && /HTTP 404/.test(error.message)) return null;
|
|
17026
17949
|
throw error;
|
|
17027
17950
|
}
|
|
17028
17951
|
const stack = parsePullRequestStack(listing.stdout, prNumber, true);
|
|
17029
17952
|
if (!stack) return null;
|
|
17030
|
-
const details = await
|
|
17953
|
+
const details = await read2(`repos/${nameWithOwner}/stacks/${stack.number}`);
|
|
17031
17954
|
const complete = parsePullRequestStack(details.stdout, prNumber);
|
|
17032
17955
|
if (complete?.number !== stack.number) throw new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "The stack changed while loading. Refresh to try again." });
|
|
17033
17956
|
return complete;
|
|
17034
17957
|
}
|
|
17035
17958
|
async getPullRequestDiff(repositoryId, prNumber) {
|
|
17036
|
-
const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-diff");
|
|
17959
|
+
const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-diff");
|
|
17037
17960
|
const result = await this.runGh(
|
|
17038
17961
|
["pr", "diff", String(prNumber), "-R", nameWithOwner],
|
|
17039
|
-
{ cwd: repository.path, operation: "gh-pr-diff", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
|
|
17962
|
+
{ auth, executable, cwd: repository.path, operation: "gh-pr-diff", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
|
|
17040
17963
|
);
|
|
17041
17964
|
const patch = result.stdout;
|
|
17042
17965
|
const binary = /Binary files .* differ|GIT binary patch/.test(patch);
|
|
@@ -17044,13 +17967,13 @@ class GitHubService {
|
|
|
17044
17967
|
}
|
|
17045
17968
|
async getPullRequestCommitDiff(repositoryId, oid) {
|
|
17046
17969
|
const operation = "gh-pr-commit-diff";
|
|
17047
|
-
const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
|
|
17970
|
+
const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
|
|
17048
17971
|
if (!/^[0-9a-f]{40,64}$/i.test(oid)) {
|
|
17049
17972
|
throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "The commit is not valid." });
|
|
17050
17973
|
}
|
|
17051
17974
|
const result = await this.runGh(
|
|
17052
17975
|
["api", `repos/${nameWithOwner}/commits/${oid}`, "--header", "Accept: application/vnd.github.diff"],
|
|
17053
|
-
{ cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
|
|
17976
|
+
{ auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
|
|
17054
17977
|
);
|
|
17055
17978
|
const patch = result.stdout;
|
|
17056
17979
|
const binary = /Binary files .* differ|GIT binary patch/.test(patch);
|
|
@@ -17058,7 +17981,13 @@ class GitHubService {
|
|
|
17058
17981
|
}
|
|
17059
17982
|
async createPullRequest(input) {
|
|
17060
17983
|
const operation = "gh-pr-create";
|
|
17061
|
-
const { repository, nameWithOwner } = await this.requireGitHub(input.repositoryId, operation);
|
|
17984
|
+
const { repository, nameWithOwner, auth, executable, context } = await this.requireGitHub(input.repositoryId, operation);
|
|
17985
|
+
const verifyAccount = () => {
|
|
17986
|
+
if (input.expectedAccount && (input.expectedAccount.revision !== this.accounts.authRevision || input.expectedAccount.revision !== context.revision || input.expectedAccount.login.toLowerCase() !== context.login?.toLowerCase())) {
|
|
17987
|
+
throw new GhOperationError({ code: "GH_ACCOUNT_UNRESOLVED", operation, message: "The GitHub account changed. Review the current identity before creating the pull request." });
|
|
17988
|
+
}
|
|
17989
|
+
};
|
|
17990
|
+
verifyAccount();
|
|
17062
17991
|
const status = await this.repositories.status(input.repositoryId, false);
|
|
17063
17992
|
if (status.detached || status.unborn || !status.branch) {
|
|
17064
17993
|
throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "Check out a branch before creating a pull request." });
|
|
@@ -17076,7 +18005,8 @@ class GitHubService {
|
|
|
17076
18005
|
await writeFile(bodyPath, input.body, { encoding: "utf8", mode: 384 });
|
|
17077
18006
|
const args = ["pr", "create", "-R", nameWithOwner, "--title", input.title, "--body-file", bodyPath, "--base", base, "--head", head];
|
|
17078
18007
|
if (input.draft) args.push("--draft");
|
|
17079
|
-
|
|
18008
|
+
verifyAccount();
|
|
18009
|
+
const result = await this.runGh(args, { auth, executable, cwd: repository.path, operation, timeoutMs: 12e4, maxOutputBytes: 1024 * 1024 });
|
|
17080
18010
|
const created = parseCreatedPullRequestUrl(result.stdout);
|
|
17081
18011
|
if (!created) throw new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "GitHub CLI did not return the URL of the new pull request." });
|
|
17082
18012
|
return created;
|
|
@@ -17084,51 +18014,32 @@ class GitHubService {
|
|
|
17084
18014
|
await rm(temporary, { recursive: true, force: true });
|
|
17085
18015
|
}
|
|
17086
18016
|
}
|
|
17087
|
-
async checkStatus(forceRefresh) {
|
|
17088
|
-
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17089
|
-
const detected = await selectCli(this.resolver, this.runner, "gh", { forceRefresh, runOptions: { cwd: os.tmpdir(), env: { ...GH_ENV } } });
|
|
17090
|
-
const executable = detected.candidate;
|
|
17091
|
-
if (!executable) {
|
|
17092
|
-
return { installed: false, availability: "error", authStatus: "unknown", message: "Install GitHub CLI (cli.github.com) to work with pull requests.", checkedAt };
|
|
17093
|
-
}
|
|
17094
|
-
if (detected.state !== "available") return { installed: true, availability: "error", authStatus: "unknown", message: "GitHub CLI was found but could not run. Check its installation and dependencies.", checkedAt };
|
|
17095
|
-
const versionLabel = detected.version ?? "";
|
|
17096
|
-
const auth = await this.run(executable, ["auth", "status", "--hostname", "github.com", "--active"], { cwd: os.tmpdir(), operation: "gh-auth-status" });
|
|
17097
|
-
if (auth.exitCode !== 0) {
|
|
17098
|
-
return { installed: true, availability: "error", authStatus: "unauthenticated", version: versionLabel, message: "Run gh auth login.", checkedAt };
|
|
17099
|
-
}
|
|
17100
|
-
return { installed: true, availability: "ready", authStatus: "authenticated", version: versionLabel, checkedAt };
|
|
17101
|
-
}
|
|
17102
18017
|
async requireGitHub(repositoryId, operation) {
|
|
17103
18018
|
const repository = this.repositories.get(repositoryId);
|
|
17104
18019
|
const info = await this.repositoryInfo(repositoryId);
|
|
17105
|
-
if (!info.isGitHub || !info.nameWithOwner) {
|
|
17106
|
-
|
|
17107
|
-
|
|
17108
|
-
|
|
17109
|
-
if (!status.installed) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation, message: "GitHub CLI is not installed." });
|
|
17110
|
-
if (status.authStatus === "unauthenticated") throw new GhOperationError({ code: "GH_AUTH_REQUIRED", operation, message: "Sign in with gh auth login to continue." });
|
|
17111
|
-
return { repository, nameWithOwner: info.nameWithOwner };
|
|
18020
|
+
if (!info.isGitHub || !info.nameWithOwner) throw new GhOperationError({ code: "GH_NOT_GITHUB_REPO", operation, message: "The origin remote does not point to a GitHub repository." });
|
|
18021
|
+
const remote = await this.remote(repositoryId);
|
|
18022
|
+
const authenticated = await this.accounts.authenticate(repository, remote, info.nameWithOwner, operation);
|
|
18023
|
+
return { repository, nameWithOwner: info.nameWithOwner, ...authenticated };
|
|
17112
18024
|
}
|
|
17113
18025
|
async runGh(args, options) {
|
|
17114
|
-
const
|
|
17115
|
-
|
|
17116
|
-
if (!executable) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation: options.operation, message: "GitHub CLI is not installed." });
|
|
17117
|
-
const result = await this.run(executable, args, options);
|
|
17118
|
-
if (result.exitCode !== 0) throw classifyFailure(result, options.operation);
|
|
18026
|
+
const result = await this.run(options.executable, args, options);
|
|
18027
|
+
if (result.exitCode !== 0) throw classifyFailure({ ...result, stdout: redactCommandToken(result.stdout, options.auth), stderr: redactCommandToken(result.stderr, options.auth) }, options.operation);
|
|
17119
18028
|
return result;
|
|
17120
18029
|
}
|
|
17121
18030
|
async run(executable, args, options) {
|
|
17122
18031
|
try {
|
|
17123
18032
|
return await this.runner.run(executable.executable, args, {
|
|
17124
18033
|
cwd: options.cwd,
|
|
17125
|
-
env: { ...executable.env, ...GH_ENV },
|
|
18034
|
+
env: { ...executable.env, ...GH_ENV, ...options.auth?.env },
|
|
18035
|
+
...options.auth?.removeEnv ? { removeEnv: options.auth.removeEnv } : {},
|
|
17126
18036
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
17127
18037
|
...options.maxOutputBytes !== void 0 ? { maxOutputBytes: options.maxOutputBytes } : {},
|
|
17128
18038
|
...options.stdin !== void 0 ? { stdin: options.stdin } : {}
|
|
17129
18039
|
});
|
|
17130
18040
|
} catch (error) {
|
|
17131
|
-
|
|
18041
|
+
const mapped = mapRunnerError(error, options.operation);
|
|
18042
|
+
throw new GhOperationError({ ...mapped.detail, message: redactCommandToken(mapped.message, options.auth ?? {}) });
|
|
17132
18043
|
}
|
|
17133
18044
|
}
|
|
17134
18045
|
}
|
|
@@ -17152,6 +18063,8 @@ ${result.stdout}`.toLowerCase();
|
|
|
17152
18063
|
if (/could not resolve|no such host|connection refused|connection reset|network|dial tcp|tls handshake/.test(raw)) {
|
|
17153
18064
|
return new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "Could not connect to GitHub. Check your network and try again.", exitCode: result.exitCode, retryable: true });
|
|
17154
18065
|
}
|
|
18066
|
+
if (/403|resource not accessible|insufficient.*scope|saml|sso/.test(raw)) return new GhOperationError({ code: "GH_ACCESS_DENIED", operation, message: "This GitHub account cannot perform the operation. Check repository permissions, token permissions and organisation SSO policies.", exitCode: result.exitCode });
|
|
18067
|
+
if (/404|could not resolve to a repository/.test(raw) && operation !== "gh-pr-stack") return new GhOperationError({ code: "GH_ACCESS_DENIED", operation, message: "The repository was not found or is not accessible with this GitHub account.", exitCode: result.exitCode });
|
|
17155
18068
|
const detail = firstLine(result.stderr) || firstLine(result.stdout);
|
|
17156
18069
|
return new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: detail || "GitHub CLI could not complete the operation.", exitCode: result.exitCode });
|
|
17157
18070
|
}
|
|
@@ -17167,6 +18080,50 @@ function mapRunnerError(error, operation) {
|
|
|
17167
18080
|
function firstLine(value) {
|
|
17168
18081
|
return (value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "").slice(0, 300);
|
|
17169
18082
|
}
|
|
18083
|
+
const statusSchema = object({
|
|
18084
|
+
id: _enum(["codex", "claude", "opencode", "grok"]),
|
|
18085
|
+
label: string(),
|
|
18086
|
+
availability: _enum(["ready", "warning", "error"]),
|
|
18087
|
+
installed: boolean(),
|
|
18088
|
+
authStatus: _enum(["authenticated", "unauthenticated", "unknown"]),
|
|
18089
|
+
installationStatus: _enum(["available", "not-found", "not-executable", "incompatible", "inspection-failed"]).optional(),
|
|
18090
|
+
executablePath: string().optional(),
|
|
18091
|
+
executableSource: _enum(["configured", "process-path", "user-path", "known-location"]).optional(),
|
|
18092
|
+
discoveryWarning: string().optional(),
|
|
18093
|
+
version: string().optional(),
|
|
18094
|
+
message: string().optional(),
|
|
18095
|
+
cliName: string().optional(),
|
|
18096
|
+
models: array(object({ id: string(), label: string(), description: string().optional() })),
|
|
18097
|
+
checkedAt: string().datetime()
|
|
18098
|
+
});
|
|
18099
|
+
const cacheSchema = object({
|
|
18100
|
+
version: literal(1),
|
|
18101
|
+
key: string(),
|
|
18102
|
+
statuses: array(statusSchema).min(1).max(4)
|
|
18103
|
+
}).refine(({ statuses }) => new Set(statuses.map((status) => status.id)).size === statuses.length);
|
|
18104
|
+
class AiStatusStore {
|
|
18105
|
+
constructor(filePath) {
|
|
18106
|
+
this.filePath = filePath;
|
|
18107
|
+
}
|
|
18108
|
+
filePath;
|
|
18109
|
+
pending = Promise.resolve();
|
|
18110
|
+
async load(key) {
|
|
18111
|
+
await this.pending;
|
|
18112
|
+
try {
|
|
18113
|
+
if ((await stat(this.filePath)).size > 4 * 1024 * 1024) return null;
|
|
18114
|
+
const parsed = cacheSchema.safeParse(JSON.parse(await readFile(this.filePath, "utf8")));
|
|
18115
|
+
return parsed.success && parsed.data.key === key ? parsed.data.statuses : null;
|
|
18116
|
+
} catch {
|
|
18117
|
+
return null;
|
|
18118
|
+
}
|
|
18119
|
+
}
|
|
18120
|
+
save(key, statuses) {
|
|
18121
|
+
const snapshot = cacheSchema.parse({ version: 1, key, statuses });
|
|
18122
|
+
const write = this.pending.then(() => writeFileAtomically(this.filePath, JSON.stringify(snapshot), { parseJson: true }));
|
|
18123
|
+
this.pending = write.catch(() => void 0);
|
|
18124
|
+
return write;
|
|
18125
|
+
}
|
|
18126
|
+
}
|
|
17170
18127
|
const MAX_PROBLEM_LOG_ENTRIES = 500;
|
|
17171
18128
|
const PROBLEM_LOG_COMPACTION_SLACK = 1.25;
|
|
17172
18129
|
const LEVELS = ["error", "warn"];
|
|
@@ -17570,6 +18527,7 @@ function normalizeRemoteFetchIntervalSeconds(value) {
|
|
|
17570
18527
|
const SETTINGS_SCHEMA_VERSION = 1;
|
|
17571
18528
|
const defaults = {
|
|
17572
18529
|
version: SETTINGS_SCHEMA_VERSION,
|
|
18530
|
+
githubAccounts: {},
|
|
17573
18531
|
recentRepositories: [],
|
|
17574
18532
|
repositoryProjects: [],
|
|
17575
18533
|
filesTreeStates: [],
|
|
@@ -17596,6 +18554,7 @@ const defaults = {
|
|
|
17596
18554
|
};
|
|
17597
18555
|
const settingsRecordSchema = looseObject({
|
|
17598
18556
|
version: unknown().optional(),
|
|
18557
|
+
githubAccounts: unknown().optional(),
|
|
17599
18558
|
recentRepositories: unknown().optional(),
|
|
17600
18559
|
repositoryProjects: unknown().optional(),
|
|
17601
18560
|
filesTreeStates: unknown().optional(),
|
|
@@ -17672,6 +18631,25 @@ class SettingsStore {
|
|
|
17672
18631
|
get windowBounds() {
|
|
17673
18632
|
return { ...this.data.windowBounds };
|
|
17674
18633
|
}
|
|
18634
|
+
githubAccount(commonDir) {
|
|
18635
|
+
return { ...this.data.githubAccounts[githubRepositoryKey(commonDir)] ?? AUTOMATIC_GITHUB_ACCOUNT };
|
|
18636
|
+
}
|
|
18637
|
+
async setGitHubAccount(commonDir, selection) {
|
|
18638
|
+
const parsed = githubAccountSelectionSchema.parse(selection);
|
|
18639
|
+
const key = githubRepositoryKey(commonDir);
|
|
18640
|
+
const previous = this.data.githubAccounts[key];
|
|
18641
|
+
if (parsed.mode === "auto") delete this.data.githubAccounts[key];
|
|
18642
|
+
else this.data.githubAccounts[key] = parsed;
|
|
18643
|
+
try {
|
|
18644
|
+
await this.save();
|
|
18645
|
+
} catch (error) {
|
|
18646
|
+
if (this.data.githubAccounts[key] === (parsed.mode === "auto" ? void 0 : parsed)) {
|
|
18647
|
+
if (previous) this.data.githubAccounts[key] = previous;
|
|
18648
|
+
else delete this.data.githubAccounts[key];
|
|
18649
|
+
}
|
|
18650
|
+
throw error;
|
|
18651
|
+
}
|
|
18652
|
+
}
|
|
17675
18653
|
async touchRepository(repository) {
|
|
17676
18654
|
const recent = { ...repository, lastOpenedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
17677
18655
|
this.data.recentRepositories = [recent, ...this.data.recentRepositories.filter((item) => item.id !== repository.id)];
|
|
@@ -17687,6 +18665,12 @@ class SettingsStore {
|
|
|
17687
18665
|
async relocateRepository(previousId, repository) {
|
|
17688
18666
|
const previous = this.data.recentRepositories.find((item) => item.id === previousId);
|
|
17689
18667
|
if (!previous) throw projectError("Unknown repository.");
|
|
18668
|
+
const previousAccountKey = githubRepositoryKey(previous.commonDir);
|
|
18669
|
+
const nextAccountKey = githubRepositoryKey(repository.commonDir);
|
|
18670
|
+
if (previousAccountKey !== nextAccountKey && this.data.githubAccounts[previousAccountKey]) {
|
|
18671
|
+
this.data.githubAccounts[nextAccountKey] ??= this.data.githubAccounts[previousAccountKey];
|
|
18672
|
+
delete this.data.githubAccounts[previousAccountKey];
|
|
18673
|
+
}
|
|
17690
18674
|
const previousKey = normalizeRepositoryKey(previous.commonDir);
|
|
17691
18675
|
const nextKey = normalizeRepositoryKey(repository.commonDir);
|
|
17692
18676
|
const destinationAlreadyAssigned = this.data.repositoryProjects.some((project) => project.repositoryKeys.some((key) => key === nextKey && key !== previousKey));
|
|
@@ -17711,6 +18695,9 @@ class SettingsStore {
|
|
|
17711
18695
|
async forgetRepository(repositoryKey) {
|
|
17712
18696
|
const key = normalizeRepositoryKey(repositoryKey);
|
|
17713
18697
|
const removedIds = new Set(this.data.recentRepositories.filter((item) => normalizeRepositoryKey(item.commonDir) === key).map((item) => item.id));
|
|
18698
|
+
for (const item of this.data.recentRepositories) {
|
|
18699
|
+
if (removedIds.has(item.id)) delete this.data.githubAccounts[githubRepositoryKey(item.commonDir)];
|
|
18700
|
+
}
|
|
17714
18701
|
this.data.recentRepositories = this.data.recentRepositories.filter((item) => !removedIds.has(item.id));
|
|
17715
18702
|
this.data.repositoryProjects = this.data.repositoryProjects.map((project) => ({
|
|
17716
18703
|
...project,
|
|
@@ -17938,6 +18925,7 @@ function validate(value, defaultDoubleControlShortcutEnabled) {
|
|
|
17938
18925
|
const windowBounds = bounds && typeof bounds.width === "number" && Number.isFinite(bounds.width) && typeof bounds.height === "number" && Number.isFinite(bounds.height) ? { width: Math.max(900, bounds.width), height: Math.max(600, bounds.height), ...typeof bounds.x === "number" && Number.isFinite(bounds.x) ? { x: bounds.x } : {}, ...typeof bounds.y === "number" && Number.isFinite(bounds.y) ? { y: bounds.y } : {} } : { ...defaults.windowBounds };
|
|
17939
18926
|
return {
|
|
17940
18927
|
version: SETTINGS_SCHEMA_VERSION,
|
|
18928
|
+
githubAccounts: normalizeGitHubAccounts(input.githubAccounts),
|
|
17941
18929
|
recentRepositories,
|
|
17942
18930
|
repositoryProjects,
|
|
17943
18931
|
filesTreeStates,
|
|
@@ -18059,6 +19047,16 @@ function executablePaths(value, strict = false, platform2 = process.platform, ho
|
|
|
18059
19047
|
}
|
|
18060
19048
|
return result;
|
|
18061
19049
|
}
|
|
19050
|
+
function normalizeGitHubAccounts(value) {
|
|
19051
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
19052
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return result;
|
|
19053
|
+
for (const [key, selection] of Object.entries(value).slice(0, 1e3)) {
|
|
19054
|
+
if (!key || key.length > MAX_REPOSITORY_KEY_LENGTH || hasControlCharacters$1(key) || !path.isAbsolute(key)) continue;
|
|
19055
|
+
const parsed = githubAccountSelectionSchema.safeParse(selection);
|
|
19056
|
+
if (parsed.success && parsed.data.mode === "account") result[githubRepositoryKey(key)] = parsed.data;
|
|
19057
|
+
}
|
|
19058
|
+
return result;
|
|
19059
|
+
}
|
|
18062
19060
|
class SystemTrash {
|
|
18063
19061
|
available;
|
|
18064
19062
|
operation;
|
|
@@ -18129,6 +19127,9 @@ class OpenTigRuntime {
|
|
|
18129
19127
|
this.lastNotifiedAt = Date.now();
|
|
18130
19128
|
this.onEvent({ type: "repository.changed", repositoryId, scope });
|
|
18131
19129
|
}
|
|
19130
|
+
publishGitHubAccountsChange() {
|
|
19131
|
+
if (!this.closePromise) this.onEvent({ type: "github.accounts-changed" });
|
|
19132
|
+
}
|
|
18132
19133
|
publishActiveRepositoryChange(repository) {
|
|
18133
19134
|
if (!this.closePromise) this.onEvent({ type: "repository.active-changed", repository });
|
|
18134
19135
|
}
|
|
@@ -18187,10 +19188,21 @@ async function createOpenTigRuntime(options) {
|
|
|
18187
19188
|
];
|
|
18188
19189
|
const aiLog = new AiLogStore(options.aiLogPath);
|
|
18189
19190
|
await aiLog.load();
|
|
18190
|
-
const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate()
|
|
19191
|
+
const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate(), {
|
|
19192
|
+
store: new AiStatusStore(path.join(path.dirname(options.settingsPath), "ai-statuses.json")),
|
|
19193
|
+
key: () => aiExecutablePathsKey(settings.preferences.aiExecutablePaths)
|
|
19194
|
+
});
|
|
18191
19195
|
const prDrafts = new PullRequestDraftService(operations, providers, aiLog);
|
|
18192
|
-
const github = new GitHubService(cliResolver, cliRunner, git, repositories);
|
|
18193
19196
|
let runtime = null;
|
|
19197
|
+
const githubAccounts = new GitHubAccountsService(
|
|
19198
|
+
cliResolver,
|
|
19199
|
+
cliRunner,
|
|
19200
|
+
git,
|
|
19201
|
+
settings,
|
|
19202
|
+
new GitHubStatusStore(path.join(path.dirname(options.settingsPath), "github-status.json")),
|
|
19203
|
+
() => runtime?.publishGitHubAccountsChange()
|
|
19204
|
+
);
|
|
19205
|
+
const github = new GitHubService(cliResolver, cliRunner, git, repositories, githubAccounts);
|
|
18194
19206
|
const events = {
|
|
18195
19207
|
repositoryChanged: (repositoryId, scope) => {
|
|
18196
19208
|
runtime?.publishRepositoryChange(repositoryId, scope);
|
|
@@ -18399,6 +19411,8 @@ const IPC = {
|
|
|
18399
19411
|
commitUndoLatest: "commit:undo-latest",
|
|
18400
19412
|
commitsList: "commits:list",
|
|
18401
19413
|
commitsFiles: "commits:files",
|
|
19414
|
+
commitAuthorship: "commits:authorship",
|
|
19415
|
+
commitSetAuthorship: "commits:set-authorship",
|
|
18402
19416
|
branchesList: "refs:branches",
|
|
18403
19417
|
branchSwitch: "refs:switch",
|
|
18404
19418
|
worktreesList: "refs:worktrees",
|
|
@@ -18411,6 +19425,10 @@ const IPC = {
|
|
|
18411
19425
|
worktreeDetails: "refs:worktree-details",
|
|
18412
19426
|
branchDelete: "refs:delete-branch",
|
|
18413
19427
|
worktreeRemove: "refs:remove-worktree",
|
|
19428
|
+
remoteBranchDetails: "refs:remote-branch-details",
|
|
19429
|
+
trackingBranchCreate: "refs:create-tracking-branch",
|
|
19430
|
+
remoteBranchDelete: "refs:delete-remote-branch",
|
|
19431
|
+
branchesFetch: "refs:fetch-branches",
|
|
18414
19432
|
aiStatuses: "ai:statuses",
|
|
18415
19433
|
aiGenerateCommitMessage: "ai:generate-commit-message",
|
|
18416
19434
|
aiCancelGeneration: "ai:cancel-generation",
|
|
@@ -18419,6 +19437,9 @@ const IPC = {
|
|
|
18419
19437
|
diagnosticsList: "diagnostics:list",
|
|
18420
19438
|
diagnosticsClear: "diagnostics:clear",
|
|
18421
19439
|
diagnosticsRecord: "diagnostics:record",
|
|
19440
|
+
githubAccountsStatus: "github:accounts-status",
|
|
19441
|
+
githubRepositoryAccount: "github:repository-account",
|
|
19442
|
+
githubSetRepositoryAccount: "github:set-repository-account",
|
|
18422
19443
|
githubStatus: "github:status",
|
|
18423
19444
|
githubRepositoryInfo: "github:repository-info",
|
|
18424
19445
|
githubPrForBranch: "github:pr-for-branch",
|
|
@@ -18484,6 +19505,8 @@ const OPEN_TIG_SERVER_COMMANDS = {
|
|
|
18484
19505
|
"index.updateConflict": command(IPC.indexUpdateConflict, "update-conflict", true, { maxRequestBytes: CONTENT_MAX_REQUEST_BYTES }),
|
|
18485
19506
|
"index.resolveConflict": command(IPC.indexResolveConflict, "resolve-conflict", true, { maxRequestBytes: CONTENT_MAX_REQUEST_BYTES }),
|
|
18486
19507
|
"commits.create": command(IPC.commitCreate, "commit", true),
|
|
19508
|
+
"commits.authorship": command(IPC.commitAuthorship, "commit-authorship-read", false),
|
|
19509
|
+
"commits.setAuthorship": command(IPC.commitSetAuthorship, "commit-authorship-set", true),
|
|
18487
19510
|
"commits.undoLatest": command(IPC.commitUndoLatest, "undo-latest-commit", true),
|
|
18488
19511
|
"commits.list": command(IPC.commitsList, "history", false),
|
|
18489
19512
|
"commits.files": command(IPC.commitsFiles, "commit-files", false),
|
|
@@ -18496,6 +19519,10 @@ const OPEN_TIG_SERVER_COMMANDS = {
|
|
|
18496
19519
|
"refs.fetch": command(IPC.refsFetch, "fetch", true),
|
|
18497
19520
|
"refs.localRefsSnapshot": command(IPC.localRefsSnapshot, "local-refs-snapshot", false),
|
|
18498
19521
|
"refs.branchDetails": command(IPC.branchDetails, "branch-details", false),
|
|
19522
|
+
"refs.remoteBranchDetails": command(IPC.remoteBranchDetails, "remote-branch-details", false, { timeoutMs: 12e4 }),
|
|
19523
|
+
"refs.createTrackingBranch": command(IPC.trackingBranchCreate, "create-tracking-branch", true),
|
|
19524
|
+
"refs.deleteRemoteBranch": command(IPC.remoteBranchDelete, "delete-remote-branch", true, { timeoutMs: 18e4 }),
|
|
19525
|
+
"refs.fetchBranches": command(IPC.branchesFetch, "fetch-branches", true, { timeoutMs: 18e4 }),
|
|
18499
19526
|
"refs.worktreeDetails": command(IPC.worktreeDetails, "worktree-details", false),
|
|
18500
19527
|
"refs.deleteBranch": command(IPC.branchDelete, "delete-branch", true),
|
|
18501
19528
|
"refs.removeWorktree": command(IPC.worktreeRemove, "remove-worktree", true),
|
|
@@ -18504,6 +19531,9 @@ const OPEN_TIG_SERVER_COMMANDS = {
|
|
|
18504
19531
|
"ai.log": command(IPC.aiLog, "ai-log", false),
|
|
18505
19532
|
"ai.clearLog": command(IPC.aiClearLog, "ai-clear-log", true),
|
|
18506
19533
|
"ai.cancelGeneration": command(IPC.aiCancelGeneration, "ai-cancel-generation", true),
|
|
19534
|
+
"github.accountsStatus": command(IPC.githubAccountsStatus, "gh-accounts-status", false, { timeoutMs: 9e4 }),
|
|
19535
|
+
"github.repositoryAccount": command(IPC.githubRepositoryAccount, "gh-repository-account", false, { timeoutMs: 6e4 }),
|
|
19536
|
+
"github.setRepositoryAccount": command(IPC.githubSetRepositoryAccount, "gh-set-repository-account", true),
|
|
18507
19537
|
"github.status": command(IPC.githubStatus, "gh-status", false),
|
|
18508
19538
|
"github.repositoryInfo": command(IPC.githubRepositoryInfo, "gh-repository-info", false),
|
|
18509
19539
|
"github.findPullRequestForBranch": command(IPC.githubPrForBranch, "gh-pr-for-branch", false),
|
|
@@ -18730,10 +19760,10 @@ async function inspectRepository(path2, git) {
|
|
|
18730
19760
|
}
|
|
18731
19761
|
const requestRecordSchema = looseObject({});
|
|
18732
19762
|
function boundedStringSchema(maxLength, options = {}) {
|
|
18733
|
-
let
|
|
18734
|
-
if (options.empty !== true)
|
|
18735
|
-
if (options.controls === false) return
|
|
18736
|
-
return
|
|
19763
|
+
let schema2 = string().max(maxLength).refine((value) => !value.includes("\0"));
|
|
19764
|
+
if (options.empty !== true) schema2 = schema2.min(1);
|
|
19765
|
+
if (options.controls === false) return schema2;
|
|
19766
|
+
return schema2.refine((value) => ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127));
|
|
18737
19767
|
}
|
|
18738
19768
|
const booleanWithDefaultSchema = (fallback) => boolean().optional().default(fallback);
|
|
18739
19769
|
function parseRecord(value) {
|
|
@@ -18884,6 +19914,22 @@ function deleteBranchArg(value, operation) {
|
|
|
18884
19914
|
force: booleanArg(request.force, operation)
|
|
18885
19915
|
};
|
|
18886
19916
|
}
|
|
19917
|
+
function createTrackingBranchArg(value) {
|
|
19918
|
+
const operation = "create-tracking-branch";
|
|
19919
|
+
const request = requestObject(value, operation);
|
|
19920
|
+
return {
|
|
19921
|
+
...branchDetailsArg(request, operation),
|
|
19922
|
+
expectedOid: oidArg(request.expectedOid, operation),
|
|
19923
|
+
localName: refsString(request.localName, operation, MAX_BRANCH_REF_LENGTH)
|
|
19924
|
+
};
|
|
19925
|
+
}
|
|
19926
|
+
function deleteRemoteBranchArg(value) {
|
|
19927
|
+
const operation = "delete-remote-branch";
|
|
19928
|
+
const request = requestObject(value, operation);
|
|
19929
|
+
const destinationId = refsString(request.destinationId, operation, 64);
|
|
19930
|
+
if (!/^[0-9a-f]{64}$/.test(destinationId)) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation, message: "Invalid remote destination." });
|
|
19931
|
+
return { ...branchDetailsArg(request, operation), expectedOid: oidArg(request.expectedOid, operation), destinationId };
|
|
19932
|
+
}
|
|
18887
19933
|
function removeWorktreeArg(value, operation) {
|
|
18888
19934
|
const request = requestObject(value, operation);
|
|
18889
19935
|
return {
|
|
@@ -18981,7 +20027,16 @@ function createPullRequestArg(value) {
|
|
|
18981
20027
|
if (typeof input.base !== "string" || !input.base || input.base.length > 300 || input.base.includes("\0")) throw invalidGh(operation);
|
|
18982
20028
|
if (typeof input.draft !== "boolean") throw invalidGh(operation);
|
|
18983
20029
|
if (typeof input.repositoryId !== "string" || !input.repositoryId || input.repositoryId.length > 64) throw invalidGh(operation);
|
|
18984
|
-
|
|
20030
|
+
const expected = input.expectedAccount;
|
|
20031
|
+
if (expected !== void 0 && (!expected || typeof expected.login !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(expected.login) || !Number.isSafeInteger(expected.revision) || expected.revision < 0)) throw invalidGh(operation);
|
|
20032
|
+
return {
|
|
20033
|
+
repositoryId: input.repositoryId,
|
|
20034
|
+
title: input.title.trim(),
|
|
20035
|
+
body: input.body,
|
|
20036
|
+
base: input.base,
|
|
20037
|
+
draft: input.draft,
|
|
20038
|
+
...expected ? { expectedAccount: { login: expected.login, revision: expected.revision } } : {}
|
|
20039
|
+
};
|
|
18985
20040
|
}
|
|
18986
20041
|
function generatePullRequestDraftArg(value) {
|
|
18987
20042
|
const operation = "ai-pr-draft";
|
|
@@ -19069,8 +20124,9 @@ function registerServerCommands(registry2, services, host) {
|
|
|
19069
20124
|
};
|
|
19070
20125
|
});
|
|
19071
20126
|
handle(IPC.preferences, "preferences", async (partial2) => {
|
|
20127
|
+
const previousPaths = aiExecutablePathsKey(services.settings.preferences.aiExecutablePaths);
|
|
19072
20128
|
const preferences = await services.settings.setPreferences(partial2 ?? {});
|
|
19073
|
-
if (
|
|
20129
|
+
if (previousPaths !== aiExecutablePathsKey(preferences.aiExecutablePaths)) services.ai.invalidateStatuses();
|
|
19074
20130
|
return preferences;
|
|
19075
20131
|
});
|
|
19076
20132
|
handle(IPC.filesTreeStateUpdate, "files-tree-state", (repositoryId, expandedPaths) => {
|
|
@@ -19329,6 +20385,17 @@ function registerServerCommands(registry2, services, host) {
|
|
|
19329
20385
|
textArg(content, "update-conflict", 8 * 1024 * 1024)
|
|
19330
20386
|
));
|
|
19331
20387
|
handle(IPC.commitCreate, "commit", (id, message) => services.operations.createCommit(stringArg(id, "commit", 64), stringArg(message, "commit", 1e5)));
|
|
20388
|
+
handle(IPC.commitAuthorship, "commit-authorship-read", (id) => services.operations.getCommitAuthorship(stringArg(id, "commit-authorship-read", 64)));
|
|
20389
|
+
handle(IPC.commitSetAuthorship, "commit-authorship-set", async (id, input) => {
|
|
20390
|
+
const repositoryId = stringArg(id, "commit-authorship-set", 64);
|
|
20391
|
+
const parsed = commitAuthorshipInputSchema.safeParse(input);
|
|
20392
|
+
if (!parsed.success) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "commit-authorship-set", message: "Enter a valid name, email and reviewed identity." });
|
|
20393
|
+
const result = await services.operations.setCommitAuthorship(repositoryId, parsed.data);
|
|
20394
|
+
const repository = services.repositories.get(repositoryId);
|
|
20395
|
+
const ids = /* @__PURE__ */ new Set([repositoryId, ...services.repositories.recents().filter((item) => item.commonDir === repository.commonDir).map((item) => item.id)]);
|
|
20396
|
+
for (const siblingId of ids) services.events.repositoryChanged(siblingId, "unknown");
|
|
20397
|
+
return result;
|
|
20398
|
+
});
|
|
19332
20399
|
handle(IPC.commitUndoLatest, "undo-latest-commit", (id, expectedOid) => services.operations.undoLatestCommit(
|
|
19333
20400
|
stringArg(id, "undo-latest-commit", 64),
|
|
19334
20401
|
oidArg(expectedOid, "undo-latest-commit")
|
|
@@ -19353,6 +20420,13 @@ function registerServerCommands(registry2, services, host) {
|
|
|
19353
20420
|
const { repositoryId, fullName } = branchDetailsArg(request, "branch-details");
|
|
19354
20421
|
return services.operations.branchDetails(repositoryId, fullName);
|
|
19355
20422
|
});
|
|
20423
|
+
handle(IPC.remoteBranchDetails, "remote-branch-details", (request) => {
|
|
20424
|
+
const { repositoryId, fullName } = branchDetailsArg(request, "remote-branch-details");
|
|
20425
|
+
return services.operations.remoteBranchDetails(repositoryId, fullName);
|
|
20426
|
+
});
|
|
20427
|
+
handle(IPC.trackingBranchCreate, "create-tracking-branch", (request) => services.operations.createTrackingBranch(createTrackingBranchArg(request)));
|
|
20428
|
+
handle(IPC.remoteBranchDelete, "delete-remote-branch", (request) => services.operations.deleteRemoteBranch(deleteRemoteBranchArg(request)));
|
|
20429
|
+
handle(IPC.branchesFetch, "fetch-branches", (id) => services.operations.fetchBranches(stringArg(id, "fetch-branches", 64)));
|
|
19356
20430
|
handle(IPC.worktreeDetails, "worktree-details", (request) => {
|
|
19357
20431
|
const { repositoryId, path: path2 } = worktreeDetailsArg(request, "worktree-details");
|
|
19358
20432
|
return services.operations.worktreeDetails(repositoryId, path2);
|
|
@@ -19390,6 +20464,13 @@ function registerServerCommands(registry2, services, host) {
|
|
|
19390
20464
|
handle(IPC.aiCancelGeneration, "ai-cancel-generation", (requestId2) => {
|
|
19391
20465
|
services.ai.cancel(aiString(requestId2, "ai-cancel-generation", 100, true));
|
|
19392
20466
|
});
|
|
20467
|
+
handle(IPC.githubAccountsStatus, "gh-accounts-status", (forceRefresh) => services.github.accounts.status(booleanArg(forceRefresh, "gh-accounts-status")));
|
|
20468
|
+
handle(IPC.githubRepositoryAccount, "gh-repository-account", (id, forceRefresh) => services.github.repositoryAccount(stringArg(id, "gh-repository-account", 64), booleanArg(forceRefresh, "gh-repository-account")));
|
|
20469
|
+
handle(IPC.githubSetRepositoryAccount, "gh-set-repository-account", (id, selection) => {
|
|
20470
|
+
const parsed = githubAccountSelectionSchema.safeParse(selection);
|
|
20471
|
+
if (!parsed.success) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "gh-set-repository-account", message: "Invalid GitHub account selection." });
|
|
20472
|
+
return services.github.setRepositoryAccount(stringArg(id, "gh-set-repository-account", 64), parsed.data);
|
|
20473
|
+
});
|
|
19393
20474
|
handle(IPC.githubStatus, "gh-status", (forceRefresh) => services.github.status(booleanArg(forceRefresh, "gh-status")));
|
|
19394
20475
|
handle(IPC.githubRepositoryInfo, "gh-repository-info", (id) => services.github.repositoryInfo(stringArg(id, "gh-repository-info", 64)));
|
|
19395
20476
|
handle(IPC.githubPrForBranch, "gh-pr-for-branch", (id, branchName) => services.github.findPullRequestForBranch(
|
|
@@ -19439,7 +20520,7 @@ function clipboardImageArg(value) {
|
|
|
19439
20520
|
if (value instanceof Uint8Array) return value;
|
|
19440
20521
|
throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "paste-entries", message: "Invalid clipboard image." });
|
|
19441
20522
|
}
|
|
19442
|
-
const protocolVersion =
|
|
20523
|
+
const protocolVersion = 4;
|
|
19443
20524
|
const protocolMetadata = {
|
|
19444
20525
|
protocolVersion
|
|
19445
20526
|
};
|
|
@@ -19455,6 +20536,85 @@ function preferredServerPort(profile) {
|
|
|
19455
20536
|
}
|
|
19456
20537
|
const AUTH_DATA_VERSION = 4;
|
|
19457
20538
|
const BROWSER_SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
20539
|
+
async function hasActiveBrowserSessions(directory, now = Date.now) {
|
|
20540
|
+
const { sessions } = await loadSessions(path.join(directory, "sessions.json"));
|
|
20541
|
+
return sessions.some((session) => session.kind !== "desktop" && session.expiresAt !== null && now() < Date.parse(session.expiresAt));
|
|
20542
|
+
}
|
|
20543
|
+
async function loadSessions(filePath) {
|
|
20544
|
+
try {
|
|
20545
|
+
const parsed = JSON.parse(await readFile(filePath, "utf8"));
|
|
20546
|
+
if (isLegacyAuthData(parsed)) {
|
|
20547
|
+
await chmod(filePath, 384);
|
|
20548
|
+
return {
|
|
20549
|
+
sessions: parsed.sessions.map((session) => ({
|
|
20550
|
+
...session,
|
|
20551
|
+
kind: "legacy",
|
|
20552
|
+
expiresAt: sessionExpiry("legacy", session.createdAt),
|
|
20553
|
+
clientName: "Legacy browser session",
|
|
20554
|
+
deviceType: "unknown",
|
|
20555
|
+
os: null,
|
|
20556
|
+
browser: null,
|
|
20557
|
+
remoteAddress: null,
|
|
20558
|
+
viaProxy: false,
|
|
20559
|
+
lastConnectedAt: null
|
|
20560
|
+
})),
|
|
20561
|
+
migrated: true
|
|
20562
|
+
};
|
|
20563
|
+
}
|
|
20564
|
+
if (isVersionTwoAuthData(parsed)) {
|
|
20565
|
+
await chmod(filePath, 384);
|
|
20566
|
+
return {
|
|
20567
|
+
sessions: parsed.sessions.map((session) => ({
|
|
20568
|
+
...session,
|
|
20569
|
+
expiresAt: sessionExpiry(session.kind, session.createdAt),
|
|
20570
|
+
clientName: session.kind === "legacy" ? "Legacy browser session" : session.clientName,
|
|
20571
|
+
deviceType: session.kind === "desktop" ? "desktop" : "unknown",
|
|
20572
|
+
os: null,
|
|
20573
|
+
browser: session.kind === "browser" ? session.clientName : null,
|
|
20574
|
+
viaProxy: false,
|
|
20575
|
+
lastConnectedAt: null
|
|
20576
|
+
})),
|
|
20577
|
+
migrated: true
|
|
20578
|
+
};
|
|
20579
|
+
}
|
|
20580
|
+
if (isVersionThreeAuthData(parsed)) {
|
|
20581
|
+
await chmod(filePath, 384);
|
|
20582
|
+
return {
|
|
20583
|
+
sessions: parsed.sessions.map((session) => ({ ...session, expiresAt: sessionExpiry(session.kind, session.createdAt) })),
|
|
20584
|
+
migrated: true
|
|
20585
|
+
};
|
|
20586
|
+
}
|
|
20587
|
+
if (!isAuthData(parsed)) throw corrupt$1("session data");
|
|
20588
|
+
await chmod(filePath, 384);
|
|
20589
|
+
return { sessions: parsed.sessions.map((session) => ({ ...session })), migrated: false };
|
|
20590
|
+
} catch (error) {
|
|
20591
|
+
if (error.code === "ENOENT") return { sessions: [], migrated: false };
|
|
20592
|
+
if (error instanceof SyntaxError) throw corrupt$1("session data");
|
|
20593
|
+
throw error;
|
|
20594
|
+
}
|
|
20595
|
+
}
|
|
20596
|
+
function isAuthData(value) {
|
|
20597
|
+
const data = value;
|
|
20598
|
+
return Boolean(data && data.version === AUTH_DATA_VERSION && isVersionThreeAuthData({ ...data, version: 3 }) && data.sessions?.every((session) => session.kind === "desktop" ? session.expiresAt === null : typeof session.expiresAt === "string" && Number.isFinite(Date.parse(session.expiresAt)) && Date.parse(session.expiresAt) > Date.parse(session.createdAt)));
|
|
20599
|
+
}
|
|
20600
|
+
function isVersionThreeAuthData(value) {
|
|
20601
|
+
const data = value;
|
|
20602
|
+
return Boolean(data && data.version === 3 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.deviceType === "desktop" || session.deviceType === "mobile" || session.deviceType === "tablet" || session.deviceType === "bot" || session.deviceType === "unknown") && (session.os === null || typeof session.os === "string" && session.os.length <= 80) && (session.browser === null || typeof session.browser === "string" && session.browser.length <= 80) && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.viaProxy === "boolean" && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt)) && (session.lastConnectedAt === null || typeof session.lastConnectedAt === "string" && !Number.isNaN(Date.parse(session.lastConnectedAt)))));
|
|
20603
|
+
}
|
|
20604
|
+
function isVersionTwoAuthData(value) {
|
|
20605
|
+
const data = value;
|
|
20606
|
+
return Boolean(data && data.version === 2 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
|
|
20607
|
+
}
|
|
20608
|
+
function isLegacyAuthData(value) {
|
|
20609
|
+
const data = value;
|
|
20610
|
+
return Boolean(data && data.version === 1 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
|
|
20611
|
+
}
|
|
20612
|
+
function corrupt$1(name) {
|
|
20613
|
+
return new Error(`OpenTig authentication ${name} is corrupt.`);
|
|
20614
|
+
}
|
|
20615
|
+
function sessionExpiry(kind, createdAt) {
|
|
20616
|
+
return kind === "desktop" ? null : new Date(Date.parse(createdAt) + BROWSER_SESSION_MAX_AGE_SECONDS * 1e3).toISOString();
|
|
20617
|
+
}
|
|
19458
20618
|
const SECRET_BYTES = 32;
|
|
19459
20619
|
const SECRET_FILE = "server-secret";
|
|
19460
20620
|
const SESSIONS_FILE = "sessions.json";
|
|
@@ -19470,6 +20630,10 @@ class PersistentAuthStore {
|
|
|
19470
20630
|
now;
|
|
19471
20631
|
sessions;
|
|
19472
20632
|
mutationQueue = Promise.resolve();
|
|
20633
|
+
/** Inspect an existing installation without issuing, revoking, or writing sessions. */
|
|
20634
|
+
static async hasActiveBrowserSessions(directory, now = Date.now) {
|
|
20635
|
+
return hasActiveBrowserSessions(directory, now);
|
|
20636
|
+
}
|
|
19473
20637
|
static async open(directory, now = Date.now) {
|
|
19474
20638
|
const resolved = path.resolve(directory);
|
|
19475
20639
|
await mkdir(resolved, { recursive: true, mode: 448 });
|
|
@@ -19659,75 +20823,6 @@ async function loadOrCreateSecret(filePath) {
|
|
|
19659
20823
|
throw error;
|
|
19660
20824
|
}
|
|
19661
20825
|
}
|
|
19662
|
-
async function loadSessions(filePath) {
|
|
19663
|
-
try {
|
|
19664
|
-
const parsed = JSON.parse(await readFile(filePath, "utf8"));
|
|
19665
|
-
if (isLegacyAuthData(parsed)) {
|
|
19666
|
-
await chmod(filePath, 384);
|
|
19667
|
-
return {
|
|
19668
|
-
sessions: parsed.sessions.map((session) => ({
|
|
19669
|
-
...session,
|
|
19670
|
-
kind: "legacy",
|
|
19671
|
-
expiresAt: sessionExpiry("legacy", session.createdAt),
|
|
19672
|
-
clientName: "Legacy browser session",
|
|
19673
|
-
deviceType: "unknown",
|
|
19674
|
-
os: null,
|
|
19675
|
-
browser: null,
|
|
19676
|
-
remoteAddress: null,
|
|
19677
|
-
viaProxy: false,
|
|
19678
|
-
lastConnectedAt: null
|
|
19679
|
-
})),
|
|
19680
|
-
migrated: true
|
|
19681
|
-
};
|
|
19682
|
-
}
|
|
19683
|
-
if (isVersionTwoAuthData(parsed)) {
|
|
19684
|
-
await chmod(filePath, 384);
|
|
19685
|
-
return {
|
|
19686
|
-
sessions: parsed.sessions.map((session) => ({
|
|
19687
|
-
...session,
|
|
19688
|
-
expiresAt: sessionExpiry(session.kind, session.createdAt),
|
|
19689
|
-
clientName: session.kind === "legacy" ? "Legacy browser session" : session.clientName,
|
|
19690
|
-
deviceType: session.kind === "desktop" ? "desktop" : "unknown",
|
|
19691
|
-
os: null,
|
|
19692
|
-
browser: session.kind === "browser" ? session.clientName : null,
|
|
19693
|
-
viaProxy: false,
|
|
19694
|
-
lastConnectedAt: null
|
|
19695
|
-
})),
|
|
19696
|
-
migrated: true
|
|
19697
|
-
};
|
|
19698
|
-
}
|
|
19699
|
-
if (isVersionThreeAuthData(parsed)) {
|
|
19700
|
-
await chmod(filePath, 384);
|
|
19701
|
-
return {
|
|
19702
|
-
sessions: parsed.sessions.map((session) => ({ ...session, expiresAt: sessionExpiry(session.kind, session.createdAt) })),
|
|
19703
|
-
migrated: true
|
|
19704
|
-
};
|
|
19705
|
-
}
|
|
19706
|
-
if (!isAuthData(parsed)) throw corrupt("session data");
|
|
19707
|
-
await chmod(filePath, 384);
|
|
19708
|
-
return { sessions: parsed.sessions.map((session) => ({ ...session })), migrated: false };
|
|
19709
|
-
} catch (error) {
|
|
19710
|
-
if (error.code === "ENOENT") return { sessions: [], migrated: false };
|
|
19711
|
-
if (error instanceof SyntaxError) throw corrupt("session data");
|
|
19712
|
-
throw error;
|
|
19713
|
-
}
|
|
19714
|
-
}
|
|
19715
|
-
function isAuthData(value) {
|
|
19716
|
-
const data = value;
|
|
19717
|
-
return Boolean(data && data.version === AUTH_DATA_VERSION && isVersionThreeAuthData({ ...data, version: 3 }) && data.sessions?.every((session) => session.kind === "desktop" ? session.expiresAt === null : typeof session.expiresAt === "string" && Number.isFinite(Date.parse(session.expiresAt)) && Date.parse(session.expiresAt) > Date.parse(session.createdAt)));
|
|
19718
|
-
}
|
|
19719
|
-
function isVersionThreeAuthData(value) {
|
|
19720
|
-
const data = value;
|
|
19721
|
-
return Boolean(data && data.version === 3 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.deviceType === "desktop" || session.deviceType === "mobile" || session.deviceType === "tablet" || session.deviceType === "bot" || session.deviceType === "unknown") && (session.os === null || typeof session.os === "string" && session.os.length <= 80) && (session.browser === null || typeof session.browser === "string" && session.browser.length <= 80) && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.viaProxy === "boolean" && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt)) && (session.lastConnectedAt === null || typeof session.lastConnectedAt === "string" && !Number.isNaN(Date.parse(session.lastConnectedAt)))));
|
|
19722
|
-
}
|
|
19723
|
-
function isVersionTwoAuthData(value) {
|
|
19724
|
-
const data = value;
|
|
19725
|
-
return Boolean(data && data.version === 2 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
|
|
19726
|
-
}
|
|
19727
|
-
function isLegacyAuthData(value) {
|
|
19728
|
-
const data = value;
|
|
19729
|
-
return Boolean(data && data.version === 1 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
|
|
19730
|
-
}
|
|
19731
20826
|
function constantTimeEqual(left, right) {
|
|
19732
20827
|
const leftBytes = Buffer.from(left);
|
|
19733
20828
|
const rightBytes = Buffer.from(right);
|
|
@@ -19745,17 +20840,15 @@ async function exists(filePath) {
|
|
|
19745
20840
|
function corrupt(name) {
|
|
19746
20841
|
return new Error(`OpenTig authentication ${name} is corrupt.`);
|
|
19747
20842
|
}
|
|
19748
|
-
function sessionExpiry(kind, createdAt) {
|
|
19749
|
-
return kind === "desktop" ? null : new Date(Date.parse(createdAt) + BROWSER_SESSION_MAX_AGE_SECONDS * 1e3).toISOString();
|
|
19750
|
-
}
|
|
19751
20843
|
const DEFAULT_PAIRING_TTL_MS = 5 * 60 * 1e3;
|
|
19752
20844
|
class OpenTigSessionAuth {
|
|
19753
|
-
constructor(source, store, secureCookies, now, profile) {
|
|
20845
|
+
constructor(source, store, secureCookies, now, profile, browserAccessEnabled) {
|
|
19754
20846
|
this.source = source;
|
|
19755
20847
|
this.store = store;
|
|
19756
20848
|
this.secureCookies = secureCookies;
|
|
19757
20849
|
this.now = now;
|
|
19758
20850
|
this.profile = profile;
|
|
20851
|
+
this.browserAccessEnabled = browserAccessEnabled;
|
|
19759
20852
|
}
|
|
19760
20853
|
source;
|
|
19761
20854
|
store;
|
|
@@ -19763,15 +20856,30 @@ class OpenTigSessionAuth {
|
|
|
19763
20856
|
now;
|
|
19764
20857
|
profile;
|
|
19765
20858
|
pairing = null;
|
|
20859
|
+
browserAccessEnabled;
|
|
20860
|
+
browserGeneration = 0;
|
|
20861
|
+
browserQueue = Promise.resolve();
|
|
19766
20862
|
static async open(options) {
|
|
19767
20863
|
const store = await PersistentAuthStore.open(options.dataDirectory, options.now);
|
|
19768
|
-
return new OpenTigSessionAuth(options.source, store, options.secureCookies ?? false, options.now ?? Date.now, options.profile ?? "production");
|
|
20864
|
+
return new OpenTigSessionAuth(options.source, store, options.secureCookies ?? false, options.now ?? Date.now, options.profile ?? "production", options.browserAccessEnabled ?? true);
|
|
19769
20865
|
}
|
|
19770
20866
|
descriptor() {
|
|
19771
20867
|
this.dropExpiredPairing();
|
|
19772
|
-
return { authenticationRequired: true, pairingAvailable: this.pairing !== null };
|
|
20868
|
+
return { authenticationRequired: true, pairingAvailable: this.browserAccessEnabled && this.pairing !== null, browserAccessEnabled: this.browserAccessEnabled };
|
|
20869
|
+
}
|
|
20870
|
+
setBrowserAccessEnabled(enabled) {
|
|
20871
|
+
if (!enabled) {
|
|
20872
|
+
this.browserAccessEnabled = false;
|
|
20873
|
+
this.browserGeneration++;
|
|
20874
|
+
this.pairing = null;
|
|
20875
|
+
}
|
|
20876
|
+
const generation = this.browserGeneration;
|
|
20877
|
+
return this.queueBrowserMutation(async () => {
|
|
20878
|
+
this.browserAccessEnabled = enabled && generation === this.browserGeneration;
|
|
20879
|
+
});
|
|
19773
20880
|
}
|
|
19774
20881
|
createPairingToken(ttlMs = DEFAULT_PAIRING_TTL_MS) {
|
|
20882
|
+
if (!this.browserAccessEnabled) throw new Error("Web access is disabled.");
|
|
19775
20883
|
if (!Number.isSafeInteger(ttlMs) || ttlMs < 1e3 || ttlMs > 15 * 60 * 1e3) throw new Error("Invalid pairing token lifetime.");
|
|
19776
20884
|
const token = randomBytes(32).toString("base64url");
|
|
19777
20885
|
const expiresAt = this.now() + ttlMs;
|
|
@@ -19793,30 +20901,43 @@ class OpenTigSessionAuth {
|
|
|
19793
20901
|
async exchangePairingToken(token, metadata, secure = false) {
|
|
19794
20902
|
this.dropExpiredPairing();
|
|
19795
20903
|
const pairing = this.pairing;
|
|
19796
|
-
if (!pairing || !isCredential(token) || !this.store.matchesDigest(token, pairing.digest)) return null;
|
|
20904
|
+
if (!this.browserAccessEnabled || !pairing || !isCredential(token) || !this.store.matchesDigest(token, pairing.digest)) return null;
|
|
19797
20905
|
this.pairing = null;
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
19806
|
-
|
|
19807
|
-
|
|
19808
|
-
|
|
19809
|
-
|
|
19810
|
-
|
|
19811
|
-
|
|
20906
|
+
const generation = this.browserGeneration;
|
|
20907
|
+
return this.queueBrowserMutation(async () => {
|
|
20908
|
+
if (!this.browserAccessEnabled || generation !== this.browserGeneration) return null;
|
|
20909
|
+
try {
|
|
20910
|
+
const cookie = await this.issueCookie({
|
|
20911
|
+
kind: "browser",
|
|
20912
|
+
clientName: metadata?.clientName ?? "Browser",
|
|
20913
|
+
deviceType: metadata?.deviceType ?? "unknown",
|
|
20914
|
+
os: metadata?.os ?? null,
|
|
20915
|
+
browser: metadata?.browser ?? null,
|
|
20916
|
+
remoteAddress: metadata?.remoteAddress ?? null,
|
|
20917
|
+
viaProxy: metadata?.viaProxy ?? false
|
|
20918
|
+
}, secure);
|
|
20919
|
+
if (!this.browserAccessEnabled || generation !== this.browserGeneration) {
|
|
20920
|
+
await this.revoke({ cookie });
|
|
20921
|
+
return null;
|
|
20922
|
+
}
|
|
20923
|
+
return cookie;
|
|
20924
|
+
} catch (error) {
|
|
20925
|
+
if (this.browserAccessEnabled && generation === this.browserGeneration && this.now() < pairing.expiresAt && !this.pairing) this.pairing = pairing;
|
|
20926
|
+
throw error;
|
|
20927
|
+
}
|
|
20928
|
+
});
|
|
19812
20929
|
}
|
|
19813
20930
|
authenticate(headers) {
|
|
19814
20931
|
const token = readCookie(headers.cookie, sessionCookieName(this.profile));
|
|
19815
|
-
|
|
20932
|
+
const sessionId = token ? this.store.authenticate(token) : null;
|
|
20933
|
+
return sessionId && this.hasSession(sessionId) ? sessionId : null;
|
|
19816
20934
|
}
|
|
19817
20935
|
async renewBrowserCookie(headers, secure = false) {
|
|
20936
|
+
if (!this.browserAccessEnabled) return null;
|
|
20937
|
+
const generation = this.browserGeneration;
|
|
19818
20938
|
const token = readCookie(headers.cookie, sessionCookieName(this.profile));
|
|
19819
20939
|
if (!token || !await this.store.renewBrowserSession(token)) return null;
|
|
20940
|
+
if (!this.browserAccessEnabled || generation !== this.browserGeneration) return null;
|
|
19820
20941
|
return this.sessionCookie(token, true, secure);
|
|
19821
20942
|
}
|
|
19822
20943
|
async revoke(headers) {
|
|
@@ -19861,11 +20982,18 @@ class OpenTigSessionAuth {
|
|
|
19861
20982
|
};
|
|
19862
20983
|
}
|
|
19863
20984
|
hasSession(sessionId) {
|
|
19864
|
-
return this.store.hasSession(sessionId);
|
|
20985
|
+
return this.store.hasSession(sessionId) && (this.browserAccessEnabled || this.sessions().some((session) => session.id === sessionId && session.kind === "desktop"));
|
|
19865
20986
|
}
|
|
19866
20987
|
close() {
|
|
20988
|
+
this.browserAccessEnabled = false;
|
|
20989
|
+
this.browserGeneration++;
|
|
19867
20990
|
this.pairing = null;
|
|
19868
|
-
return this.store.close();
|
|
20991
|
+
return this.browserQueue.then(() => this.store.close());
|
|
20992
|
+
}
|
|
20993
|
+
queueBrowserMutation(operation) {
|
|
20994
|
+
const result = this.browserQueue.then(operation);
|
|
20995
|
+
this.browserQueue = result.then(() => void 0, () => void 0);
|
|
20996
|
+
return result;
|
|
19869
20997
|
}
|
|
19870
20998
|
expiredCookie(secure = false) {
|
|
19871
20999
|
return `${sessionCookieName(this.profile)}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0${this.secureCookies || secure ? "; Secure" : ""}`;
|
|
@@ -20153,6 +21281,7 @@ async function handleRequest(context, request, response) {
|
|
|
20153
21281
|
return sendJson(response, 204, null, { "Set-Cookie": cookie });
|
|
20154
21282
|
}
|
|
20155
21283
|
if (rawPath === "/api/auth/pair" || rawPath === "/api/auth/desktop") {
|
|
21284
|
+
if (rawPath === "/api/auth/pair" && !context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
|
|
20156
21285
|
const body = await readJsonObject(request, response);
|
|
20157
21286
|
if (!body) return;
|
|
20158
21287
|
const secure = requestOriginIsSecure(request);
|
|
@@ -20164,6 +21293,7 @@ async function handleRequest(context, request, response) {
|
|
|
20164
21293
|
} else {
|
|
20165
21294
|
cookie = await context.auth.exchangeDesktopSecret(body.secret);
|
|
20166
21295
|
}
|
|
21296
|
+
if (!cookie && rawPath === "/api/auth/pair" && !context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
|
|
20167
21297
|
if (!cookie) return sendJson(response, 401, { error: "Authentication failed." });
|
|
20168
21298
|
return sendJson(response, 204, null, { "Set-Cookie": cookie });
|
|
20169
21299
|
}
|
|
@@ -20324,6 +21454,7 @@ async function handleLocalAdminPair(context, request, response) {
|
|
|
20324
21454
|
}
|
|
20325
21455
|
const origin = normalizePublicOrigin(body.publicOrigin);
|
|
20326
21456
|
if (!origin) return sendJson(response, 400, { error: "Invalid public origin." });
|
|
21457
|
+
if (!context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
|
|
20327
21458
|
const pairing = admin.createPairingToken();
|
|
20328
21459
|
const url = new URL("/pair", origin);
|
|
20329
21460
|
url.hash = new URLSearchParams({ token: pairing.token }).toString();
|
|
@@ -20456,7 +21587,10 @@ class OpenTigWebSocketTransport {
|
|
|
20456
21587
|
revokeSessions(sessionIds) {
|
|
20457
21588
|
const revoked = new Set(sessionIds);
|
|
20458
21589
|
for (const [socket, state] of this.clients) {
|
|
20459
|
-
if (revoked.has(state.sessionId))
|
|
21590
|
+
if (revoked.has(state.sessionId)) {
|
|
21591
|
+
for (const controller of state.requests.values()) controller.abort();
|
|
21592
|
+
socket.close(1008, "Session revoked");
|
|
21593
|
+
}
|
|
20460
21594
|
}
|
|
20461
21595
|
}
|
|
20462
21596
|
close() {
|
|
@@ -20499,6 +21633,7 @@ class OpenTigWebSocketTransport {
|
|
|
20499
21633
|
socket.on("error", () => this.options.logger("warn", "WebSocket connection failed."));
|
|
20500
21634
|
}
|
|
20501
21635
|
async message(socket, state, data, isBinary) {
|
|
21636
|
+
if (!this.options.auth.hasSession(state.sessionId)) return socket.close(1008, "Session expired or revoked");
|
|
20502
21637
|
if (isBinary) return socket.close(1003, "Text messages only");
|
|
20503
21638
|
const bytes = rawBytes(data);
|
|
20504
21639
|
if (bytes.byteLength > FILE_WRITE_FRAME_LIMIT) return socket.close(1009, "Message too large");
|
|
@@ -20696,7 +21831,13 @@ class OpenTigServer {
|
|
|
20696
21831
|
return this.options.auth.createPairingToken();
|
|
20697
21832
|
}
|
|
20698
21833
|
getStatus() {
|
|
20699
|
-
return { connectedSessionCount: this.webSockets.connectedSessionCount };
|
|
21834
|
+
return { connectedSessionCount: this.webSockets.connectedSessionCount, browserAccessEnabled: this.options.auth.descriptor().browserAccessEnabled };
|
|
21835
|
+
}
|
|
21836
|
+
async setBrowserAccessEnabled(enabled) {
|
|
21837
|
+
if (!this.ready) throw new Error("Server is not ready.");
|
|
21838
|
+
const pending = this.options.auth.setBrowserAccessEnabled(enabled);
|
|
21839
|
+
if (!enabled) this.disconnectSessions(this.options.auth.sessions().filter((session) => session.kind !== "desktop").map((session) => session.id));
|
|
21840
|
+
await pending;
|
|
20700
21841
|
}
|
|
20701
21842
|
async revokeAllSessions() {
|
|
20702
21843
|
if (!this.ready) throw new Error("Server is not ready.");
|
|
@@ -20754,6 +21895,7 @@ async function runOpenTigServer(config2) {
|
|
|
20754
21895
|
let transport = null;
|
|
20755
21896
|
const auth = await OpenTigSessionAuth.open({
|
|
20756
21897
|
source: config2.auth,
|
|
21898
|
+
browserAccessEnabled: config2.browserAccessEnabled ?? config2.mode === "web-access",
|
|
20757
21899
|
...config2.profile ? { profile: config2.profile } : {},
|
|
20758
21900
|
dataDirectory: config2.serverDataPath ?? path.join(path.dirname(config2.settingsPath), "server"),
|
|
20759
21901
|
...config2.secureCookies === void 0 ? {} : { secureCookies: config2.secureCookies }
|
|
@@ -20807,6 +21949,7 @@ async function runOpenTigServer(config2) {
|
|
|
20807
21949
|
createPairingLink: () => pairingLink(address.origin, transport.createPairingToken()),
|
|
20808
21950
|
getStatus: () => transport.getStatus(),
|
|
20809
21951
|
revokeAllSessions: () => transport.revokeAllSessions(),
|
|
21952
|
+
setBrowserAccessEnabled: (enabled) => transport.setBrowserAccessEnabled(enabled),
|
|
20810
21953
|
close: () => transport.stop()
|
|
20811
21954
|
};
|
|
20812
21955
|
} catch (error) {
|