@smwb/onreel-git-mcp 0.8.0 → 0.9.0

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.
@@ -24,6 +24,7 @@ var LEGACY_ISSUE_CAPABILITIES = {
24
24
  var CI_LOG_DEFAULT_BYTES = 16 * 1024;
25
25
  var CI_LOG_MAX_BYTES = 1024 * 1024;
26
26
  var CI_LOG_READ_MAX_BYTES = 64 * 1024 * 1024;
27
+ var TRACKER_REDIRECT_MAX = 5;
27
28
  var CI_LOG_TIMEOUT_MS = 6e4;
28
29
  var CI_LOG_PAGES_MAX = 50;
29
30
  var CI_FINISHED_STATUSES = ["success", "failed", "canceled", "skipped"];
@@ -318,25 +319,93 @@ var SOURCECRAFT_RUN_EVENTS = {
318
319
 
319
320
  // src/tracker/http.lib.ts
320
321
  async function requestJson(url, request) {
321
- let response;
322
+ const accept = { accept: "application/json" };
323
+ const { response, url: final } = await send(url, request, AbortSignal.timeout(TRACKER_TIMEOUT_MS), accept, {
324
+ method: request.method ?? "GET",
325
+ headers: { ...accept, ...request.body === void 0 ? {} : { "content-type": "application/json" }, ...request.headers },
326
+ body: request.body === void 0 ? void 0 : JSON.stringify(request.body)
327
+ });
328
+ let raw;
322
329
  try {
323
- response = await request.fetch(url, {
324
- method: request.method ?? "GET",
325
- headers: { accept: "application/json", ...request.body === void 0 ? {} : { "content-type": "application/json" }, ...request.headers },
326
- ...request.body === void 0 ? {} : { body: JSON.stringify(request.body) },
327
- signal: AbortSignal.timeout(TRACKER_TIMEOUT_MS)
328
- });
330
+ raw = await response.text();
329
331
  } catch (error) {
330
- throw notAnswered(url, error);
332
+ throw notAnswered(final, error);
331
333
  }
332
- const raw = await response.text();
333
- if (!response.ok) throw failureOf(url, response, raw, request);
334
+ if (!response.ok) throw failureOf(final, response, raw, request);
334
335
  if (raw === "") return { body: void 0, headers: response.headers };
335
336
  try {
336
337
  return { body: JSON.parse(raw), headers: response.headers };
337
338
  } catch {
338
- throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435 JSON.`);
339
+ throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(final)} \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435 JSON.`);
340
+ }
341
+ }
342
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
343
+ async function send(url, request, signal, anonymous, first) {
344
+ const home = originOf(request.origin ?? url, "Origin \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F");
345
+ if (originOf(url, "\u0410\u0434\u0440\u0435\u0441 \u0437\u0430\u043F\u0440\u043E\u0441\u0430") !== home) throw new TrackerError("invalid_request", `\u0410\u0434\u0440\u0435\u0441 \u0437\u0430\u043F\u0440\u043E\u0441\u0430 ${hostOf(url)} \u0432\u043D\u0435 origin \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F ${hostOf(home)}: \u0437\u0430\u043F\u0440\u043E\u0441 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D.`);
346
+ let current = url;
347
+ let hop = first;
348
+ let away = false;
349
+ for (let redirects = 0; ; redirects += 1) {
350
+ let response;
351
+ try {
352
+ response = await request.fetch(current, { method: hop.method, headers: hop.headers, ...hop.body === void 0 ? {} : { body: hop.body }, redirect: "manual", signal });
353
+ } catch (error) {
354
+ throw notAnswered(current, error);
355
+ }
356
+ const hidden = followedByFetch(response, current);
357
+ if (hidden !== void 0) {
358
+ await response.body?.cancel().catch(() => void 0);
359
+ throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(current)}: ${hidden}; \u043E\u0442\u0432\u0435\u0442 \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D.`);
360
+ }
361
+ const location = REDIRECT_STATUSES.has(response.status) ? response.headers.get("location") : null;
362
+ if (location === null || location === "") return { response, url: current };
363
+ await response.body?.cancel().catch(() => void 0);
364
+ if (redirects >= TRACKER_REDIRECT_MAX) throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)}: \u0431\u043E\u043B\u044C\u0448\u0435 ${String(TRACKER_REDIRECT_MAX)} \u0440\u0435\u0434\u0438\u0440\u0435\u043A\u0442\u043E\u0432 \u043F\u043E\u0434\u0440\u044F\u0434.`);
365
+ const next = redirectTarget(location, current);
366
+ if (response.status === 303 && hop.method !== "GET" || (response.status === 301 || response.status === 302) && hop.method === "POST") {
367
+ const { "content-type": _type, ...headers } = hop.headers;
368
+ hop = { method: "GET", headers, body: void 0 };
369
+ }
370
+ away ||= new URL(next).origin !== home;
371
+ if (away) {
372
+ if (hop.method !== "GET") throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(current)} \u043F\u0435\u0440\u0435\u043D\u0430\u043F\u0440\u0430\u0432\u0438\u043B ${hop.method} \u043D\u0430 \u0447\u0443\u0436\u043E\u0439 origin ${hostOf(next)}: \u0437\u0430\u043F\u0440\u043E\u0441 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D.`);
373
+ hop = { method: "GET", headers: anonymous, body: void 0 };
374
+ }
375
+ current = next;
376
+ }
377
+ }
378
+ function followedByFetch(response, current) {
379
+ if (response.type === "opaqueredirect") return "fetch \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u0441\u043A\u0440\u044B\u043B \u0440\u0435\u0434\u0438\u0440\u0435\u043A\u0442 (opaqueredirect), \u043F\u0440\u043E\u0439\u0442\u0438 \u0435\u0433\u043E \u043D\u0435\u043B\u044C\u0437\u044F";
380
+ let moved = response.redirected;
381
+ if (!moved && response.url !== "") {
382
+ try {
383
+ moved = new URL(response.url).origin !== new URL(current).origin;
384
+ } catch {
385
+ moved = true;
386
+ }
387
+ }
388
+ return moved ? "fetch \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u0441\u0430\u043C \u043F\u0440\u043E\u0448\u0451\u043B \u0440\u0435\u0434\u0438\u0440\u0435\u043A\u0442 \u2014 \u043E\u0431\u0451\u0440\u0442\u043A\u0430 fetch \u0434\u043E\u043B\u0436\u043D\u0430 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044C init.redirect \u0434\u0430\u043B\u044C\u0448\u0435" : void 0;
389
+ }
390
+ function redirectTarget(location, current) {
391
+ let next;
392
+ try {
393
+ next = new URL(location, current);
394
+ } catch {
395
+ throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(current)} \u0432\u0435\u0440\u043D\u0443\u043B \u0440\u0435\u0434\u0438\u0440\u0435\u043A\u0442 \u043D\u0435 \u043D\u0430 URL.`);
396
+ }
397
+ if (next.protocol !== "http:" && next.protocol !== "https:") throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(current)} \u0432\u0435\u0440\u043D\u0443\u043B \u0440\u0435\u0434\u0438\u0440\u0435\u043A\u0442 \u043D\u0435 \u043D\u0430 http \u0438\u043B\u0438 https.`);
398
+ return next.href;
399
+ }
400
+ function originOf(value, subject) {
401
+ let parsed;
402
+ try {
403
+ parsed = new URL(value);
404
+ } catch {
405
+ throw new TrackerError("invalid_request", `${subject} \u2014 \u043D\u0435 URL.`);
339
406
  }
407
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new TrackerError("invalid_request", `${subject} \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C http \u0438\u043B\u0438 https.`);
408
+ return parsed.origin;
340
409
  }
341
410
  var ERROR_BODY_READ_MAX = 16 * 1024;
342
411
  async function requestText(url, request) {
@@ -346,21 +415,17 @@ async function requestText(url, request) {
346
415
  if (![skip, bytes, readMax].every((value) => Number.isSafeInteger(value) && value >= 0)) {
347
416
  throw new TrackerError("invalid_request", "\u041E\u043A\u043D\u043E \u0447\u0442\u0435\u043D\u0438\u044F \u0442\u0435\u043A\u0441\u0442\u0430 \u2014 \u0446\u0435\u043B\u044B\u0435 \u0447\u0438\u0441\u043B\u0430 \u0431\u0430\u0439\u0442 \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435 0.");
348
417
  }
349
- let response;
350
- try {
351
- response = await request.fetch(url, {
352
- method: "GET",
353
- headers: { accept: request.accept ?? "text/plain", ...request.headers },
354
- signal: AbortSignal.timeout(request.timeoutMs ?? CI_LOG_TIMEOUT_MS)
355
- });
356
- } catch (error) {
357
- throw notAnswered(url, error);
358
- }
418
+ const accept = { accept: request.accept ?? "text/plain" };
419
+ const { response, url: final } = await send(url, request, AbortSignal.timeout(request.timeoutMs ?? CI_LOG_TIMEOUT_MS), accept, {
420
+ method: "GET",
421
+ headers: { ...accept, ...request.headers },
422
+ body: void 0
423
+ });
359
424
  if (!response.ok) {
360
- const head = await readBody(url, response, { skip: 0, bytes: ERROR_BODY_READ_MAX, from: "head", readMax: ERROR_BODY_READ_MAX });
361
- throw failureOf(url, response, decodeUtf8(head.window), request);
425
+ const head = await readBody(final, response, { skip: 0, bytes: ERROR_BODY_READ_MAX, from: "head", readMax: ERROR_BODY_READ_MAX });
426
+ throw failureOf(final, response, decodeUtf8(head.window), request);
362
427
  }
363
- const body = await readBody(url, response, { skip, bytes, from, readMax });
428
+ const body = await readBody(final, response, { skip, bytes, from, readMax });
364
429
  let start = Math.min(body.start, body.end);
365
430
  let end = body.end;
366
431
  let window = body.window;
@@ -743,6 +808,7 @@ function createGithubCiAdapter({ connection, fetch }) {
743
808
  const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
744
809
  if (!TARGET.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
745
810
  const api = baseUrl(connection.url, DEFAULT_API);
811
+ const origin = new URL(api).origin;
746
812
  const root = `${api}/repos/${target}`;
747
813
  const actions = `${root}/actions`;
748
814
  const headers = {
@@ -760,13 +826,13 @@ function createGithubCiAdapter({ connection, fetch }) {
760
826
  };
761
827
  const missingRun = (key) => ({ failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.` });
762
828
  const missingJob = (key) => ({ failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.` });
763
- const getRun = async (key) => runOf((await requestJson(`${actions}/runs/${runKey(key)}`, { fetch, headers, missing: missingRun(key) })).body);
829
+ const getRun = async (key) => runOf((await requestJson(`${actions}/runs/${runKey(key)}`, { fetch, origin, headers, missing: missingRun(key) })).body);
764
830
  const headShaOf = async (query) => {
765
831
  if (query.pullRequest === void 0) return query.sha;
766
832
  if (query.sha !== void 0) throw new TrackerError("invalid_request", `${RUNS_SUBJECT}: sha \u0438 pullRequest \u0437\u0430\u0434\u0430\u044E\u0442\u0441\u044F \u043F\u043E \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u0438 \u2014 \u043F\u0440\u043E\u0433\u043E\u043D\u044B PR \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u043F\u043E \u0435\u0433\u043E \u0433\u043E\u043B\u043E\u0432\u0435.`);
767
833
  const pull = query.pullRequest;
768
834
  if (!ID.test(pull)) throw new TrackerError("not_found", `\u0412 GitHub \u043D\u0435\u0442 pull request ${pull}.`);
769
- const { body } = await requestJson(`${root}/pulls/${pull}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 pull request ${pull}.` } });
835
+ const { body } = await requestJson(`${root}/pulls/${pull}`, { fetch, origin, headers, missing: { failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 pull request ${pull}.` } });
770
836
  const head = isRecord(body) && isRecord(body.head) ? text(body.head.sha) : void 0;
771
837
  if (head === void 0) throw new TrackerError("failed", "GitHub \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u0433\u043E\u043B\u043E\u0432\u0443 pull request.");
772
838
  return head;
@@ -776,7 +842,7 @@ function createGithubCiAdapter({ connection, fetch }) {
776
842
  assertListQuery(WORKFLOWS_SUBJECT, { filters: [], clientSide: [], sorts: [] }, query, []);
777
843
  const page = pageOf(query.cursor);
778
844
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
779
- const { body, headers: responseHeaders } = await requestJson(`${actions}/workflows?${params.toString()}`, { fetch, headers });
845
+ const { body, headers: responseHeaders } = await requestJson(`${actions}/workflows?${params.toString()}`, { fetch, origin, headers });
780
846
  return { items: rowsOf(body, "workflows").map(workflowOf), ...nextCursorOf(page, responseHeaders.get("link") ?? "") };
781
847
  },
782
848
  listRuns: async (query) => {
@@ -794,7 +860,7 @@ function createGithubCiAdapter({ connection, fetch }) {
794
860
  const created = createdParam(query);
795
861
  if (created !== void 0) params.set("created", created);
796
862
  const path = query.workflow === void 0 ? `${actions}/runs` : `${actions}/workflows/${encodeURIComponent(query.workflow)}/runs`;
797
- const { body, headers: responseHeaders } = await requestJson(`${path}?${params.toString()}`, { fetch, headers });
863
+ const { body, headers: responseHeaders } = await requestJson(`${path}?${params.toString()}`, { fetch, origin, headers });
798
864
  const items = rowsOf(body, "workflow_runs").map(runOf).filter(
799
865
  (run) => (query.status === void 0 || query.status.includes(run.status.category)) && (query.event === void 0 || run.event !== void 0 && query.event.includes(run.event.category)) && inTimeRange(run.updatedAt, query.updatedAfter, query.updatedBefore)
800
866
  );
@@ -807,22 +873,23 @@ function createGithubCiAdapter({ connection, fetch }) {
807
873
  assertListQuery(JOBS_SUBJECT, CAPABILITIES.jobs, rest, JOB_FILTERS);
808
874
  const page = pageOf(query.cursor);
809
875
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page), filter: allAttempts === true ? "all" : "latest" });
810
- const { body, headers: responseHeaders } = await requestJson(`${actions}/runs/${run}/jobs?${params.toString()}`, { fetch, headers, missing: missingRun(run) });
876
+ const { body, headers: responseHeaders } = await requestJson(`${actions}/runs/${run}/jobs?${params.toString()}`, { fetch, origin, headers, missing: missingRun(run) });
811
877
  const items = rowsOf(body, "jobs").map(jobOf).filter((job) => query.status === void 0 || query.status.includes(job.status.category));
812
878
  return { items, ...nextCursorOf(page, responseHeaders.get("link") ?? "") };
813
879
  },
814
880
  getJob: async (key) => {
815
881
  const id = jobKey(key);
816
- return jobOf((await requestJson(`${actions}/jobs/${id}`, { fetch, headers, missing: missingJob(id) })).body);
882
+ return jobOf((await requestJson(`${actions}/jobs/${id}`, { fetch, origin, headers, missing: missingJob(id) })).body);
817
883
  },
818
884
  log: async (key, query) => {
819
885
  const id = jobKey(key);
820
886
  const window = logWindow(query);
821
- const job = (await requestJson(`${actions}/jobs/${id}`, { fetch, headers, missing: missingJob(id) })).body;
887
+ const job = (await requestJson(`${actions}/jobs/${id}`, { fetch, origin, headers, missing: missingJob(id) })).body;
822
888
  const complete = isFinished(statusOf(isRecord(job) ? job : {}).category);
823
889
  if (!complete) return { text: "", truncated: false, complete: false, nextCursor: logCursor(0, 0) };
824
890
  const response = await requestText(`${actions}/jobs/${id}/logs`, {
825
891
  fetch,
892
+ origin,
826
893
  headers,
827
894
  skip: window.resume?.offset ?? 0,
828
895
  keep: { bytes: window.bytes, from: window.from },
@@ -851,6 +918,7 @@ function createGithubCiAdapter({ connection, fetch }) {
851
918
  if (count > DISPATCH_INPUTS_MAX) throw new TrackerError("invalid_request", `${TRIGGER_SUBJECT}: workflow_dispatch \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${String(DISPATCH_INPUTS_MAX)} \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432.`);
852
919
  const { body } = await requestJson(`${actions}/workflows/${encodeURIComponent(workflow)}/dispatches`, {
853
920
  fetch,
921
+ origin,
854
922
  headers,
855
923
  method: "POST",
856
924
  body: { ref: input.ref, ...count === 0 ? {} : { inputs }, return_run_details: true }
@@ -862,6 +930,7 @@ function createGithubCiAdapter({ connection, fetch }) {
862
930
  const run = runKey(key);
863
931
  await requestJson(`${actions}/runs/${run}/${input.failedOnly === true ? "rerun-failed-jobs" : "rerun"}`, {
864
932
  fetch,
933
+ origin,
865
934
  headers,
866
935
  method: "POST",
867
936
  body: { enable_debug_logging: input.debug === true },
@@ -871,15 +940,15 @@ function createGithubCiAdapter({ connection, fetch }) {
871
940
  },
872
941
  rerunJob: async (key, input) => {
873
942
  const id = jobKey(key);
874
- const job = (await requestJson(`${actions}/jobs/${id}`, { fetch, headers, missing: missingJob(id) })).body;
943
+ const job = (await requestJson(`${actions}/jobs/${id}`, { fetch, origin, headers, missing: missingJob(id) })).body;
875
944
  const run = runIdOf(isRecord(job) ? job.run_id : void 0);
876
945
  if (run === void 0) throw new TrackerError("failed", "GitHub \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u0434\u0430\u043D\u0438\u044F.");
877
- await requestJson(`${actions}/jobs/${id}/rerun`, { fetch, headers, method: "POST", body: { enable_debug_logging: input.debug === true }, missing: missingJob(id) });
946
+ await requestJson(`${actions}/jobs/${id}/rerun`, { fetch, origin, headers, method: "POST", body: { enable_debug_logging: input.debug === true }, missing: missingJob(id) });
878
947
  return getRun(run);
879
948
  },
880
949
  cancel: async (key) => {
881
950
  const run = runKey(key);
882
- await requestJson(`${actions}/runs/${run}/cancel`, { fetch, headers, method: "POST", conflictOn: [409], missing: missingRun(run) });
951
+ await requestJson(`${actions}/runs/${run}/cancel`, { fetch, origin, headers, method: "POST", conflictOn: [409], missing: missingRun(run) });
883
952
  return getRun(run);
884
953
  },
885
954
  cancelJob: async () => {
@@ -1018,7 +1087,9 @@ function gitlabCi() {
1018
1087
  function createGitlabCiAdapter({ connection, fetch }) {
1019
1088
  const target = requireTarget(connection.target, "\u043F\u0443\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 GitLab, group/project, \u0438\u043B\u0438 \u0435\u0433\u043E id");
1020
1089
  if (!TARGET2.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
1021
- const api = `${baseUrl(connection.url, DEFAULT_URL)}/api/v4`;
1090
+ const site = baseUrl(connection.url, DEFAULT_URL);
1091
+ const origin = new URL(site).origin;
1092
+ const api = `${site}/api/v4`;
1022
1093
  const project = `${api}/projects/${encodeURIComponent(target)}`;
1023
1094
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
1024
1095
  const rawOf = (table, categories) => {
@@ -1036,7 +1107,7 @@ function createGitlabCiAdapter({ connection, fetch }) {
1036
1107
  };
1037
1108
  const missingPipeline = (key) => ({ failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.` });
1038
1109
  const missingJob = (key) => ({ failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.` });
1039
- const getPipeline = async (key) => runOf2((await requestJson(`${project}/pipelines/${pipelineKey(key)}`, { fetch, headers, missing: missingPipeline(key) })).body);
1110
+ const getPipeline = async (key) => runOf2((await requestJson(`${project}/pipelines/${pipelineKey(key)}`, { fetch, origin, headers, missing: missingPipeline(key) })).body);
1040
1111
  return {
1041
1112
  listWorkflows: async () => {
1042
1113
  throw new TrackerError("unsupported", "\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI GitLab: \u0441\u043F\u0438\u0441\u043A\u0430 workflow \u0443 GitLab \u043D\u0435\u0442.");
@@ -1053,7 +1124,7 @@ function createGitlabCiAdapter({ connection, fetch }) {
1053
1124
  if (query.sort !== void 0) throw new TrackerError("unsupported", "\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI GitLab \u0434\u043B\u044F merge request: \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0430 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.");
1054
1125
  if (query.actor !== void 0) throw new TrackerError("unsupported", "\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI GitLab \u0434\u043B\u044F merge request: \u0444\u0438\u043B\u044C\u0442\u0440 actor \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.");
1055
1126
  if (query.workflow !== void 0) throw new TrackerError("unsupported", "\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI GitLab \u0434\u043B\u044F merge request: \u0444\u0438\u043B\u044C\u0442\u0440 workflow \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.");
1056
- const result = await requestJson(`${project}/merge_requests/${mr}/pipelines?${params.toString()}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 merge request ${mr}.` } });
1127
+ const result = await requestJson(`${project}/merge_requests/${mr}/pipelines?${params.toString()}`, { fetch, origin, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 merge request ${mr}.` } });
1057
1128
  body = result.body;
1058
1129
  responseHeaders = result.headers;
1059
1130
  keep = (run) => (query.status === void 0 || query.status.includes(run.status.category)) && (query.event === void 0 || run.event !== void 0 && query.event.includes(run.event.category)) && (query.ref === void 0 || run.ref === query.ref) && (query.sha === void 0 || run.sha === query.sha) && inTimeRange(run.createdAt, query.createdAfter, query.createdBefore) && inTimeRange(run.updatedAt, query.updatedAfter, query.updatedBefore);
@@ -1075,7 +1146,7 @@ function createGitlabCiAdapter({ connection, fetch }) {
1075
1146
  if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
1076
1147
  params.set("order_by", query.sort?.field === "updated" ? "updated_at" : "id");
1077
1148
  params.set("sort", query.sort?.direction ?? "desc");
1078
- const result = await requestJson(`${project}/pipelines?${params.toString()}`, { fetch, headers });
1149
+ const result = await requestJson(`${project}/pipelines?${params.toString()}`, { fetch, origin, headers });
1079
1150
  body = result.body;
1080
1151
  responseHeaders = result.headers;
1081
1152
  keep = (run) => (query.status === void 0 || query.status.includes(run.status.category)) && (query.event === void 0 || run.event !== void 0 && query.event.includes(run.event.category));
@@ -1090,18 +1161,18 @@ function createGitlabCiAdapter({ connection, fetch }) {
1090
1161
  assertListQuery("\u0417\u0430\u0434\u0430\u043D\u0438\u044F CI GitLab", CAPABILITIES2.jobs, rest, JOB_FILTERS2);
1091
1162
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(pageOf2(query.cursor)), include_retried: String(allAttempts ?? false) });
1092
1163
  for (const scope of keysFor(GITLAB_CI_STATUSES, query.status ?? [])) params.append("scope[]", scope);
1093
- const { body, headers: responseHeaders } = await requestJson(`${project}/pipelines/${pipelineKey(runKey)}/jobs?${params.toString()}`, { fetch, headers, missing: missingPipeline(runKey) });
1164
+ const { body, headers: responseHeaders } = await requestJson(`${project}/pipelines/${pipelineKey(runKey)}/jobs?${params.toString()}`, { fetch, origin, headers, missing: missingPipeline(runKey) });
1094
1165
  const items = (Array.isArray(body) ? body : []).map(jobOf2);
1095
1166
  const next = responseHeaders.get("x-next-page");
1096
1167
  return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
1097
1168
  },
1098
- getJob: async (key) => jobOf2((await requestJson(`${project}/jobs/${jobKey(key)}`, { fetch, headers, missing: missingJob(key) })).body),
1169
+ getJob: async (key) => jobOf2((await requestJson(`${project}/jobs/${jobKey(key)}`, { fetch, origin, headers, missing: missingJob(key) })).body),
1099
1170
  log: async (key, query) => {
1100
1171
  const window = logWindow(query);
1101
- const job = (await requestJson(`${project}/jobs/${jobKey(key)}`, { fetch, headers, missing: missingJob(key) })).body;
1172
+ const job = (await requestJson(`${project}/jobs/${jobKey(key)}`, { fetch, origin, headers, missing: missingJob(key) })).body;
1102
1173
  const rawStatus = isRecord(job) && typeof job.status === "string" ? job.status : "unknown";
1103
1174
  const complete = isFinished(lookup(GITLAB_CI_STATUSES, rawStatus, "running"));
1104
- const response = await requestText(`${project}/jobs/${jobKey(key)}/trace`, { fetch, headers, skip: window.resume?.offset ?? 0, keep: { bytes: window.bytes, from: window.from }, missing: missingJob(key) });
1175
+ const response = await requestText(`${project}/jobs/${jobKey(key)}/trace`, { fetch, origin, headers, skip: window.resume?.offset ?? 0, keep: { bytes: window.bytes, from: window.from }, missing: missingJob(key) });
1105
1176
  return logOf({
1106
1177
  text: response.text,
1107
1178
  truncated: window.from === "tail" ? response.start > 0 : !response.whole,
@@ -1123,6 +1194,7 @@ function createGitlabCiAdapter({ connection, fetch }) {
1123
1194
  const variables = Object.entries(input.inputs ?? {}).map(([key, value]) => ({ key, value, variable_type: "env_var" }));
1124
1195
  const { body } = await requestJson(`${project}/pipeline`, {
1125
1196
  fetch,
1197
+ origin,
1126
1198
  headers,
1127
1199
  method: "POST",
1128
1200
  body: { ref: input.ref, ...variables.length === 0 ? {} : { variables } },
@@ -1133,17 +1205,17 @@ function createGitlabCiAdapter({ connection, fetch }) {
1133
1205
  rerun: async (runKey, input) => {
1134
1206
  if (input.failedOnly !== true) throw new TrackerError("unsupported", "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A CI GitLab \u0443\u043C\u0435\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0443\u043F\u0430\u0432\u0448\u0438\u0435 \u0437\u0430\u0434\u0430\u043D\u0438\u044F: \u0437\u0430\u0434\u0430\u0439\u0442\u0435 failedOnly.");
1135
1207
  if (input.debug === true) throw new TrackerError("unsupported", "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A CI GitLab \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043E\u0442\u043B\u0430\u0434\u043E\u0447\u043D\u043E\u0435 \u043B\u043E\u0433\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435.");
1136
- return runOf2((await requestJson(`${project}/pipelines/${pipelineKey(runKey)}/retry`, { fetch, headers, method: "POST", missing: missingPipeline(runKey) })).body);
1208
+ return runOf2((await requestJson(`${project}/pipelines/${pipelineKey(runKey)}/retry`, { fetch, origin, headers, method: "POST", missing: missingPipeline(runKey) })).body);
1137
1209
  },
1138
1210
  rerunJob: async (jobKeyValue, input) => {
1139
1211
  if (input.debug === true) throw new TrackerError("unsupported", "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0437\u0430\u0434\u0430\u043D\u0438\u044F GitLab \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u043E\u0442\u043B\u0430\u0434\u043E\u0447\u043D\u043E\u0435 \u043B\u043E\u0433\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435.");
1140
- const body = (await requestJson(`${project}/jobs/${jobKey(jobKeyValue)}/retry`, { fetch, headers, method: "POST", missing: missingJob(jobKeyValue) })).body;
1212
+ const body = (await requestJson(`${project}/jobs/${jobKey(jobKeyValue)}/retry`, { fetch, origin, headers, method: "POST", missing: missingJob(jobKeyValue) })).body;
1141
1213
  const pipeline = isRecord(body) && isRecord(body.pipeline) && typeof body.pipeline.id === "number" ? String(body.pipeline.id) : void 0;
1142
1214
  if (pipeline === void 0) throw new TrackerError("failed", "GitLab \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u043F\u0430\u0439\u043F\u043B\u0430\u0439\u043D \u043D\u043E\u0432\u043E\u0433\u043E \u0437\u0430\u0434\u0430\u043D\u0438\u044F.");
1143
1215
  return getPipeline(pipeline);
1144
1216
  },
1145
- cancel: async (runKey) => runOf2((await requestJson(`${project}/pipelines/${pipelineKey(runKey)}/cancel`, { fetch, headers, method: "POST", missing: missingPipeline(runKey) })).body),
1146
- cancelJob: async (jobKeyValue) => jobOf2((await requestJson(`${project}/jobs/${jobKey(jobKeyValue)}/cancel`, { fetch, headers, method: "POST", missing: missingJob(jobKeyValue) })).body)
1217
+ cancel: async (runKey) => runOf2((await requestJson(`${project}/pipelines/${pipelineKey(runKey)}/cancel`, { fetch, origin, headers, method: "POST", missing: missingPipeline(runKey) })).body),
1218
+ cancelJob: async (jobKeyValue) => jobOf2((await requestJson(`${project}/jobs/${jobKey(jobKeyValue)}/cancel`, { fetch, origin, headers, method: "POST", missing: missingJob(jobKeyValue) })).body)
1147
1219
  };
1148
1220
  }
1149
1221
  function pageOf2(cursor) {
@@ -1228,7 +1300,9 @@ function sourcecraftCi() {
1228
1300
  function createSourceCraftCiAdapter({ connection, fetch }) {
1229
1301
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
1230
1302
  if (!TARGET3.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
1231
- const repo = `${baseUrl(connection.url, DEFAULT_API2)}/repos/${target}`;
1303
+ const api = baseUrl(connection.url, DEFAULT_API2);
1304
+ const origin = new URL(api).origin;
1305
+ const repo = `${api}/repos/${target}`;
1232
1306
  const root = `${repo}/cicd`;
1233
1307
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
1234
1308
  const missingRun = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.` });
@@ -1245,14 +1319,14 @@ function createSourceCraftCiAdapter({ connection, fetch }) {
1245
1319
  runKey(path.run);
1246
1320
  return path;
1247
1321
  };
1248
- const getRun = async (key) => runOf3((await requestJson(`${root}/runs/${runKey(key)}`, { fetch, headers, missing: missingRun(key) })).body);
1322
+ const getRun = async (key) => runOf3((await requestJson(`${root}/runs/${runKey(key)}`, { fetch, origin, headers, missing: missingRun(key) })).body);
1249
1323
  const getWorkflow = async (run, workflow) => {
1250
- const { body } = await requestJson(`${root}/runs/${runKey(run)}/${encodeURIComponent(workflow)}`, { fetch, headers, missing: missingWorkflow(run, workflow) });
1324
+ const { body } = await requestJson(`${root}/runs/${runKey(run)}/${encodeURIComponent(workflow)}`, { fetch, origin, headers, missing: missingWorkflow(run, workflow) });
1251
1325
  return isRecord(body) ? body : {};
1252
1326
  };
1253
1327
  const pullIdOf = async (slug) => {
1254
1328
  if (!PR_SLUG.test(slug)) throw new TrackerError("invalid_request", `\u041A\u043B\u044E\u0447 pull request SourceCraft \u2014 slug \u0438\u0437 \u0431\u0443\u043A\u0432, \u0446\u0438\u0444\u0440, _ \u0438 - \u0434\u043B\u0438\u043D\u043E\u0439 \u0434\u043E 100.`);
1255
- const { body } = await requestJson(`${repo}/pulls/${slug}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 pull request ${slug}.` } });
1329
+ const { body } = await requestJson(`${repo}/pulls/${slug}`, { fetch, origin, headers, missing: { failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 pull request ${slug}.` } });
1256
1330
  const id = isRecord(body) ? text(body.id) : void 0;
1257
1331
  if (id === void 0) throw new TrackerError("failed", `\u041E\u0442\u0432\u0435\u0442 SourceCraft \u043D\u0430 /pulls/${slug} \u043D\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 id.`);
1258
1332
  return id;
@@ -1282,7 +1356,7 @@ function createSourceCraftCiAdapter({ connection, fetch }) {
1282
1356
  const pullId = query.pullRequest === void 0 ? void 0 : await pullIdOf(query.pullRequest);
1283
1357
  const params = new URLSearchParams({ page_size: String(query.limit) });
1284
1358
  if (query.cursor !== void 0) params.set("page_token", query.cursor);
1285
- const { body } = await requestJson(`${root}/runs?${params.toString()}`, { fetch, headers });
1359
+ const { body } = await requestJson(`${root}/runs?${params.toString()}`, { fetch, origin, headers });
1286
1360
  const record = isRecord(body) ? body : {};
1287
1361
  const items = recordList(record.runs).map((raw) => pullSlugOf(runOf3(raw), raw, pullId, query.pullRequest)).filter((run) => keepsRun(run, query));
1288
1362
  const token = text(record.next_page_token);
@@ -1334,7 +1408,7 @@ function createSourceCraftCiAdapter({ connection, fetch }) {
1334
1408
  unread = true;
1335
1409
  break;
1336
1410
  }
1337
- const { body } = await requestJson(`${root}/logs/${path}?page=${String(page)}`, { fetch, headers, missing: missingJob(key) });
1411
+ const { body } = await requestJson(`${root}/logs/${path}?page=${String(page)}`, { fetch, origin, headers, missing: missingJob(key) });
1338
1412
  const record = isRecord(body) ? body : {};
1339
1413
  const data = encoder.encode(text(record.logs) ?? "");
1340
1414
  const rest = skip >= data.length ? new Uint8Array(0) : data.subarray(skip);
@@ -1387,7 +1461,7 @@ function createSourceCraftCiAdapter({ connection, fetch }) {
1387
1461
  if (workflows.length === 0) throw new TrackerError("invalid_request", "\u0417\u0430\u043F\u0443\u0441\u043A CI SourceCraft: \u043D\u0443\u0436\u0435\u043D \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D workflow.");
1388
1462
  const values = Object.entries(input.inputs ?? {}).map(([name, value]) => ({ name, value }));
1389
1463
  const head = input.refType === "tag" ? { tag: input.ref } : { branch: input.ref };
1390
- const { body } = await requestJson(`${root}/runs`, { fetch, headers, method: "POST", body: { head, workflows: workflows.map((name) => ({ name, values })) } });
1464
+ const { body } = await requestJson(`${root}/runs`, { fetch, origin, headers, method: "POST", body: { head, workflows: workflows.map((name) => ({ name, values })) } });
1391
1465
  return { run: runOf3(body) };
1392
1466
  },
1393
1467
  // Публичный API SourceCraft не умеет ни перезапускать, ни отменять (§1.3): отказ без запроса (§3.6).
@@ -1583,6 +1657,7 @@ function createGithubPullAdapter({ connection, fetch }) {
1583
1657
  const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
1584
1658
  if (!TARGET4.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
1585
1659
  const api = baseUrl(connection.url, DEFAULT_API3);
1660
+ const origin = new URL(api).origin;
1586
1661
  const root = `${api}/repos/${target}/pulls`;
1587
1662
  const issues = `${api}/repos/${target}/issues`;
1588
1663
  const owner = target.split("/")[0] ?? target;
@@ -1596,7 +1671,7 @@ function createGithubPullAdapter({ connection, fetch }) {
1596
1671
  if (!NUMBER.test(key)) throw new TrackerError("not_found", `\u0412 GitHub \u043D\u0435\u0442 PR ${key}.`);
1597
1672
  return key;
1598
1673
  };
1599
- const getPull = async (key) => pullOf((await requestJson(`${root}/${number(key)}`, { fetch, headers, missing: missing(key) })).body);
1674
+ const getPull = async (key) => pullOf((await requestJson(`${root}/${number(key)}`, { fetch, origin, headers, missing: missing(key) })).body);
1600
1675
  return {
1601
1676
  list: async (query) => {
1602
1677
  assertListQuery(LIST_SUBJECT, PULL_CAPABILITIES.list, query, pullRequestFilterSchema.options);
@@ -1609,25 +1684,25 @@ function createGithubPullAdapter({ connection, fetch }) {
1609
1684
  params.set("sort", query.sort.field);
1610
1685
  params.set("direction", query.sort.direction ?? "desc");
1611
1686
  }
1612
- const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
1687
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
1613
1688
  const items = (Array.isArray(body) ? body : []).map(pullOf).filter((pull) => matchesPage(pull, query, status));
1614
1689
  return { items, ...nextOf(responseHeaders.get("link"), page) };
1615
1690
  },
1616
1691
  get: (key) => getPull(key),
1617
- create: async (input) => pullOf((await requestJson(root, { fetch, headers, method: "POST", body: createBody(input) })).body),
1692
+ create: async (input) => pullOf((await requestJson(root, { fetch, origin, headers, method: "POST", body: createBody(input) })).body),
1618
1693
  update: async (key, patch) => {
1619
1694
  if (patch.status === "draft") throw new TrackerError("unsupported", "PR GitHub: \u043F\u0435\u0440\u0435\u0432\u043E\u0434 \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0447\u0435\u0440\u0435\u0437 REST.");
1620
1695
  const current = patch.status === "open" ? await getPull(key) : void 0;
1621
1696
  if (current?.status.category === "draft") throw new TrackerError("unsupported", "PR GitHub: \u0441\u043D\u044F\u0442\u0438\u0435 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0447\u0435\u0440\u0435\u0437 REST.");
1622
1697
  if (current !== void 0 && current.status.category === "open" && patch.title === void 0 && patch.description === void 0 && patch.target === void 0) return current;
1623
- return pullOf((await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody(patch), missing: missing(key) })).body);
1698
+ return pullOf((await requestJson(`${root}/${number(key)}`, { fetch, origin, headers, method: "PATCH", body: patchBody(patch), missing: missing(key) })).body);
1624
1699
  },
1625
1700
  merge: async (key, input) => {
1626
1701
  if (input.deleteSource === true) throw new TrackerError("unsupported", "PR GitHub: \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0432\u0435\u0442\u043A\u0438-\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 \u043F\u0440\u0438 \u0441\u043B\u0438\u044F\u043D\u0438\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E.");
1627
1702
  if (input.method !== void 0 && !PULL_CAPABILITIES.merge.includes(input.method)) {
1628
1703
  throw new TrackerError("unsupported", `PR GitHub: \u0441\u043B\u0438\u044F\u043D\u0438\u0435 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C ${input.method} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
1629
1704
  }
1630
- await requestJson(`${root}/${number(key)}/merge`, { fetch, headers, method: "PUT", body: mergeBody(input), conflictOn: [405, 409], missing: missing(key) });
1705
+ await requestJson(`${root}/${number(key)}/merge`, { fetch, origin, headers, method: "PUT", body: mergeBody(input), conflictOn: [405, 409], missing: missing(key) });
1631
1706
  return getPull(key);
1632
1707
  },
1633
1708
  comments: async (key, query) => {
@@ -1635,10 +1710,10 @@ function createGithubPullAdapter({ connection, fetch }) {
1635
1710
  const page = pageOf3(query.cursor);
1636
1711
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
1637
1712
  if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
1638
- const { body, headers: responseHeaders } = await requestJson(`${issues}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missing: missing(key) });
1713
+ const { body, headers: responseHeaders } = await requestJson(`${issues}/${number(key)}/comments?${params.toString()}`, { fetch, origin, headers, missing: missing(key) });
1639
1714
  return { items: (Array.isArray(body) ? body : []).map(commentOf), ...nextOf(responseHeaders.get("link"), page) };
1640
1715
  },
1641
- comment: async (key, body) => commentOf((await requestJson(`${issues}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
1716
+ comment: async (key, body) => commentOf((await requestJson(`${issues}/${number(key)}/comments`, { fetch, origin, headers, method: "POST", body: { body }, missing: missing(key) })).body)
1642
1717
  };
1643
1718
  }
1644
1719
  function pageOf3(cursor) {
@@ -1765,6 +1840,7 @@ function githubTracker() {
1765
1840
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: ["open", "in_progress", "done", "declined"] }
1766
1841
  },
1767
1842
  create: (context) => createGithubAdapter(context, issues),
1843
+ origin: (connection) => new URL(baseUrl(connection.url, DEFAULT_API4)).origin,
1768
1844
  issues,
1769
1845
  pulls: githubPulls(),
1770
1846
  ci: githubCi()
@@ -1774,6 +1850,7 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
1774
1850
  const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
1775
1851
  if (!TARGET5.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
1776
1852
  const api = baseUrl(connection.url, DEFAULT_API4);
1853
+ const origin = new URL(api).origin;
1777
1854
  const root = `${api}/repos/${target}/issues`;
1778
1855
  const headers = {
1779
1856
  accept: "application/vnd.github+json",
@@ -1785,7 +1862,7 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
1785
1862
  return key;
1786
1863
  };
1787
1864
  const rawOf = async (key) => {
1788
- const { body } = await requestJson(`${root}/${number(key)}`, { fetch, headers, missingIsIssue: true });
1865
+ const { body } = await requestJson(`${root}/${number(key)}`, { fetch, origin, headers, missingIsIssue: true });
1789
1866
  return isRecord(body) ? body : {};
1790
1867
  };
1791
1868
  return {
@@ -1795,7 +1872,7 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
1795
1872
  const page = pageOf4(query.cursor);
1796
1873
  const search = needsSearch(query);
1797
1874
  const params = search ? searchParams(target, query, page) : issuesParams(query, page);
1798
- const { body, headers: responseHeaders } = await requestJson(`${search ? `${api}/search/issues` : root}?${params.toString()}`, { fetch, headers });
1875
+ const { body, headers: responseHeaders } = await requestJson(`${search ? `${api}/search/issues` : root}?${params.toString()}`, { fetch, origin, headers });
1799
1876
  return issuePageOf(rowsOf2(body, search), query, responseHeaders.get("link") ?? "", page);
1800
1877
  },
1801
1878
  get: async (key) => issueOf(await rawOf(key)),
@@ -1807,11 +1884,11 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
1807
1884
  ...labels.length === 0 ? {} : { labels },
1808
1885
  ...input.assignee === void 0 ? {} : { assignees: [input.assignee] }
1809
1886
  };
1810
- return issueOf((await requestJson(root, { fetch, headers, method: "POST", body })).body);
1887
+ return issueOf((await requestJson(root, { fetch, origin, headers, method: "POST", body })).body);
1811
1888
  },
1812
1889
  update: async (key, patch) => {
1813
1890
  const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : issueOf(await rawOf(key));
1814
- const response = await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody2(patch, current), missingIsIssue: true });
1891
+ const response = await requestJson(`${root}/${number(key)}`, { fetch, origin, headers, method: "PATCH", body: patchBody2(patch, current), missingIsIssue: true });
1815
1892
  return issueOf(response.body);
1816
1893
  },
1817
1894
  comments: async (key, query) => {
@@ -1819,10 +1896,10 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
1819
1896
  const page = pageOf4(query.cursor);
1820
1897
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
1821
1898
  if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
1822
- const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
1899
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
1823
1900
  return { items: (Array.isArray(body) ? body : []).map(commentOf2), .../rel="next"/.test(responseHeaders.get("link") ?? "") ? { nextCursor: String(page + 1) } : {} };
1824
1901
  },
1825
- comment: async (key, body) => commentOf2((await requestJson(`${root}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1902
+ comment: async (key, body) => commentOf2((await requestJson(`${root}/${number(key)}/comments`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1826
1903
  };
1827
1904
  }
1828
1905
  function pageOf4(cursor) {
@@ -1970,13 +2047,15 @@ function gitlabPulls() {
1970
2047
  function createGitlabPullAdapter({ connection, fetch }) {
1971
2048
  const target = requireTarget(connection.target, "\u043F\u0443\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 GitLab, group/project, \u0438\u043B\u0438 \u0435\u0433\u043E id");
1972
2049
  if (!TARGET6.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
1973
- const root = `${baseUrl(connection.url, DEFAULT_URL2)}/api/v4/projects/${encodeURIComponent(target)}/merge_requests`;
2050
+ const site = baseUrl(connection.url, DEFAULT_URL2);
2051
+ const origin = new URL(site).origin;
2052
+ const root = `${site}/api/v4/projects/${encodeURIComponent(target)}/merge_requests`;
1974
2053
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
1975
2054
  const iid = (key) => {
1976
2055
  if (!IID.test(key)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.`);
1977
2056
  return key;
1978
2057
  };
1979
- const currentOf = async (key) => pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
2058
+ const currentOf = async (key) => pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, origin, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
1980
2059
  return {
1981
2060
  list: async (query) => {
1982
2061
  assertListQuery("PR GitLab", PULL_CAPABILITIES2.list, query, pullRequestFilterSchema.options);
@@ -2001,7 +2080,7 @@ function createGitlabPullAdapter({ connection, fetch }) {
2001
2080
  if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
2002
2081
  if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
2003
2082
  if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
2004
- const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
2083
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2005
2084
  const next = responseHeaders.get("x-next-page");
2006
2085
  const items = (Array.isArray(body) ? body : []).map(pullOf2);
2007
2086
  return {
@@ -2017,7 +2096,7 @@ function createGitlabPullAdapter({ connection, fetch }) {
2017
2096
  title: (input.draft === true ? GITLAB_DRAFT_PREFIX : "") + input.title,
2018
2097
  ...input.description === void 0 ? {} : { description: input.description }
2019
2098
  };
2020
- return pullOf2((await requestJson(root, { fetch, headers, method: "POST", body, conflictOn: [409] })).body);
2099
+ return pullOf2((await requestJson(root, { fetch, origin, headers, method: "POST", body, conflictOn: [409] })).body);
2021
2100
  },
2022
2101
  update: async (key, patch) => {
2023
2102
  const current = patch.status === void 0 ? void 0 : await currentOf(key);
@@ -2025,8 +2104,8 @@ function createGitlabPullAdapter({ connection, fetch }) {
2025
2104
  throw new TrackerError("unsupported", "PR GitLab: \u043F\u0435\u0440\u0435\u0432\u043E\u0434 \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u0433\u043E MR \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D.");
2026
2105
  }
2027
2106
  const body = patchBody3(patch, current);
2028
- if (Object.keys(body).length === 0) return current ?? pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
2029
- return pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
2107
+ if (Object.keys(body).length === 0) return current ?? pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, origin, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
2108
+ return pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, origin, headers, method: "PUT", body, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
2030
2109
  },
2031
2110
  merge: async (key, input) => {
2032
2111
  if (input.method !== void 0 && !PULL_CAPABILITIES2.merge.includes(input.method)) {
@@ -2039,18 +2118,18 @@ function createGitlabPullAdapter({ connection, fetch }) {
2039
2118
  ...input.deleteSource === void 0 ? {} : { should_remove_source_branch: input.deleteSource },
2040
2119
  ...input.headSha === void 0 ? {} : { sha: input.headSha }
2041
2120
  };
2042
- return pullOf2((await requestJson(`${root}/${iid(key)}/merge`, { fetch, headers, method: "PUT", body, conflictOn: [405, 409, 422], missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
2121
+ return pullOf2((await requestJson(`${root}/${iid(key)}/merge`, { fetch, origin, headers, method: "PUT", body, conflictOn: [405, 409, 422], missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
2043
2122
  },
2044
2123
  comments: async (key, query) => {
2045
2124
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", PULL_CAPABILITIES2.comments, query);
2046
2125
  const page = pageOf5(query.cursor);
2047
2126
  const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
2048
- const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } });
2127
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, origin, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } });
2049
2128
  const next = responseHeaders.get("x-next-page");
2050
2129
  const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(commentOf3);
2051
2130
  return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
2052
2131
  },
2053
- comment: async (key, body) => commentOf3((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body)
2132
+ comment: async (key, body) => commentOf3((await requestJson(`${root}/${iid(key)}/notes`, { fetch, origin, headers, method: "POST", body: { body }, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body)
2054
2133
  };
2055
2134
  }
2056
2135
  function pageOf5(cursor) {
@@ -2165,6 +2244,7 @@ function gitlabTracker() {
2165
2244
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
2166
2245
  },
2167
2246
  create: (context) => createGitlabAdapter(context),
2247
+ origin: (connection) => new URL(baseUrl(connection.url, DEFAULT_URL3)).origin,
2168
2248
  issues: ISSUE_CAPABILITIES,
2169
2249
  pulls: gitlabPulls(),
2170
2250
  ci: gitlabCi()
@@ -2173,13 +2253,15 @@ function gitlabTracker() {
2173
2253
  function createGitlabAdapter({ connection, fetch }) {
2174
2254
  const target = requireTarget(connection.target, "\u043F\u0443\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 GitLab, group/project, \u0438\u043B\u0438 \u0435\u0433\u043E id");
2175
2255
  if (!TARGET7.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
2176
- const root = `${baseUrl(connection.url, DEFAULT_URL3)}/api/v4/projects/${encodeURIComponent(target)}/issues`;
2256
+ const site = baseUrl(connection.url, DEFAULT_URL3);
2257
+ const origin = new URL(site).origin;
2258
+ const root = `${site}/api/v4/projects/${encodeURIComponent(target)}/issues`;
2177
2259
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
2178
2260
  const iid = (key) => {
2179
2261
  if (!IID2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
2180
2262
  return key;
2181
2263
  };
2182
- const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missingIsIssue: true })).body);
2264
+ const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, origin, headers, missingIsIssue: true })).body);
2183
2265
  return {
2184
2266
  list: async (query) => {
2185
2267
  assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 GitLab", ISSUE_CAPABILITIES.list, query, ISSUE_FILTERS);
@@ -2202,7 +2284,7 @@ function createGitlabAdapter({ connection, fetch }) {
2202
2284
  if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
2203
2285
  if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
2204
2286
  if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
2205
- const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
2287
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2206
2288
  const items = (Array.isArray(body) ? body : []).map(issueOf2);
2207
2289
  const next = responseHeaders.get("x-next-page");
2208
2290
  return {
@@ -2214,22 +2296,22 @@ function createGitlabAdapter({ connection, fetch }) {
2214
2296
  create: async (input) => {
2215
2297
  const labels = composeLabels2(input.labels ?? [], input.priority, void 0);
2216
2298
  const body = { title: input.title, ...input.description === void 0 ? {} : { description: input.description }, ...labels.length === 0 ? {} : { labels: labels.join(",") } };
2217
- return issueOf2((await requestJson(root, { fetch, headers, method: "POST", body })).body);
2299
+ return issueOf2((await requestJson(root, { fetch, origin, headers, method: "POST", body })).body);
2218
2300
  },
2219
2301
  update: async (key, patch) => {
2220
2302
  const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : await currentOf(key);
2221
- return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body: patchBody4(patch, current), missingIsIssue: true })).body);
2303
+ return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, origin, headers, method: "PUT", body: patchBody4(patch, current), missingIsIssue: true })).body);
2222
2304
  },
2223
2305
  comments: async (key, query) => {
2224
2306
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", ISSUE_CAPABILITIES.comments, query);
2225
2307
  const page = pageOf6(query.cursor);
2226
2308
  const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
2227
- const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missingIsIssue: true });
2309
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
2228
2310
  const next = responseHeaders.get("x-next-page");
2229
2311
  const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(commentOf4);
2230
2312
  return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
2231
2313
  },
2232
- comment: async (key, body) => commentOf4((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2314
+ comment: async (key, body) => commentOf4((await requestJson(`${root}/${iid(key)}/notes`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2233
2315
  };
2234
2316
  }
2235
2317
  function pageOf6(cursor) {
@@ -2315,14 +2397,19 @@ function jiraTracker() {
2315
2397
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
2316
2398
  },
2317
2399
  create: (context) => createJiraAdapter(context),
2400
+ origin: (connection) => new URL(siteOf(connection)).origin,
2318
2401
  issues: ISSUE_CAPABILITIES2
2319
2402
  };
2320
2403
  }
2321
- function createJiraAdapter({ connection, fetch }) {
2404
+ function siteOf(connection) {
2322
2405
  if (connection.url === void 0) throw new TrackerError("invalid_request", "\u041D\u0435 \u0437\u0430\u0434\u0430\u043D \u0430\u0434\u0440\u0435\u0441 Jira.");
2406
+ return baseUrl(connection.url, connection.url);
2407
+ }
2408
+ function createJiraAdapter({ connection, fetch }) {
2409
+ const site = siteOf(connection);
2323
2410
  const project = requireTarget(connection.target, "\u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 Jira, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 PROJ");
2324
2411
  if (!PROJECT_KEY.test(project)) throw new TrackerError("invalid_request", "target Jira \u2014 \u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u0435 \u0431\u0443\u043A\u0432\u044B \u0438 \u0446\u0438\u0444\u0440\u044B.");
2325
- const site = baseUrl(connection.url, connection.url);
2412
+ const origin = new URL(site).origin;
2326
2413
  const api = `${site}/rest/api/2`;
2327
2414
  const token = connection.token ?? "";
2328
2415
  const headers = { authorization: connection.user === void 0 ? `Bearer ${token}` : `Basic ${Buffer.from(`${connection.user}:${token}`).toString("base64")}` };
@@ -2331,7 +2418,7 @@ function createJiraAdapter({ connection, fetch }) {
2331
2418
  return value;
2332
2419
  };
2333
2420
  const toIssue2 = (raw) => issueOf3(raw, site);
2334
- const load = async (value) => toIssue2((await requestJson(`${api}/issue/${key(value)}?fields=${FIELDS}`, { fetch, headers, missingIsIssue: true })).body);
2421
+ const load = async (value) => toIssue2((await requestJson(`${api}/issue/${key(value)}?fields=${FIELDS}`, { fetch, origin, headers, missingIsIssue: true })).body);
2335
2422
  return {
2336
2423
  list: async (query) => {
2337
2424
  assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 Jira", ISSUE_CAPABILITIES2.list, query, ISSUE_FILTERS);
@@ -2341,7 +2428,7 @@ function createJiraAdapter({ connection, fetch }) {
2341
2428
  const params2 = new URLSearchParams({ jql, fields: FIELDS, maxResults: String(query.limit) });
2342
2429
  if (query.cursor !== void 0) params2.set("nextPageToken", query.cursor);
2343
2430
  try {
2344
- const { body: body2 } = await requestJson(`${api}/search/jql?${params2.toString()}`, { fetch, headers });
2431
+ const { body: body2 } = await requestJson(`${api}/search/jql?${params2.toString()}`, { fetch, origin, headers });
2345
2432
  const record2 = isRecord(body2) ? body2 : {};
2346
2433
  const next = text(record2.nextPageToken);
2347
2434
  return { items: (Array.isArray(record2.issues) ? record2.issues : []).map(toIssue2), ...next === void 0 || record2.isLast === true ? {} : { nextCursor: next } };
@@ -2351,7 +2438,7 @@ function createJiraAdapter({ connection, fetch }) {
2351
2438
  }
2352
2439
  const startAt = query.cursor?.startsWith("s:") === true ? Number(query.cursor.slice(2)) : 0;
2353
2440
  const params = new URLSearchParams({ jql, fields: FIELDS, maxResults: String(query.limit), startAt: String(Number.isInteger(startAt) && startAt >= 0 ? startAt : 0) });
2354
- const { body } = await requestJson(`${api}/search?${params.toString()}`, { fetch, headers });
2441
+ const { body } = await requestJson(`${api}/search?${params.toString()}`, { fetch, origin, headers });
2355
2442
  const record = isRecord(body) ? body : {};
2356
2443
  const issues = Array.isArray(record.issues) ? record.issues : [];
2357
2444
  const total = typeof record.total === "number" ? record.total : 0;
@@ -2368,22 +2455,22 @@ function createJiraAdapter({ connection, fetch }) {
2368
2455
  ...input.priority === void 0 ? {} : { priority: { name: JIRA_PRIORITY_NAMES[input.priority] } },
2369
2456
  ...input.labels === void 0 ? {} : { labels: input.labels.map((label) => label.replace(/\s+/g, "-")) }
2370
2457
  };
2371
- const { body } = await requestJson(`${api}/issue`, { fetch, headers, method: "POST", body: { fields } });
2458
+ const { body } = await requestJson(`${api}/issue`, { fetch, origin, headers, method: "POST", body: { fields } });
2372
2459
  const created = isRecord(body) ? text(body.key) : void 0;
2373
2460
  if (created === void 0) throw new TrackerError("failed", "Jira \u0441\u043E\u0437\u0434\u0430\u043B\u0430 \u0437\u0430\u0434\u0430\u0447\u0443, \u043D\u043E \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 \u0435\u0451 \u043A\u043B\u044E\u0447.");
2374
2461
  return load(created);
2375
2462
  },
2376
2463
  update: async (value, patch) => {
2377
2464
  const fields = fieldsOf(patch);
2378
- if (Object.keys(fields).length > 0) await requestJson(`${api}/issue/${key(value)}`, { fetch, headers, method: "PUT", body: { fields }, missingIsIssue: true });
2379
- if (patch.status !== void 0) await transition(api, key(value), patch.status, fetch, headers);
2465
+ if (Object.keys(fields).length > 0) await requestJson(`${api}/issue/${key(value)}`, { fetch, origin, headers, method: "PUT", body: { fields }, missingIsIssue: true });
2466
+ if (patch.status !== void 0) await transition(api, key(value), patch.status, { fetch, origin, headers });
2380
2467
  return load(value);
2381
2468
  },
2382
2469
  comments: async (value, query) => {
2383
2470
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 Jira", ISSUE_CAPABILITIES2.comments, query);
2384
2471
  const startAt = query.cursor === void 0 ? 0 : Number(query.cursor);
2385
2472
  const params = new URLSearchParams({ startAt: String(Number.isInteger(startAt) && startAt >= 0 ? startAt : 0), maxResults: String(query.limit), orderBy: query.order === "desc" ? "-created" : "created" });
2386
- const { body } = await requestJson(`${api}/issue/${key(value)}/comment?${params.toString()}`, { fetch, headers, missingIsIssue: true });
2473
+ const { body } = await requestJson(`${api}/issue/${key(value)}/comment?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
2387
2474
  const record = isRecord(body) ? body : {};
2388
2475
  const raw = Array.isArray(record.comments) ? record.comments : [];
2389
2476
  const items = raw.filter((item) => inTimeRange(toTimestamp(isRecord(item) ? item.updated : void 0), query.updatedAfter, void 0));
@@ -2391,7 +2478,7 @@ function createJiraAdapter({ connection, fetch }) {
2391
2478
  const at = typeof record.startAt === "number" ? record.startAt : 0;
2392
2479
  return { items: items.map(commentOf5), ...at + raw.length < total && raw.length > 0 ? { nextCursor: String(at + raw.length) } : {} };
2393
2480
  },
2394
- comment: async (value, body) => commentOf5((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2481
+ comment: async (value, body) => commentOf5((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2395
2482
  };
2396
2483
  }
2397
2484
  function listJql(project, query) {
@@ -2427,8 +2514,9 @@ function fieldsOf(patch) {
2427
2514
  ...patch.labels === void 0 ? {} : { labels: patch.labels.map((label) => label.replace(/\s+/g, "-")) }
2428
2515
  };
2429
2516
  }
2430
- async function transition(api, key, category, fetch, headers) {
2431
- const { body } = await requestJson(`${api}/issue/${key}/transitions`, { fetch, headers, missingIsIssue: true });
2517
+ async function transition(api, key, category, request) {
2518
+ const { fetch, origin, headers } = request;
2519
+ const { body } = await requestJson(`${api}/issue/${key}/transitions`, { fetch, origin, headers, missingIsIssue: true });
2432
2520
  const list = isRecord(body) && Array.isArray(body.transitions) ? body.transitions : [];
2433
2521
  const match = list.find((item) => {
2434
2522
  if (!isRecord(item) || !isRecord(item.to) || !isRecord(item.to.statusCategory)) return false;
@@ -2436,7 +2524,7 @@ async function transition(api, key, category, fetch, headers) {
2436
2524
  });
2437
2525
  const id = isRecord(match) ? text(match.id) : void 0;
2438
2526
  if (id === void 0) throw new TrackerError("invalid_request", `\u0418\u0437 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u0441\u0442\u0430\u0442\u0443\u0441\u0430 Jira \u043D\u0435\u0442 \u043F\u0435\u0440\u0435\u0445\u043E\u0434\u0430 \u0432 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044E ${category}.`);
2439
- await requestJson(`${api}/issue/${key}/transitions`, { fetch, headers, method: "POST", body: { transition: { id } }, missingIsIssue: true });
2527
+ await requestJson(`${api}/issue/${key}/transitions`, { fetch, origin, headers, method: "POST", body: { transition: { id } }, missingIsIssue: true });
2440
2528
  }
2441
2529
  function issueOf3(raw, site) {
2442
2530
  const item = isRecord(raw) ? raw : {};
@@ -2582,6 +2670,7 @@ function createPullAdapter({ connection, fetch }) {
2582
2670
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
2583
2671
  if (!TARGET8.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
2584
2672
  const api = baseUrl(connection.url, DEFAULT_API5);
2673
+ const origin = new URL(api).origin;
2585
2674
  const root = `${api}/repos/${target}/pulls`;
2586
2675
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
2587
2676
  const missing = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.` });
@@ -2589,7 +2678,7 @@ function createPullAdapter({ connection, fetch }) {
2589
2678
  if (!SLUG.test(key)) throw new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.`);
2590
2679
  return key;
2591
2680
  };
2592
- const getPull = async (key) => pullOf3((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missing: missing(key) })).body);
2681
+ const getPull = async (key) => pullOf3((await requestJson(`${root}/${slug(key)}`, { fetch, origin, headers, missing: missing(key) })).body);
2593
2682
  return {
2594
2683
  list: async (query) => {
2595
2684
  assertListQuery("PR SourceCraft", PULL_CAPABILITIES3.list, query, pullRequestFilterSchema.options);
@@ -2599,13 +2688,13 @@ function createPullAdapter({ connection, fetch }) {
2599
2688
  const filter = pullFilterOf(query, status);
2600
2689
  if (filter !== void 0) params.set("filter", filter);
2601
2690
  if (query.sort !== void 0) params.set("sort_by", `${query.sort.direction === "asc" ? "" : "-"}${PULL_SORT_FIELDS_UPSTREAM[query.sort.field]}`);
2602
- const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
2691
+ const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2603
2692
  const record = isRecord(body) ? body : {};
2604
2693
  const pulls = (Array.isArray(record.pull_requests) ? record.pull_requests : []).map(pullOf3).filter((pull) => matchesPage2(pull, query));
2605
2694
  return { items: pulls, ...nextOf2(record.next_page_token) };
2606
2695
  },
2607
2696
  get: (key) => getPull(key),
2608
- create: async (input) => pullOf3((await requestJson(root, { fetch, headers, method: "POST", body: createBody2(input) })).body),
2697
+ create: async (input) => pullOf3((await requestJson(root, { fetch, origin, headers, method: "POST", body: createBody2(input) })).body),
2609
2698
  update: async (key, patch) => {
2610
2699
  const id = slug(key);
2611
2700
  let updated;
@@ -2615,14 +2704,14 @@ function createPullAdapter({ connection, fetch }) {
2615
2704
  ...patch.description === void 0 ? {} : { description: patch.description },
2616
2705
  ...patch.target === void 0 ? {} : { target_branch: patch.target }
2617
2706
  };
2618
- updated = (await requestJson(`${root}/${id}`, { fetch, headers, method: "PATCH", body, missing: missing(key) })).body;
2707
+ updated = (await requestJson(`${root}/${id}`, { fetch, origin, headers, method: "PATCH", body, missing: missing(key) })).body;
2619
2708
  }
2620
2709
  if (patch.status !== void 0) {
2621
2710
  const action = statusActionOf(patch.status, (await getPull(key)).status.category);
2622
- if (action !== void 0) await requestJson(`${root}/${id}/${action}`, { fetch, headers, method: "POST", missing: missing(key) });
2711
+ if (action !== void 0) await requestJson(`${root}/${id}/${action}`, { fetch, origin, headers, method: "POST", missing: missing(key) });
2623
2712
  return getPull(key);
2624
2713
  }
2625
- return pullOf3(updated ?? (await requestJson(`${root}/${id}`, { fetch, headers, missing: missing(key) })).body);
2714
+ return pullOf3(updated ?? (await requestJson(`${root}/${id}`, { fetch, origin, headers, missing: missing(key) })).body);
2626
2715
  },
2627
2716
  merge: async (key, input) => {
2628
2717
  const id = slug(key);
@@ -2638,6 +2727,7 @@ function createPullAdapter({ connection, fetch }) {
2638
2727
  }
2639
2728
  const { body } = await requestJson(`${root}/${id}/merge`, {
2640
2729
  fetch,
2730
+ origin,
2641
2731
  headers,
2642
2732
  method: "POST",
2643
2733
  // squash/rebase по умолчанию наследуются из настройки репозитория — без method не шлём явно false.
@@ -2660,14 +2750,14 @@ function createPullAdapter({ connection, fetch }) {
2660
2750
  const params = new URLSearchParams({ page_size: String(query.limit) });
2661
2751
  if (query.cursor !== void 0) params.set("page_token", query.cursor);
2662
2752
  if (query.order !== void 0) params.set("sort_by", query.order === "asc" ? "created_at" : "-created_at");
2663
- const { body } = await requestJson(`${root}/${id}/comments?${params.toString()}`, { fetch, headers, missing: missing(key) });
2753
+ const { body } = await requestJson(`${root}/${id}/comments?${params.toString()}`, { fetch, origin, headers, missing: missing(key) });
2664
2754
  const record = isRecord(body) ? body : {};
2665
2755
  const rawComments = Array.isArray(record.pull_request_comments) ? record.pull_request_comments : [];
2666
2756
  const visible = rawComments.filter((raw) => !(isRecord(raw) && raw.is_deleted === true));
2667
2757
  const filtered = query.updatedAfter === void 0 ? visible : visible.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
2668
2758
  return { items: filtered.map(commentOf6), ...nextOf2(record.next_page_token) };
2669
2759
  },
2670
- comment: async (key, body) => commentOf6((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
2760
+ comment: async (key, body) => commentOf6((await requestJson(`${root}/${slug(key)}/comments`, { fetch, origin, headers, method: "POST", body: { body }, missing: missing(key) })).body)
2671
2761
  };
2672
2762
  }
2673
2763
  function createBody2(input) {
@@ -2780,6 +2870,7 @@ function sourcecraftTracker() {
2780
2870
  },
2781
2871
  issues: ISSUE_CAPABILITIES4,
2782
2872
  create: (context) => createSourceCraftAdapter(context),
2873
+ origin: (connection) => new URL(baseUrl(connection.url, DEFAULT_API6)).origin,
2783
2874
  pulls: sourcecraftPulls(),
2784
2875
  ci: sourcecraftCi()
2785
2876
  };
@@ -2788,6 +2879,7 @@ function createSourceCraftAdapter({ connection, fetch }) {
2788
2879
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
2789
2880
  if (!TARGET9.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
2790
2881
  const api = baseUrl(connection.url, DEFAULT_API6);
2882
+ const origin = new URL(api).origin;
2791
2883
  const root = `${api}/repos/${target}/issues`;
2792
2884
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
2793
2885
  const web = connection.url === void 0 ? `${DEFAULT_WEB}/${target}/issues` : void 0;
@@ -2804,31 +2896,31 @@ function createSourceCraftAdapter({ connection, fetch }) {
2804
2896
  const filter = issueFilterOf(query);
2805
2897
  if (filter !== void 0) params.set("filter", filter);
2806
2898
  if (query.sort !== void 0) params.set("sort_by", `${query.sort.direction === "asc" ? "" : "-"}${issueSortFieldOf(query.sort.field)}`);
2807
- const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
2899
+ const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2808
2900
  const record = isRecord(body) ? body : {};
2809
2901
  let issues = (Array.isArray(record.issues) ? record.issues : []).map(toIssue2);
2810
2902
  if (query.status !== void 0) issues = issues.filter((issue) => query.status?.includes(issue.status.category) === true);
2811
2903
  if (query.assignee === null) issues = issues.filter((issue) => issue.assignee === void 0);
2812
2904
  return { items: issues, ...nextOf3(record.next_page_token) };
2813
2905
  },
2814
- get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missingIsIssue: true })).body),
2906
+ get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, origin, headers, missingIsIssue: true })).body),
2815
2907
  create: async (input) => {
2816
2908
  const body = { title: input.title, ...input.description === void 0 ? {} : { description: input.description }, ...input.priority === void 0 ? {} : { priority: input.priority } };
2817
- return toIssue2((await requestJson(root, { fetch, headers, method: "POST", body })).body);
2909
+ return toIssue2((await requestJson(root, { fetch, origin, headers, method: "POST", body })).body);
2818
2910
  },
2819
- update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, method: "PATCH", body: patchBody5(patch), missingIsIssue: true })).body),
2911
+ update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, origin, headers, method: "PATCH", body: patchBody5(patch), missingIsIssue: true })).body),
2820
2912
  comments: async (key, query) => {
2821
2913
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 SourceCraft", ISSUE_CAPABILITIES4.comments, query);
2822
2914
  const params = new URLSearchParams({ page_size: String(query.limit) });
2823
2915
  if (query.cursor !== void 0) params.set("page_token", query.cursor);
2824
2916
  if (query.order !== void 0) params.set("sort_by", query.order === "asc" ? "created_at" : "-created_at");
2825
- const { body } = await requestJson(`${root}/${slug(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
2917
+ const { body } = await requestJson(`${root}/${slug(key)}/comments?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
2826
2918
  const record = isRecord(body) ? body : {};
2827
2919
  const rawComments = Array.isArray(record.issue_comments) ? record.issue_comments : [];
2828
2920
  const filtered = query.updatedAfter === void 0 ? rawComments : rawComments.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
2829
2921
  return { items: filtered.map(commentOf7), ...nextOf3(record.next_page_token) };
2830
2922
  },
2831
- comment: async (key, body) => commentOf7((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2923
+ comment: async (key, body) => commentOf7((await requestJson(`${root}/${slug(key)}/comments`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2832
2924
  };
2833
2925
  }
2834
2926
  function patchBody5(patch) {
@@ -2996,6 +3088,7 @@ export {
2996
3088
  TRACKER_ERROR_BODY_MAX,
2997
3089
  TRACKER_LOCAL_AUTHOR,
2998
3090
  TRACKER_PAGE_LIMIT_DEFAULT,
3091
+ TRACKER_REDIRECT_MAX,
2999
3092
  TRACKER_SECRETS_FILE,
3000
3093
  TRACKER_TIMEOUT_MS,
3001
3094
  TrackerError,