@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.
- package/README.md +137 -1
- package/dist/{chunk-L7CPE2SO.js → chunk-AGGJK2XZ.js} +2 -1
- package/dist/{chunk-GPRMI4PG.js → chunk-ZTME4HFD.js} +2 -2
- package/dist/git/index.js +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +6 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mcp.types.d.ts +1 -0
- package/dist/tracker/ci.lib.d.ts +36 -0
- package/dist/tracker/ci.schema.d.ts +162 -0
- package/dist/tracker/ci.types.d.ts +117 -0
- package/dist/tracker/github-ci.lib.d.ts +2 -0
- package/dist/tracker/gitlab-ci.lib.d.ts +2 -0
- package/dist/tracker/http.lib.d.ts +9 -1
- package/dist/tracker/index.d.ts +6 -0
- package/dist/tracker/index.js +1306 -66
- package/dist/tracker/index.js.map +1 -1
- package/dist/tracker/mapping.const.d.ts +15 -0
- package/dist/tracker/sourcecraft-ci.lib.d.ts +2 -0
- package/dist/tracker/tracker.const.d.ts +12 -0
- package/dist/tracker/tracker.types.d.ts +34 -0
- package/dist/worktree/index.js +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-L7CPE2SO.js.map → chunk-AGGJK2XZ.js.map} +0 -0
- /package/dist/{chunk-GPRMI4PG.js.map → chunk-ZTME4HFD.js.map} +0 -0
package/dist/tracker/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
REDACTED,
|
|
2
3
|
defaultSecretSink,
|
|
3
4
|
redactSecretText
|
|
4
|
-
} from "../chunk-
|
|
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
|
|
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 =
|
|
324
|
-
var pullRequestFilterSchema =
|
|
325
|
-
var pullRequestSortFieldSchema =
|
|
326
|
-
var mergeMethodSchema =
|
|
327
|
-
var pullRequestSchema =
|
|
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:
|
|
330
|
-
title:
|
|
331
|
-
description:
|
|
332
|
-
status:
|
|
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:
|
|
1538
|
+
author: z2.string().min(1).max(200).optional(),
|
|
335
1539
|
/** Ветка-источник и целевая ветка — имя без refs/heads/. */
|
|
336
|
-
source:
|
|
337
|
-
target:
|
|
1540
|
+
source: z2.string().min(1).max(500),
|
|
1541
|
+
target: z2.string().min(1).max(500),
|
|
338
1542
|
/** Коммит головы источника. */
|
|
339
|
-
headSha:
|
|
340
|
-
labels:
|
|
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:
|
|
343
|
-
links:
|
|
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
|
|
352
|
-
var
|
|
1555
|
+
var DEFAULT_API3 = "https://api.github.com";
|
|
1556
|
+
var TARGET4 = /^[\w.-]+\/[\w.-]+$/;
|
|
353
1557
|
var NUMBER = /^[1-9]\d{0,19}$/;
|
|
354
|
-
var
|
|
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 (!
|
|
381
|
-
const api = baseUrl(connection.url,
|
|
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 =
|
|
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 =
|
|
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
|
|
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 &&
|
|
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
|
|
543
|
-
var
|
|
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 (!
|
|
571
|
-
const api = baseUrl(connection.url,
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
744
|
-
var
|
|
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
|
|
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 (!
|
|
768
|
-
const root = `${baseUrl(connection.url,
|
|
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 =
|
|
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 =
|
|
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
|
|
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 &&
|
|
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
|
|
938
|
-
var
|
|
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 (!
|
|
970
|
-
const root = `${baseUrl(connection.url,
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
1349
|
-
var
|
|
2554
|
+
var DEFAULT_API5 = "https://api.sourcecraft.tech";
|
|
2555
|
+
var TARGET8 = /^[\w.-]+\/[\w.-]+$/;
|
|
1350
2556
|
var SLUG = /^[\w-]{1,100}$/;
|
|
1351
|
-
var
|
|
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 (!
|
|
1378
|
-
const api = baseUrl(connection.url,
|
|
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 &&
|
|
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
|
|
2755
|
+
var DEFAULT_API6 = "https://api.sourcecraft.tech";
|
|
1550
2756
|
var DEFAULT_WEB = "https://sourcecraft.dev";
|
|
1551
|
-
var
|
|
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 (!
|
|
1583
|
-
const api = baseUrl(connection.url,
|
|
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,
|