@remogram/provider-gitea-api 0.1.0-beta.2 → 0.1.0-beta.3

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.
Files changed (2) hide show
  1. package/index.js +116 -28
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -7,9 +7,20 @@ import {
7
7
  gitRevParse,
8
8
  gitCurrentBranch,
9
9
  gitAheadBehind,
10
+ refsInventory,
11
+ crInventory,
12
+ mergeBlockersFromFacts,
10
13
  ERROR_CODES,
11
14
  forgeError,
12
15
  forgeIngestCapabilityFacts,
16
+ checkPaginationCapabilityFacts,
17
+ DEFAULT_CHECK_STATUS_PAGE_SIZE,
18
+ MAX_CHECK_STATUS_PAGES,
19
+ paginateCheckStatusPages,
20
+ paginateOffsetListPages,
21
+ fetchWithIngestPageBackoff,
22
+ fetchPageWithIngestBackoff,
23
+ withPerPageParam,
13
24
  apiProviderCommands,
14
25
  } from '@remogram/core';
15
26
  const PUBLIC_GITEA_HOST = 'gitea.com';
@@ -17,6 +28,8 @@ const PUBLIC_GITEA_API = 'https://gitea.com/api/v1';
17
28
  const AUTH_CAPABILITIES = [
18
29
  'repo_status',
19
30
  'ref_compare',
31
+ 'ref_inventory',
32
+ 'cr_inventory',
20
33
  'pr_status',
21
34
  'pr_checks',
22
35
  'merge_plan',
@@ -119,23 +132,21 @@ export async function giteaFetch(config, parsed, path, options = {}) {
119
132
  });
120
133
  }
121
134
 
122
- const MAX_CHECK_PAGES = 50;
135
+ const MAX_CHECK_PAGES = MAX_CHECK_STATUS_PAGES;
123
136
  const GITEA_PAGE_SIZE = 100;
124
137
 
125
138
  export async function giteaFetchPaginated(config, parsed, path) {
126
- const all = [];
127
- for (let page = 1; page <= MAX_CHECK_PAGES; page += 1) {
128
- const separator = path.includes('?') ? '&' : '?';
129
- const body = await giteaFetch(
130
- config,
131
- parsed,
132
- `${path}${separator}limit=${GITEA_PAGE_SIZE}&page=${page}`,
133
- );
134
- const items = Array.isArray(body) ? body : [];
135
- all.push(...items);
136
- if (items.length < GITEA_PAGE_SIZE) break;
137
- }
138
- return all;
139
+ return paginateCheckStatusPages({
140
+ fetchPage: async ({ page, limit }) => {
141
+ const separator = path.includes('?') ? '&' : '?';
142
+ const body = await giteaFetch(
143
+ config,
144
+ parsed,
145
+ `${path}${separator}limit=${limit}&page=${page}`,
146
+ );
147
+ return Array.isArray(body) ? body : [];
148
+ },
149
+ });
139
150
  }
140
151
 
141
152
  export function normalizeGiteaStatusState(state) {
@@ -150,6 +161,36 @@ export function normalizeGiteaStatusState(state) {
150
161
  return 'unknown';
151
162
  }
152
163
 
164
+ function giteaStatusRecordOrder(a, b) {
165
+ const aUpdated = Date.parse(a.updated_at ?? '') || 0;
166
+ const bUpdated = Date.parse(b.updated_at ?? '') || 0;
167
+ if (aUpdated !== bUpdated) return aUpdated - bUpdated;
168
+ const aId = Number(a.id) || 0;
169
+ const bId = Number(b.id) || 0;
170
+ return aId - bId;
171
+ }
172
+
173
+ export function dedupeGiteaStatusRecords(records) {
174
+ const latestByContext = new Map();
175
+ for (const record of records) {
176
+ const context = record?.context;
177
+ if (context == null || context === '') continue;
178
+ const existing = latestByContext.get(context);
179
+ if (!existing || giteaStatusRecordOrder(record, existing) > 0) {
180
+ latestByContext.set(context, record);
181
+ }
182
+ }
183
+ return Array.from(latestByContext.values());
184
+ }
185
+
186
+ export function mapGiteaCommitStatuses(records) {
187
+ return dedupeGiteaStatusRecords(records).map((s) => ({
188
+ context: sanitizeField(s.context),
189
+ state: normalizeGiteaStatusState(s.status ?? s.state),
190
+ description: sanitizeField(s.description),
191
+ }));
192
+ }
193
+
153
194
  export function normalizeGiteaPrState(state) {
154
195
  const normalized = String(state ?? '').toLowerCase();
155
196
  if (normalized === 'open') return 'open';
@@ -173,15 +214,21 @@ export async function repoStatus(ctx) {
173
214
  }
174
215
 
175
216
  export function providerCapabilities() {
217
+ const check_sources = ['commit_statuses'];
176
218
  return {
177
219
  commands: STRUCTURED_COMMANDS,
178
220
  auth_envs: ['GITEA_TOKEN'],
179
- check_sources: ['commit_statuses'],
221
+ check_sources,
180
222
  mergeability_confidence: 'direct',
181
223
  host_binding: 'verified_remote_host',
182
224
  pagination: 'supported',
183
225
  write_support: false,
184
226
  ...forgeIngestCapabilityFacts(),
227
+ ...checkPaginationCapabilityFacts({
228
+ strategy: 'offset_limit',
229
+ pageSizeParam: 'limit',
230
+ sourceCount: check_sources.length,
231
+ }),
185
232
  };
186
233
  }
187
234
 
@@ -214,6 +261,48 @@ export async function getPull(ctx, { number }) {
214
261
  return giteaFetch(ctx.config, ctx.parsed, repoApiPath(ctx.config, 'pulls', number));
215
262
  }
216
263
 
264
+ export async function listOpenPullsWithMeta(ctx, opts = {}) {
265
+ requireToken();
266
+ const listLimit =
267
+ opts.limit != null && Number.isInteger(Number(opts.limit)) && Number(opts.limit) > 0
268
+ ? Number(opts.limit)
269
+ : null;
270
+ const pageSize =
271
+ listLimit != null ? Math.min(listLimit, GITEA_PAGE_SIZE) : GITEA_PAGE_SIZE;
272
+ const path = `${repoApiPath(ctx.config, 'pulls')}?state=open`;
273
+ const pageSep = path.includes('?') ? '&' : '?';
274
+ const { items: all, list_truncated: listTruncated } = await paginateOffsetListPages({
275
+ pageSize,
276
+ listLimit,
277
+ ...(listLimit != null && pageSize < listLimit ? { maxPagesTruncatesWithLimit: true } : {}),
278
+ fetchPage: async ({ page, limit }) => {
279
+ const body = await giteaFetch(
280
+ ctx.config,
281
+ ctx.parsed,
282
+ `${path}${pageSep}limit=${limit}&page=${page}`,
283
+ );
284
+ return Array.isArray(body) ? body : [];
285
+ },
286
+ });
287
+ let numbers = all
288
+ .map((pr) => pr.number)
289
+ .filter((number) => Number.isInteger(number))
290
+ .sort((a, b) => a - b);
291
+ if (listLimit != null && numbers.length > listLimit) {
292
+ numbers = numbers.slice(0, listLimit);
293
+ }
294
+ return { numbers, list_truncated: listTruncated };
295
+ }
296
+
297
+ export async function listOpenPulls(ctx, opts = {}) {
298
+ const meta = await listOpenPullsWithMeta(ctx, opts);
299
+ return meta.numbers;
300
+ }
301
+
302
+ export async function crInventorySlice(ctx, opts = {}) {
303
+ return crInventory(ctx, { listOpenPulls, listOpenPullsWithMeta, prView, prChecks }, opts);
304
+ }
305
+
217
306
  function mergeability(pr) {
218
307
  if (pr.mergeable === true) return 'clean';
219
308
  if (pr.mergeable === false) return 'conflicted';
@@ -255,18 +344,19 @@ export async function prChecks(ctx, opts) {
255
344
  forgeError: forgeError(ERROR_CODES.MISSING_REF, 'Could not determine head SHA for checks'),
256
345
  });
257
346
  }
258
- const statusRecords = await giteaFetchPaginated(
347
+ const { items: statusRecords, truncated: checks_truncated } = await giteaFetchPaginated(
259
348
  ctx.config,
260
349
  ctx.parsed,
261
350
  repoApiPath(ctx.config, 'commits', sha, 'statuses'),
262
351
  );
263
- const mapped = statusRecords.map((s) => ({
264
- context: sanitizeField(s.context),
265
- state: normalizeGiteaStatusState(s.state),
266
- description: sanitizeField(s.description),
267
- }));
352
+ const mapped = mapGiteaCommitStatuses(statusRecords);
268
353
  const conclusion = summarizeChecks(mapped);
269
- return { head_sha: sha, check_conclusion: conclusion, statuses: mapped };
354
+ return {
355
+ head_sha: sha,
356
+ check_conclusion: conclusion,
357
+ checks_truncated,
358
+ statuses: mapped,
359
+ };
270
360
  }
271
361
 
272
362
  export function summarizeChecks(statuses) {
@@ -280,12 +370,7 @@ export function summarizeChecks(statuses) {
280
370
  export async function mergePlan(ctx, opts) {
281
371
  const view = await prView(ctx, opts);
282
372
  const checks = await prChecks(ctx, { number: view.pr_number });
283
- const blockers = [];
284
- if (view.mergeability === 'conflicted') blockers.push('merge_conflict');
285
- if (view.state !== 'open') blockers.push('pr_not_open');
286
- if (checks.check_conclusion === 'failure') blockers.push('checks_failed');
287
- if (checks.check_conclusion === 'missing') blockers.push('checks_missing');
288
- if (checks.check_conclusion === 'pending') blockers.push('checks_pending');
373
+ const blockers = mergeBlockersFromFacts(view, checks);
289
374
  return {
290
375
  pr_number: view.pr_number,
291
376
  mergeability: view.mergeability,
@@ -326,6 +411,9 @@ export const provider = {
326
411
  providerCapabilities,
327
412
  repoStatus,
328
413
  refsCompare,
414
+ refsInventory,
415
+ listOpenPulls,
416
+ crInventory: crInventorySlice,
329
417
  prView,
330
418
  prChecks,
331
419
  mergePlan,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remogram/provider-gitea-api",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.3",
4
4
  "description": "Gitea REST API forge 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.2"
25
+ "@remogram/core": "0.1.0-beta.3"
26
26
  }
27
27
  }