@smwb/onreel-git-mcp 0.2.1 → 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 +170 -3
- 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 +1062 -77
- package/dist/tracker/index.js.map +1 -1
- package/dist/tracker/mapping.const.d.ts +11 -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.const.d.ts +12 -0
- package/dist/tracker/tracker.lib.d.ts +26 -1
- package/dist/tracker/tracker.types.d.ts +64 -2
- 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;
|
|
@@ -9,6 +13,13 @@ var TRACKER_PAGE_LIMIT_DEFAULT = 50;
|
|
|
9
13
|
var TRACKER_ERROR_BODY_MAX = 300;
|
|
10
14
|
var TRACKER_SECRETS_FILE = "tracker-secrets.json";
|
|
11
15
|
var TRACKER_LOCAL_AUTHOR = "owner";
|
|
16
|
+
var ISSUE_FILTERS = ["status", "labels", "assignee", "author", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"];
|
|
17
|
+
var ISSUE_SORT_FIELDS = ["created", "updated", "priority", "title", "comments"];
|
|
18
|
+
var COMMENT_FILTERS = ["updatedAfter"];
|
|
19
|
+
var LEGACY_ISSUE_CAPABILITIES = {
|
|
20
|
+
list: { filters: ["status"], clientSide: ["status"], sorts: [] },
|
|
21
|
+
comments: { filters: [], clientSide: [], order: ["asc"] }
|
|
22
|
+
};
|
|
12
23
|
|
|
13
24
|
// src/tracker/tracker.lib.ts
|
|
14
25
|
var TrackerError = class extends Error {
|
|
@@ -50,6 +61,89 @@ function nameOf(table, category) {
|
|
|
50
61
|
function priorityName(table, priority) {
|
|
51
62
|
return Object.entries(table).find(([, value]) => value === priority)?.[0];
|
|
52
63
|
}
|
|
64
|
+
function usedFilters(query, names) {
|
|
65
|
+
return names.filter((name) => {
|
|
66
|
+
const value = query[name];
|
|
67
|
+
return value !== void 0 && !(Array.isArray(value) && value.length === 0);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function assertListQuery(subject, capabilities, query, names) {
|
|
71
|
+
const used = usedFilters(query, names);
|
|
72
|
+
for (const name of used) {
|
|
73
|
+
if (!capabilities.filters.includes(name)) throw new TrackerError("unsupported", `${subject}: \u0444\u0438\u043B\u044C\u0442\u0440 ${name} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
|
|
74
|
+
}
|
|
75
|
+
const sort = query.sort;
|
|
76
|
+
if (sort !== void 0 && !capabilities.sorts.includes(sort.field)) {
|
|
77
|
+
throw new TrackerError("unsupported", `${subject}: \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0430 \u043F\u043E ${sort.field} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
|
|
78
|
+
}
|
|
79
|
+
if (sort?.direction !== void 0 && sort.direction !== "asc" && sort.direction !== "desc") {
|
|
80
|
+
throw new TrackerError("invalid_request", `${subject}: \u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438 \u2014 asc \u0438\u043B\u0438 desc.`);
|
|
81
|
+
}
|
|
82
|
+
const limit = query.limit;
|
|
83
|
+
if (limit !== void 0 && !(typeof limit === "number" && Number.isSafeInteger(limit) && limit >= 1)) {
|
|
84
|
+
throw new TrackerError("invalid_request", `${subject}: limit \u2014 \u0446\u0435\u043B\u043E\u0435 \u0447\u0438\u0441\u043B\u043E \u043E\u0442 1.`);
|
|
85
|
+
}
|
|
86
|
+
for (const name of used) {
|
|
87
|
+
const value = query[name];
|
|
88
|
+
if (isTimeFilter(name)) {
|
|
89
|
+
if (typeof value !== "number" || !(Number.isSafeInteger(value) && value >= 0)) {
|
|
90
|
+
throw new TrackerError("invalid_request", `${subject}: \u0444\u0438\u043B\u044C\u0442\u0440 ${name} \u2014 \u0432\u0440\u0435\u043C\u044F \u0432 \u043C\u0441, \u0446\u0435\u043B\u043E\u0435 \u0447\u0438\u0441\u043B\u043E \u043E\u0442 0.`);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (isArrayFilter(name)) {
|
|
95
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
96
|
+
throw new TrackerError("invalid_request", `${subject}: \u0444\u0438\u043B\u044C\u0442\u0440 ${name} \u2014 \u0441\u043F\u0438\u0441\u043E\u043A \u043D\u0435\u043F\u0443\u0441\u0442\u044B\u0445 \u0441\u0442\u0440\u043E\u043A.`);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (value === null) continue;
|
|
101
|
+
if (typeof value === "string") {
|
|
102
|
+
if (value.trim() === "") throw new TrackerError("invalid_request", `${subject}: \u0444\u0438\u043B\u044C\u0442\u0440 ${name} \u2014 \u043F\u0443\u0441\u0442\u0430\u044F \u0441\u0442\u0440\u043E\u043A\u0430.`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
if (value.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
107
|
+
throw new TrackerError("invalid_request", `${subject}: \u0444\u0438\u043B\u044C\u0442\u0440 ${name} \u2014 \u0441\u043F\u0438\u0441\u043E\u043A \u043D\u0435\u043F\u0443\u0441\u0442\u044B\u0445 \u0441\u0442\u0440\u043E\u043A.`);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
throw new TrackerError("invalid_request", `${subject}: \u0444\u0438\u043B\u044C\u0442\u0440 ${name} \u2014 \u0441\u0442\u0440\u043E\u043A\u0430, \u0441\u043F\u0438\u0441\u043E\u043A \u0441\u0442\u0440\u043E\u043A \u0438\u043B\u0438 null.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function isTimeFilter(name) {
|
|
115
|
+
return name.endsWith("After") || name.endsWith("Before");
|
|
116
|
+
}
|
|
117
|
+
function isArrayFilter(name) {
|
|
118
|
+
return name === "status" || name === "labels";
|
|
119
|
+
}
|
|
120
|
+
function assertCommentQuery(subject, capabilities, query) {
|
|
121
|
+
assertListQuery(subject, { filters: capabilities.filters, clientSide: capabilities.clientSide, sorts: [] }, query, COMMENT_FILTERS);
|
|
122
|
+
if (query.order !== void 0 && !capabilities.order.includes(query.order)) {
|
|
123
|
+
throw new TrackerError("unsupported", `${subject}: \u043F\u043E\u0440\u044F\u0434\u043E\u043A ${query.order} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function issueCapabilitiesOf(definition) {
|
|
127
|
+
return definition.issues ?? LEGACY_ISSUE_CAPABILITIES;
|
|
128
|
+
}
|
|
129
|
+
function isoTime(ms) {
|
|
130
|
+
return new Date(ms).toISOString();
|
|
131
|
+
}
|
|
132
|
+
function inTimeRange(value, after, before) {
|
|
133
|
+
if (after === void 0 && before === void 0) return true;
|
|
134
|
+
if (value === void 0) return false;
|
|
135
|
+
return (after === void 0 || value >= after) && (before === void 0 || value <= before);
|
|
136
|
+
}
|
|
137
|
+
function containsText(texts, needle) {
|
|
138
|
+
const lower = needle.toLowerCase();
|
|
139
|
+
return texts.some((value) => value !== void 0 && value.toLowerCase().includes(lower));
|
|
140
|
+
}
|
|
141
|
+
function quoted(value) {
|
|
142
|
+
return `"${value.replace(/[\\"]/g, "\\$&")}"`;
|
|
143
|
+
}
|
|
144
|
+
function keysFor(table, values) {
|
|
145
|
+
return Object.entries(table).filter(([, value]) => values.includes(value)).map(([key]) => key);
|
|
146
|
+
}
|
|
53
147
|
|
|
54
148
|
// src/tracker/mapping.const.ts
|
|
55
149
|
var SOURCECRAFT_STATUSES = {
|
|
@@ -66,6 +160,12 @@ var SOURCECRAFT_STATUS_SLUGS = {
|
|
|
66
160
|
done: "closed",
|
|
67
161
|
declined: "declined"
|
|
68
162
|
};
|
|
163
|
+
var SOURCECRAFT_STATUS_FILTER = {
|
|
164
|
+
open: ["open", "paused"],
|
|
165
|
+
in_progress: ["in_progress"],
|
|
166
|
+
done: ["closed"],
|
|
167
|
+
declined: ["declined", "duplicate"]
|
|
168
|
+
};
|
|
69
169
|
var SOURCECRAFT_PRIORITIES = {
|
|
70
170
|
trivial: "trivial",
|
|
71
171
|
minor: "minor",
|
|
@@ -115,6 +215,21 @@ var GITHUB_PRIORITY_LABELS = {
|
|
|
115
215
|
"priority: blocker": "blocker"
|
|
116
216
|
};
|
|
117
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
|
+
};
|
|
118
233
|
|
|
119
234
|
// src/tracker/http.lib.ts
|
|
120
235
|
async function requestJson(url, request) {
|
|
@@ -130,11 +245,7 @@ async function requestJson(url, request) {
|
|
|
130
245
|
throw new TrackerError("failed", redactSecretText(`\u0422\u0440\u0435\u043A\u0435\u0440 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B (${hostOf(url)}): ${error instanceof Error ? error.message : String(error)}`));
|
|
131
246
|
}
|
|
132
247
|
const raw = await response.text();
|
|
133
|
-
if (!response.ok)
|
|
134
|
-
if (response.status === 404 && request.missingIsIssue === true) throw new TrackerError("unknown_issue", "\u0417\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0442\u0440\u0435\u043A\u0435\u0440\u0435.");
|
|
135
|
-
const detail = redactSecretText(raw.replace(/\s+/g, " ").trim()).slice(0, TRACKER_ERROR_BODY_MAX);
|
|
136
|
-
throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u043E\u0442\u0432\u0435\u0442\u0438\u043B ${String(response.status)}${detail === "" ? "" : `: ${detail}`}.`);
|
|
137
|
-
}
|
|
248
|
+
if (!response.ok) throw failureOf(url, response, raw, request);
|
|
138
249
|
if (raw === "") return { body: void 0, headers: response.headers };
|
|
139
250
|
try {
|
|
140
251
|
return { body: JSON.parse(raw), headers: response.headers };
|
|
@@ -142,6 +253,38 @@ async function requestJson(url, request) {
|
|
|
142
253
|
throw new TrackerError("failed", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435 JSON.`);
|
|
143
254
|
}
|
|
144
255
|
}
|
|
256
|
+
function failureOf(url, response, raw, request) {
|
|
257
|
+
const status = response.status;
|
|
258
|
+
if (status === 404 && request.missing !== void 0) return new TrackerError(request.missing.failure, request.missing.message);
|
|
259
|
+
if (status === 404 && request.missingIsIssue === true) return new TrackerError("unknown_issue", "\u0417\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0442\u0440\u0435\u043A\u0435\u0440\u0435.");
|
|
260
|
+
const detail = redactSecretText(raw.replace(/\s+/g, " ").trim()).slice(0, TRACKER_ERROR_BODY_MAX);
|
|
261
|
+
const answered = `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u043E\u0442\u0432\u0435\u0442\u0438\u043B ${String(status)}${detail === "" ? "" : `: ${detail}`}.`;
|
|
262
|
+
if (request.conflictOn?.includes(status) === true) return new TrackerError("conflict", answered);
|
|
263
|
+
const secondaryLimit = status === 403 && (response.headers.get("x-ratelimit-remaining")?.trim() === "0" || retryAfterOf(response.headers.get("retry-after")?.trim(), Date.now()) !== void 0);
|
|
264
|
+
if (status === 429 || secondaryLimit) {
|
|
265
|
+
const delay = retryDelayOf(response.headers);
|
|
266
|
+
return new TrackerError("rate_limited", `\u0422\u0440\u0435\u043A\u0435\u0440 ${hostOf(url)} \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0438\u043B \u0447\u0430\u0441\u0442\u043E\u0442\u0443 \u0437\u0430\u043F\u0440\u043E\u0441\u043E\u0432${delay === void 0 ? "" : `, \u043F\u043E\u0432\u0442\u043E\u0440 \u0447\u0435\u0440\u0435\u0437 ${String(delay)} \u0441`}.`);
|
|
267
|
+
}
|
|
268
|
+
return new TrackerError("failed", answered);
|
|
269
|
+
}
|
|
270
|
+
function retryDelayOf(headers, now = Date.now()) {
|
|
271
|
+
const retryAfter = retryAfterOf(headers.get("retry-after")?.trim(), now);
|
|
272
|
+
if (retryAfter !== void 0) return retryAfter;
|
|
273
|
+
for (const name of ["x-ratelimit-reset", "ratelimit-reset"]) {
|
|
274
|
+
const reset = headers.get(name)?.trim();
|
|
275
|
+
if (reset !== void 0 && /^\d+$/.test(reset)) return resetDelay(Number(reset), now);
|
|
276
|
+
}
|
|
277
|
+
return void 0;
|
|
278
|
+
}
|
|
279
|
+
function retryAfterOf(value, now) {
|
|
280
|
+
if (value === void 0 || value === "") return void 0;
|
|
281
|
+
if (/^\d+$/.test(value)) return Number(value);
|
|
282
|
+
const at = Date.parse(value);
|
|
283
|
+
return Number.isFinite(at) ? Math.max(0, Math.ceil((at - now) / 1e3)) : void 0;
|
|
284
|
+
}
|
|
285
|
+
function resetDelay(reset, now) {
|
|
286
|
+
return reset >= 1e9 ? Math.max(0, reset - Math.floor(now / 1e3)) : reset;
|
|
287
|
+
}
|
|
145
288
|
function hostOf(url) {
|
|
146
289
|
try {
|
|
147
290
|
return new URL(url).host;
|
|
@@ -175,11 +318,237 @@ function listTrackers() {
|
|
|
175
318
|
return [...definitions.values()];
|
|
176
319
|
}
|
|
177
320
|
|
|
178
|
-
// 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
|
|
179
351
|
var DEFAULT_API = "https://api.github.com";
|
|
180
352
|
var TARGET = /^[\w.-]+\/[\w.-]+$/;
|
|
181
|
-
var NUMBER = /^[1-9]\d{0,
|
|
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";
|
|
182
547
|
function githubTracker() {
|
|
548
|
+
const issues = {
|
|
549
|
+
list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "comments"] },
|
|
550
|
+
comments: { filters: ["updatedAfter"], clientSide: [], order: ["asc"] }
|
|
551
|
+
};
|
|
183
552
|
return {
|
|
184
553
|
descriptor: {
|
|
185
554
|
tracker: "github",
|
|
@@ -191,20 +560,23 @@ function githubTracker() {
|
|
|
191
560
|
],
|
|
192
561
|
features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: ["open", "in_progress", "done", "declined"] }
|
|
193
562
|
},
|
|
194
|
-
create: (context) => createGithubAdapter(context)
|
|
563
|
+
create: (context) => createGithubAdapter(context, issues),
|
|
564
|
+
issues,
|
|
565
|
+
pulls: githubPulls()
|
|
195
566
|
};
|
|
196
567
|
}
|
|
197
|
-
function createGithubAdapter({ connection, fetch }) {
|
|
568
|
+
function createGithubAdapter({ connection, fetch }, capabilities) {
|
|
198
569
|
const target = requireTarget(connection.target, "owner/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F GitHub");
|
|
199
|
-
if (!
|
|
200
|
-
const
|
|
570
|
+
if (!TARGET2.test(target)) throw new TrackerError("invalid_request", "target GitHub \u2014 owner/repo.");
|
|
571
|
+
const api = baseUrl(connection.url, DEFAULT_API2);
|
|
572
|
+
const root = `${api}/repos/${target}/issues`;
|
|
201
573
|
const headers = {
|
|
202
574
|
accept: "application/vnd.github+json",
|
|
203
575
|
"x-github-api-version": "2022-11-28",
|
|
204
576
|
...connection.token === void 0 || connection.token === "" ? {} : { authorization: `Bearer ${connection.token}` }
|
|
205
577
|
};
|
|
206
578
|
const number = (key) => {
|
|
207
|
-
if (!
|
|
579
|
+
if (!NUMBER2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitHub \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
|
|
208
580
|
return key;
|
|
209
581
|
};
|
|
210
582
|
const rawOf = async (key) => {
|
|
@@ -213,14 +585,13 @@ function createGithubAdapter({ connection, fetch }) {
|
|
|
213
585
|
};
|
|
214
586
|
return {
|
|
215
587
|
list: async (query) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
};
|
|
588
|
+
assertListQuery(LIST_SUBJECT2, capabilities.list, query, ISSUE_FILTERS);
|
|
589
|
+
assertCsvLabels(query.labels);
|
|
590
|
+
const page = pageOf2(query.cursor);
|
|
591
|
+
const search = needsSearch(query);
|
|
592
|
+
const params = search ? searchParams(target, query, page) : issuesParams(query, page);
|
|
593
|
+
const { body, headers: responseHeaders } = await requestJson(`${search ? `${api}/search/issues` : root}?${params.toString()}`, { fetch, headers });
|
|
594
|
+
return issuePageOf(rowsOf(body, search), query, responseHeaders.get("link") ?? "", page);
|
|
224
595
|
},
|
|
225
596
|
get: async (key) => issueOf(await rawOf(key)),
|
|
226
597
|
create: async (input) => {
|
|
@@ -235,33 +606,97 @@ function createGithubAdapter({ connection, fetch }) {
|
|
|
235
606
|
},
|
|
236
607
|
update: async (key, patch) => {
|
|
237
608
|
const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : issueOf(await rawOf(key));
|
|
238
|
-
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 });
|
|
239
610
|
return issueOf(response.body);
|
|
240
611
|
},
|
|
241
612
|
comments: async (key, query) => {
|
|
242
|
-
|
|
613
|
+
assertCommentQuery(COMMENTS_SUBJECT2, capabilities.comments, query);
|
|
614
|
+
const page = pageOf2(query.cursor);
|
|
243
615
|
const params = new URLSearchParams({ per_page: String(query.limit), page: String(page) });
|
|
616
|
+
if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
|
|
244
617
|
const { body, headers: responseHeaders } = await requestJson(`${root}/${number(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
|
|
245
|
-
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) } : {} };
|
|
246
619
|
},
|
|
247
|
-
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)
|
|
248
621
|
};
|
|
249
622
|
}
|
|
250
|
-
function
|
|
623
|
+
function pageOf2(cursor) {
|
|
251
624
|
const page = cursor === void 0 ? 1 : Number(cursor);
|
|
252
625
|
return Number.isInteger(page) && page >= 1 ? page : 1;
|
|
253
626
|
}
|
|
254
|
-
function
|
|
627
|
+
function stateFilter2(status) {
|
|
255
628
|
if (status === void 0) return "all";
|
|
256
629
|
if (status.every((item) => item === "open" || item === "in_progress")) return "open";
|
|
257
630
|
if (status.every((item) => item === "done" || item === "declined")) return "closed";
|
|
258
631
|
return "all";
|
|
259
632
|
}
|
|
633
|
+
function needsSearch(query) {
|
|
634
|
+
return query.search !== void 0 || query.createdAfter !== void 0 || query.createdBefore !== void 0 || query.updatedBefore !== void 0;
|
|
635
|
+
}
|
|
636
|
+
function assertCsvLabels(labels) {
|
|
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.`);
|
|
638
|
+
}
|
|
639
|
+
function issuesParams(query, page) {
|
|
640
|
+
const params = new URLSearchParams({ state: stateFilter2(query.status), per_page: String(query.limit), page: String(page) });
|
|
641
|
+
if (query.labels !== void 0 && query.labels.length > 0) params.set("labels", query.labels.join(","));
|
|
642
|
+
if (query.assignee !== void 0) params.set("assignee", query.assignee ?? "none");
|
|
643
|
+
if (query.author !== void 0) params.set("creator", query.author);
|
|
644
|
+
if (query.updatedAfter !== void 0) params.set("since", isoTime(query.updatedAfter));
|
|
645
|
+
if (query.sort !== void 0) {
|
|
646
|
+
params.set("sort", query.sort.field);
|
|
647
|
+
params.set("direction", query.sort.direction ?? "desc");
|
|
648
|
+
}
|
|
649
|
+
return params;
|
|
650
|
+
}
|
|
651
|
+
function searchParams(target, query, page) {
|
|
652
|
+
const params = new URLSearchParams({ q: searchQuery(target, query), per_page: String(query.limit), page: String(page) });
|
|
653
|
+
const sort = query.sort;
|
|
654
|
+
params.set("sort", sort?.field ?? "created");
|
|
655
|
+
params.set("order", sort?.direction ?? "desc");
|
|
656
|
+
return params;
|
|
657
|
+
}
|
|
658
|
+
function searchQuery(target, query) {
|
|
659
|
+
const parts = [`repo:${target}`, "is:issue"];
|
|
660
|
+
const state = searchStateFilter(query.status);
|
|
661
|
+
if (state !== void 0) parts.push(state);
|
|
662
|
+
for (const label of query.labels ?? []) parts.push(`label:${quoted(label)}`);
|
|
663
|
+
if (query.assignee !== void 0) parts.push(query.assignee === null ? "no:assignee" : `assignee:${quoted(query.assignee)}`);
|
|
664
|
+
if (query.author !== void 0) parts.push(`author:${quoted(query.author)}`);
|
|
665
|
+
const created = timeRangeQualifier("created", query.createdAfter, query.createdBefore);
|
|
666
|
+
if (created !== void 0) parts.push(created);
|
|
667
|
+
const updated = timeRangeQualifier("updated", query.updatedAfter, query.updatedBefore);
|
|
668
|
+
if (updated !== void 0) parts.push(updated);
|
|
669
|
+
if (query.search !== void 0) parts.push(quoted(query.search));
|
|
670
|
+
return parts.join(" ");
|
|
671
|
+
}
|
|
672
|
+
function timeRangeQualifier(field, after, before) {
|
|
673
|
+
if (after !== void 0 && before !== void 0) return `${field}:${isoTime(after)}..${isoTime(before)}`;
|
|
674
|
+
if (after !== void 0) return `${field}:>=${isoTime(after)}`;
|
|
675
|
+
if (before !== void 0) return `${field}:<=${isoTime(before)}`;
|
|
676
|
+
return void 0;
|
|
677
|
+
}
|
|
678
|
+
function rowsOf(body, search) {
|
|
679
|
+
if (!search) return Array.isArray(body) ? body : [];
|
|
680
|
+
return isRecord(body) && Array.isArray(body.items) ? body.items : [];
|
|
681
|
+
}
|
|
682
|
+
function searchStateFilter(status) {
|
|
683
|
+
if (status === void 0) return void 0;
|
|
684
|
+
if (status.every((item) => item === "open" || item === "in_progress")) return "is:open";
|
|
685
|
+
if (status.every((item) => item === "done" || item === "declined")) return "is:closed";
|
|
686
|
+
return void 0;
|
|
687
|
+
}
|
|
688
|
+
function issuePageOf(rows, query, link, page) {
|
|
689
|
+
const items = rows.filter((row) => isRecord(row) && row.pull_request === void 0).map(issueOf);
|
|
690
|
+
return {
|
|
691
|
+
items: query.status === void 0 ? items : items.filter((issue) => query.status?.includes(issue.status.category) === true),
|
|
692
|
+
.../rel="next"/.test(link) ? { nextCursor: String(page + 1) } : {}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
260
695
|
function composeLabels(plain, priority, status) {
|
|
261
696
|
const priorityLabel = priority === void 0 ? void 0 : priorityName(GITHUB_PRIORITY_LABELS, priority);
|
|
262
697
|
return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITHUB_IN_PROGRESS_LABEL] : []];
|
|
263
698
|
}
|
|
264
|
-
function
|
|
699
|
+
function patchBody2(patch, current) {
|
|
265
700
|
const status = patch.status ?? current?.status.category;
|
|
266
701
|
const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
|
|
267
702
|
return {
|
|
@@ -297,17 +732,221 @@ function issueOf(raw) {
|
|
|
297
732
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
298
733
|
};
|
|
299
734
|
}
|
|
300
|
-
function
|
|
735
|
+
function commentOf2(raw) {
|
|
301
736
|
const item = isRecord(raw) ? raw : {};
|
|
302
737
|
const author = isRecord(item.user) ? text(item.user.login) : void 0;
|
|
303
738
|
const createdAt = toTimestamp(item.created_at);
|
|
304
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 } };
|
|
305
740
|
}
|
|
306
741
|
|
|
307
|
-
// src/tracker/gitlab.lib.ts
|
|
742
|
+
// src/tracker/gitlab-pull.lib.ts
|
|
308
743
|
var DEFAULT_URL = "https://gitlab.com";
|
|
309
|
-
var
|
|
310
|
-
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}$/;
|
|
940
|
+
var ISSUE_CAPABILITIES = {
|
|
941
|
+
list: { filters: ISSUE_FILTERS, clientSide: ["status"], sorts: ["created", "updated", "priority", "title"] },
|
|
942
|
+
comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] }
|
|
943
|
+
};
|
|
944
|
+
var ORDER_BY2 = {
|
|
945
|
+
created: "created_at",
|
|
946
|
+
updated: "updated_at",
|
|
947
|
+
priority: "priority",
|
|
948
|
+
title: "title"
|
|
949
|
+
};
|
|
311
950
|
function gitlabTracker() {
|
|
312
951
|
return {
|
|
313
952
|
descriptor: {
|
|
@@ -320,23 +959,43 @@ function gitlabTracker() {
|
|
|
320
959
|
],
|
|
321
960
|
features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
|
|
322
961
|
},
|
|
323
|
-
create: (context) => createGitlabAdapter(context)
|
|
962
|
+
create: (context) => createGitlabAdapter(context),
|
|
963
|
+
issues: ISSUE_CAPABILITIES,
|
|
964
|
+
pulls: gitlabPulls()
|
|
324
965
|
};
|
|
325
966
|
}
|
|
326
967
|
function createGitlabAdapter({ connection, fetch }) {
|
|
327
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");
|
|
328
|
-
if (!
|
|
329
|
-
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`;
|
|
330
971
|
const headers = connection.token === void 0 || connection.token === "" ? {} : { "private-token": connection.token };
|
|
331
972
|
const iid = (key) => {
|
|
332
|
-
if (!
|
|
973
|
+
if (!IID2.test(key)) throw new TrackerError("unknown_issue", `\u0412 GitLab \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
|
|
333
974
|
return key;
|
|
334
975
|
};
|
|
335
976
|
const currentOf = async (key) => issueOf2((await requestJson(`${root}/${iid(key)}`, { fetch, headers, missingIsIssue: true })).body);
|
|
336
977
|
return {
|
|
337
978
|
list: async (query) => {
|
|
338
|
-
|
|
339
|
-
const
|
|
979
|
+
assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 GitLab", ISSUE_CAPABILITIES.list, query, ISSUE_FILTERS);
|
|
980
|
+
const page = pageOf4(query.cursor);
|
|
981
|
+
const params = new URLSearchParams({
|
|
982
|
+
state: stateFilter4(query.status),
|
|
983
|
+
per_page: String(query.limit),
|
|
984
|
+
page: String(page),
|
|
985
|
+
order_by: query.sort === void 0 ? "created_at" : ORDER_BY2[query.sort.field] ?? "created_at",
|
|
986
|
+
sort: query.sort?.direction ?? "desc"
|
|
987
|
+
});
|
|
988
|
+
if (query.labels !== void 0 && query.labels.length > 0) {
|
|
989
|
+
if (query.labels.some((label) => label.includes(","))) throw new TrackerError("invalid_request", "\u0417\u0430\u0434\u0430\u0447\u0438 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.");
|
|
990
|
+
params.set("labels", query.labels.join(","));
|
|
991
|
+
}
|
|
992
|
+
if (query.assignee !== void 0) params.set(query.assignee === null ? "assignee_id" : "assignee_username", query.assignee === null ? "None" : query.assignee);
|
|
993
|
+
if (query.author !== void 0) params.set("author_username", query.author);
|
|
994
|
+
if (query.search !== void 0) params.set("search", query.search);
|
|
995
|
+
if (query.createdAfter !== void 0) params.set("created_after", isoTime(query.createdAfter));
|
|
996
|
+
if (query.createdBefore !== void 0) params.set("created_before", isoTime(query.createdBefore));
|
|
997
|
+
if (query.updatedAfter !== void 0) params.set("updated_after", isoTime(query.updatedAfter));
|
|
998
|
+
if (query.updatedBefore !== void 0) params.set("updated_before", isoTime(query.updatedBefore));
|
|
340
999
|
const { body, headers: responseHeaders } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
|
|
341
1000
|
const items = (Array.isArray(body) ? body : []).map(issueOf2);
|
|
342
1001
|
const next = responseHeaders.get("x-next-page");
|
|
@@ -353,24 +1012,25 @@ function createGitlabAdapter({ connection, fetch }) {
|
|
|
353
1012
|
},
|
|
354
1013
|
update: async (key, patch) => {
|
|
355
1014
|
const current = patch.labels === void 0 && patch.priority === void 0 && patch.status === void 0 ? void 0 : await currentOf(key);
|
|
356
|
-
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);
|
|
357
1016
|
},
|
|
358
1017
|
comments: async (key, query) => {
|
|
359
|
-
|
|
360
|
-
const
|
|
1018
|
+
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 GitLab", ISSUE_CAPABILITIES.comments, query);
|
|
1019
|
+
const page = pageOf4(query.cursor);
|
|
1020
|
+
const params = new URLSearchParams({ sort: query.order ?? "asc", order_by: "created_at", per_page: String(query.limit), page: String(page) });
|
|
361
1021
|
const { body, headers: responseHeaders } = await requestJson(`${root}/${iid(key)}/notes?${params.toString()}`, { fetch, headers, missingIsIssue: true });
|
|
362
1022
|
const next = responseHeaders.get("x-next-page");
|
|
363
|
-
const items = (Array.isArray(body) ? body : []).filter((note) => isRecord(note) && note.system !== true).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);
|
|
364
1024
|
return { items, ...next === null || next === "" ? {} : { nextCursor: next } };
|
|
365
1025
|
},
|
|
366
|
-
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)
|
|
367
1027
|
};
|
|
368
1028
|
}
|
|
369
|
-
function
|
|
1029
|
+
function pageOf4(cursor) {
|
|
370
1030
|
const page = cursor === void 0 ? 1 : Number(cursor);
|
|
371
1031
|
return Number.isInteger(page) && page >= 1 ? page : 1;
|
|
372
1032
|
}
|
|
373
|
-
function
|
|
1033
|
+
function stateFilter4(status) {
|
|
374
1034
|
if (status === void 0) return "all";
|
|
375
1035
|
if (status.every((item) => item === "open" || item === "in_progress")) return "opened";
|
|
376
1036
|
if (status.every((item) => item === "done" || item === "declined")) return "closed";
|
|
@@ -380,7 +1040,7 @@ function composeLabels2(plain, priority, status) {
|
|
|
380
1040
|
const priorityLabel = priority === void 0 ? void 0 : priorityName(GITLAB_PRIORITY_LABELS, priority);
|
|
381
1041
|
return [...plain, ...priorityLabel === void 0 ? [] : [priorityLabel], ...status === "in_progress" ? [GITLAB_IN_PROGRESS_LABEL] : []];
|
|
382
1042
|
}
|
|
383
|
-
function
|
|
1043
|
+
function patchBody4(patch, current) {
|
|
384
1044
|
const status = patch.status ?? current?.status.category;
|
|
385
1045
|
const touchesLabels = patch.labels !== void 0 || patch.priority !== void 0 || patch.status !== void 0;
|
|
386
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.");
|
|
@@ -418,7 +1078,7 @@ function issueOf2(raw) {
|
|
|
418
1078
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
419
1079
|
};
|
|
420
1080
|
}
|
|
421
|
-
function
|
|
1081
|
+
function commentOf4(raw) {
|
|
422
1082
|
const item = isRecord(raw) ? raw : {};
|
|
423
1083
|
const author = isRecord(item.author) ? text(item.author.username) : void 0;
|
|
424
1084
|
const createdAt = toTimestamp(item.created_at);
|
|
@@ -430,6 +1090,11 @@ var PROJECT_KEY = /^[A-Z][A-Z0-9_]{0,49}$/;
|
|
|
430
1090
|
var ISSUE_KEY = /^[A-Z][A-Z0-9_]{0,49}-[1-9]\d{0,14}$/;
|
|
431
1091
|
var FIELDS = "summary,description,status,priority,labels,assignee,created,updated";
|
|
432
1092
|
var CATEGORY_JQL = { open: '"To Do"', in_progress: '"In Progress"', done: "Done" };
|
|
1093
|
+
var ISSUE_CAPABILITIES2 = {
|
|
1094
|
+
list: { filters: ISSUE_FILTERS, clientSide: [], sorts: ["created", "updated", "priority", "title"] },
|
|
1095
|
+
comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] }
|
|
1096
|
+
};
|
|
1097
|
+
var JIRA_SORT_FIELDS = { created: "created", updated: "updated", priority: "priority", title: "summary" };
|
|
433
1098
|
function jiraTracker() {
|
|
434
1099
|
return {
|
|
435
1100
|
descriptor: {
|
|
@@ -443,7 +1108,8 @@ function jiraTracker() {
|
|
|
443
1108
|
],
|
|
444
1109
|
features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: false, statuses: ["open", "in_progress", "done"] }
|
|
445
1110
|
},
|
|
446
|
-
create: (context) => createJiraAdapter(context)
|
|
1111
|
+
create: (context) => createJiraAdapter(context),
|
|
1112
|
+
issues: ISSUE_CAPABILITIES2
|
|
447
1113
|
};
|
|
448
1114
|
}
|
|
449
1115
|
function createJiraAdapter({ connection, fetch }) {
|
|
@@ -462,7 +1128,8 @@ function createJiraAdapter({ connection, fetch }) {
|
|
|
462
1128
|
const load = async (value) => toIssue2((await requestJson(`${api}/issue/${key(value)}?fields=${FIELDS}`, { fetch, headers, missingIsIssue: true })).body);
|
|
463
1129
|
return {
|
|
464
1130
|
list: async (query) => {
|
|
465
|
-
|
|
1131
|
+
assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 Jira", ISSUE_CAPABILITIES2.list, query, ISSUE_FILTERS);
|
|
1132
|
+
const jql = listJql(project, query);
|
|
466
1133
|
if (jql === void 0) return { items: [] };
|
|
467
1134
|
if (query.cursor?.startsWith("s:") !== true) {
|
|
468
1135
|
const params2 = new URLSearchParams({ jql, fields: FIELDS, maxResults: String(query.limit) });
|
|
@@ -507,23 +1174,44 @@ function createJiraAdapter({ connection, fetch }) {
|
|
|
507
1174
|
return load(value);
|
|
508
1175
|
},
|
|
509
1176
|
comments: async (value, query) => {
|
|
1177
|
+
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 Jira", ISSUE_CAPABILITIES2.comments, query);
|
|
510
1178
|
const startAt = query.cursor === void 0 ? 0 : Number(query.cursor);
|
|
511
|
-
const params = new URLSearchParams({ startAt: String(Number.isInteger(startAt) && startAt >= 0 ? startAt : 0), maxResults: String(query.limit), orderBy: "created" });
|
|
1179
|
+
const params = new URLSearchParams({ startAt: String(Number.isInteger(startAt) && startAt >= 0 ? startAt : 0), maxResults: String(query.limit), orderBy: query.order === "desc" ? "-created" : "created" });
|
|
512
1180
|
const { body } = await requestJson(`${api}/issue/${key(value)}/comment?${params.toString()}`, { fetch, headers, missingIsIssue: true });
|
|
513
1181
|
const record = isRecord(body) ? body : {};
|
|
514
|
-
const
|
|
1182
|
+
const raw = Array.isArray(record.comments) ? record.comments : [];
|
|
1183
|
+
const items = raw.filter((item) => inTimeRange(toTimestamp(isRecord(item) ? item.updated : void 0), query.updatedAfter, void 0));
|
|
515
1184
|
const total = typeof record.total === "number" ? record.total : 0;
|
|
516
1185
|
const at = typeof record.startAt === "number" ? record.startAt : 0;
|
|
517
|
-
return { items: items.map(
|
|
1186
|
+
return { items: items.map(commentOf5), ...at + raw.length < total && raw.length > 0 ? { nextCursor: String(at + raw.length) } : {} };
|
|
518
1187
|
},
|
|
519
|
-
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)
|
|
520
1189
|
};
|
|
521
1190
|
}
|
|
522
|
-
function listJql(project,
|
|
523
|
-
const
|
|
524
|
-
if (status
|
|
525
|
-
|
|
526
|
-
|
|
1191
|
+
function listJql(project, query) {
|
|
1192
|
+
const clauses = [];
|
|
1193
|
+
if (query.status !== void 0) {
|
|
1194
|
+
const names = query.status.flatMap((item) => CATEGORY_JQL[item] ?? []);
|
|
1195
|
+
if (names.length === 0) return void 0;
|
|
1196
|
+
clauses.push(`statusCategory in (${names.join(", ")})`);
|
|
1197
|
+
}
|
|
1198
|
+
for (const label of query.labels ?? []) clauses.push(`labels = ${quoted(label.replace(/\s+/g, "-"))}`);
|
|
1199
|
+
if (query.assignee !== void 0) clauses.push(query.assignee === null ? "assignee is EMPTY" : `assignee = ${quoted(query.assignee)}`);
|
|
1200
|
+
if (query.author !== void 0) clauses.push(`reporter = ${quoted(query.author)}`);
|
|
1201
|
+
if (query.search !== void 0) clauses.push(`text ~ ${quoted(query.search)}`);
|
|
1202
|
+
if (query.createdAfter !== void 0) clauses.push(`created >= ${quoted(jiraTime(query.createdAfter, "down"))}`);
|
|
1203
|
+
if (query.createdBefore !== void 0) clauses.push(`created <= ${quoted(jiraTime(query.createdBefore, "up"))}`);
|
|
1204
|
+
if (query.updatedAfter !== void 0) clauses.push(`updated >= ${quoted(jiraTime(query.updatedAfter, "down"))}`);
|
|
1205
|
+
if (query.updatedBefore !== void 0) clauses.push(`updated <= ${quoted(jiraTime(query.updatedBefore, "up"))}`);
|
|
1206
|
+
const sort = query.sort;
|
|
1207
|
+
const field = sort === void 0 ? "created" : sort.field;
|
|
1208
|
+
const direction = sort?.direction ?? "desc";
|
|
1209
|
+
return `${[`project = ${quoted(project)}`, ...clauses].join(" AND ")} ORDER BY ${JIRA_SORT_FIELDS[field]} ${direction.toUpperCase()}`;
|
|
1210
|
+
}
|
|
1211
|
+
function jiraTime(ms, outward) {
|
|
1212
|
+
const at = new Date(outward === "down" ? Math.floor(ms / 6e4) * 6e4 : Math.ceil(ms / 6e4) * 6e4);
|
|
1213
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1214
|
+
return `${String(at.getUTCFullYear())}/${pad(at.getUTCMonth() + 1)}/${pad(at.getUTCDate())} ${pad(at.getUTCHours())}:${pad(at.getUTCMinutes())}`;
|
|
527
1215
|
}
|
|
528
1216
|
function fieldsOf(patch) {
|
|
529
1217
|
return {
|
|
@@ -569,7 +1257,7 @@ function issueOf3(raw, site) {
|
|
|
569
1257
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
570
1258
|
};
|
|
571
1259
|
}
|
|
572
|
-
function
|
|
1260
|
+
function commentOf5(raw) {
|
|
573
1261
|
const item = isRecord(raw) ? raw : {};
|
|
574
1262
|
const author = isRecord(item.author) ? text(item.author.displayName) : void 0;
|
|
575
1263
|
const createdAt = toTimestamp(item.created);
|
|
@@ -578,6 +1266,14 @@ function commentOf3(raw) {
|
|
|
578
1266
|
|
|
579
1267
|
// src/tracker/local.lib.ts
|
|
580
1268
|
var STATUS_NAMES = { open: "Open", in_progress: "In progress", done: "Done", declined: "Declined" };
|
|
1269
|
+
var ISSUE_CAPABILITIES3 = {
|
|
1270
|
+
list: {
|
|
1271
|
+
filters: ["status", "labels", "assignee", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"],
|
|
1272
|
+
clientSide: ["labels", "assignee", "search", "createdAfter", "createdBefore", "updatedAfter", "updatedBefore"],
|
|
1273
|
+
sorts: []
|
|
1274
|
+
},
|
|
1275
|
+
comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc"] }
|
|
1276
|
+
};
|
|
581
1277
|
function localTracker() {
|
|
582
1278
|
return {
|
|
583
1279
|
descriptor: {
|
|
@@ -586,6 +1282,7 @@ function localTracker() {
|
|
|
586
1282
|
fields: [],
|
|
587
1283
|
features: { create: true, update: true, comment: true, priority: true, labels: true, assignee: true, statuses: [...ALL_STATUSES] }
|
|
588
1284
|
},
|
|
1285
|
+
issues: ISSUE_CAPABILITIES3,
|
|
589
1286
|
create: (context) => createLocalAdapter(context)
|
|
590
1287
|
};
|
|
591
1288
|
}
|
|
@@ -600,9 +1297,11 @@ function createLocalAdapter({ store, projectId, author }) {
|
|
|
600
1297
|
return row;
|
|
601
1298
|
};
|
|
602
1299
|
return {
|
|
603
|
-
list:
|
|
1300
|
+
// list и comments async: отказ проверки запроса — rejected promise, как у остальных адаптеров.
|
|
1301
|
+
list: async (query) => {
|
|
1302
|
+
assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 Local", ISSUE_CAPABILITIES3.list, query, ISSUE_FILTERS);
|
|
604
1303
|
const page = store.listLocalIssues(projectId, { limit: query.limit, ...query.cursor === void 0 ? {} : { cursor: query.cursor }, ...query.status === void 0 ? {} : { status: query.status } });
|
|
605
|
-
return
|
|
1304
|
+
return { items: page.items.filter((row) => matchesIssueQuery(row, query)).map(toIssue), ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor } };
|
|
606
1305
|
},
|
|
607
1306
|
get: (key) => Promise.resolve(toIssue(mustGet(key))),
|
|
608
1307
|
create: (input) => Promise.resolve(toIssue(store.createLocalIssue(projectId, input))),
|
|
@@ -611,13 +1310,22 @@ function createLocalAdapter({ store, projectId, author }) {
|
|
|
611
1310
|
if (row === void 0) throw new TrackerError("unknown_issue", `\u0412 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u043C \u0442\u0440\u0435\u043A\u0435\u0440\u0435 \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
|
|
612
1311
|
return Promise.resolve(toIssue(row));
|
|
613
1312
|
},
|
|
614
|
-
comments: (key, query) => {
|
|
1313
|
+
comments: async (key, query) => {
|
|
1314
|
+
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 Local", ISSUE_CAPABILITIES3.comments, query);
|
|
615
1315
|
const page = store.listLocalComments(projectId, mustGet(key).number, { limit: query.limit, ...query.cursor === void 0 ? {} : { cursor: query.cursor } });
|
|
616
|
-
|
|
1316
|
+
const items = query.updatedAfter === void 0 ? page.items : page.items.filter((row) => inTimeRange(row.createdAt, query.updatedAfter, void 0));
|
|
1317
|
+
return { items: items.map(toComment), ...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor } };
|
|
617
1318
|
},
|
|
618
1319
|
comment: (key, body) => Promise.resolve(toComment(store.addLocalComment(projectId, mustGet(key).number, body, author)))
|
|
619
1320
|
};
|
|
620
1321
|
}
|
|
1322
|
+
function matchesIssueQuery(row, query) {
|
|
1323
|
+
if (query.labels !== void 0 && !query.labels.every((label) => row.labels.includes(label))) return false;
|
|
1324
|
+
const assignee = query.assignee;
|
|
1325
|
+
if (assignee !== void 0 && (assignee === null ? row.assignee !== void 0 : row.assignee !== assignee)) return false;
|
|
1326
|
+
if (query.search !== void 0 && !containsText([row.title, row.description], query.search)) return false;
|
|
1327
|
+
return inTimeRange(row.createdAt, query.createdAfter, query.createdBefore) && inTimeRange(row.updatedAt, query.updatedAfter, query.updatedBefore);
|
|
1328
|
+
}
|
|
621
1329
|
function toIssue(row) {
|
|
622
1330
|
return {
|
|
623
1331
|
key: String(row.number),
|
|
@@ -636,11 +1344,222 @@ function toComment(row) {
|
|
|
636
1344
|
return { id: String(row.id), ...row.author === void 0 ? {} : { author: row.author }, body: row.body, createdAt: row.createdAt };
|
|
637
1345
|
}
|
|
638
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
|
+
|
|
639
1548
|
// src/tracker/sourcecraft.lib.ts
|
|
640
|
-
var
|
|
1549
|
+
var DEFAULT_API4 = "https://api.sourcecraft.tech";
|
|
641
1550
|
var DEFAULT_WEB = "https://sourcecraft.dev";
|
|
642
|
-
var
|
|
643
|
-
var
|
|
1551
|
+
var TARGET6 = /^[\w.-]+\/[\w.-]+$/;
|
|
1552
|
+
var SLUG2 = /^[\w-]{1,100}$/;
|
|
1553
|
+
var ISSUE_SORT_FIELDS_UPSTREAM = {
|
|
1554
|
+
created: "created_at",
|
|
1555
|
+
updated: "updated_at",
|
|
1556
|
+
priority: "priority",
|
|
1557
|
+
title: "title"
|
|
1558
|
+
};
|
|
1559
|
+
var ISSUE_CAPABILITIES4 = {
|
|
1560
|
+
list: { filters: ISSUE_FILTERS, clientSide: ["assignee"], sorts: ["created", "updated", "priority", "title"] },
|
|
1561
|
+
comments: { filters: COMMENT_FILTERS, clientSide: COMMENT_FILTERS, order: ["asc", "desc"] }
|
|
1562
|
+
};
|
|
644
1563
|
function sourcecraftTracker() {
|
|
645
1564
|
return {
|
|
646
1565
|
descriptor: {
|
|
@@ -653,47 +1572,59 @@ function sourcecraftTracker() {
|
|
|
653
1572
|
],
|
|
654
1573
|
features: { create: true, update: true, comment: true, priority: true, labels: false, assignee: false, statuses: [...ALL_STATUSES] }
|
|
655
1574
|
},
|
|
656
|
-
|
|
1575
|
+
issues: ISSUE_CAPABILITIES4,
|
|
1576
|
+
create: (context) => createSourceCraftAdapter(context),
|
|
1577
|
+
pulls: sourcecraftPulls()
|
|
657
1578
|
};
|
|
658
1579
|
}
|
|
659
1580
|
function createSourceCraftAdapter({ connection, fetch }) {
|
|
660
1581
|
const target = requireTarget(connection.target, "org/repo \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F SourceCraft");
|
|
661
|
-
if (!
|
|
662
|
-
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);
|
|
663
1584
|
const root = `${api}/repos/${target}/issues`;
|
|
664
1585
|
const headers = { authorization: `Bearer ${connection.token ?? ""}` };
|
|
665
1586
|
const web = connection.url === void 0 ? `${DEFAULT_WEB}/${target}/issues` : void 0;
|
|
666
1587
|
const slug = (key) => {
|
|
667
|
-
if (!
|
|
1588
|
+
if (!SLUG2.test(key)) throw new TrackerError("unknown_issue", `\u0412 SourceCraft \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 ${key}.`);
|
|
668
1589
|
return key;
|
|
669
1590
|
};
|
|
670
1591
|
const toIssue2 = (raw) => issueOf4(raw, web);
|
|
671
1592
|
return {
|
|
672
1593
|
list: async (query) => {
|
|
1594
|
+
assertListQuery("\u0417\u0430\u0434\u0430\u0447\u0438 SourceCraft", ISSUE_CAPABILITIES4.list, query, ISSUE_FILTERS);
|
|
673
1595
|
const params = new URLSearchParams({ page_size: String(query.limit) });
|
|
674
1596
|
if (query.cursor !== void 0) params.set("page_token", query.cursor);
|
|
1597
|
+
const filter = issueFilterOf(query);
|
|
1598
|
+
if (filter !== void 0) params.set("filter", filter);
|
|
1599
|
+
if (query.sort !== void 0) params.set("sort_by", `${query.sort.direction === "asc" ? "" : "-"}${issueSortFieldOf(query.sort.field)}`);
|
|
675
1600
|
const { body } = await requestJson(`${root}?${params.toString()}`, { fetch, headers });
|
|
676
1601
|
const record = isRecord(body) ? body : {};
|
|
677
|
-
|
|
678
|
-
|
|
1602
|
+
let issues = (Array.isArray(record.issues) ? record.issues : []).map(toIssue2);
|
|
1603
|
+
if (query.status !== void 0) issues = issues.filter((issue) => query.status?.includes(issue.status.category) === true);
|
|
1604
|
+
if (query.assignee === null) issues = issues.filter((issue) => issue.assignee === void 0);
|
|
1605
|
+
return { items: issues, ...nextOf3(record.next_page_token) };
|
|
679
1606
|
},
|
|
680
1607
|
get: async (key) => toIssue2((await requestJson(`${root}/${slug(key)}`, { fetch, headers, missingIsIssue: true })).body),
|
|
681
1608
|
create: async (input) => {
|
|
682
1609
|
const body = { title: input.title, ...input.description === void 0 ? {} : { description: input.description }, ...input.priority === void 0 ? {} : { priority: input.priority } };
|
|
683
1610
|
return toIssue2((await requestJson(root, { fetch, headers, method: "POST", body })).body);
|
|
684
1611
|
},
|
|
685
|
-
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),
|
|
686
1613
|
comments: async (key, query) => {
|
|
1614
|
+
assertCommentQuery("\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 SourceCraft", ISSUE_CAPABILITIES4.comments, query);
|
|
687
1615
|
const params = new URLSearchParams({ page_size: String(query.limit) });
|
|
688
1616
|
if (query.cursor !== void 0) params.set("page_token", query.cursor);
|
|
1617
|
+
if (query.order !== void 0) params.set("sort_by", query.order === "asc" ? "created_at" : "-created_at");
|
|
689
1618
|
const { body } = await requestJson(`${root}/${slug(key)}/comments?${params.toString()}`, { fetch, headers, missingIsIssue: true });
|
|
690
1619
|
const record = isRecord(body) ? body : {};
|
|
691
|
-
|
|
1620
|
+
const rawComments = Array.isArray(record.issue_comments) ? record.issue_comments : [];
|
|
1621
|
+
const filtered = query.updatedAfter === void 0 ? rawComments : rawComments.filter((raw) => inTimeRange(toTimestamp(isRecord(raw) ? raw.updated_at : void 0), query.updatedAfter, void 0));
|
|
1622
|
+
return { items: filtered.map(commentOf7), ...nextOf3(record.next_page_token) };
|
|
692
1623
|
},
|
|
693
|
-
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)
|
|
694
1625
|
};
|
|
695
1626
|
}
|
|
696
|
-
function
|
|
1627
|
+
function patchBody5(patch) {
|
|
697
1628
|
return {
|
|
698
1629
|
...patch.title === void 0 ? {} : { title: patch.title },
|
|
699
1630
|
...patch.description === void 0 ? {} : { description: patch.description },
|
|
@@ -701,10 +1632,31 @@ function patchBody3(patch) {
|
|
|
701
1632
|
...patch.priority === void 0 ? {} : { priority: patch.priority }
|
|
702
1633
|
};
|
|
703
1634
|
}
|
|
704
|
-
function
|
|
1635
|
+
function nextOf3(token) {
|
|
705
1636
|
const value = text(token);
|
|
706
1637
|
return value === void 0 ? {} : { nextCursor: value };
|
|
707
1638
|
}
|
|
1639
|
+
function issueSortFieldOf(field) {
|
|
1640
|
+
const upstream = ISSUE_SORT_FIELDS_UPSTREAM[field];
|
|
1641
|
+
if (upstream === void 0) throw new TrackerError("unsupported", `\u0417\u0430\u0434\u0430\u0447\u0438 SourceCraft: \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0430 \u043F\u043E ${field} \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F.`);
|
|
1642
|
+
return upstream;
|
|
1643
|
+
}
|
|
1644
|
+
function issueFilterOf(query) {
|
|
1645
|
+
const clauses = [];
|
|
1646
|
+
if (query.status !== void 0 && query.status.length > 0) {
|
|
1647
|
+
const slugs = [...new Set(query.status.flatMap((category) => SOURCECRAFT_STATUS_FILTER[category]))];
|
|
1648
|
+
clauses.push(`status in (${slugs.join(", ")})`);
|
|
1649
|
+
}
|
|
1650
|
+
for (const label of query.labels ?? []) clauses.push(`label_slug=${quoted(label)}`);
|
|
1651
|
+
if (typeof query.assignee === "string") clauses.push(`assignee_slug=${quoted(query.assignee)}`);
|
|
1652
|
+
if (query.author !== void 0) clauses.push(`author_slug=${quoted(query.author)}`);
|
|
1653
|
+
if (query.search !== void 0) clauses.push(`title=${quoted(query.search)}`);
|
|
1654
|
+
if (query.createdAfter !== void 0) clauses.push(`created_at>=${quoted(isoTime(query.createdAfter))}`);
|
|
1655
|
+
if (query.createdBefore !== void 0) clauses.push(`created_at<=${quoted(isoTime(query.createdBefore))}`);
|
|
1656
|
+
if (query.updatedAfter !== void 0) clauses.push(`updated_at>=${quoted(isoTime(query.updatedAfter))}`);
|
|
1657
|
+
if (query.updatedBefore !== void 0) clauses.push(`updated_at<=${quoted(isoTime(query.updatedBefore))}`);
|
|
1658
|
+
return clauses.length === 0 ? void 0 : clauses.join(" and ");
|
|
1659
|
+
}
|
|
708
1660
|
function issueOf4(raw, web) {
|
|
709
1661
|
const item = isRecord(raw) ? raw : {};
|
|
710
1662
|
const status = isRecord(item.status) ? item.status : {};
|
|
@@ -729,7 +1681,7 @@ function issueOf4(raw, web) {
|
|
|
729
1681
|
...updatedAt === void 0 ? {} : { updatedAt }
|
|
730
1682
|
};
|
|
731
1683
|
}
|
|
732
|
-
function
|
|
1684
|
+
function commentOf7(raw) {
|
|
733
1685
|
const item = isRecord(raw) ? raw : {};
|
|
734
1686
|
const author = isRecord(item.author) ? text(item.author.slug) : void 0;
|
|
735
1687
|
const createdAt = toTimestamp(item.created_at);
|
|
@@ -792,19 +1744,34 @@ function writeTrackerToken(dataDir, projectId, token, sink = defaultSecretSink)
|
|
|
792
1744
|
function deleteTrackerToken(dataDir, projectId, sink = defaultSecretSink) {
|
|
793
1745
|
return writeTrackerToken(dataDir, projectId, "", sink);
|
|
794
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
|
+
}
|
|
795
1753
|
export {
|
|
796
1754
|
ALL_STATUSES,
|
|
1755
|
+
COMMENT_FILTERS,
|
|
797
1756
|
GITHUB_CLOSE_REASONS,
|
|
798
1757
|
GITHUB_IN_PROGRESS_LABEL,
|
|
799
1758
|
GITHUB_PRIORITY_LABELS,
|
|
1759
|
+
GITLAB_DRAFT_PATTERN,
|
|
1760
|
+
GITLAB_DRAFT_PREFIX,
|
|
800
1761
|
GITLAB_IN_PROGRESS_LABEL,
|
|
1762
|
+
GITLAB_MERGE_REQUEST_STATES,
|
|
801
1763
|
GITLAB_PRIORITY_LABELS,
|
|
1764
|
+
ISSUE_FILTERS,
|
|
1765
|
+
ISSUE_SORT_FIELDS,
|
|
802
1766
|
JIRA_ISSUE_TYPE,
|
|
803
1767
|
JIRA_PRIORITIES,
|
|
804
1768
|
JIRA_PRIORITY_NAMES,
|
|
805
1769
|
JIRA_STATUS_CATEGORIES,
|
|
1770
|
+
LEGACY_ISSUE_CAPABILITIES,
|
|
806
1771
|
SOURCECRAFT_PRIORITIES,
|
|
1772
|
+
SOURCECRAFT_PULL_STATUSES,
|
|
807
1773
|
SOURCECRAFT_STATUSES,
|
|
1774
|
+
SOURCECRAFT_STATUS_FILTER,
|
|
808
1775
|
SOURCECRAFT_STATUS_SLUGS,
|
|
809
1776
|
TRACKER_ERROR_BODY_MAX,
|
|
810
1777
|
TRACKER_LOCAL_AUTHOR,
|
|
@@ -812,29 +1779,47 @@ export {
|
|
|
812
1779
|
TRACKER_SECRETS_FILE,
|
|
813
1780
|
TRACKER_TIMEOUT_MS,
|
|
814
1781
|
TrackerError,
|
|
1782
|
+
assertCommentQuery,
|
|
1783
|
+
assertListQuery,
|
|
815
1784
|
baseUrl,
|
|
816
1785
|
clipText,
|
|
1786
|
+
containsText,
|
|
817
1787
|
deleteTrackerToken,
|
|
818
1788
|
findTracker,
|
|
1789
|
+
githubPulls,
|
|
819
1790
|
githubTracker,
|
|
1791
|
+
gitlabPulls,
|
|
820
1792
|
gitlabTracker,
|
|
821
1793
|
hasTrackerToken,
|
|
1794
|
+
inTimeRange,
|
|
822
1795
|
isRecord,
|
|
1796
|
+
isoTime,
|
|
1797
|
+
issueCapabilitiesOf,
|
|
823
1798
|
jiraTracker,
|
|
1799
|
+
keysFor,
|
|
824
1800
|
listTrackers,
|
|
825
1801
|
localTracker,
|
|
826
1802
|
lookup,
|
|
1803
|
+
mergeMethodSchema,
|
|
827
1804
|
nameOf,
|
|
828
1805
|
priorityName,
|
|
1806
|
+
pullRequestAdapterOf,
|
|
1807
|
+
pullRequestFilterSchema,
|
|
1808
|
+
pullRequestSchema,
|
|
1809
|
+
pullRequestSortFieldSchema,
|
|
1810
|
+
pullRequestStatusCategorySchema,
|
|
1811
|
+
quoted,
|
|
829
1812
|
readTrackerToken,
|
|
830
1813
|
registerBuiltinTrackers,
|
|
831
1814
|
registerTracker,
|
|
832
1815
|
requestJson,
|
|
833
1816
|
requireTarget,
|
|
1817
|
+
sourcecraftPulls,
|
|
834
1818
|
sourcecraftTracker,
|
|
835
1819
|
stringList,
|
|
836
1820
|
text,
|
|
837
1821
|
toTimestamp,
|
|
1822
|
+
usedFilters,
|
|
838
1823
|
writeTrackerToken
|
|
839
1824
|
};
|
|
840
1825
|
//# sourceMappingURL=index.js.map
|