@moritzwilksch/diffle 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -5
- package/THIRD_PARTY_NOTICES.md +95 -0
- package/dist/client/assets/{angular-html-VfoYZbqR.js → angular-html-C3AR-WKY.js} +1 -1
- package/dist/client/assets/{angular-ts-E_ds17k3.js → angular-ts-bZzNmyh2.js} +1 -1
- package/dist/client/assets/{apl-DfLg31Yo.js → apl-DKjQ1uUu.js} +1 -1
- package/dist/client/assets/{astro-DDZGZEa0.js → astro-D6YBrldj.js} +1 -1
- package/dist/client/assets/{blade-C7j7NUSc.js → blade-QC3ty25r.js} +1 -1
- package/dist/client/assets/{c-B9ldfZRm.js → c-CEfiNUIv.js} +1 -1
- package/dist/client/assets/{chapel-D1rbgfGL.js → chapel-XvVgmIw8.js} +1 -1
- package/dist/client/assets/{cobol-CvvLWZ5H.js → cobol-Cyvb7uZ8.js} +1 -1
- package/dist/client/assets/{coffee-Cq5EY8Kf.js → coffee-DKSUNEAJ.js} +1 -1
- package/dist/client/assets/{cpp-KDnKhkos.js → cpp-DvHlatt5.js} +1 -1
- package/dist/client/assets/{crystal-qnDGw82Q.js → crystal-BfxbaHfR.js} +1 -1
- package/dist/client/assets/{css-DbYR8NW9.js → css-CvzcDzFO.js} +1 -1
- package/dist/client/assets/{edge-BytNLpnp.js → edge-rOBhrNV9.js} +1 -1
- package/dist/client/assets/{elixir-BOIZTT6O.js → elixir-D3B6PlMg.js} +1 -1
- package/dist/client/assets/{elm-BLyo2uoF.js → elm-BnfJhLi3.js} +1 -1
- package/dist/client/assets/{erb-C8RG1ggg.js → erb-CkLdBLG4.js} +1 -1
- package/dist/client/assets/{git-rebase-BsJjDHkQ.js → git-rebase-wqI2zI5G.js} +1 -1
- package/dist/client/assets/{glimmer-js-F0nfRvQ4.js → glimmer-js-CorAzX48.js} +1 -1
- package/dist/client/assets/{glimmer-ts-BaUh_0sm.js → glimmer-ts-DphgSML7.js} +1 -1
- package/dist/client/assets/{glsl-r7jKENsf.js → glsl-Mt2MgDCN.js} +1 -1
- package/dist/client/assets/{graphql-DecxGIfy.js → graphql-CR58itij.js} +1 -1
- package/dist/client/assets/{hack-D1QdSbsI.js → hack-BYLE2FB3.js} +1 -1
- package/dist/client/assets/{haml-DZ3Y8IEU.js → haml-Bo5vpKMC.js} +1 -1
- package/dist/client/assets/{handlebars-9ij-Y0dS.js → handlebars-Cd6w9Qte.js} +1 -1
- package/dist/client/assets/{html-_813EOsa.js → html-EAc4fezP.js} +1 -1
- package/dist/client/assets/{html-derivative-DMoYXYJX.js → html-derivative-BCmkEMhH.js} +1 -1
- package/dist/client/assets/{http-BHZjBd1F.js → http-dQXVRwNo.js} +1 -1
- package/dist/client/assets/{hurl-B9C7GLqi.js → hurl-CTzL1YrB.js} +1 -1
- package/dist/client/assets/index-BkCL4UDj.css +2 -0
- package/dist/client/assets/{index-DDePiSWW.js → index-DWcJ2X-_.js} +98 -98
- package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/client/assets/{java-BXcKXYSC.js → java-C6GmB9Ie.js} +1 -1
- package/dist/client/assets/{javascript-DzK8bvMk.js → javascript-C3Xp2ZJG.js} +1 -1
- package/dist/client/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/client/assets/{jinja-DkPJymJ8.js → jinja-Dm5uNF1A.js} +1 -1
- package/dist/client/assets/{jison-BRfwGndr.js → jison-DWN1PAPH.js} +1 -1
- package/dist/client/assets/{json-CVsgTDXF.js → json-CD4Jl2TN.js} +1 -1
- package/dist/client/assets/{jsx-CCEGQMKy.js → jsx-Dk_mg_Sq.js} +1 -1
- package/dist/client/assets/{julia-DB5wqhUl.js → julia-ChQLeZ-J.js} +1 -1
- package/dist/client/assets/{just-saVQlczz.js → just-Dw1MNaQ7.js} +1 -1
- package/dist/client/assets/{latex-C7pJ0jXy.js → latex-CHuqntlZ.js} +1 -1
- package/dist/client/assets/{liquid-BzVhxXiW.js → liquid-CdqvKBd5.js} +1 -1
- package/dist/client/assets/{lua-B68msXNV.js → lua-DtCcLDnQ.js} +1 -1
- package/dist/client/assets/{marko-D0vTAOKD.js → marko-COZ6SLmm.js} +1 -1
- package/dist/client/assets/{mdc-C5xXBRll.js → mdc-BTrsC_q_.js} +1 -1
- package/dist/client/assets/{nginx-BgUn926n.js → nginx-B_JY_bIl.js} +1 -1
- package/dist/client/assets/{nim-BWiAEcU7.js → nim-C7Boj9eE.js} +1 -1
- package/dist/client/assets/{org-PxncFL6r.js → org-CYdh74lA.js} +1 -1
- package/dist/client/assets/{perl-DNCL3XMU.js → perl-CRiTh3Pi.js} +1 -1
- package/dist/client/assets/{php-B4HLiXa-.js → php-DSko-lNI.js} +1 -1
- package/dist/client/assets/{pug-YBRTFp5G.js → pug-BfUPln3e.js} +1 -1
- package/dist/client/assets/{qml-Kbw3ykka.js → qml-2jRMRran.js} +1 -1
- package/dist/client/assets/{r-e7ntBSQI.js → r-sDW5nKEb.js} +1 -1
- package/dist/client/assets/{razor-C-7kbnr6.js → razor-Sa7GlYLb.js} +1 -1
- package/dist/client/assets/{regexp-BfKQ26Bd.js → regexp-AWVIUt-C.js} +1 -1
- package/dist/client/assets/{rst-Dk61acH4.js → rst-D20G5Wbg.js} +1 -1
- package/dist/client/assets/{ruby-B-Gsrm_0.js → ruby-DevA-y7a.js} +1 -1
- package/dist/client/assets/{sas-DrwY32Sv.js → sas-DRiLtfNK.js} +1 -1
- package/dist/client/assets/{scss-BQ0tGjl9.js → scss-Ca4Dfxoe.js} +1 -1
- package/dist/client/assets/{shellscript-70AyB6AW.js → shellscript-BNnaYtXN.js} +1 -1
- package/dist/client/assets/{shellsession-C0TRVWC2.js → shellsession-B0nV-fpO.js} +1 -1
- package/dist/client/assets/{soy-ljPZKxfc.js → soy-CDZEZMSk.js} +1 -1
- package/dist/client/assets/{sql-CpAh8MTM.js → sql-s_6SuPDf.js} +1 -1
- package/dist/client/assets/{stata-U-vhcCzr.js → stata-CaVdvC6P.js} +1 -1
- package/dist/client/assets/{surrealql-Cq-W9D43.js → surrealql-DZ5TdMgT.js} +1 -1
- package/dist/client/assets/{svelte-CsPr3yNN.js → svelte-BkFQboE2.js} +1 -1
- package/dist/client/assets/{templ-DieGJMZV.js → templ-QUoUDGUH.js} +1 -1
- package/dist/client/assets/{tex-D0ERfPEs.js → tex-89lMVAG3.js} +1 -1
- package/dist/client/assets/{ts-tags-Bw9LC0mV.js → ts-tags-B2u4oUPE.js} +1 -1
- package/dist/client/assets/{tsx-DIXVqg6z.js → tsx-ftFKhLnx.js} +1 -1
- package/dist/client/assets/{twig-DbTBKLwc.js → twig-DQCCfpmq.js} +1 -1
- package/dist/client/assets/{typescript-Bd6Plo3n.js → typescript-DhCWy8LC.js} +1 -1
- package/dist/client/assets/{typst-DqDsanSi.js → typst-N-Gv0lEU.js} +1 -1
- package/dist/client/assets/{vue-Da_CXJw9.js → vue-C7rUB8rE.js} +1 -1
- package/dist/client/assets/{vue-html-n6FmJEJ_.js → vue-html-D-dp6lDI.js} +1 -1
- package/dist/client/assets/{vue-vine-BOc7r2gI.js → vue-vine-DEpfajiz.js} +1 -1
- package/dist/client/assets/{xml-DzU5oF9g.js → xml-BGDSWH1X.js} +1 -1
- package/dist/client/assets/{xsl-BTxmEaay.js → xsl-D8Xmp-zV.js} +1 -1
- package/dist/client/assets/{yaml-DYMpEUsY.js → yaml-B3GKVR80.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/main.js +2003 -540
- package/package.json +21 -3
- package/dist/client/assets/index-CDwn6vHT.css +0 -2
package/dist/server/main.js
CHANGED
|
@@ -3951,7 +3951,7 @@ function generateCompletion(program, { shell, executable = program.name() }) {
|
|
|
3951
3951
|
}
|
|
3952
3952
|
//#endregion
|
|
3953
3953
|
//#region package.json
|
|
3954
|
-
var version$1 = "0.1.
|
|
3954
|
+
var version$1 = "0.1.8";
|
|
3955
3955
|
//#endregion
|
|
3956
3956
|
//#region src/server/comments/anchor.ts
|
|
3957
3957
|
function splitLines(contents) {
|
|
@@ -4369,7 +4369,7 @@ var GitRepo = class GitRepo {
|
|
|
4369
4369
|
}
|
|
4370
4370
|
/**
|
|
4371
4371
|
* Changed files between two revs, or a rev and the worktree (incl. untracked).
|
|
4372
|
-
* One diff call yields status, line counts and, against a commit,
|
|
4372
|
+
* One diff call yields status, line counts and, against a commit, each side's
|
|
4373
4373
|
* blob; the worktree's blobs are hashed from disk.
|
|
4374
4374
|
*/
|
|
4375
4375
|
async numstat(oldRev, newRev) {
|
|
@@ -4395,6 +4395,7 @@ var GitRepo = class GitRepo {
|
|
|
4395
4395
|
deletions: 0,
|
|
4396
4396
|
binary: false,
|
|
4397
4397
|
blob: "",
|
|
4398
|
+
oldBlob: "",
|
|
4398
4399
|
generated: false
|
|
4399
4400
|
};
|
|
4400
4401
|
const st = await lstat(resolve(this.root, path)).catch(() => null);
|
|
@@ -4411,44 +4412,68 @@ var GitRepo = class GitRepo {
|
|
|
4411
4412
|
return file;
|
|
4412
4413
|
});
|
|
4413
4414
|
const all = [...files, ...extra].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
4414
|
-
if (newRev === "worktree")
|
|
4415
|
+
if (newRev === "worktree") {
|
|
4416
|
+
const present = all.filter((f) => f.status !== "D");
|
|
4417
|
+
(await this.hashWorktree(present)).forEach((sha, i) => present[i].blob = sha);
|
|
4418
|
+
}
|
|
4419
|
+
if (reverse) {
|
|
4420
|
+
const present = all.filter((f) => f.status !== "A");
|
|
4421
|
+
const entries = present.map((f) => ({
|
|
4422
|
+
path: f.oldPath ?? f.path,
|
|
4423
|
+
submodule: f.submodule
|
|
4424
|
+
}));
|
|
4425
|
+
(await this.hashWorktree(entries)).forEach((sha, i) => present[i].oldBlob = sha);
|
|
4426
|
+
}
|
|
4415
4427
|
return all;
|
|
4416
4428
|
}
|
|
4417
4429
|
/**
|
|
4418
|
-
*
|
|
4430
|
+
* What git would store for each worktree entry, in order: the file's
|
|
4419
4431
|
* blob, a symlink's target blob, a submodule's checked-out commit. Regular
|
|
4420
4432
|
* files go to one `hash-object --stdin-paths` call; everything else is hashed
|
|
4421
4433
|
* on its own, so a gitlink, a broken link or an unreadable path never fails
|
|
4422
4434
|
* the batch. A path containing a newline cannot ride the stdin list either.
|
|
4423
|
-
* A path that cannot be hashed
|
|
4435
|
+
* A path that cannot be hashed gets an empty blob.
|
|
4424
4436
|
*/
|
|
4425
4437
|
async hashWorktree(files) {
|
|
4438
|
+
const shas = files.map(() => "");
|
|
4426
4439
|
const regular = [];
|
|
4427
|
-
await mapLimit(files, READ_CONCURRENCY$1, async (
|
|
4428
|
-
|
|
4429
|
-
if (
|
|
4430
|
-
|
|
4440
|
+
await mapLimit([...files.keys()], READ_CONCURRENCY$1, async (i) => {
|
|
4441
|
+
const { path, submodule } = files[i];
|
|
4442
|
+
if (submodule) {
|
|
4443
|
+
shas[i] = await this.submoduleHead(path);
|
|
4431
4444
|
return;
|
|
4432
4445
|
}
|
|
4433
|
-
const st = await lstat(resolve(this.root,
|
|
4434
|
-
if (st?.isSymbolicLink())
|
|
4435
|
-
else if (st?.isFile()) regular.push(
|
|
4446
|
+
const st = await lstat(resolve(this.root, path)).catch(() => null);
|
|
4447
|
+
if (st?.isSymbolicLink()) shas[i] = blobSha(Buffer.from(await readlink(resolve(this.root, path)), "utf8"));
|
|
4448
|
+
else if (st?.isFile()) regular.push(i);
|
|
4436
4449
|
});
|
|
4437
|
-
const batch = regular.filter((
|
|
4438
|
-
const single = regular.filter((
|
|
4450
|
+
const batch = regular.filter((i) => !files[i].path.includes("\n"));
|
|
4451
|
+
const single = regular.filter((i) => files[i].path.includes("\n"));
|
|
4439
4452
|
if (batch.length) try {
|
|
4440
|
-
const
|
|
4441
|
-
batch.forEach((
|
|
4453
|
+
const lines = (await this.text(["hash-object", "--stdin-paths"], { input: batch.map((i) => files[i].path).join("\n") + "\n" })).trim().split("\n");
|
|
4454
|
+
batch.forEach((i, j) => shas[i] = lines[j] ?? "");
|
|
4442
4455
|
} catch {
|
|
4443
4456
|
single.push(...batch);
|
|
4444
4457
|
}
|
|
4445
|
-
await mapLimit(single, READ_CONCURRENCY$1, async (
|
|
4446
|
-
|
|
4458
|
+
await mapLimit(single, READ_CONCURRENCY$1, async (i) => {
|
|
4459
|
+
shas[i] = await this.text([
|
|
4447
4460
|
"hash-object",
|
|
4448
4461
|
"--",
|
|
4449
|
-
|
|
4462
|
+
files[i].path
|
|
4450
4463
|
]).then((s) => s.trim()).catch(() => "");
|
|
4451
4464
|
});
|
|
4465
|
+
return shas;
|
|
4466
|
+
}
|
|
4467
|
+
/**
|
|
4468
|
+
* What git would store for `content` at worktree `path`, its clean filters applied: equals the
|
|
4469
|
+
* snapshot's blob for a worktree file whose bytes are still the snapshot's.
|
|
4470
|
+
*/
|
|
4471
|
+
async hashObject(content, path) {
|
|
4472
|
+
return (await this.text([
|
|
4473
|
+
"hash-object",
|
|
4474
|
+
"--stdin",
|
|
4475
|
+
`--path=${path}`
|
|
4476
|
+
], { input: content })).trim();
|
|
4452
4477
|
}
|
|
4453
4478
|
/** The commit a checked-out submodule is at, or '' if it is not a repository. */
|
|
4454
4479
|
async submoduleHead(path) {
|
|
@@ -5048,8 +5073,8 @@ function splitZ(buf) {
|
|
|
5048
5073
|
* `-z -M --raw --no-abbrev --numstat`: the raw records come first, then the numstat records,
|
|
5049
5074
|
* each path-terminated by NUL. Raw: ":mode mode sha sha X\0path\0", renames and
|
|
5050
5075
|
* copies "R100\0old\0new\0". Numstat: "add\tdel\tpath\0", renames "add\tdel\t\0old\0new\0".
|
|
5051
|
-
* Binary files count as "-\t-". The dst
|
|
5052
|
-
* commit; against the worktree
|
|
5076
|
+
* Binary files count as "-\t-". The src and dst shas are each side's blob
|
|
5077
|
+
* against a commit; against the worktree, or where a side is absent, they are zeros.
|
|
5053
5078
|
* Mode 160000 on either side marks a gitlink, which no file operation may touch.
|
|
5054
5079
|
*/
|
|
5055
5080
|
function parseRawNumstat(buf) {
|
|
@@ -5057,7 +5082,7 @@ function parseRawNumstat(buf) {
|
|
|
5057
5082
|
const byPath = /* @__PURE__ */ new Map();
|
|
5058
5083
|
let i = 0;
|
|
5059
5084
|
for (; i < parts.length && parts[i].startsWith(":"); i++) {
|
|
5060
|
-
const [srcMode = "", dstMode = "", , dstSha = "", code = ""] = parts[i].split(" ");
|
|
5085
|
+
const [srcMode = "", dstMode = "", srcSha = "", dstSha = "", code = ""] = parts[i].split(" ");
|
|
5061
5086
|
const status = code[0];
|
|
5062
5087
|
const oldPath = status === "R" || status === "C" ? parts[++i] : void 0;
|
|
5063
5088
|
const path = parts[++i];
|
|
@@ -5068,6 +5093,7 @@ function parseRawNumstat(buf) {
|
|
|
5068
5093
|
deletions: 0,
|
|
5069
5094
|
binary: false,
|
|
5070
5095
|
blob: /^0+$/.test(dstSha) ? "" : dstSha,
|
|
5096
|
+
oldBlob: /^0+$/.test(srcSha) ? "" : srcSha,
|
|
5071
5097
|
generated: false
|
|
5072
5098
|
};
|
|
5073
5099
|
if (oldPath != null) file.oldPath = oldPath;
|
|
@@ -5112,460 +5138,1340 @@ function diffRange(oldRev, newRev) {
|
|
|
5112
5138
|
return oldRev === "worktree" ? ["-R", newRev] : newRev === "worktree" ? [oldRev] : [oldRev, newRev];
|
|
5113
5139
|
}
|
|
5114
5140
|
//#endregion
|
|
5115
|
-
//#region
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5141
|
+
//#region node_modules/universal-user-agent/index.js
|
|
5142
|
+
function getUserAgent() {
|
|
5143
|
+
if (typeof navigator === "object" && "userAgent" in navigator) return navigator.userAgent;
|
|
5144
|
+
if (typeof process === "object" && process.version !== void 0) return `Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`;
|
|
5145
|
+
return "<environment undetectable>";
|
|
5146
|
+
}
|
|
5147
|
+
//#endregion
|
|
5148
|
+
//#region node_modules/before-after-hook/lib/register.js
|
|
5149
|
+
function register(state, name, method, options) {
|
|
5150
|
+
if (typeof method !== "function") throw new Error("method for before hook must be a function");
|
|
5151
|
+
if (!options) options = {};
|
|
5152
|
+
if (Array.isArray(name)) return name.reverse().reduce((callback, name) => {
|
|
5153
|
+
return register.bind(null, state, name, callback, options);
|
|
5154
|
+
}, method)();
|
|
5155
|
+
return Promise.resolve().then(() => {
|
|
5156
|
+
if (!state.registry[name]) return method(options);
|
|
5157
|
+
return state.registry[name].reduce((method, registered) => {
|
|
5158
|
+
return registered.hook.bind(null, method, options);
|
|
5159
|
+
}, method)();
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
//#endregion
|
|
5163
|
+
//#region node_modules/before-after-hook/lib/add.js
|
|
5164
|
+
function addHook(state, kind, name, hook) {
|
|
5165
|
+
const orig = hook;
|
|
5166
|
+
if (!state.registry[name]) state.registry[name] = [];
|
|
5167
|
+
if (kind === "before") hook = (method, options) => {
|
|
5168
|
+
return Promise.resolve().then(orig.bind(null, options)).then(method.bind(null, options));
|
|
5169
|
+
};
|
|
5170
|
+
if (kind === "after") hook = (method, options) => {
|
|
5171
|
+
let result;
|
|
5172
|
+
return Promise.resolve().then(method.bind(null, options)).then((result_) => {
|
|
5173
|
+
result = result_;
|
|
5174
|
+
return orig(result, options);
|
|
5175
|
+
}).then(() => {
|
|
5176
|
+
return result;
|
|
5177
|
+
});
|
|
5178
|
+
};
|
|
5179
|
+
if (kind === "error") hook = (method, options) => {
|
|
5180
|
+
return Promise.resolve().then(method.bind(null, options)).catch((error) => {
|
|
5181
|
+
return orig(error, options);
|
|
5182
|
+
});
|
|
5183
|
+
};
|
|
5184
|
+
state.registry[name].push({
|
|
5185
|
+
hook,
|
|
5186
|
+
orig
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
5189
|
+
//#endregion
|
|
5190
|
+
//#region node_modules/before-after-hook/lib/remove.js
|
|
5191
|
+
function removeHook(state, name, method) {
|
|
5192
|
+
if (!state.registry[name]) return;
|
|
5193
|
+
const index = state.registry[name].map((registered) => {
|
|
5194
|
+
return registered.orig;
|
|
5195
|
+
}).indexOf(method);
|
|
5196
|
+
if (index === -1) return;
|
|
5197
|
+
state.registry[name].splice(index, 1);
|
|
5198
|
+
}
|
|
5199
|
+
//#endregion
|
|
5200
|
+
//#region node_modules/before-after-hook/index.js
|
|
5201
|
+
const bind = Function.bind;
|
|
5202
|
+
const bindable = bind.bind(bind);
|
|
5203
|
+
function bindApi(hook, state, name) {
|
|
5204
|
+
const removeHookRef = bindable(removeHook, null).apply(null, name ? [state, name] : [state]);
|
|
5205
|
+
hook.api = { remove: removeHookRef };
|
|
5206
|
+
hook.remove = removeHookRef;
|
|
5207
|
+
[
|
|
5208
|
+
"before",
|
|
5209
|
+
"error",
|
|
5210
|
+
"after",
|
|
5211
|
+
"wrap"
|
|
5212
|
+
].forEach((kind) => {
|
|
5213
|
+
const args = name ? [
|
|
5214
|
+
state,
|
|
5215
|
+
kind,
|
|
5216
|
+
name
|
|
5217
|
+
] : [state, kind];
|
|
5218
|
+
hook[kind] = hook.api[kind] = bindable(addHook, null).apply(null, args);
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
function Singular() {
|
|
5222
|
+
const singularHookName = Symbol("Singular");
|
|
5223
|
+
const singularHookState = { registry: {} };
|
|
5224
|
+
const singularHook = register.bind(null, singularHookState, singularHookName);
|
|
5225
|
+
bindApi(singularHook, singularHookState, singularHookName);
|
|
5226
|
+
return singularHook;
|
|
5227
|
+
}
|
|
5228
|
+
function Collection() {
|
|
5229
|
+
const state = { registry: {} };
|
|
5230
|
+
const hook = register.bind(null, state);
|
|
5231
|
+
bindApi(hook, state);
|
|
5232
|
+
return hook;
|
|
5233
|
+
}
|
|
5234
|
+
var before_after_hook_default = {
|
|
5235
|
+
Singular,
|
|
5236
|
+
Collection
|
|
5123
5237
|
};
|
|
5124
|
-
|
|
5125
|
-
|
|
5238
|
+
//#endregion
|
|
5239
|
+
//#region node_modules/@octokit/endpoint/dist-bundle/index.js
|
|
5240
|
+
var DEFAULTS = {
|
|
5241
|
+
method: "GET",
|
|
5242
|
+
baseUrl: "https://api.github.com",
|
|
5243
|
+
headers: {
|
|
5244
|
+
accept: "application/vnd.github.v3+json",
|
|
5245
|
+
"user-agent": `octokit-endpoint.js/0.0.0-development ${getUserAgent()}`
|
|
5246
|
+
},
|
|
5247
|
+
mediaType: { format: "" }
|
|
5248
|
+
};
|
|
5249
|
+
function lowercaseKeys(object) {
|
|
5250
|
+
if (!object) return {};
|
|
5251
|
+
return Object.keys(object).reduce((newObj, key) => {
|
|
5252
|
+
newObj[key.toLowerCase()] = object[key];
|
|
5253
|
+
return newObj;
|
|
5254
|
+
}, {});
|
|
5255
|
+
}
|
|
5256
|
+
function isPlainObject$2(value) {
|
|
5257
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5258
|
+
if (Object.prototype.toString.call(value) !== "[object Object]") return false;
|
|
5259
|
+
const proto = Object.getPrototypeOf(value);
|
|
5260
|
+
if (proto === null) return true;
|
|
5261
|
+
const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
|
|
5262
|
+
return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value);
|
|
5263
|
+
}
|
|
5264
|
+
function mergeDeep(defaults, options) {
|
|
5265
|
+
const result = Object.assign({}, defaults);
|
|
5266
|
+
Object.keys(options).forEach((key) => {
|
|
5267
|
+
if (isPlainObject$2(options[key])) {
|
|
5268
|
+
if (!(key in defaults)) Object.assign(result, { [key]: options[key] });
|
|
5269
|
+
else result[key] = mergeDeep(defaults[key], options[key]);
|
|
5270
|
+
} else Object.assign(result, { [key]: options[key] });
|
|
5271
|
+
});
|
|
5272
|
+
return result;
|
|
5126
5273
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5274
|
+
function removeUndefinedProperties(obj) {
|
|
5275
|
+
for (const key in obj) if (obj[key] === void 0) delete obj[key];
|
|
5276
|
+
return obj;
|
|
5277
|
+
}
|
|
5278
|
+
function merge$1(defaults, route, options) {
|
|
5279
|
+
if (typeof route === "string") {
|
|
5280
|
+
let [method, url] = route.split(" ");
|
|
5281
|
+
options = Object.assign(url ? {
|
|
5282
|
+
method,
|
|
5283
|
+
url
|
|
5284
|
+
} : { url: method }, options);
|
|
5285
|
+
} else options = Object.assign({}, route);
|
|
5286
|
+
options.headers = lowercaseKeys(options.headers);
|
|
5287
|
+
removeUndefinedProperties(options);
|
|
5288
|
+
removeUndefinedProperties(options.headers);
|
|
5289
|
+
const mergedOptions = mergeDeep(defaults || {}, options);
|
|
5290
|
+
if (options.url === "/graphql") {
|
|
5291
|
+
if (defaults && defaults.mediaType.previews?.length) mergedOptions.mediaType.previews = defaults.mediaType.previews.filter((preview) => !mergedOptions.mediaType.previews.includes(preview)).concat(mergedOptions.mediaType.previews);
|
|
5292
|
+
mergedOptions.mediaType.previews = (mergedOptions.mediaType.previews || []).map((preview) => preview.replace(/-preview/, ""));
|
|
5293
|
+
}
|
|
5294
|
+
return mergedOptions;
|
|
5295
|
+
}
|
|
5296
|
+
function addQueryParameters(url, parameters) {
|
|
5297
|
+
const separator = /\?/.test(url) ? "&" : "?";
|
|
5298
|
+
const names = Object.keys(parameters);
|
|
5299
|
+
if (names.length === 0) return url;
|
|
5300
|
+
return url + separator + names.map((name) => {
|
|
5301
|
+
if (name === "q") return "q=" + parameters.q.split("+").map(encodeURIComponent).join("+");
|
|
5302
|
+
return `${name}=${encodeURIComponent(parameters[name])}`;
|
|
5303
|
+
}).join("&");
|
|
5304
|
+
}
|
|
5305
|
+
var urlVariableRegex = /\{[^{}}]+\}/g;
|
|
5306
|
+
function removeNonChars(variableName) {
|
|
5307
|
+
return variableName.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
|
|
5308
|
+
}
|
|
5309
|
+
function extractUrlVariableNames(url) {
|
|
5310
|
+
const matches = url.match(urlVariableRegex);
|
|
5311
|
+
if (!matches) return [];
|
|
5312
|
+
return matches.map(removeNonChars).reduce((a, b) => a.concat(b), []);
|
|
5313
|
+
}
|
|
5314
|
+
function omit$1(object, keysToOmit) {
|
|
5315
|
+
const result = { __proto__: null };
|
|
5316
|
+
for (const key of Object.keys(object)) if (keysToOmit.indexOf(key) === -1) result[key] = object[key];
|
|
5317
|
+
return result;
|
|
5318
|
+
}
|
|
5319
|
+
function encodeReserved(str) {
|
|
5320
|
+
return str.split(/(%[0-9A-Fa-f]{2})/g).map(function(part) {
|
|
5321
|
+
if (!/%[0-9A-Fa-f]/.test(part)) part = encodeURI(part).replace(/%5B/g, "[").replace(/%5D/g, "]");
|
|
5322
|
+
return part;
|
|
5323
|
+
}).join("");
|
|
5324
|
+
}
|
|
5325
|
+
function encodeUnreserved(str) {
|
|
5326
|
+
return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
|
|
5327
|
+
return "%" + c.charCodeAt(0).toString(16).toUpperCase();
|
|
5328
|
+
});
|
|
5329
|
+
}
|
|
5330
|
+
function encodeValue(operator, value, key) {
|
|
5331
|
+
value = operator === "+" || operator === "#" ? encodeReserved(value) : encodeUnreserved(value);
|
|
5332
|
+
if (key) return encodeUnreserved(key) + "=" + value;
|
|
5333
|
+
else return value;
|
|
5334
|
+
}
|
|
5335
|
+
function isDefined(value) {
|
|
5336
|
+
return value !== void 0 && value !== null;
|
|
5337
|
+
}
|
|
5338
|
+
function isKeyOperator(operator) {
|
|
5339
|
+
return operator === ";" || operator === "&" || operator === "?";
|
|
5340
|
+
}
|
|
5341
|
+
function getValues(context, operator, key, modifier) {
|
|
5342
|
+
var value = context[key], result = [];
|
|
5343
|
+
if (isDefined(value) && value !== "") {
|
|
5344
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
5345
|
+
value = value.toString();
|
|
5346
|
+
if (modifier && modifier !== "*") value = value.substring(0, parseInt(modifier, 10));
|
|
5347
|
+
result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : ""));
|
|
5348
|
+
} else if (modifier === "*") {
|
|
5349
|
+
if (Array.isArray(value)) value.filter(isDefined).forEach(function(value2) {
|
|
5350
|
+
result.push(encodeValue(operator, value2, isKeyOperator(operator) ? key : ""));
|
|
5351
|
+
});
|
|
5352
|
+
else Object.keys(value).forEach(function(k) {
|
|
5353
|
+
if (isDefined(value[k])) result.push(encodeValue(operator, value[k], k));
|
|
5354
|
+
});
|
|
5355
|
+
} else {
|
|
5356
|
+
const tmp = [];
|
|
5357
|
+
if (Array.isArray(value)) value.filter(isDefined).forEach(function(value2) {
|
|
5358
|
+
tmp.push(encodeValue(operator, value2));
|
|
5359
|
+
});
|
|
5360
|
+
else Object.keys(value).forEach(function(k) {
|
|
5361
|
+
if (isDefined(value[k])) {
|
|
5362
|
+
tmp.push(encodeUnreserved(k));
|
|
5363
|
+
tmp.push(encodeValue(operator, value[k].toString()));
|
|
5364
|
+
}
|
|
5146
5365
|
});
|
|
5366
|
+
if (isKeyOperator(operator)) result.push(encodeUnreserved(key) + "=" + tmp.join(","));
|
|
5367
|
+
else if (tmp.length !== 0) result.push(tmp.join(","));
|
|
5147
5368
|
}
|
|
5148
|
-
} else
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5369
|
+
} else if (operator === ";") {
|
|
5370
|
+
if (isDefined(value)) result.push(encodeUnreserved(key));
|
|
5371
|
+
} else if (value === "" && (operator === "&" || operator === "?")) result.push(encodeUnreserved(key) + "=");
|
|
5372
|
+
else if (value === "") result.push("");
|
|
5373
|
+
return result;
|
|
5374
|
+
}
|
|
5375
|
+
function parseUrl(template) {
|
|
5376
|
+
return { expand: expand.bind(null, template) };
|
|
5377
|
+
}
|
|
5378
|
+
function expand(template, context) {
|
|
5379
|
+
var operators = [
|
|
5380
|
+
"+",
|
|
5381
|
+
"#",
|
|
5382
|
+
".",
|
|
5383
|
+
"/",
|
|
5384
|
+
";",
|
|
5385
|
+
"?",
|
|
5386
|
+
"&"
|
|
5387
|
+
];
|
|
5388
|
+
template = template.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g, function(_, expression, literal) {
|
|
5389
|
+
if (expression) {
|
|
5390
|
+
let operator = "";
|
|
5391
|
+
const values = [];
|
|
5392
|
+
if (operators.indexOf(expression.charAt(0)) !== -1) {
|
|
5393
|
+
operator = expression.charAt(0);
|
|
5394
|
+
expression = expression.substr(1);
|
|
5395
|
+
}
|
|
5396
|
+
expression.split(/,/g).forEach(function(variable) {
|
|
5397
|
+
var tmp = /([^:\*]*)(?::(\d+)|(\*))?/.exec(variable);
|
|
5398
|
+
values.push(getValues(context, operator, tmp[1], tmp[2] || tmp[3]));
|
|
5156
5399
|
});
|
|
5157
|
-
|
|
5400
|
+
if (operator && operator !== "+") {
|
|
5401
|
+
var separator = ",";
|
|
5402
|
+
if (operator === "?") separator = "&";
|
|
5403
|
+
else if (operator !== "#") separator = operator;
|
|
5404
|
+
return (values.length !== 0 ? operator : "") + values.join(separator);
|
|
5405
|
+
} else return values.join(",");
|
|
5406
|
+
} else return encodeReserved(literal);
|
|
5407
|
+
});
|
|
5408
|
+
if (template === "/") return template;
|
|
5409
|
+
else return template.replace(/\/$/, "");
|
|
5410
|
+
}
|
|
5411
|
+
function parse$2(options) {
|
|
5412
|
+
let method = options.method.toUpperCase();
|
|
5413
|
+
let url = (options.url || "/").replace(/:([a-z]\w+)/g, "{$1}");
|
|
5414
|
+
let headers = Object.assign({}, options.headers);
|
|
5415
|
+
let body;
|
|
5416
|
+
let parameters = omit$1(options, [
|
|
5417
|
+
"method",
|
|
5418
|
+
"baseUrl",
|
|
5419
|
+
"url",
|
|
5420
|
+
"headers",
|
|
5421
|
+
"request",
|
|
5422
|
+
"mediaType"
|
|
5423
|
+
]);
|
|
5424
|
+
const urlVariableNames = extractUrlVariableNames(url);
|
|
5425
|
+
url = parseUrl(url).expand(parameters);
|
|
5426
|
+
if (!/^http/.test(url)) url = options.baseUrl + url;
|
|
5427
|
+
const remainingParameters = omit$1(parameters, Object.keys(options).filter((option) => urlVariableNames.includes(option)).concat("baseUrl"));
|
|
5428
|
+
if (!/application\/octet-stream/i.test(headers.accept)) {
|
|
5429
|
+
if (options.mediaType.format) headers.accept = headers.accept.split(/,/).map((format) => format.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/, `application/vnd$1$2.${options.mediaType.format}`)).join(",");
|
|
5430
|
+
if (url.endsWith("/graphql")) {
|
|
5431
|
+
if (options.mediaType.previews?.length) headers.accept = (headers.accept.match(/(?<![\w-])[\w-]+(?=-preview)/g) || []).concat(options.mediaType.previews).map((preview) => {
|
|
5432
|
+
return `application/vnd.github.${preview}-preview${options.mediaType.format ? `.${options.mediaType.format}` : "+json"}`;
|
|
5433
|
+
}).join(",");
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
if (["GET", "HEAD"].includes(method)) url = addQueryParameters(url, remainingParameters);
|
|
5437
|
+
else if ("data" in remainingParameters) body = remainingParameters.data;
|
|
5438
|
+
else if (Object.keys(remainingParameters).length) body = remainingParameters;
|
|
5439
|
+
if (!headers["content-type"] && typeof body !== "undefined") headers["content-type"] = "application/json; charset=utf-8";
|
|
5440
|
+
if (["PATCH", "PUT"].includes(method) && typeof body === "undefined") body = "";
|
|
5441
|
+
return Object.assign({
|
|
5442
|
+
method,
|
|
5443
|
+
url,
|
|
5444
|
+
headers
|
|
5445
|
+
}, typeof body !== "undefined" ? { body } : null, options.request ? { request: options.request } : null);
|
|
5446
|
+
}
|
|
5447
|
+
function endpointWithDefaults(defaults, route, options) {
|
|
5448
|
+
return parse$2(merge$1(defaults, route, options));
|
|
5449
|
+
}
|
|
5450
|
+
function withDefaults$2(oldDefaults, newDefaults) {
|
|
5451
|
+
const DEFAULTS2 = merge$1(oldDefaults, newDefaults);
|
|
5452
|
+
const endpoint2 = endpointWithDefaults.bind(null, DEFAULTS2);
|
|
5453
|
+
return Object.assign(endpoint2, {
|
|
5454
|
+
DEFAULTS: DEFAULTS2,
|
|
5455
|
+
defaults: withDefaults$2.bind(null, DEFAULTS2),
|
|
5456
|
+
merge: merge$1.bind(null, DEFAULTS2),
|
|
5457
|
+
parse: parse$2
|
|
5458
|
+
});
|
|
5459
|
+
}
|
|
5460
|
+
var endpoint = withDefaults$2(null, DEFAULTS);
|
|
5461
|
+
//#endregion
|
|
5462
|
+
//#region node_modules/content-type/dist/index.js
|
|
5463
|
+
/*!
|
|
5464
|
+
* content-type
|
|
5465
|
+
* Copyright(c) 2015 Douglas Christopher Wilson
|
|
5466
|
+
* MIT Licensed
|
|
5467
|
+
*/
|
|
5468
|
+
const SP = 32;
|
|
5469
|
+
const HTAB = 9;
|
|
5470
|
+
const SEMI = 59;
|
|
5471
|
+
const EQ = 61;
|
|
5472
|
+
const DQUOTE = 34;
|
|
5473
|
+
const BSLASH = 92;
|
|
5474
|
+
const COMMA = 44;
|
|
5475
|
+
const LOWER_CASE = 1;
|
|
5476
|
+
const OWS = 2;
|
|
5477
|
+
const SEMI_FLAG = 4;
|
|
5478
|
+
const COMMA_FLAG = 8;
|
|
5479
|
+
const TOKEN_FLAG = 16;
|
|
5480
|
+
const NON_ASCII = 65280;
|
|
5481
|
+
const CASE_FLAGS = 65281;
|
|
5482
|
+
/**
|
|
5483
|
+
* Character flags used to normalize HTTP field values while scanning.
|
|
5484
|
+
* Out-of-range reads intentionally coerce to zero in bitwise expressions.
|
|
5485
|
+
*/
|
|
5486
|
+
const CHAR_MAP = /* @__PURE__ */ new Uint8Array(256);
|
|
5487
|
+
CHAR_MAP[HTAB] |= OWS;
|
|
5488
|
+
CHAR_MAP[SP] |= OWS;
|
|
5489
|
+
CHAR_MAP[SEMI] |= SEMI_FLAG;
|
|
5490
|
+
CHAR_MAP[COMMA] |= COMMA_FLAG;
|
|
5491
|
+
for (let code = 128; code <= 255; code++) CHAR_MAP[code] |= LOWER_CASE;
|
|
5492
|
+
for (const char of "!#$%&'*+-.^_`|~") CHAR_MAP[char.charCodeAt(0)] |= TOKEN_FLAG;
|
|
5493
|
+
for (let code = 48; code <= 57; code++) CHAR_MAP[code] |= TOKEN_FLAG;
|
|
5494
|
+
for (let code = 65; code <= 90; code++) CHAR_MAP[code] |= 17;
|
|
5495
|
+
for (let code = 97; code <= 122; code++) CHAR_MAP[code] |= TOKEN_FLAG;
|
|
5496
|
+
/**
|
|
5497
|
+
* Null object perf optimization. Faster than `Object.create(null)` and `{ __proto__: null }`.
|
|
5498
|
+
*/
|
|
5499
|
+
const NullObject = /* @__PURE__ */ (() => {
|
|
5500
|
+
const C = function() {};
|
|
5501
|
+
C.prototype = Object.create(null);
|
|
5502
|
+
return C;
|
|
5503
|
+
})();
|
|
5504
|
+
/**
|
|
5505
|
+
* Parse a `Content-Type` header.
|
|
5506
|
+
*/
|
|
5507
|
+
function parse$1(header, options) {
|
|
5508
|
+
const stopFlags = SEMI_FLAG | (options?.comma === true ? COMMA_FLAG : 0);
|
|
5509
|
+
const len = header.length;
|
|
5510
|
+
let valueStart = options?.start ?? 0;
|
|
5511
|
+
while ((CHAR_MAP[header.charCodeAt(valueStart)] & OWS) !== 0) valueStart++;
|
|
5512
|
+
let index = valueStart;
|
|
5513
|
+
let typeFlags = 0;
|
|
5514
|
+
let whitespace = -1;
|
|
5515
|
+
let stop = options?.parameters === false ? COMMA_FLAG : 0;
|
|
5516
|
+
while (index < len) {
|
|
5517
|
+
const code = header.charCodeAt(index);
|
|
5518
|
+
const flags = CHAR_MAP[code];
|
|
5519
|
+
if ((flags & stopFlags) !== 0) {
|
|
5520
|
+
stop |= flags & COMMA_FLAG;
|
|
5521
|
+
break;
|
|
5522
|
+
}
|
|
5523
|
+
if ((flags & OWS) !== 0) {
|
|
5524
|
+
if (whitespace === -1) whitespace = index;
|
|
5525
|
+
} else whitespace = -1;
|
|
5526
|
+
typeFlags |= code & NON_ASCII | flags;
|
|
5527
|
+
index++;
|
|
5528
|
+
}
|
|
5529
|
+
const valueEnd = whitespace === -1 ? index : whitespace;
|
|
5530
|
+
const value = header.slice(valueStart, valueEnd);
|
|
5531
|
+
const type = (typeFlags & CASE_FLAGS) === 0 ? value : value.toLowerCase();
|
|
5532
|
+
if (index === len || stop !== 0) return {
|
|
5533
|
+
type,
|
|
5534
|
+
index,
|
|
5535
|
+
parameters: new NullObject()
|
|
5536
|
+
};
|
|
5537
|
+
return parseParameters(header, type, index, len, stopFlags);
|
|
5538
|
+
}
|
|
5539
|
+
/**
|
|
5540
|
+
* Parses the parameters of a `Content-Type` header starting at the given index.
|
|
5541
|
+
*/
|
|
5542
|
+
function parseParameters(header, type, index, len, stopFlags) {
|
|
5543
|
+
const parameters = new NullObject();
|
|
5544
|
+
parameter: while (index < len) {
|
|
5545
|
+
index++;
|
|
5546
|
+
while ((CHAR_MAP[header.charCodeAt(index)] & OWS) !== 0) index++;
|
|
5547
|
+
const keyStart = index;
|
|
5548
|
+
let keyFlags = 0;
|
|
5549
|
+
let keyWhitespace = -1;
|
|
5550
|
+
while (index < len) {
|
|
5551
|
+
const code = header.charCodeAt(index);
|
|
5552
|
+
const flags = CHAR_MAP[code];
|
|
5553
|
+
if ((flags & stopFlags) !== 0) {
|
|
5554
|
+
if ((flags & COMMA_FLAG) !== 0) break parameter;
|
|
5555
|
+
continue parameter;
|
|
5556
|
+
}
|
|
5557
|
+
if (code === EQ) {
|
|
5558
|
+
const keyEnd = keyWhitespace === -1 ? index : keyWhitespace;
|
|
5559
|
+
const value = header.slice(keyStart, keyEnd);
|
|
5560
|
+
const key = (keyFlags & CASE_FLAGS) === 0 ? value : value.toLowerCase();
|
|
5561
|
+
index++;
|
|
5562
|
+
while ((CHAR_MAP[header.charCodeAt(index)] & OWS) !== 0) index++;
|
|
5563
|
+
if (index < len && header.charCodeAt(index) === DQUOTE) {
|
|
5564
|
+
const quotedStart = ++index;
|
|
5565
|
+
let escaped = false;
|
|
5566
|
+
while (index < len) {
|
|
5567
|
+
const code = header.charCodeAt(index);
|
|
5568
|
+
if (code === DQUOTE) {
|
|
5569
|
+
if (parameters[key] === void 0) parameters[key] = escaped ? unescapeQuotedPairs(header, quotedStart, index) : header.slice(quotedStart, index);
|
|
5570
|
+
index++;
|
|
5571
|
+
let stop = 0;
|
|
5572
|
+
while (index < len) {
|
|
5573
|
+
const code = header.charCodeAt(index);
|
|
5574
|
+
const flags = CHAR_MAP[code];
|
|
5575
|
+
if ((flags & stopFlags) !== 0) {
|
|
5576
|
+
stop = flags & COMMA_FLAG;
|
|
5577
|
+
break;
|
|
5578
|
+
}
|
|
5579
|
+
index++;
|
|
5580
|
+
}
|
|
5581
|
+
if (stop !== 0) break parameter;
|
|
5582
|
+
continue parameter;
|
|
5583
|
+
}
|
|
5584
|
+
if (code === BSLASH && index + 1 < len) {
|
|
5585
|
+
escaped = true;
|
|
5586
|
+
index += 2;
|
|
5587
|
+
continue;
|
|
5588
|
+
}
|
|
5589
|
+
index++;
|
|
5590
|
+
}
|
|
5591
|
+
continue parameter;
|
|
5592
|
+
}
|
|
5593
|
+
const valueStart = index;
|
|
5594
|
+
let stop = 0;
|
|
5595
|
+
let valueWhitespace = -1;
|
|
5596
|
+
while (index < len) {
|
|
5597
|
+
const code = header.charCodeAt(index);
|
|
5598
|
+
const flags = CHAR_MAP[code];
|
|
5599
|
+
if ((flags & stopFlags) !== 0) {
|
|
5600
|
+
stop = flags & COMMA_FLAG;
|
|
5601
|
+
break;
|
|
5602
|
+
}
|
|
5603
|
+
if ((flags & OWS) !== 0) {
|
|
5604
|
+
if (valueWhitespace === -1) valueWhitespace = index;
|
|
5605
|
+
} else valueWhitespace = -1;
|
|
5606
|
+
index++;
|
|
5607
|
+
}
|
|
5608
|
+
if (parameters[key] === void 0) {
|
|
5609
|
+
const valueEnd = valueWhitespace === -1 ? index : valueWhitespace;
|
|
5610
|
+
parameters[key] = header.slice(valueStart, valueEnd);
|
|
5611
|
+
}
|
|
5612
|
+
if (stop !== 0) break parameter;
|
|
5613
|
+
continue parameter;
|
|
5614
|
+
}
|
|
5615
|
+
if ((flags & OWS) !== 0) {
|
|
5616
|
+
if (keyWhitespace === -1) keyWhitespace = index;
|
|
5617
|
+
} else keyWhitespace = -1;
|
|
5618
|
+
keyFlags |= code & NON_ASCII | flags;
|
|
5619
|
+
index++;
|
|
5158
5620
|
}
|
|
5159
|
-
comments.push(toReviewComment(t));
|
|
5160
|
-
ids.push(t.id);
|
|
5161
5621
|
}
|
|
5162
5622
|
return {
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
},
|
|
5167
|
-
comments,
|
|
5168
|
-
ids,
|
|
5169
|
-
skipped
|
|
5623
|
+
type,
|
|
5624
|
+
index,
|
|
5625
|
+
parameters
|
|
5170
5626
|
};
|
|
5171
5627
|
}
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
const side = anchor.side === "old" ? "LEFT" : "RIGHT";
|
|
5181
|
-
const c = {
|
|
5182
|
-
path: anchor.path,
|
|
5183
|
-
line: anchor.endLine,
|
|
5184
|
-
side,
|
|
5185
|
-
body
|
|
5186
|
-
};
|
|
5187
|
-
if (anchor.startLine !== anchor.endLine) {
|
|
5188
|
-
c.start_line = anchor.startLine;
|
|
5189
|
-
c.start_side = side;
|
|
5628
|
+
/**
|
|
5629
|
+
* Remove backslashes from quoted pairs in a known-terminated quoted string body.
|
|
5630
|
+
*/
|
|
5631
|
+
function unescapeQuotedPairs(str, start, end) {
|
|
5632
|
+
let result = "";
|
|
5633
|
+
for (let index = start; index < end; index++) if (str.charCodeAt(index) === BSLASH) {
|
|
5634
|
+
result += str.slice(start, index);
|
|
5635
|
+
start = ++index;
|
|
5190
5636
|
}
|
|
5191
|
-
return
|
|
5637
|
+
return result + str.slice(start, end);
|
|
5192
5638
|
}
|
|
5639
|
+
//#endregion
|
|
5640
|
+
//#region node_modules/json-with-bigint/json-with-bigint.js
|
|
5641
|
+
const intRegex = /^-?\d+$/;
|
|
5642
|
+
const noiseValue = /^-?\d+n+$/;
|
|
5643
|
+
const originalStringify = JSON.stringify;
|
|
5644
|
+
const originalParse = JSON.parse;
|
|
5645
|
+
const customFormat = /^-?\d+n$/;
|
|
5646
|
+
const bigIntsStringify = /([\[:])?"(-?\d+)n"($|\s*[,\}\]])/g;
|
|
5647
|
+
const noiseStringify = /([\[:])?("-?\d+n+)n("$|"\s*[,\}\]])/g;
|
|
5193
5648
|
/**
|
|
5194
|
-
*
|
|
5195
|
-
*
|
|
5649
|
+
* @typedef {(this: any, key: string | number | undefined, value: any) => any} Replacer
|
|
5650
|
+
* @typedef {(key: string | number | undefined, value: any, context?: { source: string }) => any} Reviver
|
|
5196
5651
|
*/
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
function
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5652
|
+
/**
|
|
5653
|
+
* Checks if a value is unstringifiable according to native JSON.stringify rules.
|
|
5654
|
+
*
|
|
5655
|
+
* @param {any} val The value to check.
|
|
5656
|
+
* @returns {boolean} True if the value is undefined, a function, or a symbol.
|
|
5657
|
+
*/
|
|
5658
|
+
const isUnstringifiable = (val) => val === void 0 || typeof val === "function" || typeof val === "symbol";
|
|
5659
|
+
/**
|
|
5660
|
+
* Checks if a value is a native JSON.rawJSON object (Node.js 22+).
|
|
5661
|
+
*
|
|
5662
|
+
* @param {any} val The value to check.
|
|
5663
|
+
* @returns {boolean} True if the value is a RawJSON instance.
|
|
5664
|
+
*/
|
|
5665
|
+
const isRawJSON = (val) => val !== null && typeof val === "object" && val.constructor && val.constructor.name === "RawJSON";
|
|
5666
|
+
/**
|
|
5667
|
+
* Iteratively converts a JS value to a JSON string.
|
|
5668
|
+
* Used as a fallback when the native JSON.stringify hits the Maximum Call Stack size.
|
|
5669
|
+
* Fully compliant with JSON formatting (space), replacers, and toJSON behaviors.
|
|
5670
|
+
*
|
|
5671
|
+
* @param {any} rootValue The value to stringify.
|
|
5672
|
+
* @param {Replacer | Array<string | number> | null} [replacer] User's custom replacer function.
|
|
5673
|
+
* @param {string | number} [spaceParam] Indentation for pretty-printing.
|
|
5674
|
+
* @returns {string | undefined} The generated JSON string.
|
|
5675
|
+
*/
|
|
5676
|
+
const stringifyIteratively = (rootValue, replacer, spaceParam) => {
|
|
5677
|
+
let space = "";
|
|
5678
|
+
if (typeof spaceParam === "number") space = " ".repeat(Math.min(10, Math.max(0, Math.floor(spaceParam))));
|
|
5679
|
+
else if (typeof spaceParam === "string") space = spaceParam.slice(0, 10);
|
|
5680
|
+
const isFunctionReplacer = typeof replacer === "function";
|
|
5681
|
+
const propertyList = Array.isArray(replacer) ? new Set(replacer.map(String)) : null;
|
|
5682
|
+
/**
|
|
5683
|
+
* Prepares a value for stringification by resolving toJSON, handling BigInts,
|
|
5684
|
+
* applying custom replacers, and unwrapping primitive objects.
|
|
5685
|
+
*
|
|
5686
|
+
* @param {object|Array} parent The parent object or array holding the value.
|
|
5687
|
+
* @param {string} key The key associated with the value.
|
|
5688
|
+
* @param {any} val The raw value to process.
|
|
5689
|
+
* @returns {any} The processed value ready for stringification.
|
|
5690
|
+
*/
|
|
5691
|
+
const prepareVal = (parent, key, val) => {
|
|
5692
|
+
if (val !== null && typeof val === "object" && typeof val.toJSON === "function") val = val.toJSON(key);
|
|
5693
|
+
if (typeof val === "string" && noiseValue.test(val)) return val + "n";
|
|
5694
|
+
if (typeof val === "bigint") {
|
|
5695
|
+
if ("rawJSON" in JSON) return JSON.rawJSON(val.toString());
|
|
5696
|
+
return val.toString() + "n";
|
|
5697
|
+
}
|
|
5698
|
+
if (isFunctionReplacer) val = replacer.call(parent, key, val);
|
|
5699
|
+
if (val !== null && typeof val === "object") {
|
|
5700
|
+
if (val instanceof Number || val instanceof String || val instanceof Boolean) val = val.valueOf();
|
|
5701
|
+
}
|
|
5702
|
+
return val;
|
|
5207
5703
|
};
|
|
5208
|
-
}
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5704
|
+
const rootProcessed = prepareVal({ "": rootValue }, "", rootValue);
|
|
5705
|
+
if (isUnstringifiable(rootProcessed)) return;
|
|
5706
|
+
const isRootPrimitive = rootProcessed === null || typeof rootProcessed !== "object";
|
|
5707
|
+
const isRootNativeRawJSON = isRawJSON(rootProcessed);
|
|
5708
|
+
if (isRootPrimitive || isRootNativeRawJSON) return originalStringify(rootProcessed);
|
|
5709
|
+
const chunks = [];
|
|
5710
|
+
let level = 0;
|
|
5711
|
+
const stack = [{
|
|
5712
|
+
parent: { "": rootProcessed },
|
|
5713
|
+
key: "",
|
|
5714
|
+
val: rootProcessed,
|
|
5715
|
+
isArray: Array.isArray(rootProcessed),
|
|
5716
|
+
keys: Array.isArray(rootProcessed) ? null : Object.keys(rootProcessed),
|
|
5717
|
+
index: 0,
|
|
5718
|
+
first: true
|
|
5719
|
+
}];
|
|
5720
|
+
const visited = new WeakSet([rootProcessed]);
|
|
5721
|
+
while (stack.length > 0) {
|
|
5722
|
+
const node = stack[stack.length - 1];
|
|
5723
|
+
if (node.index === 0) {
|
|
5724
|
+
chunks.push(node.isArray ? "[" : "{");
|
|
5725
|
+
level++;
|
|
5726
|
+
}
|
|
5727
|
+
let isDone = false;
|
|
5728
|
+
if (node.isArray) {
|
|
5729
|
+
if (node.index < node.val.length) {
|
|
5730
|
+
if (!node.first) chunks.push(",");
|
|
5731
|
+
if (space) chunks.push("\n" + space.repeat(level));
|
|
5732
|
+
const childRaw = node.val[node.index];
|
|
5733
|
+
const childVal = prepareVal(node.val, String(node.index), childRaw);
|
|
5734
|
+
if (isUnstringifiable(childVal)) {
|
|
5735
|
+
chunks.push("null");
|
|
5736
|
+
node.first = false;
|
|
5737
|
+
node.index++;
|
|
5738
|
+
} else {
|
|
5739
|
+
const isComplexObject = childVal !== null && typeof childVal === "object";
|
|
5740
|
+
const isNativeRaw = isRawJSON(childVal);
|
|
5741
|
+
if (isComplexObject && !isNativeRaw) {
|
|
5742
|
+
if (visited.has(childVal)) throw new TypeError("Converting circular structure to JSON");
|
|
5743
|
+
visited.add(childVal);
|
|
5744
|
+
stack.push({
|
|
5745
|
+
parent: node.val,
|
|
5746
|
+
key: String(node.index),
|
|
5747
|
+
val: childVal,
|
|
5748
|
+
isArray: Array.isArray(childVal),
|
|
5749
|
+
keys: Array.isArray(childVal) ? null : Object.keys(childVal),
|
|
5750
|
+
index: 0,
|
|
5751
|
+
first: true
|
|
5752
|
+
});
|
|
5753
|
+
node.first = false;
|
|
5754
|
+
node.index++;
|
|
5755
|
+
} else {
|
|
5756
|
+
chunks.push(originalStringify(childVal));
|
|
5757
|
+
node.first = false;
|
|
5758
|
+
node.index++;
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
} else isDone = true;
|
|
5762
|
+
} else {
|
|
5763
|
+
while (node.index < node.keys.length) {
|
|
5764
|
+
const k = node.keys[node.index++];
|
|
5765
|
+
if (propertyList && !propertyList.has(k)) continue;
|
|
5766
|
+
const childRaw = node.val[k];
|
|
5767
|
+
const childVal = prepareVal(node.val, k, childRaw);
|
|
5768
|
+
if (isUnstringifiable(childVal)) continue;
|
|
5769
|
+
if (!node.first) chunks.push(",");
|
|
5770
|
+
if (space) chunks.push("\n" + space.repeat(level) + originalStringify(k) + ": ");
|
|
5771
|
+
else chunks.push(originalStringify(k) + ":");
|
|
5772
|
+
const isComplexObject = childVal !== null && typeof childVal === "object";
|
|
5773
|
+
const isNativeRaw = isRawJSON(childVal);
|
|
5774
|
+
if (isComplexObject && !isNativeRaw) {
|
|
5775
|
+
if (visited.has(childVal)) throw new TypeError("Converting circular structure to JSON");
|
|
5776
|
+
visited.add(childVal);
|
|
5777
|
+
stack.push({
|
|
5778
|
+
parent: node.val,
|
|
5779
|
+
key: k,
|
|
5780
|
+
val: childVal,
|
|
5781
|
+
isArray: Array.isArray(childVal),
|
|
5782
|
+
keys: Array.isArray(childVal) ? null : Object.keys(childVal),
|
|
5783
|
+
index: 0,
|
|
5784
|
+
first: true
|
|
5785
|
+
});
|
|
5786
|
+
node.first = false;
|
|
5787
|
+
break;
|
|
5788
|
+
} else {
|
|
5789
|
+
chunks.push(originalStringify(childVal));
|
|
5790
|
+
node.first = false;
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
if (node.index >= node.keys.length && stack[stack.length - 1] === node) isDone = true;
|
|
5794
|
+
}
|
|
5795
|
+
if (isDone) {
|
|
5796
|
+
level--;
|
|
5797
|
+
if (!node.first && space) chunks.push("\n" + space.repeat(level));
|
|
5798
|
+
chunks.push(node.isArray ? "]" : "}");
|
|
5799
|
+
visited.delete(node.val);
|
|
5800
|
+
stack.pop();
|
|
5801
|
+
}
|
|
5216
5802
|
}
|
|
5803
|
+
return chunks.join("");
|
|
5217
5804
|
};
|
|
5218
5805
|
/**
|
|
5219
|
-
*
|
|
5220
|
-
* when there is none. The review is never submitted: the human opens the PR and submits
|
|
5221
|
-
* it themselves. The caller validates the comparison inside the export queue before posting.
|
|
5806
|
+
* Converts a JavaScript value to a JSON string.
|
|
5222
5807
|
*
|
|
5223
|
-
*
|
|
5224
|
-
*
|
|
5225
|
-
*
|
|
5226
|
-
*
|
|
5808
|
+
* Supports serialization of BigInt values using two strategies:
|
|
5809
|
+
* 1. Custom format "123n" → "123" (universal fallback)
|
|
5810
|
+
* 2. Native JSON.rawJSON() (Node.js 22+, fastest) when available
|
|
5811
|
+
*
|
|
5812
|
+
* All other values are serialized exactly like native JSON.stringify().
|
|
5813
|
+
*
|
|
5814
|
+
* @param {*} value The value to convert to a JSON string.
|
|
5815
|
+
* @param {Replacer | Array<string | number> | null} [replacer]
|
|
5816
|
+
* A function that alters the behavior of the stringification process,
|
|
5817
|
+
* or an array of strings/numbers to indicate properties to exclude.
|
|
5818
|
+
* @param {string | number} [space]
|
|
5819
|
+
* A string or number to specify indentation or pretty-printing.
|
|
5820
|
+
* @returns {string} The JSON string representation.
|
|
5227
5821
|
*/
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5822
|
+
const JSONStringify = (value, replacer, space) => {
|
|
5823
|
+
try {
|
|
5824
|
+
if ("rawJSON" in JSON) return originalStringify(value, (key, val) => {
|
|
5825
|
+
if (typeof val === "bigint") return JSON.rawJSON(val.toString());
|
|
5826
|
+
if (typeof replacer === "function") return replacer(key, val);
|
|
5827
|
+
if (Array.isArray(replacer) && replacer.includes(key)) return val;
|
|
5828
|
+
return val;
|
|
5829
|
+
}, space);
|
|
5830
|
+
if (!value) return originalStringify(value, replacer, space);
|
|
5831
|
+
return originalStringify(value, (key, val) => {
|
|
5832
|
+
if (typeof val === "string" && noiseValue.test(val)) return val.toString() + "n";
|
|
5833
|
+
if (typeof val === "bigint") return val.toString() + "n";
|
|
5834
|
+
if (typeof replacer === "function") return replacer(key, val);
|
|
5835
|
+
if (Array.isArray(replacer) && replacer.includes(key)) return val;
|
|
5836
|
+
return val;
|
|
5837
|
+
}, space).replace(bigIntsStringify, "$1$2$3").replace(noiseStringify, "$1$2$3");
|
|
5838
|
+
} catch (error) {
|
|
5839
|
+
if (error instanceof RangeError) {
|
|
5840
|
+
const convertedJSON = stringifyIteratively(value, replacer, space);
|
|
5841
|
+
if (convertedJSON === void 0) return void 0;
|
|
5842
|
+
if ("rawJSON" in JSON) return convertedJSON;
|
|
5843
|
+
return convertedJSON.replace(bigIntsStringify, "$1$2$3").replace(noiseStringify, "$1$2$3");
|
|
5245
5844
|
}
|
|
5246
|
-
|
|
5247
|
-
url: pr.url,
|
|
5248
|
-
posted: comments.length,
|
|
5249
|
-
updated: 0,
|
|
5250
|
-
review: "created",
|
|
5251
|
-
skipped
|
|
5252
|
-
};
|
|
5845
|
+
throw error;
|
|
5253
5846
|
}
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5847
|
+
};
|
|
5848
|
+
const featureCache = /* @__PURE__ */ new Map();
|
|
5849
|
+
/**
|
|
5850
|
+
* Detects if the current JSON.parse implementation supports the context.source feature.
|
|
5851
|
+
*
|
|
5852
|
+
* Uses toString() fingerprinting to cache results and automatically detect runtime
|
|
5853
|
+
* replacements of JSON.parse (polyfills, mocks, etc.).
|
|
5854
|
+
*
|
|
5855
|
+
* @returns {boolean} true if context.source is supported, false otherwise.
|
|
5856
|
+
*/
|
|
5857
|
+
const isContextSourceSupported = () => {
|
|
5858
|
+
const parseFingerprint = JSON.parse.toString();
|
|
5859
|
+
if (featureCache.has(parseFingerprint)) return featureCache.get(parseFingerprint);
|
|
5258
5860
|
try {
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
add.push(c);
|
|
5266
|
-
continue;
|
|
5267
|
-
}
|
|
5268
|
-
if (hit.body.trim() === c.body.trim()) {
|
|
5269
|
-
skipped.push({
|
|
5270
|
-
id,
|
|
5271
|
-
reason: "already in the review"
|
|
5272
|
-
});
|
|
5273
|
-
continue;
|
|
5274
|
-
}
|
|
5275
|
-
await graphql(run, snap.root, UPDATE_COMMENT, { input: {
|
|
5276
|
-
pullRequestReviewCommentId: hit.nodeId,
|
|
5277
|
-
body: c.body
|
|
5278
|
-
} });
|
|
5279
|
-
updated++;
|
|
5280
|
-
}
|
|
5281
|
-
for (const c of add) {
|
|
5282
|
-
await addToPendingReview(run, snap.root, pending.id, c);
|
|
5283
|
-
posted++;
|
|
5284
|
-
}
|
|
5285
|
-
} catch (e) {
|
|
5286
|
-
if (updated === 0 && posted === 0) throw e;
|
|
5287
|
-
const detail = e instanceof Error ? e.message : String(e);
|
|
5288
|
-
throw new GithubError(`updated ${updated} and added ${posted} comments in the pending review, then ${detail}`, 502);
|
|
5861
|
+
const result = JSON.parse("1", (_, __, context) => !!context?.source && context.source === "1");
|
|
5862
|
+
featureCache.set(parseFingerprint, result);
|
|
5863
|
+
return result;
|
|
5864
|
+
} catch {
|
|
5865
|
+
featureCache.set(parseFingerprint, false);
|
|
5866
|
+
return false;
|
|
5289
5867
|
}
|
|
5290
|
-
|
|
5291
|
-
url: pr.url,
|
|
5292
|
-
posted,
|
|
5293
|
-
updated,
|
|
5294
|
-
review: "existing",
|
|
5295
|
-
skipped
|
|
5296
|
-
};
|
|
5297
|
-
}
|
|
5298
|
-
function threadMarker(id) {
|
|
5299
|
-
return `<!-- diffle-thread:${encodeURIComponent(id)} -->`;
|
|
5300
|
-
}
|
|
5301
|
-
/** The anchor must still match before we rewrite an exported thread. */
|
|
5302
|
-
function anchorKey(c) {
|
|
5303
|
-
if (isFileComment(c)) return [c.path, "FILE"].join("\0");
|
|
5304
|
-
return [
|
|
5305
|
-
c.path,
|
|
5306
|
-
c.side,
|
|
5307
|
-
c.start_line ?? c.line,
|
|
5308
|
-
c.line
|
|
5309
|
-
].join("\0");
|
|
5310
|
-
}
|
|
5311
|
-
const PENDING_QUERY = `query($owner:String!,$repo:String!,$number:Int!){
|
|
5312
|
-
viewer{login}
|
|
5313
|
-
repository(owner:$owner,name:$repo){pullRequest(number:$number){reviews(last:20,states:[PENDING]){nodes{id author{login}}}}}
|
|
5314
|
-
}`;
|
|
5868
|
+
};
|
|
5315
5869
|
/**
|
|
5316
|
-
*
|
|
5317
|
-
*
|
|
5318
|
-
*
|
|
5870
|
+
* Reviver function that converts custom-format BigInt strings back to BigInt values.
|
|
5871
|
+
* Also handles "noise" strings that accidentally match the BigInt format.
|
|
5872
|
+
*
|
|
5873
|
+
* @param {string | number | undefined} key The object key.
|
|
5874
|
+
* @param {*} value The value being parsed.
|
|
5875
|
+
* @param {object} [context] Parse context (if supported by JSON.parse).
|
|
5876
|
+
* @param {Reviver} [userReviver] User's custom reviver function.
|
|
5877
|
+
* @returns {any} The transformed value.
|
|
5319
5878
|
*/
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5879
|
+
const convertMarkedBigIntsReviver = (key, value, context, userReviver) => {
|
|
5880
|
+
if (typeof value === "string" && customFormat.test(value)) return BigInt(value.slice(0, -1));
|
|
5881
|
+
if (typeof value === "string" && noiseValue.test(value)) return value.slice(0, -1);
|
|
5882
|
+
if (!(typeof userReviver === "function")) return value;
|
|
5883
|
+
return userReviver(key, value, context);
|
|
5884
|
+
};
|
|
5885
|
+
/**
|
|
5886
|
+
* Fast JSON.parse implementation (~2x faster than classic fallback).
|
|
5887
|
+
* Uses JSON.parse's context.source feature to detect integers and convert
|
|
5888
|
+
* large numbers directly to BigInt without string manipulation.
|
|
5889
|
+
*
|
|
5890
|
+
* Does not support legacy custom format from v1 of this library.
|
|
5891
|
+
*
|
|
5892
|
+
* @param {string} text JSON string to parse.
|
|
5893
|
+
* @param {Reviver} [reviver] Transform function to apply to each value.
|
|
5894
|
+
* @returns {any} Parsed JavaScript value.
|
|
5895
|
+
*/
|
|
5896
|
+
const JSONParseV2 = (text, reviver) => {
|
|
5897
|
+
return JSON.parse(text, (key, value, context) => {
|
|
5898
|
+
const isNumber = typeof value === "number";
|
|
5899
|
+
const isOutOfBounds = value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER;
|
|
5900
|
+
const isBigNumber = isNumber && isOutOfBounds;
|
|
5901
|
+
const isInt = context && intRegex.test(context.source);
|
|
5902
|
+
if (isBigNumber && isInt) return BigInt(context.source);
|
|
5903
|
+
if (!(typeof reviver === "function")) return value;
|
|
5904
|
+
return reviver(key, value, context);
|
|
5326
5905
|
});
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
const
|
|
5332
|
-
repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100,after:$after){
|
|
5333
|
-
pageInfo{hasNextPage endCursor}
|
|
5334
|
-
nodes{path line startLine diffSide subjectType comments(first:1){nodes{id body pullRequestReview{id}}}}
|
|
5335
|
-
}}}
|
|
5336
|
-
}`;
|
|
5906
|
+
};
|
|
5907
|
+
const MAX_INT = Number.MAX_SAFE_INTEGER.toString();
|
|
5908
|
+
const MAX_DIGITS = MAX_INT.length;
|
|
5909
|
+
const stringsOrLargeNumbers = /"(?:[^"\\]|\\.)*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g;
|
|
5910
|
+
const noiseValueWithQuotes = /^"-?\d+n+"$/;
|
|
5337
5911
|
/**
|
|
5338
|
-
*
|
|
5339
|
-
*
|
|
5340
|
-
*
|
|
5341
|
-
*
|
|
5912
|
+
* Iteratively traverses the parsed object bottom-up (post-order),
|
|
5913
|
+
* emulating the native JSON.parse reviver behavior.
|
|
5914
|
+
* This avoids Call Stack overflows (RangeError) on deeply nested structures.
|
|
5915
|
+
*
|
|
5916
|
+
* @param {any} parsed The natively parsed JSON object.
|
|
5917
|
+
* @param {Reviver} [userReviver] User's custom reviver function.
|
|
5918
|
+
* @returns {any} The fully processed object.
|
|
5342
5919
|
*/
|
|
5343
|
-
|
|
5344
|
-
const
|
|
5345
|
-
const
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
}
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5920
|
+
const applyReviverIteratively = (parsed, userReviver) => {
|
|
5921
|
+
const rootHolder = { "": parsed };
|
|
5922
|
+
const stack = [{
|
|
5923
|
+
parent: rootHolder,
|
|
5924
|
+
key: "",
|
|
5925
|
+
visited: false
|
|
5926
|
+
}];
|
|
5927
|
+
while (stack.length > 0) {
|
|
5928
|
+
const node = stack[stack.length - 1];
|
|
5929
|
+
if (!node.visited) {
|
|
5930
|
+
node.visited = true;
|
|
5931
|
+
const value = node.parent[node.key];
|
|
5932
|
+
if (value !== null && typeof value === "object") {
|
|
5933
|
+
const keys = Object.keys(value);
|
|
5934
|
+
for (let i = keys.length - 1; i >= 0; i--) stack.push({
|
|
5935
|
+
parent: value,
|
|
5936
|
+
key: keys[i],
|
|
5937
|
+
visited: false
|
|
5938
|
+
});
|
|
5939
|
+
}
|
|
5940
|
+
} else {
|
|
5941
|
+
const { parent, key } = node;
|
|
5942
|
+
let value = parent[key];
|
|
5943
|
+
if (typeof value === "string") {
|
|
5944
|
+
if (customFormat.test(value)) value = BigInt(value.slice(0, -1));
|
|
5945
|
+
else if (noiseValue.test(value)) value = value.slice(0, -1);
|
|
5946
|
+
}
|
|
5947
|
+
if (typeof userReviver === "function") value = userReviver.call(parent, key, value);
|
|
5948
|
+
if (value === void 0) delete parent[key];
|
|
5949
|
+
else parent[key] = value;
|
|
5950
|
+
stack.pop();
|
|
5369
5951
|
}
|
|
5370
|
-
if (!threads.pageInfo.hasNextPage) return by;
|
|
5371
|
-
if (!threads.pageInfo.endCursor || threads.pageInfo.endCursor === after) throw new GithubError("cannot read the next page of the pending review", 502);
|
|
5372
|
-
after = threads.pageInfo.endCursor;
|
|
5373
5952
|
}
|
|
5374
|
-
|
|
5375
|
-
}
|
|
5376
|
-
/** The anchor key of a thread already in the review, or null for one GitHub reports without a position. */
|
|
5377
|
-
function pendingKey(t) {
|
|
5378
|
-
const path = t.path;
|
|
5379
|
-
if (t.subjectType === "FILE") return anchorKey({
|
|
5380
|
-
path,
|
|
5381
|
-
subject_type: "file",
|
|
5382
|
-
body: ""
|
|
5383
|
-
});
|
|
5384
|
-
if (t.line == null) return null;
|
|
5385
|
-
return anchorKey({
|
|
5386
|
-
path,
|
|
5387
|
-
side: t.diffSide === "LEFT" ? "LEFT" : "RIGHT",
|
|
5388
|
-
line: t.line,
|
|
5389
|
-
start_line: t.startLine ?? void 0,
|
|
5390
|
-
body: ""
|
|
5391
|
-
});
|
|
5392
|
-
}
|
|
5393
|
-
const UPDATE_COMMENT = `mutation($input:UpdatePullRequestReviewCommentInput!){updatePullRequestReviewComment(input:$input){pullRequestReviewComment{id}}}`;
|
|
5953
|
+
return rootHolder[""];
|
|
5954
|
+
};
|
|
5394
5955
|
/**
|
|
5395
|
-
*
|
|
5396
|
-
*
|
|
5956
|
+
* Pre-processes the JSON string to mark large numbers with an 'n' suffix.
|
|
5957
|
+
*
|
|
5958
|
+
* @param {string} text The raw JSON string.
|
|
5959
|
+
* @returns {string} The serialized string with marked BigInts.
|
|
5397
5960
|
*/
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
"
|
|
5401
|
-
"
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
"
|
|
5406
|
-
], {
|
|
5407
|
-
cwd,
|
|
5408
|
-
input: JSON.stringify(review)
|
|
5961
|
+
const serializeBigInts = (text) => {
|
|
5962
|
+
return text.replace(stringsOrLargeNumbers, (match, digits, fractional, exponential) => {
|
|
5963
|
+
const isString = match[0] === "\"";
|
|
5964
|
+
if (isString && noiseValueWithQuotes.test(match)) return match.substring(0, match.length - 1) + "n\"";
|
|
5965
|
+
const hasFractionalOrExponential = fractional || exponential;
|
|
5966
|
+
const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS || digits.length === MAX_DIGITS && digits <= MAX_INT);
|
|
5967
|
+
if (isString || hasFractionalOrExponential || isLessThanMaxSafeInt) return match;
|
|
5968
|
+
return "\"" + match + "n\"";
|
|
5409
5969
|
});
|
|
5410
|
-
|
|
5970
|
+
};
|
|
5971
|
+
/**
|
|
5972
|
+
* Converts a JSON string into a JavaScript value.
|
|
5973
|
+
*
|
|
5974
|
+
* Supports parsing of large integers using two strategies:
|
|
5975
|
+
* 1. Classic fallback: Marks large numbers with "123n" format, then converts to BigInt
|
|
5976
|
+
* 2. Fast path (JSONParseV2): Uses context.source feature (~2x faster) when available
|
|
5977
|
+
*
|
|
5978
|
+
* All other JSON values are parsed exactly like native JSON.parse().
|
|
5979
|
+
*
|
|
5980
|
+
* @param {string} text A valid JSON string.
|
|
5981
|
+
* @param {Reviver} [reviver]
|
|
5982
|
+
* A function that transforms the results. This function is called for each member
|
|
5983
|
+
* of the object. If a member contains nested objects, the nested objects are
|
|
5984
|
+
* transformed before the parent object is.
|
|
5985
|
+
* @returns {any} The parsed JavaScript value.
|
|
5986
|
+
* @throws {SyntaxError} If text is not valid JSON.
|
|
5987
|
+
*/
|
|
5988
|
+
const JSONParse = (text, reviver) => {
|
|
5989
|
+
if (!text) return originalParse(text, reviver);
|
|
5411
5990
|
try {
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5991
|
+
if (isContextSourceSupported()) return JSONParseV2(text, reviver);
|
|
5992
|
+
const serializedData = serializeBigInts(text);
|
|
5993
|
+
return originalParse(serializedData, (key, value, context) => convertMarkedBigIntsReviver(key, value, context, reviver));
|
|
5994
|
+
} catch (error) {
|
|
5995
|
+
if (error instanceof RangeError) {
|
|
5996
|
+
const serializedData = serializeBigInts(text);
|
|
5997
|
+
const parsed = originalParse(serializedData);
|
|
5998
|
+
return applyReviverIteratively(parsed, reviver);
|
|
5999
|
+
}
|
|
6000
|
+
throw error;
|
|
5415
6001
|
}
|
|
5416
|
-
|
|
5417
|
-
|
|
6002
|
+
};
|
|
6003
|
+
//#endregion
|
|
6004
|
+
//#region node_modules/@octokit/request-error/dist-src/index.js
|
|
6005
|
+
var RequestError$1 = class extends Error {
|
|
6006
|
+
name;
|
|
6007
|
+
/**
|
|
6008
|
+
* http status code
|
|
6009
|
+
*/
|
|
6010
|
+
status;
|
|
6011
|
+
/**
|
|
6012
|
+
* Request options that lead to the error.
|
|
6013
|
+
*/
|
|
6014
|
+
request;
|
|
6015
|
+
/**
|
|
6016
|
+
* Response object if a response was received
|
|
6017
|
+
*/
|
|
6018
|
+
response;
|
|
6019
|
+
constructor(message, statusCode, options) {
|
|
6020
|
+
super(message, { cause: options.cause });
|
|
6021
|
+
this.name = "HttpError";
|
|
6022
|
+
this.status = Number.parseInt(statusCode);
|
|
6023
|
+
if (Number.isNaN(this.status)) this.status = 0;
|
|
6024
|
+
/* v8 ignore else -- @preserve -- Bug with vitest coverage where it sees an else branch that doesn't exist */
|
|
6025
|
+
if ("response" in options) this.response = options.response;
|
|
6026
|
+
const requestCopy = Object.assign({}, options.request);
|
|
6027
|
+
if (options.request.headers.authorization) requestCopy.headers = Object.assign({}, options.request.headers, { authorization: options.request.headers.authorization.replace(/(?<! ) .*$/, " [REDACTED]") });
|
|
6028
|
+
requestCopy.url = requestCopy.url.replace(/\bclient_secret=\w+/g, "client_secret=[REDACTED]").replace(/\baccess_token=\w+/g, "access_token=[REDACTED]");
|
|
6029
|
+
this.request = requestCopy;
|
|
6030
|
+
}
|
|
6031
|
+
};
|
|
6032
|
+
//#endregion
|
|
6033
|
+
//#region node_modules/@octokit/request/dist-bundle/index.js
|
|
6034
|
+
var defaults_default = { headers: { "user-agent": `octokit-request.js/10.0.16 ${getUserAgent()}` } };
|
|
6035
|
+
function isPlainObject$1(value) {
|
|
6036
|
+
if (typeof value !== "object" || value === null) return false;
|
|
6037
|
+
if (Object.prototype.toString.call(value) !== "[object Object]") return false;
|
|
6038
|
+
const proto = Object.getPrototypeOf(value);
|
|
6039
|
+
if (proto === null) return true;
|
|
6040
|
+
const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
|
|
6041
|
+
return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value);
|
|
6042
|
+
}
|
|
6043
|
+
var noop$1 = () => "";
|
|
6044
|
+
async function fetchWrapper(requestOptions) {
|
|
6045
|
+
const fetch = requestOptions.request?.fetch || globalThis.fetch;
|
|
6046
|
+
if (!fetch) throw new Error("fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing");
|
|
6047
|
+
const log = requestOptions.request?.log || console;
|
|
6048
|
+
const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
|
|
6049
|
+
const body = isPlainObject$1(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
|
|
6050
|
+
const requestHeaders = Object.fromEntries(Object.entries(requestOptions.headers).map(([name, value]) => [name, String(value)]));
|
|
6051
|
+
let fetchResponse;
|
|
6052
|
+
try {
|
|
6053
|
+
fetchResponse = await fetch(requestOptions.url, {
|
|
6054
|
+
method: requestOptions.method,
|
|
6055
|
+
body,
|
|
6056
|
+
redirect: requestOptions.request?.redirect,
|
|
6057
|
+
headers: requestHeaders,
|
|
6058
|
+
signal: requestOptions.request?.signal,
|
|
6059
|
+
...requestOptions.body && { duplex: "half" }
|
|
6060
|
+
});
|
|
6061
|
+
} catch (error) {
|
|
6062
|
+
let message = "Unknown Error";
|
|
6063
|
+
if (error instanceof Error) {
|
|
6064
|
+
if (error.name === "AbortError") {
|
|
6065
|
+
error.status = 500;
|
|
6066
|
+
throw error;
|
|
6067
|
+
}
|
|
6068
|
+
message = error.message;
|
|
6069
|
+
if (error.name === "TypeError" && "cause" in error) {
|
|
6070
|
+
if (error.cause instanceof Error) message = error.cause.message;
|
|
6071
|
+
else if (typeof error.cause === "string") message = error.cause;
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
const requestError = new RequestError$1(message, 500, { request: requestOptions });
|
|
6075
|
+
requestError.cause = error;
|
|
6076
|
+
throw requestError;
|
|
6077
|
+
}
|
|
6078
|
+
const status = fetchResponse.status;
|
|
6079
|
+
const url = fetchResponse.url;
|
|
6080
|
+
const responseHeaders = {};
|
|
6081
|
+
for (const [key, value] of fetchResponse.headers) responseHeaders[key] = value;
|
|
6082
|
+
const octokitResponse = {
|
|
6083
|
+
url,
|
|
6084
|
+
status,
|
|
6085
|
+
headers: responseHeaders,
|
|
6086
|
+
data: ""
|
|
6087
|
+
};
|
|
6088
|
+
if ("deprecation" in responseHeaders) {
|
|
6089
|
+
const matches = responseHeaders.link && responseHeaders.link.match(/<([^<>]+)>; rel="deprecation"/);
|
|
6090
|
+
const deprecationLink = matches && matches.pop();
|
|
6091
|
+
log.warn(`[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}`);
|
|
6092
|
+
}
|
|
6093
|
+
if (status === 204 || status === 205) return octokitResponse;
|
|
6094
|
+
if (requestOptions.method === "HEAD") {
|
|
6095
|
+
if (status < 400) return octokitResponse;
|
|
6096
|
+
throw new RequestError$1(fetchResponse.statusText, status, {
|
|
6097
|
+
response: octokitResponse,
|
|
6098
|
+
request: requestOptions
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
6101
|
+
if (status === 304) {
|
|
6102
|
+
octokitResponse.data = await getResponseData(fetchResponse);
|
|
6103
|
+
throw new RequestError$1("Not modified", status, {
|
|
6104
|
+
response: octokitResponse,
|
|
6105
|
+
request: requestOptions
|
|
6106
|
+
});
|
|
6107
|
+
}
|
|
6108
|
+
if (status >= 400) {
|
|
6109
|
+
octokitResponse.data = await getResponseData(fetchResponse);
|
|
6110
|
+
throw new RequestError$1(toErrorMessage(octokitResponse.data), status, {
|
|
6111
|
+
response: octokitResponse,
|
|
6112
|
+
request: requestOptions
|
|
6113
|
+
});
|
|
6114
|
+
}
|
|
6115
|
+
octokitResponse.data = parseSuccessResponseBody ? await getResponseData(fetchResponse) : fetchResponse.body;
|
|
6116
|
+
return octokitResponse;
|
|
5418
6117
|
}
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
const
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
6118
|
+
async function getResponseData(response) {
|
|
6119
|
+
const contentType = response.headers.get("content-type");
|
|
6120
|
+
if (!contentType) return response.text().catch(noop$1);
|
|
6121
|
+
const mimetype = parse$1(contentType);
|
|
6122
|
+
if (isJSONResponse(mimetype)) {
|
|
6123
|
+
let text = "";
|
|
6124
|
+
try {
|
|
6125
|
+
text = await response.text();
|
|
6126
|
+
return JSONParse(text);
|
|
6127
|
+
} catch (err) {
|
|
6128
|
+
return text;
|
|
6129
|
+
}
|
|
6130
|
+
} else if (mimetype.type.startsWith("text/") || mimetype.parameters.charset?.toLowerCase() === "utf-8" && mimetype.type !== "application/octet-stream") return response.text().catch(noop$1);
|
|
6131
|
+
else return response.arrayBuffer().catch(
|
|
6132
|
+
/* v8 ignore next -- @preserve */
|
|
6133
|
+
() => /* @__PURE__ */ new ArrayBuffer(0)
|
|
6134
|
+
);
|
|
6135
|
+
}
|
|
6136
|
+
function isJSONResponse(mimetype) {
|
|
6137
|
+
return mimetype.type === "application/json" || mimetype.type === "application/scim+json";
|
|
6138
|
+
}
|
|
6139
|
+
function toErrorMessage(data) {
|
|
6140
|
+
if (typeof data === "string") return data;
|
|
6141
|
+
if (data instanceof ArrayBuffer) return "Unknown error";
|
|
6142
|
+
if (typeof data === "object" && data !== null && "message" in data) {
|
|
6143
|
+
const objectData = data;
|
|
6144
|
+
const suffix = "documentation_url" in objectData ? ` - ${objectData.documentation_url}` : "";
|
|
6145
|
+
return Array.isArray(objectData.errors) ? `${objectData.message}: ${objectData.errors.map((v) => JSON.stringify(v)).join(", ")}${suffix}` : `${objectData.message}${suffix}`;
|
|
6146
|
+
}
|
|
6147
|
+
return `Unknown error: ${JSON.stringify(data)}`;
|
|
6148
|
+
}
|
|
6149
|
+
function withDefaults$1(oldEndpoint, newDefaults) {
|
|
6150
|
+
const endpoint2 = oldEndpoint.defaults(newDefaults);
|
|
6151
|
+
const newApi = function(route, parameters) {
|
|
6152
|
+
const endpointOptions = endpoint2.merge(route, parameters);
|
|
6153
|
+
if (!endpointOptions.request || !endpointOptions.request.hook) return fetchWrapper(endpoint2.parse(endpointOptions));
|
|
6154
|
+
const request2 = (route2, parameters2) => {
|
|
6155
|
+
return fetchWrapper(endpoint2.parse(endpoint2.merge(route2, parameters2)));
|
|
6156
|
+
};
|
|
6157
|
+
Object.assign(request2, {
|
|
6158
|
+
endpoint: endpoint2,
|
|
6159
|
+
defaults: withDefaults$1.bind(null, endpoint2)
|
|
6160
|
+
});
|
|
6161
|
+
return endpointOptions.request.hook(request2, endpointOptions);
|
|
5426
6162
|
};
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
input.side = c.side;
|
|
5431
|
-
if (c.start_line != null) {
|
|
5432
|
-
input.startLine = c.start_line;
|
|
5433
|
-
input.startSide = c.start_side;
|
|
5434
|
-
}
|
|
5435
|
-
}
|
|
5436
|
-
await graphql(run, cwd, ADD_THREAD, { input });
|
|
5437
|
-
}
|
|
5438
|
-
/** Runs a GraphQL document through `gh api graphql` and returns its `data`. */
|
|
5439
|
-
async function graphql(run, cwd, query, variables) {
|
|
5440
|
-
const out = await run([
|
|
5441
|
-
"api",
|
|
5442
|
-
"graphql",
|
|
5443
|
-
"--input",
|
|
5444
|
-
"-"
|
|
5445
|
-
], {
|
|
5446
|
-
cwd,
|
|
5447
|
-
input: JSON.stringify({
|
|
5448
|
-
query,
|
|
5449
|
-
variables
|
|
5450
|
-
})
|
|
6163
|
+
return Object.assign(newApi, {
|
|
6164
|
+
endpoint: endpoint2,
|
|
6165
|
+
defaults: withDefaults$1.bind(null, endpoint2)
|
|
5451
6166
|
});
|
|
5452
|
-
let body;
|
|
5453
|
-
try {
|
|
5454
|
-
body = JSON.parse(out);
|
|
5455
|
-
} catch {
|
|
5456
|
-
throw new GithubError(`unexpected output from gh api graphql: ${out.slice(0, 200)}`, 502);
|
|
5457
|
-
}
|
|
5458
|
-
if (body.data == null) throw new GithubError(`unexpected output from gh api graphql: ${out.slice(0, 200)}`, 502);
|
|
5459
|
-
return body.data;
|
|
5460
6167
|
}
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
6168
|
+
var request = withDefaults$1(endpoint, defaults_default);
|
|
6169
|
+
/* v8 ignore next -- @preserve */
|
|
6170
|
+
/* v8 ignore else -- @preserve */
|
|
6171
|
+
//#endregion
|
|
6172
|
+
//#region node_modules/@octokit/graphql/dist-bundle/index.js
|
|
6173
|
+
var VERSION$1 = "0.0.0-development";
|
|
6174
|
+
function _buildMessageForResponseErrors(data) {
|
|
6175
|
+
return `Request failed due to following response errors:
|
|
6176
|
+
` + data.errors.map((e) => ` - ${e.message}`).join("\n");
|
|
6177
|
+
}
|
|
6178
|
+
var GraphqlResponseError = class extends Error {
|
|
6179
|
+
constructor(request2, headers, response) {
|
|
6180
|
+
super(_buildMessageForResponseErrors(response));
|
|
6181
|
+
this.request = request2;
|
|
6182
|
+
this.headers = headers;
|
|
6183
|
+
this.response = response;
|
|
6184
|
+
this.errors = response.errors;
|
|
6185
|
+
this.data = response.data;
|
|
6186
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
6187
|
+
}
|
|
6188
|
+
request;
|
|
6189
|
+
headers;
|
|
6190
|
+
response;
|
|
6191
|
+
name = "GraphqlResponseError";
|
|
6192
|
+
errors;
|
|
6193
|
+
data;
|
|
6194
|
+
};
|
|
6195
|
+
var NON_VARIABLE_OPTIONS = [
|
|
6196
|
+
"method",
|
|
6197
|
+
"baseUrl",
|
|
6198
|
+
"url",
|
|
6199
|
+
"headers",
|
|
6200
|
+
"request",
|
|
6201
|
+
"query",
|
|
6202
|
+
"mediaType",
|
|
6203
|
+
"operationName"
|
|
6204
|
+
];
|
|
6205
|
+
var FORBIDDEN_VARIABLE_OPTIONS = [
|
|
6206
|
+
"query",
|
|
6207
|
+
"method",
|
|
6208
|
+
"url"
|
|
6209
|
+
];
|
|
6210
|
+
var GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/;
|
|
6211
|
+
function graphql(request2, query, options) {
|
|
6212
|
+
if (options) {
|
|
6213
|
+
if (typeof query === "string" && "query" in options) return Promise.reject(/* @__PURE__ */ new Error(`[@octokit/graphql] "query" cannot be used as variable name`));
|
|
6214
|
+
for (const key in options) {
|
|
6215
|
+
if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue;
|
|
6216
|
+
return Promise.reject(/* @__PURE__ */ new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`));
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
const parsedOptions = typeof query === "string" ? Object.assign({ query }, options) : query;
|
|
6220
|
+
const requestOptions = Object.keys(parsedOptions).reduce((result, key) => {
|
|
6221
|
+
if (NON_VARIABLE_OPTIONS.includes(key)) {
|
|
6222
|
+
result[key] = parsedOptions[key];
|
|
6223
|
+
return result;
|
|
6224
|
+
}
|
|
6225
|
+
if (!result.variables) result.variables = {};
|
|
6226
|
+
result.variables[key] = parsedOptions[key];
|
|
6227
|
+
return result;
|
|
6228
|
+
}, {});
|
|
6229
|
+
const baseUrl = parsedOptions.baseUrl || request2.endpoint.DEFAULTS.baseUrl;
|
|
6230
|
+
if (GHES_V3_SUFFIX_REGEX.test(baseUrl)) requestOptions.url = baseUrl.replace(GHES_V3_SUFFIX_REGEX, "/api/graphql");
|
|
6231
|
+
return request2(requestOptions).then((response) => {
|
|
6232
|
+
if (response.data.errors) {
|
|
6233
|
+
const headers = {};
|
|
6234
|
+
for (const key of Object.keys(response.headers)) headers[key] = response.headers[key];
|
|
6235
|
+
throw new GraphqlResponseError(requestOptions, headers, response.data);
|
|
6236
|
+
}
|
|
6237
|
+
return response.data.data;
|
|
6238
|
+
});
|
|
5465
6239
|
}
|
|
5466
|
-
|
|
5467
|
-
const
|
|
5468
|
-
const
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
if (err.code === "ENOENT") return reject(new GithubError("gh is not installed; see https://cli.github.com"));
|
|
5475
|
-
const detail = (stderr || err.message).trim().split("\n")[0] ?? "";
|
|
5476
|
-
reject(new GithubError(`gh ${args[0]} ${args[1] ?? ""} failed: ${detail}`.trim(), args[0] === "api" ? 502 : 409));
|
|
6240
|
+
function withDefaults(request2, newDefaults) {
|
|
6241
|
+
const newRequest = request2.defaults(newDefaults);
|
|
6242
|
+
const newApi = (query, options) => {
|
|
6243
|
+
return graphql(newRequest, query, options);
|
|
6244
|
+
};
|
|
6245
|
+
return Object.assign(newApi, {
|
|
6246
|
+
defaults: withDefaults.bind(null, newRequest),
|
|
6247
|
+
endpoint: newRequest.endpoint
|
|
5477
6248
|
});
|
|
5478
|
-
|
|
5479
|
-
|
|
6249
|
+
}
|
|
6250
|
+
withDefaults(request, {
|
|
6251
|
+
headers: { "user-agent": `octokit-graphql.js/${VERSION$1} ${getUserAgent()}` },
|
|
6252
|
+
method: "POST",
|
|
6253
|
+
url: "/graphql"
|
|
5480
6254
|
});
|
|
5481
|
-
|
|
6255
|
+
function withCustomRequest(customRequest) {
|
|
6256
|
+
return withDefaults(customRequest, {
|
|
6257
|
+
method: "POST",
|
|
6258
|
+
url: "/graphql"
|
|
6259
|
+
});
|
|
6260
|
+
}
|
|
6261
|
+
/* v8 ignore if -- @preserve */
|
|
6262
|
+
//#endregion
|
|
6263
|
+
//#region node_modules/@octokit/auth-token/dist-bundle/index.js
|
|
6264
|
+
var b64url = "(?:[a-zA-Z0-9_-]+)";
|
|
6265
|
+
var sep$1 = "\\.";
|
|
6266
|
+
var jwtRE = new RegExp(`^${b64url}${sep$1}${b64url}${sep$1}${b64url}$`);
|
|
6267
|
+
var isJWT = jwtRE.test.bind(jwtRE);
|
|
6268
|
+
async function auth(token) {
|
|
6269
|
+
const isApp = isJWT(token);
|
|
6270
|
+
const isInstallation = token.startsWith("v1.") || token.startsWith("ghs_");
|
|
6271
|
+
const isUserToServer = token.startsWith("ghu_");
|
|
6272
|
+
return {
|
|
6273
|
+
type: "token",
|
|
6274
|
+
token,
|
|
6275
|
+
tokenType: isApp ? "app" : isInstallation ? "installation" : isUserToServer ? "user-to-server" : "oauth"
|
|
6276
|
+
};
|
|
6277
|
+
}
|
|
6278
|
+
function withAuthorizationPrefix(token) {
|
|
6279
|
+
if (token.split(/\./).length === 3) return `bearer ${token}`;
|
|
6280
|
+
return `token ${token}`;
|
|
6281
|
+
}
|
|
6282
|
+
async function hook(token, request, route, parameters) {
|
|
6283
|
+
const endpoint = request.endpoint.merge(route, parameters);
|
|
6284
|
+
endpoint.headers.authorization = withAuthorizationPrefix(token);
|
|
6285
|
+
return request(endpoint);
|
|
6286
|
+
}
|
|
6287
|
+
var createTokenAuth = function createTokenAuth2(token) {
|
|
6288
|
+
if (!token) throw new Error("[@octokit/auth-token] No token passed to createTokenAuth");
|
|
6289
|
+
if (typeof token !== "string") throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string");
|
|
6290
|
+
token = token.replace(/^(token|bearer) +/i, "");
|
|
6291
|
+
return Object.assign(auth.bind(null, token), { hook: hook.bind(null, token) });
|
|
6292
|
+
};
|
|
6293
|
+
//#endregion
|
|
6294
|
+
//#region node_modules/@octokit/core/dist-src/version.js
|
|
6295
|
+
const VERSION = "7.0.8";
|
|
6296
|
+
//#endregion
|
|
6297
|
+
//#region node_modules/@octokit/core/dist-src/index.js
|
|
6298
|
+
const noop = () => {};
|
|
6299
|
+
const consoleWarn = console.warn.bind(console);
|
|
6300
|
+
const consoleError = console.error.bind(console);
|
|
6301
|
+
function createLogger(logger = {}) {
|
|
6302
|
+
if (typeof logger.debug !== "function") logger.debug = noop;
|
|
6303
|
+
if (typeof logger.info !== "function") logger.info = noop;
|
|
6304
|
+
if (typeof logger.warn !== "function") logger.warn = consoleWarn;
|
|
6305
|
+
if (typeof logger.error !== "function") logger.error = consoleError;
|
|
6306
|
+
return logger;
|
|
6307
|
+
}
|
|
6308
|
+
const userAgentTrail = `octokit-core.js/${VERSION} ${getUserAgent()}`;
|
|
6309
|
+
var Octokit = class {
|
|
6310
|
+
static VERSION = VERSION;
|
|
6311
|
+
static defaults(defaults) {
|
|
6312
|
+
const OctokitWithDefaults = class extends this {
|
|
6313
|
+
constructor(...args) {
|
|
6314
|
+
const options = args[0] || {};
|
|
6315
|
+
if (typeof defaults === "function") {
|
|
6316
|
+
super(defaults(options));
|
|
6317
|
+
return;
|
|
6318
|
+
}
|
|
6319
|
+
super(Object.assign({}, defaults, options, options.userAgent && defaults.userAgent ? { userAgent: `${options.userAgent} ${defaults.userAgent}` } : null));
|
|
6320
|
+
}
|
|
6321
|
+
};
|
|
6322
|
+
return OctokitWithDefaults;
|
|
6323
|
+
}
|
|
6324
|
+
static plugins = [];
|
|
6325
|
+
/**
|
|
6326
|
+
* Attach a plugin (or many) to your Octokit instance.
|
|
6327
|
+
*
|
|
6328
|
+
* @example
|
|
6329
|
+
* const API = Octokit.plugin(plugin1, plugin2, plugin3, ...)
|
|
6330
|
+
*/
|
|
6331
|
+
static plugin(...newPlugins) {
|
|
6332
|
+
const currentPlugins = this.plugins;
|
|
6333
|
+
const NewOctokit = class extends this {
|
|
6334
|
+
static plugins = currentPlugins.concat(newPlugins.filter((plugin) => !currentPlugins.includes(plugin)));
|
|
6335
|
+
};
|
|
6336
|
+
return NewOctokit;
|
|
6337
|
+
}
|
|
6338
|
+
constructor(options = {}) {
|
|
6339
|
+
const hook = new before_after_hook_default.Collection();
|
|
6340
|
+
const requestDefaults = {
|
|
6341
|
+
baseUrl: request.endpoint.DEFAULTS.baseUrl,
|
|
6342
|
+
headers: {},
|
|
6343
|
+
request: Object.assign({}, options.request, { hook: hook.bind(null, "request") }),
|
|
6344
|
+
mediaType: {
|
|
6345
|
+
previews: [],
|
|
6346
|
+
format: ""
|
|
6347
|
+
}
|
|
6348
|
+
};
|
|
6349
|
+
requestDefaults.headers["user-agent"] = options.userAgent ? `${options.userAgent} ${userAgentTrail}` : userAgentTrail;
|
|
6350
|
+
if (options.baseUrl) requestDefaults.baseUrl = options.baseUrl;
|
|
6351
|
+
if (options.previews) requestDefaults.mediaType.previews = options.previews;
|
|
6352
|
+
if (options.timeZone) requestDefaults.headers["time-zone"] = options.timeZone;
|
|
6353
|
+
this.request = request.defaults(requestDefaults);
|
|
6354
|
+
this.graphql = withCustomRequest(this.request).defaults(requestDefaults);
|
|
6355
|
+
this.log = createLogger(options.log);
|
|
6356
|
+
this.hook = hook;
|
|
6357
|
+
if (!options.authStrategy) {
|
|
6358
|
+
if (!options.auth) this.auth = async () => ({ type: "unauthenticated" });
|
|
6359
|
+
else {
|
|
6360
|
+
const auth = createTokenAuth(options.auth);
|
|
6361
|
+
hook.wrap("request", auth.hook);
|
|
6362
|
+
this.auth = auth;
|
|
6363
|
+
}
|
|
6364
|
+
} else {
|
|
6365
|
+
const { authStrategy, ...otherOptions } = options;
|
|
6366
|
+
const auth = authStrategy(Object.assign({
|
|
6367
|
+
request: this.request,
|
|
6368
|
+
log: this.log,
|
|
6369
|
+
octokit: this,
|
|
6370
|
+
octokitOptions: otherOptions
|
|
6371
|
+
}, options.auth));
|
|
6372
|
+
hook.wrap("request", auth.hook);
|
|
6373
|
+
this.auth = auth;
|
|
6374
|
+
}
|
|
6375
|
+
const classConstructor = this.constructor;
|
|
6376
|
+
for (let i = 0; i < classConstructor.plugins.length; ++i) Object.assign(this, classConstructor.plugins[i](this, options));
|
|
6377
|
+
}
|
|
6378
|
+
request;
|
|
6379
|
+
graphql;
|
|
6380
|
+
log;
|
|
6381
|
+
hook;
|
|
6382
|
+
auth;
|
|
6383
|
+
};
|
|
6384
|
+
//#endregion
|
|
6385
|
+
//#region src/server/github/client.ts
|
|
6386
|
+
/** A precondition the user can fix (no token, no PR, wrong mode, unpushed head): the route answers 4xx. */
|
|
6387
|
+
var GithubError = class extends Error {
|
|
6388
|
+
status;
|
|
6389
|
+
constructor(message, status = 409) {
|
|
6390
|
+
super(message);
|
|
6391
|
+
this.status = status;
|
|
6392
|
+
}
|
|
6393
|
+
};
|
|
6394
|
+
/** Said wherever a GitHub feature is asked for without a token. */
|
|
6395
|
+
const NO_TOKEN = "No GitHub token: set GITHUB_TOKEN or sign in with gh auth login";
|
|
5482
6396
|
/**
|
|
5483
|
-
* `
|
|
5484
|
-
*
|
|
5485
|
-
*
|
|
6397
|
+
* `GITHUB_TOKEN`, then `GH_TOKEN`, then the token `gh` holds. Null when none of them has
|
|
6398
|
+
* one: diffle then runs without GitHub, the origin repository still shows and everything
|
|
6399
|
+
* else says why it is off.
|
|
5486
6400
|
*/
|
|
5487
|
-
async function
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
"
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
"open",
|
|
5512
|
-
"--limit",
|
|
5513
|
-
"100",
|
|
5514
|
-
"--json",
|
|
5515
|
-
PR_FIELDS
|
|
5516
|
-
], {
|
|
5517
|
-
cwd,
|
|
5518
|
-
timeoutMs
|
|
5519
|
-
});
|
|
5520
|
-
const values = parseJson(out, "list");
|
|
5521
|
-
if (!Array.isArray(values)) throw new GithubError(`unexpected output from gh pr list: ${out.slice(0, 200)}`, 502);
|
|
5522
|
-
return values.flatMap((value) => {
|
|
5523
|
-
const candidate = value;
|
|
5524
|
-
const owner = candidate?.headRepositoryOwner?.login;
|
|
5525
|
-
const name = candidate?.headRepository?.name;
|
|
5526
|
-
if (typeof owner !== "string" || typeof name !== "string") throw new GithubError(`unexpected output from gh pr list: ${out.slice(0, 200)}`, 502);
|
|
5527
|
-
if (`${owner}/${name}`.toLowerCase() !== headRepo.toLowerCase()) return [];
|
|
5528
|
-
return [parsePullRequest(value)];
|
|
6401
|
+
async function resolveToken(env = process.env, ghToken = ghAuthToken) {
|
|
6402
|
+
for (const source of ["GITHUB_TOKEN", "GH_TOKEN"]) {
|
|
6403
|
+
const token = env[source]?.trim();
|
|
6404
|
+
if (token) return {
|
|
6405
|
+
token,
|
|
6406
|
+
source
|
|
6407
|
+
};
|
|
6408
|
+
}
|
|
6409
|
+
const token = await ghToken();
|
|
6410
|
+
return token ? {
|
|
6411
|
+
token,
|
|
6412
|
+
source: "gh auth token"
|
|
6413
|
+
} : null;
|
|
6414
|
+
}
|
|
6415
|
+
/** The only `gh` left: its stored token, or null when gh is missing, signed out, or slow. */
|
|
6416
|
+
function ghAuthToken() {
|
|
6417
|
+
return new Promise((resolve) => {
|
|
6418
|
+
execFile("gh", ["auth", "token"], {
|
|
6419
|
+
timeout: 5e3,
|
|
6420
|
+
windowsHide: true
|
|
6421
|
+
}, (err, stdout) => {
|
|
6422
|
+
const token = stdout?.trim();
|
|
6423
|
+
resolve(!err && token ? token : null);
|
|
6424
|
+
});
|
|
5529
6425
|
});
|
|
5530
6426
|
}
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
}
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
6427
|
+
/** The real client, over `@octokit/core`. */
|
|
6428
|
+
function createGithubClient(token, { baseUrl, fetch: customFetch } = {}) {
|
|
6429
|
+
const octokit = new Octokit({
|
|
6430
|
+
auth: token,
|
|
6431
|
+
baseUrl: baseUrl || process.env.GITHUB_API_URL || void 0,
|
|
6432
|
+
userAgent: `diffle/${version$1}`,
|
|
6433
|
+
request: customFetch ? { fetch: customFetch } : {}
|
|
6434
|
+
});
|
|
6435
|
+
return { async graphql(query, variables, opts = {}) {
|
|
6436
|
+
const request = opts.timeoutMs ? { signal: AbortSignal.timeout(opts.timeoutMs) } : void 0;
|
|
6437
|
+
try {
|
|
6438
|
+
return await octokit.graphql(query, {
|
|
6439
|
+
...variables,
|
|
6440
|
+
request
|
|
6441
|
+
});
|
|
6442
|
+
} catch (e) {
|
|
6443
|
+
throw toGithubError(e, opts.timeoutMs);
|
|
6444
|
+
}
|
|
6445
|
+
} };
|
|
5543
6446
|
}
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
};
|
|
6447
|
+
/**
|
|
6448
|
+
* GitHub's answer, as a `GithubError`. Something the user can fix (a rejected token, a missing
|
|
6449
|
+
* pull request) is 409; GitHub refusing the content (422) or not answering at all is 502.
|
|
6450
|
+
*/
|
|
6451
|
+
function toGithubError(e, timeoutMs) {
|
|
6452
|
+
if (e instanceof GithubError) return e;
|
|
6453
|
+
const err = e;
|
|
6454
|
+
if (err?.name === "GraphqlResponseError" && Array.isArray(err.errors) && err.errors.length) {
|
|
6455
|
+
const detail = err.errors.map((x) => x.message ?? "unknown error").join("; ");
|
|
6456
|
+
const fixable = err.errors.every((x) => x.type === "NOT_FOUND" || x.type === "FORBIDDEN");
|
|
6457
|
+
return new GithubError(`GitHub: ${detail}`, fixable ? 409 : 502);
|
|
6458
|
+
}
|
|
6459
|
+
if (err?.name === "HttpError" && typeof err.status === "number") {
|
|
6460
|
+
const detail = firstLine(err.message);
|
|
6461
|
+
if (err.response === void 0) {
|
|
6462
|
+
const cause = err.cause?.name;
|
|
6463
|
+
if (cause === "TimeoutError" || cause === "AbortError") return new GithubError(`GitHub did not answer within ${timeoutMs ?? "?"}ms`, 502);
|
|
6464
|
+
return new GithubError(`cannot reach GitHub: ${detail}`, 502);
|
|
6465
|
+
}
|
|
6466
|
+
if (err.status === 401) return new GithubError(`GitHub rejected the token (401): ${detail}`, 409);
|
|
6467
|
+
if (err.status === 403 || err.status === 404) return new GithubError(`GitHub ${err.status}: ${detail}`, 409);
|
|
6468
|
+
return new GithubError(`GitHub ${err.status}: ${detail}`, 502);
|
|
6469
|
+
}
|
|
6470
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") return new GithubError(`GitHub did not answer within ${timeoutMs ?? "?"}ms`, 502);
|
|
6471
|
+
return new GithubError(`cannot reach GitHub: ${e instanceof Error ? firstLine(e.message) : String(e)}`, 502);
|
|
6472
|
+
}
|
|
6473
|
+
function firstLine(s) {
|
|
6474
|
+
return (s ?? "").trim().split("\n")[0] ?? "";
|
|
5569
6475
|
}
|
|
5570
6476
|
//#endregion
|
|
5571
6477
|
//#region node_modules/zod/v4/core/util.js
|
|
@@ -11255,6 +12161,8 @@ const ChangedFileSchema = object({
|
|
|
11255
12161
|
binary: boolean(),
|
|
11256
12162
|
/** New-side blob sha. '' for deletions. Keys the viewed state. */
|
|
11257
12163
|
blob: string(),
|
|
12164
|
+
/** Old-side blob sha. '' for additions. A worktree side's blob, like `blob`, is '' when the file cannot be hashed. */
|
|
12165
|
+
oldBlob: string(),
|
|
11258
12166
|
/** The `linguist-generated` gitattribute, a path pattern or a content sniff said this file is generated. Feeds auto-collapse and risk ranking. */
|
|
11259
12167
|
generated: boolean(),
|
|
11260
12168
|
/** A gitlink (mode 160000): `blob` is the recorded commit, the patch shows the commit-id change, and there is no file to open. */
|
|
@@ -11618,6 +12526,21 @@ const FileQuerySchema = object({
|
|
|
11618
12526
|
path: string().min(1),
|
|
11619
12527
|
rev: SideSchema
|
|
11620
12528
|
});
|
|
12529
|
+
/**
|
|
12530
|
+
* `GET /api/image`: one side of a file as image bytes. The server answers 404 unless `key` is that side's
|
|
12531
|
+
* `imageKey` in its current snapshot, the bytes are still the ones the key names, and they are a raster image.
|
|
12532
|
+
*/
|
|
12533
|
+
const ImageQuerySchema = FileQuerySchema.extend({ key: string().min(1) });
|
|
12534
|
+
/**
|
|
12535
|
+
* Names the bytes of one side of `path`, or null when the side has no name: a changed file's blob on that
|
|
12536
|
+
* side, else the commit of a side that is one, since an unchanged path is alike on both. What a key names
|
|
12537
|
+
* never changes, so an image served under it may be cached for good.
|
|
12538
|
+
*/
|
|
12539
|
+
function imageKey(snap, path, side) {
|
|
12540
|
+
const file = snap.changed.find((f) => f.path === path);
|
|
12541
|
+
if (file) return (side === "new" ? file.blob : file.oldBlob) || null;
|
|
12542
|
+
return [snap.newSha, snap.oldSha].find((rev) => rev !== "worktree") ?? null;
|
|
12543
|
+
}
|
|
11621
12544
|
const offsetSchema = string().regex(/^[0-9]+$/).transform(Number).pipe(number().int().nonnegative());
|
|
11622
12545
|
const LastCommitsQuerySchema = object({
|
|
11623
12546
|
oldOffset: offsetSchema,
|
|
@@ -15886,7 +16809,7 @@ const LOOPBACK = /* @__PURE__ */ new Set([
|
|
|
15886
16809
|
"::1",
|
|
15887
16810
|
"localhost"
|
|
15888
16811
|
]);
|
|
15889
|
-
const
|
|
16812
|
+
const UNCONFIGURED = "if a reverse proxy sent it, trust that origin with --allowed-origin";
|
|
15890
16813
|
/**
|
|
15891
16814
|
* Restricts API and WebSocket requests to local hosts and the configured public origin.
|
|
15892
16815
|
* Proxies may preserve the public Host or rewrite it to an allowed upstream Host.
|
|
@@ -15900,15 +16823,16 @@ function requestGuard(bindHost, names = ownNames(), allowedOrigin) {
|
|
|
15900
16823
|
...names.map((n) => n.toLowerCase())
|
|
15901
16824
|
]);
|
|
15902
16825
|
const publicHost = allowedOrigin == null ? void 0 : new URL(allowedOrigin).host;
|
|
16826
|
+
const hint = allowedOrigin == null ? UNCONFIGURED : `allowed origin is "${allowedOrigin}"`;
|
|
15903
16827
|
return ({ host, origin }) => {
|
|
15904
16828
|
if (!host) return "missing Host header";
|
|
15905
16829
|
const name = hostnameOf(host).toLowerCase();
|
|
15906
16830
|
const localHost = loopback ? LOOPBACK.has(name) : isIP(name) !== 0 || allowed.has(name);
|
|
15907
16831
|
const proxyHost = publicHost != null && host.toLowerCase() === publicHost;
|
|
15908
|
-
if (!localHost && !proxyHost) return `forbidden Host "${host}"; ${
|
|
16832
|
+
if (!localHost && !proxyHost) return `forbidden Host "${host}"; ${hint}`;
|
|
15909
16833
|
if (origin == null || allowedOrigin != null && origin === allowedOrigin) return null;
|
|
15910
16834
|
if (!proxyHost && parseHost(origin) === host) return null;
|
|
15911
|
-
return `forbidden Origin "${origin}" for Host "${host}"; ${
|
|
16835
|
+
return `forbidden Origin "${origin}" for Host "${host}"; ${hint}`;
|
|
15912
16836
|
};
|
|
15913
16837
|
}
|
|
15914
16838
|
/** The host of a syntactically valid origin; undefined for `null` and other non-URLs. */
|
|
@@ -16405,6 +17329,67 @@ function migrateV1(v1) {
|
|
|
16405
17329
|
};
|
|
16406
17330
|
}
|
|
16407
17331
|
//#endregion
|
|
17332
|
+
//#region src/server/image.ts
|
|
17333
|
+
/** Leading bytes of the raster formats browsers display; `null` matches any byte. */
|
|
17334
|
+
const SIGNATURES = [
|
|
17335
|
+
["image/png", [
|
|
17336
|
+
137,
|
|
17337
|
+
80,
|
|
17338
|
+
78,
|
|
17339
|
+
71,
|
|
17340
|
+
13,
|
|
17341
|
+
10,
|
|
17342
|
+
26,
|
|
17343
|
+
10
|
|
17344
|
+
]],
|
|
17345
|
+
["image/jpeg", [
|
|
17346
|
+
255,
|
|
17347
|
+
216,
|
|
17348
|
+
255
|
|
17349
|
+
]],
|
|
17350
|
+
["image/gif", ascii("GIF8")],
|
|
17351
|
+
["image/webp", [
|
|
17352
|
+
...ascii("RIFF"),
|
|
17353
|
+
null,
|
|
17354
|
+
null,
|
|
17355
|
+
null,
|
|
17356
|
+
null,
|
|
17357
|
+
...ascii("WEBP")
|
|
17358
|
+
]],
|
|
17359
|
+
["image/avif", [
|
|
17360
|
+
null,
|
|
17361
|
+
null,
|
|
17362
|
+
null,
|
|
17363
|
+
null,
|
|
17364
|
+
...ascii("ftypavif")
|
|
17365
|
+
]],
|
|
17366
|
+
["image/avif", [
|
|
17367
|
+
null,
|
|
17368
|
+
null,
|
|
17369
|
+
null,
|
|
17370
|
+
null,
|
|
17371
|
+
...ascii("ftypavis")
|
|
17372
|
+
]],
|
|
17373
|
+
["image/bmp", ascii("BM")],
|
|
17374
|
+
["image/x-icon", [
|
|
17375
|
+
0,
|
|
17376
|
+
0,
|
|
17377
|
+
1,
|
|
17378
|
+
0
|
|
17379
|
+
]]
|
|
17380
|
+
];
|
|
17381
|
+
function ascii(s) {
|
|
17382
|
+
return [...s].map((ch) => ch.charCodeAt(0));
|
|
17383
|
+
}
|
|
17384
|
+
/**
|
|
17385
|
+
* The media type of a raster image, read from its leading bytes; null for anything else. The
|
|
17386
|
+
* extension is not consulted: only bytes that are an image get served as one.
|
|
17387
|
+
*/
|
|
17388
|
+
function imageType(buf) {
|
|
17389
|
+
for (const [type, magic] of SIGNATURES) if (buf.length >= magic.length && magic.every((b, i) => b == null || buf[i] === b)) return type;
|
|
17390
|
+
return null;
|
|
17391
|
+
}
|
|
17392
|
+
//#endregion
|
|
16408
17393
|
//#region src/server/routes.ts
|
|
16409
17394
|
/** Invalid JSON is a request error; failures after parsing still propagate. */
|
|
16410
17395
|
async function readJson(c) {
|
|
@@ -16470,6 +17455,18 @@ function createApi(deps) {
|
|
|
16470
17455
|
};
|
|
16471
17456
|
return c.json(body);
|
|
16472
17457
|
});
|
|
17458
|
+
app.get("/api/image", async (c) => {
|
|
17459
|
+
const { path, rev, key } = ImageQuerySchema.parse(c.req.query());
|
|
17460
|
+
const buf = await session.readImage(await session.snapshotter.current(), path, rev, key);
|
|
17461
|
+
const type = buf && imageType(buf);
|
|
17462
|
+
if (!type) return c.json({ error: "no image under that key" }, 404);
|
|
17463
|
+
return c.body(new Uint8Array(buf), 200, {
|
|
17464
|
+
"content-type": type,
|
|
17465
|
+
"cache-control": "private, max-age=31536000, immutable",
|
|
17466
|
+
"x-content-type-options": "nosniff",
|
|
17467
|
+
"cross-origin-resource-policy": "same-origin"
|
|
17468
|
+
});
|
|
17469
|
+
});
|
|
16473
17470
|
app.get("/api/search", async (c) => {
|
|
16474
17471
|
const query = SearchQuerySchema.parse(c.req.query());
|
|
16475
17472
|
const { q, scope } = query;
|
|
@@ -16775,99 +17772,529 @@ var Server = class {
|
|
|
16775
17772
|
hasErrorLogged: () => false,
|
|
16776
17773
|
hasWarned: false
|
|
16777
17774
|
}
|
|
16778
|
-
});
|
|
16779
|
-
this.vite = vite;
|
|
17775
|
+
});
|
|
17776
|
+
this.vite = vite;
|
|
17777
|
+
}
|
|
17778
|
+
async close() {
|
|
17779
|
+
await this.deps.hub.close();
|
|
17780
|
+
await this.vite?.close().catch(() => {});
|
|
17781
|
+
await new Promise((res) => {
|
|
17782
|
+
if (!this.http) return res();
|
|
17783
|
+
this.http.close(() => res());
|
|
17784
|
+
this.http.closeAllConnections();
|
|
17785
|
+
});
|
|
17786
|
+
}
|
|
17787
|
+
};
|
|
17788
|
+
function projectRoot() {
|
|
17789
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
17790
|
+
}
|
|
17791
|
+
function resolveClientDir() {
|
|
17792
|
+
return join(projectRoot(), "dist", "client");
|
|
17793
|
+
}
|
|
17794
|
+
function hasClientBuild() {
|
|
17795
|
+
return existsSync(join(resolveClientDir(), "index.html"));
|
|
17796
|
+
}
|
|
17797
|
+
//#endregion
|
|
17798
|
+
//#region src/server/github/pulls.ts
|
|
17799
|
+
/** GitHub.com identity from a configured HTTPS, SSH, or scp-style remote URL. */
|
|
17800
|
+
function githubRepository(url) {
|
|
17801
|
+
const match = /^(?:https?:\/\/github\.com\/|ssh:\/\/(?:[^/@]+@)?github\.com(?::\d+)?\/|(?:[^/@]+@)?github\.com:)([^/]+)\/([^/]+?)\/?$/i.exec(url);
|
|
17802
|
+
return match ? `${match[1]}/${match[2].replace(/\.git$/i, "")}` : null;
|
|
17803
|
+
}
|
|
17804
|
+
/** Local-only origin identity; works without a token or a network connection. */
|
|
17805
|
+
async function originRepository(repo) {
|
|
17806
|
+
const origin = (await repo.remotes()).find((r) => r.name === "origin");
|
|
17807
|
+
return origin ? githubRepository(origin.url) : null;
|
|
17808
|
+
}
|
|
17809
|
+
/**
|
|
17810
|
+
* The GitHub repository pull requests are looked up in: `origin`, or the only GitHub remote
|
|
17811
|
+
* when origin is not one. Any other layout has to name the pull request by url.
|
|
17812
|
+
*/
|
|
17813
|
+
async function baseRepository(repo) {
|
|
17814
|
+
if (!repo) throw new GithubError("not in a git repository; name the pull request by url", 400);
|
|
17815
|
+
const remotes = (await repo.remotes()).flatMap((r) => {
|
|
17816
|
+
const repository = githubRepository(r.url);
|
|
17817
|
+
return repository ? [{
|
|
17818
|
+
name: r.name,
|
|
17819
|
+
repository
|
|
17820
|
+
}] : [];
|
|
17821
|
+
});
|
|
17822
|
+
const base = remotes.find((r) => r.name === "origin") ?? (remotes.length === 1 ? remotes[0] : void 0);
|
|
17823
|
+
if (!base) throw new GithubError(`${remotes.length ? `${remotes.length} GitHub remotes and no origin` : "no GitHub remote"}; name the pull request by url`, 400);
|
|
17824
|
+
return base.repository;
|
|
17825
|
+
}
|
|
17826
|
+
/** The base repository the pull request lives in, taken from its url (a fork's own remote is not it). */
|
|
17827
|
+
function repoOfPrUrl(url) {
|
|
17828
|
+
const parsed = parsePrUrl(url);
|
|
17829
|
+
if (!parsed) throw new GithubError(`cannot read owner/repo from the pull request url: ${url}`, 502);
|
|
17830
|
+
return {
|
|
17831
|
+
owner: parsed.owner,
|
|
17832
|
+
repo: parsed.repo
|
|
17833
|
+
};
|
|
17834
|
+
}
|
|
17835
|
+
function parsePrUrl(url) {
|
|
17836
|
+
let pathname;
|
|
17837
|
+
try {
|
|
17838
|
+
pathname = new URL(url).pathname;
|
|
17839
|
+
} catch {
|
|
17840
|
+
return null;
|
|
17841
|
+
}
|
|
17842
|
+
const m = /^\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\/|$)/.exec(pathname);
|
|
17843
|
+
return m ? {
|
|
17844
|
+
owner: m[1],
|
|
17845
|
+
repo: m[2],
|
|
17846
|
+
number: Number(m[3])
|
|
17847
|
+
} : null;
|
|
17848
|
+
}
|
|
17849
|
+
const PR_FIELDS = "number url title state isDraft baseRefOid baseRefName headRefName headRefOid headRepository{name owner{login}}";
|
|
17850
|
+
const BY_NUMBER = `query($owner:String!,$repo:String!,$number:Int!){
|
|
17851
|
+
repository(owner:$owner,name:$repo){pullRequest(number:$number){${PR_FIELDS}}}
|
|
17852
|
+
}`;
|
|
17853
|
+
const BY_HEAD = `query($owner:String!,$repo:String!,$head:String!,$base:String){
|
|
17854
|
+
repository(owner:$owner,name:$repo){pullRequests(headRefName:$head,baseRefName:$base,states:[OPEN],first:100){nodes{${PR_FIELDS}}}}
|
|
17855
|
+
}`;
|
|
17856
|
+
/**
|
|
17857
|
+
* The pull request a selector names: a number, `#number`, a URL, a branch (`owner:branch`
|
|
17858
|
+
* for a fork's), or, without one, the checked-out branch's. Everything but a URL is
|
|
17859
|
+
* looked up in the base repository (see `baseRepository`). Option-shaped selectors are refused.
|
|
17860
|
+
*/
|
|
17861
|
+
async function viewPr(selector, repo, github, timeoutMs) {
|
|
17862
|
+
if (selector == null) {
|
|
17863
|
+
if (!repo) throw new GithubError("not in a git repository; name the pull request by url", 400);
|
|
17864
|
+
const ref = await repo.branchRef("HEAD");
|
|
17865
|
+
if (!ref?.startsWith("refs/heads/")) throw new GithubError("HEAD is not on a branch; name the pull request by number or url", 400);
|
|
17866
|
+
const upstream = await repo.upstreamBranch("HEAD");
|
|
17867
|
+
const remotes = await repo.remotes();
|
|
17868
|
+
const remoteUrl = upstream && remotes.find((r) => r.name === upstream.remote)?.url;
|
|
17869
|
+
const headRepo = remoteUrl ? githubRepository(remoteUrl) : null;
|
|
17870
|
+
return byHead(github, await baseRepository(repo), upstream?.branch ?? ref.slice(11), {
|
|
17871
|
+
owner: headRepo?.split("/")[0],
|
|
17872
|
+
timeoutMs
|
|
17873
|
+
});
|
|
17874
|
+
}
|
|
17875
|
+
const s = selector.trim();
|
|
17876
|
+
if (s.startsWith("-")) throw new GithubError(`not a pull request: ${selector}`, 400);
|
|
17877
|
+
const url = /^https?:\/\//.test(s) ? parsePrUrl(s) : null;
|
|
17878
|
+
if (url) return byNumber(github, `${url.owner}/${url.repo}`, url.number, timeoutMs);
|
|
17879
|
+
if (/^https?:\/\//.test(s)) throw new GithubError(`not a pull request url: ${selector}`, 400);
|
|
17880
|
+
const num = /^#?(\d+)$/.exec(s);
|
|
17881
|
+
if (num) return byNumber(github, await baseRepository(repo), Number(num[1]), timeoutMs);
|
|
17882
|
+
const colon = s.indexOf(":");
|
|
17883
|
+
const owner = colon > 0 ? s.slice(0, colon) : void 0;
|
|
17884
|
+
return byHead(github, await baseRepository(repo), colon > 0 ? s.slice(colon + 1) : s, {
|
|
17885
|
+
owner,
|
|
17886
|
+
timeoutMs
|
|
17887
|
+
});
|
|
17888
|
+
}
|
|
17889
|
+
async function byNumber(github, repository, number, timeoutMs) {
|
|
17890
|
+
const [owner, repo] = repository.split("/");
|
|
17891
|
+
const data = await github.graphql(BY_NUMBER, {
|
|
17892
|
+
owner,
|
|
17893
|
+
repo,
|
|
17894
|
+
number
|
|
17895
|
+
}, { timeoutMs });
|
|
17896
|
+
if (!data.repository?.pullRequest) throw new GithubError(`no pull request ${repository}#${number}`, 409);
|
|
17897
|
+
return parsePullRequest(data.repository.pullRequest);
|
|
17898
|
+
}
|
|
17899
|
+
async function byHead(github, baseRepo, head, { owner, timeoutMs }) {
|
|
17900
|
+
const label = owner ? `${owner}:${head}` : head;
|
|
17901
|
+
const prs = (await pullRequestsForHead(github, baseRepo, head, void 0, timeoutMs)).filter((pr) => !owner || pr.headRepository?.split("/")[0]?.toLowerCase() === owner.toLowerCase());
|
|
17902
|
+
if (prs.length === 0) throw new GithubError(`no open pull request in ${baseRepo} for ${label}`, 409);
|
|
17903
|
+
if (prs.length > 1) throw new GithubError(`${prs.length} open pull requests in ${baseRepo} for ${label}; open one by number`, 409);
|
|
17904
|
+
return prs[0];
|
|
17905
|
+
}
|
|
17906
|
+
/** Open pull requests of `baseRepo` whose head is the named branch, from any head repository, into `base` if given. */
|
|
17907
|
+
async function pullRequestsForHead(github, baseRepo, head, base, timeoutMs) {
|
|
17908
|
+
const [owner, repo] = baseRepo.split("/");
|
|
17909
|
+
const nodes = (await github.graphql(BY_HEAD, {
|
|
17910
|
+
owner,
|
|
17911
|
+
repo,
|
|
17912
|
+
head,
|
|
17913
|
+
base: base ?? null
|
|
17914
|
+
}, { timeoutMs })).repository?.pullRequests?.nodes;
|
|
17915
|
+
if (!Array.isArray(nodes)) throw new GithubError("unexpected pull request data from GitHub", 502);
|
|
17916
|
+
return nodes.map(parsePullRequest);
|
|
17917
|
+
}
|
|
17918
|
+
const PullRequestNodeSchema = GithubPullRequestSchema.omit({ repository: true }).extend({
|
|
17919
|
+
baseRefOid: string(),
|
|
17920
|
+
baseRefName: string(),
|
|
17921
|
+
headRefName: string(),
|
|
17922
|
+
headRefOid: string(),
|
|
17923
|
+
headRepository: object({
|
|
17924
|
+
name: string(),
|
|
17925
|
+
owner: object({ login: string() })
|
|
17926
|
+
}).nullable().transform((source) => source ? `${source.owner.login}/${source.name}` : null)
|
|
17927
|
+
});
|
|
17928
|
+
function parsePullRequest(value) {
|
|
17929
|
+
const result = PullRequestNodeSchema.safeParse(value);
|
|
17930
|
+
if (!result.success) throw new GithubError("unexpected pull request data from GitHub", 502);
|
|
17931
|
+
const pr = result.data;
|
|
17932
|
+
const slug = /^https?:\/\/[^/]+\/([^/]+\/[^/]+)\/pull\/\d+/.exec(pr.url);
|
|
17933
|
+
if (!slug) throw new GithubError(`cannot read the repository from the pull request url: ${pr.url}`, 502);
|
|
17934
|
+
return {
|
|
17935
|
+
...pr,
|
|
17936
|
+
repository: slug[1]
|
|
17937
|
+
};
|
|
17938
|
+
}
|
|
17939
|
+
//#endregion
|
|
17940
|
+
//#region src/server/GithubMetadata.ts
|
|
17941
|
+
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
17942
|
+
/**
|
|
17943
|
+
* Enriches a comparison without fetching commits, changing its endpoints, or changing comment
|
|
17944
|
+
* storage. Without a client (no token), only the origin repository is known.
|
|
17945
|
+
*/
|
|
17946
|
+
async function discoverGithub(repo, snap, { github, prUrl } = {}) {
|
|
17947
|
+
const repository = await originRepository(repo);
|
|
17948
|
+
const absent = (reason) => ({
|
|
17949
|
+
version: snap.version,
|
|
17950
|
+
repository,
|
|
17951
|
+
pullRequest: null,
|
|
17952
|
+
reason
|
|
17953
|
+
});
|
|
17954
|
+
if (!github) return absent(NO_TOKEN);
|
|
17955
|
+
try {
|
|
17956
|
+
let pr;
|
|
17957
|
+
if (prUrl) pr = await viewPr(prUrl, repo, github, LOOKUP_TIMEOUT_MS);
|
|
17958
|
+
else {
|
|
17959
|
+
const [old, next, remotes] = await Promise.all([
|
|
17960
|
+
repo.upstreamBranch(snap.mode.old),
|
|
17961
|
+
repo.upstreamBranch(snap.mode.new),
|
|
17962
|
+
repo.remotes()
|
|
17963
|
+
]);
|
|
17964
|
+
if (!old || !next) return absent("No branch with an upstream available for both endpoints");
|
|
17965
|
+
const repository = (remote) => {
|
|
17966
|
+
const url = remotes.find((r) => r.name === remote)?.url;
|
|
17967
|
+
return url ? githubRepository(url) : null;
|
|
17968
|
+
};
|
|
17969
|
+
const baseRepo = repository(old.remote);
|
|
17970
|
+
const headRepo = repository(next.remote);
|
|
17971
|
+
if (!baseRepo || !headRepo) return absent("Both branches must have GitHub remotes");
|
|
17972
|
+
const prs = (await pullRequestsForHead(github, baseRepo, next.branch, old.branch, LOOKUP_TIMEOUT_MS)).filter((pr) => pr.headRepository?.toLowerCase() === headRepo.toLowerCase());
|
|
17973
|
+
if (!prs.length) return absent("No matching pull request");
|
|
17974
|
+
if (prs.length !== 1) return absent("Multiple matching pull requests; open one explicitly");
|
|
17975
|
+
pr = prs[0];
|
|
17976
|
+
}
|
|
17977
|
+
let reason = null;
|
|
17978
|
+
if (pr.state !== "OPEN") reason = "The pull request is not open";
|
|
17979
|
+
else if (snap.oldSha === "worktree" || snap.newSha === "worktree") reason = "GitHub cannot anchor comments to the worktree";
|
|
17980
|
+
else if (snap.newSha !== pr.headRefOid) reason = "The comparison does not end at the pull request head";
|
|
17981
|
+
else try {
|
|
17982
|
+
if (snap.oldSha !== await repo.mergeBase(pr.baseRefOid, pr.headRefOid)) reason = "The comparison does not match the pull request diff";
|
|
17983
|
+
} catch (e) {
|
|
17984
|
+
if (!(e instanceof GitError)) throw e;
|
|
17985
|
+
reason = "Fetch the pull request base to verify its diff";
|
|
17986
|
+
}
|
|
17987
|
+
return {
|
|
17988
|
+
version: snap.version,
|
|
17989
|
+
repository,
|
|
17990
|
+
pullRequest: {
|
|
17991
|
+
repository: pr.repository,
|
|
17992
|
+
number: pr.number,
|
|
17993
|
+
url: pr.url,
|
|
17994
|
+
title: pr.title,
|
|
17995
|
+
state: pr.state,
|
|
17996
|
+
isDraft: pr.isDraft
|
|
17997
|
+
},
|
|
17998
|
+
reason
|
|
17999
|
+
};
|
|
18000
|
+
} catch (error) {
|
|
18001
|
+
if (!(error instanceof GithubError)) throw error;
|
|
18002
|
+
return absent(`Lookup failed: ${error.message}`);
|
|
18003
|
+
}
|
|
18004
|
+
}
|
|
18005
|
+
//#endregion
|
|
18006
|
+
//#region src/server/github/review.ts
|
|
18007
|
+
function isFileComment(c) {
|
|
18008
|
+
return "subjectType" in c;
|
|
18009
|
+
}
|
|
18010
|
+
/**
|
|
18011
|
+
* Threads → the comments of one pending GitHub review, one per thread. Stale threads are skipped:
|
|
18012
|
+
* their lines no longer sit in the diff, and GitHub would refuse them anyway. With
|
|
18013
|
+
* `threadIds`, only those (resolved included, the user asked for them by hand);
|
|
18014
|
+
* without, every unresolved thread.
|
|
18015
|
+
*/
|
|
18016
|
+
function buildReview(threads, threadIds) {
|
|
18017
|
+
const skipped = [];
|
|
18018
|
+
let chosen;
|
|
18019
|
+
if (threadIds) {
|
|
18020
|
+
const byId = new Map(threads.map((t) => [t.id, t]));
|
|
18021
|
+
chosen = [];
|
|
18022
|
+
for (const id of threadIds) {
|
|
18023
|
+
const t = byId.get(id);
|
|
18024
|
+
if (t) chosen.push(t);
|
|
18025
|
+
else skipped.push({
|
|
18026
|
+
id,
|
|
18027
|
+
reason: "unknown thread"
|
|
18028
|
+
});
|
|
18029
|
+
}
|
|
18030
|
+
} else chosen = threads.filter((t) => !t.resolved);
|
|
18031
|
+
const comments = [];
|
|
18032
|
+
const ids = [];
|
|
18033
|
+
for (const t of [...chosen].sort(compareThreads)) {
|
|
18034
|
+
if (t.stale) {
|
|
18035
|
+
skipped.push({
|
|
18036
|
+
id: t.id,
|
|
18037
|
+
reason: "stale"
|
|
18038
|
+
});
|
|
18039
|
+
continue;
|
|
18040
|
+
}
|
|
18041
|
+
comments.push(toReviewComment(t));
|
|
18042
|
+
ids.push(t.id);
|
|
18043
|
+
}
|
|
18044
|
+
return {
|
|
18045
|
+
comments,
|
|
18046
|
+
ids,
|
|
18047
|
+
skipped
|
|
18048
|
+
};
|
|
18049
|
+
}
|
|
18050
|
+
function toReviewComment(t) {
|
|
18051
|
+
const { anchor } = t;
|
|
18052
|
+
const body = formatBody(t.messages);
|
|
18053
|
+
if (anchor.kind === "file") return {
|
|
18054
|
+
path: anchor.path,
|
|
18055
|
+
subjectType: "FILE",
|
|
18056
|
+
body
|
|
18057
|
+
};
|
|
18058
|
+
const side = anchor.side === "old" ? "LEFT" : "RIGHT";
|
|
18059
|
+
const c = {
|
|
18060
|
+
path: anchor.path,
|
|
18061
|
+
line: anchor.endLine,
|
|
18062
|
+
side,
|
|
18063
|
+
body
|
|
18064
|
+
};
|
|
18065
|
+
if (anchor.startLine !== anchor.endLine) {
|
|
18066
|
+
c.startLine = anchor.startLine;
|
|
18067
|
+
c.startSide = side;
|
|
18068
|
+
}
|
|
18069
|
+
return c;
|
|
18070
|
+
}
|
|
18071
|
+
/**
|
|
18072
|
+
* Messages joined as markdown. GitHub renders ```suggestion fences itself, so bodies
|
|
18073
|
+
* stay verbatim.
|
|
18074
|
+
*/
|
|
18075
|
+
function formatBody(messages) {
|
|
18076
|
+
return messages.map((m) => m.body.trim()).join("\n\n---\n\n");
|
|
18077
|
+
}
|
|
18078
|
+
/** Serializes validation and writes so overlapping exports cannot add the same thread twice. */
|
|
18079
|
+
var GithubExporter = class {
|
|
18080
|
+
queue = Promise.resolve();
|
|
18081
|
+
export(prepare) {
|
|
18082
|
+
const result = this.queue.then(async () => exportToGithub(await prepare()));
|
|
18083
|
+
this.queue = result.catch(() => {});
|
|
18084
|
+
return result;
|
|
18085
|
+
}
|
|
18086
|
+
};
|
|
18087
|
+
/**
|
|
18088
|
+
* Adds the threads to a *pending* review on the active PR, creating the pending review
|
|
18089
|
+
* when there is none. The review is never submitted: the human opens the PR and submits
|
|
18090
|
+
* it themselves. The caller validates the comparison inside the export queue before posting.
|
|
18091
|
+
*
|
|
18092
|
+
* Re-exporting matches a hidden thread ID and its anchor in the pending review.
|
|
18093
|
+
* A matching comment is left alone when its body matches and rewritten when it does not, so
|
|
18094
|
+
* editing a thread and exporting again does not stack a second comment on the line. Comments
|
|
18095
|
+
* of an already-submitted review are out of reach — those would have to be replied to.
|
|
18096
|
+
*/
|
|
18097
|
+
async function exportToGithub({ snap, pullRequest: pr, threads, threadIds, github }) {
|
|
18098
|
+
if (snap.newSha === "worktree") throw new GithubError("GitHub cannot anchor comments to the worktree");
|
|
18099
|
+
const { comments, ids, skipped } = buildReview(threads, threadIds);
|
|
18100
|
+
if (comments.length === 0) throw new GithubError(skipped.length ? `nothing to post: ${describe(skipped)}` : "nothing to post", 400);
|
|
18101
|
+
for (const [i, comment] of comments.entries()) comment.body += `\n\n${threadMarker(ids[i])}`;
|
|
18102
|
+
const { pullRequestId, pending } = await findPendingReview(github, pr);
|
|
18103
|
+
if (!pending) {
|
|
18104
|
+
const lines = comments.filter((c) => !isFileComment(c));
|
|
18105
|
+
const created = await createPendingReview(github, pullRequestId, snap.newSha, lines);
|
|
18106
|
+
let appended = 0;
|
|
18107
|
+
try {
|
|
18108
|
+
for (const c of comments.filter(isFileComment)) {
|
|
18109
|
+
await addToPendingReview(github, created.id, c);
|
|
18110
|
+
appended++;
|
|
18111
|
+
}
|
|
18112
|
+
} catch (e) {
|
|
18113
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
18114
|
+
throw new GithubError(`created the pending review with ${lines.length + appended} comments, then ${detail}`, 502);
|
|
18115
|
+
}
|
|
18116
|
+
return {
|
|
18117
|
+
url: pr.url,
|
|
18118
|
+
posted: comments.length,
|
|
18119
|
+
updated: 0,
|
|
18120
|
+
review: "created",
|
|
18121
|
+
skipped
|
|
18122
|
+
};
|
|
16780
18123
|
}
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
18124
|
+
const existing = await pendingComments(github, pr, pending.id);
|
|
18125
|
+
const add = [];
|
|
18126
|
+
let updated = 0;
|
|
18127
|
+
let posted = 0;
|
|
18128
|
+
try {
|
|
18129
|
+
for (const [i, c] of comments.entries()) {
|
|
18130
|
+
const id = ids[i];
|
|
18131
|
+
const at = existing.get(anchorKey(c));
|
|
18132
|
+
const index = at?.findIndex((comment) => comment.body.trimEnd().endsWith(threadMarker(id))) ?? -1;
|
|
18133
|
+
const hit = index < 0 ? void 0 : at.splice(index, 1)[0];
|
|
18134
|
+
if (!hit) {
|
|
18135
|
+
add.push(c);
|
|
18136
|
+
continue;
|
|
18137
|
+
}
|
|
18138
|
+
if (hit.body.trim() === c.body.trim()) {
|
|
18139
|
+
skipped.push({
|
|
18140
|
+
id,
|
|
18141
|
+
reason: "already in the review"
|
|
18142
|
+
});
|
|
18143
|
+
continue;
|
|
18144
|
+
}
|
|
18145
|
+
await github.graphql(UPDATE_COMMENT, { input: {
|
|
18146
|
+
pullRequestReviewCommentId: hit.nodeId,
|
|
18147
|
+
body: c.body
|
|
18148
|
+
} });
|
|
18149
|
+
updated++;
|
|
18150
|
+
}
|
|
18151
|
+
for (const c of add) {
|
|
18152
|
+
await addToPendingReview(github, pending.id, c);
|
|
18153
|
+
posted++;
|
|
18154
|
+
}
|
|
18155
|
+
} catch (e) {
|
|
18156
|
+
if (updated === 0 && posted === 0) throw e;
|
|
18157
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
18158
|
+
throw new GithubError(`updated ${updated} and added ${posted} comments in the pending review, then ${detail}`, 502);
|
|
16789
18159
|
}
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
}
|
|
16797
|
-
function hasClientBuild() {
|
|
16798
|
-
return existsSync(join(resolveClientDir(), "index.html"));
|
|
18160
|
+
return {
|
|
18161
|
+
url: pr.url,
|
|
18162
|
+
posted,
|
|
18163
|
+
updated,
|
|
18164
|
+
review: "existing",
|
|
18165
|
+
skipped
|
|
18166
|
+
};
|
|
16799
18167
|
}
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
16803
|
-
/** GitHub.com identity from a configured HTTPS, SSH, or scp-style remote URL. */
|
|
16804
|
-
function githubRepository(url) {
|
|
16805
|
-
const match = /^(?:https?:\/\/github\.com\/|ssh:\/\/(?:[^/@]+@)?github\.com(?::\d+)?\/|(?:[^/@]+@)?github\.com:)([^/]+)\/([^/]+?)\/?$/i.exec(url);
|
|
16806
|
-
return match ? `${match[1]}/${match[2].replace(/\.git$/i, "")}` : null;
|
|
18168
|
+
function threadMarker(id) {
|
|
18169
|
+
return `<!-- diffle-thread:${encodeURIComponent(id)} -->`;
|
|
16807
18170
|
}
|
|
16808
|
-
/**
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
return
|
|
18171
|
+
/** The anchor must still match before we rewrite an exported thread. */
|
|
18172
|
+
function anchorKey(c) {
|
|
18173
|
+
if (isFileComment(c)) return [c.path, "FILE"].join("\0");
|
|
18174
|
+
return [
|
|
18175
|
+
c.path,
|
|
18176
|
+
c.side,
|
|
18177
|
+
c.startLine ?? c.line,
|
|
18178
|
+
c.line
|
|
18179
|
+
].join("\0");
|
|
16812
18180
|
}
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
18181
|
+
const PENDING_QUERY = `query($owner:String!,$repo:String!,$number:Int!){
|
|
18182
|
+
viewer{login}
|
|
18183
|
+
repository(owner:$owner,name:$repo){pullRequest(number:$number){id reviews(last:20,states:[PENDING]){nodes{id author{login}}}}}
|
|
18184
|
+
}`;
|
|
18185
|
+
/**
|
|
18186
|
+
* The PR's node id and the viewer's own pending review on it, or null. GitHub hides other
|
|
18187
|
+
* people's pending reviews, and the login check makes sure of it: appending to someone
|
|
18188
|
+
* else's draft would put our comments in their review.
|
|
18189
|
+
*/
|
|
18190
|
+
async function findPendingReview(github, pr) {
|
|
18191
|
+
const { owner, repo } = repoOfPrUrl(pr.url);
|
|
18192
|
+
const data = await github.graphql(PENDING_QUERY, {
|
|
18193
|
+
owner,
|
|
18194
|
+
repo,
|
|
18195
|
+
number: pr.number
|
|
16821
18196
|
});
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
18197
|
+
const pullRequestId = data.repository?.pullRequest?.id;
|
|
18198
|
+
if (typeof pullRequestId !== "string") throw new GithubError(`cannot read pull request ${pr.url} from GitHub`, 502);
|
|
18199
|
+
const login = data.viewer?.login;
|
|
18200
|
+
for (const node of data.repository?.pullRequest?.reviews?.nodes ?? []) if (node?.id && node.author?.login === login) return {
|
|
18201
|
+
pullRequestId,
|
|
18202
|
+
pending: { id: node.id }
|
|
18203
|
+
};
|
|
18204
|
+
return {
|
|
18205
|
+
pullRequestId,
|
|
18206
|
+
pending: null
|
|
18207
|
+
};
|
|
18208
|
+
}
|
|
18209
|
+
const THREADS_QUERY = `query($owner:String!,$repo:String!,$number:Int!,$after:String){
|
|
18210
|
+
repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100,after:$after){
|
|
18211
|
+
pageInfo{hasNextPage endCursor}
|
|
18212
|
+
nodes{path line startLine diffSide subjectType comments(first:1){nodes{id body pullRequestReview{id}}}}
|
|
18213
|
+
}}}
|
|
18214
|
+
}`;
|
|
18215
|
+
/**
|
|
18216
|
+
* The pending review's own comments, by anchor. It has to be `reviewThreads`: it is the only view
|
|
18217
|
+
* that reports a *pending* comment's real position — REST's review-comment list leaves `line`,
|
|
18218
|
+
* `side` and `start_line` null for one, and the GraphQL comment type has no side field at all.
|
|
18219
|
+
* A failed or incomplete lookup aborts the export: absence is safe to infer only from a complete read.
|
|
18220
|
+
*/
|
|
18221
|
+
async function pendingComments(github, pr, reviewId) {
|
|
18222
|
+
const by = /* @__PURE__ */ new Map();
|
|
18223
|
+
const { owner, repo } = repoOfPrUrl(pr.url);
|
|
18224
|
+
let after = null;
|
|
18225
|
+
for (let page = 0; page < 10; page++) {
|
|
18226
|
+
const threads = (await github.graphql(THREADS_QUERY, {
|
|
18227
|
+
owner,
|
|
18228
|
+
repo,
|
|
18229
|
+
number: pr.number,
|
|
18230
|
+
after
|
|
18231
|
+
})).repository?.pullRequest?.reviewThreads;
|
|
18232
|
+
if (!threads?.nodes || typeof threads.pageInfo?.hasNextPage !== "boolean") throw new GithubError("cannot read the complete pending review", 502);
|
|
18233
|
+
for (const t of threads.nodes) {
|
|
18234
|
+
const c = t?.comments?.nodes?.[0];
|
|
18235
|
+
if (!t?.path || !c?.id || c.pullRequestReview?.id !== reviewId) continue;
|
|
18236
|
+
const key = pendingKey(t);
|
|
18237
|
+
if (key == null) continue;
|
|
18238
|
+
const at = by.get(key);
|
|
18239
|
+
if (at) at.push({
|
|
18240
|
+
nodeId: c.id,
|
|
18241
|
+
body: c.body ?? ""
|
|
18242
|
+
});
|
|
18243
|
+
else by.set(key, [{
|
|
18244
|
+
nodeId: c.id,
|
|
18245
|
+
body: c.body ?? ""
|
|
18246
|
+
}]);
|
|
16853
18247
|
}
|
|
16854
|
-
return
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
pullRequest: {
|
|
16858
|
-
repository: pr.repository,
|
|
16859
|
-
number: pr.number,
|
|
16860
|
-
url: pr.url,
|
|
16861
|
-
title: pr.title,
|
|
16862
|
-
state: pr.state,
|
|
16863
|
-
isDraft: pr.isDraft
|
|
16864
|
-
},
|
|
16865
|
-
reason
|
|
16866
|
-
};
|
|
16867
|
-
} catch (error) {
|
|
16868
|
-
if (!(error instanceof GithubError)) throw error;
|
|
16869
|
-
return absent(`Lookup failed: ${error.message}`);
|
|
18248
|
+
if (!threads.pageInfo.hasNextPage) return by;
|
|
18249
|
+
if (!threads.pageInfo.endCursor || threads.pageInfo.endCursor === after) throw new GithubError("cannot read the next page of the pending review", 502);
|
|
18250
|
+
after = threads.pageInfo.endCursor;
|
|
16870
18251
|
}
|
|
18252
|
+
throw new GithubError("pending review exceeds the 1000-thread lookup limit; export stopped without changes", 502);
|
|
18253
|
+
}
|
|
18254
|
+
/** The anchor key of a thread already in the review, or null for one GitHub reports without a position. */
|
|
18255
|
+
function pendingKey(t) {
|
|
18256
|
+
const path = t.path;
|
|
18257
|
+
if (t.subjectType === "FILE") return anchorKey({
|
|
18258
|
+
path,
|
|
18259
|
+
subjectType: "FILE",
|
|
18260
|
+
body: ""
|
|
18261
|
+
});
|
|
18262
|
+
if (t.line == null) return null;
|
|
18263
|
+
return anchorKey({
|
|
18264
|
+
path,
|
|
18265
|
+
side: t.diffSide === "LEFT" ? "LEFT" : "RIGHT",
|
|
18266
|
+
line: t.line,
|
|
18267
|
+
startLine: t.startLine ?? void 0,
|
|
18268
|
+
body: ""
|
|
18269
|
+
});
|
|
18270
|
+
}
|
|
18271
|
+
const UPDATE_COMMENT = `mutation($input:UpdatePullRequestReviewCommentInput!){updatePullRequestReviewComment(input:$input){pullRequestReviewComment{id}}}`;
|
|
18272
|
+
const CREATE_REVIEW = `mutation($input:AddPullRequestReviewInput!){addPullRequestReview(input:$input){pullRequestReview{id}}}`;
|
|
18273
|
+
/**
|
|
18274
|
+
* No `event` in the input, so GitHub keeps the new review pending with all of its threads.
|
|
18275
|
+
* Resolves the review's node id, for the comments a new review cannot carry.
|
|
18276
|
+
*/
|
|
18277
|
+
async function createPendingReview(github, pullRequestId, commitOID, threads) {
|
|
18278
|
+
const id = (await github.graphql(CREATE_REVIEW, { input: {
|
|
18279
|
+
pullRequestId,
|
|
18280
|
+
commitOID,
|
|
18281
|
+
threads
|
|
18282
|
+
} })).addPullRequestReview?.pullRequestReview?.id;
|
|
18283
|
+
if (typeof id !== "string") throw new GithubError("GitHub did not return the created review", 502);
|
|
18284
|
+
return { id };
|
|
18285
|
+
}
|
|
18286
|
+
const ADD_THREAD = `mutation($input:AddPullRequestReviewThreadInput!){addPullRequestReviewThread(input:$input){thread{id}}}`;
|
|
18287
|
+
/** Appends one thread to the pending review; the only way to extend one, or to place a file comment. */
|
|
18288
|
+
async function addToPendingReview(github, reviewId, c) {
|
|
18289
|
+
await github.graphql(ADD_THREAD, { input: {
|
|
18290
|
+
pullRequestReviewId: reviewId,
|
|
18291
|
+
...c
|
|
18292
|
+
} });
|
|
18293
|
+
}
|
|
18294
|
+
function describe(skipped) {
|
|
18295
|
+
const counts = /* @__PURE__ */ new Map();
|
|
18296
|
+
for (const s of skipped) counts.set(s.reason, (counts.get(s.reason) ?? 0) + 1);
|
|
18297
|
+
return [...counts].map(([reason, n]) => `${n} ${reason}`).join(", ");
|
|
16871
18298
|
}
|
|
16872
18299
|
//#endregion
|
|
16873
18300
|
//#region src/server/mode.ts
|
|
@@ -16926,8 +18353,8 @@ async function resolveComparison(repo, comparison) {
|
|
|
16926
18353
|
};
|
|
16927
18354
|
}
|
|
16928
18355
|
/** Resolves an input command into a comparison and optional explicit PR identity. */
|
|
16929
|
-
async function resolveReview(req, repo,
|
|
16930
|
-
if (req.kind === "pr") return resolvePr(req, repo,
|
|
18356
|
+
async function resolveReview(req, repo, github) {
|
|
18357
|
+
if (req.kind === "pr") return resolvePr(req, repo, github);
|
|
16931
18358
|
const parsed = req.kind === "working" ? {
|
|
16932
18359
|
old: "HEAD",
|
|
16933
18360
|
new: "worktree",
|
|
@@ -16979,14 +18406,15 @@ function branchKey(old, next, mergeBase) {
|
|
|
16979
18406
|
}
|
|
16980
18407
|
/**
|
|
16981
18408
|
* A GitHub pull request, as GitHub shows it: merge-base(base tip, head) vs head.
|
|
16982
|
-
*
|
|
18409
|
+
* GitHub names the PR, then one fetch brings the base tip and the PR head into
|
|
16983
18410
|
* session-owned refs named after the branches, so a PR nobody has checked out
|
|
16984
18411
|
* is reviewable. These refs stay fixed until another fetch; review state uses
|
|
16985
18412
|
* the same branch identities as ordinary comparisons, so comments survive
|
|
16986
18413
|
* pushes and reopening by branch.
|
|
16987
18414
|
*/
|
|
16988
|
-
async function resolvePr(req, repo,
|
|
16989
|
-
|
|
18415
|
+
async function resolvePr(req, repo, github) {
|
|
18416
|
+
if (!github) throw new GithubError(NO_TOKEN);
|
|
18417
|
+
const pr = await viewPr(req.pr, repo, github);
|
|
16990
18418
|
const head = `${repo.reviewRefs}/${pr.number}/head/${pr.headRefName}`;
|
|
16991
18419
|
const base = `${repo.reviewRefs}/${pr.number}/base/${pr.baseRefName}`;
|
|
16992
18420
|
await repo.fetch(await fetchSource(repo, pr), [`+refs/pull/${pr.number}/head:${head}`, `+refs/heads/${pr.baseRefName}:${base}`]);
|
|
@@ -17263,7 +18691,7 @@ var Session = class {
|
|
|
17263
18691
|
const cached = active.github;
|
|
17264
18692
|
if (cached?.snapshot === snap && cached.expires > Date.now()) return cached.promise;
|
|
17265
18693
|
const promise = discoverGithub(this.repo, snap, {
|
|
17266
|
-
|
|
18694
|
+
github: this.opts.github,
|
|
17267
18695
|
prUrl: active.prUrl
|
|
17268
18696
|
});
|
|
17269
18697
|
const entry = {
|
|
@@ -17283,8 +18711,10 @@ var Session = class {
|
|
|
17283
18711
|
const active = this.require();
|
|
17284
18712
|
const snap = await active.snapshotter.current();
|
|
17285
18713
|
if (active !== this.active) throw new GithubError("Comparison changed; try again");
|
|
18714
|
+
const github = this.opts.github;
|
|
18715
|
+
if (!github) throw new GithubError(NO_TOKEN);
|
|
17286
18716
|
const metadata = await discoverGithub(this.repo, snap, {
|
|
17287
|
-
|
|
18717
|
+
github,
|
|
17288
18718
|
prUrl: active.prUrl
|
|
17289
18719
|
});
|
|
17290
18720
|
if (!metadata.pullRequest || metadata.reason !== null) throw new GithubError(metadata.reason ?? "No matching pull request");
|
|
@@ -17294,7 +18724,7 @@ var Session = class {
|
|
|
17294
18724
|
pullRequest: metadata.pullRequest,
|
|
17295
18725
|
threads: active.comments.threads({ state: "all" }),
|
|
17296
18726
|
threadIds,
|
|
17297
|
-
|
|
18727
|
+
github
|
|
17298
18728
|
};
|
|
17299
18729
|
});
|
|
17300
18730
|
}
|
|
@@ -17321,10 +18751,17 @@ var Session = class {
|
|
|
17321
18751
|
console.error("[diffle] snapshot listener failed:", e);
|
|
17322
18752
|
}
|
|
17323
18753
|
}
|
|
17324
|
-
/**
|
|
17325
|
-
|
|
18754
|
+
/**
|
|
18755
|
+
* Resolves a request into a review without activating it, so a caller can reject
|
|
18756
|
+
* invalid input before serving. Queued, so `close()` waits for a PR fetch.
|
|
18757
|
+
*/
|
|
18758
|
+
resolve(req) {
|
|
18759
|
+
return this.run(() => resolveReview(req, this.repo, this.opts.github));
|
|
18760
|
+
}
|
|
18761
|
+
/** First mode, from `resolve`. Rejects `ready()` on failure so early requests error out. */
|
|
18762
|
+
async start(review) {
|
|
17326
18763
|
try {
|
|
17327
|
-
const snap = await this.
|
|
18764
|
+
const snap = await this.run(() => this.activate(review));
|
|
17328
18765
|
this.resolveReady();
|
|
17329
18766
|
return snap;
|
|
17330
18767
|
} catch (e) {
|
|
@@ -17334,7 +18771,7 @@ var Session = class {
|
|
|
17334
18771
|
}
|
|
17335
18772
|
/** Switch modes at runtime. Broadcasts a snapshot bump on success. */
|
|
17336
18773
|
async switchMode(req) {
|
|
17337
|
-
const snap = await this.
|
|
18774
|
+
const snap = await this.run(async () => this.activate(await resolveReview(req, this.repo, this.opts.github)));
|
|
17338
18775
|
this.hub.broadcast({
|
|
17339
18776
|
type: "snapshot",
|
|
17340
18777
|
version: snap.version
|
|
@@ -17350,11 +18787,7 @@ var Session = class {
|
|
|
17350
18787
|
this.queue = r.catch(() => {});
|
|
17351
18788
|
return r;
|
|
17352
18789
|
}
|
|
17353
|
-
|
|
17354
|
-
return this.run(() => this.transition(req));
|
|
17355
|
-
}
|
|
17356
|
-
async transition(req) {
|
|
17357
|
-
const { mode, prUrl } = await resolveReview(req, this.repo, this.opts.gh);
|
|
18790
|
+
async activate({ mode, prUrl }) {
|
|
17358
18791
|
const snapshotter = new Snapshotter(this.repo, mode, ++this.version, this.opts.context);
|
|
17359
18792
|
const [comments, snap] = await Promise.all([CommentStore.open(this.repo.gitDir, mode.commentKey), snapshotter.current()]);
|
|
17360
18793
|
const next = {
|
|
@@ -17447,6 +18880,18 @@ var Session = class {
|
|
|
17447
18880
|
if (side === "new") return snap.newSha === "worktree" ? this.repo.readWorktree(target) : this.repo.show(snap.newSha, target);
|
|
17448
18881
|
return snap.oldSha === "worktree" ? this.repo.readWorktree(target) : this.repo.show(snap.oldSha, target);
|
|
17449
18882
|
}
|
|
18883
|
+
/**
|
|
18884
|
+
* The bytes `key` names on one side of `path`, or null unless `key` is that side's `imageKey` in `snap`.
|
|
18885
|
+
* A worktree file that changed since `snap` reads as null too, so a key never serves other bytes.
|
|
18886
|
+
*/
|
|
18887
|
+
async readImage(snap, path, side, key) {
|
|
18888
|
+
if (key !== imageKey(snap, path, side)) return null;
|
|
18889
|
+
if (!snap.changed.some((f) => f.path === path)) return this.readSide(snap, path, key === snap.newSha ? "new" : "old");
|
|
18890
|
+
const buf = await this.readSide(snap, path, side);
|
|
18891
|
+
if (buf == null || (side === "new" ? snap.newSha : snap.oldSha) !== "worktree") return buf;
|
|
18892
|
+
const target = sidePath(this.readablePaths(snap), path, side);
|
|
18893
|
+
return target != null && await this.repo.hashObject(buf, target) === key ? buf : null;
|
|
18894
|
+
}
|
|
17450
18895
|
/** Whether `readSide` would find `path` on `side`: the allowlist alone, no read. */
|
|
17451
18896
|
hasSide(snap, path, side) {
|
|
17452
18897
|
return sidePath(this.readablePaths(snap), path, side) != null;
|
|
@@ -21372,7 +22817,7 @@ function openBrowser(url) {
|
|
|
21372
22817
|
//#endregion
|
|
21373
22818
|
//#region src/cli/repository.ts
|
|
21374
22819
|
/** Foreign PRs live in a disposable clone, never in the caller's object database. */
|
|
21375
|
-
async function openReviewRepository(req, cwd,
|
|
22820
|
+
async function openReviewRepository(req, cwd, github) {
|
|
21376
22821
|
let local;
|
|
21377
22822
|
try {
|
|
21378
22823
|
local = await GitRepo.open(cwd);
|
|
@@ -21383,7 +22828,8 @@ async function openReviewRepository(req, cwd, gh = runGh) {
|
|
|
21383
22828
|
repo: local,
|
|
21384
22829
|
close: () => local.cleanReviewRefs()
|
|
21385
22830
|
};
|
|
21386
|
-
|
|
22831
|
+
if (!github) throw new GithubError(NO_TOKEN);
|
|
22832
|
+
const pr = await viewPr(req.pr, local ?? null, github);
|
|
21387
22833
|
if (local && (await local.remotes()).some((r) => remoteSlug(r.url) === pr.repository.toLowerCase())) return {
|
|
21388
22834
|
repo: local,
|
|
21389
22835
|
close: () => local.cleanReviewRefs()
|
|
@@ -21540,14 +22986,16 @@ async function run(req, opts) {
|
|
|
21540
22986
|
process.on("SIGTERM", interrupt);
|
|
21541
22987
|
let review;
|
|
21542
22988
|
try {
|
|
21543
|
-
|
|
22989
|
+
const github = await connectGithub();
|
|
22990
|
+
timing.mark("github token");
|
|
22991
|
+
review = await openReviewRepository(req, opts.C ?? process.cwd(), github.client);
|
|
21544
22992
|
timing.mark("open repository");
|
|
21545
22993
|
const config = await UserConfigStore.open();
|
|
21546
22994
|
if (interrupted) {
|
|
21547
22995
|
await review.close();
|
|
21548
22996
|
return;
|
|
21549
22997
|
}
|
|
21550
|
-
await serve(req, opts, review.repo, review.close, config, timing);
|
|
22998
|
+
await serve(req, opts, review.repo, review.close, config, github, timing);
|
|
21551
22999
|
} catch (e) {
|
|
21552
23000
|
await review?.close();
|
|
21553
23001
|
throw e;
|
|
@@ -21556,11 +23004,12 @@ async function run(req, opts) {
|
|
|
21556
23004
|
process.off("SIGTERM", interrupt);
|
|
21557
23005
|
}
|
|
21558
23006
|
}
|
|
21559
|
-
async function serve(req, opts, repo, closeRepo, config, timing) {
|
|
23007
|
+
async function serve(req, opts, repo, closeRepo, config, github, timing) {
|
|
21560
23008
|
const hub = new WsHub();
|
|
21561
23009
|
const session = new Session(repo, hub, {
|
|
21562
23010
|
watch: opts.watch,
|
|
21563
|
-
context: opts.context ?? config.get().contextLines
|
|
23011
|
+
context: opts.context ?? config.get().contextLines,
|
|
23012
|
+
github: github.client ?? void 0
|
|
21564
23013
|
});
|
|
21565
23014
|
const named = Array.isArray(opts.lsp) ? opts.lsp : [];
|
|
21566
23015
|
const overrides = {
|
|
@@ -21600,6 +23049,9 @@ async function serve(req, opts, repo, closeRepo, config, timing) {
|
|
|
21600
23049
|
process.on("SIGTERM", shutdown);
|
|
21601
23050
|
stopBrowserWatch = watchBrowserLifetime(hub, opts.open && !opts.keepAlive, shutdown);
|
|
21602
23051
|
try {
|
|
23052
|
+
const review = await session.resolve(req);
|
|
23053
|
+
timing.mark("resolve");
|
|
23054
|
+
if (closing) return;
|
|
21603
23055
|
const url = await server.listen();
|
|
21604
23056
|
timing.mark("listen");
|
|
21605
23057
|
if (opts.open) {
|
|
@@ -21609,7 +23061,9 @@ async function serve(req, opts, repo, closeRepo, config, timing) {
|
|
|
21609
23061
|
}
|
|
21610
23062
|
console.error(`🚀 diffle running at ${c.cyan(url.href)}`);
|
|
21611
23063
|
console.error(`📂 ${c.dim("repo")} ${repo.root}`);
|
|
21612
|
-
|
|
23064
|
+
if (github.client) console.error(`🐙 ${c.dim("github")} ${c.dim(`token from ${github.source}`)}`);
|
|
23065
|
+
else console.error(`🐙 ${c.dim("github")} ${c.dim("off: no token in GITHUB_TOKEN, GH_TOKEN or gh auth token")}`);
|
|
23066
|
+
const snap = await session.start(review);
|
|
21613
23067
|
timing.mark("snapshot");
|
|
21614
23068
|
const n = snap.changed.length;
|
|
21615
23069
|
const adds = snap.changed.reduce((a, f) => a + f.additions, 0);
|
|
@@ -21635,6 +23089,15 @@ async function serve(req, opts, repo, closeRepo, config, timing) {
|
|
|
21635
23089
|
throw e;
|
|
21636
23090
|
}
|
|
21637
23091
|
}
|
|
23092
|
+
/** One token lookup per run; without one, GitHub features are off rather than failing later. */
|
|
23093
|
+
async function connectGithub() {
|
|
23094
|
+
const found = await resolveToken();
|
|
23095
|
+
if (!found) return { client: null };
|
|
23096
|
+
return {
|
|
23097
|
+
client: createGithubClient(found.token),
|
|
23098
|
+
source: found.source
|
|
23099
|
+
};
|
|
23100
|
+
}
|
|
21638
23101
|
/**
|
|
21639
23102
|
* The pool reads new-side text through the session, so the snapshot allowlist applies to
|
|
21640
23103
|
* every LSP path. Each snapshot decides which languages run: it re-opens the diff's files
|