@smwb/onreel-git-mcp 0.4.0 → 0.6.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.
@@ -1,7 +1,8 @@
1
1
  import {
2
+ REDACTED,
2
3
  defaultSecretSink,
3
4
  redactSecretText
4
- } from "../chunk-L7CPE2SO.js";
5
+ } from "../chunk-AGGJK2XZ.js";
5
6
  import {
6
7
  issueLinkSchema,
7
8
  timestampSchema
@@ -20,6 +21,12 @@ var LEGACY_ISSUE_CAPABILITIES = {
20
21
  list: { filters: ["status"], clientSide: ["status"], sorts: [] },
21
22
  comments: { filters: [], clientSide: [], order: ["asc"] }
22
23
  };
24
+ var CI_LOG_DEFAULT_BYTES = 16 * 1024;
25
+ var CI_LOG_MAX_BYTES = 1024 * 1024;
26
+ var CI_LOG_READ_MAX_BYTES = 64 * 1024 * 1024;
27
+ var CI_LOG_TIMEOUT_MS = 6e4;
28
+ var CI_LOG_PAGES_MAX = 50;
29
+ var CI_FINISHED_STATUSES = ["success", "failed", "canceled", "skipped"];
23
30
 
24
31
  // src/tracker/tracker.lib.ts
25
32
  var TrackerError = class extends Error {
@@ -230,6 +237,84 @@ var SOURCECRAFT_PULL_STATUSES = {
230
237
  merged: "merged",
231
238
  discarded: "closed"
232
239
  };
240
+ var GITHUB_RUN_STATUSES = {
241
+ queued: "queued",
242
+ requested: "queued",
243
+ pending: "queued",
244
+ waiting: "waiting",
245
+ in_progress: "running"
246
+ };
247
+ var GITHUB_RUN_CONCLUSIONS = {
248
+ success: "success",
249
+ neutral: "success",
250
+ failure: "failed",
251
+ timed_out: "failed",
252
+ startup_failure: "failed",
253
+ cancelled: "canceled",
254
+ stale: "canceled",
255
+ skipped: "skipped",
256
+ action_required: "waiting"
257
+ };
258
+ var GITHUB_RUN_EVENTS = {
259
+ push: "push",
260
+ pull_request: "pull_request",
261
+ pull_request_target: "pull_request",
262
+ merge_group: "pull_request",
263
+ workflow_dispatch: "manual",
264
+ schedule: "schedule",
265
+ repository_dispatch: "trigger",
266
+ workflow_run: "trigger",
267
+ workflow_call: "trigger"
268
+ };
269
+ var GITLAB_CI_STATUSES = {
270
+ created: "queued",
271
+ waiting_for_resource: "queued",
272
+ preparing: "queued",
273
+ pending: "queued",
274
+ running: "running",
275
+ canceling: "running",
276
+ waiting_for_callback: "waiting",
277
+ manual: "waiting",
278
+ scheduled: "waiting",
279
+ success: "success",
280
+ failed: "failed",
281
+ canceled: "canceled",
282
+ skipped: "skipped"
283
+ };
284
+ var GITLAB_PIPELINE_SOURCES = {
285
+ push: "push",
286
+ merge_request_event: "pull_request",
287
+ external_pull_request_event: "pull_request",
288
+ web: "manual",
289
+ api: "manual",
290
+ chat: "manual",
291
+ webide: "manual",
292
+ schedule: "schedule",
293
+ pipeline_execution_policy_schedule: "schedule",
294
+ trigger: "trigger",
295
+ pipeline: "trigger",
296
+ parent_pipeline: "trigger"
297
+ };
298
+ var SOURCECRAFT_CI_STATUSES = {
299
+ created: "queued",
300
+ prepared: "queued",
301
+ processing: "running",
302
+ awaiting_approval: "waiting",
303
+ success: "success",
304
+ failed: "failed",
305
+ timeout: "failed",
306
+ canceled: "canceled",
307
+ rejected: "canceled",
308
+ skipped: "skipped"
309
+ };
310
+ var SOURCECRAFT_RUN_EVENTS = {
311
+ push: "push",
312
+ pr_update: "pull_request",
313
+ manual: "manual",
314
+ restart: "retry",
315
+ schedule: "schedule",
316
+ repository_event: "trigger"
317
+ };
233
318
 
234
319
  // src/tracker/http.lib.ts
235
320
  async function requestJson(url, request) {
@@ -242,7 +327,7 @@ async function requestJson(url, request) {
242
327
  signal: AbortSignal.timeout(TRACKER_TIMEOUT_MS)
243
328
  });
244
329
  } catch (error) {
245
- throw new TrackerError("failed", redactSecretText(`\u0422\u0440\u0435\u043A\u0435\u0440 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B (${hostOf(url)}): ${error instanceof Error ? error.message : String(error)}`));
330
+ throw notAnswered(url, error);
246
331
  }
247
332
  const raw = await response.text();
248
333
  if (!response.ok) throw failureOf(url, response, raw, request);
@@ -253,6 +338,131 @@ async function requestJson(url, request) {
253
338
  throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435 JSON.`);
254
339
  }
255
340
  }
341
+ var ERROR_BODY_READ_MAX = 16 * 1024;
342
+ async function requestText(url, request) {
343
+ const skip = request.skip ?? 0;
344
+ const { bytes, from } = request.keep;
345
+ const readMax = request.readMax ?? CI_LOG_READ_MAX_BYTES;
346
+ if (![skip, bytes, readMax].every((value) => Number.isSafeInteger(value) && value >= 0)) {
347
+ 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
+ }
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
+ }
359
+ 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);
362
+ }
363
+ const body = await readBody(url, response, { skip, bytes, from, readMax });
364
+ let start = Math.min(body.start, body.end);
365
+ let end = body.end;
366
+ let window = body.window;
367
+ const lead = start > 0 ? leadingContinuation(window) : 0;
368
+ const tail = trailingPartial(window.subarray(lead));
369
+ if (lead > 0 || tail > 0) {
370
+ window = window.subarray(lead, window.length - tail);
371
+ start += lead;
372
+ end -= tail;
373
+ }
374
+ return { text: decodeUtf8(window), start, end, read: body.read, whole: body.whole, headers: response.headers };
375
+ }
376
+ async function readBody(url, response, window) {
377
+ const { skip, bytes, from, readMax } = window;
378
+ const reader = response.body?.getReader();
379
+ const kept = from === "head" ? headBuffer(bytes) : ringBuffer(bytes);
380
+ let read = 0;
381
+ let whole = true;
382
+ if (reader !== void 0) {
383
+ try {
384
+ for (; ; ) {
385
+ const { done, value } = await reader.read();
386
+ if (done) break;
387
+ const chunk = value.subarray(0, Math.max(0, readMax - read));
388
+ const cut = chunk.length < value.length;
389
+ const offset = Math.max(0, skip - read);
390
+ if (offset < chunk.length) kept.push(chunk.subarray(offset));
391
+ read += chunk.length;
392
+ if (cut || kept.full()) {
393
+ whole = false;
394
+ await reader.cancel().catch(() => void 0);
395
+ break;
396
+ }
397
+ }
398
+ } catch (error) {
399
+ await reader.cancel().catch(() => void 0);
400
+ throw notAnswered(url, error);
401
+ }
402
+ }
403
+ const data = kept.bytes();
404
+ const start = from === "head" ? skip : Math.max(skip, read - data.length);
405
+ return { window: data, start, end: from === "head" ? skip + data.length : read, read, whole };
406
+ }
407
+ function headBuffer(capacity) {
408
+ const buffer = new Uint8Array(capacity);
409
+ let length = 0;
410
+ let overflow = false;
411
+ return {
412
+ push(chunk) {
413
+ const room = capacity - length;
414
+ buffer.set(chunk.subarray(0, room), length);
415
+ length += Math.min(room, chunk.length);
416
+ if (chunk.length > room) overflow = true;
417
+ },
418
+ full: () => overflow,
419
+ bytes: () => buffer.subarray(0, length)
420
+ };
421
+ }
422
+ function ringBuffer(capacity) {
423
+ const buffer = new Uint8Array(capacity);
424
+ let next = 0;
425
+ let length = 0;
426
+ return {
427
+ push(chunk) {
428
+ if (capacity === 0) return;
429
+ const part = chunk.subarray(Math.max(0, chunk.length - capacity));
430
+ const first = Math.min(part.length, capacity - next);
431
+ buffer.set(part.subarray(0, first), next);
432
+ buffer.set(part.subarray(first), 0);
433
+ next = (next + part.length) % capacity;
434
+ length = Math.min(capacity, length + part.length);
435
+ },
436
+ full: () => false,
437
+ bytes() {
438
+ if (length < capacity) return buffer.subarray(0, length);
439
+ const ordered = new Uint8Array(capacity);
440
+ ordered.set(buffer.subarray(next), 0);
441
+ ordered.set(buffer.subarray(0, next), capacity - next);
442
+ return ordered;
443
+ }
444
+ };
445
+ }
446
+ function leadingContinuation(window) {
447
+ let count = 0;
448
+ while (count < 3 && count < window.length && ((window[count] ?? 0) & 192) === 128) count += 1;
449
+ return count;
450
+ }
451
+ function trailingPartial(window) {
452
+ for (let back = 1; back <= Math.min(4, window.length); back += 1) {
453
+ const byte = window[window.length - back] ?? 0;
454
+ if ((byte & 192) === 128) continue;
455
+ const length = byte >= 240 ? 4 : byte >= 224 ? 3 : byte >= 192 ? 2 : 1;
456
+ return length > back ? back : 0;
457
+ }
458
+ return 0;
459
+ }
460
+ function decodeUtf8(bytes) {
461
+ return new TextDecoder("utf-8").decode(bytes);
462
+ }
463
+ function notAnswered(url, error) {
464
+ return new TrackerError("failed", redactSecretText(`\u0422\u0440\u0435\u043A\u0435\u0440 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B (${hostOf(url)}): ${error instanceof Error ? error.message : String(error)}`));
465
+ }
256
466
  function failureOf(url, response, raw, request) {
257
467
  const status = response.status;
258
468
  if (status === 404 && request.missing !== void 0) return new TrackerError(request.missing.failure, request.missing.message);
@@ -304,6 +514,1000 @@ function baseUrl(value, fallback) {
304
514
  return raw;
305
515
  }
306
516
 
517
+ // src/tracker/ci.schema.ts
518
+ import { z } from "zod";
519
+ var ciStatusCategorySchema = z.enum(["queued", "running", "waiting", "success", "failed", "canceled", "skipped"]);
520
+ var ciEventCategorySchema = z.enum(["push", "pull_request", "manual", "schedule", "retry", "trigger", "other"]);
521
+ var ciRunFilterSchema = z.enum(["status", "ref", "sha", "event", "workflow", "actor", "pullRequest", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"]);
522
+ var ciRunSortFieldSchema = z.enum(["created", "updated"]);
523
+ var ciJobFilterSchema = z.enum(["status", "allAttempts"]);
524
+ var ciStatusSchema = z.object({ category: ciStatusCategorySchema, name: z.string().min(1).max(100) });
525
+ var ciWorkflowSchema = z.object({
526
+ /** Ключ workflow: GitHub — id строкой; подходит в trigger.workflows и фильтр workflow. */
527
+ key: z.string().min(1).max(200),
528
+ name: z.string().min(1).max(500),
529
+ path: z.string().min(1).max(1e3).optional(),
530
+ enabled: z.boolean()
531
+ });
532
+ var ciRunSchema = z.object({
533
+ /** Ключ прогона: GitHub run id, GitLab pipeline id, SourceCraft slug-счётчик; непрозрачная строка. */
534
+ key: z.string().min(1).max(200),
535
+ /** Номер для людей: GitHub run_number, GitLab iid, SourceCraft — счётчик. */
536
+ number: z.number().int().nonnegative().optional(),
537
+ /** Workflow прогона: GitHub — один, SourceCraft — все в прогоне, GitLab — имя пайплайна, если задано. */
538
+ workflows: z.array(z.string().min(1).max(500)).max(100),
539
+ status: ciStatusSchema,
540
+ event: z.object({ category: ciEventCategorySchema, name: z.string().min(1).max(100) }).optional(),
541
+ /** Ветка или тег прогона. */
542
+ ref: z.string().min(1).max(500).optional(),
543
+ sha: z.string().regex(/^[0-9a-f]{7,64}$/).optional(),
544
+ /** Кто запустил — логин или slug трекера. */
545
+ actor: z.string().min(1).max(200).optional(),
546
+ /** Ключ PR того же трекера, для которого прогон. */
547
+ pullRequest: z.string().min(1).max(200).optional(),
548
+ /** Попытка прогона (GitHub run_attempt). */
549
+ attempt: z.number().int().positive().optional(),
550
+ /** Ошибки прогона от трекера (SourceCraft error_messages). */
551
+ errors: z.array(z.string().min(1).max(4e3)).max(20).optional(),
552
+ links: z.array(issueLinkSchema).max(10),
553
+ createdAt: timestampSchema.optional(),
554
+ startedAt: timestampSchema.optional(),
555
+ finishedAt: timestampSchema.optional(),
556
+ updatedAt: timestampSchema.optional()
557
+ });
558
+ var ciJobSchema = z.object({
559
+ /** Ключ задания: GitHub и GitLab — job id, SourceCraft — `<run>/<workflow>/<task>/<cube>`, части через encodeURIComponent. */
560
+ key: z.string().min(1).max(1e3),
561
+ runKey: z.string().min(1).max(200),
562
+ name: z.string().min(1).max(500),
563
+ /** Группа: GitLab — stage, SourceCraft — `<workflow>/<task>`; у GitHub нет. */
564
+ stage: z.string().min(1).max(1e3).optional(),
565
+ status: ciStatusSchema,
566
+ /** Шаги GitHub: где упало; у остальных нет. */
567
+ steps: z.array(z.object({ number: z.number().int().nonnegative(), name: z.string().min(1).max(500), status: ciStatusSchema })).max(1e3).optional(),
568
+ attempt: z.number().int().positive().optional(),
569
+ links: z.array(issueLinkSchema).max(10),
570
+ createdAt: timestampSchema.optional(),
571
+ startedAt: timestampSchema.optional(),
572
+ finishedAt: timestampSchema.optional()
573
+ });
574
+ var ciLogSchema = z.object({
575
+ /** Окно лога: очищено от ANSI (если не raw), секреты замаскированы. */
576
+ text: z.string().max(CI_LOG_MAX_BYTES),
577
+ /** Окно не покрывает весь лог от курсора: срезано спереди (tail), сзади (head) или упёрлись в потолок чтения. */
578
+ truncated: z.boolean(),
579
+ /** Задание закончилось — лог больше не растёт. */
580
+ complete: z.boolean(),
581
+ /** Курсор дочитывания: передайте в log() — придёт текст после этого места. Нет — дальше ничего не будет. */
582
+ nextCursor: z.string().min(1).max(200).optional(),
583
+ /** Размер лога в байтах, если тело прочитано целиком. */
584
+ size: z.number().int().nonnegative().optional()
585
+ });
586
+
587
+ // src/tracker/ci.lib.ts
588
+ function ciAdapterOf(definition, context) {
589
+ if (definition.ci === void 0) throw new TrackerError("unsupported", `\u0422\u0440\u0435\u043A\u0435\u0440 ${definition.descriptor.title} \u043D\u0435 \u0432\u0435\u0434\u0451\u0442 CI.`);
590
+ return definition.ci.create(context);
591
+ }
592
+ var LOG_CURSOR = /^c:(\d{1,15}):(\d{1,15})$/;
593
+ function logWindow(query) {
594
+ const bytes = query.maxBytes ?? CI_LOG_DEFAULT_BYTES;
595
+ if (!Number.isInteger(bytes) || bytes < 1 || bytes > CI_LOG_MAX_BYTES) {
596
+ throw new TrackerError("invalid_request", `\u041E\u043A\u043D\u043E \u043B\u043E\u0433\u0430 maxBytes \u2014 \u0446\u0435\u043B\u043E\u0435 \u043E\u0442 1 \u0434\u043E ${String(CI_LOG_MAX_BYTES)}.`);
597
+ }
598
+ const from = query.from ?? "tail";
599
+ if (from !== "head" && from !== "tail") throw new TrackerError("invalid_request", "\u041D\u0430\u0447\u0430\u043B\u043E \u043E\u043A\u043D\u0430 \u043B\u043E\u0433\u0430 from \u2014 head \u0438\u043B\u0438 tail.");
600
+ if (query.cursor === void 0) return { bytes, from };
601
+ const match = LOG_CURSOR.exec(query.cursor);
602
+ if (match === null) {
603
+ throw new TrackerError("invalid_request", "\u041A\u0443\u0440\u0441\u043E\u0440 \u043B\u043E\u0433\u0430 \u043D\u0435\u0432\u0435\u0440\u0435\u043D: \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 nextCursor \u043F\u0440\u043E\u0448\u043B\u043E\u0433\u043E \u043E\u0442\u0432\u0435\u0442\u0430 log() \u0438\u043B\u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439\u0442\u0435 cursor.");
604
+ }
605
+ return { bytes, from: "head", resume: { page: Number(match[1]), offset: Number(match[2]) } };
606
+ }
607
+ function logCursor(page, offset) {
608
+ return `c:${String(page)}:${String(offset)}`;
609
+ }
610
+ function logOf(input) {
611
+ const stalled = input.text === "" && input.more;
612
+ const follow = !input.complete || input.more && !stalled;
613
+ const window = lineWindow(input.text, { start: input.startCut ?? (input.truncated && !input.more), end: follow, endBytes: input.end });
614
+ const clean = maskKeyTail(input.raw ? window.text : cleanLogText(window.text));
615
+ const text2 = clipText(redactSecretText(clean), CI_LOG_MAX_BYTES);
616
+ return {
617
+ text: text2,
618
+ truncated: input.truncated,
619
+ complete: input.complete,
620
+ ...follow ? { nextCursor: logCursor(input.page, input.end - window.held) } : {},
621
+ ...input.size === void 0 ? {} : { size: input.size }
622
+ };
623
+ }
624
+ var KEY_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/g;
625
+ var KEY_END = /-----END [A-Z ]*PRIVATE KEY-----/;
626
+ var WHOLE_ANSI_SEQUENCE = /^(?:\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[ -/]*(?![[\]])[0-~])/;
627
+ var VALUE_KEY = /(?:[A-Za-z][\w.-]{0,63}["']?\s*[=:]|\b(?:Bearer|Basic|Token))\s*["']?$/i;
628
+ var VALUE_KEY_LOOKBACK = 80;
629
+ function lineWindow(text2, cut) {
630
+ let to = text2.length;
631
+ if (cut.end) {
632
+ const line = text2.lastIndexOf("\n");
633
+ to = keyStart(text2, line >= 0 ? line + 1 : wordCut(text2, byteCut(text2)));
634
+ }
635
+ let held = to === text2.length ? 0 : utf8Length(text2.slice(to));
636
+ if (held > cut.endBytes) {
637
+ to = suffixStart(text2, cut.endBytes);
638
+ held = cut.endBytes;
639
+ }
640
+ let from = 0;
641
+ if (cut.start) {
642
+ const line = text2.indexOf("\n");
643
+ from = Math.min(line >= 0 ? line + 1 : firstWordEnd(text2), to);
644
+ }
645
+ return { text: text2.slice(from, to), held };
646
+ }
647
+ function byteCut(text2) {
648
+ let to = text2.length;
649
+ const escape = text2.lastIndexOf("\x1B");
650
+ if (escape > 0 && !WHOLE_ANSI_SEQUENCE.test(text2.slice(escape))) to = escape;
651
+ if (to > 1 && text2[to - 1] === "\r") to -= 1;
652
+ return to;
653
+ }
654
+ function wordCut(text2, to) {
655
+ let space = to - 1;
656
+ while (space >= 0 && !/\s/.test(text2.charAt(space))) space -= 1;
657
+ if (space < 0) return to;
658
+ const word = space + 1;
659
+ const before = text2.slice(Math.max(0, word - VALUE_KEY_LOOKBACK), word);
660
+ const key = VALUE_KEY.exec(before);
661
+ const keyed = key === null ? word : word - before.length + key.index;
662
+ return keyed > 0 ? keyed : word;
663
+ }
664
+ function firstWordEnd(text2) {
665
+ const space = /\s+/.exec(text2);
666
+ return space === null ? 0 : space.index + space[0].length;
667
+ }
668
+ function keyStart(text2, to) {
669
+ const head = text2.slice(0, to);
670
+ const begin = [...head.matchAll(KEY_BEGIN)].at(-1);
671
+ if (begin === void 0 || KEY_END.test(head.slice(begin.index))) return to;
672
+ const line = head.lastIndexOf("\n", begin.index) + 1;
673
+ return line > 0 ? line : begin.index > 0 ? begin.index : to;
674
+ }
675
+ function maskKeyTail(text2) {
676
+ const end = KEY_END.exec(text2);
677
+ if (end !== null && [...text2.slice(0, end.index).matchAll(KEY_BEGIN)].length === 0) return REDACTED + text2.slice(end.index + end[0].length);
678
+ let at = 0;
679
+ for (; ; ) {
680
+ const next = text2.indexOf("\n", at);
681
+ if (!isKeyBodyLine(text2.slice(at, next < 0 ? text2.length : next))) break;
682
+ at = next < 0 ? text2.length : next + 1;
683
+ if (next < 0) break;
684
+ }
685
+ if (at === 0) return text2;
686
+ return REDACTED + (text2[at - 1] === "\n" ? text2.slice(at - 1) : "");
687
+ }
688
+ function isKeyBodyLine(line) {
689
+ const body = line.endsWith("\r") ? line.slice(0, -1) : line;
690
+ return /^[A-Za-z0-9+/]{16,76}={0,2}$/.test(body) && /[A-Z]/.test(body) && /[a-z]/.test(body) && /\d/.test(body);
691
+ }
692
+ var utf8 = new TextEncoder();
693
+ function utf8Length(text2) {
694
+ return utf8.encode(text2).length;
695
+ }
696
+ function suffixStart(text2, bytes) {
697
+ let index = text2.length;
698
+ let count = 0;
699
+ while (index > 0 && count < bytes) {
700
+ const low = text2.charCodeAt(index - 1);
701
+ const pair = index > 1 && low >= 56320 && low <= 57343 && text2.charCodeAt(index - 2) >= 55296 && text2.charCodeAt(index - 2) <= 56319;
702
+ count += utf8Length(text2.slice(index - (pair ? 2 : 1), index));
703
+ index -= pair ? 2 : 1;
704
+ }
705
+ return index;
706
+ }
707
+ var ANSI_SEQUENCE = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\|$)|\x1b[ -/]*[0-~]/g;
708
+ var GITLAB_SECTION_MARKER = /section_(?:start|end):\d+:[A-Za-z0-9_.-]+(?:\[[^\]\r\n]*\])?\r?/g;
709
+ function cleanLogText(text2) {
710
+ const plain = text2.replace(ANSI_SEQUENCE, "").replace(GITLAB_SECTION_MARKER, "").replace(/\r+\n/g, "\n");
711
+ if (!plain.includes("\r")) return plain;
712
+ return plain.split("\n").map((line) => line.includes("\r") ? line.split("\r").findLast((part) => part !== "") ?? "" : line).join("\n");
713
+ }
714
+ function isFinished(category) {
715
+ return CI_FINISHED_STATUSES.includes(category);
716
+ }
717
+
718
+ // src/tracker/github-ci.lib.ts
719
+ var DEFAULT_API = "https://api.github.com";
720
+ var TARGET = /^[\w.-]+\/[\w.-]+$/;
721
+ var ID = /^[1-9]\d{0,19}$/;
722
+ var SHA = /^[0-9a-f]{7,64}$/;
723
+ var DISPATCH_INPUTS_MAX = 25;
724
+ var RUN_FILTERS = ciRunFilterSchema.options;
725
+ var JOB_FILTERS = ciJobFilterSchema.options;
726
+ var RUNS_SUBJECT = "\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI GitHub";
727
+ var JOBS_SUBJECT = "\u0417\u0430\u0434\u0430\u043D\u0438\u044F CI GitHub";
728
+ var WORKFLOWS_SUBJECT = "\u0421\u043F\u0438\u0441\u043E\u043A workflow GitHub";
729
+ var TRIGGER_SUBJECT = "\u0417\u0430\u043F\u0443\u0441\u043A CI GitHub";
730
+ var CAPABILITIES = {
731
+ workflows: true,
732
+ runs: { filters: RUN_FILTERS, clientSide: ["status", "event", "updatedAfter", "updatedBefore"], sorts: [] },
733
+ jobs: { filters: JOB_FILTERS, clientSide: ["status"], sorts: [] },
734
+ log: { live: false, cursor: true },
735
+ trigger: { supported: true, workflows: "one", inputs: true, tags: true },
736
+ rerun: { all: true, failedOnly: true, job: true, debug: true },
737
+ cancel: { run: true, job: false }
738
+ };
739
+ function githubCi() {
740
+ return { capabilities: CAPABILITIES, create: (context) => createGithubCiAdapter(context) };
741
+ }
742
+ function createGithubCiAdapter({ connection, fetch }) {
743
+ const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
744
+ if (!TARGET.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
745
+ const api = baseUrl(connection.url, DEFAULT_API);
746
+ const root = `${api}/repos/${target}`;
747
+ const actions = `${root}/actions`;
748
+ const headers = {
749
+ accept: "application/vnd.github+json",
750
+ "x-github-api-version": "2022-11-28",
751
+ ...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
752
+ };
753
+ const runKey = (key) => {
754
+ if (!ID.test(key)) throw new TrackerError("not_found", `\u0412 GitHub \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.`);
755
+ return key;
756
+ };
757
+ const jobKey = (key) => {
758
+ if (!ID.test(key)) throw new TrackerError("not_found", `\u0412 GitHub \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.`);
759
+ return key;
760
+ };
761
+ const missingRun = (key) => ({ failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.` });
762
+ 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);
764
+ const headShaOf = async (query) => {
765
+ if (query.pullRequest === void 0) return query.sha;
766
+ 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
+ const pull = query.pullRequest;
768
+ 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}.` } });
770
+ const head = isRecord(body) && isRecord(body.head) ? text(body.head.sha) : void 0;
771
+ 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
+ return head;
773
+ };
774
+ return {
775
+ listWorkflows: async (query) => {
776
+ assertListQuery(WORKFLOWS_SUBJECT, { filters: [], clientSide: [], sorts: [] }, query, []);
777
+ const page = pageOf(query.cursor);
778
+ 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 });
780
+ return { items: rowsOf(body, "workflows").map(workflowOf), ...nextCursorOf(page, responseHeaders.get("link") ?? "") };
781
+ },
782
+ listRuns: async (query) => {
783
+ assertListQuery(RUNS_SUBJECT, CAPABILITIES.runs, query, RUN_FILTERS);
784
+ const page = pageOf(query.cursor);
785
+ const sha = await headShaOf(query);
786
+ const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
787
+ if (query.ref !== void 0) params.set("branch", query.ref);
788
+ if (sha !== void 0) params.set("head_sha", sha);
789
+ if (query.actor !== void 0) params.set("actor", query.actor);
790
+ const status = statusParam(query.status);
791
+ if (status !== void 0) params.set("status", status);
792
+ const event = eventParam(query.event);
793
+ if (event !== void 0) params.set("event", event);
794
+ const created = createdParam(query);
795
+ if (created !== void 0) params.set("created", created);
796
+ 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 });
798
+ const items = rowsOf(body, "workflow_runs").map(runOf).filter(
799
+ (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
+ );
801
+ return { items, ...nextCursorOf(page, responseHeaders.get("link") ?? "") };
802
+ },
803
+ getRun,
804
+ listJobs: async (key, query) => {
805
+ const run = runKey(key);
806
+ const { allAttempts, ...rest } = query;
807
+ assertListQuery(JOBS_SUBJECT, CAPABILITIES.jobs, rest, JOB_FILTERS);
808
+ const page = pageOf(query.cursor);
809
+ 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) });
811
+ const items = rowsOf(body, "jobs").map(jobOf).filter((job) => query.status === void 0 || query.status.includes(job.status.category));
812
+ return { items, ...nextCursorOf(page, responseHeaders.get("link") ?? "") };
813
+ },
814
+ getJob: async (key) => {
815
+ const id = jobKey(key);
816
+ return jobOf((await requestJson(`${actions}/jobs/${id}`, { fetch, headers, missing: missingJob(id) })).body);
817
+ },
818
+ log: async (key, query) => {
819
+ const id = jobKey(key);
820
+ const window = logWindow(query);
821
+ const job = (await requestJson(`${actions}/jobs/${id}`, { fetch, headers, missing: missingJob(id) })).body;
822
+ const complete = isFinished(statusOf(isRecord(job) ? job : {}).category);
823
+ if (!complete) return { text: "", truncated: false, complete: false, nextCursor: logCursor(0, 0) };
824
+ const response = await requestText(`${actions}/jobs/${id}/logs`, {
825
+ fetch,
826
+ headers,
827
+ skip: window.resume?.offset ?? 0,
828
+ keep: { bytes: window.bytes, from: window.from },
829
+ missing: missingJob(id)
830
+ });
831
+ return logOf({
832
+ text: response.text,
833
+ truncated: window.from === "tail" ? response.start > 0 : !response.whole,
834
+ complete,
835
+ page: 0,
836
+ end: response.end,
837
+ // Курсор дальше имеет смысл только когда окно head оборвалось раньше конца тела: tail уже читает конец.
838
+ more: window.from === "head" && !response.whole,
839
+ ...response.whole ? { size: response.read } : {},
840
+ raw: query.raw ?? false
841
+ });
842
+ },
843
+ trigger: async (input) => {
844
+ const workflows = input.workflows ?? [];
845
+ const workflow = workflows[0];
846
+ if (workflows.length !== 1 || workflow === void 0) {
847
+ throw new TrackerError("invalid_request", `${TRIGGER_SUBJECT}: \u043D\u0443\u0436\u0435\u043D \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u0438\u043D workflow \u2014 id \u0438\u043B\u0438 \u0438\u043C\u044F \u0444\u0430\u0439\u043B\u0430.`);
848
+ }
849
+ const inputs = input.inputs ?? {};
850
+ const count = Object.keys(inputs).length;
851
+ 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
+ const { body } = await requestJson(`${actions}/workflows/${encodeURIComponent(workflow)}/dispatches`, {
853
+ fetch,
854
+ headers,
855
+ method: "POST",
856
+ body: { ref: input.ref, ...count === 0 ? {} : { inputs }, return_run_details: true }
857
+ });
858
+ const run = runIdOf(isRecord(body) ? body.workflow_run_id : void 0);
859
+ return run === void 0 ? {} : { run: await getRun(run) };
860
+ },
861
+ rerun: async (key, input) => {
862
+ const run = runKey(key);
863
+ await requestJson(`${actions}/runs/${run}/${input.failedOnly === true ? "rerun-failed-jobs" : "rerun"}`, {
864
+ fetch,
865
+ headers,
866
+ method: "POST",
867
+ body: { enable_debug_logging: input.debug === true },
868
+ missing: missingRun(run)
869
+ });
870
+ return getRun(run);
871
+ },
872
+ rerunJob: async (key, input) => {
873
+ const id = jobKey(key);
874
+ const job = (await requestJson(`${actions}/jobs/${id}`, { fetch, headers, missing: missingJob(id) })).body;
875
+ const run = runIdOf(isRecord(job) ? job.run_id : void 0);
876
+ 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) });
878
+ return getRun(run);
879
+ },
880
+ cancel: async (key) => {
881
+ const run = runKey(key);
882
+ await requestJson(`${actions}/runs/${run}/cancel`, { fetch, headers, method: "POST", conflictOn: [409], missing: missingRun(run) });
883
+ return getRun(run);
884
+ },
885
+ cancelJob: async () => {
886
+ throw new TrackerError("unsupported", "\u041E\u0442\u043C\u0435\u043D\u0430 CI GitHub: \u0437\u0430\u0434\u0430\u043D\u0438\u044F \u043F\u043E \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u0438 \u043D\u0435 \u043E\u0442\u043C\u0435\u043D\u044F\u044E\u0442\u0441\u044F, \u043E\u0442\u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0433\u043E\u043D.");
887
+ }
888
+ };
889
+ }
890
+ function pageOf(cursor) {
891
+ const page = cursor === void 0 ? 1 : Number(cursor);
892
+ return Number.isInteger(page) && page >= 1 ? page : 1;
893
+ }
894
+ function nextCursorOf(page, link) {
895
+ return /rel="next"/.test(link) ? { nextCursor: String(page + 1) } : {};
896
+ }
897
+ function rowsOf(body, field) {
898
+ const rows = isRecord(body) ? body[field] : void 0;
899
+ return Array.isArray(rows) ? rows : [];
900
+ }
901
+ function keyOf(value) {
902
+ if (typeof value === "number" && Number.isSafeInteger(value)) return String(value);
903
+ return typeof value === "string" && value !== "" ? value : "?";
904
+ }
905
+ function positiveInt(value) {
906
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
907
+ }
908
+ function runIdOf(value) {
909
+ const id = positiveInt(value);
910
+ return id === void 0 ? void 0 : String(id);
911
+ }
912
+ function statusOf(item) {
913
+ const raw = text(item.status) ?? "unknown";
914
+ if (raw !== "completed") return { category: lookup(GITHUB_RUN_STATUSES, raw, "running"), name: raw };
915
+ const conclusion = text(item.conclusion);
916
+ return { category: lookup(GITHUB_RUN_CONCLUSIONS, conclusion, "failed"), name: conclusion ?? raw };
917
+ }
918
+ function statusParam(status) {
919
+ if (status === void 0 || status.length === 0) return void 0;
920
+ if (status.every(isFinished)) return "completed";
921
+ return status.length === 1 && status[0] === "running" ? "in_progress" : void 0;
922
+ }
923
+ function eventParam(event) {
924
+ if (event === void 0 || event.length !== 1) return void 0;
925
+ const raws = keysFor(GITHUB_RUN_EVENTS, event);
926
+ return raws.length === 1 ? raws[0] : void 0;
927
+ }
928
+ function createdParam(query) {
929
+ const { createdAfter: after, createdBefore: before } = query;
930
+ if (after !== void 0 && before !== void 0) return `${isoTime(after)}..${isoTime(before)}`;
931
+ if (after !== void 0) return `>=${isoTime(after)}`;
932
+ return before !== void 0 ? `<=${isoTime(before)}` : void 0;
933
+ }
934
+ function workflowOf(raw) {
935
+ const item = isRecord(raw) ? raw : {};
936
+ const key = keyOf(item.id);
937
+ const path = text(item.path);
938
+ return { key, name: text(item.name) ?? key, ...path === void 0 ? {} : { path }, enabled: item.state === "active" };
939
+ }
940
+ function runOf(raw) {
941
+ const item = isRecord(raw) ? raw : {};
942
+ const key = keyOf(item.id);
943
+ const status = statusOf(item);
944
+ const name = text(item.name) ?? text(item.path);
945
+ const branch = text(item.head_branch);
946
+ const sha = text(item.head_sha);
947
+ const actor = isRecord(item.actor) ? text(item.actor.login) : void 0;
948
+ const pull = Array.isArray(item.pull_requests) ? item.pull_requests[0] : void 0;
949
+ const pullNumber = isRecord(pull) && typeof pull.number === "number" ? String(pull.number) : void 0;
950
+ const number = positiveInt(item.run_number);
951
+ const attempt = positiveInt(item.run_attempt);
952
+ const url = text(item.html_url);
953
+ const createdAt = toTimestamp(item.created_at);
954
+ const startedAt = toTimestamp(item.run_started_at);
955
+ const updatedAt = toTimestamp(item.updated_at);
956
+ const finishedAt = text(item.status) === "completed" ? updatedAt : void 0;
957
+ const event = text(item.event);
958
+ return {
959
+ key,
960
+ ...number === void 0 ? {} : { number },
961
+ workflows: name === void 0 ? [] : [name],
962
+ status,
963
+ ...event === void 0 ? {} : { event: { category: lookup(GITHUB_RUN_EVENTS, event, "other"), name: event } },
964
+ ...branch === void 0 ? {} : { ref: branch },
965
+ ...sha === void 0 || !SHA.test(sha) ? {} : { sha },
966
+ ...actor === void 0 ? {} : { actor },
967
+ ...pullNumber === void 0 ? {} : { pullRequest: pullNumber },
968
+ ...attempt === void 0 ? {} : { attempt },
969
+ links: url === void 0 ? [] : [{ url, title: "GitHub Actions" }],
970
+ ...createdAt === void 0 ? {} : { createdAt },
971
+ ...startedAt === void 0 ? {} : { startedAt },
972
+ ...finishedAt === void 0 ? {} : { finishedAt },
973
+ ...updatedAt === void 0 ? {} : { updatedAt }
974
+ };
975
+ }
976
+ function jobOf(raw) {
977
+ const item = isRecord(raw) ? raw : {};
978
+ const key = keyOf(item.id);
979
+ const url = text(item.html_url);
980
+ const startedAt = toTimestamp(item.started_at);
981
+ const finishedAt = toTimestamp(item.completed_at);
982
+ const steps = Array.isArray(item.steps) ? item.steps.map(stepOf) : void 0;
983
+ return {
984
+ key,
985
+ runKey: runIdOf(item.run_id) ?? "?",
986
+ name: text(item.name) ?? key,
987
+ status: statusOf(item),
988
+ ...steps === void 0 ? {} : { steps },
989
+ links: url === void 0 ? [] : [{ url, title: "GitHub Actions" }],
990
+ ...startedAt === void 0 ? {} : { startedAt },
991
+ ...finishedAt === void 0 ? {} : { finishedAt }
992
+ };
993
+ }
994
+ function stepOf(raw) {
995
+ const item = isRecord(raw) ? raw : {};
996
+ const number = positiveInt(item.number) ?? 0;
997
+ return { number, name: text(item.name) ?? String(number), status: statusOf(item) };
998
+ }
999
+
1000
+ // src/tracker/gitlab-ci.lib.ts
1001
+ var DEFAULT_URL = "https://gitlab.com";
1002
+ var TARGET2 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
1003
+ var ID2 = /^[1-9]\d{0,19}$/;
1004
+ var RUN_FILTERS2 = ciRunFilterSchema.options;
1005
+ var JOB_FILTERS2 = ciJobFilterSchema.options;
1006
+ var CAPABILITIES2 = {
1007
+ workflows: false,
1008
+ runs: { filters: RUN_FILTERS2, clientSide: ["status", "event"], sorts: ["created", "updated"] },
1009
+ jobs: { filters: JOB_FILTERS2, clientSide: [], sorts: [] },
1010
+ log: { live: true, cursor: true },
1011
+ trigger: { supported: true, workflows: "none", inputs: true, tags: true },
1012
+ rerun: { all: false, failedOnly: true, job: true, debug: false },
1013
+ cancel: { run: true, job: true }
1014
+ };
1015
+ function gitlabCi() {
1016
+ return { capabilities: CAPABILITIES2, create: (context) => createGitlabCiAdapter(context) };
1017
+ }
1018
+ function createGitlabCiAdapter({ connection, fetch }) {
1019
+ 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
+ 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`;
1022
+ const project = `${api}/projects/${encodeURIComponent(target)}`;
1023
+ const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
1024
+ const rawOf = (table, categories) => {
1025
+ if (categories === void 0 || categories.length !== 1) return void 0;
1026
+ const raws = keysFor(table, categories);
1027
+ return raws.length === 1 ? raws[0] : void 0;
1028
+ };
1029
+ const pipelineKey = (key) => {
1030
+ if (!ID2.test(key)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.`);
1031
+ return key;
1032
+ };
1033
+ const jobKey = (key) => {
1034
+ if (!ID2.test(key)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.`);
1035
+ return key;
1036
+ };
1037
+ const missingPipeline = (key) => ({ failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.` });
1038
+ 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);
1040
+ return {
1041
+ listWorkflows: async () => {
1042
+ 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.");
1043
+ },
1044
+ listRuns: async (query) => {
1045
+ assertListQuery("\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI GitLab", CAPABILITIES2.runs, query, RUN_FILTERS2);
1046
+ const params = new URLSearchParams({ per_page: String(query.limit), page: String(pageOf2(query.cursor)) });
1047
+ let body;
1048
+ let responseHeaders;
1049
+ let keep;
1050
+ if (query.pullRequest !== void 0) {
1051
+ const mr = query.pullRequest;
1052
+ if (!ID2.test(mr)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 merge request ${mr}.`);
1053
+ 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
+ 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
+ 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}.` } });
1057
+ body = result.body;
1058
+ responseHeaders = result.headers;
1059
+ 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);
1060
+ } else {
1061
+ if (query.ref !== void 0) params.set("ref", query.ref);
1062
+ if (query.sha !== void 0) params.set("sha", query.sha);
1063
+ if (query.actor !== void 0) params.set("username", query.actor);
1064
+ if (query.workflow !== void 0) params.set("name", query.workflow);
1065
+ if (query.status !== void 0 && query.status.length > 0) {
1066
+ const status = rawOf(GITLAB_CI_STATUSES, query.status);
1067
+ if (status !== void 0) params.set("status", status);
1068
+ if (status === void 0 && query.status.every(isFinished)) params.set("scope", "finished");
1069
+ }
1070
+ const source = rawOf(GITLAB_PIPELINE_SOURCES, query.event);
1071
+ if (source !== void 0) params.set("source", source);
1072
+ if (query.createdAfter !== void 0) params.set("created_after", isoTime(query.createdAfter));
1073
+ if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
1074
+ if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
1075
+ if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
1076
+ params.set("order_by", query.sort?.field === "updated" ? "updated_at" : "id");
1077
+ params.set("sort", query.sort?.direction ?? "desc");
1078
+ const result = await requestJson(`${project}/pipelines?${params.toString()}`, { fetch, headers });
1079
+ body = result.body;
1080
+ responseHeaders = result.headers;
1081
+ 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));
1082
+ }
1083
+ const items = (Array.isArray(body) ? body : []).map(runOf2).filter(keep);
1084
+ const next = responseHeaders.get("x-next-page");
1085
+ return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
1086
+ },
1087
+ getRun: getPipeline,
1088
+ listJobs: async (runKey, query) => {
1089
+ const { allAttempts, ...rest } = query;
1090
+ assertListQuery("\u0417\u0430\u0434\u0430\u043D\u0438\u044F CI GitLab", CAPABILITIES2.jobs, rest, JOB_FILTERS2);
1091
+ const params = new URLSearchParams({ per_page: String(query.limit), page: String(pageOf2(query.cursor)), include_retried: String(allAttempts ?? false) });
1092
+ 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) });
1094
+ const items = (Array.isArray(body) ? body : []).map(jobOf2);
1095
+ const next = responseHeaders.get("x-next-page");
1096
+ return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
1097
+ },
1098
+ getJob: async (key) => jobOf2((await requestJson(`${project}/jobs/${jobKey(key)}`, { fetch, headers, missing: missingJob(key) })).body),
1099
+ log: async (key, query) => {
1100
+ const window = logWindow(query);
1101
+ const job = (await requestJson(`${project}/jobs/${jobKey(key)}`, { fetch, headers, missing: missingJob(key) })).body;
1102
+ const rawStatus = isRecord(job) && typeof job.status === "string" ? job.status : "unknown";
1103
+ 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) });
1105
+ return logOf({
1106
+ text: response.text,
1107
+ truncated: window.from === "tail" ? response.start > 0 : !response.whole,
1108
+ complete,
1109
+ page: 0,
1110
+ end: response.end,
1111
+ more: !response.whole,
1112
+ ...response.whole ? { size: response.read } : {},
1113
+ // Кольцевой буфер отрезал начало хвоста и при потолке чтения (whole=false): там default
1114
+ // startCut (truncated && !more) молчит, а первая неполная строка в окне остаётся.
1115
+ startCut: window.from === "tail" && response.start > 0,
1116
+ raw: query.raw ?? false
1117
+ });
1118
+ },
1119
+ trigger: async (input) => {
1120
+ if (input.workflows !== void 0 && input.workflows.length > 0) {
1121
+ throw new TrackerError("unsupported", "\u0417\u0430\u043F\u0443\u0441\u043A CI GitLab: workflow \u043D\u0435 \u0437\u0430\u0434\u0430\u0451\u0442\u0441\u044F \u2014 \u0442\u043E\u043B\u044C\u043A\u043E ref \u0438 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435.");
1122
+ }
1123
+ const variables = Object.entries(input.inputs ?? {}).map(([key, value]) => ({ key, value, variable_type: "env_var" }));
1124
+ const { body } = await requestJson(`${project}/pipeline`, {
1125
+ fetch,
1126
+ headers,
1127
+ method: "POST",
1128
+ body: { ref: input.ref, ...variables.length === 0 ? {} : { variables } },
1129
+ missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 \u0432\u0435\u0442\u043A\u0438 \u0438\u043B\u0438 \u0442\u0435\u0433\u0430 ${input.ref}.` }
1130
+ });
1131
+ return { run: runOf2(body) };
1132
+ },
1133
+ rerun: async (runKey, input) => {
1134
+ 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
+ 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);
1137
+ },
1138
+ rerunJob: async (jobKeyValue, input) => {
1139
+ 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;
1141
+ const pipeline = isRecord(body) && isRecord(body.pipeline) && typeof body.pipeline.id === "number" ? String(body.pipeline.id) : void 0;
1142
+ 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
+ return getPipeline(pipeline);
1144
+ },
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)
1147
+ };
1148
+ }
1149
+ function pageOf2(cursor) {
1150
+ const page = cursor === void 0 ? 1 : Number(cursor);
1151
+ return Number.isInteger(page) && page >= 1 ? page : 1;
1152
+ }
1153
+ function runOf2(raw) {
1154
+ const item = isRecord(raw) ? raw : {};
1155
+ const key = typeof item.id === "number" ? String(item.id) : "?";
1156
+ const rawStatus = typeof item.status === "string" ? item.status : "unknown";
1157
+ const rawSource = text(item.source);
1158
+ const mr = typeof item.ref === "string" ? /^refs\/merge-requests\/(\d+)\/head$/.exec(item.ref) : null;
1159
+ const user = isRecord(item.user) ? text(item.user.username) : void 0;
1160
+ const url = text(item.web_url);
1161
+ const name = text(item.name);
1162
+ const createdAt = toTimestamp(item.created_at);
1163
+ const startedAt = toTimestamp(item.started_at);
1164
+ const finishedAt = toTimestamp(item.finished_at);
1165
+ const updatedAt = toTimestamp(item.updated_at);
1166
+ return {
1167
+ key,
1168
+ ...typeof item.iid === "number" ? { number: item.iid } : {},
1169
+ workflows: name === void 0 ? [] : [name],
1170
+ status: { category: lookup(GITLAB_CI_STATUSES, rawStatus, "running"), name: rawStatus },
1171
+ ...rawSource === void 0 ? {} : { event: { category: lookup(GITLAB_PIPELINE_SOURCES, rawSource, "other"), name: rawSource } },
1172
+ ...typeof item.ref === "string" && item.ref !== "" ? { ref: item.ref } : {},
1173
+ ...typeof item.sha === "string" && /^[0-9a-f]{7,64}$/.test(item.sha) ? { sha: item.sha } : {},
1174
+ ...user === void 0 ? {} : { actor: user },
1175
+ ...mr === null ? {} : { pullRequest: mr[1] },
1176
+ links: url === void 0 ? [] : [{ url, title: "GitLab" }],
1177
+ ...createdAt === void 0 ? {} : { createdAt },
1178
+ ...startedAt === void 0 ? {} : { startedAt },
1179
+ ...finishedAt === void 0 ? {} : { finishedAt },
1180
+ ...updatedAt === void 0 ? {} : { updatedAt }
1181
+ };
1182
+ }
1183
+ function jobOf2(raw) {
1184
+ const item = isRecord(raw) ? raw : {};
1185
+ const key = typeof item.id === "number" ? String(item.id) : "?";
1186
+ const rawStatus = typeof item.status === "string" ? item.status : "unknown";
1187
+ const pipeline = isRecord(item.pipeline) && typeof item.pipeline.id === "number" ? String(item.pipeline.id) : "?";
1188
+ const url = text(item.web_url);
1189
+ const stage = text(item.stage);
1190
+ const startedAt = toTimestamp(item.started_at);
1191
+ const finishedAt = toTimestamp(item.finished_at);
1192
+ return {
1193
+ key,
1194
+ runKey: pipeline,
1195
+ name: text(item.name) ?? key,
1196
+ ...stage === void 0 ? {} : { stage },
1197
+ status: { category: lookup(GITLAB_CI_STATUSES, rawStatus, "running"), name: rawStatus },
1198
+ links: url === void 0 ? [] : [{ url, title: "GitLab" }],
1199
+ ...startedAt === void 0 ? {} : { startedAt },
1200
+ ...finishedAt === void 0 ? {} : { finishedAt }
1201
+ };
1202
+ }
1203
+
1204
+ // src/tracker/sourcecraft-ci.lib.ts
1205
+ var DEFAULT_API2 = "https://api.sourcecraft.tech";
1206
+ var TARGET3 = /^[\w.-]+\/[\w.-]+$/;
1207
+ var RUN_SLUG = /^\d{1,20}$/;
1208
+ var PR_SLUG = /^[\w-]{1,100}$/;
1209
+ var JOB_KEY = /^([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)$/;
1210
+ var EXTERNAL_USER = "<external-user>";
1211
+ var JOB_OFFSET_CURSOR = /^o:(\d{1,15})$/;
1212
+ var RUN_FILTER_NAMES = ciRunFilterSchema.options;
1213
+ var RUN_FILTERS3 = RUN_FILTER_NAMES.filter((name) => name !== "ref" && name !== "sha");
1214
+ var JOB_FILTER_NAMES = ciJobFilterSchema.options;
1215
+ var JOB_FILTERS3 = ["status"];
1216
+ var CAPABILITIES3 = {
1217
+ workflows: false,
1218
+ runs: { filters: RUN_FILTERS3, clientSide: RUN_FILTERS3, sorts: [] },
1219
+ jobs: { filters: JOB_FILTERS3, clientSide: JOB_FILTERS3, sorts: [] },
1220
+ log: { live: true, cursor: true },
1221
+ trigger: { supported: true, workflows: "many", inputs: true, tags: true },
1222
+ rerun: { all: false, failedOnly: false, job: false, debug: false },
1223
+ cancel: { run: false, job: false }
1224
+ };
1225
+ function sourcecraftCi() {
1226
+ return { capabilities: CAPABILITIES3, create: (context) => createSourceCraftCiAdapter(context) };
1227
+ }
1228
+ function createSourceCraftCiAdapter({ connection, fetch }) {
1229
+ const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
1230
+ if (!TARGET3.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
1231
+ const repo = `${baseUrl(connection.url, DEFAULT_API2)}/repos/${target}`;
1232
+ const root = `${repo}/cicd`;
1233
+ const headers = { authorization: `Bearer ${connection.token ?? ""}` };
1234
+ const missingRun = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.` });
1235
+ const missingJob = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.` });
1236
+ const missingWorkflow = (run, workflow) => ({ failure: "not_found", message: `\u0412 \u043F\u0440\u043E\u0433\u043E\u043D\u0435 ${run} SourceCraft \u043D\u0435\u0442 workflow ${workflow}.` });
1237
+ const runKey = (key) => {
1238
+ if (!RUN_SLUG.test(key)) throw new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${key}.`);
1239
+ return key;
1240
+ };
1241
+ const jobPath = (key) => {
1242
+ const match = JOB_KEY.exec(key);
1243
+ if (match === null) throw new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.`);
1244
+ const path = { run: partOf(match[1], key), workflow: partOf(match[2], key), task: partOf(match[3], key), cube: partOf(match[4], key) };
1245
+ runKey(path.run);
1246
+ return path;
1247
+ };
1248
+ const getRun = async (key) => runOf3((await requestJson(`${root}/runs/${runKey(key)}`, { fetch, headers, missing: missingRun(key) })).body);
1249
+ const getWorkflow = async (run, workflow) => {
1250
+ const { body } = await requestJson(`${root}/runs/${runKey(run)}/${encodeURIComponent(workflow)}`, { fetch, headers, missing: missingWorkflow(run, workflow) });
1251
+ return isRecord(body) ? body : {};
1252
+ };
1253
+ const pullIdOf = async (slug) => {
1254
+ 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}.` } });
1256
+ const id = isRecord(body) ? text(body.id) : void 0;
1257
+ 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
+ return id;
1259
+ };
1260
+ const flattenJobs = async (run) => {
1261
+ const jobs = [];
1262
+ for (const workflow of (await getRun(run)).workflows) {
1263
+ const tree = await getWorkflow(run, workflow);
1264
+ for (const task of recordList(tree.tasks)) {
1265
+ const taskSlug = text(task.slug);
1266
+ if (taskSlug === void 0) continue;
1267
+ for (const cube of recordList(task.cubes)) {
1268
+ const cubeSlug = text(cube.slug);
1269
+ if (cubeSlug === void 0) continue;
1270
+ jobs.push(jobOf3(run, workflow, taskSlug, cubeSlug, cube));
1271
+ }
1272
+ }
1273
+ }
1274
+ return jobs;
1275
+ };
1276
+ return {
1277
+ listWorkflows: async () => {
1278
+ throw new TrackerError("unsupported", "\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI SourceCraft: \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0433\u043E \u0441\u043F\u0438\u0441\u043A\u0430 workflow \u043D\u0435\u0442, workflow \u0435\u0441\u0442\u044C \u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.");
1279
+ },
1280
+ listRuns: async (query) => {
1281
+ assertListQuery("\u041F\u0440\u043E\u0433\u043E\u043D\u044B CI SourceCraft", CAPABILITIES3.runs, query, RUN_FILTER_NAMES);
1282
+ const pullId = query.pullRequest === void 0 ? void 0 : await pullIdOf(query.pullRequest);
1283
+ const params = new URLSearchParams({ page_size: String(query.limit) });
1284
+ if (query.cursor !== void 0) params.set("page_token", query.cursor);
1285
+ const { body } = await requestJson(`${root}/runs?${params.toString()}`, { fetch, headers });
1286
+ const record = isRecord(body) ? body : {};
1287
+ const items = recordList(record.runs).map((raw) => pullSlugOf(runOf3(raw), raw, pullId, query.pullRequest)).filter((run) => keepsRun(run, query));
1288
+ const token = text(record.next_page_token);
1289
+ return { items, ...token === void 0 ? {} : { nextCursor: token } };
1290
+ },
1291
+ getRun,
1292
+ listJobs: async (runKeyValue, query) => {
1293
+ assertListQuery("\u0417\u0430\u0434\u0430\u043D\u0438\u044F CI SourceCraft", CAPABILITIES3.jobs, query, JOB_FILTER_NAMES);
1294
+ const jobs = await flattenJobs(runKey(runKeyValue));
1295
+ const from = offsetOf(query.cursor);
1296
+ const next = from + query.limit;
1297
+ const page = jobs.slice(from, next);
1298
+ const items = query.status === void 0 ? page : page.filter((job) => query.status?.includes(job.status.category) === true);
1299
+ return { items, ...next < jobs.length ? { nextCursor: `o:${String(next)}` } : {} };
1300
+ },
1301
+ getJob: async (key) => {
1302
+ const path = jobPath(key);
1303
+ const tree = await getWorkflow(path.run, path.workflow);
1304
+ for (const task of recordList(tree.tasks)) {
1305
+ if (text(task.slug) !== path.task) continue;
1306
+ const cube = recordList(task.cubes).find((item) => text(item.slug) === path.cube);
1307
+ if (cube !== void 0) return jobOf3(path.run, path.workflow, path.task, path.cube, cube);
1308
+ }
1309
+ throw new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.`);
1310
+ },
1311
+ log: async (key, query) => {
1312
+ const parsed = jobPath(key);
1313
+ if (parsed.cube.startsWith("#")) {
1314
+ throw new TrackerError("unsupported", `\u041B\u043E\u0433 \u0437\u0430\u0434\u0430\u043D\u0438\u044F SourceCraft ${key}: \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0439 \u043A\u0443\u0431, \u043B\u043E\u0433 \u0447\u0435\u0440\u0435\u0437 API \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D.`);
1315
+ }
1316
+ const window = logWindow(query);
1317
+ const path = pathOf(parsed);
1318
+ const tail = window.resume === void 0 && window.from === "tail";
1319
+ const first = window.resume?.page ?? 1;
1320
+ const deadline = Date.now() + CI_LOG_TIMEOUT_MS;
1321
+ let page = first;
1322
+ let skip = window.resume?.offset ?? 0;
1323
+ let kept = new Uint8Array(0);
1324
+ let dropped = 0;
1325
+ let seen = 0;
1326
+ let lastPage = first;
1327
+ let lastEnd = 0;
1328
+ let done = false;
1329
+ let unread = false;
1330
+ const encoder = new TextEncoder();
1331
+ const decoder = new TextDecoder("utf-8");
1332
+ for (; ; ) {
1333
+ if (!tail && kept.length >= window.bytes) {
1334
+ unread = true;
1335
+ break;
1336
+ }
1337
+ const { body } = await requestJson(`${root}/logs/${path}?page=${String(page)}`, { fetch, headers, missing: missingJob(key) });
1338
+ const record = isRecord(body) ? body : {};
1339
+ const data = encoder.encode(text(record.logs) ?? "");
1340
+ const rest = skip >= data.length ? new Uint8Array(0) : data.subarray(skip);
1341
+ let slice = rest;
1342
+ if (!tail) {
1343
+ const room = rest.subarray(0, Math.min(window.bytes - kept.length, rest.length));
1344
+ slice = room.subarray(0, room.length - trailingByte(room));
1345
+ }
1346
+ const merged = concat(kept, slice);
1347
+ if (merged.length > window.bytes) {
1348
+ const fitted = merged.subarray(merged.length - window.bytes);
1349
+ dropped += merged.length - fitted.length;
1350
+ kept = fitted;
1351
+ } else {
1352
+ kept = merged;
1353
+ }
1354
+ seen += data.length;
1355
+ lastPage = page;
1356
+ lastEnd = tail ? data.length : skip + slice.length;
1357
+ skip = 0;
1358
+ done = record.done === true;
1359
+ if (!tail && rest.length > slice.length) {
1360
+ unread = true;
1361
+ break;
1362
+ }
1363
+ if (done || record.page_complete === false) break;
1364
+ if (page - first + 1 >= CI_LOG_PAGES_MAX || seen >= CI_LOG_READ_MAX_BYTES || Date.now() >= deadline) {
1365
+ unread = true;
1366
+ break;
1367
+ }
1368
+ page += 1;
1369
+ }
1370
+ return logOf({
1371
+ text: decoder.decode(kept.subarray(leadingByte(kept))),
1372
+ truncated: dropped > 0 || unread,
1373
+ complete: done,
1374
+ page: lastPage,
1375
+ end: lastEnd,
1376
+ more: unread,
1377
+ // Размер всего лога известен, только если с первой страницы прочитали их все.
1378
+ size: window.resume === void 0 && !unread ? seen : void 0,
1379
+ // Страниц было прочитано больше окна: начало хвоста отрезано кольцевым буфером, и первая неполная
1380
+ // строка уходит из окна даже когда за ним ещё есть страницы (default startCut там молчит).
1381
+ startCut: tail && dropped > 0,
1382
+ raw: query.raw ?? false
1383
+ });
1384
+ },
1385
+ trigger: async (input) => {
1386
+ const workflows = input.workflows ?? [];
1387
+ 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
+ const values = Object.entries(input.inputs ?? {}).map(([name, value]) => ({ name, value }));
1389
+ 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 })) } });
1391
+ return { run: runOf3(body) };
1392
+ },
1393
+ // Публичный API SourceCraft не умеет ни перезапускать, ни отменять (§1.3): отказ без запроса (§3.6).
1394
+ rerun: async () => {
1395
+ throw new TrackerError("unsupported", "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u0433\u043E\u043D\u0430 CI SourceCraft \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: \u0443 \u043F\u043B\u043E\u0449\u0430\u0434\u043A\u0438 \u043D\u0435\u0442 \u0442\u0430\u043A\u043E\u0433\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0432 API.");
1396
+ },
1397
+ rerunJob: async () => {
1398
+ throw new TrackerError("unsupported", "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0437\u0430\u0434\u0430\u043D\u0438\u044F CI SourceCraft \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: \u0443 \u043F\u043B\u043E\u0449\u0430\u0434\u043A\u0438 \u043D\u0435\u0442 \u0442\u0430\u043A\u043E\u0433\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0432 API.");
1399
+ },
1400
+ cancel: async () => {
1401
+ throw new TrackerError("unsupported", "\u041E\u0442\u043C\u0435\u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 CI SourceCraft \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: \u0443 \u043F\u043B\u043E\u0449\u0430\u0434\u043A\u0438 \u043D\u0435\u0442 \u0442\u0430\u043A\u043E\u0433\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0432 API.");
1402
+ },
1403
+ cancelJob: async () => {
1404
+ throw new TrackerError("unsupported", "\u041E\u0442\u043C\u0435\u043D\u0430 \u0437\u0430\u0434\u0430\u043D\u0438\u044F CI SourceCraft \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: \u0443 \u043F\u043B\u043E\u0449\u0430\u0434\u043A\u0438 \u043D\u0435\u0442 \u0442\u0430\u043A\u043E\u0433\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0432 API.");
1405
+ }
1406
+ };
1407
+ }
1408
+ function partOf(value, key) {
1409
+ const refusal = new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u043D\u0438\u044F ${key}.`);
1410
+ if (value === void 0) throw refusal;
1411
+ let decoded;
1412
+ try {
1413
+ decoded = decodeURIComponent(value);
1414
+ } catch {
1415
+ throw refusal;
1416
+ }
1417
+ if (decoded === "") throw refusal;
1418
+ return decoded;
1419
+ }
1420
+ function pathOf(path) {
1421
+ return [path.run, path.workflow, path.task, path.cube].map(encodeURIComponent).join("/");
1422
+ }
1423
+ function recordList(value) {
1424
+ return Array.isArray(value) ? value.filter(isRecord) : [];
1425
+ }
1426
+ function offsetOf(cursor) {
1427
+ if (cursor === void 0) return 0;
1428
+ const match = JOB_OFFSET_CURSOR.exec(cursor);
1429
+ if (match === null) return 0;
1430
+ const from = Number(match[1]);
1431
+ return Number.isSafeInteger(from) && from >= 0 ? from : 0;
1432
+ }
1433
+ function runOf3(raw) {
1434
+ const item = isRecord(raw) ? raw : {};
1435
+ const key = text(item.slug) ?? "?";
1436
+ const rawStatus = text(item.status) ?? "unknown";
1437
+ const rawEvent = text(item.event_type);
1438
+ const user = isRecord(item.user) ? text(item.user.slug) : void 0;
1439
+ const pull = isRecord(item.pull) ? text(item.pull.slug) : void 0;
1440
+ const dates = isRecord(item.dates) ? item.dates : {};
1441
+ const createdAt = toTimestamp(dates.created_at);
1442
+ const startedAt = toTimestamp(dates.started_at);
1443
+ const finishedAt = toTimestamp(dates.finished_at);
1444
+ const updatedAt = toTimestamp(dates.updated_at);
1445
+ const errors = stringList(item.error_messages);
1446
+ return {
1447
+ key,
1448
+ ...RUN_SLUG.test(key) ? { number: Number(key) } : {},
1449
+ workflows: stringList(recordList(item.workflows).map((workflow) => workflow.slug)),
1450
+ status: { category: lookup(SOURCECRAFT_CI_STATUSES, rawStatus, "running"), name: rawStatus },
1451
+ ...rawEvent === void 0 ? {} : { event: { category: lookup(SOURCECRAFT_RUN_EVENTS, rawEvent, "other"), name: rawEvent } },
1452
+ ...user === void 0 || user === EXTERNAL_USER ? {} : { actor: user },
1453
+ ...pull === void 0 ? {} : { pullRequest: pull },
1454
+ ...errors.length === 0 ? {} : { errors },
1455
+ links: [],
1456
+ ...createdAt === void 0 ? {} : { createdAt },
1457
+ ...startedAt === void 0 ? {} : { startedAt },
1458
+ ...finishedAt === void 0 ? {} : { finishedAt },
1459
+ ...updatedAt === void 0 ? {} : { updatedAt }
1460
+ };
1461
+ }
1462
+ function jobOf3(run, workflow, task, cubeSlug, cube) {
1463
+ const rawStatus = text(cube.status) ?? "unknown";
1464
+ const dates = isRecord(cube.dates) ? cube.dates : {};
1465
+ const createdAt = toTimestamp(dates.created_at);
1466
+ const startedAt = toTimestamp(dates.started_at);
1467
+ const finishedAt = toTimestamp(dates.finished_at);
1468
+ return {
1469
+ key: [run, workflow, task, cubeSlug].map(encodeURIComponent).join("/"),
1470
+ runKey: run,
1471
+ name: cubeSlug,
1472
+ stage: `${workflow}/${task}`,
1473
+ status: { category: lookup(SOURCECRAFT_CI_STATUSES, rawStatus, "running"), name: rawStatus },
1474
+ links: [],
1475
+ ...createdAt === void 0 ? {} : { createdAt },
1476
+ ...startedAt === void 0 ? {} : { startedAt },
1477
+ ...finishedAt === void 0 ? {} : { finishedAt }
1478
+ };
1479
+ }
1480
+ function keepsRun(run, query) {
1481
+ return (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.workflow === void 0 || run.workflows.includes(query.workflow)) && (query.actor === void 0 || run.actor === query.actor) && (query.pullRequest === void 0 || run.pullRequest === query.pullRequest) && inTimeRange(run.createdAt, query.createdAfter, query.createdBefore) && inTimeRange(run.updatedAt, query.updatedAfter, query.updatedBefore);
1482
+ }
1483
+ function pullSlugOf(run, raw, pullId, slug) {
1484
+ if (run.pullRequest !== void 0 || pullId === void 0 || slug === void 0) return run;
1485
+ const id = isRecord(raw.pull) ? text(raw.pull.id) : void 0;
1486
+ return id === pullId ? { ...run, pullRequest: slug } : run;
1487
+ }
1488
+ function leadingByte(window) {
1489
+ let count = 0;
1490
+ while (count < 3 && count < window.length && ((window[count] ?? 0) & 192) === 128) count += 1;
1491
+ return count;
1492
+ }
1493
+ function trailingByte(window) {
1494
+ for (let back = 1; back <= Math.min(4, window.length); back += 1) {
1495
+ const byte = window[window.length - back] ?? 0;
1496
+ if ((byte & 192) === 128) continue;
1497
+ const size = byte >= 240 ? 4 : byte >= 224 ? 3 : byte >= 192 ? 2 : 1;
1498
+ return size > back ? back : 0;
1499
+ }
1500
+ return 0;
1501
+ }
1502
+ function concat(head, tail) {
1503
+ if (head.length === 0) return tail;
1504
+ if (tail.length === 0) return head;
1505
+ const merged = new Uint8Array(head.length + tail.length);
1506
+ merged.set(head, 0);
1507
+ merged.set(tail, head.length);
1508
+ return merged;
1509
+ }
1510
+
307
1511
  // src/tracker/registry.lib.ts
308
1512
  var definitions = /* @__PURE__ */ new Map();
309
1513
  function registerTracker(definition) {
@@ -319,28 +1523,28 @@ function listTrackers() {
319
1523
  }
320
1524
 
321
1525
  // src/tracker/pull.schema.ts
322
- import { z } from "zod";
323
- var pullRequestStatusCategorySchema = z.enum(["draft", "open", "merged", "closed"]);
324
- var pullRequestFilterSchema = z.enum(["status", "author", "source", "target", "labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"]);
325
- var pullRequestSortFieldSchema = z.enum(["created", "updated", "title"]);
326
- var mergeMethodSchema = z.enum(["merge", "squash", "rebase"]);
327
- var pullRequestSchema = z.object({
1526
+ import { z as z2 } from "zod";
1527
+ var pullRequestStatusCategorySchema = z2.enum(["draft", "open", "merged", "closed"]);
1528
+ var pullRequestFilterSchema = z2.enum(["status", "author", "source", "target", "labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"]);
1529
+ var pullRequestSortFieldSchema = z2.enum(["created", "updated", "title"]);
1530
+ var mergeMethodSchema = z2.enum(["merge", "squash", "rebase"]);
1531
+ var pullRequestSchema = z2.object({
328
1532
  /** Номер PR в своём трекере: GitHub number, GitLab iid, SourceCraft slug; для клиента непрозрачная строка. */
329
- key: z.string().min(1).max(200),
330
- title: z.string().min(1).max(1024),
331
- description: z.string().max(1048576).optional(),
332
- status: z.object({ category: pullRequestStatusCategorySchema, name: z.string().min(1).max(200) }),
1533
+ key: z2.string().min(1).max(200),
1534
+ title: z2.string().min(1).max(1024),
1535
+ description: z2.string().max(1048576).optional(),
1536
+ status: z2.object({ category: pullRequestStatusCategorySchema, name: z2.string().min(1).max(200) }),
333
1537
  /** Автор — логин или slug трекера. */
334
- author: z.string().min(1).max(200).optional(),
1538
+ author: z2.string().min(1).max(200).optional(),
335
1539
  /** Ветка-источник и целевая ветка — имя без refs/heads/. */
336
- source: z.string().min(1).max(500),
337
- target: z.string().min(1).max(500),
1540
+ source: z2.string().min(1).max(500),
1541
+ target: z2.string().min(1).max(500),
338
1542
  /** Коммит головы источника. */
339
- headSha: z.string().regex(/^[0-9a-f]{7,64}$/).optional(),
340
- labels: z.array(z.string().min(1).max(200)).max(100),
1543
+ headSha: z2.string().regex(/^[0-9a-f]{7,64}$/).optional(),
1544
+ labels: z2.array(z2.string().min(1).max(200)).max(100),
341
1545
  /** Запрошенные ревьюеры; нет — трекер их в PR не отдаёт. */
342
- reviewers: z.array(z.string().min(1).max(200)).max(100).optional(),
343
- links: z.array(issueLinkSchema).max(100),
1546
+ reviewers: z2.array(z2.string().min(1).max(200)).max(100).optional(),
1547
+ links: z2.array(issueLinkSchema).max(100),
344
1548
  createdAt: timestampSchema.optional(),
345
1549
  updatedAt: timestampSchema.optional(),
346
1550
  mergedAt: timestampSchema.optional(),
@@ -348,10 +1552,10 @@ var pullRequestSchema = z.object({
348
1552
  });
349
1553
 
350
1554
  // src/tracker/github-pull.lib.ts
351
- var DEFAULT_API = "https://api.github.com";
352
- var TARGET = /^[\w.-]+\/[\w.-]+$/;
1555
+ var DEFAULT_API3 = "https://api.github.com";
1556
+ var TARGET4 = /^[\w.-]+\/[\w.-]+$/;
353
1557
  var NUMBER = /^[1-9]\d{0,19}$/;
354
- var SHA = /^[0-9a-f]{7,64}$/;
1558
+ var SHA2 = /^[0-9a-f]{7,64}$/;
355
1559
  var LIST_SUBJECT = "PR GitHub";
356
1560
  var COMMENTS_SUBJECT = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 PR GitHub";
357
1561
  var PULL_CAPABILITIES = {
@@ -377,8 +1581,8 @@ function githubPulls() {
377
1581
  }
378
1582
  function createGithubPullAdapter({ connection, fetch }) {
379
1583
  const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
380
- if (!TARGET.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
381
- const api = baseUrl(connection.url, DEFAULT_API);
1584
+ if (!TARGET4.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
1585
+ const api = baseUrl(connection.url, DEFAULT_API3);
382
1586
  const root = `${api}/repos/${target}/pulls`;
383
1587
  const issues = `${api}/repos/${target}/issues`;
384
1588
  const owner = target.split("/")[0] ?? target;
@@ -397,7 +1601,7 @@ function createGithubPullAdapter({ connection, fetch }) {
397
1601
  list: async (query) => {
398
1602
  assertListQuery(LIST_SUBJECT, PULL_CAPABILITIES.list, query, pullRequestFilterSchema.options);
399
1603
  const status = usedStatuses(query.status);
400
- const page = pageOf(query.cursor);
1604
+ const page = pageOf3(query.cursor);
401
1605
  const params = new URLSearchParams({ state: stateFilter(status), per_page: String(query.limit), page: String(page) });
402
1606
  if (query.source !== void 0) params.set("head", `${owner}:${query.source}`);
403
1607
  if (query.target !== void 0) params.set("base", query.target);
@@ -428,7 +1632,7 @@ function createGithubPullAdapter({ connection, fetch }) {
428
1632
  },
429
1633
  comments: async (key, query) => {
430
1634
  assertCommentQuery(COMMENTS_SUBJECT, PULL_CAPABILITIES.comments, query);
431
- const page = pageOf(query.cursor);
1635
+ const page = pageOf3(query.cursor);
432
1636
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
433
1637
  if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
434
1638
  const { body, headers: responseHeaders } = await requestJson(`${issues}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missing: missing(key) });
@@ -437,7 +1641,7 @@ function createGithubPullAdapter({ connection, fetch }) {
437
1641
  comment: async (key, body) => commentOf((await requestJson(`${issues}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
438
1642
  };
439
1643
  }
440
- function pageOf(cursor) {
1644
+ function pageOf3(cursor) {
441
1645
  const page = cursor === void 0 ? 1 : Number(cursor);
442
1646
  return Number.isInteger(page) && page >= 1 ? page : 1;
443
1647
  }
@@ -516,7 +1720,7 @@ function pullOf(raw) {
516
1720
  ...author === void 0 ? {} : { author },
517
1721
  source: text(head.ref) ?? "?",
518
1722
  target: text(base.ref) ?? "?",
519
- ...sha !== void 0 && SHA.test(sha) ? { headSha: sha } : {},
1723
+ ...sha !== void 0 && SHA2.test(sha) ? { headSha: sha } : {},
520
1724
  labels: Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : label)) : [],
521
1725
  reviewers: logins(item.requested_reviewers),
522
1726
  links: url === void 0 ? [] : [{ url, title: "GitHub" }],
@@ -539,8 +1743,8 @@ function commentOf(raw) {
539
1743
  }
540
1744
 
541
1745
  // src/tracker/github.lib.ts
542
- var DEFAULT_API2 = "https://api.github.com";
543
- var TARGET2 = /^[\w.-]+\/[\w.-]+$/;
1746
+ var DEFAULT_API4 = "https://api.github.com";
1747
+ var TARGET5 = /^[\w.-]+\/[\w.-]+$/;
544
1748
  var NUMBER2 = /^[1-9]\d{0,14}$/;
545
1749
  var LIST_SUBJECT2 = "\u0417\u0430\u0434\u0430\u0447\u0438 GitHub";
546
1750
  var COMMENTS_SUBJECT2 = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitHub";
@@ -562,13 +1766,14 @@ function githubTracker() {
562
1766
  },
563
1767
  create: (context) => createGithubAdapter(context, issues),
564
1768
  issues,
565
- pulls: githubPulls()
1769
+ pulls: githubPulls(),
1770
+ ci: githubCi()
566
1771
  };
567
1772
  }
568
1773
  function createGithubAdapter({ connection, fetch }, capabilities) {
569
1774
  const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
570
- if (!TARGET2.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
571
- const api = baseUrl(connection.url, DEFAULT_API2);
1775
+ if (!TARGET5.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
1776
+ const api = baseUrl(connection.url, DEFAULT_API4);
572
1777
  const root = `${api}/repos/${target}/issues`;
573
1778
  const headers = {
574
1779
  accept: "application/vnd.github+json",
@@ -587,11 +1792,11 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
587
1792
  list: async (query) => {
588
1793
  assertListQuery(LIST_SUBJECT2, capabilities.list, query, ISSUE_FILTERS);
589
1794
  assertCsvLabels(query.labels);
590
- const page = pageOf2(query.cursor);
1795
+ const page = pageOf4(query.cursor);
591
1796
  const search = needsSearch(query);
592
1797
  const params = search ? searchParams(target, query, page) : issuesParams(query, page);
593
1798
  const { body, headers: responseHeaders } = await requestJson(`${search ? `${api}/search/issues` : root}?${params.toString()}`, { fetch, headers });
594
- return issuePageOf(rowsOf(body, search), query, responseHeaders.get("link") ?? "", page);
1799
+ return issuePageOf(rowsOf2(body, search), query, responseHeaders.get("link") ?? "", page);
595
1800
  },
596
1801
  get: async (key) => issueOf(await rawOf(key)),
597
1802
  create: async (input) => {
@@ -611,7 +1816,7 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
611
1816
  },
612
1817
  comments: async (key, query) => {
613
1818
  assertCommentQuery(COMMENTS_SUBJECT2, capabilities.comments, query);
614
- const page = pageOf2(query.cursor);
1819
+ const page = pageOf4(query.cursor);
615
1820
  const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
616
1821
  if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
617
1822
  const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
@@ -620,7 +1825,7 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
620
1825
  comment: async (key, body) => commentOf2((await requestJson(`${root}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
621
1826
  };
622
1827
  }
623
- function pageOf2(cursor) {
1828
+ function pageOf4(cursor) {
624
1829
  const page = cursor === void 0 ? 1 : Number(cursor);
625
1830
  return Number.isInteger(page) && page >= 1 ? page : 1;
626
1831
  }
@@ -675,7 +1880,7 @@ function timeRangeQualifier(field, after, before) {
675
1880
  if (before !== void 0) return `${field}:<=${isoTime(before)}`;
676
1881
  return void 0;
677
1882
  }
678
- function rowsOf(body, search) {
1883
+ function rowsOf2(body, search) {
679
1884
  if (!search) return Array.isArray(body) ? body : [];
680
1885
  return isRecord(body) && Array.isArray(body.items) ? body.items : [];
681
1886
  }
@@ -740,10 +1945,10 @@ function commentOf2(raw) {
740
1945
  }
741
1946
 
742
1947
  // src/tracker/gitlab-pull.lib.ts
743
- var DEFAULT_URL = "https://gitlab.com";
744
- var TARGET3 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
1948
+ var DEFAULT_URL2 = "https://gitlab.com";
1949
+ var TARGET6 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
745
1950
  var IID = /^[1-9]\d{0,19}$/;
746
- var SHA2 = /^[0-9a-f]{7,64}$/;
1951
+ var SHA3 = /^[0-9a-f]{7,64}$/;
747
1952
  var PULL_CAPABILITIES2 = {
748
1953
  list: { filters: pullRequestFilterSchema.options, clientSide: ["status"], sorts: ["created", "updated", "title"] },
749
1954
  comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] },
@@ -764,8 +1969,8 @@ function gitlabPulls() {
764
1969
  }
765
1970
  function createGitlabPullAdapter({ connection, fetch }) {
766
1971
  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");
767
- if (!TARGET3.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
768
- const root = `${baseUrl(connection.url, DEFAULT_URL)}/api/v4/projects/${encodeURIComponent(target)}/merge_requests`;
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`;
769
1974
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
770
1975
  const iid = (key) => {
771
1976
  if (!IID.test(key)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.`);
@@ -776,7 +1981,7 @@ function createGitlabPullAdapter({ connection, fetch }) {
776
1981
  list: async (query) => {
777
1982
  assertListQuery("PR GitLab", PULL_CAPABILITIES2.list, query, pullRequestFilterSchema.options);
778
1983
  const status = usedStatuses2(query.status);
779
- const page = pageOf3(query.cursor);
1984
+ const page = pageOf5(query.cursor);
780
1985
  const params = new URLSearchParams({
781
1986
  state: stateFilter3(status),
782
1987
  per_page: String(query.limit),
@@ -838,7 +2043,7 @@ function createGitlabPullAdapter({ connection, fetch }) {
838
2043
  },
839
2044
  comments: async (key, query) => {
840
2045
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", PULL_CAPABILITIES2.comments, query);
841
- const page = pageOf3(query.cursor);
2046
+ const page = pageOf5(query.cursor);
842
2047
  const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
843
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}.` } });
844
2049
  const next = responseHeaders.get("x-next-page");
@@ -848,7 +2053,7 @@ function createGitlabPullAdapter({ connection, fetch }) {
848
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)
849
2054
  };
850
2055
  }
851
- function pageOf3(cursor) {
2056
+ function pageOf5(cursor) {
852
2057
  const page = cursor === void 0 ? 1 : Number(cursor);
853
2058
  return Number.isInteger(page) && page >= 1 ? page : 1;
854
2059
  }
@@ -916,7 +2121,7 @@ function pullOf2(raw) {
916
2121
  ...author === void 0 ? {} : { author },
917
2122
  source: text(item.source_branch) ?? "?",
918
2123
  target: text(item.target_branch) ?? "?",
919
- ...sha !== void 0 && SHA2.test(sha) ? { headSha: sha } : {},
2124
+ ...sha !== void 0 && SHA3.test(sha) ? { headSha: sha } : {},
920
2125
  labels: labelNames(item.labels),
921
2126
  reviewers: usernames(item.reviewers),
922
2127
  links: url === void 0 ? [] : [{ url, title: "GitLab" }],
@@ -934,8 +2139,8 @@ function commentOf3(raw) {
934
2139
  }
935
2140
 
936
2141
  // src/tracker/gitlab.lib.ts
937
- var DEFAULT_URL2 = "https://gitlab.com";
938
- var TARGET4 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
2142
+ var DEFAULT_URL3 = "https://gitlab.com";
2143
+ var TARGET7 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
939
2144
  var IID2 = /^[1-9]\d{0,14}$/;
940
2145
  var ISSUE_CAPABILITIES = {
941
2146
  list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "priority", "title"] },
@@ -961,13 +2166,14 @@ function gitlabTracker() {
961
2166
  },
962
2167
  create: (context) => createGitlabAdapter(context),
963
2168
  issues: ISSUE_CAPABILITIES,
964
- pulls: gitlabPulls()
2169
+ pulls: gitlabPulls(),
2170
+ ci: gitlabCi()
965
2171
  };
966
2172
  }
967
2173
  function createGitlabAdapter({ connection, fetch }) {
968
2174
  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");
969
- if (!TARGET4.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
970
- const root = `${baseUrl(connection.url, DEFAULT_URL2)}/api/v4/projects/${encodeURIComponent(target)}/issues`;
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`;
971
2177
  const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
972
2178
  const iid = (key) => {
973
2179
  if (!IID2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
@@ -977,7 +2183,7 @@ function createGitlabAdapter({ connection, fetch }) {
977
2183
  return {
978
2184
  list: async (query) => {
979
2185
  assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 GitLab", ISSUE_CAPABILITIES.list, query, ISSUE_FILTERS);
980
- const page = pageOf4(query.cursor);
2186
+ const page = pageOf6(query.cursor);
981
2187
  const params = new URLSearchParams({
982
2188
  state: stateFilter4(query.status),
983
2189
  per_page: String(query.limit),
@@ -1016,7 +2222,7 @@ function createGitlabAdapter({ connection, fetch }) {
1016
2222
  },
1017
2223
  comments: async (key, query) => {
1018
2224
  assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", ISSUE_CAPABILITIES.comments, query);
1019
- const page = pageOf4(query.cursor);
2225
+ const page = pageOf6(query.cursor);
1020
2226
  const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
1021
2227
  const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missingIsIssue: true });
1022
2228
  const next = responseHeaders.get("x-next-page");
@@ -1026,7 +2232,7 @@ function createGitlabAdapter({ connection, fetch }) {
1026
2232
  comment: async (key, body) => commentOf4((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
1027
2233
  };
1028
2234
  }
1029
- function pageOf4(cursor) {
2235
+ function pageOf6(cursor) {
1030
2236
  const page = cursor === void 0 ? 1 : Number(cursor);
1031
2237
  return Number.isInteger(page) && page >= 1 ? page : 1;
1032
2238
  }
@@ -1345,10 +2551,10 @@ function toComment(row) {
1345
2551
  }
1346
2552
 
1347
2553
  // src/tracker/sourcecraft-pull.lib.ts
1348
- var DEFAULT_API3 = "https://api.sourcecraft.tech";
1349
- var TARGET5 = /^[\w.-]+\/[\w.-]+$/;
2554
+ var DEFAULT_API5 = "https://api.sourcecraft.tech";
2555
+ var TARGET8 = /^[\w.-]+\/[\w.-]+$/;
1350
2556
  var SLUG = /^[\w-]{1,100}$/;
1351
- var SHA3 = /^[0-9a-f]{7,64}$/;
2557
+ var SHA4 = /^[0-9a-f]{7,64}$/;
1352
2558
  var PULL_SORT_FIELDS_UPSTREAM = {
1353
2559
  created: "created_at",
1354
2560
  updated: "updated_at",
@@ -1374,8 +2580,8 @@ function sourcecraftPulls() {
1374
2580
  }
1375
2581
  function createPullAdapter({ connection, fetch }) {
1376
2582
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
1377
- if (!TARGET5.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
1378
- const api = baseUrl(connection.url, DEFAULT_API3);
2583
+ if (!TARGET8.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
2584
+ const api = baseUrl(connection.url, DEFAULT_API5);
1379
2585
  const root = `${api}/repos/${target}/pulls`;
1380
2586
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
1381
2587
  const missing = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.` });
@@ -1529,7 +2735,7 @@ function pullOf3(raw) {
1529
2735
  ...author === void 0 ? {} : { author },
1530
2736
  source: text(source.ref) ?? text(item.source_branch) ?? "?",
1531
2737
  target: text(target.ref) ?? text(item.target_branch) ?? "?",
1532
- ...headSha !== void 0 && SHA3.test(headSha) ? { headSha } : {},
2738
+ ...headSha !== void 0 && SHA4.test(headSha) ? { headSha } : {},
1533
2739
  labels: Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : void 0)) : [],
1534
2740
  links: [],
1535
2741
  ...createdAt === void 0 ? {} : { createdAt },
@@ -1546,9 +2752,9 @@ function commentOf6(raw) {
1546
2752
  }
1547
2753
 
1548
2754
  // src/tracker/sourcecraft.lib.ts
1549
- var DEFAULT_API4 = "https://api.sourcecraft.tech";
2755
+ var DEFAULT_API6 = "https://api.sourcecraft.tech";
1550
2756
  var DEFAULT_WEB = "https://sourcecraft.dev";
1551
- var TARGET6 = /^[\w.-]+\/[\w.-]+$/;
2757
+ var TARGET9 = /^[\w.-]+\/[\w.-]+$/;
1552
2758
  var SLUG2 = /^[\w-]{1,100}$/;
1553
2759
  var ISSUE_SORT_FIELDS_UPSTREAM = {
1554
2760
  created: "created_at",
@@ -1574,13 +2780,14 @@ function sourcecraftTracker() {
1574
2780
  },
1575
2781
  issues: ISSUE_CAPABILITIES4,
1576
2782
  create: (context) => createSourceCraftAdapter(context),
1577
- pulls: sourcecraftPulls()
2783
+ pulls: sourcecraftPulls(),
2784
+ ci: sourcecraftCi()
1578
2785
  };
1579
2786
  }
1580
2787
  function createSourceCraftAdapter({ connection, fetch }) {
1581
2788
  const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
1582
- if (!TARGET6.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
1583
- const api = baseUrl(connection.url, DEFAULT_API4);
2789
+ if (!TARGET9.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
2790
+ const api = baseUrl(connection.url, DEFAULT_API6);
1584
2791
  const root = `${api}/repos/${target}/issues`;
1585
2792
  const headers = { authorization: `Bearer ${connection.token ?? ""}` };
1586
2793
  const web = connection.url === void 0 ? `${DEFAULT_WEB}/${target}/issues` : void 0;
@@ -1752,14 +2959,25 @@ function pullRequestAdapterOf(definition, context) {
1752
2959
  }
1753
2960
  export {
1754
2961
  ALL_STATUSES,
2962
+ CI_FINISHED_STATUSES,
2963
+ CI_LOG_DEFAULT_BYTES,
2964
+ CI_LOG_MAX_BYTES,
2965
+ CI_LOG_PAGES_MAX,
2966
+ CI_LOG_READ_MAX_BYTES,
2967
+ CI_LOG_TIMEOUT_MS,
1755
2968
  COMMENT_FILTERS,
1756
2969
  GITHUB_CLOSE_REASONS,
1757
2970
  GITHUB_IN_PROGRESS_LABEL,
1758
2971
  GITHUB_PRIORITY_LABELS,
2972
+ GITHUB_RUN_CONCLUSIONS,
2973
+ GITHUB_RUN_EVENTS,
2974
+ GITHUB_RUN_STATUSES,
2975
+ GITLAB_CI_STATUSES,
1759
2976
  GITLAB_DRAFT_PATTERN,
1760
2977
  GITLAB_DRAFT_PREFIX,
1761
2978
  GITLAB_IN_PROGRESS_LABEL,
1762
2979
  GITLAB_MERGE_REQUEST_STATES,
2980
+ GITLAB_PIPELINE_SOURCES,
1763
2981
  GITLAB_PRIORITY_LABELS,
1764
2982
  ISSUE_FILTERS,
1765
2983
  ISSUE_SORT_FIELDS,
@@ -1768,8 +2986,10 @@ export {
1768
2986
  JIRA_PRIORITY_NAMES,
1769
2987
  JIRA_STATUS_CATEGORIES,
1770
2988
  LEGACY_ISSUE_CAPABILITIES,
2989
+ SOURCECRAFT_CI_STATUSES,
1771
2990
  SOURCECRAFT_PRIORITIES,
1772
2991
  SOURCECRAFT_PULL_STATUSES,
2992
+ SOURCECRAFT_RUN_EVENTS,
1773
2993
  SOURCECRAFT_STATUSES,
1774
2994
  SOURCECRAFT_STATUS_FILTER,
1775
2995
  SOURCECRAFT_STATUS_SLUGS,
@@ -1782,16 +3002,31 @@ export {
1782
3002
  assertCommentQuery,
1783
3003
  assertListQuery,
1784
3004
  baseUrl,
3005
+ ciAdapterOf,
3006
+ ciEventCategorySchema,
3007
+ ciJobFilterSchema,
3008
+ ciJobSchema,
3009
+ ciLogSchema,
3010
+ ciRunFilterSchema,
3011
+ ciRunSchema,
3012
+ ciRunSortFieldSchema,
3013
+ ciStatusCategorySchema,
3014
+ ciStatusSchema,
3015
+ ciWorkflowSchema,
3016
+ cleanLogText,
1785
3017
  clipText,
1786
3018
  containsText,
1787
3019
  deleteTrackerToken,
1788
3020
  findTracker,
3021
+ githubCi,
1789
3022
  githubPulls,
1790
3023
  githubTracker,
3024
+ gitlabCi,
1791
3025
  gitlabPulls,
1792
3026
  gitlabTracker,
1793
3027
  hasTrackerToken,
1794
3028
  inTimeRange,
3029
+ isFinished,
1795
3030
  isRecord,
1796
3031
  isoTime,
1797
3032
  issueCapabilitiesOf,
@@ -1799,6 +3034,9 @@ export {
1799
3034
  keysFor,
1800
3035
  listTrackers,
1801
3036
  localTracker,
3037
+ logCursor,
3038
+ logOf,
3039
+ logWindow,
1802
3040
  lookup,
1803
3041
  mergeMethodSchema,
1804
3042
  nameOf,
@@ -1813,7 +3051,9 @@ export {
1813
3051
  registerBuiltinTrackers,
1814
3052
  registerTracker,
1815
3053
  requestJson,
3054
+ requestText,
1816
3055
  requireTarget,
3056
+ sourcecraftCi,
1817
3057
  sourcecraftPulls,
1818
3058
  sourcecraftTracker,
1819
3059
  stringList,