@moritzwilksch/diffle 0.1.5 → 0.1.7
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 +11 -6
- package/THIRD_PARTY_NOTICES.md +95 -0
- package/dist/client/assets/{angular-html-DmrG9JtS.js → angular-html-gsHTuMGB.js} +1 -1
- package/dist/client/assets/{angular-ts-ViWJivwt.js → angular-ts-xhBJShFG.js} +1 -1
- package/dist/client/assets/{apl-C5EQbFuR.js → apl-nsC6ra4J.js} +1 -1
- package/dist/client/assets/{astro-Dn81Z0Ad.js → astro-X5_I_ruP.js} +1 -1
- package/dist/client/assets/{blade-Bw99tw7z.js → blade-BqlxrrkS.js} +1 -1
- package/dist/client/assets/{c-BUkR06RC.js → c-DVgCXnBp.js} +1 -1
- package/dist/client/assets/{chapel-BkX4HVjh.js → chapel-LgKo5FoM.js} +1 -1
- package/dist/client/assets/{cobol-DHYNHdYd.js → cobol-BVkDOaHP.js} +1 -1
- package/dist/client/assets/{coffee-CwXH1Vh3.js → coffee-BVuWHkH6.js} +1 -1
- package/dist/client/assets/{cpp-CH4TrKz5.js → cpp-DnxElGuW.js} +1 -1
- package/dist/client/assets/{crystal-DKLVzvRx.js → crystal-CC8Vxuob.js} +1 -1
- package/dist/client/assets/{css-Nq8Uu_QZ.js → css-BBuKGmvC.js} +1 -1
- package/dist/client/assets/{edge-CJRhOJkj.js → edge-CNOZBEfw.js} +1 -1
- package/dist/client/assets/{elixir-B6z04L_L.js → elixir-BG8NE-xg.js} +1 -1
- package/dist/client/assets/{elm-CylqdfmR.js → elm-Cyp6TnT_.js} +1 -1
- package/dist/client/assets/{erb--LFUnTAk.js → erb-C58VxpfX.js} +1 -1
- package/dist/client/assets/{git-rebase-CJezoI_U.js → git-rebase-A6HkVJC_.js} +1 -1
- package/dist/client/assets/{glimmer-js-CVs-5jG6.js → glimmer-js-CqR_jN8G.js} +1 -1
- package/dist/client/assets/{glimmer-ts-C8GkuLHj.js → glimmer-ts-M3ExOO4n.js} +1 -1
- package/dist/client/assets/{glsl-Cre-e57h.js → glsl--WIxeXSU.js} +1 -1
- package/dist/client/assets/{graphql-Bflp-kge.js → graphql-BXRMLGZY.js} +1 -1
- package/dist/client/assets/{hack-BVXbQcT2.js → hack-C8N7KWCY.js} +1 -1
- package/dist/client/assets/{haml-De4W5AvJ.js → haml-CxHPK-iA.js} +1 -1
- package/dist/client/assets/{handlebars-DrAV6XbB.js → handlebars-BNjxS1qh.js} +1 -1
- package/dist/client/assets/{html-DWrE5ZtW.js → html-CO9JbMOk.js} +1 -1
- package/dist/client/assets/{html-derivative-4y8Ldk_X.js → html-derivative-CD1w-J8T.js} +1 -1
- package/dist/client/assets/{http-BlF3sH0P.js → http-DvdIZe4B.js} +1 -1
- package/dist/client/assets/{hurl-8Yd7qaL-.js → hurl-BPchDEWD.js} +1 -1
- package/dist/client/assets/index-B47iHZXu.css +2 -0
- package/dist/client/assets/{index-B5j0g-YF.js → index-BcTYvSA1.js} +104 -93
- 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-Da6TVA-D.js → java-Cw12dAwx.js} +1 -1
- package/dist/client/assets/{javascript-9H4TWodB.js → javascript-CVrIgFqG.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-CB5h6IWK.js → jinja-Dcw1Cb-z.js} +1 -1
- package/dist/client/assets/{jison-DdpPjkKm.js → jison-BCHOo1FF.js} +1 -1
- package/dist/client/assets/{json-lKrYnKUy.js → json-BkfEJ7uD.js} +1 -1
- package/dist/client/assets/{jsx-Cq_s8bb1.js → jsx-CBE97uyR.js} +1 -1
- package/dist/client/assets/{julia-B2fUm1la.js → julia-Dt37WVZB.js} +1 -1
- package/dist/client/assets/{just-EU9poDOS.js → just-DFHYF8cW.js} +1 -1
- package/dist/client/assets/{latex-D94S8ANd.js → latex-BtlIyAq9.js} +1 -1
- package/dist/client/assets/{liquid-Dq3Y6XOW.js → liquid-PdbqGeMi.js} +1 -1
- package/dist/client/assets/{lua-Dl3Y8EmE.js → lua-lhtz56hU.js} +1 -1
- package/dist/client/assets/{marko-BUvlzVCc.js → marko-DPsaza10.js} +1 -1
- package/dist/client/assets/{mdc-5MteepiP.js → mdc-DIH2Ec5y.js} +1 -1
- package/dist/client/assets/{nginx-DPAEohPR.js → nginx-bHk_XM7O.js} +1 -1
- package/dist/client/assets/{nim-Dy-zxDpe.js → nim-BgUabkBA.js} +1 -1
- package/dist/client/assets/{org-a4b5Lf2G.js → org-PMkj4aaP.js} +1 -1
- package/dist/client/assets/{perl-Ba0EQGS8.js → perl-B1ZCbepN.js} +1 -1
- package/dist/client/assets/{php-BwWPnF9y.js → php-Vj6Jmzuy.js} +1 -1
- package/dist/client/assets/{pug-DiduM0zv.js → pug-B0ziVZ95.js} +1 -1
- package/dist/client/assets/{qml-Zm_NS-qG.js → qml--NUJOMX2.js} +1 -1
- package/dist/client/assets/{r-Dq0UKcOZ.js → r-CCy6kg5Q.js} +1 -1
- package/dist/client/assets/{razor-BOvDDN47.js → razor-DBTJol_D.js} +1 -1
- package/dist/client/assets/{regexp-D3YxDQ6s.js → regexp-B-0M8p3f.js} +1 -1
- package/dist/client/assets/{rst-DHbzSb6z.js → rst-DVPZR2Zw.js} +1 -1
- package/dist/client/assets/{ruby-CvI-mn5Q.js → ruby-CY-1xRWL.js} +1 -1
- package/dist/client/assets/{sas-CWQFfwiE.js → sas-BfWvhRC7.js} +1 -1
- package/dist/client/assets/{scss-CCpN0AyS.js → scss-CDSNB-cI.js} +1 -1
- package/dist/client/assets/{shellscript-BfpoO4g6.js → shellscript-x3a-v0TL.js} +1 -1
- package/dist/client/assets/{shellsession-agjx723h.js → shellsession-CBXmn1pk.js} +1 -1
- package/dist/client/assets/{soy-pEVWulWp.js → soy-Dx9wgv4j.js} +1 -1
- package/dist/client/assets/{sql-DS0UAPd6.js → sql-GJE8dpQY.js} +1 -1
- package/dist/client/assets/{stata-D8L-Tcxs.js → stata-Cu3eQvZM.js} +1 -1
- package/dist/client/assets/{surrealql-DOkpssDf.js → surrealql-BSIyNQF3.js} +1 -1
- package/dist/client/assets/{svelte-B4ieXAtz.js → svelte-BC05E7SM.js} +1 -1
- package/dist/client/assets/{templ-BdVEAAo4.js → templ-BZb4iLGg.js} +1 -1
- package/dist/client/assets/{tex-CYjNIefm.js → tex-BgSQdktB.js} +1 -1
- package/dist/client/assets/{ts-tags-BFD-4qTD.js → ts-tags-ByZvgo6s.js} +1 -1
- package/dist/client/assets/{tsx-CZLDU0bf.js → tsx-BkywoqnG.js} +1 -1
- package/dist/client/assets/{twig-DYcKmD5x.js → twig-DT3o-XyD.js} +1 -1
- package/dist/client/assets/{typescript-d_JvksIp.js → typescript-Dc_VYLnG.js} +1 -1
- package/dist/client/assets/{typst-B0Uo-CKu.js → typst-Dufd3udg.js} +1 -1
- package/dist/client/assets/{vue-B3Iiop2I.js → vue-CWow0PK2.js} +1 -1
- package/dist/client/assets/{vue-html-B9s0WJG6.js → vue-html-Dukrnida.js} +1 -1
- package/dist/client/assets/{vue-vine-CIMF412S.js → vue-vine-DeFkcU4G.js} +1 -1
- package/dist/client/assets/{xml-BzXG-icR.js → xml-w1QbHsNc.js} +1 -1
- package/dist/client/assets/{xsl-Dp1sXze8.js → xsl-DQ1uA8pZ.js} +1 -1
- package/dist/client/assets/{yaml-BW2dXlES.js → yaml-Bt6SDR__.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/main.js +1810 -455
- package/package.json +6 -2
- package/dist/client/assets/index-CBL0EMlj.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.7";
|
|
3955
3955
|
//#endregion
|
|
3956
3956
|
//#region src/server/comments/anchor.ts
|
|
3957
3957
|
function splitLines(contents) {
|
|
@@ -4520,10 +4520,11 @@ var GitRepo = class GitRepo {
|
|
|
4520
4520
|
* Worktree searches include untracked files. The result is
|
|
4521
4521
|
* bounded globally: git is stopped once `limit + 1` records have arrived, so a
|
|
4522
4522
|
* common query in a large repository cannot flood the process. `paths`
|
|
4523
|
-
* restricts
|
|
4523
|
+
* restricts files; `ranges` restricts new-side lines before applying the limit.
|
|
4524
|
+
* An empty path list or range map matches nothing.
|
|
4524
4525
|
*/
|
|
4525
4526
|
async grep(query, rev, limit = 500, opts = {}) {
|
|
4526
|
-
if (!query || opts.paths?.length === 0) return {
|
|
4527
|
+
if (!query || opts.paths?.length === 0 || opts.ranges?.size === 0) return {
|
|
4527
4528
|
matches: [],
|
|
4528
4529
|
truncated: false
|
|
4529
4530
|
};
|
|
@@ -4533,9 +4534,9 @@ var GitRepo = class GitRepo {
|
|
|
4533
4534
|
"--no-column",
|
|
4534
4535
|
"-I",
|
|
4535
4536
|
opts.regex ? "-E" : "-F",
|
|
4536
|
-
"-z"
|
|
4537
|
-
`--max-count=${limit + 1}`
|
|
4537
|
+
"-z"
|
|
4538
4538
|
];
|
|
4539
|
+
if (!opts.ranges) args.push(`--max-count=${limit + 1}`);
|
|
4539
4540
|
if (opts.word) args.push("-w");
|
|
4540
4541
|
if (opts.ignoreCase) args.push("-i");
|
|
4541
4542
|
args.push("-e", query);
|
|
@@ -4543,20 +4544,25 @@ var GitRepo = class GitRepo {
|
|
|
4543
4544
|
else args.push(rev);
|
|
4544
4545
|
args.push("--");
|
|
4545
4546
|
for (const p of opts.paths ?? []) args.push(`:(literal)${p}`);
|
|
4547
|
+
const parse = (rec) => {
|
|
4548
|
+
const [rawPath = "", line = "", ...rest] = rec.split("\0");
|
|
4549
|
+
return {
|
|
4550
|
+
path: rev === "worktree" ? rawPath : rawPath.slice(rawPath.indexOf(":") + 1),
|
|
4551
|
+
line: Number(line),
|
|
4552
|
+
text: rest.join("\0").slice(0, 300)
|
|
4553
|
+
};
|
|
4554
|
+
};
|
|
4546
4555
|
const records = await execGitRecords(this.root, args, limit + 1, {
|
|
4547
4556
|
okCodes: [0, 1],
|
|
4548
|
-
timeoutMs: GREP_TIMEOUT_MS
|
|
4557
|
+
timeoutMs: GREP_TIMEOUT_MS,
|
|
4558
|
+
accept: opts.ranges ? (record) => {
|
|
4559
|
+
const { path, line } = parse(record);
|
|
4560
|
+
return opts.ranges.get(path)?.some(([start, end]) => line >= start && line <= end) ?? false;
|
|
4561
|
+
} : void 0
|
|
4549
4562
|
});
|
|
4550
4563
|
const truncated = records.length > limit;
|
|
4551
4564
|
return {
|
|
4552
|
-
matches: records.slice(0, limit).map(
|
|
4553
|
-
const [rawPath = "", line = "", ...rest] = rec.split("\0");
|
|
4554
|
-
return {
|
|
4555
|
-
path: rev === "worktree" ? rawPath : rawPath.slice(rawPath.indexOf(":") + 1),
|
|
4556
|
-
line: Number(line),
|
|
4557
|
-
text: rest.join("\0").slice(0, 300)
|
|
4558
|
-
};
|
|
4559
|
-
}),
|
|
4565
|
+
matches: records.slice(0, limit).map(parse),
|
|
4560
4566
|
truncated
|
|
4561
4567
|
};
|
|
4562
4568
|
}
|
|
@@ -4983,9 +4989,10 @@ function execGitRecords(cwd, args, maxRecords, opts = {}) {
|
|
|
4983
4989
|
GIT_OPTIONAL_LOCKS: "0"
|
|
4984
4990
|
}
|
|
4985
4991
|
});
|
|
4986
|
-
const
|
|
4992
|
+
const records = [];
|
|
4993
|
+
let chunks = [];
|
|
4994
|
+
let fields = 0;
|
|
4987
4995
|
const errChunks = [];
|
|
4988
|
-
let seen = 0;
|
|
4989
4996
|
let done = false;
|
|
4990
4997
|
let timedOut = false;
|
|
4991
4998
|
const timer = opts.timeoutMs == null ? null : setTimeout(() => {
|
|
@@ -4994,16 +5001,24 @@ function execGitRecords(cwd, args, maxRecords, opts = {}) {
|
|
|
4994
5001
|
}, opts.timeoutMs);
|
|
4995
5002
|
child.stdout.on("data", (chunk) => {
|
|
4996
5003
|
if (done) return;
|
|
5004
|
+
let start = 0;
|
|
4997
5005
|
for (let i = 0; i < chunk.length; i++) {
|
|
4998
|
-
if (chunk[i]
|
|
4999
|
-
if (
|
|
5000
|
-
|
|
5006
|
+
if (chunk[i] === 0) fields++;
|
|
5007
|
+
if (chunk[i] !== 10 || fields < 2) continue;
|
|
5008
|
+
chunks.push(chunk.subarray(start, i));
|
|
5009
|
+
const record = Buffer.concat(chunks).toString("utf8");
|
|
5010
|
+
chunks = [];
|
|
5011
|
+
fields = 0;
|
|
5012
|
+
start = i + 1;
|
|
5013
|
+
if (opts.accept && !opts.accept(record)) continue;
|
|
5014
|
+
records.push(record);
|
|
5015
|
+
if (records.length === maxRecords) {
|
|
5001
5016
|
done = true;
|
|
5002
5017
|
child.kill();
|
|
5003
5018
|
return;
|
|
5004
5019
|
}
|
|
5005
5020
|
}
|
|
5006
|
-
chunks.push(chunk);
|
|
5021
|
+
if (start < chunk.length) chunks.push(chunk.subarray(start));
|
|
5007
5022
|
});
|
|
5008
5023
|
child.stderr.on("data", (chunk) => errChunks.push(chunk));
|
|
5009
5024
|
child.on("error", (err) => rej(new GitError(`git ${args.join(" ")} failed: ${err.message}`, args, null, "")));
|
|
@@ -5018,8 +5033,6 @@ function execGitRecords(cwd, args, maxRecords, opts = {}) {
|
|
|
5018
5033
|
rej(new GitError(`git ${args.join(" ")} failed: ${stderr.trim()}`, args, code, stderr));
|
|
5019
5034
|
return;
|
|
5020
5035
|
}
|
|
5021
|
-
const records = Buffer.concat(chunks).toString("utf8").split("\n");
|
|
5022
|
-
if (records[records.length - 1] === "") records.pop();
|
|
5023
5036
|
res(records);
|
|
5024
5037
|
});
|
|
5025
5038
|
});
|
|
@@ -5099,460 +5112,1340 @@ function diffRange(oldRev, newRev) {
|
|
|
5099
5112
|
return oldRev === "worktree" ? ["-R", newRev] : newRev === "worktree" ? [oldRev] : [oldRev, newRev];
|
|
5100
5113
|
}
|
|
5101
5114
|
//#endregion
|
|
5102
|
-
//#region
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5115
|
+
//#region node_modules/universal-user-agent/index.js
|
|
5116
|
+
function getUserAgent() {
|
|
5117
|
+
if (typeof navigator === "object" && "userAgent" in navigator) return navigator.userAgent;
|
|
5118
|
+
if (typeof process === "object" && process.version !== void 0) return `Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`;
|
|
5119
|
+
return "<environment undetectable>";
|
|
5120
|
+
}
|
|
5121
|
+
//#endregion
|
|
5122
|
+
//#region node_modules/before-after-hook/lib/register.js
|
|
5123
|
+
function register(state, name, method, options) {
|
|
5124
|
+
if (typeof method !== "function") throw new Error("method for before hook must be a function");
|
|
5125
|
+
if (!options) options = {};
|
|
5126
|
+
if (Array.isArray(name)) return name.reverse().reduce((callback, name) => {
|
|
5127
|
+
return register.bind(null, state, name, callback, options);
|
|
5128
|
+
}, method)();
|
|
5129
|
+
return Promise.resolve().then(() => {
|
|
5130
|
+
if (!state.registry[name]) return method(options);
|
|
5131
|
+
return state.registry[name].reduce((method, registered) => {
|
|
5132
|
+
return registered.hook.bind(null, method, options);
|
|
5133
|
+
}, method)();
|
|
5134
|
+
});
|
|
5135
|
+
}
|
|
5136
|
+
//#endregion
|
|
5137
|
+
//#region node_modules/before-after-hook/lib/add.js
|
|
5138
|
+
function addHook(state, kind, name, hook) {
|
|
5139
|
+
const orig = hook;
|
|
5140
|
+
if (!state.registry[name]) state.registry[name] = [];
|
|
5141
|
+
if (kind === "before") hook = (method, options) => {
|
|
5142
|
+
return Promise.resolve().then(orig.bind(null, options)).then(method.bind(null, options));
|
|
5143
|
+
};
|
|
5144
|
+
if (kind === "after") hook = (method, options) => {
|
|
5145
|
+
let result;
|
|
5146
|
+
return Promise.resolve().then(method.bind(null, options)).then((result_) => {
|
|
5147
|
+
result = result_;
|
|
5148
|
+
return orig(result, options);
|
|
5149
|
+
}).then(() => {
|
|
5150
|
+
return result;
|
|
5151
|
+
});
|
|
5152
|
+
};
|
|
5153
|
+
if (kind === "error") hook = (method, options) => {
|
|
5154
|
+
return Promise.resolve().then(method.bind(null, options)).catch((error) => {
|
|
5155
|
+
return orig(error, options);
|
|
5156
|
+
});
|
|
5157
|
+
};
|
|
5158
|
+
state.registry[name].push({
|
|
5159
|
+
hook,
|
|
5160
|
+
orig
|
|
5161
|
+
});
|
|
5162
|
+
}
|
|
5163
|
+
//#endregion
|
|
5164
|
+
//#region node_modules/before-after-hook/lib/remove.js
|
|
5165
|
+
function removeHook(state, name, method) {
|
|
5166
|
+
if (!state.registry[name]) return;
|
|
5167
|
+
const index = state.registry[name].map((registered) => {
|
|
5168
|
+
return registered.orig;
|
|
5169
|
+
}).indexOf(method);
|
|
5170
|
+
if (index === -1) return;
|
|
5171
|
+
state.registry[name].splice(index, 1);
|
|
5172
|
+
}
|
|
5173
|
+
//#endregion
|
|
5174
|
+
//#region node_modules/before-after-hook/index.js
|
|
5175
|
+
const bind = Function.bind;
|
|
5176
|
+
const bindable = bind.bind(bind);
|
|
5177
|
+
function bindApi(hook, state, name) {
|
|
5178
|
+
const removeHookRef = bindable(removeHook, null).apply(null, name ? [state, name] : [state]);
|
|
5179
|
+
hook.api = { remove: removeHookRef };
|
|
5180
|
+
hook.remove = removeHookRef;
|
|
5181
|
+
[
|
|
5182
|
+
"before",
|
|
5183
|
+
"error",
|
|
5184
|
+
"after",
|
|
5185
|
+
"wrap"
|
|
5186
|
+
].forEach((kind) => {
|
|
5187
|
+
const args = name ? [
|
|
5188
|
+
state,
|
|
5189
|
+
kind,
|
|
5190
|
+
name
|
|
5191
|
+
] : [state, kind];
|
|
5192
|
+
hook[kind] = hook.api[kind] = bindable(addHook, null).apply(null, args);
|
|
5193
|
+
});
|
|
5194
|
+
}
|
|
5195
|
+
function Singular() {
|
|
5196
|
+
const singularHookName = Symbol("Singular");
|
|
5197
|
+
const singularHookState = { registry: {} };
|
|
5198
|
+
const singularHook = register.bind(null, singularHookState, singularHookName);
|
|
5199
|
+
bindApi(singularHook, singularHookState, singularHookName);
|
|
5200
|
+
return singularHook;
|
|
5201
|
+
}
|
|
5202
|
+
function Collection() {
|
|
5203
|
+
const state = { registry: {} };
|
|
5204
|
+
const hook = register.bind(null, state);
|
|
5205
|
+
bindApi(hook, state);
|
|
5206
|
+
return hook;
|
|
5207
|
+
}
|
|
5208
|
+
var before_after_hook_default = {
|
|
5209
|
+
Singular,
|
|
5210
|
+
Collection
|
|
5110
5211
|
};
|
|
5111
|
-
|
|
5112
|
-
|
|
5212
|
+
//#endregion
|
|
5213
|
+
//#region node_modules/@octokit/endpoint/dist-bundle/index.js
|
|
5214
|
+
var DEFAULTS = {
|
|
5215
|
+
method: "GET",
|
|
5216
|
+
baseUrl: "https://api.github.com",
|
|
5217
|
+
headers: {
|
|
5218
|
+
accept: "application/vnd.github.v3+json",
|
|
5219
|
+
"user-agent": `octokit-endpoint.js/0.0.0-development ${getUserAgent()}`
|
|
5220
|
+
},
|
|
5221
|
+
mediaType: { format: "" }
|
|
5222
|
+
};
|
|
5223
|
+
function lowercaseKeys(object) {
|
|
5224
|
+
if (!object) return {};
|
|
5225
|
+
return Object.keys(object).reduce((newObj, key) => {
|
|
5226
|
+
newObj[key.toLowerCase()] = object[key];
|
|
5227
|
+
return newObj;
|
|
5228
|
+
}, {});
|
|
5229
|
+
}
|
|
5230
|
+
function isPlainObject$2(value) {
|
|
5231
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5232
|
+
if (Object.prototype.toString.call(value) !== "[object Object]") return false;
|
|
5233
|
+
const proto = Object.getPrototypeOf(value);
|
|
5234
|
+
if (proto === null) return true;
|
|
5235
|
+
const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
|
|
5236
|
+
return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value);
|
|
5237
|
+
}
|
|
5238
|
+
function mergeDeep(defaults, options) {
|
|
5239
|
+
const result = Object.assign({}, defaults);
|
|
5240
|
+
Object.keys(options).forEach((key) => {
|
|
5241
|
+
if (isPlainObject$2(options[key])) {
|
|
5242
|
+
if (!(key in defaults)) Object.assign(result, { [key]: options[key] });
|
|
5243
|
+
else result[key] = mergeDeep(defaults[key], options[key]);
|
|
5244
|
+
} else Object.assign(result, { [key]: options[key] });
|
|
5245
|
+
});
|
|
5246
|
+
return result;
|
|
5113
5247
|
}
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5248
|
+
function removeUndefinedProperties(obj) {
|
|
5249
|
+
for (const key in obj) if (obj[key] === void 0) delete obj[key];
|
|
5250
|
+
return obj;
|
|
5251
|
+
}
|
|
5252
|
+
function merge$1(defaults, route, options) {
|
|
5253
|
+
if (typeof route === "string") {
|
|
5254
|
+
let [method, url] = route.split(" ");
|
|
5255
|
+
options = Object.assign(url ? {
|
|
5256
|
+
method,
|
|
5257
|
+
url
|
|
5258
|
+
} : { url: method }, options);
|
|
5259
|
+
} else options = Object.assign({}, route);
|
|
5260
|
+
options.headers = lowercaseKeys(options.headers);
|
|
5261
|
+
removeUndefinedProperties(options);
|
|
5262
|
+
removeUndefinedProperties(options.headers);
|
|
5263
|
+
const mergedOptions = mergeDeep(defaults || {}, options);
|
|
5264
|
+
if (options.url === "/graphql") {
|
|
5265
|
+
if (defaults && defaults.mediaType.previews?.length) mergedOptions.mediaType.previews = defaults.mediaType.previews.filter((preview) => !mergedOptions.mediaType.previews.includes(preview)).concat(mergedOptions.mediaType.previews);
|
|
5266
|
+
mergedOptions.mediaType.previews = (mergedOptions.mediaType.previews || []).map((preview) => preview.replace(/-preview/, ""));
|
|
5267
|
+
}
|
|
5268
|
+
return mergedOptions;
|
|
5269
|
+
}
|
|
5270
|
+
function addQueryParameters(url, parameters) {
|
|
5271
|
+
const separator = /\?/.test(url) ? "&" : "?";
|
|
5272
|
+
const names = Object.keys(parameters);
|
|
5273
|
+
if (names.length === 0) return url;
|
|
5274
|
+
return url + separator + names.map((name) => {
|
|
5275
|
+
if (name === "q") return "q=" + parameters.q.split("+").map(encodeURIComponent).join("+");
|
|
5276
|
+
return `${name}=${encodeURIComponent(parameters[name])}`;
|
|
5277
|
+
}).join("&");
|
|
5278
|
+
}
|
|
5279
|
+
var urlVariableRegex = /\{[^{}}]+\}/g;
|
|
5280
|
+
function removeNonChars(variableName) {
|
|
5281
|
+
return variableName.replace(/(?:^\W+)|(?:(?<!\W)\W+$)/g, "").split(/,/);
|
|
5282
|
+
}
|
|
5283
|
+
function extractUrlVariableNames(url) {
|
|
5284
|
+
const matches = url.match(urlVariableRegex);
|
|
5285
|
+
if (!matches) return [];
|
|
5286
|
+
return matches.map(removeNonChars).reduce((a, b) => a.concat(b), []);
|
|
5287
|
+
}
|
|
5288
|
+
function omit$1(object, keysToOmit) {
|
|
5289
|
+
const result = { __proto__: null };
|
|
5290
|
+
for (const key of Object.keys(object)) if (keysToOmit.indexOf(key) === -1) result[key] = object[key];
|
|
5291
|
+
return result;
|
|
5292
|
+
}
|
|
5293
|
+
function encodeReserved(str) {
|
|
5294
|
+
return str.split(/(%[0-9A-Fa-f]{2})/g).map(function(part) {
|
|
5295
|
+
if (!/%[0-9A-Fa-f]/.test(part)) part = encodeURI(part).replace(/%5B/g, "[").replace(/%5D/g, "]");
|
|
5296
|
+
return part;
|
|
5297
|
+
}).join("");
|
|
5298
|
+
}
|
|
5299
|
+
function encodeUnreserved(str) {
|
|
5300
|
+
return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
|
|
5301
|
+
return "%" + c.charCodeAt(0).toString(16).toUpperCase();
|
|
5302
|
+
});
|
|
5303
|
+
}
|
|
5304
|
+
function encodeValue(operator, value, key) {
|
|
5305
|
+
value = operator === "+" || operator === "#" ? encodeReserved(value) : encodeUnreserved(value);
|
|
5306
|
+
if (key) return encodeUnreserved(key) + "=" + value;
|
|
5307
|
+
else return value;
|
|
5308
|
+
}
|
|
5309
|
+
function isDefined(value) {
|
|
5310
|
+
return value !== void 0 && value !== null;
|
|
5311
|
+
}
|
|
5312
|
+
function isKeyOperator(operator) {
|
|
5313
|
+
return operator === ";" || operator === "&" || operator === "?";
|
|
5314
|
+
}
|
|
5315
|
+
function getValues(context, operator, key, modifier) {
|
|
5316
|
+
var value = context[key], result = [];
|
|
5317
|
+
if (isDefined(value) && value !== "") {
|
|
5318
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
5319
|
+
value = value.toString();
|
|
5320
|
+
if (modifier && modifier !== "*") value = value.substring(0, parseInt(modifier, 10));
|
|
5321
|
+
result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : ""));
|
|
5322
|
+
} else if (modifier === "*") {
|
|
5323
|
+
if (Array.isArray(value)) value.filter(isDefined).forEach(function(value2) {
|
|
5324
|
+
result.push(encodeValue(operator, value2, isKeyOperator(operator) ? key : ""));
|
|
5133
5325
|
});
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
const comments = [];
|
|
5137
|
-
const ids = [];
|
|
5138
|
-
for (const t of [...chosen].sort(compareThreads)) {
|
|
5139
|
-
if (t.stale) {
|
|
5140
|
-
skipped.push({
|
|
5141
|
-
id: t.id,
|
|
5142
|
-
reason: "stale"
|
|
5326
|
+
else Object.keys(value).forEach(function(k) {
|
|
5327
|
+
if (isDefined(value[k])) result.push(encodeValue(operator, value[k], k));
|
|
5143
5328
|
});
|
|
5144
|
-
|
|
5329
|
+
} else {
|
|
5330
|
+
const tmp = [];
|
|
5331
|
+
if (Array.isArray(value)) value.filter(isDefined).forEach(function(value2) {
|
|
5332
|
+
tmp.push(encodeValue(operator, value2));
|
|
5333
|
+
});
|
|
5334
|
+
else Object.keys(value).forEach(function(k) {
|
|
5335
|
+
if (isDefined(value[k])) {
|
|
5336
|
+
tmp.push(encodeUnreserved(k));
|
|
5337
|
+
tmp.push(encodeValue(operator, value[k].toString()));
|
|
5338
|
+
}
|
|
5339
|
+
});
|
|
5340
|
+
if (isKeyOperator(operator)) result.push(encodeUnreserved(key) + "=" + tmp.join(","));
|
|
5341
|
+
else if (tmp.length !== 0) result.push(tmp.join(","));
|
|
5145
5342
|
}
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
}
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
commit_id: commitId,
|
|
5152
|
-
comments: comments.filter((c) => !isFileComment(c))
|
|
5153
|
-
},
|
|
5154
|
-
comments,
|
|
5155
|
-
ids,
|
|
5156
|
-
skipped
|
|
5157
|
-
};
|
|
5343
|
+
} else if (operator === ";") {
|
|
5344
|
+
if (isDefined(value)) result.push(encodeUnreserved(key));
|
|
5345
|
+
} else if (value === "" && (operator === "&" || operator === "?")) result.push(encodeUnreserved(key) + "=");
|
|
5346
|
+
else if (value === "") result.push("");
|
|
5347
|
+
return result;
|
|
5158
5348
|
}
|
|
5159
|
-
function
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5349
|
+
function parseUrl(template) {
|
|
5350
|
+
return { expand: expand.bind(null, template) };
|
|
5351
|
+
}
|
|
5352
|
+
function expand(template, context) {
|
|
5353
|
+
var operators = [
|
|
5354
|
+
"+",
|
|
5355
|
+
"#",
|
|
5356
|
+
".",
|
|
5357
|
+
"/",
|
|
5358
|
+
";",
|
|
5359
|
+
"?",
|
|
5360
|
+
"&"
|
|
5361
|
+
];
|
|
5362
|
+
template = template.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g, function(_, expression, literal) {
|
|
5363
|
+
if (expression) {
|
|
5364
|
+
let operator = "";
|
|
5365
|
+
const values = [];
|
|
5366
|
+
if (operators.indexOf(expression.charAt(0)) !== -1) {
|
|
5367
|
+
operator = expression.charAt(0);
|
|
5368
|
+
expression = expression.substr(1);
|
|
5369
|
+
}
|
|
5370
|
+
expression.split(/,/g).forEach(function(variable) {
|
|
5371
|
+
var tmp = /([^:\*]*)(?::(\d+)|(\*))?/.exec(variable);
|
|
5372
|
+
values.push(getValues(context, operator, tmp[1], tmp[2] || tmp[3]));
|
|
5373
|
+
});
|
|
5374
|
+
if (operator && operator !== "+") {
|
|
5375
|
+
var separator = ",";
|
|
5376
|
+
if (operator === "?") separator = "&";
|
|
5377
|
+
else if (operator !== "#") separator = operator;
|
|
5378
|
+
return (values.length !== 0 ? operator : "") + values.join(separator);
|
|
5379
|
+
} else return values.join(",");
|
|
5380
|
+
} else return encodeReserved(literal);
|
|
5381
|
+
});
|
|
5382
|
+
if (template === "/") return template;
|
|
5383
|
+
else return template.replace(/\/$/, "");
|
|
5179
5384
|
}
|
|
5385
|
+
function parse$2(options) {
|
|
5386
|
+
let method = options.method.toUpperCase();
|
|
5387
|
+
let url = (options.url || "/").replace(/:([a-z]\w+)/g, "{$1}");
|
|
5388
|
+
let headers = Object.assign({}, options.headers);
|
|
5389
|
+
let body;
|
|
5390
|
+
let parameters = omit$1(options, [
|
|
5391
|
+
"method",
|
|
5392
|
+
"baseUrl",
|
|
5393
|
+
"url",
|
|
5394
|
+
"headers",
|
|
5395
|
+
"request",
|
|
5396
|
+
"mediaType"
|
|
5397
|
+
]);
|
|
5398
|
+
const urlVariableNames = extractUrlVariableNames(url);
|
|
5399
|
+
url = parseUrl(url).expand(parameters);
|
|
5400
|
+
if (!/^http/.test(url)) url = options.baseUrl + url;
|
|
5401
|
+
const remainingParameters = omit$1(parameters, Object.keys(options).filter((option) => urlVariableNames.includes(option)).concat("baseUrl"));
|
|
5402
|
+
if (!/application\/octet-stream/i.test(headers.accept)) {
|
|
5403
|
+
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(",");
|
|
5404
|
+
if (url.endsWith("/graphql")) {
|
|
5405
|
+
if (options.mediaType.previews?.length) headers.accept = (headers.accept.match(/(?<![\w-])[\w-]+(?=-preview)/g) || []).concat(options.mediaType.previews).map((preview) => {
|
|
5406
|
+
return `application/vnd.github.${preview}-preview${options.mediaType.format ? `.${options.mediaType.format}` : "+json"}`;
|
|
5407
|
+
}).join(",");
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
if (["GET", "HEAD"].includes(method)) url = addQueryParameters(url, remainingParameters);
|
|
5411
|
+
else if ("data" in remainingParameters) body = remainingParameters.data;
|
|
5412
|
+
else if (Object.keys(remainingParameters).length) body = remainingParameters;
|
|
5413
|
+
if (!headers["content-type"] && typeof body !== "undefined") headers["content-type"] = "application/json; charset=utf-8";
|
|
5414
|
+
if (["PATCH", "PUT"].includes(method) && typeof body === "undefined") body = "";
|
|
5415
|
+
return Object.assign({
|
|
5416
|
+
method,
|
|
5417
|
+
url,
|
|
5418
|
+
headers
|
|
5419
|
+
}, typeof body !== "undefined" ? { body } : null, options.request ? { request: options.request } : null);
|
|
5420
|
+
}
|
|
5421
|
+
function endpointWithDefaults(defaults, route, options) {
|
|
5422
|
+
return parse$2(merge$1(defaults, route, options));
|
|
5423
|
+
}
|
|
5424
|
+
function withDefaults$2(oldDefaults, newDefaults) {
|
|
5425
|
+
const DEFAULTS2 = merge$1(oldDefaults, newDefaults);
|
|
5426
|
+
const endpoint2 = endpointWithDefaults.bind(null, DEFAULTS2);
|
|
5427
|
+
return Object.assign(endpoint2, {
|
|
5428
|
+
DEFAULTS: DEFAULTS2,
|
|
5429
|
+
defaults: withDefaults$2.bind(null, DEFAULTS2),
|
|
5430
|
+
merge: merge$1.bind(null, DEFAULTS2),
|
|
5431
|
+
parse: parse$2
|
|
5432
|
+
});
|
|
5433
|
+
}
|
|
5434
|
+
var endpoint = withDefaults$2(null, DEFAULTS);
|
|
5435
|
+
//#endregion
|
|
5436
|
+
//#region node_modules/content-type/dist/index.js
|
|
5437
|
+
/*!
|
|
5438
|
+
* content-type
|
|
5439
|
+
* Copyright(c) 2015 Douglas Christopher Wilson
|
|
5440
|
+
* MIT Licensed
|
|
5441
|
+
*/
|
|
5442
|
+
const SP = 32;
|
|
5443
|
+
const HTAB = 9;
|
|
5444
|
+
const SEMI = 59;
|
|
5445
|
+
const EQ = 61;
|
|
5446
|
+
const DQUOTE = 34;
|
|
5447
|
+
const BSLASH = 92;
|
|
5448
|
+
const COMMA = 44;
|
|
5449
|
+
const LOWER_CASE = 1;
|
|
5450
|
+
const OWS = 2;
|
|
5451
|
+
const SEMI_FLAG = 4;
|
|
5452
|
+
const COMMA_FLAG = 8;
|
|
5453
|
+
const TOKEN_FLAG = 16;
|
|
5454
|
+
const NON_ASCII = 65280;
|
|
5455
|
+
const CASE_FLAGS = 65281;
|
|
5180
5456
|
/**
|
|
5181
|
-
*
|
|
5182
|
-
*
|
|
5457
|
+
* Character flags used to normalize HTTP field values while scanning.
|
|
5458
|
+
* Out-of-range reads intentionally coerce to zero in bitwise expressions.
|
|
5183
5459
|
*/
|
|
5184
|
-
|
|
5185
|
-
|
|
5460
|
+
const CHAR_MAP = /* @__PURE__ */ new Uint8Array(256);
|
|
5461
|
+
CHAR_MAP[HTAB] |= OWS;
|
|
5462
|
+
CHAR_MAP[SP] |= OWS;
|
|
5463
|
+
CHAR_MAP[SEMI] |= SEMI_FLAG;
|
|
5464
|
+
CHAR_MAP[COMMA] |= COMMA_FLAG;
|
|
5465
|
+
for (let code = 128; code <= 255; code++) CHAR_MAP[code] |= LOWER_CASE;
|
|
5466
|
+
for (const char of "!#$%&'*+-.^_`|~") CHAR_MAP[char.charCodeAt(0)] |= TOKEN_FLAG;
|
|
5467
|
+
for (let code = 48; code <= 57; code++) CHAR_MAP[code] |= TOKEN_FLAG;
|
|
5468
|
+
for (let code = 65; code <= 90; code++) CHAR_MAP[code] |= 17;
|
|
5469
|
+
for (let code = 97; code <= 122; code++) CHAR_MAP[code] |= TOKEN_FLAG;
|
|
5470
|
+
/**
|
|
5471
|
+
* Null object perf optimization. Faster than `Object.create(null)` and `{ __proto__: null }`.
|
|
5472
|
+
*/
|
|
5473
|
+
const NullObject = /* @__PURE__ */ (() => {
|
|
5474
|
+
const C = function() {};
|
|
5475
|
+
C.prototype = Object.create(null);
|
|
5476
|
+
return C;
|
|
5477
|
+
})();
|
|
5478
|
+
/**
|
|
5479
|
+
* Parse a `Content-Type` header.
|
|
5480
|
+
*/
|
|
5481
|
+
function parse$1(header, options) {
|
|
5482
|
+
const stopFlags = SEMI_FLAG | (options?.comma === true ? COMMA_FLAG : 0);
|
|
5483
|
+
const len = header.length;
|
|
5484
|
+
let valueStart = options?.start ?? 0;
|
|
5485
|
+
while ((CHAR_MAP[header.charCodeAt(valueStart)] & OWS) !== 0) valueStart++;
|
|
5486
|
+
let index = valueStart;
|
|
5487
|
+
let typeFlags = 0;
|
|
5488
|
+
let whitespace = -1;
|
|
5489
|
+
let stop = options?.parameters === false ? COMMA_FLAG : 0;
|
|
5490
|
+
while (index < len) {
|
|
5491
|
+
const code = header.charCodeAt(index);
|
|
5492
|
+
const flags = CHAR_MAP[code];
|
|
5493
|
+
if ((flags & stopFlags) !== 0) {
|
|
5494
|
+
stop |= flags & COMMA_FLAG;
|
|
5495
|
+
break;
|
|
5496
|
+
}
|
|
5497
|
+
if ((flags & OWS) !== 0) {
|
|
5498
|
+
if (whitespace === -1) whitespace = index;
|
|
5499
|
+
} else whitespace = -1;
|
|
5500
|
+
typeFlags |= code & NON_ASCII | flags;
|
|
5501
|
+
index++;
|
|
5502
|
+
}
|
|
5503
|
+
const valueEnd = whitespace === -1 ? index : whitespace;
|
|
5504
|
+
const value = header.slice(valueStart, valueEnd);
|
|
5505
|
+
const type = (typeFlags & CASE_FLAGS) === 0 ? value : value.toLowerCase();
|
|
5506
|
+
if (index === len || stop !== 0) return {
|
|
5507
|
+
type,
|
|
5508
|
+
index,
|
|
5509
|
+
parameters: new NullObject()
|
|
5510
|
+
};
|
|
5511
|
+
return parseParameters(header, type, index, len, stopFlags);
|
|
5186
5512
|
}
|
|
5187
|
-
/**
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5513
|
+
/**
|
|
5514
|
+
* Parses the parameters of a `Content-Type` header starting at the given index.
|
|
5515
|
+
*/
|
|
5516
|
+
function parseParameters(header, type, index, len, stopFlags) {
|
|
5517
|
+
const parameters = new NullObject();
|
|
5518
|
+
parameter: while (index < len) {
|
|
5519
|
+
index++;
|
|
5520
|
+
while ((CHAR_MAP[header.charCodeAt(index)] & OWS) !== 0) index++;
|
|
5521
|
+
const keyStart = index;
|
|
5522
|
+
let keyFlags = 0;
|
|
5523
|
+
let keyWhitespace = -1;
|
|
5524
|
+
while (index < len) {
|
|
5525
|
+
const code = header.charCodeAt(index);
|
|
5526
|
+
const flags = CHAR_MAP[code];
|
|
5527
|
+
if ((flags & stopFlags) !== 0) {
|
|
5528
|
+
if ((flags & COMMA_FLAG) !== 0) break parameter;
|
|
5529
|
+
continue parameter;
|
|
5530
|
+
}
|
|
5531
|
+
if (code === EQ) {
|
|
5532
|
+
const keyEnd = keyWhitespace === -1 ? index : keyWhitespace;
|
|
5533
|
+
const value = header.slice(keyStart, keyEnd);
|
|
5534
|
+
const key = (keyFlags & CASE_FLAGS) === 0 ? value : value.toLowerCase();
|
|
5535
|
+
index++;
|
|
5536
|
+
while ((CHAR_MAP[header.charCodeAt(index)] & OWS) !== 0) index++;
|
|
5537
|
+
if (index < len && header.charCodeAt(index) === DQUOTE) {
|
|
5538
|
+
const quotedStart = ++index;
|
|
5539
|
+
let escaped = false;
|
|
5540
|
+
while (index < len) {
|
|
5541
|
+
const code = header.charCodeAt(index);
|
|
5542
|
+
if (code === DQUOTE) {
|
|
5543
|
+
if (parameters[key] === void 0) parameters[key] = escaped ? unescapeQuotedPairs(header, quotedStart, index) : header.slice(quotedStart, index);
|
|
5544
|
+
index++;
|
|
5545
|
+
let stop = 0;
|
|
5546
|
+
while (index < len) {
|
|
5547
|
+
const code = header.charCodeAt(index);
|
|
5548
|
+
const flags = CHAR_MAP[code];
|
|
5549
|
+
if ((flags & stopFlags) !== 0) {
|
|
5550
|
+
stop = flags & COMMA_FLAG;
|
|
5551
|
+
break;
|
|
5552
|
+
}
|
|
5553
|
+
index++;
|
|
5554
|
+
}
|
|
5555
|
+
if (stop !== 0) break parameter;
|
|
5556
|
+
continue parameter;
|
|
5557
|
+
}
|
|
5558
|
+
if (code === BSLASH && index + 1 < len) {
|
|
5559
|
+
escaped = true;
|
|
5560
|
+
index += 2;
|
|
5561
|
+
continue;
|
|
5562
|
+
}
|
|
5563
|
+
index++;
|
|
5564
|
+
}
|
|
5565
|
+
continue parameter;
|
|
5566
|
+
}
|
|
5567
|
+
const valueStart = index;
|
|
5568
|
+
let stop = 0;
|
|
5569
|
+
let valueWhitespace = -1;
|
|
5570
|
+
while (index < len) {
|
|
5571
|
+
const code = header.charCodeAt(index);
|
|
5572
|
+
const flags = CHAR_MAP[code];
|
|
5573
|
+
if ((flags & stopFlags) !== 0) {
|
|
5574
|
+
stop = flags & COMMA_FLAG;
|
|
5575
|
+
break;
|
|
5576
|
+
}
|
|
5577
|
+
if ((flags & OWS) !== 0) {
|
|
5578
|
+
if (valueWhitespace === -1) valueWhitespace = index;
|
|
5579
|
+
} else valueWhitespace = -1;
|
|
5580
|
+
index++;
|
|
5581
|
+
}
|
|
5582
|
+
if (parameters[key] === void 0) {
|
|
5583
|
+
const valueEnd = valueWhitespace === -1 ? index : valueWhitespace;
|
|
5584
|
+
parameters[key] = header.slice(valueStart, valueEnd);
|
|
5585
|
+
}
|
|
5586
|
+
if (stop !== 0) break parameter;
|
|
5587
|
+
continue parameter;
|
|
5588
|
+
}
|
|
5589
|
+
if ((flags & OWS) !== 0) {
|
|
5590
|
+
if (keyWhitespace === -1) keyWhitespace = index;
|
|
5591
|
+
} else keyWhitespace = -1;
|
|
5592
|
+
keyFlags |= code & NON_ASCII | flags;
|
|
5593
|
+
index++;
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5191
5596
|
return {
|
|
5192
|
-
|
|
5193
|
-
|
|
5597
|
+
type,
|
|
5598
|
+
index,
|
|
5599
|
+
parameters
|
|
5194
5600
|
};
|
|
5195
5601
|
}
|
|
5196
|
-
/**
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5602
|
+
/**
|
|
5603
|
+
* Remove backslashes from quoted pairs in a known-terminated quoted string body.
|
|
5604
|
+
*/
|
|
5605
|
+
function unescapeQuotedPairs(str, start, end) {
|
|
5606
|
+
let result = "";
|
|
5607
|
+
for (let index = start; index < end; index++) if (str.charCodeAt(index) === BSLASH) {
|
|
5608
|
+
result += str.slice(start, index);
|
|
5609
|
+
start = ++index;
|
|
5203
5610
|
}
|
|
5204
|
-
|
|
5611
|
+
return result + str.slice(start, end);
|
|
5612
|
+
}
|
|
5613
|
+
//#endregion
|
|
5614
|
+
//#region node_modules/json-with-bigint/json-with-bigint.js
|
|
5615
|
+
const intRegex = /^-?\d+$/;
|
|
5616
|
+
const noiseValue = /^-?\d+n+$/;
|
|
5617
|
+
const originalStringify = JSON.stringify;
|
|
5618
|
+
const originalParse = JSON.parse;
|
|
5619
|
+
const customFormat = /^-?\d+n$/;
|
|
5620
|
+
const bigIntsStringify = /([\[:])?"(-?\d+)n"($|\s*[,\}\]])/g;
|
|
5621
|
+
const noiseStringify = /([\[:])?("-?\d+n+)n("$|"\s*[,\}\]])/g;
|
|
5205
5622
|
/**
|
|
5206
|
-
*
|
|
5207
|
-
*
|
|
5208
|
-
|
|
5623
|
+
* @typedef {(this: any, key: string | number | undefined, value: any) => any} Replacer
|
|
5624
|
+
* @typedef {(key: string | number | undefined, value: any, context?: { source: string }) => any} Reviver
|
|
5625
|
+
*/
|
|
5626
|
+
/**
|
|
5627
|
+
* Checks if a value is unstringifiable according to native JSON.stringify rules.
|
|
5209
5628
|
*
|
|
5210
|
-
*
|
|
5211
|
-
*
|
|
5212
|
-
* editing a thread and exporting again does not stack a second comment on the line. Comments
|
|
5213
|
-
* of an already-submitted review are out of reach — those would have to be replied to.
|
|
5629
|
+
* @param {any} val The value to check.
|
|
5630
|
+
* @returns {boolean} True if the value is undefined, a function, or a symbol.
|
|
5214
5631
|
*/
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5632
|
+
const isUnstringifiable = (val) => val === void 0 || typeof val === "function" || typeof val === "symbol";
|
|
5633
|
+
/**
|
|
5634
|
+
* Checks if a value is a native JSON.rawJSON object (Node.js 22+).
|
|
5635
|
+
*
|
|
5636
|
+
* @param {any} val The value to check.
|
|
5637
|
+
* @returns {boolean} True if the value is a RawJSON instance.
|
|
5638
|
+
*/
|
|
5639
|
+
const isRawJSON = (val) => val !== null && typeof val === "object" && val.constructor && val.constructor.name === "RawJSON";
|
|
5640
|
+
/**
|
|
5641
|
+
* Iteratively converts a JS value to a JSON string.
|
|
5642
|
+
* Used as a fallback when the native JSON.stringify hits the Maximum Call Stack size.
|
|
5643
|
+
* Fully compliant with JSON formatting (space), replacers, and toJSON behaviors.
|
|
5644
|
+
*
|
|
5645
|
+
* @param {any} rootValue The value to stringify.
|
|
5646
|
+
* @param {Replacer | Array<string | number> | null} [replacer] User's custom replacer function.
|
|
5647
|
+
* @param {string | number} [spaceParam] Indentation for pretty-printing.
|
|
5648
|
+
* @returns {string | undefined} The generated JSON string.
|
|
5649
|
+
*/
|
|
5650
|
+
const stringifyIteratively = (rootValue, replacer, spaceParam) => {
|
|
5651
|
+
let space = "";
|
|
5652
|
+
if (typeof spaceParam === "number") space = " ".repeat(Math.min(10, Math.max(0, Math.floor(spaceParam))));
|
|
5653
|
+
else if (typeof spaceParam === "string") space = spaceParam.slice(0, 10);
|
|
5654
|
+
const isFunctionReplacer = typeof replacer === "function";
|
|
5655
|
+
const propertyList = Array.isArray(replacer) ? new Set(replacer.map(String)) : null;
|
|
5656
|
+
/**
|
|
5657
|
+
* Prepares a value for stringification by resolving toJSON, handling BigInts,
|
|
5658
|
+
* applying custom replacers, and unwrapping primitive objects.
|
|
5659
|
+
*
|
|
5660
|
+
* @param {object|Array} parent The parent object or array holding the value.
|
|
5661
|
+
* @param {string} key The key associated with the value.
|
|
5662
|
+
* @param {any} val The raw value to process.
|
|
5663
|
+
* @returns {any} The processed value ready for stringification.
|
|
5664
|
+
*/
|
|
5665
|
+
const prepareVal = (parent, key, val) => {
|
|
5666
|
+
if (val !== null && typeof val === "object" && typeof val.toJSON === "function") val = val.toJSON(key);
|
|
5667
|
+
if (typeof val === "string" && noiseValue.test(val)) return val + "n";
|
|
5668
|
+
if (typeof val === "bigint") {
|
|
5669
|
+
if ("rawJSON" in JSON) return JSON.rawJSON(val.toString());
|
|
5670
|
+
return val.toString() + "n";
|
|
5671
|
+
}
|
|
5672
|
+
if (isFunctionReplacer) val = replacer.call(parent, key, val);
|
|
5673
|
+
if (val !== null && typeof val === "object") {
|
|
5674
|
+
if (val instanceof Number || val instanceof String || val instanceof Boolean) val = val.valueOf();
|
|
5675
|
+
}
|
|
5676
|
+
return val;
|
|
5677
|
+
};
|
|
5678
|
+
const rootProcessed = prepareVal({ "": rootValue }, "", rootValue);
|
|
5679
|
+
if (isUnstringifiable(rootProcessed)) return;
|
|
5680
|
+
const isRootPrimitive = rootProcessed === null || typeof rootProcessed !== "object";
|
|
5681
|
+
const isRootNativeRawJSON = isRawJSON(rootProcessed);
|
|
5682
|
+
if (isRootPrimitive || isRootNativeRawJSON) return originalStringify(rootProcessed);
|
|
5683
|
+
const chunks = [];
|
|
5684
|
+
let level = 0;
|
|
5685
|
+
const stack = [{
|
|
5686
|
+
parent: { "": rootProcessed },
|
|
5687
|
+
key: "",
|
|
5688
|
+
val: rootProcessed,
|
|
5689
|
+
isArray: Array.isArray(rootProcessed),
|
|
5690
|
+
keys: Array.isArray(rootProcessed) ? null : Object.keys(rootProcessed),
|
|
5691
|
+
index: 0,
|
|
5692
|
+
first: true
|
|
5693
|
+
}];
|
|
5694
|
+
const visited = new WeakSet([rootProcessed]);
|
|
5695
|
+
while (stack.length > 0) {
|
|
5696
|
+
const node = stack[stack.length - 1];
|
|
5697
|
+
if (node.index === 0) {
|
|
5698
|
+
chunks.push(node.isArray ? "[" : "{");
|
|
5699
|
+
level++;
|
|
5700
|
+
}
|
|
5701
|
+
let isDone = false;
|
|
5702
|
+
if (node.isArray) {
|
|
5703
|
+
if (node.index < node.val.length) {
|
|
5704
|
+
if (!node.first) chunks.push(",");
|
|
5705
|
+
if (space) chunks.push("\n" + space.repeat(level));
|
|
5706
|
+
const childRaw = node.val[node.index];
|
|
5707
|
+
const childVal = prepareVal(node.val, String(node.index), childRaw);
|
|
5708
|
+
if (isUnstringifiable(childVal)) {
|
|
5709
|
+
chunks.push("null");
|
|
5710
|
+
node.first = false;
|
|
5711
|
+
node.index++;
|
|
5712
|
+
} else {
|
|
5713
|
+
const isComplexObject = childVal !== null && typeof childVal === "object";
|
|
5714
|
+
const isNativeRaw = isRawJSON(childVal);
|
|
5715
|
+
if (isComplexObject && !isNativeRaw) {
|
|
5716
|
+
if (visited.has(childVal)) throw new TypeError("Converting circular structure to JSON");
|
|
5717
|
+
visited.add(childVal);
|
|
5718
|
+
stack.push({
|
|
5719
|
+
parent: node.val,
|
|
5720
|
+
key: String(node.index),
|
|
5721
|
+
val: childVal,
|
|
5722
|
+
isArray: Array.isArray(childVal),
|
|
5723
|
+
keys: Array.isArray(childVal) ? null : Object.keys(childVal),
|
|
5724
|
+
index: 0,
|
|
5725
|
+
first: true
|
|
5726
|
+
});
|
|
5727
|
+
node.first = false;
|
|
5728
|
+
node.index++;
|
|
5729
|
+
} else {
|
|
5730
|
+
chunks.push(originalStringify(childVal));
|
|
5731
|
+
node.first = false;
|
|
5732
|
+
node.index++;
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
} else isDone = true;
|
|
5736
|
+
} else {
|
|
5737
|
+
while (node.index < node.keys.length) {
|
|
5738
|
+
const k = node.keys[node.index++];
|
|
5739
|
+
if (propertyList && !propertyList.has(k)) continue;
|
|
5740
|
+
const childRaw = node.val[k];
|
|
5741
|
+
const childVal = prepareVal(node.val, k, childRaw);
|
|
5742
|
+
if (isUnstringifiable(childVal)) continue;
|
|
5743
|
+
if (!node.first) chunks.push(",");
|
|
5744
|
+
if (space) chunks.push("\n" + space.repeat(level) + originalStringify(k) + ": ");
|
|
5745
|
+
else chunks.push(originalStringify(k) + ":");
|
|
5746
|
+
const isComplexObject = childVal !== null && typeof childVal === "object";
|
|
5747
|
+
const isNativeRaw = isRawJSON(childVal);
|
|
5748
|
+
if (isComplexObject && !isNativeRaw) {
|
|
5749
|
+
if (visited.has(childVal)) throw new TypeError("Converting circular structure to JSON");
|
|
5750
|
+
visited.add(childVal);
|
|
5751
|
+
stack.push({
|
|
5752
|
+
parent: node.val,
|
|
5753
|
+
key: k,
|
|
5754
|
+
val: childVal,
|
|
5755
|
+
isArray: Array.isArray(childVal),
|
|
5756
|
+
keys: Array.isArray(childVal) ? null : Object.keys(childVal),
|
|
5757
|
+
index: 0,
|
|
5758
|
+
first: true
|
|
5759
|
+
});
|
|
5760
|
+
node.first = false;
|
|
5761
|
+
break;
|
|
5762
|
+
} else {
|
|
5763
|
+
chunks.push(originalStringify(childVal));
|
|
5764
|
+
node.first = false;
|
|
5765
|
+
}
|
|
5228
5766
|
}
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5767
|
+
if (node.index >= node.keys.length && stack[stack.length - 1] === node) isDone = true;
|
|
5768
|
+
}
|
|
5769
|
+
if (isDone) {
|
|
5770
|
+
level--;
|
|
5771
|
+
if (!node.first && space) chunks.push("\n" + space.repeat(level));
|
|
5772
|
+
chunks.push(node.isArray ? "]" : "}");
|
|
5773
|
+
visited.delete(node.val);
|
|
5774
|
+
stack.pop();
|
|
5232
5775
|
}
|
|
5233
|
-
return {
|
|
5234
|
-
url: pr.url,
|
|
5235
|
-
posted: comments.length,
|
|
5236
|
-
updated: 0,
|
|
5237
|
-
review: "created",
|
|
5238
|
-
skipped
|
|
5239
|
-
};
|
|
5240
5776
|
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5777
|
+
return chunks.join("");
|
|
5778
|
+
};
|
|
5779
|
+
/**
|
|
5780
|
+
* Converts a JavaScript value to a JSON string.
|
|
5781
|
+
*
|
|
5782
|
+
* Supports serialization of BigInt values using two strategies:
|
|
5783
|
+
* 1. Custom format "123n" → "123" (universal fallback)
|
|
5784
|
+
* 2. Native JSON.rawJSON() (Node.js 22+, fastest) when available
|
|
5785
|
+
*
|
|
5786
|
+
* All other values are serialized exactly like native JSON.stringify().
|
|
5787
|
+
*
|
|
5788
|
+
* @param {*} value The value to convert to a JSON string.
|
|
5789
|
+
* @param {Replacer | Array<string | number> | null} [replacer]
|
|
5790
|
+
* A function that alters the behavior of the stringification process,
|
|
5791
|
+
* or an array of strings/numbers to indicate properties to exclude.
|
|
5792
|
+
* @param {string | number} [space]
|
|
5793
|
+
* A string or number to specify indentation or pretty-printing.
|
|
5794
|
+
* @returns {string} The JSON string representation.
|
|
5795
|
+
*/
|
|
5796
|
+
const JSONStringify = (value, replacer, space) => {
|
|
5245
5797
|
try {
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
if (
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
updated++;
|
|
5267
|
-
}
|
|
5268
|
-
for (const c of add) {
|
|
5269
|
-
await addToPendingReview(run, snap.root, pending.id, c);
|
|
5270
|
-
posted++;
|
|
5798
|
+
if ("rawJSON" in JSON) return originalStringify(value, (key, val) => {
|
|
5799
|
+
if (typeof val === "bigint") return JSON.rawJSON(val.toString());
|
|
5800
|
+
if (typeof replacer === "function") return replacer(key, val);
|
|
5801
|
+
if (Array.isArray(replacer) && replacer.includes(key)) return val;
|
|
5802
|
+
return val;
|
|
5803
|
+
}, space);
|
|
5804
|
+
if (!value) return originalStringify(value, replacer, space);
|
|
5805
|
+
return originalStringify(value, (key, val) => {
|
|
5806
|
+
if (typeof val === "string" && noiseValue.test(val)) return val.toString() + "n";
|
|
5807
|
+
if (typeof val === "bigint") return val.toString() + "n";
|
|
5808
|
+
if (typeof replacer === "function") return replacer(key, val);
|
|
5809
|
+
if (Array.isArray(replacer) && replacer.includes(key)) return val;
|
|
5810
|
+
return val;
|
|
5811
|
+
}, space).replace(bigIntsStringify, "$1$2$3").replace(noiseStringify, "$1$2$3");
|
|
5812
|
+
} catch (error) {
|
|
5813
|
+
if (error instanceof RangeError) {
|
|
5814
|
+
const convertedJSON = stringifyIteratively(value, replacer, space);
|
|
5815
|
+
if (convertedJSON === void 0) return void 0;
|
|
5816
|
+
if ("rawJSON" in JSON) return convertedJSON;
|
|
5817
|
+
return convertedJSON.replace(bigIntsStringify, "$1$2$3").replace(noiseStringify, "$1$2$3");
|
|
5271
5818
|
}
|
|
5272
|
-
|
|
5273
|
-
if (updated === 0 && posted === 0) throw e;
|
|
5274
|
-
const detail = e instanceof Error ? e.message : String(e);
|
|
5275
|
-
throw new GithubError(`updated ${updated} and added ${posted} comments in the pending review, then ${detail}`, 502);
|
|
5819
|
+
throw error;
|
|
5276
5820
|
}
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
}
|
|
5298
|
-
|
|
5299
|
-
viewer{login}
|
|
5300
|
-
repository(owner:$owner,name:$repo){pullRequest(number:$number){reviews(last:20,states:[PENDING]){nodes{id author{login}}}}}
|
|
5301
|
-
}`;
|
|
5821
|
+
};
|
|
5822
|
+
const featureCache = /* @__PURE__ */ new Map();
|
|
5823
|
+
/**
|
|
5824
|
+
* Detects if the current JSON.parse implementation supports the context.source feature.
|
|
5825
|
+
*
|
|
5826
|
+
* Uses toString() fingerprinting to cache results and automatically detect runtime
|
|
5827
|
+
* replacements of JSON.parse (polyfills, mocks, etc.).
|
|
5828
|
+
*
|
|
5829
|
+
* @returns {boolean} true if context.source is supported, false otherwise.
|
|
5830
|
+
*/
|
|
5831
|
+
const isContextSourceSupported = () => {
|
|
5832
|
+
const parseFingerprint = JSON.parse.toString();
|
|
5833
|
+
if (featureCache.has(parseFingerprint)) return featureCache.get(parseFingerprint);
|
|
5834
|
+
try {
|
|
5835
|
+
const result = JSON.parse("1", (_, __, context) => !!context?.source && context.source === "1");
|
|
5836
|
+
featureCache.set(parseFingerprint, result);
|
|
5837
|
+
return result;
|
|
5838
|
+
} catch {
|
|
5839
|
+
featureCache.set(parseFingerprint, false);
|
|
5840
|
+
return false;
|
|
5841
|
+
}
|
|
5842
|
+
};
|
|
5302
5843
|
/**
|
|
5303
|
-
*
|
|
5304
|
-
*
|
|
5305
|
-
*
|
|
5844
|
+
* Reviver function that converts custom-format BigInt strings back to BigInt values.
|
|
5845
|
+
* Also handles "noise" strings that accidentally match the BigInt format.
|
|
5846
|
+
*
|
|
5847
|
+
* @param {string | number | undefined} key The object key.
|
|
5848
|
+
* @param {*} value The value being parsed.
|
|
5849
|
+
* @param {object} [context] Parse context (if supported by JSON.parse).
|
|
5850
|
+
* @param {Reviver} [userReviver] User's custom reviver function.
|
|
5851
|
+
* @returns {any} The transformed value.
|
|
5306
5852
|
*/
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5853
|
+
const convertMarkedBigIntsReviver = (key, value, context, userReviver) => {
|
|
5854
|
+
if (typeof value === "string" && customFormat.test(value)) return BigInt(value.slice(0, -1));
|
|
5855
|
+
if (typeof value === "string" && noiseValue.test(value)) return value.slice(0, -1);
|
|
5856
|
+
if (!(typeof userReviver === "function")) return value;
|
|
5857
|
+
return userReviver(key, value, context);
|
|
5858
|
+
};
|
|
5859
|
+
/**
|
|
5860
|
+
* Fast JSON.parse implementation (~2x faster than classic fallback).
|
|
5861
|
+
* Uses JSON.parse's context.source feature to detect integers and convert
|
|
5862
|
+
* large numbers directly to BigInt without string manipulation.
|
|
5863
|
+
*
|
|
5864
|
+
* Does not support legacy custom format from v1 of this library.
|
|
5865
|
+
*
|
|
5866
|
+
* @param {string} text JSON string to parse.
|
|
5867
|
+
* @param {Reviver} [reviver] Transform function to apply to each value.
|
|
5868
|
+
* @returns {any} Parsed JavaScript value.
|
|
5869
|
+
*/
|
|
5870
|
+
const JSONParseV2 = (text, reviver) => {
|
|
5871
|
+
return JSON.parse(text, (key, value, context) => {
|
|
5872
|
+
const isNumber = typeof value === "number";
|
|
5873
|
+
const isOutOfBounds = value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER;
|
|
5874
|
+
const isBigNumber = isNumber && isOutOfBounds;
|
|
5875
|
+
const isInt = context && intRegex.test(context.source);
|
|
5876
|
+
if (isBigNumber && isInt) return BigInt(context.source);
|
|
5877
|
+
if (!(typeof reviver === "function")) return value;
|
|
5878
|
+
return reviver(key, value, context);
|
|
5313
5879
|
});
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
const
|
|
5319
|
-
repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100,after:$after){
|
|
5320
|
-
pageInfo{hasNextPage endCursor}
|
|
5321
|
-
nodes{path line startLine diffSide subjectType comments(first:1){nodes{id body pullRequestReview{id}}}}
|
|
5322
|
-
}}}
|
|
5323
|
-
}`;
|
|
5880
|
+
};
|
|
5881
|
+
const MAX_INT = Number.MAX_SAFE_INTEGER.toString();
|
|
5882
|
+
const MAX_DIGITS = MAX_INT.length;
|
|
5883
|
+
const stringsOrLargeNumbers = /"(?:[^"\\]|\\.)*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g;
|
|
5884
|
+
const noiseValueWithQuotes = /^"-?\d+n+"$/;
|
|
5324
5885
|
/**
|
|
5325
|
-
*
|
|
5326
|
-
*
|
|
5327
|
-
*
|
|
5328
|
-
*
|
|
5886
|
+
* Iteratively traverses the parsed object bottom-up (post-order),
|
|
5887
|
+
* emulating the native JSON.parse reviver behavior.
|
|
5888
|
+
* This avoids Call Stack overflows (RangeError) on deeply nested structures.
|
|
5889
|
+
*
|
|
5890
|
+
* @param {any} parsed The natively parsed JSON object.
|
|
5891
|
+
* @param {Reviver} [userReviver] User's custom reviver function.
|
|
5892
|
+
* @returns {any} The fully processed object.
|
|
5329
5893
|
*/
|
|
5330
|
-
|
|
5331
|
-
const
|
|
5332
|
-
const
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
}
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5894
|
+
const applyReviverIteratively = (parsed, userReviver) => {
|
|
5895
|
+
const rootHolder = { "": parsed };
|
|
5896
|
+
const stack = [{
|
|
5897
|
+
parent: rootHolder,
|
|
5898
|
+
key: "",
|
|
5899
|
+
visited: false
|
|
5900
|
+
}];
|
|
5901
|
+
while (stack.length > 0) {
|
|
5902
|
+
const node = stack[stack.length - 1];
|
|
5903
|
+
if (!node.visited) {
|
|
5904
|
+
node.visited = true;
|
|
5905
|
+
const value = node.parent[node.key];
|
|
5906
|
+
if (value !== null && typeof value === "object") {
|
|
5907
|
+
const keys = Object.keys(value);
|
|
5908
|
+
for (let i = keys.length - 1; i >= 0; i--) stack.push({
|
|
5909
|
+
parent: value,
|
|
5910
|
+
key: keys[i],
|
|
5911
|
+
visited: false
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5914
|
+
} else {
|
|
5915
|
+
const { parent, key } = node;
|
|
5916
|
+
let value = parent[key];
|
|
5917
|
+
if (typeof value === "string") {
|
|
5918
|
+
if (customFormat.test(value)) value = BigInt(value.slice(0, -1));
|
|
5919
|
+
else if (noiseValue.test(value)) value = value.slice(0, -1);
|
|
5920
|
+
}
|
|
5921
|
+
if (typeof userReviver === "function") value = userReviver.call(parent, key, value);
|
|
5922
|
+
if (value === void 0) delete parent[key];
|
|
5923
|
+
else parent[key] = value;
|
|
5924
|
+
stack.pop();
|
|
5356
5925
|
}
|
|
5357
|
-
if (!threads.pageInfo.hasNextPage) return by;
|
|
5358
|
-
if (!threads.pageInfo.endCursor || threads.pageInfo.endCursor === after) throw new GithubError("cannot read the next page of the pending review", 502);
|
|
5359
|
-
after = threads.pageInfo.endCursor;
|
|
5360
5926
|
}
|
|
5361
|
-
|
|
5362
|
-
}
|
|
5363
|
-
/** The anchor key of a thread already in the review, or null for one GitHub reports without a position. */
|
|
5364
|
-
function pendingKey(t) {
|
|
5365
|
-
const path = t.path;
|
|
5366
|
-
if (t.subjectType === "FILE") return anchorKey({
|
|
5367
|
-
path,
|
|
5368
|
-
subject_type: "file",
|
|
5369
|
-
body: ""
|
|
5370
|
-
});
|
|
5371
|
-
if (t.line == null) return null;
|
|
5372
|
-
return anchorKey({
|
|
5373
|
-
path,
|
|
5374
|
-
side: t.diffSide === "LEFT" ? "LEFT" : "RIGHT",
|
|
5375
|
-
line: t.line,
|
|
5376
|
-
start_line: t.startLine ?? void 0,
|
|
5377
|
-
body: ""
|
|
5378
|
-
});
|
|
5379
|
-
}
|
|
5380
|
-
const UPDATE_COMMENT = `mutation($input:UpdatePullRequestReviewCommentInput!){updatePullRequestReviewComment(input:$input){pullRequestReviewComment{id}}}`;
|
|
5927
|
+
return rootHolder[""];
|
|
5928
|
+
};
|
|
5381
5929
|
/**
|
|
5382
|
-
*
|
|
5383
|
-
*
|
|
5930
|
+
* Pre-processes the JSON string to mark large numbers with an 'n' suffix.
|
|
5931
|
+
*
|
|
5932
|
+
* @param {string} text The raw JSON string.
|
|
5933
|
+
* @returns {string} The serialized string with marked BigInts.
|
|
5384
5934
|
*/
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
"
|
|
5388
|
-
"
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
"
|
|
5393
|
-
], {
|
|
5394
|
-
cwd,
|
|
5395
|
-
input: JSON.stringify(review)
|
|
5935
|
+
const serializeBigInts = (text) => {
|
|
5936
|
+
return text.replace(stringsOrLargeNumbers, (match, digits, fractional, exponential) => {
|
|
5937
|
+
const isString = match[0] === "\"";
|
|
5938
|
+
if (isString && noiseValueWithQuotes.test(match)) return match.substring(0, match.length - 1) + "n\"";
|
|
5939
|
+
const hasFractionalOrExponential = fractional || exponential;
|
|
5940
|
+
const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS || digits.length === MAX_DIGITS && digits <= MAX_INT);
|
|
5941
|
+
if (isString || hasFractionalOrExponential || isLessThanMaxSafeInt) return match;
|
|
5942
|
+
return "\"" + match + "n\"";
|
|
5396
5943
|
});
|
|
5397
|
-
|
|
5944
|
+
};
|
|
5945
|
+
/**
|
|
5946
|
+
* Converts a JSON string into a JavaScript value.
|
|
5947
|
+
*
|
|
5948
|
+
* Supports parsing of large integers using two strategies:
|
|
5949
|
+
* 1. Classic fallback: Marks large numbers with "123n" format, then converts to BigInt
|
|
5950
|
+
* 2. Fast path (JSONParseV2): Uses context.source feature (~2x faster) when available
|
|
5951
|
+
*
|
|
5952
|
+
* All other JSON values are parsed exactly like native JSON.parse().
|
|
5953
|
+
*
|
|
5954
|
+
* @param {string} text A valid JSON string.
|
|
5955
|
+
* @param {Reviver} [reviver]
|
|
5956
|
+
* A function that transforms the results. This function is called for each member
|
|
5957
|
+
* of the object. If a member contains nested objects, the nested objects are
|
|
5958
|
+
* transformed before the parent object is.
|
|
5959
|
+
* @returns {any} The parsed JavaScript value.
|
|
5960
|
+
* @throws {SyntaxError} If text is not valid JSON.
|
|
5961
|
+
*/
|
|
5962
|
+
const JSONParse = (text, reviver) => {
|
|
5963
|
+
if (!text) return originalParse(text, reviver);
|
|
5398
5964
|
try {
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5965
|
+
if (isContextSourceSupported()) return JSONParseV2(text, reviver);
|
|
5966
|
+
const serializedData = serializeBigInts(text);
|
|
5967
|
+
return originalParse(serializedData, (key, value, context) => convertMarkedBigIntsReviver(key, value, context, reviver));
|
|
5968
|
+
} catch (error) {
|
|
5969
|
+
if (error instanceof RangeError) {
|
|
5970
|
+
const serializedData = serializeBigInts(text);
|
|
5971
|
+
const parsed = originalParse(serializedData);
|
|
5972
|
+
return applyReviverIteratively(parsed, reviver);
|
|
5973
|
+
}
|
|
5974
|
+
throw error;
|
|
5402
5975
|
}
|
|
5403
|
-
|
|
5404
|
-
|
|
5976
|
+
};
|
|
5977
|
+
//#endregion
|
|
5978
|
+
//#region node_modules/@octokit/request-error/dist-src/index.js
|
|
5979
|
+
var RequestError$1 = class extends Error {
|
|
5980
|
+
name;
|
|
5981
|
+
/**
|
|
5982
|
+
* http status code
|
|
5983
|
+
*/
|
|
5984
|
+
status;
|
|
5985
|
+
/**
|
|
5986
|
+
* Request options that lead to the error.
|
|
5987
|
+
*/
|
|
5988
|
+
request;
|
|
5989
|
+
/**
|
|
5990
|
+
* Response object if a response was received
|
|
5991
|
+
*/
|
|
5992
|
+
response;
|
|
5993
|
+
constructor(message, statusCode, options) {
|
|
5994
|
+
super(message, { cause: options.cause });
|
|
5995
|
+
this.name = "HttpError";
|
|
5996
|
+
this.status = Number.parseInt(statusCode);
|
|
5997
|
+
if (Number.isNaN(this.status)) this.status = 0;
|
|
5998
|
+
/* v8 ignore else -- @preserve -- Bug with vitest coverage where it sees an else branch that doesn't exist */
|
|
5999
|
+
if ("response" in options) this.response = options.response;
|
|
6000
|
+
const requestCopy = Object.assign({}, options.request);
|
|
6001
|
+
if (options.request.headers.authorization) requestCopy.headers = Object.assign({}, options.request.headers, { authorization: options.request.headers.authorization.replace(/(?<! ) .*$/, " [REDACTED]") });
|
|
6002
|
+
requestCopy.url = requestCopy.url.replace(/\bclient_secret=\w+/g, "client_secret=[REDACTED]").replace(/\baccess_token=\w+/g, "access_token=[REDACTED]");
|
|
6003
|
+
this.request = requestCopy;
|
|
6004
|
+
}
|
|
6005
|
+
};
|
|
6006
|
+
//#endregion
|
|
6007
|
+
//#region node_modules/@octokit/request/dist-bundle/index.js
|
|
6008
|
+
var defaults_default = { headers: { "user-agent": `octokit-request.js/10.0.16 ${getUserAgent()}` } };
|
|
6009
|
+
function isPlainObject$1(value) {
|
|
6010
|
+
if (typeof value !== "object" || value === null) return false;
|
|
6011
|
+
if (Object.prototype.toString.call(value) !== "[object Object]") return false;
|
|
6012
|
+
const proto = Object.getPrototypeOf(value);
|
|
6013
|
+
if (proto === null) return true;
|
|
6014
|
+
const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
|
|
6015
|
+
return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value);
|
|
6016
|
+
}
|
|
6017
|
+
var noop$1 = () => "";
|
|
6018
|
+
async function fetchWrapper(requestOptions) {
|
|
6019
|
+
const fetch = requestOptions.request?.fetch || globalThis.fetch;
|
|
6020
|
+
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");
|
|
6021
|
+
const log = requestOptions.request?.log || console;
|
|
6022
|
+
const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
|
|
6023
|
+
const body = isPlainObject$1(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
|
|
6024
|
+
const requestHeaders = Object.fromEntries(Object.entries(requestOptions.headers).map(([name, value]) => [name, String(value)]));
|
|
6025
|
+
let fetchResponse;
|
|
6026
|
+
try {
|
|
6027
|
+
fetchResponse = await fetch(requestOptions.url, {
|
|
6028
|
+
method: requestOptions.method,
|
|
6029
|
+
body,
|
|
6030
|
+
redirect: requestOptions.request?.redirect,
|
|
6031
|
+
headers: requestHeaders,
|
|
6032
|
+
signal: requestOptions.request?.signal,
|
|
6033
|
+
...requestOptions.body && { duplex: "half" }
|
|
6034
|
+
});
|
|
6035
|
+
} catch (error) {
|
|
6036
|
+
let message = "Unknown Error";
|
|
6037
|
+
if (error instanceof Error) {
|
|
6038
|
+
if (error.name === "AbortError") {
|
|
6039
|
+
error.status = 500;
|
|
6040
|
+
throw error;
|
|
6041
|
+
}
|
|
6042
|
+
message = error.message;
|
|
6043
|
+
if (error.name === "TypeError" && "cause" in error) {
|
|
6044
|
+
if (error.cause instanceof Error) message = error.cause.message;
|
|
6045
|
+
else if (typeof error.cause === "string") message = error.cause;
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
6048
|
+
const requestError = new RequestError$1(message, 500, { request: requestOptions });
|
|
6049
|
+
requestError.cause = error;
|
|
6050
|
+
throw requestError;
|
|
6051
|
+
}
|
|
6052
|
+
const status = fetchResponse.status;
|
|
6053
|
+
const url = fetchResponse.url;
|
|
6054
|
+
const responseHeaders = {};
|
|
6055
|
+
for (const [key, value] of fetchResponse.headers) responseHeaders[key] = value;
|
|
6056
|
+
const octokitResponse = {
|
|
6057
|
+
url,
|
|
6058
|
+
status,
|
|
6059
|
+
headers: responseHeaders,
|
|
6060
|
+
data: ""
|
|
6061
|
+
};
|
|
6062
|
+
if ("deprecation" in responseHeaders) {
|
|
6063
|
+
const matches = responseHeaders.link && responseHeaders.link.match(/<([^<>]+)>; rel="deprecation"/);
|
|
6064
|
+
const deprecationLink = matches && matches.pop();
|
|
6065
|
+
log.warn(`[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}`);
|
|
6066
|
+
}
|
|
6067
|
+
if (status === 204 || status === 205) return octokitResponse;
|
|
6068
|
+
if (requestOptions.method === "HEAD") {
|
|
6069
|
+
if (status < 400) return octokitResponse;
|
|
6070
|
+
throw new RequestError$1(fetchResponse.statusText, status, {
|
|
6071
|
+
response: octokitResponse,
|
|
6072
|
+
request: requestOptions
|
|
6073
|
+
});
|
|
6074
|
+
}
|
|
6075
|
+
if (status === 304) {
|
|
6076
|
+
octokitResponse.data = await getResponseData(fetchResponse);
|
|
6077
|
+
throw new RequestError$1("Not modified", status, {
|
|
6078
|
+
response: octokitResponse,
|
|
6079
|
+
request: requestOptions
|
|
6080
|
+
});
|
|
6081
|
+
}
|
|
6082
|
+
if (status >= 400) {
|
|
6083
|
+
octokitResponse.data = await getResponseData(fetchResponse);
|
|
6084
|
+
throw new RequestError$1(toErrorMessage(octokitResponse.data), status, {
|
|
6085
|
+
response: octokitResponse,
|
|
6086
|
+
request: requestOptions
|
|
6087
|
+
});
|
|
6088
|
+
}
|
|
6089
|
+
octokitResponse.data = parseSuccessResponseBody ? await getResponseData(fetchResponse) : fetchResponse.body;
|
|
6090
|
+
return octokitResponse;
|
|
5405
6091
|
}
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
const
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
6092
|
+
async function getResponseData(response) {
|
|
6093
|
+
const contentType = response.headers.get("content-type");
|
|
6094
|
+
if (!contentType) return response.text().catch(noop$1);
|
|
6095
|
+
const mimetype = parse$1(contentType);
|
|
6096
|
+
if (isJSONResponse(mimetype)) {
|
|
6097
|
+
let text = "";
|
|
6098
|
+
try {
|
|
6099
|
+
text = await response.text();
|
|
6100
|
+
return JSONParse(text);
|
|
6101
|
+
} catch (err) {
|
|
6102
|
+
return text;
|
|
6103
|
+
}
|
|
6104
|
+
} else if (mimetype.type.startsWith("text/") || mimetype.parameters.charset?.toLowerCase() === "utf-8" && mimetype.type !== "application/octet-stream") return response.text().catch(noop$1);
|
|
6105
|
+
else return response.arrayBuffer().catch(
|
|
6106
|
+
/* v8 ignore next -- @preserve */
|
|
6107
|
+
() => /* @__PURE__ */ new ArrayBuffer(0)
|
|
6108
|
+
);
|
|
6109
|
+
}
|
|
6110
|
+
function isJSONResponse(mimetype) {
|
|
6111
|
+
return mimetype.type === "application/json" || mimetype.type === "application/scim+json";
|
|
6112
|
+
}
|
|
6113
|
+
function toErrorMessage(data) {
|
|
6114
|
+
if (typeof data === "string") return data;
|
|
6115
|
+
if (data instanceof ArrayBuffer) return "Unknown error";
|
|
6116
|
+
if (typeof data === "object" && data !== null && "message" in data) {
|
|
6117
|
+
const objectData = data;
|
|
6118
|
+
const suffix = "documentation_url" in objectData ? ` - ${objectData.documentation_url}` : "";
|
|
6119
|
+
return Array.isArray(objectData.errors) ? `${objectData.message}: ${objectData.errors.map((v) => JSON.stringify(v)).join(", ")}${suffix}` : `${objectData.message}${suffix}`;
|
|
6120
|
+
}
|
|
6121
|
+
return `Unknown error: ${JSON.stringify(data)}`;
|
|
6122
|
+
}
|
|
6123
|
+
function withDefaults$1(oldEndpoint, newDefaults) {
|
|
6124
|
+
const endpoint2 = oldEndpoint.defaults(newDefaults);
|
|
6125
|
+
const newApi = function(route, parameters) {
|
|
6126
|
+
const endpointOptions = endpoint2.merge(route, parameters);
|
|
6127
|
+
if (!endpointOptions.request || !endpointOptions.request.hook) return fetchWrapper(endpoint2.parse(endpointOptions));
|
|
6128
|
+
const request2 = (route2, parameters2) => {
|
|
6129
|
+
return fetchWrapper(endpoint2.parse(endpoint2.merge(route2, parameters2)));
|
|
6130
|
+
};
|
|
6131
|
+
Object.assign(request2, {
|
|
6132
|
+
endpoint: endpoint2,
|
|
6133
|
+
defaults: withDefaults$1.bind(null, endpoint2)
|
|
6134
|
+
});
|
|
6135
|
+
return endpointOptions.request.hook(request2, endpointOptions);
|
|
5413
6136
|
};
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
input.side = c.side;
|
|
5418
|
-
if (c.start_line != null) {
|
|
5419
|
-
input.startLine = c.start_line;
|
|
5420
|
-
input.startSide = c.start_side;
|
|
5421
|
-
}
|
|
5422
|
-
}
|
|
5423
|
-
await graphql(run, cwd, ADD_THREAD, { input });
|
|
5424
|
-
}
|
|
5425
|
-
/** Runs a GraphQL document through `gh api graphql` and returns its `data`. */
|
|
5426
|
-
async function graphql(run, cwd, query, variables) {
|
|
5427
|
-
const out = await run([
|
|
5428
|
-
"api",
|
|
5429
|
-
"graphql",
|
|
5430
|
-
"--input",
|
|
5431
|
-
"-"
|
|
5432
|
-
], {
|
|
5433
|
-
cwd,
|
|
5434
|
-
input: JSON.stringify({
|
|
5435
|
-
query,
|
|
5436
|
-
variables
|
|
5437
|
-
})
|
|
6137
|
+
return Object.assign(newApi, {
|
|
6138
|
+
endpoint: endpoint2,
|
|
6139
|
+
defaults: withDefaults$1.bind(null, endpoint2)
|
|
5438
6140
|
});
|
|
5439
|
-
let body;
|
|
5440
|
-
try {
|
|
5441
|
-
body = JSON.parse(out);
|
|
5442
|
-
} catch {
|
|
5443
|
-
throw new GithubError(`unexpected output from gh api graphql: ${out.slice(0, 200)}`, 502);
|
|
5444
|
-
}
|
|
5445
|
-
if (body.data == null) throw new GithubError(`unexpected output from gh api graphql: ${out.slice(0, 200)}`, 502);
|
|
5446
|
-
return body.data;
|
|
5447
6141
|
}
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
6142
|
+
var request = withDefaults$1(endpoint, defaults_default);
|
|
6143
|
+
/* v8 ignore next -- @preserve */
|
|
6144
|
+
/* v8 ignore else -- @preserve */
|
|
6145
|
+
//#endregion
|
|
6146
|
+
//#region node_modules/@octokit/graphql/dist-bundle/index.js
|
|
6147
|
+
var VERSION$1 = "0.0.0-development";
|
|
6148
|
+
function _buildMessageForResponseErrors(data) {
|
|
6149
|
+
return `Request failed due to following response errors:
|
|
6150
|
+
` + data.errors.map((e) => ` - ${e.message}`).join("\n");
|
|
6151
|
+
}
|
|
6152
|
+
var GraphqlResponseError = class extends Error {
|
|
6153
|
+
constructor(request2, headers, response) {
|
|
6154
|
+
super(_buildMessageForResponseErrors(response));
|
|
6155
|
+
this.request = request2;
|
|
6156
|
+
this.headers = headers;
|
|
6157
|
+
this.response = response;
|
|
6158
|
+
this.errors = response.errors;
|
|
6159
|
+
this.data = response.data;
|
|
6160
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
6161
|
+
}
|
|
6162
|
+
request;
|
|
6163
|
+
headers;
|
|
6164
|
+
response;
|
|
6165
|
+
name = "GraphqlResponseError";
|
|
6166
|
+
errors;
|
|
6167
|
+
data;
|
|
6168
|
+
};
|
|
6169
|
+
var NON_VARIABLE_OPTIONS = [
|
|
6170
|
+
"method",
|
|
6171
|
+
"baseUrl",
|
|
6172
|
+
"url",
|
|
6173
|
+
"headers",
|
|
6174
|
+
"request",
|
|
6175
|
+
"query",
|
|
6176
|
+
"mediaType",
|
|
6177
|
+
"operationName"
|
|
6178
|
+
];
|
|
6179
|
+
var FORBIDDEN_VARIABLE_OPTIONS = [
|
|
6180
|
+
"query",
|
|
6181
|
+
"method",
|
|
6182
|
+
"url"
|
|
6183
|
+
];
|
|
6184
|
+
var GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/;
|
|
6185
|
+
function graphql(request2, query, options) {
|
|
6186
|
+
if (options) {
|
|
6187
|
+
if (typeof query === "string" && "query" in options) return Promise.reject(/* @__PURE__ */ new Error(`[@octokit/graphql] "query" cannot be used as variable name`));
|
|
6188
|
+
for (const key in options) {
|
|
6189
|
+
if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) continue;
|
|
6190
|
+
return Promise.reject(/* @__PURE__ */ new Error(`[@octokit/graphql] "${key}" cannot be used as variable name`));
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
const parsedOptions = typeof query === "string" ? Object.assign({ query }, options) : query;
|
|
6194
|
+
const requestOptions = Object.keys(parsedOptions).reduce((result, key) => {
|
|
6195
|
+
if (NON_VARIABLE_OPTIONS.includes(key)) {
|
|
6196
|
+
result[key] = parsedOptions[key];
|
|
6197
|
+
return result;
|
|
6198
|
+
}
|
|
6199
|
+
if (!result.variables) result.variables = {};
|
|
6200
|
+
result.variables[key] = parsedOptions[key];
|
|
6201
|
+
return result;
|
|
6202
|
+
}, {});
|
|
6203
|
+
const baseUrl = parsedOptions.baseUrl || request2.endpoint.DEFAULTS.baseUrl;
|
|
6204
|
+
if (GHES_V3_SUFFIX_REGEX.test(baseUrl)) requestOptions.url = baseUrl.replace(GHES_V3_SUFFIX_REGEX, "/api/graphql");
|
|
6205
|
+
return request2(requestOptions).then((response) => {
|
|
6206
|
+
if (response.data.errors) {
|
|
6207
|
+
const headers = {};
|
|
6208
|
+
for (const key of Object.keys(response.headers)) headers[key] = response.headers[key];
|
|
6209
|
+
throw new GraphqlResponseError(requestOptions, headers, response.data);
|
|
6210
|
+
}
|
|
6211
|
+
return response.data.data;
|
|
6212
|
+
});
|
|
5452
6213
|
}
|
|
5453
|
-
|
|
5454
|
-
const
|
|
5455
|
-
const
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
if (err.code === "ENOENT") return reject(new GithubError("gh is not installed; see https://cli.github.com"));
|
|
5462
|
-
const detail = (stderr || err.message).trim().split("\n")[0] ?? "";
|
|
5463
|
-
reject(new GithubError(`gh ${args[0]} ${args[1] ?? ""} failed: ${detail}`.trim(), args[0] === "api" ? 502 : 409));
|
|
6214
|
+
function withDefaults(request2, newDefaults) {
|
|
6215
|
+
const newRequest = request2.defaults(newDefaults);
|
|
6216
|
+
const newApi = (query, options) => {
|
|
6217
|
+
return graphql(newRequest, query, options);
|
|
6218
|
+
};
|
|
6219
|
+
return Object.assign(newApi, {
|
|
6220
|
+
defaults: withDefaults.bind(null, newRequest),
|
|
6221
|
+
endpoint: newRequest.endpoint
|
|
5464
6222
|
});
|
|
5465
|
-
|
|
5466
|
-
|
|
6223
|
+
}
|
|
6224
|
+
withDefaults(request, {
|
|
6225
|
+
headers: { "user-agent": `octokit-graphql.js/${VERSION$1} ${getUserAgent()}` },
|
|
6226
|
+
method: "POST",
|
|
6227
|
+
url: "/graphql"
|
|
5467
6228
|
});
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
* refused so a stray flag never reaches gh.
|
|
5473
|
-
*/
|
|
5474
|
-
async function viewPr(selector, cwd, run = runGh, timeoutMs) {
|
|
5475
|
-
return parsePullRequest(parseJson(await run([
|
|
5476
|
-
"pr",
|
|
5477
|
-
"view",
|
|
5478
|
-
...selector == null ? [] : [normalizeSelector(selector)],
|
|
5479
|
-
"--json",
|
|
5480
|
-
PR_FIELDS
|
|
5481
|
-
], {
|
|
5482
|
-
cwd,
|
|
5483
|
-
timeoutMs
|
|
5484
|
-
}), "view"));
|
|
5485
|
-
}
|
|
5486
|
-
/** Open pull requests whose head is the named branch in the named remote repository. */
|
|
5487
|
-
async function listPrsForHead(baseRepo, base, head, headRepo, cwd, run = runGh, timeoutMs) {
|
|
5488
|
-
const out = await run([
|
|
5489
|
-
"pr",
|
|
5490
|
-
"list",
|
|
5491
|
-
"--repo",
|
|
5492
|
-
baseRepo,
|
|
5493
|
-
"--base",
|
|
5494
|
-
base,
|
|
5495
|
-
"--head",
|
|
5496
|
-
head,
|
|
5497
|
-
"--state",
|
|
5498
|
-
"open",
|
|
5499
|
-
"--limit",
|
|
5500
|
-
"100",
|
|
5501
|
-
"--json",
|
|
5502
|
-
PR_FIELDS
|
|
5503
|
-
], {
|
|
5504
|
-
cwd,
|
|
5505
|
-
timeoutMs
|
|
5506
|
-
});
|
|
5507
|
-
const values = parseJson(out, "list");
|
|
5508
|
-
if (!Array.isArray(values)) throw new GithubError(`unexpected output from gh pr list: ${out.slice(0, 200)}`, 502);
|
|
5509
|
-
return values.flatMap((value) => {
|
|
5510
|
-
const candidate = value;
|
|
5511
|
-
const owner = candidate?.headRepositoryOwner?.login;
|
|
5512
|
-
const name = candidate?.headRepository?.name;
|
|
5513
|
-
if (typeof owner !== "string" || typeof name !== "string") throw new GithubError(`unexpected output from gh pr list: ${out.slice(0, 200)}`, 502);
|
|
5514
|
-
if (`${owner}/${name}`.toLowerCase() !== headRepo.toLowerCase()) return [];
|
|
5515
|
-
return [parsePullRequest(value)];
|
|
6229
|
+
function withCustomRequest(customRequest) {
|
|
6230
|
+
return withDefaults(customRequest, {
|
|
6231
|
+
method: "POST",
|
|
6232
|
+
url: "/graphql"
|
|
5516
6233
|
});
|
|
5517
6234
|
}
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
6235
|
+
/* v8 ignore if -- @preserve */
|
|
6236
|
+
//#endregion
|
|
6237
|
+
//#region node_modules/@octokit/auth-token/dist-bundle/index.js
|
|
6238
|
+
var b64url = "(?:[a-zA-Z0-9_-]+)";
|
|
6239
|
+
var sep$1 = "\\.";
|
|
6240
|
+
var jwtRE = new RegExp(`^${b64url}${sep$1}${b64url}${sep$1}${b64url}$`);
|
|
6241
|
+
var isJWT = jwtRE.test.bind(jwtRE);
|
|
6242
|
+
async function auth(token) {
|
|
6243
|
+
const isApp = isJWT(token);
|
|
6244
|
+
const isInstallation = token.startsWith("v1.") || token.startsWith("ghs_");
|
|
6245
|
+
const isUserToServer = token.startsWith("ghu_");
|
|
6246
|
+
return {
|
|
6247
|
+
type: "token",
|
|
6248
|
+
token,
|
|
6249
|
+
tokenType: isApp ? "app" : isInstallation ? "installation" : isUserToServer ? "user-to-server" : "oauth"
|
|
6250
|
+
};
|
|
5523
6251
|
}
|
|
5524
|
-
function
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
6252
|
+
function withAuthorizationPrefix(token) {
|
|
6253
|
+
if (token.split(/\./).length === 3) return `bearer ${token}`;
|
|
6254
|
+
return `token ${token}`;
|
|
6255
|
+
}
|
|
6256
|
+
async function hook(token, request, route, parameters) {
|
|
6257
|
+
const endpoint = request.endpoint.merge(route, parameters);
|
|
6258
|
+
endpoint.headers.authorization = withAuthorizationPrefix(token);
|
|
6259
|
+
return request(endpoint);
|
|
6260
|
+
}
|
|
6261
|
+
var createTokenAuth = function createTokenAuth2(token) {
|
|
6262
|
+
if (!token) throw new Error("[@octokit/auth-token] No token passed to createTokenAuth");
|
|
6263
|
+
if (typeof token !== "string") throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string");
|
|
6264
|
+
token = token.replace(/^(token|bearer) +/i, "");
|
|
6265
|
+
return Object.assign(auth.bind(null, token), { hook: hook.bind(null, token) });
|
|
6266
|
+
};
|
|
6267
|
+
//#endregion
|
|
6268
|
+
//#region node_modules/@octokit/core/dist-src/version.js
|
|
6269
|
+
const VERSION = "7.0.8";
|
|
6270
|
+
//#endregion
|
|
6271
|
+
//#region node_modules/@octokit/core/dist-src/index.js
|
|
6272
|
+
const noop = () => {};
|
|
6273
|
+
const consoleWarn = console.warn.bind(console);
|
|
6274
|
+
const consoleError = console.error.bind(console);
|
|
6275
|
+
function createLogger(logger = {}) {
|
|
6276
|
+
if (typeof logger.debug !== "function") logger.debug = noop;
|
|
6277
|
+
if (typeof logger.info !== "function") logger.info = noop;
|
|
6278
|
+
if (typeof logger.warn !== "function") logger.warn = consoleWarn;
|
|
6279
|
+
if (typeof logger.error !== "function") logger.error = consoleError;
|
|
6280
|
+
return logger;
|
|
6281
|
+
}
|
|
6282
|
+
const userAgentTrail = `octokit-core.js/${VERSION} ${getUserAgent()}`;
|
|
6283
|
+
var Octokit = class {
|
|
6284
|
+
static VERSION = VERSION;
|
|
6285
|
+
static defaults(defaults) {
|
|
6286
|
+
const OctokitWithDefaults = class extends this {
|
|
6287
|
+
constructor(...args) {
|
|
6288
|
+
const options = args[0] || {};
|
|
6289
|
+
if (typeof defaults === "function") {
|
|
6290
|
+
super(defaults(options));
|
|
6291
|
+
return;
|
|
6292
|
+
}
|
|
6293
|
+
super(Object.assign({}, defaults, options, options.userAgent && defaults.userAgent ? { userAgent: `${options.userAgent} ${defaults.userAgent}` } : null));
|
|
6294
|
+
}
|
|
6295
|
+
};
|
|
6296
|
+
return OctokitWithDefaults;
|
|
6297
|
+
}
|
|
6298
|
+
static plugins = [];
|
|
6299
|
+
/**
|
|
6300
|
+
* Attach a plugin (or many) to your Octokit instance.
|
|
6301
|
+
*
|
|
6302
|
+
* @example
|
|
6303
|
+
* const API = Octokit.plugin(plugin1, plugin2, plugin3, ...)
|
|
6304
|
+
*/
|
|
6305
|
+
static plugin(...newPlugins) {
|
|
6306
|
+
const currentPlugins = this.plugins;
|
|
6307
|
+
const NewOctokit = class extends this {
|
|
6308
|
+
static plugins = currentPlugins.concat(newPlugins.filter((plugin) => !currentPlugins.includes(plugin)));
|
|
6309
|
+
};
|
|
6310
|
+
return NewOctokit;
|
|
6311
|
+
}
|
|
6312
|
+
constructor(options = {}) {
|
|
6313
|
+
const hook = new before_after_hook_default.Collection();
|
|
6314
|
+
const requestDefaults = {
|
|
6315
|
+
baseUrl: request.endpoint.DEFAULTS.baseUrl,
|
|
6316
|
+
headers: {},
|
|
6317
|
+
request: Object.assign({}, options.request, { hook: hook.bind(null, "request") }),
|
|
6318
|
+
mediaType: {
|
|
6319
|
+
previews: [],
|
|
6320
|
+
format: ""
|
|
6321
|
+
}
|
|
6322
|
+
};
|
|
6323
|
+
requestDefaults.headers["user-agent"] = options.userAgent ? `${options.userAgent} ${userAgentTrail}` : userAgentTrail;
|
|
6324
|
+
if (options.baseUrl) requestDefaults.baseUrl = options.baseUrl;
|
|
6325
|
+
if (options.previews) requestDefaults.mediaType.previews = options.previews;
|
|
6326
|
+
if (options.timeZone) requestDefaults.headers["time-zone"] = options.timeZone;
|
|
6327
|
+
this.request = request.defaults(requestDefaults);
|
|
6328
|
+
this.graphql = withCustomRequest(this.request).defaults(requestDefaults);
|
|
6329
|
+
this.log = createLogger(options.log);
|
|
6330
|
+
this.hook = hook;
|
|
6331
|
+
if (!options.authStrategy) {
|
|
6332
|
+
if (!options.auth) this.auth = async () => ({ type: "unauthenticated" });
|
|
6333
|
+
else {
|
|
6334
|
+
const auth = createTokenAuth(options.auth);
|
|
6335
|
+
hook.wrap("request", auth.hook);
|
|
6336
|
+
this.auth = auth;
|
|
6337
|
+
}
|
|
6338
|
+
} else {
|
|
6339
|
+
const { authStrategy, ...otherOptions } = options;
|
|
6340
|
+
const auth = authStrategy(Object.assign({
|
|
6341
|
+
request: this.request,
|
|
6342
|
+
log: this.log,
|
|
6343
|
+
octokit: this,
|
|
6344
|
+
octokitOptions: otherOptions
|
|
6345
|
+
}, options.auth));
|
|
6346
|
+
hook.wrap("request", auth.hook);
|
|
6347
|
+
this.auth = auth;
|
|
6348
|
+
}
|
|
6349
|
+
const classConstructor = this.constructor;
|
|
6350
|
+
for (let i = 0; i < classConstructor.plugins.length; ++i) Object.assign(this, classConstructor.plugins[i](this, options));
|
|
6351
|
+
}
|
|
6352
|
+
request;
|
|
6353
|
+
graphql;
|
|
6354
|
+
log;
|
|
6355
|
+
hook;
|
|
6356
|
+
auth;
|
|
6357
|
+
};
|
|
6358
|
+
//#endregion
|
|
6359
|
+
//#region src/server/github/client.ts
|
|
6360
|
+
/** A precondition the user can fix (no token, no PR, wrong mode, unpushed head): the route answers 4xx. */
|
|
6361
|
+
var GithubError = class extends Error {
|
|
6362
|
+
status;
|
|
6363
|
+
constructor(message, status = 409) {
|
|
6364
|
+
super(message);
|
|
6365
|
+
this.status = status;
|
|
6366
|
+
}
|
|
6367
|
+
};
|
|
6368
|
+
/** Said wherever a GitHub feature is asked for without a token. */
|
|
6369
|
+
const NO_TOKEN = "No GitHub token: set GITHUB_TOKEN or sign in with gh auth login";
|
|
6370
|
+
/**
|
|
6371
|
+
* `GITHUB_TOKEN`, then `GH_TOKEN`, then the token `gh` holds. Null when none of them has
|
|
6372
|
+
* one: diffle then runs without GitHub, the origin repository still shows and everything
|
|
6373
|
+
* else says why it is off.
|
|
6374
|
+
*/
|
|
6375
|
+
async function resolveToken(env = process.env, ghToken = ghAuthToken) {
|
|
6376
|
+
for (const source of ["GITHUB_TOKEN", "GH_TOKEN"]) {
|
|
6377
|
+
const token = env[source]?.trim();
|
|
6378
|
+
if (token) return {
|
|
6379
|
+
token,
|
|
6380
|
+
source
|
|
6381
|
+
};
|
|
5529
6382
|
}
|
|
6383
|
+
const token = await ghToken();
|
|
6384
|
+
return token ? {
|
|
6385
|
+
token,
|
|
6386
|
+
source: "gh auth token"
|
|
6387
|
+
} : null;
|
|
6388
|
+
}
|
|
6389
|
+
/** The only `gh` left: its stored token, or null when gh is missing, signed out, or slow. */
|
|
6390
|
+
function ghAuthToken() {
|
|
6391
|
+
return new Promise((resolve) => {
|
|
6392
|
+
execFile("gh", ["auth", "token"], {
|
|
6393
|
+
timeout: 5e3,
|
|
6394
|
+
windowsHide: true
|
|
6395
|
+
}, (err, stdout) => {
|
|
6396
|
+
const token = stdout?.trim();
|
|
6397
|
+
resolve(!err && token ? token : null);
|
|
6398
|
+
});
|
|
6399
|
+
});
|
|
5530
6400
|
}
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
6401
|
+
/** The real client, over `@octokit/core`. */
|
|
6402
|
+
function createGithubClient(token, { baseUrl, fetch: customFetch } = {}) {
|
|
6403
|
+
const octokit = new Octokit({
|
|
6404
|
+
auth: token,
|
|
6405
|
+
baseUrl: baseUrl || process.env.GITHUB_API_URL || void 0,
|
|
6406
|
+
userAgent: `diffle/${version$1}`,
|
|
6407
|
+
request: customFetch ? { fetch: customFetch } : {}
|
|
6408
|
+
});
|
|
6409
|
+
return { async graphql(query, variables, opts = {}) {
|
|
6410
|
+
const request = opts.timeoutMs ? { signal: AbortSignal.timeout(opts.timeoutMs) } : void 0;
|
|
6411
|
+
try {
|
|
6412
|
+
return await octokit.graphql(query, {
|
|
6413
|
+
...variables,
|
|
6414
|
+
request
|
|
6415
|
+
});
|
|
6416
|
+
} catch (e) {
|
|
6417
|
+
throw toGithubError(e, opts.timeoutMs);
|
|
6418
|
+
}
|
|
6419
|
+
} };
|
|
6420
|
+
}
|
|
6421
|
+
/**
|
|
6422
|
+
* GitHub's answer, as a `GithubError`. Something the user can fix (a rejected token, a missing
|
|
6423
|
+
* pull request) is 409; GitHub refusing the content (422) or not answering at all is 502.
|
|
6424
|
+
*/
|
|
6425
|
+
function toGithubError(e, timeoutMs) {
|
|
6426
|
+
if (e instanceof GithubError) return e;
|
|
6427
|
+
const err = e;
|
|
6428
|
+
if (err?.name === "GraphqlResponseError" && Array.isArray(err.errors) && err.errors.length) {
|
|
6429
|
+
const detail = err.errors.map((x) => x.message ?? "unknown error").join("; ");
|
|
6430
|
+
const fixable = err.errors.every((x) => x.type === "NOT_FOUND" || x.type === "FORBIDDEN");
|
|
6431
|
+
return new GithubError(`GitHub: ${detail}`, fixable ? 409 : 502);
|
|
6432
|
+
}
|
|
6433
|
+
if (err?.name === "HttpError" && typeof err.status === "number") {
|
|
6434
|
+
const detail = firstLine(err.message);
|
|
6435
|
+
if (err.response === void 0) {
|
|
6436
|
+
const cause = err.cause?.name;
|
|
6437
|
+
if (cause === "TimeoutError" || cause === "AbortError") return new GithubError(`GitHub did not answer within ${timeoutMs ?? "?"}ms`, 502);
|
|
6438
|
+
return new GithubError(`cannot reach GitHub: ${detail}`, 502);
|
|
6439
|
+
}
|
|
6440
|
+
if (err.status === 401) return new GithubError(`GitHub rejected the token (401): ${detail}`, 409);
|
|
6441
|
+
if (err.status === 403 || err.status === 404) return new GithubError(`GitHub ${err.status}: ${detail}`, 409);
|
|
6442
|
+
return new GithubError(`GitHub ${err.status}: ${detail}`, 502);
|
|
6443
|
+
}
|
|
6444
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") return new GithubError(`GitHub did not answer within ${timeoutMs ?? "?"}ms`, 502);
|
|
6445
|
+
return new GithubError(`cannot reach GitHub: ${e instanceof Error ? firstLine(e.message) : String(e)}`, 502);
|
|
6446
|
+
}
|
|
6447
|
+
function firstLine(s) {
|
|
6448
|
+
return (s ?? "").trim().split("\n")[0] ?? "";
|
|
5556
6449
|
}
|
|
5557
6450
|
//#endregion
|
|
5558
6451
|
//#region node_modules/zod/v4/core/util.js
|
|
@@ -11391,6 +12284,8 @@ const SearchScopeSchema = _enum([
|
|
|
11391
12284
|
"diff",
|
|
11392
12285
|
"repo"
|
|
11393
12286
|
]);
|
|
12287
|
+
/** Whether search includes whole files or only new-side diff hunks and their context. */
|
|
12288
|
+
const SearchContentSchema = _enum(["diff", "full"]);
|
|
11394
12289
|
object({
|
|
11395
12290
|
query: string(),
|
|
11396
12291
|
matches: SearchMatchSchema.array(),
|
|
@@ -11612,6 +12507,7 @@ const flagSchema = _enum(["0", "1"]).optional().transform((value) => value === "
|
|
|
11612
12507
|
const SearchQuerySchema = object({
|
|
11613
12508
|
q: string().default(""),
|
|
11614
12509
|
scope: SearchScopeSchema.default("diff"),
|
|
12510
|
+
content: SearchContentSchema.default("diff"),
|
|
11615
12511
|
path: string().optional(),
|
|
11616
12512
|
word: flagSchema,
|
|
11617
12513
|
i: flagSchema,
|
|
@@ -16458,12 +17354,22 @@ function createApi(deps) {
|
|
|
16458
17354
|
const query = SearchQuerySchema.parse(c.req.query());
|
|
16459
17355
|
const { q, scope } = query;
|
|
16460
17356
|
const snap = await session.snapshotter.current();
|
|
16461
|
-
|
|
17357
|
+
let paths = scope === "repo" ? void 0 : scope === "file" ? snap.tree.filter((p) => p === query.path) : snap.changed.filter((f) => f.status !== "D").map((f) => f.path);
|
|
17358
|
+
let ranges;
|
|
17359
|
+
if (query.content === "diff") {
|
|
17360
|
+
const entries = await mapLimit(snap.changed.filter((f) => f.status !== "D" && !f.binary && (!paths || paths.includes(f.path))), 8, async (file) => {
|
|
17361
|
+
const patch = await session.repo.patch(snap.oldSha, snap.newSha, file, snap.context);
|
|
17362
|
+
return [file.path, shownRanges(patch, "new")];
|
|
17363
|
+
});
|
|
17364
|
+
ranges = new Map(entries);
|
|
17365
|
+
paths = entries.filter(([, spans]) => spans.length > 0).map(([path]) => path);
|
|
17366
|
+
}
|
|
16462
17367
|
const { matches, truncated } = await session.repo.grep(q, snap.newSha, 500, {
|
|
16463
17368
|
word: query.word,
|
|
16464
17369
|
ignoreCase: query.i,
|
|
16465
17370
|
regex: query.re,
|
|
16466
|
-
paths
|
|
17371
|
+
paths,
|
|
17372
|
+
ranges
|
|
16467
17373
|
});
|
|
16468
17374
|
const body = {
|
|
16469
17375
|
query: q,
|
|
@@ -16772,20 +17678,155 @@ function hasClientBuild() {
|
|
|
16772
17678
|
return existsSync(join(resolveClientDir(), "index.html"));
|
|
16773
17679
|
}
|
|
16774
17680
|
//#endregion
|
|
16775
|
-
//#region src/server/
|
|
16776
|
-
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
17681
|
+
//#region src/server/github/pulls.ts
|
|
16777
17682
|
/** GitHub.com identity from a configured HTTPS, SSH, or scp-style remote URL. */
|
|
16778
17683
|
function githubRepository(url) {
|
|
16779
17684
|
const match = /^(?:https?:\/\/github\.com\/|ssh:\/\/(?:[^/@]+@)?github\.com(?::\d+)?\/|(?:[^/@]+@)?github\.com:)([^/]+)\/([^/]+?)\/?$/i.exec(url);
|
|
16780
17685
|
return match ? `${match[1]}/${match[2].replace(/\.git$/i, "")}` : null;
|
|
16781
17686
|
}
|
|
16782
|
-
/** Local-only origin identity; works without
|
|
17687
|
+
/** Local-only origin identity; works without a token or a network connection. */
|
|
16783
17688
|
async function originRepository(repo) {
|
|
16784
17689
|
const origin = (await repo.remotes()).find((r) => r.name === "origin");
|
|
16785
17690
|
return origin ? githubRepository(origin.url) : null;
|
|
16786
17691
|
}
|
|
16787
|
-
/**
|
|
16788
|
-
|
|
17692
|
+
/**
|
|
17693
|
+
* The GitHub repository pull requests are looked up in: `origin`, or the only GitHub remote
|
|
17694
|
+
* when origin is not one. Any other layout has to name the pull request by url.
|
|
17695
|
+
*/
|
|
17696
|
+
async function baseRepository(repo) {
|
|
17697
|
+
if (!repo) throw new GithubError("not in a git repository; name the pull request by url", 400);
|
|
17698
|
+
const remotes = (await repo.remotes()).flatMap((r) => {
|
|
17699
|
+
const repository = githubRepository(r.url);
|
|
17700
|
+
return repository ? [{
|
|
17701
|
+
name: r.name,
|
|
17702
|
+
repository
|
|
17703
|
+
}] : [];
|
|
17704
|
+
});
|
|
17705
|
+
const base = remotes.find((r) => r.name === "origin") ?? (remotes.length === 1 ? remotes[0] : void 0);
|
|
17706
|
+
if (!base) throw new GithubError(`${remotes.length ? `${remotes.length} GitHub remotes and no origin` : "no GitHub remote"}; name the pull request by url`, 400);
|
|
17707
|
+
return base.repository;
|
|
17708
|
+
}
|
|
17709
|
+
/** The base repository the pull request lives in, taken from its url (a fork's own remote is not it). */
|
|
17710
|
+
function repoOfPrUrl(url) {
|
|
17711
|
+
const parsed = parsePrUrl(url);
|
|
17712
|
+
if (!parsed) throw new GithubError(`cannot read owner/repo from the pull request url: ${url}`, 502);
|
|
17713
|
+
return {
|
|
17714
|
+
owner: parsed.owner,
|
|
17715
|
+
repo: parsed.repo
|
|
17716
|
+
};
|
|
17717
|
+
}
|
|
17718
|
+
function parsePrUrl(url) {
|
|
17719
|
+
let pathname;
|
|
17720
|
+
try {
|
|
17721
|
+
pathname = new URL(url).pathname;
|
|
17722
|
+
} catch {
|
|
17723
|
+
return null;
|
|
17724
|
+
}
|
|
17725
|
+
const m = /^\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\/|$)/.exec(pathname);
|
|
17726
|
+
return m ? {
|
|
17727
|
+
owner: m[1],
|
|
17728
|
+
repo: m[2],
|
|
17729
|
+
number: Number(m[3])
|
|
17730
|
+
} : null;
|
|
17731
|
+
}
|
|
17732
|
+
const PR_FIELDS = "number url title state isDraft baseRefOid baseRefName headRefName headRefOid headRepository{name owner{login}}";
|
|
17733
|
+
const BY_NUMBER = `query($owner:String!,$repo:String!,$number:Int!){
|
|
17734
|
+
repository(owner:$owner,name:$repo){pullRequest(number:$number){${PR_FIELDS}}}
|
|
17735
|
+
}`;
|
|
17736
|
+
const BY_HEAD = `query($owner:String!,$repo:String!,$head:String!,$base:String){
|
|
17737
|
+
repository(owner:$owner,name:$repo){pullRequests(headRefName:$head,baseRefName:$base,states:[OPEN],first:100){nodes{${PR_FIELDS}}}}
|
|
17738
|
+
}`;
|
|
17739
|
+
/**
|
|
17740
|
+
* The pull request a selector names: a number, `#number`, a URL, a branch (`owner:branch`
|
|
17741
|
+
* for a fork's), or, without one, the checked-out branch's. Everything but a URL is
|
|
17742
|
+
* looked up in the base repository (see `baseRepository`). Option-shaped selectors are refused.
|
|
17743
|
+
*/
|
|
17744
|
+
async function viewPr(selector, repo, github, timeoutMs) {
|
|
17745
|
+
if (selector == null) {
|
|
17746
|
+
if (!repo) throw new GithubError("not in a git repository; name the pull request by url", 400);
|
|
17747
|
+
const ref = await repo.branchRef("HEAD");
|
|
17748
|
+
if (!ref?.startsWith("refs/heads/")) throw new GithubError("HEAD is not on a branch; name the pull request by number or url", 400);
|
|
17749
|
+
const upstream = await repo.upstreamBranch("HEAD");
|
|
17750
|
+
const remotes = await repo.remotes();
|
|
17751
|
+
const remoteUrl = upstream && remotes.find((r) => r.name === upstream.remote)?.url;
|
|
17752
|
+
const headRepo = remoteUrl ? githubRepository(remoteUrl) : null;
|
|
17753
|
+
return byHead(github, await baseRepository(repo), upstream?.branch ?? ref.slice(11), {
|
|
17754
|
+
owner: headRepo?.split("/")[0],
|
|
17755
|
+
timeoutMs
|
|
17756
|
+
});
|
|
17757
|
+
}
|
|
17758
|
+
const s = selector.trim();
|
|
17759
|
+
if (s.startsWith("-")) throw new GithubError(`not a pull request: ${selector}`, 400);
|
|
17760
|
+
const url = /^https?:\/\//.test(s) ? parsePrUrl(s) : null;
|
|
17761
|
+
if (url) return byNumber(github, `${url.owner}/${url.repo}`, url.number, timeoutMs);
|
|
17762
|
+
if (/^https?:\/\//.test(s)) throw new GithubError(`not a pull request url: ${selector}`, 400);
|
|
17763
|
+
const num = /^#?(\d+)$/.exec(s);
|
|
17764
|
+
if (num) return byNumber(github, await baseRepository(repo), Number(num[1]), timeoutMs);
|
|
17765
|
+
const colon = s.indexOf(":");
|
|
17766
|
+
const owner = colon > 0 ? s.slice(0, colon) : void 0;
|
|
17767
|
+
return byHead(github, await baseRepository(repo), colon > 0 ? s.slice(colon + 1) : s, {
|
|
17768
|
+
owner,
|
|
17769
|
+
timeoutMs
|
|
17770
|
+
});
|
|
17771
|
+
}
|
|
17772
|
+
async function byNumber(github, repository, number, timeoutMs) {
|
|
17773
|
+
const [owner, repo] = repository.split("/");
|
|
17774
|
+
const data = await github.graphql(BY_NUMBER, {
|
|
17775
|
+
owner,
|
|
17776
|
+
repo,
|
|
17777
|
+
number
|
|
17778
|
+
}, { timeoutMs });
|
|
17779
|
+
if (!data.repository?.pullRequest) throw new GithubError(`no pull request ${repository}#${number}`, 409);
|
|
17780
|
+
return parsePullRequest(data.repository.pullRequest);
|
|
17781
|
+
}
|
|
17782
|
+
async function byHead(github, baseRepo, head, { owner, timeoutMs }) {
|
|
17783
|
+
const label = owner ? `${owner}:${head}` : head;
|
|
17784
|
+
const prs = (await pullRequestsForHead(github, baseRepo, head, void 0, timeoutMs)).filter((pr) => !owner || pr.headRepository?.split("/")[0]?.toLowerCase() === owner.toLowerCase());
|
|
17785
|
+
if (prs.length === 0) throw new GithubError(`no open pull request in ${baseRepo} for ${label}`, 409);
|
|
17786
|
+
if (prs.length > 1) throw new GithubError(`${prs.length} open pull requests in ${baseRepo} for ${label}; open one by number`, 409);
|
|
17787
|
+
return prs[0];
|
|
17788
|
+
}
|
|
17789
|
+
/** Open pull requests of `baseRepo` whose head is the named branch, from any head repository, into `base` if given. */
|
|
17790
|
+
async function pullRequestsForHead(github, baseRepo, head, base, timeoutMs) {
|
|
17791
|
+
const [owner, repo] = baseRepo.split("/");
|
|
17792
|
+
const nodes = (await github.graphql(BY_HEAD, {
|
|
17793
|
+
owner,
|
|
17794
|
+
repo,
|
|
17795
|
+
head,
|
|
17796
|
+
base: base ?? null
|
|
17797
|
+
}, { timeoutMs })).repository?.pullRequests?.nodes;
|
|
17798
|
+
if (!Array.isArray(nodes)) throw new GithubError("unexpected pull request data from GitHub", 502);
|
|
17799
|
+
return nodes.map(parsePullRequest);
|
|
17800
|
+
}
|
|
17801
|
+
const PullRequestNodeSchema = GithubPullRequestSchema.omit({ repository: true }).extend({
|
|
17802
|
+
baseRefOid: string(),
|
|
17803
|
+
baseRefName: string(),
|
|
17804
|
+
headRefName: string(),
|
|
17805
|
+
headRefOid: string(),
|
|
17806
|
+
headRepository: object({
|
|
17807
|
+
name: string(),
|
|
17808
|
+
owner: object({ login: string() })
|
|
17809
|
+
}).nullable().transform((source) => source ? `${source.owner.login}/${source.name}` : null)
|
|
17810
|
+
});
|
|
17811
|
+
function parsePullRequest(value) {
|
|
17812
|
+
const result = PullRequestNodeSchema.safeParse(value);
|
|
17813
|
+
if (!result.success) throw new GithubError("unexpected pull request data from GitHub", 502);
|
|
17814
|
+
const pr = result.data;
|
|
17815
|
+
const slug = /^https?:\/\/[^/]+\/([^/]+\/[^/]+)\/pull\/\d+/.exec(pr.url);
|
|
17816
|
+
if (!slug) throw new GithubError(`cannot read the repository from the pull request url: ${pr.url}`, 502);
|
|
17817
|
+
return {
|
|
17818
|
+
...pr,
|
|
17819
|
+
repository: slug[1]
|
|
17820
|
+
};
|
|
17821
|
+
}
|
|
17822
|
+
//#endregion
|
|
17823
|
+
//#region src/server/GithubMetadata.ts
|
|
17824
|
+
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
17825
|
+
/**
|
|
17826
|
+
* Enriches a comparison without fetching commits, changing its endpoints, or changing comment
|
|
17827
|
+
* storage. Without a client (no token), only the origin repository is known.
|
|
17828
|
+
*/
|
|
17829
|
+
async function discoverGithub(repo, snap, { github, prUrl } = {}) {
|
|
16789
17830
|
const repository = await originRepository(repo);
|
|
16790
17831
|
const absent = (reason) => ({
|
|
16791
17832
|
version: snap.version,
|
|
@@ -16793,9 +17834,10 @@ async function discoverGithub(repo, snap, { run = runGh, prUrl } = {}) {
|
|
|
16793
17834
|
pullRequest: null,
|
|
16794
17835
|
reason
|
|
16795
17836
|
});
|
|
17837
|
+
if (!github) return absent(NO_TOKEN);
|
|
16796
17838
|
try {
|
|
16797
17839
|
let pr;
|
|
16798
|
-
if (prUrl) pr = await viewPr(prUrl, repo
|
|
17840
|
+
if (prUrl) pr = await viewPr(prUrl, repo, github, LOOKUP_TIMEOUT_MS);
|
|
16799
17841
|
else {
|
|
16800
17842
|
const [old, next, remotes] = await Promise.all([
|
|
16801
17843
|
repo.upstreamBranch(snap.mode.old),
|
|
@@ -16810,7 +17852,7 @@ async function discoverGithub(repo, snap, { run = runGh, prUrl } = {}) {
|
|
|
16810
17852
|
const baseRepo = repository(old.remote);
|
|
16811
17853
|
const headRepo = repository(next.remote);
|
|
16812
17854
|
if (!baseRepo || !headRepo) return absent("Both branches must have GitHub remotes");
|
|
16813
|
-
const prs = await
|
|
17855
|
+
const prs = (await pullRequestsForHead(github, baseRepo, next.branch, old.branch, LOOKUP_TIMEOUT_MS)).filter((pr) => pr.headRepository?.toLowerCase() === headRepo.toLowerCase());
|
|
16814
17856
|
if (!prs.length) return absent("No matching pull request");
|
|
16815
17857
|
if (prs.length !== 1) return absent("Multiple matching pull requests; open one explicitly");
|
|
16816
17858
|
pr = prs[0];
|
|
@@ -16844,6 +17886,300 @@ async function discoverGithub(repo, snap, { run = runGh, prUrl } = {}) {
|
|
|
16844
17886
|
}
|
|
16845
17887
|
}
|
|
16846
17888
|
//#endregion
|
|
17889
|
+
//#region src/server/github/review.ts
|
|
17890
|
+
function isFileComment(c) {
|
|
17891
|
+
return "subjectType" in c;
|
|
17892
|
+
}
|
|
17893
|
+
/**
|
|
17894
|
+
* Threads → the comments of one pending GitHub review, one per thread. Stale threads are skipped:
|
|
17895
|
+
* their lines no longer sit in the diff, and GitHub would refuse them anyway. With
|
|
17896
|
+
* `threadIds`, only those (resolved included, the user asked for them by hand);
|
|
17897
|
+
* without, every unresolved thread.
|
|
17898
|
+
*/
|
|
17899
|
+
function buildReview(threads, threadIds) {
|
|
17900
|
+
const skipped = [];
|
|
17901
|
+
let chosen;
|
|
17902
|
+
if (threadIds) {
|
|
17903
|
+
const byId = new Map(threads.map((t) => [t.id, t]));
|
|
17904
|
+
chosen = [];
|
|
17905
|
+
for (const id of threadIds) {
|
|
17906
|
+
const t = byId.get(id);
|
|
17907
|
+
if (t) chosen.push(t);
|
|
17908
|
+
else skipped.push({
|
|
17909
|
+
id,
|
|
17910
|
+
reason: "unknown thread"
|
|
17911
|
+
});
|
|
17912
|
+
}
|
|
17913
|
+
} else chosen = threads.filter((t) => !t.resolved);
|
|
17914
|
+
const comments = [];
|
|
17915
|
+
const ids = [];
|
|
17916
|
+
for (const t of [...chosen].sort(compareThreads)) {
|
|
17917
|
+
if (t.stale) {
|
|
17918
|
+
skipped.push({
|
|
17919
|
+
id: t.id,
|
|
17920
|
+
reason: "stale"
|
|
17921
|
+
});
|
|
17922
|
+
continue;
|
|
17923
|
+
}
|
|
17924
|
+
comments.push(toReviewComment(t));
|
|
17925
|
+
ids.push(t.id);
|
|
17926
|
+
}
|
|
17927
|
+
return {
|
|
17928
|
+
comments,
|
|
17929
|
+
ids,
|
|
17930
|
+
skipped
|
|
17931
|
+
};
|
|
17932
|
+
}
|
|
17933
|
+
function toReviewComment(t) {
|
|
17934
|
+
const { anchor } = t;
|
|
17935
|
+
const body = formatBody(t.messages);
|
|
17936
|
+
if (anchor.kind === "file") return {
|
|
17937
|
+
path: anchor.path,
|
|
17938
|
+
subjectType: "FILE",
|
|
17939
|
+
body
|
|
17940
|
+
};
|
|
17941
|
+
const side = anchor.side === "old" ? "LEFT" : "RIGHT";
|
|
17942
|
+
const c = {
|
|
17943
|
+
path: anchor.path,
|
|
17944
|
+
line: anchor.endLine,
|
|
17945
|
+
side,
|
|
17946
|
+
body
|
|
17947
|
+
};
|
|
17948
|
+
if (anchor.startLine !== anchor.endLine) {
|
|
17949
|
+
c.startLine = anchor.startLine;
|
|
17950
|
+
c.startSide = side;
|
|
17951
|
+
}
|
|
17952
|
+
return c;
|
|
17953
|
+
}
|
|
17954
|
+
/**
|
|
17955
|
+
* Messages joined as markdown. GitHub renders ```suggestion fences itself, so bodies
|
|
17956
|
+
* stay verbatim.
|
|
17957
|
+
*/
|
|
17958
|
+
function formatBody(messages) {
|
|
17959
|
+
return messages.map((m) => m.body.trim()).join("\n\n---\n\n");
|
|
17960
|
+
}
|
|
17961
|
+
/** Serializes validation and writes so overlapping exports cannot add the same thread twice. */
|
|
17962
|
+
var GithubExporter = class {
|
|
17963
|
+
queue = Promise.resolve();
|
|
17964
|
+
export(prepare) {
|
|
17965
|
+
const result = this.queue.then(async () => exportToGithub(await prepare()));
|
|
17966
|
+
this.queue = result.catch(() => {});
|
|
17967
|
+
return result;
|
|
17968
|
+
}
|
|
17969
|
+
};
|
|
17970
|
+
/**
|
|
17971
|
+
* Adds the threads to a *pending* review on the active PR, creating the pending review
|
|
17972
|
+
* when there is none. The review is never submitted: the human opens the PR and submits
|
|
17973
|
+
* it themselves. The caller validates the comparison inside the export queue before posting.
|
|
17974
|
+
*
|
|
17975
|
+
* Re-exporting matches a hidden thread ID and its anchor in the pending review.
|
|
17976
|
+
* A matching comment is left alone when its body matches and rewritten when it does not, so
|
|
17977
|
+
* editing a thread and exporting again does not stack a second comment on the line. Comments
|
|
17978
|
+
* of an already-submitted review are out of reach — those would have to be replied to.
|
|
17979
|
+
*/
|
|
17980
|
+
async function exportToGithub({ snap, pullRequest: pr, threads, threadIds, github }) {
|
|
17981
|
+
if (snap.newSha === "worktree") throw new GithubError("GitHub cannot anchor comments to the worktree");
|
|
17982
|
+
const { comments, ids, skipped } = buildReview(threads, threadIds);
|
|
17983
|
+
if (comments.length === 0) throw new GithubError(skipped.length ? `nothing to post: ${describe(skipped)}` : "nothing to post", 400);
|
|
17984
|
+
for (const [i, comment] of comments.entries()) comment.body += `\n\n${threadMarker(ids[i])}`;
|
|
17985
|
+
const { pullRequestId, pending } = await findPendingReview(github, pr);
|
|
17986
|
+
if (!pending) {
|
|
17987
|
+
const lines = comments.filter((c) => !isFileComment(c));
|
|
17988
|
+
const created = await createPendingReview(github, pullRequestId, snap.newSha, lines);
|
|
17989
|
+
let appended = 0;
|
|
17990
|
+
try {
|
|
17991
|
+
for (const c of comments.filter(isFileComment)) {
|
|
17992
|
+
await addToPendingReview(github, created.id, c);
|
|
17993
|
+
appended++;
|
|
17994
|
+
}
|
|
17995
|
+
} catch (e) {
|
|
17996
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
17997
|
+
throw new GithubError(`created the pending review with ${lines.length + appended} comments, then ${detail}`, 502);
|
|
17998
|
+
}
|
|
17999
|
+
return {
|
|
18000
|
+
url: pr.url,
|
|
18001
|
+
posted: comments.length,
|
|
18002
|
+
updated: 0,
|
|
18003
|
+
review: "created",
|
|
18004
|
+
skipped
|
|
18005
|
+
};
|
|
18006
|
+
}
|
|
18007
|
+
const existing = await pendingComments(github, pr, pending.id);
|
|
18008
|
+
const add = [];
|
|
18009
|
+
let updated = 0;
|
|
18010
|
+
let posted = 0;
|
|
18011
|
+
try {
|
|
18012
|
+
for (const [i, c] of comments.entries()) {
|
|
18013
|
+
const id = ids[i];
|
|
18014
|
+
const at = existing.get(anchorKey(c));
|
|
18015
|
+
const index = at?.findIndex((comment) => comment.body.trimEnd().endsWith(threadMarker(id))) ?? -1;
|
|
18016
|
+
const hit = index < 0 ? void 0 : at.splice(index, 1)[0];
|
|
18017
|
+
if (!hit) {
|
|
18018
|
+
add.push(c);
|
|
18019
|
+
continue;
|
|
18020
|
+
}
|
|
18021
|
+
if (hit.body.trim() === c.body.trim()) {
|
|
18022
|
+
skipped.push({
|
|
18023
|
+
id,
|
|
18024
|
+
reason: "already in the review"
|
|
18025
|
+
});
|
|
18026
|
+
continue;
|
|
18027
|
+
}
|
|
18028
|
+
await github.graphql(UPDATE_COMMENT, { input: {
|
|
18029
|
+
pullRequestReviewCommentId: hit.nodeId,
|
|
18030
|
+
body: c.body
|
|
18031
|
+
} });
|
|
18032
|
+
updated++;
|
|
18033
|
+
}
|
|
18034
|
+
for (const c of add) {
|
|
18035
|
+
await addToPendingReview(github, pending.id, c);
|
|
18036
|
+
posted++;
|
|
18037
|
+
}
|
|
18038
|
+
} catch (e) {
|
|
18039
|
+
if (updated === 0 && posted === 0) throw e;
|
|
18040
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
18041
|
+
throw new GithubError(`updated ${updated} and added ${posted} comments in the pending review, then ${detail}`, 502);
|
|
18042
|
+
}
|
|
18043
|
+
return {
|
|
18044
|
+
url: pr.url,
|
|
18045
|
+
posted,
|
|
18046
|
+
updated,
|
|
18047
|
+
review: "existing",
|
|
18048
|
+
skipped
|
|
18049
|
+
};
|
|
18050
|
+
}
|
|
18051
|
+
function threadMarker(id) {
|
|
18052
|
+
return `<!-- diffle-thread:${encodeURIComponent(id)} -->`;
|
|
18053
|
+
}
|
|
18054
|
+
/** The anchor must still match before we rewrite an exported thread. */
|
|
18055
|
+
function anchorKey(c) {
|
|
18056
|
+
if (isFileComment(c)) return [c.path, "FILE"].join("\0");
|
|
18057
|
+
return [
|
|
18058
|
+
c.path,
|
|
18059
|
+
c.side,
|
|
18060
|
+
c.startLine ?? c.line,
|
|
18061
|
+
c.line
|
|
18062
|
+
].join("\0");
|
|
18063
|
+
}
|
|
18064
|
+
const PENDING_QUERY = `query($owner:String!,$repo:String!,$number:Int!){
|
|
18065
|
+
viewer{login}
|
|
18066
|
+
repository(owner:$owner,name:$repo){pullRequest(number:$number){id reviews(last:20,states:[PENDING]){nodes{id author{login}}}}}
|
|
18067
|
+
}`;
|
|
18068
|
+
/**
|
|
18069
|
+
* The PR's node id and the viewer's own pending review on it, or null. GitHub hides other
|
|
18070
|
+
* people's pending reviews, and the login check makes sure of it: appending to someone
|
|
18071
|
+
* else's draft would put our comments in their review.
|
|
18072
|
+
*/
|
|
18073
|
+
async function findPendingReview(github, pr) {
|
|
18074
|
+
const { owner, repo } = repoOfPrUrl(pr.url);
|
|
18075
|
+
const data = await github.graphql(PENDING_QUERY, {
|
|
18076
|
+
owner,
|
|
18077
|
+
repo,
|
|
18078
|
+
number: pr.number
|
|
18079
|
+
});
|
|
18080
|
+
const pullRequestId = data.repository?.pullRequest?.id;
|
|
18081
|
+
if (typeof pullRequestId !== "string") throw new GithubError(`cannot read pull request ${pr.url} from GitHub`, 502);
|
|
18082
|
+
const login = data.viewer?.login;
|
|
18083
|
+
for (const node of data.repository?.pullRequest?.reviews?.nodes ?? []) if (node?.id && node.author?.login === login) return {
|
|
18084
|
+
pullRequestId,
|
|
18085
|
+
pending: { id: node.id }
|
|
18086
|
+
};
|
|
18087
|
+
return {
|
|
18088
|
+
pullRequestId,
|
|
18089
|
+
pending: null
|
|
18090
|
+
};
|
|
18091
|
+
}
|
|
18092
|
+
const THREADS_QUERY = `query($owner:String!,$repo:String!,$number:Int!,$after:String){
|
|
18093
|
+
repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewThreads(first:100,after:$after){
|
|
18094
|
+
pageInfo{hasNextPage endCursor}
|
|
18095
|
+
nodes{path line startLine diffSide subjectType comments(first:1){nodes{id body pullRequestReview{id}}}}
|
|
18096
|
+
}}}
|
|
18097
|
+
}`;
|
|
18098
|
+
/**
|
|
18099
|
+
* The pending review's own comments, by anchor. It has to be `reviewThreads`: it is the only view
|
|
18100
|
+
* that reports a *pending* comment's real position — REST's review-comment list leaves `line`,
|
|
18101
|
+
* `side` and `start_line` null for one, and the GraphQL comment type has no side field at all.
|
|
18102
|
+
* A failed or incomplete lookup aborts the export: absence is safe to infer only from a complete read.
|
|
18103
|
+
*/
|
|
18104
|
+
async function pendingComments(github, pr, reviewId) {
|
|
18105
|
+
const by = /* @__PURE__ */ new Map();
|
|
18106
|
+
const { owner, repo } = repoOfPrUrl(pr.url);
|
|
18107
|
+
let after = null;
|
|
18108
|
+
for (let page = 0; page < 10; page++) {
|
|
18109
|
+
const threads = (await github.graphql(THREADS_QUERY, {
|
|
18110
|
+
owner,
|
|
18111
|
+
repo,
|
|
18112
|
+
number: pr.number,
|
|
18113
|
+
after
|
|
18114
|
+
})).repository?.pullRequest?.reviewThreads;
|
|
18115
|
+
if (!threads?.nodes || typeof threads.pageInfo?.hasNextPage !== "boolean") throw new GithubError("cannot read the complete pending review", 502);
|
|
18116
|
+
for (const t of threads.nodes) {
|
|
18117
|
+
const c = t?.comments?.nodes?.[0];
|
|
18118
|
+
if (!t?.path || !c?.id || c.pullRequestReview?.id !== reviewId) continue;
|
|
18119
|
+
const key = pendingKey(t);
|
|
18120
|
+
if (key == null) continue;
|
|
18121
|
+
const at = by.get(key);
|
|
18122
|
+
if (at) at.push({
|
|
18123
|
+
nodeId: c.id,
|
|
18124
|
+
body: c.body ?? ""
|
|
18125
|
+
});
|
|
18126
|
+
else by.set(key, [{
|
|
18127
|
+
nodeId: c.id,
|
|
18128
|
+
body: c.body ?? ""
|
|
18129
|
+
}]);
|
|
18130
|
+
}
|
|
18131
|
+
if (!threads.pageInfo.hasNextPage) return by;
|
|
18132
|
+
if (!threads.pageInfo.endCursor || threads.pageInfo.endCursor === after) throw new GithubError("cannot read the next page of the pending review", 502);
|
|
18133
|
+
after = threads.pageInfo.endCursor;
|
|
18134
|
+
}
|
|
18135
|
+
throw new GithubError("pending review exceeds the 1000-thread lookup limit; export stopped without changes", 502);
|
|
18136
|
+
}
|
|
18137
|
+
/** The anchor key of a thread already in the review, or null for one GitHub reports without a position. */
|
|
18138
|
+
function pendingKey(t) {
|
|
18139
|
+
const path = t.path;
|
|
18140
|
+
if (t.subjectType === "FILE") return anchorKey({
|
|
18141
|
+
path,
|
|
18142
|
+
subjectType: "FILE",
|
|
18143
|
+
body: ""
|
|
18144
|
+
});
|
|
18145
|
+
if (t.line == null) return null;
|
|
18146
|
+
return anchorKey({
|
|
18147
|
+
path,
|
|
18148
|
+
side: t.diffSide === "LEFT" ? "LEFT" : "RIGHT",
|
|
18149
|
+
line: t.line,
|
|
18150
|
+
startLine: t.startLine ?? void 0,
|
|
18151
|
+
body: ""
|
|
18152
|
+
});
|
|
18153
|
+
}
|
|
18154
|
+
const UPDATE_COMMENT = `mutation($input:UpdatePullRequestReviewCommentInput!){updatePullRequestReviewComment(input:$input){pullRequestReviewComment{id}}}`;
|
|
18155
|
+
const CREATE_REVIEW = `mutation($input:AddPullRequestReviewInput!){addPullRequestReview(input:$input){pullRequestReview{id}}}`;
|
|
18156
|
+
/**
|
|
18157
|
+
* No `event` in the input, so GitHub keeps the new review pending with all of its threads.
|
|
18158
|
+
* Resolves the review's node id, for the comments a new review cannot carry.
|
|
18159
|
+
*/
|
|
18160
|
+
async function createPendingReview(github, pullRequestId, commitOID, threads) {
|
|
18161
|
+
const id = (await github.graphql(CREATE_REVIEW, { input: {
|
|
18162
|
+
pullRequestId,
|
|
18163
|
+
commitOID,
|
|
18164
|
+
threads
|
|
18165
|
+
} })).addPullRequestReview?.pullRequestReview?.id;
|
|
18166
|
+
if (typeof id !== "string") throw new GithubError("GitHub did not return the created review", 502);
|
|
18167
|
+
return { id };
|
|
18168
|
+
}
|
|
18169
|
+
const ADD_THREAD = `mutation($input:AddPullRequestReviewThreadInput!){addPullRequestReviewThread(input:$input){thread{id}}}`;
|
|
18170
|
+
/** Appends one thread to the pending review; the only way to extend one, or to place a file comment. */
|
|
18171
|
+
async function addToPendingReview(github, reviewId, c) {
|
|
18172
|
+
await github.graphql(ADD_THREAD, { input: {
|
|
18173
|
+
pullRequestReviewId: reviewId,
|
|
18174
|
+
...c
|
|
18175
|
+
} });
|
|
18176
|
+
}
|
|
18177
|
+
function describe(skipped) {
|
|
18178
|
+
const counts = /* @__PURE__ */ new Map();
|
|
18179
|
+
for (const s of skipped) counts.set(s.reason, (counts.get(s.reason) ?? 0) + 1);
|
|
18180
|
+
return [...counts].map(([reason, n]) => `${n} ${reason}`).join(", ");
|
|
18181
|
+
}
|
|
18182
|
+
//#endregion
|
|
16847
18183
|
//#region src/server/mode.ts
|
|
16848
18184
|
/**
|
|
16849
18185
|
* Resolves a comparison to Git endpoints for initial mode setup, PR setup after
|
|
@@ -16900,8 +18236,8 @@ async function resolveComparison(repo, comparison) {
|
|
|
16900
18236
|
};
|
|
16901
18237
|
}
|
|
16902
18238
|
/** Resolves an input command into a comparison and optional explicit PR identity. */
|
|
16903
|
-
async function resolveReview(req, repo,
|
|
16904
|
-
if (req.kind === "pr") return resolvePr(req, repo,
|
|
18239
|
+
async function resolveReview(req, repo, github) {
|
|
18240
|
+
if (req.kind === "pr") return resolvePr(req, repo, github);
|
|
16905
18241
|
const parsed = req.kind === "working" ? {
|
|
16906
18242
|
old: "HEAD",
|
|
16907
18243
|
new: "worktree",
|
|
@@ -16953,14 +18289,15 @@ function branchKey(old, next, mergeBase) {
|
|
|
16953
18289
|
}
|
|
16954
18290
|
/**
|
|
16955
18291
|
* A GitHub pull request, as GitHub shows it: merge-base(base tip, head) vs head.
|
|
16956
|
-
*
|
|
18292
|
+
* GitHub names the PR, then one fetch brings the base tip and the PR head into
|
|
16957
18293
|
* session-owned refs named after the branches, so a PR nobody has checked out
|
|
16958
18294
|
* is reviewable. These refs stay fixed until another fetch; review state uses
|
|
16959
18295
|
* the same branch identities as ordinary comparisons, so comments survive
|
|
16960
18296
|
* pushes and reopening by branch.
|
|
16961
18297
|
*/
|
|
16962
|
-
async function resolvePr(req, repo,
|
|
16963
|
-
|
|
18298
|
+
async function resolvePr(req, repo, github) {
|
|
18299
|
+
if (!github) throw new GithubError(NO_TOKEN);
|
|
18300
|
+
const pr = await viewPr(req.pr, repo, github);
|
|
16964
18301
|
const head = `${repo.reviewRefs}/${pr.number}/head/${pr.headRefName}`;
|
|
16965
18302
|
const base = `${repo.reviewRefs}/${pr.number}/base/${pr.baseRefName}`;
|
|
16966
18303
|
await repo.fetch(await fetchSource(repo, pr), [`+refs/pull/${pr.number}/head:${head}`, `+refs/heads/${pr.baseRefName}:${base}`]);
|
|
@@ -17237,7 +18574,7 @@ var Session = class {
|
|
|
17237
18574
|
const cached = active.github;
|
|
17238
18575
|
if (cached?.snapshot === snap && cached.expires > Date.now()) return cached.promise;
|
|
17239
18576
|
const promise = discoverGithub(this.repo, snap, {
|
|
17240
|
-
|
|
18577
|
+
github: this.opts.github,
|
|
17241
18578
|
prUrl: active.prUrl
|
|
17242
18579
|
});
|
|
17243
18580
|
const entry = {
|
|
@@ -17257,8 +18594,10 @@ var Session = class {
|
|
|
17257
18594
|
const active = this.require();
|
|
17258
18595
|
const snap = await active.snapshotter.current();
|
|
17259
18596
|
if (active !== this.active) throw new GithubError("Comparison changed; try again");
|
|
18597
|
+
const github = this.opts.github;
|
|
18598
|
+
if (!github) throw new GithubError(NO_TOKEN);
|
|
17260
18599
|
const metadata = await discoverGithub(this.repo, snap, {
|
|
17261
|
-
|
|
18600
|
+
github,
|
|
17262
18601
|
prUrl: active.prUrl
|
|
17263
18602
|
});
|
|
17264
18603
|
if (!metadata.pullRequest || metadata.reason !== null) throw new GithubError(metadata.reason ?? "No matching pull request");
|
|
@@ -17268,7 +18607,7 @@ var Session = class {
|
|
|
17268
18607
|
pullRequest: metadata.pullRequest,
|
|
17269
18608
|
threads: active.comments.threads({ state: "all" }),
|
|
17270
18609
|
threadIds,
|
|
17271
|
-
|
|
18610
|
+
github
|
|
17272
18611
|
};
|
|
17273
18612
|
});
|
|
17274
18613
|
}
|
|
@@ -17328,7 +18667,7 @@ var Session = class {
|
|
|
17328
18667
|
return this.run(() => this.transition(req));
|
|
17329
18668
|
}
|
|
17330
18669
|
async transition(req) {
|
|
17331
|
-
const { mode, prUrl } = await resolveReview(req, this.repo, this.opts.
|
|
18670
|
+
const { mode, prUrl } = await resolveReview(req, this.repo, this.opts.github);
|
|
17332
18671
|
const snapshotter = new Snapshotter(this.repo, mode, ++this.version, this.opts.context);
|
|
17333
18672
|
const [comments, snap] = await Promise.all([CommentStore.open(this.repo.gitDir, mode.commentKey), snapshotter.current()]);
|
|
17334
18673
|
const next = {
|
|
@@ -21346,7 +22685,7 @@ function openBrowser(url) {
|
|
|
21346
22685
|
//#endregion
|
|
21347
22686
|
//#region src/cli/repository.ts
|
|
21348
22687
|
/** Foreign PRs live in a disposable clone, never in the caller's object database. */
|
|
21349
|
-
async function openReviewRepository(req, cwd,
|
|
22688
|
+
async function openReviewRepository(req, cwd, github) {
|
|
21350
22689
|
let local;
|
|
21351
22690
|
try {
|
|
21352
22691
|
local = await GitRepo.open(cwd);
|
|
@@ -21357,7 +22696,8 @@ async function openReviewRepository(req, cwd, gh = runGh) {
|
|
|
21357
22696
|
repo: local,
|
|
21358
22697
|
close: () => local.cleanReviewRefs()
|
|
21359
22698
|
};
|
|
21360
|
-
|
|
22699
|
+
if (!github) throw new GithubError(NO_TOKEN);
|
|
22700
|
+
const pr = await viewPr(req.pr, local ?? null, github);
|
|
21361
22701
|
if (local && (await local.remotes()).some((r) => remoteSlug(r.url) === pr.repository.toLowerCase())) return {
|
|
21362
22702
|
repo: local,
|
|
21363
22703
|
close: () => local.cleanReviewRefs()
|
|
@@ -21431,11 +22771,12 @@ Revisions follow git diff, except that a lone one compares from the merge base:
|
|
|
21431
22771
|
diffle main...feat what feat added since it left main
|
|
21432
22772
|
diffle main..worktree main vs the uncommitted tree ("worktree" works on either side)
|
|
21433
22773
|
|
|
22774
|
+
Without revisions, diffle compares HEAD..worktree.
|
|
22775
|
+
|
|
21434
22776
|
Status goes to stderr, so stdout carries only the review: unless --keep-alive is set,
|
|
21435
22777
|
closing the last auto-opened browser tab stops diffle and prints the open comments as a prompt for an agent.
|
|
21436
22778
|
Ctrl+C always stops it.`).action(async (revs, _o, cmd) => {
|
|
21437
|
-
|
|
21438
|
-
await run({
|
|
22779
|
+
await run(revs.length === 0 ? { kind: "working" } : {
|
|
21439
22780
|
kind: "revspec",
|
|
21440
22781
|
args: revs
|
|
21441
22782
|
}, cmd.optsWithGlobals());
|
|
@@ -21513,14 +22854,16 @@ async function run(req, opts) {
|
|
|
21513
22854
|
process.on("SIGTERM", interrupt);
|
|
21514
22855
|
let review;
|
|
21515
22856
|
try {
|
|
21516
|
-
|
|
22857
|
+
const github = await connectGithub();
|
|
22858
|
+
timing.mark("github token");
|
|
22859
|
+
review = await openReviewRepository(req, opts.C ?? process.cwd(), github.client);
|
|
21517
22860
|
timing.mark("open repository");
|
|
21518
22861
|
const config = await UserConfigStore.open();
|
|
21519
22862
|
if (interrupted) {
|
|
21520
22863
|
await review.close();
|
|
21521
22864
|
return;
|
|
21522
22865
|
}
|
|
21523
|
-
await serve(req, opts, review.repo, review.close, config, timing);
|
|
22866
|
+
await serve(req, opts, review.repo, review.close, config, github, timing);
|
|
21524
22867
|
} catch (e) {
|
|
21525
22868
|
await review?.close();
|
|
21526
22869
|
throw e;
|
|
@@ -21529,11 +22872,12 @@ async function run(req, opts) {
|
|
|
21529
22872
|
process.off("SIGTERM", interrupt);
|
|
21530
22873
|
}
|
|
21531
22874
|
}
|
|
21532
|
-
async function serve(req, opts, repo, closeRepo, config, timing) {
|
|
22875
|
+
async function serve(req, opts, repo, closeRepo, config, github, timing) {
|
|
21533
22876
|
const hub = new WsHub();
|
|
21534
22877
|
const session = new Session(repo, hub, {
|
|
21535
22878
|
watch: opts.watch,
|
|
21536
|
-
context: opts.context ?? config.get().contextLines
|
|
22879
|
+
context: opts.context ?? config.get().contextLines,
|
|
22880
|
+
github: github.client ?? void 0
|
|
21537
22881
|
});
|
|
21538
22882
|
const named = Array.isArray(opts.lsp) ? opts.lsp : [];
|
|
21539
22883
|
const overrides = {
|
|
@@ -21582,6 +22926,8 @@ async function serve(req, opts, repo, closeRepo, config, timing) {
|
|
|
21582
22926
|
}
|
|
21583
22927
|
console.error(`🚀 diffle running at ${c.cyan(url.href)}`);
|
|
21584
22928
|
console.error(`📂 ${c.dim("repo")} ${repo.root}`);
|
|
22929
|
+
if (github.client) console.error(`🐙 ${c.dim("github")} ${c.dim(`token from ${github.source}`)}`);
|
|
22930
|
+
else console.error(`🐙 ${c.dim("github")} ${c.dim("off: no token in GITHUB_TOKEN, GH_TOKEN or gh auth token")}`);
|
|
21585
22931
|
const snap = await session.start(req);
|
|
21586
22932
|
timing.mark("snapshot");
|
|
21587
22933
|
const n = snap.changed.length;
|
|
@@ -21608,6 +22954,15 @@ async function serve(req, opts, repo, closeRepo, config, timing) {
|
|
|
21608
22954
|
throw e;
|
|
21609
22955
|
}
|
|
21610
22956
|
}
|
|
22957
|
+
/** One token lookup per run; without one, GitHub features are off rather than failing later. */
|
|
22958
|
+
async function connectGithub() {
|
|
22959
|
+
const found = await resolveToken();
|
|
22960
|
+
if (!found) return { client: null };
|
|
22961
|
+
return {
|
|
22962
|
+
client: createGithubClient(found.token),
|
|
22963
|
+
source: found.source
|
|
22964
|
+
};
|
|
22965
|
+
}
|
|
21611
22966
|
/**
|
|
21612
22967
|
* The pool reads new-side text through the session, so the snapshot allowlist applies to
|
|
21613
22968
|
* every LSP path. Each snapshot decides which languages run: it re-opens the diff's files
|