@remogram/provider-gitea-api 0.1.0-beta.6 → 0.1.0-beta.8
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 +137 -14
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
parseSinceObservedAt,
|
|
28
28
|
ERROR_CODES,
|
|
29
29
|
forgeError,
|
|
30
|
+
assertExpectedSha,
|
|
30
31
|
forgeIngestCapabilityFacts,
|
|
31
32
|
checkPaginationCapabilityFacts,
|
|
32
33
|
idempotencyScanCapabilityFacts,
|
|
@@ -85,6 +86,7 @@ const STRUCTURED_COMMANDS = apiProviderCommands({
|
|
|
85
86
|
crFilesImplemented: true,
|
|
86
87
|
crCommentsImplemented: true,
|
|
87
88
|
forgeChangesImplemented: true,
|
|
89
|
+
mergeExecuteImplemented: true,
|
|
88
90
|
});
|
|
89
91
|
|
|
90
92
|
export function giteaToken() {
|
|
@@ -165,13 +167,36 @@ export function authHeaders(token) {
|
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
export function repoApiPath(config, ...segments) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
return repoApiPathFor(config.owner, config.repo, ...segments);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function repoApiPathFor(owner, repo, ...segments) {
|
|
174
|
+
const encodedOwner = encodeURIComponent(owner);
|
|
175
|
+
const encodedRepo = encodeURIComponent(repo);
|
|
176
|
+
const base = `/repos/${encodedOwner}/${encodedRepo}`;
|
|
171
177
|
if (!segments.length) return base;
|
|
172
178
|
return `${base}/${segments.map((s) => encodeURIComponent(String(s))).join('/')}`;
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
export function forgeSourceRepoIdFromPull(config, pr) {
|
|
182
|
+
const headOwner = sanitizeField(pr.head?.repo?.owner?.login ?? pr.head?.repo?.owner?.name);
|
|
183
|
+
const headRepo = sanitizeField(pr.head?.repo?.name);
|
|
184
|
+
if (!headOwner || !headRepo) return null;
|
|
185
|
+
const configOwner = String(config.owner ?? '').toLowerCase();
|
|
186
|
+
const configRepo = String(config.repo ?? '').toLowerCase();
|
|
187
|
+
if (headOwner.toLowerCase() === configOwner && headRepo.toLowerCase() === configRepo) return null;
|
|
188
|
+
return `${headOwner}/${headRepo}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function isGiteaHeadOutOfDate409(err) {
|
|
192
|
+
const status = err.status ?? err.forgeError?.status ?? null;
|
|
193
|
+
if (status !== 409) return false;
|
|
194
|
+
const message = err.forgeError?.message ?? err.message ?? '';
|
|
195
|
+
if (/head out of date/i.test(message)) return true;
|
|
196
|
+
if (/sha mismatch/i.test(message)) return true;
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
175
200
|
export async function giteaFetch(config, parsed, path, options = {}) {
|
|
176
201
|
const token = requireToken();
|
|
177
202
|
const url = `${apiBase(config, parsed)}${path}`;
|
|
@@ -302,6 +327,47 @@ export async function branchProtection(ctx, { branchRef }) {
|
|
|
302
327
|
return buildBranchProtectionFromGiteaProtection(branchRef, protection);
|
|
303
328
|
}
|
|
304
329
|
|
|
330
|
+
export async function branchHeadSha(ctx, branchRef, { repoId } = {}) {
|
|
331
|
+
requireToken();
|
|
332
|
+
assertGitRef(branchRef, 'head_ref');
|
|
333
|
+
let owner = ctx.config.owner;
|
|
334
|
+
let repo = ctx.config.repo;
|
|
335
|
+
if (repoId) {
|
|
336
|
+
const parts = String(repoId).split('/');
|
|
337
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
338
|
+
throw Object.assign(new Error('Invalid repoId'), {
|
|
339
|
+
forgeError: forgeError(ERROR_CODES.INVALID_ARGS, 'repoId must be owner/repo'),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
owner = parts[0];
|
|
343
|
+
repo = parts[1];
|
|
344
|
+
}
|
|
345
|
+
const branch = await giteaFetch(
|
|
346
|
+
ctx.config,
|
|
347
|
+
ctx.parsed,
|
|
348
|
+
repoApiPathFor(owner, repo, 'branches', branchRef),
|
|
349
|
+
);
|
|
350
|
+
const rawSha = sanitizeField(branch?.commit?.id);
|
|
351
|
+
if (!rawSha) {
|
|
352
|
+
throw Object.assign(new Error('Branch commit id missing'), {
|
|
353
|
+
forgeError: forgeError(
|
|
354
|
+
ERROR_CODES.UNPARSEABLE_PROVIDER_OUTPUT,
|
|
355
|
+
'Gitea branch response missing commit id',
|
|
356
|
+
),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
return assertExpectedSha(rawSha, 'branch commit id');
|
|
361
|
+
} catch (err) {
|
|
362
|
+
throw Object.assign(new Error('Branch commit id invalid'), {
|
|
363
|
+
forgeError: forgeError(
|
|
364
|
+
ERROR_CODES.UNPARSEABLE_PROVIDER_OUTPUT,
|
|
365
|
+
sanitizeField(err.invalidArgs) || 'Gitea branch response commit id is not a valid SHA',
|
|
366
|
+
),
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
305
371
|
export async function crFiles(ctx, { number }) {
|
|
306
372
|
requireToken();
|
|
307
373
|
if (number == null) {
|
|
@@ -485,7 +551,7 @@ export function providerCapabilities() {
|
|
|
485
551
|
host_binding: 'verified_remote_host',
|
|
486
552
|
pagination: 'supported',
|
|
487
553
|
write_support: true,
|
|
488
|
-
write_commands: ['cr_open', 'status_set'],
|
|
554
|
+
write_commands: ['cr_open', 'status_set', 'merge'],
|
|
489
555
|
...forgeIngestCapabilityFacts(),
|
|
490
556
|
...checkPaginationCapabilityFacts({
|
|
491
557
|
strategy: 'offset_limit',
|
|
@@ -520,10 +586,10 @@ export async function refsCompare(ctx, baseRef, headRef) {
|
|
|
520
586
|
}
|
|
521
587
|
const counts = gitAheadBehind(ctx.cwd, baseSha, headSha);
|
|
522
588
|
return {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
589
|
+
compare_base_ref: sanitizeField(baseRef),
|
|
590
|
+
compare_base_sha: baseSha,
|
|
591
|
+
compare_head_ref: sanitizeField(headRef),
|
|
592
|
+
compare_head_sha: headSha,
|
|
527
593
|
...counts,
|
|
528
594
|
};
|
|
529
595
|
}
|
|
@@ -706,6 +772,58 @@ export async function crOpen(ctx, { head, base, title, body: prBody }) {
|
|
|
706
772
|
return buildChangeRequestOpenedBody(pull, { head, base, title });
|
|
707
773
|
}
|
|
708
774
|
|
|
775
|
+
export async function mergeExecute(ctx, { number, method = 'merge', expectedHeadSha }) {
|
|
776
|
+
assertWriteCommandConfigured(ctx.config, 'merge');
|
|
777
|
+
if (method !== 'merge') {
|
|
778
|
+
throw Object.assign(new Error('Unsupported merge method'), {
|
|
779
|
+
forgeError: forgeError(
|
|
780
|
+
ERROR_CODES.INVALID_ARGS,
|
|
781
|
+
'Only --method merge is supported in v1',
|
|
782
|
+
),
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const pullIndex = Number(number);
|
|
786
|
+
if (!Number.isInteger(pullIndex) || pullIndex <= 0) {
|
|
787
|
+
throw Object.assign(new Error('Invalid PR number'), {
|
|
788
|
+
forgeError: forgeError(ERROR_CODES.INVALID_ARGS, 'PR number must be a positive integer'),
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
const headCommitId = assertExpectedSha(expectedHeadSha, 'expectedHeadSha');
|
|
792
|
+
let result;
|
|
793
|
+
try {
|
|
794
|
+
result = await giteaFetch(
|
|
795
|
+
ctx.config,
|
|
796
|
+
ctx.parsed,
|
|
797
|
+
repoApiPath(ctx.config, 'pulls', String(pullIndex), 'merge'),
|
|
798
|
+
{
|
|
799
|
+
method: 'POST',
|
|
800
|
+
headers: { 'Content-Type': 'application/json' },
|
|
801
|
+
body: JSON.stringify({ Do: 'merge', head_commit_id: headCommitId }),
|
|
802
|
+
},
|
|
803
|
+
);
|
|
804
|
+
} catch (err) {
|
|
805
|
+
const status = err.status ?? err.forgeError?.status ?? null;
|
|
806
|
+
const message = err.forgeError?.message ?? err.message ?? '';
|
|
807
|
+
if (isGiteaHeadOutOfDate409(err)) {
|
|
808
|
+
throw Object.assign(new Error(message), {
|
|
809
|
+
status,
|
|
810
|
+
mergeBlockedBlockers: ['head_ref_moved'],
|
|
811
|
+
forgeError: forgeError(
|
|
812
|
+
ERROR_CODES.MERGE_BLOCKED,
|
|
813
|
+
sanitizeField(message) || 'Head branch out of date at merge POST',
|
|
814
|
+
status,
|
|
815
|
+
),
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
throw err;
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
commit_sha: sanitizeField(result?.sha ?? result?.merge_commit_sha ?? null),
|
|
822
|
+
provider_status: 200,
|
|
823
|
+
base_sha: sanitizeField(result?.base_sha ?? null),
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
709
827
|
const GITEA_OPEN_PULL_COMPLIANT_MAX =
|
|
710
828
|
DEFAULT_OPEN_PULL_LIST_PAGE_SIZE * MAX_CHECK_STATUS_PAGES;
|
|
711
829
|
|
|
@@ -916,17 +1034,20 @@ function mergeability(pr) {
|
|
|
916
1034
|
|
|
917
1035
|
export async function prView(ctx, opts) {
|
|
918
1036
|
const pr = await getPull(ctx, opts);
|
|
919
|
-
|
|
1037
|
+
const body = {
|
|
920
1038
|
pr_number: pr.number,
|
|
921
1039
|
url: sanitizeUrl(pr.html_url ?? pr.url),
|
|
922
1040
|
title: sanitizeField(pr.title),
|
|
923
1041
|
state: normalizeGiteaPrState(pr.state),
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1042
|
+
forge_target_branch_ref: sanitizeField(pr.base?.ref),
|
|
1043
|
+
forge_target_sha: sanitizeField(pr.base?.sha),
|
|
1044
|
+
forge_source_branch_ref: sanitizeField(pr.head?.ref),
|
|
1045
|
+
forge_source_sha: sanitizeField(pr.head?.sha),
|
|
928
1046
|
mergeability: mergeability(pr),
|
|
929
1047
|
};
|
|
1048
|
+
const forgeSourceRepoId = forgeSourceRepoIdFromPull(ctx.config, pr);
|
|
1049
|
+
if (forgeSourceRepoId) body.forge_source_repo_id = forgeSourceRepoId;
|
|
1050
|
+
return body;
|
|
930
1051
|
}
|
|
931
1052
|
|
|
932
1053
|
export async function prChecks(ctx, opts) {
|
|
@@ -957,7 +1078,7 @@ export async function prChecks(ctx, opts) {
|
|
|
957
1078
|
const mapped = mapGiteaCommitStatuses(statusRecords);
|
|
958
1079
|
const conclusion = summarizeChecks(mapped);
|
|
959
1080
|
return {
|
|
960
|
-
|
|
1081
|
+
forge_source_sha: sha,
|
|
961
1082
|
check_conclusion: conclusion,
|
|
962
1083
|
checks_truncated,
|
|
963
1084
|
statuses: mapped,
|
|
@@ -1016,9 +1137,11 @@ export const provider = {
|
|
|
1016
1137
|
mergePlan,
|
|
1017
1138
|
syncPlan,
|
|
1018
1139
|
crOpen,
|
|
1140
|
+
mergeExecute,
|
|
1019
1141
|
statusSet,
|
|
1020
1142
|
whoami,
|
|
1021
1143
|
branchProtection,
|
|
1144
|
+
branchHeadSha,
|
|
1022
1145
|
crFiles,
|
|
1023
1146
|
crComments,
|
|
1024
1147
|
forgeChanges,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remogram/provider-gitea-api",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.8",
|
|
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.
|
|
25
|
+
"@remogram/core": "0.1.0-beta.8"
|
|
26
26
|
}
|
|
27
27
|
}
|