@smwb/onreel-git-mcp 0.3.0 → 0.4.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 +87 -1
- package/dist/git/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +3 -3
- package/dist/tracker/github-pull.lib.d.ts +2 -0
- package/dist/tracker/gitlab-pull.lib.d.ts +2 -0
- package/dist/tracker/index.d.ts +6 -0
- package/dist/tracker/index.js +713 -57
- package/dist/tracker/index.js.map +1 -1
- package/dist/tracker/mapping.const.d.ts +9 -0
- package/dist/tracker/pull.lib.d.ts +4 -0
- package/dist/tracker/pull.schema.d.ts +63 -0
- package/dist/tracker/pull.types.d.ts +74 -0
- package/dist/tracker/sourcecraft-pull.lib.d.ts +2 -0
- package/dist/tracker/tracker.types.d.ts +3 -0
- package/dist/worktree/index.js +1 -1
- package/package.json +1 -1
package/dist/tracker/index.js
CHANGED
|
@@ -2,6 +2,10 @@ import {
|
|
|
2
2
|
defaultSecretSink,
|
|
3
3
|
redactSecretText
|
|
4
4
|
} from "../chunk-L7CPE2SO.js";
|
|
5
|
+
import {
|
|
6
|
+
issueLinkSchema,
|
|
7
|
+
timestampSchema
|
|
8
|
+
} from "../chunk-4ZGRXIVA.js";
|
|
5
9
|
|
|
6
10
|
// src/tracker/tracker.const.ts
|
|
7
11
|
var TRACKER_TIMEOUT_MS = 15e3;
|
|
@@ -211,6 +215,21 @@ var GITHUB_PRIORITY_LABELS = {
|
|
|
211
215
|
"priority: blocker": "blocker"
|
|
212
216
|
};
|
|
213
217
|
var ALL_STATUSES = ["open", "in_progress", "done", "declined"];
|
|
218
|
+
var GITLAB_MERGE_REQUEST_STATES = {
|
|
219
|
+
opened: "open",
|
|
220
|
+
locked: "open",
|
|
221
|
+
merged: "merged",
|
|
222
|
+
closed: "closed"
|
|
223
|
+
};
|
|
224
|
+
var GITLAB_DRAFT_PREFIX = "Draft: ";
|
|
225
|
+
var GITLAB_DRAFT_PATTERN = /^\s*(?:\[draft\]|\(draft\)|draft:)\s*/i;
|
|
226
|
+
var SOURCECRAFT_PULL_STATUSES = {
|
|
227
|
+
draft: "draft",
|
|
228
|
+
open: "open",
|
|
229
|
+
merging: "open",
|
|
230
|
+
merged: "merged",
|
|
231
|
+
discarded: "closed"
|
|
232
|
+
};
|
|
214
233
|
|
|
215
234
|
// src/tracker/http.lib.ts
|
|
216
235
|
async function requestJson(url, request) {
|
|
@@ -299,12 +318,232 @@ function listTrackers() {
|
|
|
299
318
|
return [...definitions.values()];
|
|
300
319
|
}
|
|
301
320
|
|
|
302
|
-
// src/tracker/
|
|
321
|
+
// src/tracker/pull.schema.ts
|
|
322
|
+
import { z } from "zod";
|
|
323
|
+
var pullRequestStatusCategorySchema = z.enum(["draft", "open", "merged", "closed"]);
|
|
324
|
+
var pullRequestFilterSchema = z.enum(["status", "author", "source", "target", "labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"]);
|
|
325
|
+
var pullRequestSortFieldSchema = z.enum(["created", "updated", "title"]);
|
|
326
|
+
var mergeMethodSchema = z.enum(["merge", "squash", "rebase"]);
|
|
327
|
+
var pullRequestSchema = z.object({
|
|
328
|
+
/** Номер PR в своём трекере: GitHub number, GitLab iid, SourceCraft slug; для клиента непрозрачная строка. */
|
|
329
|
+
key: z.string().min(1).max(200),
|
|
330
|
+
title: z.string().min(1).max(1024),
|
|
331
|
+
description: z.string().max(1048576).optional(),
|
|
332
|
+
status: z.object({ category: pullRequestStatusCategorySchema, name: z.string().min(1).max(200) }),
|
|
333
|
+
/** Автор — логин или slug трекера. */
|
|
334
|
+
author: z.string().min(1).max(200).optional(),
|
|
335
|
+
/** Ветка-источник и целевая ветка — имя без refs/heads/. */
|
|
336
|
+
source: z.string().min(1).max(500),
|
|
337
|
+
target: z.string().min(1).max(500),
|
|
338
|
+
/** Коммит головы источника. */
|
|
339
|
+
headSha: z.string().regex(/^[0-9a-f]{7,64}$/).optional(),
|
|
340
|
+
labels: z.array(z.string().min(1).max(200)).max(100),
|
|
341
|
+
/** Запрошенные ревьюеры; нет — трекер их в PR не отдаёт. */
|
|
342
|
+
reviewers: z.array(z.string().min(1).max(200)).max(100).optional(),
|
|
343
|
+
links: z.array(issueLinkSchema).max(100),
|
|
344
|
+
createdAt: timestampSchema.optional(),
|
|
345
|
+
updatedAt: timestampSchema.optional(),
|
|
346
|
+
mergedAt: timestampSchema.optional(),
|
|
347
|
+
closedAt: timestampSchema.optional()
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// src/tracker/github-pull.lib.ts
|
|
303
351
|
var DEFAULT_API = "https://api.github.com";
|
|
304
352
|
var TARGET = /^[\w.-]+\/[\w.-]+$/;
|
|
305
|
-
var NUMBER = /^[1-9]\d{0,
|
|
306
|
-
var
|
|
307
|
-
var
|
|
353
|
+
var NUMBER = /^[1-9]\d{0,19}$/;
|
|
354
|
+
var SHA = /^[0-9a-f]{7,64}$/;
|
|
355
|
+
var LIST_SUBJECT = "PR GitHub";
|
|
356
|
+
var COMMENTS_SUBJECT = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 PR GitHub";
|
|
357
|
+
var PULL_CAPABILITIES = {
|
|
358
|
+
list: {
|
|
359
|
+
filters: pullRequestFilterSchema.options,
|
|
360
|
+
clientSide: ["status", "author", "labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"],
|
|
361
|
+
sorts: ["created", "updated"]
|
|
362
|
+
},
|
|
363
|
+
comments: { filters: COMMENT_FILTERS, clientSide: [], order: ["asc"] },
|
|
364
|
+
create: true,
|
|
365
|
+
createDraft: true,
|
|
366
|
+
update: ["title", "description", "target"],
|
|
367
|
+
statuses: ["open", "closed"],
|
|
368
|
+
comment: true,
|
|
369
|
+
merge: ["merge", "squash", "rebase"],
|
|
370
|
+
deleteSourceOnMerge: false
|
|
371
|
+
};
|
|
372
|
+
function githubPulls() {
|
|
373
|
+
return {
|
|
374
|
+
capabilities: PULL_CAPABILITIES,
|
|
375
|
+
create: (context) => createGithubPullAdapter(context)
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function createGithubPullAdapter({ connection, fetch }) {
|
|
379
|
+
const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
|
|
380
|
+
if (!TARGET.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
|
|
381
|
+
const api = baseUrl(connection.url, DEFAULT_API);
|
|
382
|
+
const root = `${api}/repos/${target}/pulls`;
|
|
383
|
+
const issues = `${api}/repos/${target}/issues`;
|
|
384
|
+
const owner = target.split("/")[0] ?? target;
|
|
385
|
+
const headers = {
|
|
386
|
+
accept: "application/vnd.github+json",
|
|
387
|
+
"x-github-api-version": "2022-11-28",
|
|
388
|
+
...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
|
|
389
|
+
};
|
|
390
|
+
const missing = (key) => ({ failure: "not_found", message: `\u0412 GitHub \u043D\u0435\u0442 PR ${key}.` });
|
|
391
|
+
const number = (key) => {
|
|
392
|
+
if (!NUMBER.test(key)) throw new TrackerError("not_found", `\u0412 GitHub \u043D\u0435\u0442 PR ${key}.`);
|
|
393
|
+
return key;
|
|
394
|
+
};
|
|
395
|
+
const getPull = async (key) => pullOf((await requestJson(`${root}/${number(key)}`, { fetch, headers, missing: missing(key) })).body);
|
|
396
|
+
return {
|
|
397
|
+
list: async (query) => {
|
|
398
|
+
assertListQuery(LIST_SUBJECT, PULL_CAPABILITIES.list, query, pullRequestFilterSchema.options);
|
|
399
|
+
const status = usedStatuses(query.status);
|
|
400
|
+
const page = pageOf(query.cursor);
|
|
401
|
+
const params = new URLSearchParams({ state: stateFilter(status), per_page: String(query.limit), page: String(page) });
|
|
402
|
+
if (query.source !== void 0) params.set("head", `${owner}:${query.source}`);
|
|
403
|
+
if (query.target !== void 0) params.set("base", query.target);
|
|
404
|
+
if (query.sort !== void 0) {
|
|
405
|
+
params.set("sort", query.sort.field);
|
|
406
|
+
params.set("direction", query.sort.direction ?? "desc");
|
|
407
|
+
}
|
|
408
|
+
const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
|
|
409
|
+
const items = (Array.isArray(body) ? body : []).map(pullOf).filter((pull) => matchesPage(pull, query, status));
|
|
410
|
+
return { items, ...nextOf(responseHeaders.get("link"), page) };
|
|
411
|
+
},
|
|
412
|
+
get: (key) => getPull(key),
|
|
413
|
+
create: async (input) => pullOf((await requestJson(root, { fetch, headers, method: "POST", body: createBody(input) })).body),
|
|
414
|
+
update: async (key, patch) => {
|
|
415
|
+
if (patch.status === "draft") throw new TrackerError("unsupported", "PR GitHub: \u043F\u0435\u0440\u0435\u0432\u043E\u0434 \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0447\u0435\u0440\u0435\u0437 REST.");
|
|
416
|
+
const current = patch.status === "open" ? await getPull(key) : void 0;
|
|
417
|
+
if (current?.status.category === "draft") throw new TrackerError("unsupported", "PR GitHub: \u0441\u043D\u044F\u0442\u0438\u0435 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0447\u0435\u0440\u0435\u0437 REST.");
|
|
418
|
+
if (current !== void 0 && current.status.category === "open" && patch.title === void 0 && patch.description === void 0 && patch.target === void 0) return current;
|
|
419
|
+
return pullOf((await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody(patch), missing: missing(key) })).body);
|
|
420
|
+
},
|
|
421
|
+
merge: async (key, input) => {
|
|
422
|
+
if (input.deleteSource === true) throw new TrackerError("unsupported", "PR GitHub: \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0432\u0435\u0442\u043A\u0438-\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 \u043F\u0440\u0438 \u0441\u043B\u0438\u044F\u043D\u0438\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E.");
|
|
423
|
+
if (input.method !== void 0 && !PULL_CAPABILITIES.merge.includes(input.method)) {
|
|
424
|
+
throw new TrackerError("unsupported", `PR GitHub: \u0441\u043B\u0438\u044F\u043D\u0438\u0435 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C ${input.method} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
|
|
425
|
+
}
|
|
426
|
+
await requestJson(`${root}/${number(key)}/merge`, { fetch, headers, method: "PUT", body: mergeBody(input), conflictOn: [405, 409], missing: missing(key) });
|
|
427
|
+
return getPull(key);
|
|
428
|
+
},
|
|
429
|
+
comments: async (key, query) => {
|
|
430
|
+
assertCommentQuery(COMMENTS_SUBJECT, PULL_CAPABILITIES.comments, query);
|
|
431
|
+
const page = pageOf(query.cursor);
|
|
432
|
+
const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
|
|
433
|
+
if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
|
|
434
|
+
const { body, headers: responseHeaders } = await requestJson(`${issues}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missing: missing(key) });
|
|
435
|
+
return { items: (Array.isArray(body) ? body : []).map(commentOf), ...nextOf(responseHeaders.get("link"), page) };
|
|
436
|
+
},
|
|
437
|
+
comment: async (key, body) => commentOf((await requestJson(`${issues}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function pageOf(cursor) {
|
|
441
|
+
const page = cursor === void 0 ? 1 : Number(cursor);
|
|
442
|
+
return Number.isInteger(page) && page >= 1 ? page : 1;
|
|
443
|
+
}
|
|
444
|
+
function nextOf(link, page) {
|
|
445
|
+
return link !== null && /rel="next"/.test(link) ? { nextCursor: String(page + 1) } : {};
|
|
446
|
+
}
|
|
447
|
+
function stateFilter(status) {
|
|
448
|
+
if (status === void 0) return "all";
|
|
449
|
+
if (status.every((item) => item === "draft" || item === "open")) return "open";
|
|
450
|
+
if (status.every((item) => item === "merged" || item === "closed")) return "closed";
|
|
451
|
+
return "all";
|
|
452
|
+
}
|
|
453
|
+
function matchesPage(pull, query, status) {
|
|
454
|
+
if (status !== void 0 && !status.includes(pull.status.category)) return false;
|
|
455
|
+
if (query.author !== void 0 && pull.author !== query.author) return false;
|
|
456
|
+
if (query.labels !== void 0 && !query.labels.every((label) => pull.labels.includes(label))) return false;
|
|
457
|
+
if (query.search !== void 0 && !containsText([pull.title, pull.description], query.search)) return false;
|
|
458
|
+
return inTimeRange(pull.createdAt, query.createdAfter, query.createdBefore) && inTimeRange(pull.updatedAt, query.updatedAfter, query.updatedBefore);
|
|
459
|
+
}
|
|
460
|
+
function usedStatuses(status) {
|
|
461
|
+
if (status === void 0 || status.length === 0) return void 0;
|
|
462
|
+
for (const item of status) {
|
|
463
|
+
if (!pullRequestStatusCategorySchema.options.includes(item)) {
|
|
464
|
+
throw new TrackerError("invalid_request", `PR GitHub: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u0430 ${String(item)}.`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return status;
|
|
468
|
+
}
|
|
469
|
+
function createBody(input) {
|
|
470
|
+
return {
|
|
471
|
+
title: input.title,
|
|
472
|
+
head: input.source,
|
|
473
|
+
base: input.target,
|
|
474
|
+
...input.description === void 0 ? {} : { body: input.description },
|
|
475
|
+
...input.draft === true ? { draft: true } : {}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function patchBody(patch) {
|
|
479
|
+
return {
|
|
480
|
+
...patch.title === void 0 ? {} : { title: patch.title },
|
|
481
|
+
...patch.description === void 0 ? {} : { body: patch.description },
|
|
482
|
+
...patch.target === void 0 ? {} : { base: patch.target },
|
|
483
|
+
...patch.status === void 0 ? {} : { state: patch.status === "closed" ? "closed" : "open" }
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function mergeBody(input) {
|
|
487
|
+
const lines = input.message?.split("\n") ?? [];
|
|
488
|
+
return {
|
|
489
|
+
...input.method === void 0 ? {} : { merge_method: input.method },
|
|
490
|
+
...input.headSha === void 0 ? {} : { sha: input.headSha },
|
|
491
|
+
...input.message === void 0 ? {} : { commit_title: lines[0] ?? "" },
|
|
492
|
+
...lines.length > 1 ? { commit_message: lines.slice(1).join("\n") } : {}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function pullOf(raw) {
|
|
496
|
+
const item = isRecord(raw) ? raw : {};
|
|
497
|
+
const head = isRecord(item.head) ? item.head : {};
|
|
498
|
+
const base = isRecord(item.base) ? item.base : {};
|
|
499
|
+
const key = typeof item.number === "number" ? String(item.number) : "?";
|
|
500
|
+
const description = text(item.body);
|
|
501
|
+
const author = isRecord(item.user) ? text(item.user.login) : void 0;
|
|
502
|
+
const sha = text(head.sha);
|
|
503
|
+
const url = text(item.html_url);
|
|
504
|
+
const createdAt = toTimestamp(item.created_at);
|
|
505
|
+
const updatedAt = toTimestamp(item.updated_at);
|
|
506
|
+
const mergedAt = toTimestamp(item.merged_at);
|
|
507
|
+
const closedAt = toTimestamp(item.closed_at);
|
|
508
|
+
const closed = item.state === "closed";
|
|
509
|
+
const merged = item.merged_at !== void 0 && item.merged_at !== null;
|
|
510
|
+
const category = closed ? merged ? "merged" : "closed" : item.draft === true ? "draft" : "open";
|
|
511
|
+
return {
|
|
512
|
+
key,
|
|
513
|
+
title: text(item.title) ?? key,
|
|
514
|
+
...description === void 0 ? {} : { description },
|
|
515
|
+
status: { category, name: category },
|
|
516
|
+
...author === void 0 ? {} : { author },
|
|
517
|
+
source: text(head.ref) ?? "?",
|
|
518
|
+
target: text(base.ref) ?? "?",
|
|
519
|
+
...sha !== void 0 && SHA.test(sha) ? { headSha: sha } : {},
|
|
520
|
+
labels: Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : label)) : [],
|
|
521
|
+
reviewers: logins(item.requested_reviewers),
|
|
522
|
+
links: url === void 0 ? [] : [{ url, title: "GitHub" }],
|
|
523
|
+
...createdAt === void 0 ? {} : { createdAt },
|
|
524
|
+
...updatedAt === void 0 ? {} : { updatedAt },
|
|
525
|
+
...mergedAt === void 0 ? {} : { mergedAt },
|
|
526
|
+
...closedAt === void 0 ? {} : { closedAt }
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function logins(value) {
|
|
530
|
+
if (!Array.isArray(value)) return [];
|
|
531
|
+
const names = value.map((item) => isRecord(item) ? text(item.login) : void 0);
|
|
532
|
+
return [...new Set(names.filter((name) => name !== void 0))];
|
|
533
|
+
}
|
|
534
|
+
function commentOf(raw) {
|
|
535
|
+
const item = isRecord(raw) ? raw : {};
|
|
536
|
+
const author = isRecord(item.user) ? text(item.user.login) : void 0;
|
|
537
|
+
const createdAt = toTimestamp(item.created_at);
|
|
538
|
+
return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/tracker/github.lib.ts
|
|
542
|
+
var DEFAULT_API2 = "https://api.github.com";
|
|
543
|
+
var TARGET2 = /^[\w.-]+\/[\w.-]+$/;
|
|
544
|
+
var NUMBER2 = /^[1-9]\d{0,14}$/;
|
|
545
|
+
var LIST_SUBJECT2 = "\u0417\u0430\u0434\u0430\u0447\u0438 GitHub";
|
|
546
|
+
var COMMENTS_SUBJECT2 = "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitHub";
|
|
308
547
|
function githubTracker() {
|
|
309
548
|
const issues = {
|
|
310
549
|
list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "comments"] },
|
|
@@ -322,13 +561,14 @@ function githubTracker() {
|
|
|
322
561
|
features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: ["open", "in_progress", "done", "declined"] }
|
|
323
562
|
},
|
|
324
563
|
create: (context) => createGithubAdapter(context, issues),
|
|
325
|
-
issues
|
|
564
|
+
issues,
|
|
565
|
+
pulls: githubPulls()
|
|
326
566
|
};
|
|
327
567
|
}
|
|
328
568
|
function createGithubAdapter({ connection, fetch }, capabilities) {
|
|
329
569
|
const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
|
|
330
|
-
if (!
|
|
331
|
-
const api = baseUrl(connection.url,
|
|
570
|
+
if (!TARGET2.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
|
|
571
|
+
const api = baseUrl(connection.url, DEFAULT_API2);
|
|
332
572
|
const root = `${api}/repos/${target}/issues`;
|
|
333
573
|
const headers = {
|
|
334
574
|
accept: "application/vnd.github+json",
|
|
@@ -336,7 +576,7 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
|
|
|
336
576
|
...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
|
|
337
577
|
};
|
|
338
578
|
const number = (key) => {
|
|
339
|
-
if (!
|
|
579
|
+
if (!NUMBER2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitHub \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
|
|
340
580
|
return key;
|
|
341
581
|
};
|
|
342
582
|
const rawOf = async (key) => {
|
|
@@ -345,9 +585,9 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
|
|
|
345
585
|
};
|
|
346
586
|
return {
|
|
347
587
|
list: async (query) => {
|
|
348
|
-
assertListQuery(
|
|
588
|
+
assertListQuery(LIST_SUBJECT2, capabilities.list, query, ISSUE_FILTERS);
|
|
349
589
|
assertCsvLabels(query.labels);
|
|
350
|
-
const page =
|
|
590
|
+
const page = pageOf2(query.cursor);
|
|
351
591
|
const search = needsSearch(query);
|
|
352
592
|
const params = search ? searchParams(target, query, page) : issuesParams(query, page);
|
|
353
593
|
const { body, headers: responseHeaders } = await requestJson(`${search ? `${api}/search/issues` : root}?${params.toString()}`, { fetch, headers });
|
|
@@ -366,25 +606,25 @@ function createGithubAdapter({ connection, fetch }, capabilities) {
|
|
|
366
606
|
},
|
|
367
607
|
update: async (key, patch) => {
|
|
368
608
|
const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : issueOf(await rawOf(key));
|
|
369
|
-
const response = await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body:
|
|
609
|
+
const response = await requestJson(`${root}/${number(key)}`, { fetch, headers, method: "PATCH", body: patchBody2(patch, current), missingIsIssue: true });
|
|
370
610
|
return issueOf(response.body);
|
|
371
611
|
},
|
|
372
612
|
comments: async (key, query) => {
|
|
373
|
-
assertCommentQuery(
|
|
374
|
-
const page =
|
|
613
|
+
assertCommentQuery(COMMENTS_SUBJECT2, capabilities.comments, query);
|
|
614
|
+
const page = pageOf2(query.cursor);
|
|
375
615
|
const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
|
|
376
616
|
if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
|
|
377
617
|
const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
|
|
378
|
-
return { items: (Array.isArray(body) ? body : []).map(
|
|
618
|
+
return { items: (Array.isArray(body) ? body : []).map(commentOf2), .../rel="next"/.test(responseHeaders.get("link") ?? "") ? { nextCursor: String(page + 1) } : {} };
|
|
379
619
|
},
|
|
380
|
-
comment: async (key, body) =>
|
|
620
|
+
comment: async (key, body) => commentOf2((await requestJson(`${root}/${number(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
|
|
381
621
|
};
|
|
382
622
|
}
|
|
383
|
-
function
|
|
623
|
+
function pageOf2(cursor) {
|
|
384
624
|
const page = cursor === void 0 ? 1 : Number(cursor);
|
|
385
625
|
return Number.isInteger(page) && page >= 1 ? page : 1;
|
|
386
626
|
}
|
|
387
|
-
function
|
|
627
|
+
function stateFilter2(status) {
|
|
388
628
|
if (status === void 0) return "all";
|
|
389
629
|
if (status.every((item) => item === "open" || item === "in_progress")) return "open";
|
|
390
630
|
if (status.every((item) => item === "done" || item === "declined")) return "closed";
|
|
@@ -394,10 +634,10 @@ function needsSearch(query) {
|
|
|
394
634
|
return query.search !== void 0 || query.createdAfter !== void 0 || query.createdBefore !== void 0 || query.updatedBefore !== void 0;
|
|
395
635
|
}
|
|
396
636
|
function assertCsvLabels(labels) {
|
|
397
|
-
if (labels?.some((label) => label.includes(",")) === true) throw new TrackerError("invalid_request", `${
|
|
637
|
+
if (labels?.some((label) => label.includes(",")) === true) throw new TrackerError("invalid_request", `${LIST_SUBJECT2}: \u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u0430 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0437\u0430\u043F\u044F\u0442\u0443\u044E.`);
|
|
398
638
|
}
|
|
399
639
|
function issuesParams(query, page) {
|
|
400
|
-
const params = new URLSearchParams({ state:
|
|
640
|
+
const params = new URLSearchParams({ state: stateFilter2(query.status), per_page: String(query.limit), page: String(page) });
|
|
401
641
|
if (query.labels !== void 0 && query.labels.length > 0) params.set("labels", query.labels.join(","));
|
|
402
642
|
if (query.assignee !== void 0) params.set("assignee", query.assignee ?? "none");
|
|
403
643
|
if (query.author !== void 0) params.set("creator", query.author);
|
|
@@ -456,7 +696,7 @@ function composeLabels(plain, priority, status) {
|
|
|
456
696
|
const priorityLabel = priority === void 0 ? void 0 : priorityName(GITHUB_PRIORITY_LABELS, priority);
|
|
457
697
|
return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITHUB_IN_PROGRESS_LABEL] : []];
|
|
458
698
|
}
|
|
459
|
-
function
|
|
699
|
+
function patchBody2(patch, current) {
|
|
460
700
|
const status = patch.status ?? current?.status.category;
|
|
461
701
|
const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
|
|
462
702
|
return {
|
|
@@ -492,22 +732,216 @@ function issueOf(raw) {
|
|
|
492
732
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
493
733
|
};
|
|
494
734
|
}
|
|
495
|
-
function
|
|
735
|
+
function commentOf2(raw) {
|
|
496
736
|
const item = isRecord(raw) ? raw : {};
|
|
497
737
|
const author = isRecord(item.user) ? text(item.user.login) : void 0;
|
|
498
738
|
const createdAt = toTimestamp(item.created_at);
|
|
499
739
|
return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
|
|
500
740
|
}
|
|
501
741
|
|
|
502
|
-
// src/tracker/gitlab.lib.ts
|
|
742
|
+
// src/tracker/gitlab-pull.lib.ts
|
|
503
743
|
var DEFAULT_URL = "https://gitlab.com";
|
|
504
|
-
var
|
|
505
|
-
var IID = /^[1-9]\d{0,
|
|
744
|
+
var TARGET3 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
|
|
745
|
+
var IID = /^[1-9]\d{0,19}$/;
|
|
746
|
+
var SHA2 = /^[0-9a-f]{7,64}$/;
|
|
747
|
+
var PULL_CAPABILITIES2 = {
|
|
748
|
+
list: { filters: pullRequestFilterSchema.options, clientSide: ["status"], sorts: ["created", "updated", "title"] },
|
|
749
|
+
comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] },
|
|
750
|
+
create: true,
|
|
751
|
+
createDraft: true,
|
|
752
|
+
update: ["title", "description", "target"],
|
|
753
|
+
statuses: ["draft", "open", "closed"],
|
|
754
|
+
comment: true,
|
|
755
|
+
merge: ["merge", "squash"],
|
|
756
|
+
deleteSourceOnMerge: true
|
|
757
|
+
};
|
|
758
|
+
var ORDER_BY = { created: "created_at", updated: "updated_at", title: "title" };
|
|
759
|
+
function gitlabPulls() {
|
|
760
|
+
return {
|
|
761
|
+
capabilities: PULL_CAPABILITIES2,
|
|
762
|
+
create: (context) => createGitlabPullAdapter(context)
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function createGitlabPullAdapter({ connection, fetch }) {
|
|
766
|
+
const target = requireTarget(connection.target, "\u043F\u0443\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442\u0430 GitLab, group/project, \u0438\u043B\u0438 \u0435\u0433\u043E id");
|
|
767
|
+
if (!TARGET3.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
|
|
768
|
+
const root = `${baseUrl(connection.url, DEFAULT_URL)}/api/v4/projects/${encodeURIComponent(target)}/merge_requests`;
|
|
769
|
+
const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
|
|
770
|
+
const iid = (key) => {
|
|
771
|
+
if (!IID.test(key)) throw new TrackerError("not_found", `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.`);
|
|
772
|
+
return key;
|
|
773
|
+
};
|
|
774
|
+
const currentOf = async (key) => pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
|
|
775
|
+
return {
|
|
776
|
+
list: async (query) => {
|
|
777
|
+
assertListQuery("PR GitLab", PULL_CAPABILITIES2.list, query, pullRequestFilterSchema.options);
|
|
778
|
+
const status = usedStatuses2(query.status);
|
|
779
|
+
const page = pageOf3(query.cursor);
|
|
780
|
+
const params = new URLSearchParams({
|
|
781
|
+
state: stateFilter3(status),
|
|
782
|
+
per_page: String(query.limit),
|
|
783
|
+
page: String(page),
|
|
784
|
+
order_by: query.sort === void 0 ? "created_at" : ORDER_BY[query.sort.field] ?? "created_at",
|
|
785
|
+
sort: query.sort?.direction ?? "desc"
|
|
786
|
+
});
|
|
787
|
+
if (query.labels !== void 0 && query.labels.length > 0) {
|
|
788
|
+
if (query.labels.some((label) => label.includes(","))) throw new TrackerError("invalid_request", "PR GitLab: \u043C\u0435\u0442\u043A\u0430 \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u0430 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0437\u0430\u043F\u044F\u0442\u0443\u044E.");
|
|
789
|
+
params.set("labels", query.labels.join(","));
|
|
790
|
+
}
|
|
791
|
+
if (query.author !== void 0) params.set("author_username", query.author);
|
|
792
|
+
if (query.source !== void 0) params.set("source_branch", query.source);
|
|
793
|
+
if (query.target !== void 0) params.set("target_branch", query.target);
|
|
794
|
+
if (query.search !== void 0) params.set("search", query.search);
|
|
795
|
+
if (query.createdAfter !== void 0) params.set("created_after", isoTime(query.createdAfter));
|
|
796
|
+
if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
|
|
797
|
+
if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
|
|
798
|
+
if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
|
|
799
|
+
const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
|
|
800
|
+
const next = responseHeaders.get("x-next-page");
|
|
801
|
+
const items = (Array.isArray(body) ? body : []).map(pullOf2);
|
|
802
|
+
return {
|
|
803
|
+
items: status === void 0 ? items : items.filter((pr) => status.includes(pr.status.category) === true),
|
|
804
|
+
...next === null || next === "" ? {} : { nextCursor: next }
|
|
805
|
+
};
|
|
806
|
+
},
|
|
807
|
+
get: currentOf,
|
|
808
|
+
create: async (input) => {
|
|
809
|
+
const body = {
|
|
810
|
+
source_branch: input.source,
|
|
811
|
+
target_branch: input.target,
|
|
812
|
+
title: (input.draft === true ? GITLAB_DRAFT_PREFIX : "") + input.title,
|
|
813
|
+
...input.description === void 0 ? {} : { description: input.description }
|
|
814
|
+
};
|
|
815
|
+
return pullOf2((await requestJson(root, { fetch, headers, method: "POST", body, conflictOn: [409] })).body);
|
|
816
|
+
},
|
|
817
|
+
update: async (key, patch) => {
|
|
818
|
+
const current = patch.status === void 0 ? void 0 : await currentOf(key);
|
|
819
|
+
if (patch.status === "draft" && current?.status.category === "closed") {
|
|
820
|
+
throw new TrackerError("unsupported", "PR GitLab: \u043F\u0435\u0440\u0435\u0432\u043E\u0434 \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u0433\u043E MR \u0432 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D.");
|
|
821
|
+
}
|
|
822
|
+
const body = patchBody3(patch, current);
|
|
823
|
+
if (Object.keys(body).length === 0) return current ?? pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
|
|
824
|
+
return pullOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body, missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
|
|
825
|
+
},
|
|
826
|
+
merge: async (key, input) => {
|
|
827
|
+
if (input.method !== void 0 && !PULL_CAPABILITIES2.merge.includes(input.method)) {
|
|
828
|
+
throw new TrackerError("unsupported", `PR GitLab: \u0441\u043B\u0438\u044F\u043D\u0438\u0435 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C ${input.method} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
|
|
829
|
+
}
|
|
830
|
+
const body = {
|
|
831
|
+
// squash: true ставит squash; false GitLab игнорирует — при method 'merge' действует настройка MR/проекта.
|
|
832
|
+
...input.method === "squash" ? { squash: true } : {},
|
|
833
|
+
...input.message === void 0 ? {} : input.method === "squash" ? { squash_commit_message: input.message } : { merge_commit_message: input.message },
|
|
834
|
+
...input.deleteSource === void 0 ? {} : { should_remove_source_branch: input.deleteSource },
|
|
835
|
+
...input.headSha === void 0 ? {} : { sha: input.headSha }
|
|
836
|
+
};
|
|
837
|
+
return pullOf2((await requestJson(`${root}/${iid(key)}/merge`, { fetch, headers, method: "PUT", body, conflictOn: [405, 409, 422], missing: { failure: "not_found", message: `\u0412 GitLab \u043D\u0435\u0442 MR ${key}.` } })).body);
|
|
838
|
+
},
|
|
839
|
+
comments: async (key, query) => {
|
|
840
|
+
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", PULL_CAPABILITIES2.comments, query);
|
|
841
|
+
const page = pageOf3(query.cursor);
|
|
842
|
+
const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
|
|
843
|
+
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
|
+
const next = responseHeaders.get("x-next-page");
|
|
845
|
+
const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(commentOf3);
|
|
846
|
+
return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
|
|
847
|
+
},
|
|
848
|
+
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
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function pageOf3(cursor) {
|
|
852
|
+
const page = cursor === void 0 ? 1 : Number(cursor);
|
|
853
|
+
return Number.isInteger(page) && page >= 1 ? page : 1;
|
|
854
|
+
}
|
|
855
|
+
function stateFilter3(status) {
|
|
856
|
+
if (status === void 0) return "all";
|
|
857
|
+
if (status.every((item) => item === "draft" || item === "open")) return "opened";
|
|
858
|
+
if (status.every((item) => item === "merged")) return "merged";
|
|
859
|
+
if (status.every((item) => item === "closed")) return "closed";
|
|
860
|
+
return "all";
|
|
861
|
+
}
|
|
862
|
+
function patchBody3(patch, current) {
|
|
863
|
+
const body = {};
|
|
864
|
+
if (patch.description !== void 0) body.description = patch.description;
|
|
865
|
+
if (patch.target !== void 0) body.target_branch = patch.target;
|
|
866
|
+
if (patch.status === "closed" && current?.status.category !== "closed") body.state_event = "close";
|
|
867
|
+
if (patch.status === "open" && current?.status.category === "closed") body.state_event = "reopen";
|
|
868
|
+
if (patch.status === "draft" || patch.status === "open") {
|
|
869
|
+
const title = patch.title ?? current?.title ?? "";
|
|
870
|
+
const base = title.replace(GITLAB_DRAFT_PATTERN, "");
|
|
871
|
+
const next = patch.status === "draft" ? GITLAB_DRAFT_PREFIX + base : current?.status.category === "draft" || title !== base ? base : patch.title;
|
|
872
|
+
if (next !== void 0 && next !== current?.title) body.title = next;
|
|
873
|
+
} else if (patch.title !== void 0 && patch.title !== current?.title) {
|
|
874
|
+
body.title = patch.title;
|
|
875
|
+
}
|
|
876
|
+
return body;
|
|
877
|
+
}
|
|
878
|
+
function usedStatuses2(status) {
|
|
879
|
+
if (status === void 0 || status.length === 0) return void 0;
|
|
880
|
+
for (const item of status) {
|
|
881
|
+
if (!pullRequestStatusCategorySchema.options.includes(item)) {
|
|
882
|
+
throw new TrackerError("invalid_request", `PR GitLab: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u0430 ${String(item)}.`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return status;
|
|
886
|
+
}
|
|
887
|
+
function labelNames(value) {
|
|
888
|
+
if (!Array.isArray(value)) return [];
|
|
889
|
+
const names = value.map((item) => typeof item === "string" ? item : isRecord(item) ? text(item.name) : void 0);
|
|
890
|
+
return [...new Set(names.filter((name) => name !== void 0 && name !== ""))];
|
|
891
|
+
}
|
|
892
|
+
function usernames(value) {
|
|
893
|
+
if (!Array.isArray(value)) return [];
|
|
894
|
+
const names = value.map((item) => isRecord(item) ? text(item.username) : void 0);
|
|
895
|
+
return [...new Set(names.filter((name) => name !== void 0 && name !== ""))];
|
|
896
|
+
}
|
|
897
|
+
function pullOf2(raw) {
|
|
898
|
+
const item = isRecord(raw) ? raw : {};
|
|
899
|
+
const state = text(item.state) ?? "opened";
|
|
900
|
+
const draft = item.draft === true;
|
|
901
|
+
const category = lookup(GITLAB_MERGE_REQUEST_STATES, state, "open");
|
|
902
|
+
const key = typeof item.iid === "number" ? String(item.iid) : "?";
|
|
903
|
+
const description = text(item.description);
|
|
904
|
+
const author = isRecord(item.author) ? text(item.author.username) : void 0;
|
|
905
|
+
const sha = text(item.sha);
|
|
906
|
+
const url = text(item.web_url);
|
|
907
|
+
const createdAt = toTimestamp(item.created_at);
|
|
908
|
+
const updatedAt = toTimestamp(item.updated_at);
|
|
909
|
+
const mergedAt = toTimestamp(item.merged_at);
|
|
910
|
+
const closedAt = toTimestamp(item.closed_at);
|
|
911
|
+
return {
|
|
912
|
+
key,
|
|
913
|
+
title: text(item.title) ?? key,
|
|
914
|
+
...description === void 0 ? {} : { description },
|
|
915
|
+
status: { category: draft && category === "open" ? "draft" : category, name: draft && category === "open" ? "draft" : state },
|
|
916
|
+
...author === void 0 ? {} : { author },
|
|
917
|
+
source: text(item.source_branch) ?? "?",
|
|
918
|
+
target: text(item.target_branch) ?? "?",
|
|
919
|
+
...sha !== void 0 && SHA2.test(sha) ? { headSha: sha } : {},
|
|
920
|
+
labels: labelNames(item.labels),
|
|
921
|
+
reviewers: usernames(item.reviewers),
|
|
922
|
+
links: url === void 0 ? [] : [{ url, title: "GitLab" }],
|
|
923
|
+
...createdAt === void 0 ? {} : { createdAt },
|
|
924
|
+
...updatedAt === void 0 ? {} : { updatedAt },
|
|
925
|
+
...mergedAt === void 0 ? {} : { mergedAt },
|
|
926
|
+
...closedAt === void 0 ? {} : { closedAt }
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function commentOf3(raw) {
|
|
930
|
+
const item = isRecord(raw) ? raw : {};
|
|
931
|
+
const author = isRecord(item.author) ? text(item.author.username) : void 0;
|
|
932
|
+
const createdAt = toTimestamp(item.created_at);
|
|
933
|
+
return { id: typeof item.id === "number" ? String(item.id) : "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/tracker/gitlab.lib.ts
|
|
937
|
+
var DEFAULT_URL2 = "https://gitlab.com";
|
|
938
|
+
var TARGET4 = /^(\d{1,15}|[\w.-]+(\/[\w.-]+)+)$/;
|
|
939
|
+
var IID2 = /^[1-9]\d{0,14}$/;
|
|
506
940
|
var ISSUE_CAPABILITIES = {
|
|
507
941
|
list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "priority", "title"] },
|
|
508
942
|
comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] }
|
|
509
943
|
};
|
|
510
|
-
var
|
|
944
|
+
var ORDER_BY2 = {
|
|
511
945
|
created: "created_at",
|
|
512
946
|
updated: "updated_at",
|
|
513
947
|
priority: "priority",
|
|
@@ -526,28 +960,29 @@ function gitlabTracker() {
|
|
|
526
960
|
features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
|
|
527
961
|
},
|
|
528
962
|
create: (context) => createGitlabAdapter(context),
|
|
529
|
-
issues: ISSUE_CAPABILITIES
|
|
963
|
+
issues: ISSUE_CAPABILITIES,
|
|
964
|
+
pulls: gitlabPulls()
|
|
530
965
|
};
|
|
531
966
|
}
|
|
532
967
|
function createGitlabAdapter({ connection, fetch }) {
|
|
533
968
|
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");
|
|
534
|
-
if (!
|
|
535
|
-
const root = `${baseUrl(connection.url,
|
|
969
|
+
if (!TARGET4.test(target)) throw new TrackerError("invalid_request", "target GitLab \u2014 group/project \u0438\u043B\u0438 \u0447\u0438\u0441\u043B\u043E\u0432\u043E\u0439 id.");
|
|
970
|
+
const root = `${baseUrl(connection.url, DEFAULT_URL2)}/api/v4/projects/${encodeURIComponent(target)}/issues`;
|
|
536
971
|
const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
|
|
537
972
|
const iid = (key) => {
|
|
538
|
-
if (!
|
|
973
|
+
if (!IID2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
|
|
539
974
|
return key;
|
|
540
975
|
};
|
|
541
976
|
const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missingIsIssue: true })).body);
|
|
542
977
|
return {
|
|
543
978
|
list: async (query) => {
|
|
544
979
|
assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 GitLab", ISSUE_CAPABILITIES.list, query, ISSUE_FILTERS);
|
|
545
|
-
const page =
|
|
980
|
+
const page = pageOf4(query.cursor);
|
|
546
981
|
const params = new URLSearchParams({
|
|
547
|
-
state:
|
|
982
|
+
state: stateFilter4(query.status),
|
|
548
983
|
per_page: String(query.limit),
|
|
549
984
|
page: String(page),
|
|
550
|
-
order_by: query.sort === void 0 ? "created_at" :
|
|
985
|
+
order_by: query.sort === void 0 ? "created_at" : ORDER_BY2[query.sort.field] ?? "created_at",
|
|
551
986
|
sort: query.sort?.direction ?? "desc"
|
|
552
987
|
});
|
|
553
988
|
if (query.labels !== void 0 && query.labels.length > 0) {
|
|
@@ -577,25 +1012,25 @@ function createGitlabAdapter({ connection, fetch }) {
|
|
|
577
1012
|
},
|
|
578
1013
|
update: async (key, patch) => {
|
|
579
1014
|
const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : await currentOf(key);
|
|
580
|
-
return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body:
|
|
1015
|
+
return issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, method: "PUT", body: patchBody4(patch, current), missingIsIssue: true })).body);
|
|
581
1016
|
},
|
|
582
1017
|
comments: async (key, query) => {
|
|
583
1018
|
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", ISSUE_CAPABILITIES.comments, query);
|
|
584
|
-
const page =
|
|
1019
|
+
const page = pageOf4(query.cursor);
|
|
585
1020
|
const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
|
|
586
1021
|
const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missingIsIssue: true });
|
|
587
1022
|
const next = responseHeaders.get("x-next-page");
|
|
588
|
-
const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(
|
|
1023
|
+
const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true && inTimeRange(toTimestamp(note.updated_at), query.updatedAfter, void 0)).map(commentOf4);
|
|
589
1024
|
return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
|
|
590
1025
|
},
|
|
591
|
-
comment: async (key, body) =>
|
|
1026
|
+
comment: async (key, body) => commentOf4((await requestJson(`${root}/${iid(key)}/notes`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
|
|
592
1027
|
};
|
|
593
1028
|
}
|
|
594
|
-
function
|
|
1029
|
+
function pageOf4(cursor) {
|
|
595
1030
|
const page = cursor === void 0 ? 1 : Number(cursor);
|
|
596
1031
|
return Number.isInteger(page) && page >= 1 ? page : 1;
|
|
597
1032
|
}
|
|
598
|
-
function
|
|
1033
|
+
function stateFilter4(status) {
|
|
599
1034
|
if (status === void 0) return "all";
|
|
600
1035
|
if (status.every((item) => item === "open" || item === "in_progress")) return "opened";
|
|
601
1036
|
if (status.every((item) => item === "done" || item === "declined")) return "closed";
|
|
@@ -605,7 +1040,7 @@ function composeLabels2(plain, priority, status) {
|
|
|
605
1040
|
const priorityLabel = priority === void 0 ? void 0 : priorityName(GITLAB_PRIORITY_LABELS, priority);
|
|
606
1041
|
return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITLAB_IN_PROGRESS_LABEL] : []];
|
|
607
1042
|
}
|
|
608
|
-
function
|
|
1043
|
+
function patchBody4(patch, current) {
|
|
609
1044
|
const status = patch.status ?? current?.status.category;
|
|
610
1045
|
const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
|
|
611
1046
|
if (patch.status === "declined") throw new TrackerError("invalid_request", "\u0412 GitLab \u043D\u0435\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \xAB\u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0430\xBB.");
|
|
@@ -643,7 +1078,7 @@ function issueOf2(raw) {
|
|
|
643
1078
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
644
1079
|
};
|
|
645
1080
|
}
|
|
646
|
-
function
|
|
1081
|
+
function commentOf4(raw) {
|
|
647
1082
|
const item = isRecord(raw) ? raw : {};
|
|
648
1083
|
const author = isRecord(item.author) ? text(item.author.username) : void 0;
|
|
649
1084
|
const createdAt = toTimestamp(item.created_at);
|
|
@@ -748,9 +1183,9 @@ function createJiraAdapter({ connection, fetch }) {
|
|
|
748
1183
|
const items = raw.filter((item) => inTimeRange(toTimestamp(isRecord(item) ? item.updated : void 0), query.updatedAfter, void 0));
|
|
749
1184
|
const total = typeof record.total === "number" ? record.total : 0;
|
|
750
1185
|
const at = typeof record.startAt === "number" ? record.startAt : 0;
|
|
751
|
-
return { items: items.map(
|
|
1186
|
+
return { items: items.map(commentOf5), ...at + raw.length < total && raw.length > 0 ? { nextCursor: String(at + raw.length) } : {} };
|
|
752
1187
|
},
|
|
753
|
-
comment: async (value, body) =>
|
|
1188
|
+
comment: async (value, body) => commentOf5((await requestJson(`${api}/issue/${key(value)}/comment`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
|
|
754
1189
|
};
|
|
755
1190
|
}
|
|
756
1191
|
function listJql(project, query) {
|
|
@@ -822,7 +1257,7 @@ function issueOf3(raw, site) {
|
|
|
822
1257
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
823
1258
|
};
|
|
824
1259
|
}
|
|
825
|
-
function
|
|
1260
|
+
function commentOf5(raw) {
|
|
826
1261
|
const item = isRecord(raw) ? raw : {};
|
|
827
1262
|
const author = isRecord(item.author) ? text(item.author.displayName) : void 0;
|
|
828
1263
|
const createdAt = toTimestamp(item.created);
|
|
@@ -909,11 +1344,212 @@ function toComment(row) {
|
|
|
909
1344
|
return { id: String(row.id), ...row.author === void 0 ? {} : { author: row.author }, body: row.body, createdAt: row.createdAt };
|
|
910
1345
|
}
|
|
911
1346
|
|
|
1347
|
+
// src/tracker/sourcecraft-pull.lib.ts
|
|
1348
|
+
var DEFAULT_API3 = "https://api.sourcecraft.tech";
|
|
1349
|
+
var TARGET5 = /^[\w.-]+\/[\w.-]+$/;
|
|
1350
|
+
var SLUG = /^[\w-]{1,100}$/;
|
|
1351
|
+
var SHA3 = /^[0-9a-f]{7,64}$/;
|
|
1352
|
+
var PULL_SORT_FIELDS_UPSTREAM = {
|
|
1353
|
+
created: "created_at",
|
|
1354
|
+
updated: "updated_at",
|
|
1355
|
+
title: "title"
|
|
1356
|
+
};
|
|
1357
|
+
var PULL_CAPABILITIES3 = {
|
|
1358
|
+
list: {
|
|
1359
|
+
filters: pullRequestFilterSchema.options,
|
|
1360
|
+
clientSide: ["labels", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"],
|
|
1361
|
+
sorts: ["created", "updated", "title"]
|
|
1362
|
+
},
|
|
1363
|
+
comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] },
|
|
1364
|
+
create: true,
|
|
1365
|
+
createDraft: true,
|
|
1366
|
+
update: ["title", "description", "target"],
|
|
1367
|
+
statuses: ["draft", "open", "closed"],
|
|
1368
|
+
comment: true,
|
|
1369
|
+
merge: ["merge", "squash", "rebase"],
|
|
1370
|
+
deleteSourceOnMerge: true
|
|
1371
|
+
};
|
|
1372
|
+
function sourcecraftPulls() {
|
|
1373
|
+
return { capabilities: PULL_CAPABILITIES3, create: (context) => createPullAdapter(context) };
|
|
1374
|
+
}
|
|
1375
|
+
function createPullAdapter({ connection, fetch }) {
|
|
1376
|
+
const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
|
|
1377
|
+
if (!TARGET5.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
|
|
1378
|
+
const api = baseUrl(connection.url, DEFAULT_API3);
|
|
1379
|
+
const root = `${api}/repos/${target}/pulls`;
|
|
1380
|
+
const headers = { authorization: `Bearer ${connection.token ?? ""}` };
|
|
1381
|
+
const missing = (key) => ({ failure: "not_found", message: `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.` });
|
|
1382
|
+
const slug = (key) => {
|
|
1383
|
+
if (!SLUG.test(key)) throw new TrackerError("not_found", `\u0412 SourceCraft \u043D\u0435\u0442 PR ${key}.`);
|
|
1384
|
+
return key;
|
|
1385
|
+
};
|
|
1386
|
+
const getPull = async (key) => pullOf3((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missing: missing(key) })).body);
|
|
1387
|
+
return {
|
|
1388
|
+
list: async (query) => {
|
|
1389
|
+
assertListQuery("PR SourceCraft", PULL_CAPABILITIES3.list, query, pullRequestFilterSchema.options);
|
|
1390
|
+
const status = usedStatuses3(query.status);
|
|
1391
|
+
const params = new URLSearchParams({ page_size: String(query.limit) });
|
|
1392
|
+
if (query.cursor !== void 0) params.set("page_token", query.cursor);
|
|
1393
|
+
const filter = pullFilterOf(query, status);
|
|
1394
|
+
if (filter !== void 0) params.set("filter", filter);
|
|
1395
|
+
if (query.sort !== void 0) params.set("sort_by", `${query.sort.direction === "asc" ? "" : "-"}${PULL_SORT_FIELDS_UPSTREAM[query.sort.field]}`);
|
|
1396
|
+
const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
|
|
1397
|
+
const record = isRecord(body) ? body : {};
|
|
1398
|
+
const pulls = (Array.isArray(record.pull_requests) ? record.pull_requests : []).map(pullOf3).filter((pull) => matchesPage2(pull, query));
|
|
1399
|
+
return { items: pulls, ...nextOf2(record.next_page_token) };
|
|
1400
|
+
},
|
|
1401
|
+
get: (key) => getPull(key),
|
|
1402
|
+
create: async (input) => pullOf3((await requestJson(root, { fetch, headers, method: "POST", body: createBody2(input) })).body),
|
|
1403
|
+
update: async (key, patch) => {
|
|
1404
|
+
const id = slug(key);
|
|
1405
|
+
let updated;
|
|
1406
|
+
if (patch.title !== void 0 || patch.description !== void 0 || patch.target !== void 0) {
|
|
1407
|
+
const body = {
|
|
1408
|
+
...patch.title === void 0 ? {} : { title: patch.title },
|
|
1409
|
+
...patch.description === void 0 ? {} : { description: patch.description },
|
|
1410
|
+
...patch.target === void 0 ? {} : { target_branch: patch.target }
|
|
1411
|
+
};
|
|
1412
|
+
updated = (await requestJson(`${root}/${id}`, { fetch, headers, method: "PATCH", body, missing: missing(key) })).body;
|
|
1413
|
+
}
|
|
1414
|
+
if (patch.status !== void 0) {
|
|
1415
|
+
const action = statusActionOf(patch.status, (await getPull(key)).status.category);
|
|
1416
|
+
if (action !== void 0) await requestJson(`${root}/${id}/${action}`, { fetch, headers, method: "POST", missing: missing(key) });
|
|
1417
|
+
return getPull(key);
|
|
1418
|
+
}
|
|
1419
|
+
return pullOf3(updated ?? (await requestJson(`${root}/${id}`, { fetch, headers, missing: missing(key) })).body);
|
|
1420
|
+
},
|
|
1421
|
+
merge: async (key, input) => {
|
|
1422
|
+
const id = slug(key);
|
|
1423
|
+
if (input.message !== void 0) throw new TrackerError("unsupported", "PR SourceCraft: \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u0441\u043B\u0438\u044F\u043D\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.");
|
|
1424
|
+
if (input.method !== void 0 && !PULL_CAPABILITIES3.merge.includes(input.method)) {
|
|
1425
|
+
throw new TrackerError("unsupported", `PR SourceCraft: \u0441\u043B\u0438\u044F\u043D\u0438\u0435 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C ${input.method} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
|
|
1426
|
+
}
|
|
1427
|
+
if (input.headSha !== void 0) {
|
|
1428
|
+
const current = await getPull(key);
|
|
1429
|
+
if ((current.headSha ?? "").toLowerCase() !== input.headSha.toLowerCase()) {
|
|
1430
|
+
throw new TrackerError("conflict", `\u0413\u043E\u043B\u043E\u0432\u0430 PR ${key} \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0430\u0441\u044C: \u043E\u0436\u0438\u0434\u0430\u043B\u0441\u044F ${input.headSha}, \u0441\u0435\u0439\u0447\u0430\u0441 ${current.headSha ?? "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D"}.`);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
const { body } = await requestJson(`${root}/${id}/merge`, {
|
|
1434
|
+
fetch,
|
|
1435
|
+
headers,
|
|
1436
|
+
method: "POST",
|
|
1437
|
+
// squash/rebase по умолчанию наследуются из настройки репозитория — без method не шлём явно false.
|
|
1438
|
+
body: {
|
|
1439
|
+
...input.method === void 0 ? {} : { squash: input.method === "squash", rebase: input.method === "rebase" },
|
|
1440
|
+
...input.deleteSource === void 0 ? {} : { delete_branch: input.deleteSource }
|
|
1441
|
+
},
|
|
1442
|
+
missing: missing(key)
|
|
1443
|
+
});
|
|
1444
|
+
const operation = isRecord(body) ? body : {};
|
|
1445
|
+
if (operation.status === "failed" || operation.status === "cancel") {
|
|
1446
|
+
const errorMessage = isRecord(operation.error) ? text(operation.error.message) : void 0;
|
|
1447
|
+
throw new TrackerError("conflict", `\u0421\u043B\u0438\u044F\u043D\u0438\u0435 PR ${key} \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u043E: ${redactSecretText(errorMessage ?? String(operation.status))}.`);
|
|
1448
|
+
}
|
|
1449
|
+
return getPull(key);
|
|
1450
|
+
},
|
|
1451
|
+
comments: async (key, query) => {
|
|
1452
|
+
const id = slug(key);
|
|
1453
|
+
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 PR SourceCraft", PULL_CAPABILITIES3.comments, query);
|
|
1454
|
+
const params = new URLSearchParams({ page_size: String(query.limit) });
|
|
1455
|
+
if (query.cursor !== void 0) params.set("page_token", query.cursor);
|
|
1456
|
+
if (query.order !== void 0) params.set("sort_by", query.order === "asc" ? "created_at" : "-created_at");
|
|
1457
|
+
const { body } = await requestJson(`${root}/${id}/comments?${params.toString()}`, { fetch, headers, missing: missing(key) });
|
|
1458
|
+
const record = isRecord(body) ? body : {};
|
|
1459
|
+
const rawComments = Array.isArray(record.pull_request_comments) ? record.pull_request_comments : [];
|
|
1460
|
+
const visible = rawComments.filter((raw) => !(isRecord(raw) && raw.is_deleted === true));
|
|
1461
|
+
const filtered = query.updatedAfter === void 0 ? visible : visible.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
|
|
1462
|
+
return { items: filtered.map(commentOf6), ...nextOf2(record.next_page_token) };
|
|
1463
|
+
},
|
|
1464
|
+
comment: async (key, body) => commentOf6((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missing: missing(key) })).body)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
function createBody2(input) {
|
|
1468
|
+
return {
|
|
1469
|
+
title: input.title,
|
|
1470
|
+
source_branch: input.source,
|
|
1471
|
+
target_branch: input.target,
|
|
1472
|
+
...input.description === void 0 ? {} : { description: input.description },
|
|
1473
|
+
publish: input.draft !== true
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function statusActionOf(requested, current) {
|
|
1477
|
+
if (current === requested) return void 0;
|
|
1478
|
+
if (requested === "draft") return "draft";
|
|
1479
|
+
if (requested === "open") return current === "draft" ? "publish" : "reopen";
|
|
1480
|
+
return "discard";
|
|
1481
|
+
}
|
|
1482
|
+
function usedStatuses3(status) {
|
|
1483
|
+
if (status === void 0 || status.length === 0) return void 0;
|
|
1484
|
+
for (const item of status) {
|
|
1485
|
+
if (!pullRequestStatusCategorySchema.options.includes(item)) {
|
|
1486
|
+
throw new TrackerError("invalid_request", `PR SourceCraft: \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u0430 ${String(item)}.`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return status;
|
|
1490
|
+
}
|
|
1491
|
+
function matchesPage2(pull, query) {
|
|
1492
|
+
if (query.labels !== void 0 && !query.labels.every((label) => pull.labels.includes(label))) return false;
|
|
1493
|
+
if (query.search !== void 0 && !containsText([pull.title, pull.description], query.search)) return false;
|
|
1494
|
+
return inTimeRange(pull.createdAt, query.createdAfter, query.createdBefore) && inTimeRange(pull.updatedAt, query.updatedAfter, query.updatedBefore);
|
|
1495
|
+
}
|
|
1496
|
+
function pullFilterOf(query, status) {
|
|
1497
|
+
const clauses = [];
|
|
1498
|
+
if (status !== void 0) {
|
|
1499
|
+
const slugs = [...new Set(keysFor(SOURCECRAFT_PULL_STATUSES, status))];
|
|
1500
|
+
clauses.push(`status in (${slugs.join(", ")})`);
|
|
1501
|
+
}
|
|
1502
|
+
if (query.author !== void 0) clauses.push(`author_slug=${quoted(query.author)}`);
|
|
1503
|
+
if (query.source !== void 0) clauses.push(`source_branch=${quoted(query.source)}`);
|
|
1504
|
+
if (query.target !== void 0) clauses.push(`target_branch=${quoted(query.target)}`);
|
|
1505
|
+
return clauses.length === 0 ? void 0 : clauses.join(" and ");
|
|
1506
|
+
}
|
|
1507
|
+
function nextOf2(token) {
|
|
1508
|
+
const value = text(token);
|
|
1509
|
+
return value === void 0 ? {} : { nextCursor: value };
|
|
1510
|
+
}
|
|
1511
|
+
function pullOf3(raw) {
|
|
1512
|
+
const item = isRecord(raw) ? raw : {};
|
|
1513
|
+
const source = isRecord(item.source) ? item.source : {};
|
|
1514
|
+
const target = isRecord(item.target) ? item.target : {};
|
|
1515
|
+
const headSha = text(source.sha);
|
|
1516
|
+
const description = text(item.description);
|
|
1517
|
+
const author = isRecord(item.author) ? text(item.author.slug) : void 0;
|
|
1518
|
+
const createdAt = toTimestamp(item.created_at);
|
|
1519
|
+
const updatedAt = toTimestamp(item.updated_at);
|
|
1520
|
+
const mergedAt = toTimestamp(item.merged_at);
|
|
1521
|
+
const closedAt = toTimestamp(item.closed_at);
|
|
1522
|
+
const key = text(item.slug) ?? "?";
|
|
1523
|
+
const statusName = text(item.status) ?? "open";
|
|
1524
|
+
return {
|
|
1525
|
+
key,
|
|
1526
|
+
title: text(item.title) ?? key,
|
|
1527
|
+
...description === void 0 ? {} : { description },
|
|
1528
|
+
status: { category: lookup(SOURCECRAFT_PULL_STATUSES, statusName, "open"), name: statusName },
|
|
1529
|
+
...author === void 0 ? {} : { author },
|
|
1530
|
+
source: text(source.ref) ?? text(item.source_branch) ?? "?",
|
|
1531
|
+
target: text(target.ref) ?? text(item.target_branch) ?? "?",
|
|
1532
|
+
...headSha !== void 0 && SHA3.test(headSha) ? { headSha } : {},
|
|
1533
|
+
labels: Array.isArray(item.labels) ? stringList(item.labels.map((label) => isRecord(label) ? label.name : void 0)) : [],
|
|
1534
|
+
links: [],
|
|
1535
|
+
...createdAt === void 0 ? {} : { createdAt },
|
|
1536
|
+
...updatedAt === void 0 ? {} : { updatedAt },
|
|
1537
|
+
...mergedAt === void 0 ? {} : { mergedAt },
|
|
1538
|
+
...closedAt === void 0 ? {} : { closedAt }
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function commentOf6(raw) {
|
|
1542
|
+
const item = isRecord(raw) ? raw : {};
|
|
1543
|
+
const author = isRecord(item.author) ? text(item.author.slug) : void 0;
|
|
1544
|
+
const createdAt = toTimestamp(item.created_at);
|
|
1545
|
+
return { id: text(item.id) ?? "?", ...author === void 0 ? {} : { author }, body: typeof item.body === "string" ? item.body : "", ...createdAt === void 0 ? {} : { createdAt } };
|
|
1546
|
+
}
|
|
1547
|
+
|
|
912
1548
|
// src/tracker/sourcecraft.lib.ts
|
|
913
|
-
var
|
|
1549
|
+
var DEFAULT_API4 = "https://api.sourcecraft.tech";
|
|
914
1550
|
var DEFAULT_WEB = "https://sourcecraft.dev";
|
|
915
|
-
var
|
|
916
|
-
var
|
|
1551
|
+
var TARGET6 = /^[\w.-]+\/[\w.-]+$/;
|
|
1552
|
+
var SLUG2 = /^[\w-]{1,100}$/;
|
|
917
1553
|
var ISSUE_SORT_FIELDS_UPSTREAM = {
|
|
918
1554
|
created: "created_at",
|
|
919
1555
|
updated: "updated_at",
|
|
@@ -937,18 +1573,19 @@ function sourcecraftTracker() {
|
|
|
937
1573
|
features: { create: true, update: true, comment: true, priority: true, labels: false, assignee: false, statuses: [...ALL_STATUSES] }
|
|
938
1574
|
},
|
|
939
1575
|
issues: ISSUE_CAPABILITIES4,
|
|
940
|
-
create: (context) => createSourceCraftAdapter(context)
|
|
1576
|
+
create: (context) => createSourceCraftAdapter(context),
|
|
1577
|
+
pulls: sourcecraftPulls()
|
|
941
1578
|
};
|
|
942
1579
|
}
|
|
943
1580
|
function createSourceCraftAdapter({ connection, fetch }) {
|
|
944
1581
|
const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
|
|
945
|
-
if (!
|
|
946
|
-
const api = baseUrl(connection.url,
|
|
1582
|
+
if (!TARGET6.test(target)) throw new TrackerError("invalid_request", "target SourceCraft \u2014 org/repo.");
|
|
1583
|
+
const api = baseUrl(connection.url, DEFAULT_API4);
|
|
947
1584
|
const root = `${api}/repos/${target}/issues`;
|
|
948
1585
|
const headers = { authorization: `Bearer ${connection.token ?? ""}` };
|
|
949
1586
|
const web = connection.url === void 0 ? `${DEFAULT_WEB}/${target}/issues` : void 0;
|
|
950
1587
|
const slug = (key) => {
|
|
951
|
-
if (!
|
|
1588
|
+
if (!SLUG2.test(key)) throw new TrackerError("unknown_issue", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
|
|
952
1589
|
return key;
|
|
953
1590
|
};
|
|
954
1591
|
const toIssue2 = (raw) => issueOf4(raw, web);
|
|
@@ -965,14 +1602,14 @@ function createSourceCraftAdapter({ connection, fetch }) {
|
|
|
965
1602
|
let issues = (Array.isArray(record.issues) ? record.issues : []).map(toIssue2);
|
|
966
1603
|
if (query.status !== void 0) issues = issues.filter((issue) => query.status?.includes(issue.status.category) === true);
|
|
967
1604
|
if (query.assignee === null) issues = issues.filter((issue) => issue.assignee === void 0);
|
|
968
|
-
return { items: issues, ...
|
|
1605
|
+
return { items: issues, ...nextOf3(record.next_page_token) };
|
|
969
1606
|
},
|
|
970
1607
|
get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missingIsIssue: true })).body),
|
|
971
1608
|
create: async (input) => {
|
|
972
1609
|
const body = { title: input.title, ...input.description === void 0 ? {} : { description: input.description }, ...input.priority === void 0 ? {} : { priority: input.priority } };
|
|
973
1610
|
return toIssue2((await requestJson(root, { fetch, headers, method: "POST", body })).body);
|
|
974
1611
|
},
|
|
975
|
-
update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, method: "PATCH", body:
|
|
1612
|
+
update: async (key, patch) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, method: "PATCH", body: patchBody5(patch), missingIsIssue: true })).body),
|
|
976
1613
|
comments: async (key, query) => {
|
|
977
1614
|
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 SourceCraft", ISSUE_CAPABILITIES4.comments, query);
|
|
978
1615
|
const params = new URLSearchParams({ page_size: String(query.limit) });
|
|
@@ -982,12 +1619,12 @@ function createSourceCraftAdapter({ connection, fetch }) {
|
|
|
982
1619
|
const record = isRecord(body) ? body : {};
|
|
983
1620
|
const rawComments = Array.isArray(record.issue_comments) ? record.issue_comments : [];
|
|
984
1621
|
const filtered = query.updatedAfter === void 0 ? rawComments : rawComments.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
|
|
985
|
-
return { items: filtered.map(
|
|
1622
|
+
return { items: filtered.map(commentOf7), ...nextOf3(record.next_page_token) };
|
|
986
1623
|
},
|
|
987
|
-
comment: async (key, body) =>
|
|
1624
|
+
comment: async (key, body) => commentOf7((await requestJson(`${root}/${slug(key)}/comments`, { fetch, headers, method: "POST", body: { body }, missingIsIssue: true })).body)
|
|
988
1625
|
};
|
|
989
1626
|
}
|
|
990
|
-
function
|
|
1627
|
+
function patchBody5(patch) {
|
|
991
1628
|
return {
|
|
992
1629
|
...patch.title === void 0 ? {} : { title: patch.title },
|
|
993
1630
|
...patch.description === void 0 ? {} : { description: patch.description },
|
|
@@ -995,7 +1632,7 @@ function patchBody3(patch) {
|
|
|
995
1632
|
...patch.priority === void 0 ? {} : { priority: patch.priority }
|
|
996
1633
|
};
|
|
997
1634
|
}
|
|
998
|
-
function
|
|
1635
|
+
function nextOf3(token) {
|
|
999
1636
|
const value = text(token);
|
|
1000
1637
|
return value === void 0 ? {} : { nextCursor: value };
|
|
1001
1638
|
}
|
|
@@ -1044,7 +1681,7 @@ function issueOf4(raw, web) {
|
|
|
1044
1681
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
1045
1682
|
};
|
|
1046
1683
|
}
|
|
1047
|
-
function
|
|
1684
|
+
function commentOf7(raw) {
|
|
1048
1685
|
const item = isRecord(raw) ? raw : {};
|
|
1049
1686
|
const author = isRecord(item.author) ? text(item.author.slug) : void 0;
|
|
1050
1687
|
const createdAt = toTimestamp(item.created_at);
|
|
@@ -1107,13 +1744,22 @@ function writeTrackerToken(dataDir, projectId, token, sink = defaultSecretSink)
|
|
|
1107
1744
|
function deleteTrackerToken(dataDir, projectId, sink = defaultSecretSink) {
|
|
1108
1745
|
return writeTrackerToken(dataDir, projectId, "", sink);
|
|
1109
1746
|
}
|
|
1747
|
+
|
|
1748
|
+
// src/tracker/pull.lib.ts
|
|
1749
|
+
function pullRequestAdapterOf(definition, context) {
|
|
1750
|
+
if (definition.pulls === void 0) throw new TrackerError("unsupported", `\u0422\u0440\u0435\u043A\u0435\u0440 ${definition.descriptor.title} \u043D\u0435 \u0432\u0435\u0434\u0451\u0442 pull request-\u044B.`);
|
|
1751
|
+
return definition.pulls.create(context);
|
|
1752
|
+
}
|
|
1110
1753
|
export {
|
|
1111
1754
|
ALL_STATUSES,
|
|
1112
1755
|
COMMENT_FILTERS,
|
|
1113
1756
|
GITHUB_CLOSE_REASONS,
|
|
1114
1757
|
GITHUB_IN_PROGRESS_LABEL,
|
|
1115
1758
|
GITHUB_PRIORITY_LABELS,
|
|
1759
|
+
GITLAB_DRAFT_PATTERN,
|
|
1760
|
+
GITLAB_DRAFT_PREFIX,
|
|
1116
1761
|
GITLAB_IN_PROGRESS_LABEL,
|
|
1762
|
+
GITLAB_MERGE_REQUEST_STATES,
|
|
1117
1763
|
GITLAB_PRIORITY_LABELS,
|
|
1118
1764
|
ISSUE_FILTERS,
|
|
1119
1765
|
ISSUE_SORT_FIELDS,
|
|
@@ -1123,6 +1769,7 @@ export {
|
|
|
1123
1769
|
JIRA_STATUS_CATEGORIES,
|
|
1124
1770
|
LEGACY_ISSUE_CAPABILITIES,
|
|
1125
1771
|
SOURCECRAFT_PRIORITIES,
|
|
1772
|
+
SOURCECRAFT_PULL_STATUSES,
|
|
1126
1773
|
SOURCECRAFT_STATUSES,
|
|
1127
1774
|
SOURCECRAFT_STATUS_FILTER,
|
|
1128
1775
|
SOURCECRAFT_STATUS_SLUGS,
|
|
@@ -1139,7 +1786,9 @@ export {
|
|
|
1139
1786
|
containsText,
|
|
1140
1787
|
deleteTrackerToken,
|
|
1141
1788
|
findTracker,
|
|
1789
|
+
githubPulls,
|
|
1142
1790
|
githubTracker,
|
|
1791
|
+
gitlabPulls,
|
|
1143
1792
|
gitlabTracker,
|
|
1144
1793
|
hasTrackerToken,
|
|
1145
1794
|
inTimeRange,
|
|
@@ -1151,14 +1800,21 @@ export {
|
|
|
1151
1800
|
listTrackers,
|
|
1152
1801
|
localTracker,
|
|
1153
1802
|
lookup,
|
|
1803
|
+
mergeMethodSchema,
|
|
1154
1804
|
nameOf,
|
|
1155
1805
|
priorityName,
|
|
1806
|
+
pullRequestAdapterOf,
|
|
1807
|
+
pullRequestFilterSchema,
|
|
1808
|
+
pullRequestSchema,
|
|
1809
|
+
pullRequestSortFieldSchema,
|
|
1810
|
+
pullRequestStatusCategorySchema,
|
|
1156
1811
|
quoted,
|
|
1157
1812
|
readTrackerToken,
|
|
1158
1813
|
registerBuiltinTrackers,
|
|
1159
1814
|
registerTracker,
|
|
1160
1815
|
requestJson,
|
|
1161
1816
|
requireTarget,
|
|
1817
|
+
sourcecraftPulls,
|
|
1162
1818
|
sourcecraftTracker,
|
|
1163
1819
|
stringList,
|
|
1164
1820
|
text,
|