@smwb/onreel-git-mcp 0.8.0 → 0.10.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) {
@@ -1742,9 +1817,105 @@ function commentOf(raw) {
1742
1817
  return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
1743
1818
  }
1744
1819
 
1745
- // src/tracker/github.lib.ts
1820
+ // src/tracker/repo.schema.ts
1821
+ import { z as z3 } from "zod";
1822
+ var repoVisibilitySchema = z3.enum(["public", "internal", "private"]);
1823
+ var branchFilterSchema = z3.enum(["search"]);
1824
+ var repoSchema = z3.object({
1825
+ /** Имя репозитория, как его называет трекер: GitHub name, GitLab name, SourceCraft slug. */
1826
+ name: z3.string().min(1).max(500),
1827
+ /** Ветка по умолчанию; у пустого репозитория её нет. */
1828
+ defaultBranch: z3.string().min(1).max(500).optional(),
1829
+ description: z3.string().max(1e5).optional(),
1830
+ visibility: repoVisibilitySchema.optional(),
1831
+ /** Ссылки: страница репозитория в трекере (url первой). */
1832
+ links: z3.array(issueLinkSchema).max(10)
1833
+ });
1834
+ var branchSchema = z3.object({
1835
+ /** Имя ветки без refs/heads/. */
1836
+ name: z3.string().min(1).max(500),
1837
+ /** Коммит головы ветки. */
1838
+ sha: z3.string().regex(/^[0-9a-f]{7,64}$/).optional(),
1839
+ /** Ветка защищена; трекер, который признака не отдаёт, поле не заполняет. */
1840
+ protected: z3.boolean().optional(),
1841
+ /** Ветка по умолчанию репозитория; трекер, который признака не отдаёт, поле не заполняет. */
1842
+ isDefault: z3.boolean().optional()
1843
+ });
1844
+
1845
+ // src/tracker/github-repo.lib.ts
1746
1846
  var DEFAULT_API4 = "https://api.github.com";
1747
1847
  var TARGET5 = /^[\w.-]+\/[\w.-]+$/;
1848
+ var SHA3 = /^[0-9a-f]{7,64}$/;
1849
+ var BRANCHES_SUBJECT = "\u0412\u0435\u0442\u043A\u0438 GitHub";
1850
+ var BRANCH_FILTERS = branchFilterSchema.options;
1851
+ var CAPABILITIES4 = {
1852
+ branches: { filters: BRANCH_FILTERS, clientSide: BRANCH_FILTERS, sorts: [] }
1853
+ };
1854
+ function githubRepo() {
1855
+ return { capabilities: CAPABILITIES4, create: (context) => createGithubRepoAdapter(context) };
1856
+ }
1857
+ function createGithubRepoAdapter({ connection, fetch }) {
1858
+ const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
1859
+ if (!TARGET5.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
1860
+ const api = baseUrl(connection.url, DEFAULT_API4);
1861
+ const origin = new URL(api).origin;
1862
+ const root = `${api}/repos/${target}`;
1863
+ const headers = {
1864
+ accept: "application/vnd.github+json",
1865
+ "x-github-api-version": "2022-11-28",
1866
+ ...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
1867
+ };
1868
+ const missing = { failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F ${target}.` };
1869
+ return {
1870
+ get: async () => repoOf((await requestJson(root, { fetch, origin, headers, missing })).body, target),
1871
+ branches: async (query) => {
1872
+ assertListQuery(BRANCHES_SUBJECT, CAPABILITIES4.branches, query, BRANCH_FILTERS);
1873
+ const page = pageOf4(query.cursor);
1874
+ const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
1875
+ const { body, headers: responseHeaders } = await requestJson(`${root}/branches?${params.toString()}`, { fetch, origin, headers, missing });
1876
+ const items = (Array.isArray(body) ? body : []).map(branchOf).filter((branch) => query.search === void 0 || containsText([branch.name], query.search));
1877
+ return { items, ...nextOf2(responseHeaders.get("link"), page) };
1878
+ }
1879
+ };
1880
+ }
1881
+ function pageOf4(cursor) {
1882
+ const page = cursor === void 0 ? 1 : Number(cursor);
1883
+ return Number.isInteger(page) && page >= 1 ? page : 1;
1884
+ }
1885
+ function nextOf2(link, page) {
1886
+ return link !== null && /rel="next"/.test(link) ? { nextCursor: String(page + 1) } : {};
1887
+ }
1888
+ function repoOf(raw, target) {
1889
+ const item = isRecord(raw) ? raw : {};
1890
+ const defaultBranch = text(item.default_branch);
1891
+ const description = text(item.description);
1892
+ const visibility = visibilityOf(item.visibility);
1893
+ const url = text(item.html_url);
1894
+ return {
1895
+ name: text(item.name) ?? text(item.full_name) ?? target,
1896
+ ...defaultBranch === void 0 ? {} : { defaultBranch },
1897
+ ...description === void 0 ? {} : { description },
1898
+ ...visibility === void 0 ? {} : { visibility },
1899
+ links: url === void 0 ? [] : [{ url, title: "GitHub" }]
1900
+ };
1901
+ }
1902
+ function branchOf(raw) {
1903
+ const item = isRecord(raw) ? raw : {};
1904
+ const sha = isRecord(item.commit) ? text(item.commit.sha) : void 0;
1905
+ return {
1906
+ name: text(item.name) ?? "?",
1907
+ ...sha === void 0 || !SHA3.test(sha) ? {} : { sha },
1908
+ ...typeof item.protected === "boolean" ? { protected: item.protected } : {}
1909
+ };
1910
+ }
1911
+ function visibilityOf(value) {
1912
+ const parsed = repoVisibilitySchema.safeParse(value);
1913
+ return parsed.success ? parsed.data : void 0;
1914
+ }
1915
+
1916
+ // src/tracker/github.lib.ts
1917
+ var DEFAULT_API5 = "https://api.github.com";
1918
+ var TARGET6 = /^[\w.-]+\/[\w.-]+$/;
1748
1919
  var NUMBER2 = /^[1-9]\d{0,14}$/;
1749
1920
  var LIST_SUBJECT2 = "\u0417\u0430\u0434\u0430\u0447\u0438 GitHub";
1750
1921
  var COMMENTS_SUBJECT2 = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitHub";
@@ -1765,15 +1936,18 @@ function githubTracker() {
1765
1936
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: ["open", "in_progress", "done", "declined"] }
1766
1937
  },
1767
1938
  create: (context) => createGithubAdapter(context, issues),
1939
+ origin: (connection) => new URL(baseUrl(connection.url, DEFAULT_API5)).origin,
1768
1940
  issues,
1769
1941
  pulls: githubPulls(),
1770
- ci: githubCi()
1942
+ ci: githubCi(),
1943
+ repo: githubRepo()
1771
1944
  };
1772
1945
  }
1773
1946
  function createGithubAdapter({ connection, fetch }, capabilities) {
1774
1947
  const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
1775
- if (!TARGET5.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
1776
- const api = baseUrl(connection.url, DEFAULT_API4);
1948
+ if (!TARGET6.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
1949
+ const api = baseUrl(connection.url, DEFAULT_API5);
1950
+ const origin = new URL(api).origin;
1777
1951
  const root = `${api}/repos/${target}/issues`;
1778
1952
  const headers = {
1779
1953
  accept: "application/vnd.github+json",
@@ -1785,17 +1959,17 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
1785
1959
  return key;
1786
1960
  };
1787
1961
  const rawOf = async (key) => {
1788
- const { body } = await requestJson(`${root}/${number(key)}`, { fetch, headers, missingIsIssue: true });
1962
+ const { body } = await requestJson(`${root}/${number(key)}`, { fetch, origin, headers, missingIsIssue: true });
1789
1963
  return isRecord(body) ? body : {};
1790
1964
  };
1791
1965
  return {
1792
1966
  list: async (query) => {
1793
1967
  assertListQuery(LIST_SUBJECT2, capabilities.list, query, ISSUE_FILTERS);
1794
1968
  assertCsvLabels(query.labels);
1795
- const page = pageOf4(query.cursor);
1969
+ const page = pageOf5(query.cursor);
1796
1970
  const search = needsSearch(query);
1797
1971
  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 });
1972
+ const { body, headers: responseHeaders } = await requestJson(`${search ? `${api}/search/issues` : root}?${params.toString()}`, { fetch, origin, headers });
1799
1973
  return issuePageOf(rowsOf2(body, search), query, responseHeaders.get("link") ?? "", page);
1800
1974
  },
1801
1975
  get: async (key) => issueOf(await rawOf(key)),
@@ -1807,25 +1981,25 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
1807
1981
  ...labels.length === 0 ? {} : { labels },
1808
1982
  ...input.assignee === void 0 ? {} : { assignees: [input.assignee] }
1809
1983
  };
1810
- return issueOf((await requestJson(root, { fetch, headers, method: "POST", body })).body);
1984
+ return issueOf((await requestJson(root, { fetch, origin, headers, method: "POST", body })).body);
1811
1985
  },
1812
1986
  update: async (key, patch) => {
1813
1987
  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 });
1988
+ const response = await requestJson(`${root}/${number(key)}`, { fetch, origin, headers, method: "PATCH", body: patchBody2(patch, current), missingIsIssue: true });
1815
1989
  return issueOf(response.body);
1816
1990
  },
1817
1991
  comments: async (key, query) => {
1818
1992
  assertCommentQuery(COMMENTS_SUBJECT2, capabilities.comments, query);
1819
- const page = pageOf4(query.cursor);
1993
+ const page = pageOf5(query.cursor);
1820
1994
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
1821
1995
  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 });
1996
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
1823
1997
  return { items: (Array.isArray(body) ? body : []).map(commentOf2), .../rel="next"/.test(responseHeaders.get("link") ?? "") ? { nextCursor: String(page + 1) } : {} };
1824
1998
  },
1825
- comment: async (key, body) => commentOf2((await requestJson(`${root}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1999
+ comment: async (key, body) => commentOf2((await requestJson(`${root}/${number(key)}/comments`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1826
2000
  };
1827
2001
  }
1828
- function pageOf4(cursor) {
2002
+ function pageOf5(cursor) {
1829
2003
  const page = cursor === void 0 ? 1 : Number(cursor);
1830
2004
  return Number.isInteger(page) && page >= 1 ? page : 1;
1831
2005
  }
@@ -1946,9 +2120,9 @@ function commentOf2(raw) {
1946
2120
 
1947
2121
  // src/tracker/gitlab-pull.lib.ts
1948
2122
  var DEFAULT_URL2 = "https://gitlab.com";
1949
- var TARGET6 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
2123
+ var TARGET7 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
1950
2124
  var IID = /^[1-9]\d{0,19}$/;
1951
- var SHA3 = /^[0-9a-f]{7,64}$/;
2125
+ var SHA4 = /^[0-9a-f]{7,64}$/;
1952
2126
  var PULL_CAPABILITIES2 = {
1953
2127
  list: { filters: pullRequestFilterSchema.options, clientSide: ["status"], sorts: ["created", "updated", "title"] },
1954
2128
  comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] },
@@ -1969,19 +2143,21 @@ function gitlabPulls() {
1969
2143
  }
1970
2144
  function createGitlabPullAdapter({ connection, fetch }) {
1971
2145
  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
- 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`;
2146
+ 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.");
2147
+ const site = baseUrl(connection.url, DEFAULT_URL2);
2148
+ const origin = new URL(site).origin;
2149
+ const root = `${site}/api/v4/projects/${encodeURIComponent(target)}/merge_requests`;
1974
2150
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
1975
2151
  const iid = (key) => {
1976
2152
  if (!IID.test(key)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.`);
1977
2153
  return key;
1978
2154
  };
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);
2155
+ 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
2156
  return {
1981
2157
  list: async (query) => {
1982
2158
  assertListQuery("PR GitLab", PULL_CAPABILITIES2.list, query, pullRequestFilterSchema.options);
1983
2159
  const status = usedStatuses2(query.status);
1984
- const page = pageOf5(query.cursor);
2160
+ const page = pageOf6(query.cursor);
1985
2161
  const params = new URLSearchParams({
1986
2162
  state: stateFilter3(status),
1987
2163
  per_page: String(query.limit),
@@ -2001,7 +2177,7 @@ function createGitlabPullAdapter({ connection, fetch }) {
2001
2177
  if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
2002
2178
  if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
2003
2179
  if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
2004
- const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
2180
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2005
2181
  const next = responseHeaders.get("x-next-page");
2006
2182
  const items = (Array.isArray(body) ? body : []).map(pullOf2);
2007
2183
  return {
@@ -2017,7 +2193,7 @@ function createGitlabPullAdapter({ connection, fetch }) {
2017
2193
  title: (input.draft === true ? GITLAB_DRAFT_PREFIX : "") + input.title,
2018
2194
  ...input.description === void 0 ? {} : { description: input.description }
2019
2195
  };
2020
- return pullOf2((await requestJson(root, { fetch, headers, method: "POST", body, conflictOn: [409] })).body);
2196
+ return pullOf2((await requestJson(root, { fetch, origin, headers, method: "POST", body, conflictOn: [409] })).body);
2021
2197
  },
2022
2198
  update: async (key, patch) => {
2023
2199
  const current = patch.status === void 0 ? void 0 : await currentOf(key);
@@ -2025,8 +2201,8 @@ function createGitlabPullAdapter({ connection, fetch }) {
2025
2201
  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
2202
  }
2027
2203
  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);
2204
+ 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);
2205
+ 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
2206
  },
2031
2207
  merge: async (key, input) => {
2032
2208
  if (input.method !== void 0 && !PULL_CAPABILITIES2.merge.includes(input.method)) {
@@ -2039,21 +2215,21 @@ function createGitlabPullAdapter({ connection, fetch }) {
2039
2215
  ...input.deleteSource === void 0 ? {} : { should_remove_source_branch: input.deleteSource },
2040
2216
  ...input.headSha === void 0 ? {} : { sha: input.headSha }
2041
2217
  };
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);
2218
+ 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
2219
  },
2044
2220
  comments: async (key, query) => {
2045
2221
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", PULL_CAPABILITIES2.comments, query);
2046
- const page = pageOf5(query.cursor);
2222
+ const page = pageOf6(query.cursor);
2047
2223
  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}.` } });
2224
+ 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
2225
  const next = responseHeaders.get("x-next-page");
2050
2226
  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
2227
  return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
2052
2228
  },
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)
2229
+ 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
2230
  };
2055
2231
  }
2056
- function pageOf5(cursor) {
2232
+ function pageOf6(cursor) {
2057
2233
  const page = cursor === void 0 ? 1 : Number(cursor);
2058
2234
  return Number.isInteger(page) && page >= 1 ? page : 1;
2059
2235
  }
@@ -2121,7 +2297,7 @@ function pullOf2(raw) {
2121
2297
  ...author === void 0 ? {} : { author },
2122
2298
  source: text(item.source_branch) ?? "?",
2123
2299
  target: text(item.target_branch) ?? "?",
2124
- ...sha !== void 0 && SHA3.test(sha) ? { headSha: sha } : {},
2300
+ ...sha !== void 0 && SHA4.test(sha) ? { headSha: sha } : {},
2125
2301
  labels: labelNames(item.labels),
2126
2302
  reviewers: usernames(item.reviewers),
2127
2303
  links: url === void 0 ? [] : [{ url, title: "GitLab" }],
@@ -2138,9 +2314,78 @@ function commentOf3(raw) {
2138
2314
  return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
2139
2315
  }
2140
2316
 
2141
- // src/tracker/gitlab.lib.ts
2317
+ // src/tracker/gitlab-repo.lib.ts
2142
2318
  var DEFAULT_URL3 = "https://gitlab.com";
2143
- var TARGET7 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
2319
+ var TARGET8 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
2320
+ var SHA5 = /^[0-9a-f]{7,64}$/;
2321
+ var BRANCHES_SUBJECT2 = "\u0412\u0435\u0442\u043A\u0438 GitLab";
2322
+ var BRANCH_FILTERS2 = branchFilterSchema.options;
2323
+ var CAPABILITIES5 = {
2324
+ branches: { filters: BRANCH_FILTERS2, clientSide: [], sorts: [] }
2325
+ };
2326
+ function gitlabRepo() {
2327
+ return { capabilities: CAPABILITIES5, create: (context) => createGitlabRepoAdapter(context) };
2328
+ }
2329
+ function createGitlabRepoAdapter({ connection, fetch }) {
2330
+ 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");
2331
+ if (!TARGET8.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
2332
+ const site = baseUrl(connection.url, DEFAULT_URL3);
2333
+ const origin = new URL(site).origin;
2334
+ const project = `${site}/api/v4/projects/${encodeURIComponent(target)}`;
2335
+ const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
2336
+ const missing = { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 ${target}.` };
2337
+ return {
2338
+ get: async () => repoOf2((await requestJson(project, { fetch, origin, headers, missing })).body, target),
2339
+ branches: async (query) => {
2340
+ assertListQuery(BRANCHES_SUBJECT2, CAPABILITIES5.branches, query, BRANCH_FILTERS2);
2341
+ const params = new URLSearchParams({ per_page: String(query.limit), page: String(pageOf7(query.cursor)) });
2342
+ if (query.search !== void 0) params.set("search", searchOf(query.search));
2343
+ const { body, headers: responseHeaders } = await requestJson(`${project}/repository/branches?${params.toString()}`, { fetch, origin, headers, missing });
2344
+ const next = responseHeaders.get("x-next-page");
2345
+ return { items: (Array.isArray(body) ? body : []).map(branchOf2), ...next === null || next === "" ? {} : { nextCursor: next } };
2346
+ }
2347
+ };
2348
+ }
2349
+ function pageOf7(cursor) {
2350
+ const page = cursor === void 0 ? 1 : Number(cursor);
2351
+ return Number.isInteger(page) && page >= 1 ? page : 1;
2352
+ }
2353
+ function searchOf(value) {
2354
+ const stripped = value.replace(/^\^/, "").replace(/\$$/, "");
2355
+ return stripped === "" ? value : stripped;
2356
+ }
2357
+ function repoOf2(raw, target) {
2358
+ const item = isRecord(raw) ? raw : {};
2359
+ const defaultBranch = text(item.default_branch);
2360
+ const description = text(item.description);
2361
+ const visibility = visibilityOf2(item.visibility);
2362
+ const url = text(item.web_url);
2363
+ return {
2364
+ name: text(item.name) ?? text(item.path_with_namespace) ?? target,
2365
+ ...defaultBranch === void 0 ? {} : { defaultBranch },
2366
+ ...description === void 0 ? {} : { description },
2367
+ ...visibility === void 0 ? {} : { visibility },
2368
+ links: url === void 0 ? [] : [{ url, title: "GitLab" }]
2369
+ };
2370
+ }
2371
+ function branchOf2(raw) {
2372
+ const item = isRecord(raw) ? raw : {};
2373
+ const sha = isRecord(item.commit) ? text(item.commit.id) : void 0;
2374
+ return {
2375
+ name: text(item.name) ?? "?",
2376
+ ...sha === void 0 || !SHA5.test(sha) ? {} : { sha },
2377
+ ...typeof item.protected === "boolean" ? { protected: item.protected } : {},
2378
+ ...typeof item.default === "boolean" ? { isDefault: item.default } : {}
2379
+ };
2380
+ }
2381
+ function visibilityOf2(value) {
2382
+ const parsed = repoVisibilitySchema.safeParse(value);
2383
+ return parsed.success ? parsed.data : void 0;
2384
+ }
2385
+
2386
+ // src/tracker/gitlab.lib.ts
2387
+ var DEFAULT_URL4 = "https://gitlab.com";
2388
+ var TARGET9 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
2144
2389
  var IID2 = /^[1-9]\d{0,14}$/;
2145
2390
  var ISSUE_CAPABILITIES = {
2146
2391
  list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "priority", "title"] },
@@ -2165,25 +2410,29 @@ function gitlabTracker() {
2165
2410
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
2166
2411
  },
2167
2412
  create: (context) => createGitlabAdapter(context),
2413
+ origin: (connection) => new URL(baseUrl(connection.url, DEFAULT_URL4)).origin,
2168
2414
  issues: ISSUE_CAPABILITIES,
2169
2415
  pulls: gitlabPulls(),
2170
- ci: gitlabCi()
2416
+ ci: gitlabCi(),
2417
+ repo: gitlabRepo()
2171
2418
  };
2172
2419
  }
2173
2420
  function createGitlabAdapter({ connection, fetch }) {
2174
2421
  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
- 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`;
2422
+ if (!TARGET9.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
2423
+ const site = baseUrl(connection.url, DEFAULT_URL4);
2424
+ const origin = new URL(site).origin;
2425
+ const root = `${site}/api/v4/projects/${encodeURIComponent(target)}/issues`;
2177
2426
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
2178
2427
  const iid = (key) => {
2179
2428
  if (!IID2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
2180
2429
  return key;
2181
2430
  };
2182
- const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missingIsIssue: true })).body);
2431
+ const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, origin, headers, missingIsIssue: true })).body);
2183
2432
  return {
2184
2433
  list: async (query) => {
2185
2434
  assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 GitLab", ISSUE_CAPABILITIES.list, query, ISSUE_FILTERS);
2186
- const page = pageOf6(query.cursor);
2435
+ const page = pageOf8(query.cursor);
2187
2436
  const params = new URLSearchParams({
2188
2437
  state: stateFilter4(query.status),
2189
2438
  per_page: String(query.limit),
@@ -2202,7 +2451,7 @@ function createGitlabAdapter({ connection, fetch }) {
2202
2451
  if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
2203
2452
  if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
2204
2453
  if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
2205
- const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
2454
+ const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2206
2455
  const items = (Array.isArray(body) ? body : []).map(issueOf2);
2207
2456
  const next = responseHeaders.get("x-next-page");
2208
2457
  return {
@@ -2214,25 +2463,25 @@ function createGitlabAdapter({ connection, fetch }) {
2214
2463
  create: async (input) => {
2215
2464
  const labels = composeLabels2(input.labels ?? [], input.priority, void 0);
2216
2465
  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);
2466
+ return issueOf2((await requestJson(root, { fetch, origin, headers, method: "POST", body })).body);
2218
2467
  },
2219
2468
  update: async (key, patch) => {
2220
2469
  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);
2470
+ return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, origin, headers, method: "PUT", body: patchBody4(patch, current), missingIsIssue: true })).body);
2222
2471
  },
2223
2472
  comments: async (key, query) => {
2224
2473
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", ISSUE_CAPABILITIES.comments, query);
2225
- const page = pageOf6(query.cursor);
2474
+ const page = pageOf8(query.cursor);
2226
2475
  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 });
2476
+ const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
2228
2477
  const next = responseHeaders.get("x-next-page");
2229
2478
  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
2479
  return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
2231
2480
  },
2232
- comment: async (key, body) => commentOf4((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2481
+ comment: async (key, body) => commentOf4((await requestJson(`${root}/${iid(key)}/notes`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2233
2482
  };
2234
2483
  }
2235
- function pageOf6(cursor) {
2484
+ function pageOf8(cursor) {
2236
2485
  const page = cursor === void 0 ? 1 : Number(cursor);
2237
2486
  return Number.isInteger(page) && page >= 1 ? page : 1;
2238
2487
  }
@@ -2315,14 +2564,19 @@ function jiraTracker() {
2315
2564
  features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
2316
2565
  },
2317
2566
  create: (context) => createJiraAdapter(context),
2567
+ origin: (connection) => new URL(siteOf(connection)).origin,
2318
2568
  issues: ISSUE_CAPABILITIES2
2319
2569
  };
2320
2570
  }
2321
- function createJiraAdapter({ connection, fetch }) {
2571
+ function siteOf(connection) {
2322
2572
  if (connection.url === void 0) throw new TrackerError("invalid_request", "\u041D\u0435 \u0437\u0430\u0434\u0430\u043D \u0430\u0434\u0440\u0435\u0441 Jira.");
2573
+ return baseUrl(connection.url, connection.url);
2574
+ }
2575
+ function createJiraAdapter({ connection, fetch }) {
2576
+ const site = siteOf(connection);
2323
2577
  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
2578
  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);
2579
+ const origin = new URL(site).origin;
2326
2580
  const api = `${site}/rest/api/2`;
2327
2581
  const token = connection.token ?? "";
2328
2582
  const headers = { authorization: connection.user === void 0 ? `Bearer ${token}` : `Basic ${Buffer.from(`${connection.user}:${token}`).toString("base64")}` };
@@ -2331,7 +2585,7 @@ function createJiraAdapter({ connection, fetch }) {
2331
2585
  return value;
2332
2586
  };
2333
2587
  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);
2588
+ const load = async (value) => toIssue2((await requestJson(`${api}/issue/${key(value)}?fields=${FIELDS}`, { fetch, origin, headers, missingIsIssue: true })).body);
2335
2589
  return {
2336
2590
  list: async (query) => {
2337
2591
  assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 Jira", ISSUE_CAPABILITIES2.list, query, ISSUE_FILTERS);
@@ -2341,7 +2595,7 @@ function createJiraAdapter({ connection, fetch }) {
2341
2595
  const params2 = new URLSearchParams({ jql, fields: FIELDS, maxResults: String(query.limit) });
2342
2596
  if (query.cursor !== void 0) params2.set("nextPageToken", query.cursor);
2343
2597
  try {
2344
- const { body: body2 } = await requestJson(`${api}/search/jql?${params2.toString()}`, { fetch, headers });
2598
+ const { body: body2 } = await requestJson(`${api}/search/jql?${params2.toString()}`, { fetch, origin, headers });
2345
2599
  const record2 = isRecord(body2) ? body2 : {};
2346
2600
  const next = text(record2.nextPageToken);
2347
2601
  return { items: (Array.isArray(record2.issues) ? record2.issues : []).map(toIssue2), ...next === void 0 || record2.isLast === true ? {} : { nextCursor: next } };
@@ -2351,7 +2605,7 @@ function createJiraAdapter({ connection, fetch }) {
2351
2605
  }
2352
2606
  const startAt = query.cursor?.startsWith("s:") === true ? Number(query.cursor.slice(2)) : 0;
2353
2607
  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 });
2608
+ const { body } = await requestJson(`${api}/search?${params.toString()}`, { fetch, origin, headers });
2355
2609
  const record = isRecord(body) ? body : {};
2356
2610
  const issues = Array.isArray(record.issues) ? record.issues : [];
2357
2611
  const total = typeof record.total === "number" ? record.total : 0;
@@ -2368,22 +2622,22 @@ function createJiraAdapter({ connection, fetch }) {
2368
2622
  ...input.priority === void 0 ? {} : { priority: { name: JIRA_PRIORITY_NAMES[input.priority] } },
2369
2623
  ...input.labels === void 0 ? {} : { labels: input.labels.map((label) => label.replace(/\s+/g, "-")) }
2370
2624
  };
2371
- const { body } = await requestJson(`${api}/issue`, { fetch, headers, method: "POST", body: { fields } });
2625
+ const { body } = await requestJson(`${api}/issue`, { fetch, origin, headers, method: "POST", body: { fields } });
2372
2626
  const created = isRecord(body) ? text(body.key) : void 0;
2373
2627
  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
2628
  return load(created);
2375
2629
  },
2376
2630
  update: async (value, patch) => {
2377
2631
  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);
2632
+ if (Object.keys(fields).length > 0) await requestJson(`${api}/issue/${key(value)}`, { fetch, origin, headers, method: "PUT", body: { fields }, missingIsIssue: true });
2633
+ if (patch.status !== void 0) await transition(api, key(value), patch.status, { fetch, origin, headers });
2380
2634
  return load(value);
2381
2635
  },
2382
2636
  comments: async (value, query) => {
2383
2637
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 Jira", ISSUE_CAPABILITIES2.comments, query);
2384
2638
  const startAt = query.cursor === void 0 ? 0 : Number(query.cursor);
2385
2639
  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 });
2640
+ const { body } = await requestJson(`${api}/issue/${key(value)}/comment?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
2387
2641
  const record = isRecord(body) ? body : {};
2388
2642
  const raw = Array.isArray(record.comments) ? record.comments : [];
2389
2643
  const items = raw.filter((item) => inTimeRange(toTimestamp(isRecord(item) ? item.updated : void 0), query.updatedAfter, void 0));
@@ -2391,7 +2645,7 @@ function createJiraAdapter({ connection, fetch }) {
2391
2645
  const at = typeof record.startAt === "number" ? record.startAt : 0;
2392
2646
  return { items: items.map(commentOf5), ...at + raw.length < total && raw.length > 0 ? { nextCursor: String(at + raw.length) } : {} };
2393
2647
  },
2394
- comment: async (value, body) => commentOf5((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2648
+ comment: async (value, body) => commentOf5((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2395
2649
  };
2396
2650
  }
2397
2651
  function listJql(project, query) {
@@ -2427,8 +2681,9 @@ function fieldsOf(patch) {
2427
2681
  ...patch.labels === void 0 ? {} : { labels: patch.labels.map((label) => label.replace(/\s+/g, "-")) }
2428
2682
  };
2429
2683
  }
2430
- async function transition(api, key, category, fetch, headers) {
2431
- const { body } = await requestJson(`${api}/issue/${key}/transitions`, { fetch, headers, missingIsIssue: true });
2684
+ async function transition(api, key, category, request) {
2685
+ const { fetch, origin, headers } = request;
2686
+ const { body } = await requestJson(`${api}/issue/${key}/transitions`, { fetch, origin, headers, missingIsIssue: true });
2432
2687
  const list = isRecord(body) && Array.isArray(body.transitions) ? body.transitions : [];
2433
2688
  const match = list.find((item) => {
2434
2689
  if (!isRecord(item) || !isRecord(item.to) || !isRecord(item.to.statusCategory)) return false;
@@ -2436,7 +2691,7 @@ async function transition(api, key, category, fetch, headers) {
2436
2691
  });
2437
2692
  const id = isRecord(match) ? text(match.id) : void 0;
2438
2693
  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 });
2694
+ await requestJson(`${api}/issue/${key}/transitions`, { fetch, origin, headers, method: "POST", body: { transition: { id } }, missingIsIssue: true });
2440
2695
  }
2441
2696
  function issueOf3(raw, site) {
2442
2697
  const item = isRecord(raw) ? raw : {};
@@ -2551,10 +2806,10 @@ function toComment(row) {
2551
2806
  }
2552
2807
 
2553
2808
  // src/tracker/sourcecraft-pull.lib.ts
2554
- var DEFAULT_API5 = "https://api.sourcecraft.tech";
2555
- var TARGET8 = /^[\w.-]+\/[\w.-]+$/;
2809
+ var DEFAULT_API6 = "https://api.sourcecraft.tech";
2810
+ var TARGET10 = /^[\w.-]+\/[\w.-]+$/;
2556
2811
  var SLUG = /^[\w-]{1,100}$/;
2557
- var SHA4 = /^[0-9a-f]{7,64}$/;
2812
+ var SHA6 = /^[0-9a-f]{7,64}$/;
2558
2813
  var PULL_SORT_FIELDS_UPSTREAM = {
2559
2814
  created: "created_at",
2560
2815
  updated: "updated_at",
@@ -2580,8 +2835,9 @@ function sourcecraftPulls() {
2580
2835
  }
2581
2836
  function createPullAdapter({ connection, fetch }) {
2582
2837
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
2583
- if (!TARGET8.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
2584
- const api = baseUrl(connection.url, DEFAULT_API5);
2838
+ if (!TARGET10.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
2839
+ const api = baseUrl(connection.url, DEFAULT_API6);
2840
+ const origin = new URL(api).origin;
2585
2841
  const root = `${api}/repos/${target}/pulls`;
2586
2842
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
2587
2843
  const missing = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.` });
@@ -2589,7 +2845,7 @@ function createPullAdapter({ connection, fetch }) {
2589
2845
  if (!SLUG.test(key)) throw new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.`);
2590
2846
  return key;
2591
2847
  };
2592
- const getPull = async (key) => pullOf3((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missing: missing(key) })).body);
2848
+ const getPull = async (key) => pullOf3((await requestJson(`${root}/${slug(key)}`, { fetch, origin, headers, missing: missing(key) })).body);
2593
2849
  return {
2594
2850
  list: async (query) => {
2595
2851
  assertListQuery("PR SourceCraft", PULL_CAPABILITIES3.list, query, pullRequestFilterSchema.options);
@@ -2599,13 +2855,13 @@ function createPullAdapter({ connection, fetch }) {
2599
2855
  const filter = pullFilterOf(query, status);
2600
2856
  if (filter !== void 0) params.set("filter", filter);
2601
2857
  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 });
2858
+ const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2603
2859
  const record = isRecord(body) ? body : {};
2604
2860
  const pulls = (Array.isArray(record.pull_requests) ? record.pull_requests : []).map(pullOf3).filter((pull) => matchesPage2(pull, query));
2605
- return { items: pulls, ...nextOf2(record.next_page_token) };
2861
+ return { items: pulls, ...nextOf3(record.next_page_token) };
2606
2862
  },
2607
2863
  get: (key) => getPull(key),
2608
- create: async (input) => pullOf3((await requestJson(root, { fetch, headers, method: "POST", body: createBody2(input) })).body),
2864
+ create: async (input) => pullOf3((await requestJson(root, { fetch, origin, headers, method: "POST", body: createBody2(input) })).body),
2609
2865
  update: async (key, patch) => {
2610
2866
  const id = slug(key);
2611
2867
  let updated;
@@ -2615,14 +2871,14 @@ function createPullAdapter({ connection, fetch }) {
2615
2871
  ...patch.description === void 0 ? {} : { description: patch.description },
2616
2872
  ...patch.target === void 0 ? {} : { target_branch: patch.target }
2617
2873
  };
2618
- updated = (await requestJson(`${root}/${id}`, { fetch, headers, method: "PATCH", body, missing: missing(key) })).body;
2874
+ updated = (await requestJson(`${root}/${id}`, { fetch, origin, headers, method: "PATCH", body, missing: missing(key) })).body;
2619
2875
  }
2620
2876
  if (patch.status !== void 0) {
2621
2877
  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) });
2878
+ if (action !== void 0) await requestJson(`${root}/${id}/${action}`, { fetch, origin, headers, method: "POST", missing: missing(key) });
2623
2879
  return getPull(key);
2624
2880
  }
2625
- return pullOf3(updated ?? (await requestJson(`${root}/${id}`, { fetch, headers, missing: missing(key) })).body);
2881
+ return pullOf3(updated ?? (await requestJson(`${root}/${id}`, { fetch, origin, headers, missing: missing(key) })).body);
2626
2882
  },
2627
2883
  merge: async (key, input) => {
2628
2884
  const id = slug(key);
@@ -2638,6 +2894,7 @@ function createPullAdapter({ connection, fetch }) {
2638
2894
  }
2639
2895
  const { body } = await requestJson(`${root}/${id}/merge`, {
2640
2896
  fetch,
2897
+ origin,
2641
2898
  headers,
2642
2899
  method: "POST",
2643
2900
  // squash/rebase по умолчанию наследуются из настройки репозитория — без method не шлём явно false.
@@ -2660,14 +2917,14 @@ function createPullAdapter({ connection, fetch }) {
2660
2917
  const params = new URLSearchParams({ page_size: String(query.limit) });
2661
2918
  if (query.cursor !== void 0) params.set("page_token", query.cursor);
2662
2919
  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) });
2920
+ const { body } = await requestJson(`${root}/${id}/comments?${params.toString()}`, { fetch, origin, headers, missing: missing(key) });
2664
2921
  const record = isRecord(body) ? body : {};
2665
2922
  const rawComments = Array.isArray(record.pull_request_comments) ? record.pull_request_comments : [];
2666
2923
  const visible = rawComments.filter((raw) => !(isRecord(raw) && raw.is_deleted === true));
2667
2924
  const filtered = query.updatedAfter === void 0 ? visible : visible.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
2668
- return { items: filtered.map(commentOf6), ...nextOf2(record.next_page_token) };
2925
+ return { items: filtered.map(commentOf6), ...nextOf3(record.next_page_token) };
2669
2926
  },
2670
- comment: async (key, body) => commentOf6((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
2927
+ comment: async (key, body) => commentOf6((await requestJson(`${root}/${slug(key)}/comments`, { fetch, origin, headers, method: "POST", body: { body }, missing: missing(key) })).body)
2671
2928
  };
2672
2929
  }
2673
2930
  function createBody2(input) {
@@ -2710,7 +2967,7 @@ function pullFilterOf(query, status) {
2710
2967
  if (query.target !== void 0) clauses.push(`target_branch=${quoted(query.target)}`);
2711
2968
  return clauses.length === 0 ? void 0 : clauses.join(" and ");
2712
2969
  }
2713
- function nextOf2(token) {
2970
+ function nextOf3(token) {
2714
2971
  const value = text(token);
2715
2972
  return value === void 0 ? {} : { nextCursor: value };
2716
2973
  }
@@ -2735,7 +2992,7 @@ function pullOf3(raw) {
2735
2992
  ...author === void 0 ? {} : { author },
2736
2993
  source: text(source.ref) ?? text(item.source_branch) ?? "?",
2737
2994
  target: text(target.ref) ?? text(item.target_branch) ?? "?",
2738
- ...headSha !== void 0 && SHA4.test(headSha) ? { headSha } : {},
2995
+ ...headSha !== void 0 && SHA6.test(headSha) ? { headSha } : {},
2739
2996
  labels: Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : void 0)) : [],
2740
2997
  links: [],
2741
2998
  ...createdAt === void 0 ? {} : { createdAt },
@@ -2751,10 +3008,73 @@ function commentOf6(raw) {
2751
3008
  return { id: text(item.id) ?? "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
2752
3009
  }
2753
3010
 
3011
+ // src/tracker/sourcecraft-repo.lib.ts
3012
+ var DEFAULT_API7 = "https://api.sourcecraft.tech";
3013
+ var TARGET11 = /^[\w.-]+\/[\w.-]+$/;
3014
+ var SHA7 = /^[0-9a-f]{7,64}$/;
3015
+ var BRANCHES_SUBJECT3 = "\u0412\u0435\u0442\u043A\u0438 SourceCraft";
3016
+ var BRANCH_FILTERS3 = branchFilterSchema.options;
3017
+ var PAGE_SIZE_MAX = 100;
3018
+ var CAPABILITIES6 = {
3019
+ branches: { filters: BRANCH_FILTERS3, clientSide: [], sorts: [] }
3020
+ };
3021
+ function sourcecraftRepo() {
3022
+ return { capabilities: CAPABILITIES6, create: (context) => createSourceCraftRepoAdapter(context) };
3023
+ }
3024
+ function createSourceCraftRepoAdapter({ connection, fetch }) {
3025
+ const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
3026
+ if (!TARGET11.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
3027
+ const api = baseUrl(connection.url, DEFAULT_API7);
3028
+ const origin = new URL(api).origin;
3029
+ const repo = `${api}/repos/${target}`;
3030
+ const headers = { authorization: `Bearer ${connection.token ?? ""}` };
3031
+ const missing = { failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F ${target}.` };
3032
+ return {
3033
+ get: async () => repoOf3((await requestJson(repo, { fetch, origin, headers, missing })).body, target),
3034
+ branches: async (query) => {
3035
+ assertListQuery(BRANCHES_SUBJECT3, CAPABILITIES6.branches, query, BRANCH_FILTERS3);
3036
+ const params = new URLSearchParams({ page_size: String(Math.min(query.limit, PAGE_SIZE_MAX)) });
3037
+ if (query.cursor !== void 0) params.set("page_token", query.cursor);
3038
+ if (query.search !== void 0) params.set("filter", query.search);
3039
+ const { body } = await requestJson(`${repo}/branches?${params.toString()}`, { fetch, origin, headers, missing });
3040
+ const record = isRecord(body) ? body : {};
3041
+ const branches = Array.isArray(record.branches) ? record.branches : [];
3042
+ const next = text(record.next_page_token);
3043
+ return { items: branches.map(branchOf3), ...next === void 0 ? {} : { nextCursor: next } };
3044
+ }
3045
+ };
3046
+ }
3047
+ function repoOf3(raw, target) {
3048
+ const item = isRecord(raw) ? raw : {};
3049
+ const defaultBranch = text(item.default_branch);
3050
+ const description = text(item.description);
3051
+ const visibility = visibilityOf3(item.visibility);
3052
+ const url = text(item.web_url);
3053
+ return {
3054
+ name: text(item.slug) ?? text(item.name) ?? target,
3055
+ ...defaultBranch === void 0 ? {} : { defaultBranch },
3056
+ ...description === void 0 ? {} : { description },
3057
+ ...visibility === void 0 ? {} : { visibility },
3058
+ links: url === void 0 ? [] : [{ url, title: "SourceCraft" }]
3059
+ };
3060
+ }
3061
+ function branchOf3(raw) {
3062
+ const item = isRecord(raw) ? raw : {};
3063
+ const sha = isRecord(item.commit) ? text(item.commit.hash) : void 0;
3064
+ return {
3065
+ name: text(item.name) ?? "?",
3066
+ ...sha === void 0 || !SHA7.test(sha) ? {} : { sha }
3067
+ };
3068
+ }
3069
+ function visibilityOf3(value) {
3070
+ const parsed = repoVisibilitySchema.safeParse(value);
3071
+ return parsed.success ? parsed.data : void 0;
3072
+ }
3073
+
2754
3074
  // src/tracker/sourcecraft.lib.ts
2755
- var DEFAULT_API6 = "https://api.sourcecraft.tech";
3075
+ var DEFAULT_API8 = "https://api.sourcecraft.tech";
2756
3076
  var DEFAULT_WEB = "https://sourcecraft.dev";
2757
- var TARGET9 = /^[\w.-]+\/[\w.-]+$/;
3077
+ var TARGET12 = /^[\w.-]+\/[\w.-]+$/;
2758
3078
  var SLUG2 = /^[\w-]{1,100}$/;
2759
3079
  var ISSUE_SORT_FIELDS_UPSTREAM = {
2760
3080
  created: "created_at",
@@ -2780,14 +3100,17 @@ function sourcecraftTracker() {
2780
3100
  },
2781
3101
  issues: ISSUE_CAPABILITIES4,
2782
3102
  create: (context) => createSourceCraftAdapter(context),
3103
+ origin: (connection) => new URL(baseUrl(connection.url, DEFAULT_API8)).origin,
2783
3104
  pulls: sourcecraftPulls(),
2784
- ci: sourcecraftCi()
3105
+ ci: sourcecraftCi(),
3106
+ repo: sourcecraftRepo()
2785
3107
  };
2786
3108
  }
2787
3109
  function createSourceCraftAdapter({ connection, fetch }) {
2788
3110
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
2789
- if (!TARGET9.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
2790
- const api = baseUrl(connection.url, DEFAULT_API6);
3111
+ if (!TARGET12.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
3112
+ const api = baseUrl(connection.url, DEFAULT_API8);
3113
+ const origin = new URL(api).origin;
2791
3114
  const root = `${api}/repos/${target}/issues`;
2792
3115
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
2793
3116
  const web = connection.url === void 0 ? `${DEFAULT_WEB}/${target}/issues` : void 0;
@@ -2804,31 +3127,31 @@ function createSourceCraftAdapter({ connection, fetch }) {
2804
3127
  const filter = issueFilterOf(query);
2805
3128
  if (filter !== void 0) params.set("filter", filter);
2806
3129
  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 });
3130
+ const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, origin, headers });
2808
3131
  const record = isRecord(body) ? body : {};
2809
3132
  let issues = (Array.isArray(record.issues) ? record.issues : []).map(toIssue2);
2810
3133
  if (query.status !== void 0) issues = issues.filter((issue) => query.status?.includes(issue.status.category) === true);
2811
3134
  if (query.assignee === null) issues = issues.filter((issue) => issue.assignee === void 0);
2812
- return { items: issues, ...nextOf3(record.next_page_token) };
3135
+ return { items: issues, ...nextOf4(record.next_page_token) };
2813
3136
  },
2814
- get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missingIsIssue: true })).body),
3137
+ get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, origin, headers, missingIsIssue: true })).body),
2815
3138
  create: async (input) => {
2816
3139
  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);
3140
+ return toIssue2((await requestJson(root, { fetch, origin, headers, method: "POST", body })).body);
2818
3141
  },
2819
- update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, method: "PATCH", body: patchBody5(patch), missingIsIssue: true })).body),
3142
+ update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, origin, headers, method: "PATCH", body: patchBody5(patch), missingIsIssue: true })).body),
2820
3143
  comments: async (key, query) => {
2821
3144
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 SourceCraft", ISSUE_CAPABILITIES4.comments, query);
2822
3145
  const params = new URLSearchParams({ page_size: String(query.limit) });
2823
3146
  if (query.cursor !== void 0) params.set("page_token", query.cursor);
2824
3147
  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 });
3148
+ const { body } = await requestJson(`${root}/${slug(key)}/comments?${params.toString()}`, { fetch, origin, headers, missingIsIssue: true });
2826
3149
  const record = isRecord(body) ? body : {};
2827
3150
  const rawComments = Array.isArray(record.issue_comments) ? record.issue_comments : [];
2828
3151
  const filtered = query.updatedAfter === void 0 ? rawComments : rawComments.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
2829
- return { items: filtered.map(commentOf7), ...nextOf3(record.next_page_token) };
3152
+ return { items: filtered.map(commentOf7), ...nextOf4(record.next_page_token) };
2830
3153
  },
2831
- comment: async (key, body) => commentOf7((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
3154
+ comment: async (key, body) => commentOf7((await requestJson(`${root}/${slug(key)}/comments`, { fetch, origin, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
2832
3155
  };
2833
3156
  }
2834
3157
  function patchBody5(patch) {
@@ -2839,7 +3162,7 @@ function patchBody5(patch) {
2839
3162
  ...patch.priority === void 0 ? {} : { priority: patch.priority }
2840
3163
  };
2841
3164
  }
2842
- function nextOf3(token) {
3165
+ function nextOf4(token) {
2843
3166
  const value = text(token);
2844
3167
  return value === void 0 ? {} : { nextCursor: value };
2845
3168
  }
@@ -2957,6 +3280,12 @@ function pullRequestAdapterOf(definition, context) {
2957
3280
  if (definition.pulls === void 0) throw new TrackerError("unsupported", `\u0422\u0440\u0435\u043A\u0435\u0440 ${definition.descriptor.title} \u043D\u0435 \u0432\u0435\u0434\u0451\u0442 pull request-\u044B.`);
2958
3281
  return definition.pulls.create(context);
2959
3282
  }
3283
+
3284
+ // src/tracker/repo.lib.ts
3285
+ function repoAdapterOf(definition, context) {
3286
+ if (definition.repo === void 0) throw new TrackerError("unsupported", `\u0422\u0440\u0435\u043A\u0435\u0440 ${definition.descriptor.title} \u043D\u0435 \u0432\u0435\u0434\u0451\u0442 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439.`);
3287
+ return definition.repo.create(context);
3288
+ }
2960
3289
  export {
2961
3290
  ALL_STATUSES,
2962
3291
  CI_FINISHED_STATUSES,
@@ -2996,12 +3325,15 @@ export {
2996
3325
  TRACKER_ERROR_BODY_MAX,
2997
3326
  TRACKER_LOCAL_AUTHOR,
2998
3327
  TRACKER_PAGE_LIMIT_DEFAULT,
3328
+ TRACKER_REDIRECT_MAX,
2999
3329
  TRACKER_SECRETS_FILE,
3000
3330
  TRACKER_TIMEOUT_MS,
3001
3331
  TrackerError,
3002
3332
  assertCommentQuery,
3003
3333
  assertListQuery,
3004
3334
  baseUrl,
3335
+ branchFilterSchema,
3336
+ branchSchema,
3005
3337
  ciAdapterOf,
3006
3338
  ciEventCategorySchema,
3007
3339
  ciJobFilterSchema,
@@ -3020,9 +3352,11 @@ export {
3020
3352
  findTracker,
3021
3353
  githubCi,
3022
3354
  githubPulls,
3355
+ githubRepo,
3023
3356
  githubTracker,
3024
3357
  gitlabCi,
3025
3358
  gitlabPulls,
3359
+ gitlabRepo,
3026
3360
  gitlabTracker,
3027
3361
  hasTrackerToken,
3028
3362
  inTimeRange,
@@ -3050,11 +3384,15 @@ export {
3050
3384
  readTrackerToken,
3051
3385
  registerBuiltinTrackers,
3052
3386
  registerTracker,
3387
+ repoAdapterOf,
3388
+ repoSchema,
3389
+ repoVisibilitySchema,
3053
3390
  requestJson,
3054
3391
  requestText,
3055
3392
  requireTarget,
3056
3393
  sourcecraftCi,
3057
3394
  sourcecraftPulls,
3395
+ sourcecraftRepo,
3058
3396
  sourcecraftTracker,
3059
3397
  stringList,
3060
3398
  text,