@remogram/provider-gitlab-api 0.1.0-beta.2 → 0.1.0-beta.4
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/index.js +232 -27
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fetchJson,
|
|
3
|
+
fetchJsonWithMeta,
|
|
3
4
|
sanitizeField,
|
|
4
5
|
sanitizeUrl,
|
|
5
6
|
assertGitRef,
|
|
@@ -7,10 +8,35 @@ import {
|
|
|
7
8
|
gitRevParse,
|
|
8
9
|
gitCurrentBranch,
|
|
9
10
|
gitAheadBehind,
|
|
11
|
+
refsInventory,
|
|
12
|
+
crInventory,
|
|
13
|
+
buildMergePlanBodyFromFacts,
|
|
10
14
|
ERROR_CODES,
|
|
11
15
|
forgeError,
|
|
12
16
|
forgeIngestCapabilityFacts,
|
|
17
|
+
checkPaginationCapabilityFacts,
|
|
18
|
+
openPullListCapabilityFacts,
|
|
19
|
+
DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
20
|
+
MAX_CHECK_STATUS_PAGES,
|
|
21
|
+
DEFAULT_OPEN_PULL_LIST_PAGE_SIZE,
|
|
22
|
+
paginateCheckStatusPages,
|
|
23
|
+
paginateOffsetListPages,
|
|
24
|
+
fetchWithIngestPageBackoff,
|
|
25
|
+
fetchPageWithIngestBackoff,
|
|
26
|
+
withPerPageParam,
|
|
13
27
|
apiProviderCommands,
|
|
28
|
+
normalizeCrInventorySort,
|
|
29
|
+
DEFAULT_CR_INVENTORY_SLICE_SORT,
|
|
30
|
+
parseTotalCountHeader,
|
|
31
|
+
isCrInventoryFastPathEligible,
|
|
32
|
+
validateFastPathPageLength,
|
|
33
|
+
isNumberSortFastPathEligible,
|
|
34
|
+
isNumberSortFullCollectRequired,
|
|
35
|
+
resolveListTruncatedWithTrustedTotal,
|
|
36
|
+
orderOpenPullNumbers,
|
|
37
|
+
buildOpenPullListMeta,
|
|
38
|
+
gitlabOpenPullSortQuery,
|
|
39
|
+
appendSortQuery,
|
|
14
40
|
} from '@remogram/core';
|
|
15
41
|
|
|
16
42
|
const PUBLIC_GITLAB_HOST = 'gitlab.com';
|
|
@@ -18,6 +44,8 @@ const PUBLIC_GITLAB_API = 'https://gitlab.com/api/v4';
|
|
|
18
44
|
const AUTH_CAPABILITIES = [
|
|
19
45
|
'repo_status',
|
|
20
46
|
'ref_compare',
|
|
47
|
+
'ref_inventory',
|
|
48
|
+
'cr_inventory',
|
|
21
49
|
'pr_status',
|
|
22
50
|
'pr_checks',
|
|
23
51
|
'merge_plan',
|
|
@@ -116,35 +144,53 @@ export async function gitlabFetch(config, parsed, path, options = {}) {
|
|
|
116
144
|
});
|
|
117
145
|
}
|
|
118
146
|
|
|
119
|
-
|
|
147
|
+
export async function gitlabFetchWithMeta(config, parsed, path, options = {}) {
|
|
148
|
+
const base = apiBase(config, parsed);
|
|
149
|
+
const token = requireToken();
|
|
150
|
+
const url = `${base}${path}`;
|
|
151
|
+
return fetchJsonWithMeta(url, {
|
|
152
|
+
...options,
|
|
153
|
+
headers: { ...authHeaders(token), ...(options.headers || {}) },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const MAX_CHECK_PAGES = MAX_CHECK_STATUS_PAGES;
|
|
120
158
|
const GITLAB_PAGE_SIZE = 100;
|
|
121
159
|
|
|
122
160
|
export async function gitlabFetchPaginated(config, parsed, path) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
return all;
|
|
161
|
+
return paginateCheckStatusPages({
|
|
162
|
+
fetchPage: async ({ page, limit }) => {
|
|
163
|
+
const separator = path.includes('?') ? '&' : '?';
|
|
164
|
+
const body = await gitlabFetch(
|
|
165
|
+
config,
|
|
166
|
+
parsed,
|
|
167
|
+
`${path}${separator}per_page=${limit}&page=${page}`,
|
|
168
|
+
);
|
|
169
|
+
return Array.isArray(body) ? body : [];
|
|
170
|
+
},
|
|
171
|
+
});
|
|
136
172
|
}
|
|
137
173
|
|
|
138
174
|
export function providerCapabilities() {
|
|
175
|
+
const check_sources = ['commit_statuses', 'pipelines'];
|
|
139
176
|
return {
|
|
140
177
|
commands: STRUCTURED_COMMANDS,
|
|
141
178
|
auth_envs: ['GITLAB_TOKEN'],
|
|
142
|
-
check_sources
|
|
179
|
+
check_sources,
|
|
143
180
|
mergeability_confidence: 'derived',
|
|
144
181
|
host_binding: 'verified_remote_host',
|
|
145
182
|
pagination: 'supported',
|
|
146
183
|
write_support: false,
|
|
147
184
|
...forgeIngestCapabilityFacts(),
|
|
185
|
+
...checkPaginationCapabilityFacts({
|
|
186
|
+
strategy: 'offset_limit',
|
|
187
|
+
pageSizeParam: 'per_page',
|
|
188
|
+
sourceCount: check_sources.length,
|
|
189
|
+
}),
|
|
190
|
+
...openPullListCapabilityFacts({
|
|
191
|
+
totalCountSource: 'response_header',
|
|
192
|
+
totalCountHeader: 'X-Total',
|
|
193
|
+
}),
|
|
148
194
|
};
|
|
149
195
|
}
|
|
150
196
|
|
|
@@ -260,7 +306,7 @@ export async function prChecks(ctx, opts) {
|
|
|
260
306
|
});
|
|
261
307
|
}
|
|
262
308
|
|
|
263
|
-
const [
|
|
309
|
+
const [statusResult, pipelineResult] = await Promise.all([
|
|
264
310
|
gitlabFetchPaginated(
|
|
265
311
|
ctx.config,
|
|
266
312
|
ctx.parsed,
|
|
@@ -272,6 +318,8 @@ export async function prChecks(ctx, opts) {
|
|
|
272
318
|
`${projectApiPath(ctx.config, 'pipelines')}?sha=${encodeURIComponent(sha)}`,
|
|
273
319
|
),
|
|
274
320
|
]);
|
|
321
|
+
const statusRecords = statusResult.items;
|
|
322
|
+
const pipelineRecords = pipelineResult.items;
|
|
275
323
|
const mappedStatuses = statusRecords.map((status) => ({
|
|
276
324
|
context: sanitizeField(status.name || status.context),
|
|
277
325
|
state: normalizeStatusState(status.status),
|
|
@@ -283,26 +331,180 @@ export async function prChecks(ctx, opts) {
|
|
|
283
331
|
description: sanitizeField(pipeline.status),
|
|
284
332
|
}));
|
|
285
333
|
const mapped = [...mappedStatuses, ...mappedPipelines];
|
|
286
|
-
|
|
334
|
+
const checks_truncated = statusResult.truncated || pipelineResult.truncated;
|
|
335
|
+
return {
|
|
336
|
+
head_sha: sha,
|
|
337
|
+
check_conclusion: summarizeChecks(mapped),
|
|
338
|
+
checks_truncated,
|
|
339
|
+
statuses: mapped,
|
|
340
|
+
};
|
|
287
341
|
}
|
|
288
342
|
|
|
289
343
|
export async function mergePlan(ctx, opts) {
|
|
290
344
|
const view = await prView(ctx, opts);
|
|
291
345
|
const checks = await prChecks(ctx, { number: view.pr_number });
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
346
|
+
return buildMergePlanBodyFromFacts(ctx, view, checks, opts);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const GITLAB_OPEN_PULL_COMPLIANT_MAX =
|
|
350
|
+
DEFAULT_OPEN_PULL_LIST_PAGE_SIZE * MAX_CHECK_STATUS_PAGES;
|
|
351
|
+
|
|
352
|
+
async function probeGitlabOpenPullPageOne(ctx, retainMax, sliceSort) {
|
|
353
|
+
const maxTrusted = GITLAB_OPEN_PULL_COMPLIANT_MAX * 2;
|
|
354
|
+
let path = `${projectApiPath(ctx.config, 'merge_requests')}?state=opened`;
|
|
355
|
+
path = appendSortQuery(path, gitlabOpenPullSortQuery(sliceSort));
|
|
356
|
+
const separator = path.includes('?') ? '&' : '?';
|
|
357
|
+
const requestLimit = Math.min(retainMax, GITLAB_PAGE_SIZE);
|
|
358
|
+
try {
|
|
359
|
+
const { body, headers } = await gitlabFetchWithMeta(
|
|
360
|
+
ctx.config,
|
|
361
|
+
ctx.parsed,
|
|
362
|
+
`${path}${separator}per_page=${requestLimit}&page=1`,
|
|
363
|
+
);
|
|
364
|
+
if (!Array.isArray(body)) return null;
|
|
365
|
+
const totalCount = parseTotalCountHeader(headers, 'X-Total', { maxTrusted });
|
|
366
|
+
if (totalCount == null) return null;
|
|
367
|
+
const listTruncated = totalCount > GITLAB_OPEN_PULL_COMPLIANT_MAX;
|
|
368
|
+
return { body, totalCount, listTruncated, requestLimit };
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function buildGitlabOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, listTruncated) {
|
|
375
|
+
let numbers = orderOpenPullNumbers(body, (mr) => mr?.iid, sliceSort);
|
|
376
|
+
if (numbers.length > retainMax) numbers = numbers.slice(0, retainMax);
|
|
377
|
+
return buildOpenPullListMeta({
|
|
378
|
+
totalCount,
|
|
379
|
+
numbers,
|
|
380
|
+
listTruncated,
|
|
381
|
+
sliceSort,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function gitlabProbePaginationOpts(probe, extra = {}) {
|
|
386
|
+
const { body, totalCount, requestLimit } = probe;
|
|
298
387
|
return {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
blockers,
|
|
388
|
+
trustedTotalCount: totalCount,
|
|
389
|
+
seededFirstPage: { items: body, usedLimit: requestLimit },
|
|
390
|
+
...extra,
|
|
303
391
|
};
|
|
304
392
|
}
|
|
305
393
|
|
|
394
|
+
async function paginateGitlabOpenPullList(ctx, opts, sliceSort, paginationOpts = {}) {
|
|
395
|
+
const {
|
|
396
|
+
trustedTotalCount = null,
|
|
397
|
+
numberSortFullCollect = false,
|
|
398
|
+
seededFirstPage = null,
|
|
399
|
+
startPage = 1,
|
|
400
|
+
maxPages = MAX_CHECK_STATUS_PAGES,
|
|
401
|
+
suppressFinalPageProbe = false,
|
|
402
|
+
} = paginationOpts;
|
|
403
|
+
const listLimit =
|
|
404
|
+
opts.limit != null && Number.isInteger(Number(opts.limit)) && Number(opts.limit) > 0
|
|
405
|
+
? Number(opts.limit)
|
|
406
|
+
: null;
|
|
407
|
+
const retainMax =
|
|
408
|
+
listLimit == null &&
|
|
409
|
+
opts.retain_max != null &&
|
|
410
|
+
Number.isInteger(Number(opts.retain_max)) &&
|
|
411
|
+
Number(opts.retain_max) > 0
|
|
412
|
+
? Number(opts.retain_max)
|
|
413
|
+
: null;
|
|
414
|
+
let path = `${projectApiPath(ctx.config, 'merge_requests')}?state=opened`;
|
|
415
|
+
path = appendSortQuery(path, gitlabOpenPullSortQuery(sliceSort));
|
|
416
|
+
const separator = path.includes('?') ? '&' : '?';
|
|
417
|
+
const effectiveRetainMax = numberSortFullCollect ? null : retainMax;
|
|
418
|
+
const {
|
|
419
|
+
items: all,
|
|
420
|
+
list_truncated: listTruncated,
|
|
421
|
+
entry_count: entryCount,
|
|
422
|
+
walked_count: walkedCount,
|
|
423
|
+
} = await paginateOffsetListPages({
|
|
424
|
+
pageSize: GITLAB_PAGE_SIZE,
|
|
425
|
+
listLimit,
|
|
426
|
+
retainMax: effectiveRetainMax,
|
|
427
|
+
trustedEntryCount: trustedTotalCount,
|
|
428
|
+
seededFirstPage,
|
|
429
|
+
startPage,
|
|
430
|
+
maxPages,
|
|
431
|
+
suppressFinalPageProbe,
|
|
432
|
+
...(listLimit != null ? { maxPagesTruncatesWithLimit: true } : {}),
|
|
433
|
+
fetchPage: async ({ page, limit }) => {
|
|
434
|
+
const body = await gitlabFetch(
|
|
435
|
+
ctx.config,
|
|
436
|
+
ctx.parsed,
|
|
437
|
+
`${path}${separator}per_page=${limit}&page=${page}`,
|
|
438
|
+
);
|
|
439
|
+
return Array.isArray(body) ? body : [];
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
let numbers = orderOpenPullNumbers(all, (mr) => mr?.iid, sliceSort);
|
|
443
|
+
const outputCap = listLimit ?? retainMax;
|
|
444
|
+
if (outputCap != null && numbers.length > outputCap) {
|
|
445
|
+
numbers = numbers.slice(0, outputCap);
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
numbers,
|
|
449
|
+
list_truncated: resolveListTruncatedWithTrustedTotal({
|
|
450
|
+
listTruncated,
|
|
451
|
+
trustedTotalCount,
|
|
452
|
+
walkedCount,
|
|
453
|
+
fullCollect: numberSortFullCollect,
|
|
454
|
+
}),
|
|
455
|
+
...(entryCount != null ? { entry_count: entryCount } : {}),
|
|
456
|
+
slice_sort: sliceSort,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export async function listOpenPullsWithMeta(ctx, opts = {}) {
|
|
461
|
+
apiBase(ctx.config, ctx.parsed);
|
|
462
|
+
requireToken();
|
|
463
|
+
const sliceSort = normalizeCrInventorySort(opts.sort ?? DEFAULT_CR_INVENTORY_SLICE_SORT);
|
|
464
|
+
if (!isCrInventoryFastPathEligible(opts)) {
|
|
465
|
+
return paginateGitlabOpenPullList(ctx, opts, sliceSort);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const retainMax = Number(opts.retain_max);
|
|
469
|
+
const probe = await probeGitlabOpenPullPageOne(ctx, retainMax, sliceSort);
|
|
470
|
+
if (!probe) {
|
|
471
|
+
return paginateGitlabOpenPullList(ctx, opts, sliceSort);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const { body, totalCount, listTruncated, requestLimit } = probe;
|
|
475
|
+
|
|
476
|
+
if (listTruncated) {
|
|
477
|
+
if (body.length === 0) {
|
|
478
|
+
return paginateGitlabOpenPullList(ctx, opts, sliceSort, gitlabProbePaginationOpts(probe));
|
|
479
|
+
}
|
|
480
|
+
return buildGitlabOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, true);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (
|
|
484
|
+
isNumberSortFastPathEligible(totalCount, retainMax, sliceSort) &&
|
|
485
|
+
validateFastPathPageLength(totalCount, requestLimit, body.length)
|
|
486
|
+
) {
|
|
487
|
+
return buildGitlabOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, false);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const numberSortFullCollect = isNumberSortFullCollectRequired(totalCount, retainMax, sliceSort);
|
|
491
|
+
return paginateGitlabOpenPullList(
|
|
492
|
+
ctx,
|
|
493
|
+
opts,
|
|
494
|
+
sliceSort,
|
|
495
|
+
gitlabProbePaginationOpts(probe, { numberSortFullCollect }),
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export async function listOpenPulls(ctx, opts = {}) {
|
|
500
|
+
const meta = await listOpenPullsWithMeta(ctx, opts);
|
|
501
|
+
return meta.numbers;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export async function crInventorySlice(ctx, opts = {}) {
|
|
505
|
+
return crInventory(ctx, { listOpenPulls, listOpenPullsWithMeta, prView, prChecks }, opts);
|
|
506
|
+
}
|
|
507
|
+
|
|
306
508
|
export async function syncPlan(ctx, remoteName = 'origin') {
|
|
307
509
|
assertGitRemote(remoteName, 'remote');
|
|
308
510
|
apiBase(ctx.config, ctx.parsed);
|
|
@@ -336,6 +538,9 @@ export const provider = {
|
|
|
336
538
|
providerCapabilities,
|
|
337
539
|
repoStatus,
|
|
338
540
|
refsCompare,
|
|
541
|
+
refsInventory,
|
|
542
|
+
listOpenPulls,
|
|
543
|
+
crInventory: crInventorySlice,
|
|
339
544
|
prView,
|
|
340
545
|
prChecks,
|
|
341
546
|
mergePlan,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remogram/provider-gitlab-api",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "GitLab API provider for remogram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@remogram/core": "0.1.0-beta.
|
|
25
|
+
"@remogram/core": "0.1.0-beta.4"
|
|
26
26
|
}
|
|
27
27
|
}
|