renovate 44.83.3 → 44.85.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.
Files changed (45) hide show
  1. package/dist/config/presets/forgejo/index.js +3 -2
  2. package/dist/config/presets/forgejo/index.js.map +1 -1
  3. package/dist/config/presets/gitea/index.js +10 -3
  4. package/dist/config/presets/gitea/index.js.map +1 -1
  5. package/dist/modules/manager/fingerprint.generated.js +1 -1
  6. package/dist/modules/manager/fingerprint.generated.js.map +1 -1
  7. package/dist/modules/manager/github-actions/extract.js +1 -33
  8. package/dist/modules/manager/github-actions/extract.js.map +1 -1
  9. package/dist/modules/manager/github-actions/{community.js → known-actions.js} +62 -5
  10. package/dist/modules/manager/github-actions/known-actions.js.map +1 -0
  11. package/dist/modules/manager/github-actions/schema.js +2 -2
  12. package/dist/modules/manager/github-actions/schema.js.map +1 -1
  13. package/dist/modules/platform/api.js +6 -6
  14. package/dist/modules/platform/forgejo/index.js +21 -587
  15. package/dist/modules/platform/forgejo/index.js.map +1 -1
  16. package/dist/modules/platform/gitea/gitea-helper.js +90 -74
  17. package/dist/modules/platform/gitea/gitea-helper.js.map +1 -1
  18. package/dist/modules/platform/gitea/index.js +561 -529
  19. package/dist/modules/platform/gitea/index.js.map +1 -1
  20. package/dist/modules/platform/gitea/labels.js +42 -0
  21. package/dist/modules/platform/gitea/labels.js.map +1 -0
  22. package/dist/modules/platform/gitea/pr-cache.js +66 -28
  23. package/dist/modules/platform/gitea/pr-cache.js.map +1 -1
  24. package/dist/modules/platform/gitea/schema.js +141 -5
  25. package/dist/modules/platform/gitea/schema.js.map +1 -1
  26. package/dist/modules/platform/gitea/utils.js +10 -8
  27. package/dist/modules/platform/gitea/utils.js.map +1 -1
  28. package/dist/util/cache/repository/types.d.ts +13 -8
  29. package/dist/util/http/forgejo.js +6 -35
  30. package/dist/util/http/forgejo.js.map +1 -1
  31. package/dist/workers/repository/update/pr/changelog/forgejo/index.js +1 -1
  32. package/dist/workers/repository/update/pr/changelog/forgejo/index.js.map +1 -1
  33. package/dist/workers/repository/update/pr/changelog/gitea/index.js +3 -3
  34. package/dist/workers/repository/update/pr/changelog/gitea/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/renovate-schema.json +2 -2
  37. package/dist/modules/manager/github-actions/community.js.map +0 -1
  38. package/dist/modules/platform/forgejo/forgejo-helper.js +0 -235
  39. package/dist/modules/platform/forgejo/forgejo-helper.js.map +0 -1
  40. package/dist/modules/platform/forgejo/pr-cache.js +0 -114
  41. package/dist/modules/platform/forgejo/pr-cache.js.map +0 -1
  42. package/dist/modules/platform/forgejo/schema.js +0 -149
  43. package/dist/modules/platform/forgejo/schema.js.map +0 -1
  44. package/dist/modules/platform/forgejo/utils.js +0 -115
  45. package/dist/modules/platform/forgejo/utils.js.map +0 -1
@@ -9,12 +9,13 @@ import { ensureTrailingSlash } from "../../../util/url.js";
9
9
  import { parseJson } from "../../../util/common.js";
10
10
  import { map } from "../../../util/promises.js";
11
11
  import { getBranchCommit, initRepo as initRepo$1 } from "../../../util/git/index.js";
12
- import { setBaseUrl } from "../../../util/http/gitea.js";
12
+ import { GiteaHttp, setBaseUrl } from "../../../util/http/gitea.js";
13
13
  import { getPrBodyStruct, hashBody } from "../pr-body.js";
14
14
  import { repoFingerprint } from "../util.js";
15
15
  import { smartTruncate } from "../utils/pr-body.js";
16
- import { DRAFT_PREFIX, getMergeMethod, getRepoUrl, isAllowed, smartLinks, toRenovatePR, trimTrailingApiPath, usableRepo } from "./utils.js";
17
- import { closeIssue, createComment, createCommitStatus, createIssue, createPR, deleteComment, getCombinedCommitStatus, getComments, getCurrentUser, getIssue as getIssue$1, getOrgLabels, getPR, getPRByBranch, getRepo, getRepoContents, getRepoLabels, getVersion, giteaHttp, giteaToRenovateStatusMapping, mergePR, orgListRepos, renovateToGiteaStatusMapping, requestPrReviewers, searchIssues, searchRepos, unassignLabel, updateComment, updateIssue, updateIssueLabels, updatePR } from "./gitea-helper.js";
16
+ import { DRAFT_PREFIX, getMergeMethod, getRepoUrl, isAllowed, platformDisplayName, smartLinks, toRenovatePR, trimTrailingApiPath, usableRepo } from "./utils.js";
17
+ import { closeIssue, createComment, createCommitStatus, createIssue, createPR, deleteComment, getCombinedCommitStatus, getComments, getCurrentUser, getIssue as getIssue$1, getPR, getPRByBranch, getRepo, getRepoContents, getVersion, isOrg, mergePR, orgListRepos, requestPrReviewers, searchIssues, searchRepos, toPlatformStatusMapping, toRenovateStatusMapping, unassignLabel, updateComment, updateIssue, updateIssueLabels, updatePR } from "./gitea-helper.js";
18
+ import { lookupLabelByName } from "./labels.js";
18
19
  import { GiteaPrCache } from "./pr-cache.js";
19
20
  import { isNumber, isString } from "@sindresorhus/is";
20
21
  import semver from "semver";
@@ -22,6 +23,7 @@ import semver from "semver";
22
23
  var gitea_exports = /* @__PURE__ */ __exportAll({
23
24
  addAssignees: () => addAssignees,
24
25
  addReviewers: () => addReviewers,
26
+ createPlatform: () => createPlatform,
25
27
  createPr: () => createPr,
26
28
  deleteLabel: () => deleteLabel,
27
29
  ensureComment: () => ensureComment,
@@ -40,6 +42,7 @@ var gitea_exports = /* @__PURE__ */ __exportAll({
40
42
  getPrList: () => getPrList,
41
43
  getRawFile: () => getRawFile,
42
44
  getRepos: () => getRepos,
45
+ giteaHttp: () => giteaHttp,
43
46
  id: () => id,
44
47
  initPlatform: () => initPlatform,
45
48
  initRepo: () => initRepo,
@@ -51,25 +54,7 @@ var gitea_exports = /* @__PURE__ */ __exportAll({
51
54
  updatePr: () => updatePr
52
55
  });
53
56
  const id = "gitea";
54
- const defaults = {
55
- hostType: "gitea",
56
- endpoint: "https://gitea.com/",
57
- version: "0.0.0",
58
- isForgejo: false
59
- };
60
- let config = {};
61
- let botUserID;
62
- let botUserName;
63
- function resetPlatform() {
64
- config = {};
65
- botUserID = void 0;
66
- botUserName = void 0;
67
- defaults.hostType = "gitea";
68
- defaults.endpoint = "https://gitea.com/";
69
- defaults.version = "0.0.0";
70
- defaults.isForgejo = false;
71
- setBaseUrl(defaults.endpoint);
72
- }
57
+ const giteaHttp = new GiteaHttp();
73
58
  function toRenovateIssue(data) {
74
59
  return {
75
60
  number: data.number,
@@ -89,538 +74,585 @@ function findCommentByTopic(comments, topic) {
89
74
  function findCommentByContent(comments, content) {
90
75
  return comments.find((c) => c.body.trim() === content) ?? null;
91
76
  }
92
- function getLabelList() {
93
- if (config.labelList === null) {
94
- const repoLabels = getRepoLabels(config.repository, { memCache: false }).then((labels) => {
95
- logger.debug(`Retrieved ${labels.length} repo labels`);
96
- return labels;
97
- });
98
- const orgLabels = getOrgLabels(config.repository.split("/")[0], { memCache: false }).then((labels) => {
99
- logger.debug(`Retrieved ${labels.length} org labels`);
100
- return labels;
101
- }).catch(() => {
102
- logger.debug(`Unable to fetch organization labels`);
103
- return [];
104
- });
105
- config.labelList = Promise.all([repoLabels, orgLabels]).then((labels) => [].concat(...labels));
77
+ /**
78
+ * Builds the platform implementation shared by every platform which speaks the
79
+ * Gitea API. Each call owns its own module state, so `gitea` and `forgejo` do
80
+ * not share caches or configuration.
81
+ */
82
+ function createPlatform(options) {
83
+ const { id, http, setBaseUrl, logDetectedVersion, checkNativeAutomerge, minReviewerVersion } = options;
84
+ const name = platformDisplayName[id];
85
+ const defaults = {
86
+ endpoint: options.defaultEndpoint,
87
+ version: "0.0.0"
88
+ };
89
+ let config = {};
90
+ let botUserID;
91
+ let botUserName;
92
+ const prCache = new GiteaPrCache(http, id);
93
+ function resetPlatform() {
94
+ config = {};
95
+ prCache.reset();
96
+ botUserID = void 0;
97
+ botUserName = void 0;
98
+ defaults.endpoint = options.defaultEndpoint;
99
+ defaults.version = "0.0.0";
100
+ setBaseUrl(defaults.endpoint);
106
101
  }
107
- return config.labelList;
108
- }
109
- async function lookupLabelByName(name) {
110
- logger.debug(`lookupLabelByName(${name})`);
111
- return (await getLabelList()).find((l) => l.name === name)?.id ?? null;
112
- }
113
- async function fetchRepositories({ topic, sort, order }) {
114
- return (await searchRepos({
115
- uid: botUserID,
116
- archived: false,
117
- ...topic && {
118
- topic: true,
119
- q: topic
102
+ async function fetchIssueList() {
103
+ const issueList = (await searchIssues(http, config.repository, { state: "all" }, { memCache: false })).map(toRenovateIssue);
104
+ logger.debug(`Retrieved ${issueList.length} Issues`);
105
+ return issueList;
106
+ }
107
+ async function fetchRepositories({ topic, sort, order }) {
108
+ return (await searchRepos(http, {
109
+ uid: botUserID,
110
+ archived: false,
111
+ ...topic && {
112
+ topic: true,
113
+ q: topic
114
+ },
115
+ ...sort && { sort },
116
+ ...order && { order }
117
+ })).filter(usableRepo).map((r) => r.full_name);
118
+ }
119
+ const platform = {
120
+ async initPlatform({ endpoint, token }) {
121
+ if (!token) throw new Error(`Init: You must configure a ${name} personal access token`);
122
+ if (endpoint) {
123
+ let baseEndpoint = trimTrailingApiPath(endpoint);
124
+ baseEndpoint = ensureTrailingSlash(baseEndpoint);
125
+ defaults.endpoint = baseEndpoint;
126
+ } else logger.debug(`Using default ${name} endpoint: ${defaults.endpoint}`);
127
+ setBaseUrl(defaults.endpoint);
128
+ let gitAuthor;
129
+ try {
130
+ const user = await getCurrentUser(http, { token });
131
+ gitAuthor = `${user.full_name || user.login} <${user.email}>`;
132
+ botUserID = user.id;
133
+ botUserName = user.login;
134
+ const env = getEnv();
135
+ /* v8 ignore if: experimental feature */
136
+ if (semver.valid(env.RENOVATE_X_PLATFORM_VERSION)) defaults.version = env.RENOVATE_X_PLATFORM_VERSION;
137
+ else defaults.version = await getVersion(http, { token });
138
+ logDetectedVersion(defaults.version);
139
+ } catch (err) {
140
+ logger.debug({ err }, `Error authenticating with ${name}. Check your token`);
141
+ throw new Error("Init: Authentication failure");
142
+ }
143
+ return {
144
+ endpoint: defaults.endpoint,
145
+ gitAuthor
146
+ };
120
147
  },
121
- ...sort && { sort },
122
- ...order && { order }
123
- })).filter(usableRepo).map((r) => r.full_name);
124
- }
125
- const platform = {
126
- async initPlatform({ endpoint, token }) {
127
- if (!token) throw new Error("Init: You must configure a Gitea personal access token");
128
- if (endpoint) {
129
- let baseEndpoint = trimTrailingApiPath(endpoint);
130
- baseEndpoint = ensureTrailingSlash(baseEndpoint);
131
- defaults.endpoint = baseEndpoint;
132
- } else logger.debug(`Using default Gitea endpoint: ${defaults.endpoint}`);
133
- setBaseUrl(defaults.endpoint);
134
- let gitAuthor;
135
- try {
136
- const user = await getCurrentUser({ token });
137
- gitAuthor = `${user.full_name || user.login} <${user.email}>`;
138
- botUserID = user.id;
139
- botUserName = user.login;
140
- const env = getEnv();
141
- /* v8 ignore next: experimental feature */
142
- if (semver.valid(env.RENOVATE_X_PLATFORM_VERSION)) defaults.version = env.RENOVATE_X_PLATFORM_VERSION;
143
- else defaults.version = await getVersion({ token });
144
- if (defaults.version?.includes("gitea-")) {
145
- defaults.isForgejo = true;
146
- logger.info(`Detected Forgejo instance, please use 'forgejo' platform instead`);
148
+ async getRawFile(fileName, repoName, branchOrTag) {
149
+ const repo = repoName ?? config.repository;
150
+ const contents = await getRepoContents(http, repo, fileName, branchOrTag);
151
+ if (contents.type !== "file") return null;
152
+ return contents.contentString;
153
+ },
154
+ async getJsonFile(fileName, repoName, branchOrTag) {
155
+ const raw = await platform.getRawFile(fileName, repoName, branchOrTag);
156
+ return parseJson(raw, fileName);
157
+ },
158
+ async initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, gitUrl }) {
159
+ let repo;
160
+ config = {};
161
+ config.repository = repository;
162
+ config.cloneSubmodules = !!cloneSubmodules;
163
+ config.cloneSubmodulesFilter = cloneSubmodulesFilter;
164
+ config.ignorePrAuthor = GlobalConfig.get("ignorePrAuthor");
165
+ try {
166
+ repo = await getRepo(http, repository);
167
+ } catch (err) {
168
+ logger.debug({ err }, `Unknown ${name} initRepo error`);
169
+ throw err;
147
170
  }
148
- logger.debug(`${defaults.isForgejo ? "Forgejo" : "Gitea"} version: ${defaults.version}`);
149
- } catch (err) {
150
- logger.debug({ err }, "Error authenticating with Gitea. Check your token");
151
- throw new Error("Init: Authentication failure");
152
- }
153
- return {
154
- endpoint: defaults.endpoint,
155
- gitAuthor
156
- };
157
- },
158
- async getRawFile(fileName, repoName, branchOrTag) {
159
- const repo = repoName ?? config.repository;
160
- return (await getRepoContents(repo, fileName, branchOrTag)).contentString ?? null;
161
- },
162
- async getJsonFile(fileName, repoName, branchOrTag) {
163
- const raw = await platform.getRawFile(fileName, repoName, branchOrTag);
164
- return parseJson(raw, fileName);
165
- },
166
- async initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, gitUrl }) {
167
- let repo;
168
- config = {};
169
- config.repository = repository;
170
- config.cloneSubmodules = !!cloneSubmodules;
171
- config.cloneSubmodulesFilter = cloneSubmodulesFilter;
172
- config.ignorePrAuthor = GlobalConfig.get("ignorePrAuthor");
173
- try {
174
- repo = await getRepo(repository);
175
- } catch (err) {
176
- logger.debug({ err }, "Unknown Gitea initRepo error");
177
- throw err;
178
- }
179
- if (repo.archived) {
180
- logger.debug("Repository is archived - aborting renovation");
181
- throw new Error(REPOSITORY_ARCHIVED);
182
- }
183
- if (repo.mirror) {
184
- logger.debug("Repository is a mirror - aborting renovation");
185
- throw new Error(REPOSITORY_MIRRORED);
186
- }
187
- if (repo.permissions.pull === false || repo.permissions.push === false) {
188
- logger.debug("Repository does not permit pull or push - aborting renovation");
189
- throw new Error(REPOSITORY_ACCESS_FORBIDDEN);
190
- }
191
- if (repo.empty) {
192
- logger.debug("Repository is empty - aborting renovation");
193
- throw new Error(REPOSITORY_EMPTY);
194
- }
195
- if (repo.has_pull_requests === false) {
196
- logger.debug("Repo has disabled pull requests - aborting renovation");
197
- throw new Error(REPOSITORY_BLOCKED);
198
- }
199
- const mergeStyle = [
200
- repo.default_merge_style,
201
- "fast-forward-only",
202
- "squash",
203
- "merge",
204
- "rebase",
205
- "rebase-merge"
206
- ].find((style) => isAllowed(style, repo));
207
- if (mergeStyle) config.mergeMethod = mergeStyle;
208
- else {
209
- logger.debug("Repository has no allowed merge methods - aborting renovation");
210
- throw new Error(REPOSITORY_BLOCKED);
211
- }
212
- config.defaultBranch = repo.default_branch;
213
- logger.debug(`${repository} default branch = ${config.defaultBranch}`);
214
- const url = getRepoUrl(repo, gitUrl, defaults.endpoint);
215
- await initRepo$1({
216
- ...config,
217
- url
218
- });
219
- config.issueList = null;
220
- config.labelList = null;
221
- config.hasIssuesEnabled = !repo.external_tracker && repo.has_issues;
222
- return {
223
- defaultBranch: config.defaultBranch,
224
- isFork: !!repo.fork,
225
- repoFingerprint: repoFingerprint(repo.id, defaults.endpoint)
226
- };
227
- },
228
- async getRepos(config) {
229
- logger.debug("Auto-discovering Gitea repositories");
230
- try {
231
- if (config?.topics) {
232
- logger.debug({ topics: config.topics }, "Auto-discovering by topics");
233
- const fetchRepoArgs = config.topics.map((topic) => {
234
- return {
235
- topic,
236
- sort: config.sort,
237
- order: config.order
238
- };
239
- });
240
- const repos = await map(fetchRepoArgs, fetchRepositories);
241
- return deduplicateArray(repos.flat());
171
+ if (repo.archived) {
172
+ logger.debug("Repository is archived - aborting renovation");
173
+ throw new Error(REPOSITORY_ARCHIVED);
242
174
  }
243
- if (config?.namespaces) {
244
- logger.debug({ namespaces: config.namespaces }, "Auto-discovering by organization");
245
- const repos = await map(config.namespaces, async (organization) => {
246
- return (await orgListRepos(organization)).filter((r) => !r.mirror && !r.archived).map((r) => r.full_name);
247
- });
248
- return deduplicateArray(repos.flat());
175
+ if (repo.mirror) {
176
+ logger.debug("Repository is a mirror - aborting renovation");
177
+ throw new Error(REPOSITORY_MIRRORED);
249
178
  }
250
- return await fetchRepositories({
251
- sort: config?.sort,
252
- order: config?.order
253
- });
254
- } catch (err) {
255
- logger.error({ err }, "Gitea getRepos() error");
256
- throw err;
257
- }
258
- },
259
- async setBranchStatus({ branchName, context, description, state, url: target_url }) {
260
- try {
261
- const branchCommit = getBranchCommit(branchName);
262
- await createCommitStatus(config.repository, branchCommit, {
263
- state: renovateToGiteaStatusMapping[state] || "pending",
264
- context,
265
- description,
266
- ...target_url && { target_url }
267
- });
268
- await getCombinedCommitStatus(config.repository, branchName, { memCache: false });
269
- } catch (err) {
270
- logger.warn({ err }, "Failed to set branch status");
271
- }
272
- },
273
- async getBranchStatus(branchName, internalChecksAsSuccess) {
274
- let ccs;
275
- try {
276
- ccs = await getCombinedCommitStatus(config.repository, branchName);
277
- } catch (err) {
278
- if (err.statusCode === 404) {
279
- logger.debug("Received 404 when checking branch status, assuming branch deletion");
280
- throw new Error(REPOSITORY_CHANGED);
179
+ if (!repo.permissions.pull || !repo.permissions.push) {
180
+ logger.debug("Repository does not permit pull or push - aborting renovation");
181
+ throw new Error(REPOSITORY_ACCESS_FORBIDDEN);
281
182
  }
282
- logger.debug("Unknown error when checking branch status");
283
- throw err;
284
- }
285
- logger.debug({ ccs }, "Branch status check result");
286
- if (!internalChecksAsSuccess && ccs.worstStatus === "success" && ccs.statuses.every((status) => status.context?.startsWith("renovate/"))) {
287
- logger.debug("Successful checks are all internal renovate/ checks, so returning \"pending\" branch status");
288
- return "yellow";
289
- }
290
- /* v8 ignore next -- the mapping covers every status Gitea returns, 'yellow' fallback is defensive */
291
- return giteaToRenovateStatusMapping[ccs.worstStatus] ?? "yellow";
292
- },
293
- async getBranchStatusCheck(branchName, context) {
294
- const cs = (await getCombinedCommitStatus(config.repository, branchName)).statuses.find((s) => s.context === context);
295
- if (!cs) return null;
296
- const status = giteaToRenovateStatusMapping[cs.status];
297
- if (status) return status;
298
- logger.warn({ check: cs }, "Could not map Gitea status value to Renovate status");
299
- return "yellow";
300
- },
301
- getPrList() {
302
- return GiteaPrCache.getPrs(giteaHttp, config.repository, config.ignorePrAuthor, botUserName);
303
- },
304
- async getPr(number) {
305
- let pr = (await platform.getPrList()).find((p) => p.number === number) ?? null;
306
- if (pr) logger.debug("Returning from cached PRs");
307
- else {
308
- logger.debug("PR not found in cached PRs - trying to fetch directly");
309
- const gpr = await getPR(config.repository, number);
310
- pr = toRenovatePR(gpr, botUserName);
311
- if (pr) await GiteaPrCache.setPr(giteaHttp, config.repository, config.ignorePrAuthor, botUserName, pr);
312
- }
313
- if (!pr) return null;
314
- return pr;
315
- },
316
- async findPr({ branchName, prTitle: title, state = "all", includeOtherAuthors, targetBranch }) {
317
- logger.debug(`findPr(${branchName}, ${title}, ${state})`);
318
- if (includeOtherAuthors && isString(targetBranch)) {
319
- const pr = await getPRByBranch(config.repository, targetBranch, branchName);
320
- if (!pr) return null;
321
- return toRenovatePR(pr, null);
322
- }
323
- const pr = (await platform.getPrList()).find((p) => p.sourceRepo === config.repository && p.sourceBranch === branchName && matchesState(p.state, state) && (!title || p.title === title));
324
- if (pr) logger.debug(`Found PR #${pr.number}`);
325
- return pr ?? null;
326
- },
327
- async createPr({ sourceBranch, targetBranch, prTitle, prBody: rawBody, labels: labelNames, platformPrOptions, draftPR }) {
328
- let title = prTitle;
329
- const base = targetBranch;
330
- const head = sourceBranch;
331
- const body = sanitize(rawBody);
332
- if (draftPR) title = DRAFT_PREFIX + title;
333
- logger.debug(`Creating pull request: ${title} (${head} => ${base})`);
334
- try {
335
- const labels = Array.isArray(labelNames) ? await map(labelNames, lookupLabelByName) : [];
336
- const gpr = await createPR(config.repository, {
337
- base,
338
- head,
339
- title,
340
- body,
341
- labels: labels.filter(isNumber)
183
+ if (repo.empty) {
184
+ logger.debug("Repository is empty - aborting renovation");
185
+ throw new Error(REPOSITORY_EMPTY);
186
+ }
187
+ if (repo.has_pull_requests === false) {
188
+ logger.debug("Repo has disabled pull requests - aborting renovation");
189
+ throw new Error(REPOSITORY_BLOCKED);
190
+ }
191
+ const mergeStyle = [
192
+ ...repo.default_merge_style ? [repo.default_merge_style] : [],
193
+ "fast-forward-only",
194
+ "squash",
195
+ "merge",
196
+ "rebase",
197
+ "rebase-merge"
198
+ ].find((style) => isAllowed(style, repo));
199
+ if (mergeStyle) config.mergeMethod = mergeStyle;
200
+ else {
201
+ logger.debug("Repository has no allowed merge methods - aborting renovation");
202
+ throw new Error(REPOSITORY_BLOCKED);
203
+ }
204
+ try {
205
+ config.isOrgRepo = await isOrg(http, id, repo.owner.login);
206
+ } catch (err) {
207
+ logger.debug({ err }, `${name} initRepo() error`);
208
+ throw err;
209
+ }
210
+ config.defaultBranch = repo.default_branch;
211
+ logger.debug(`${repository} default branch = ${config.defaultBranch}`);
212
+ const url = getRepoUrl(repo, gitUrl, defaults.endpoint, id);
213
+ await initRepo$1({
214
+ ...config,
215
+ url
342
216
  });
343
- if (platformPrOptions?.usePlatformAutomerge) {
344
- if (semver.gte(defaults.version, defaults.isForgejo ? "10.0.0" : "1.24.0")) try {
345
- await mergePR(config.repository, gpr.number, {
346
- Do: getMergeMethod(platformPrOptions?.automergeStrategy) ?? config.mergeMethod,
347
- merge_when_checks_succeed: true,
348
- delete_branch_after_merge: true
217
+ config.issueList = null;
218
+ config.labelList = null;
219
+ prCache.initRepo(config.repository, config.ignorePrAuthor, botUserName);
220
+ config.hasIssuesEnabled = !repo.external_tracker && repo.has_issues;
221
+ config.orgName = repo.owner.login;
222
+ return {
223
+ defaultBranch: config.defaultBranch,
224
+ isFork: !!repo.fork,
225
+ repoFingerprint: repoFingerprint(repo.id, defaults.endpoint)
226
+ };
227
+ },
228
+ async getRepos(config) {
229
+ logger.debug(`Auto-discovering ${name} repositories`);
230
+ try {
231
+ if (config?.topics) {
232
+ logger.debug({ topics: config.topics }, "Auto-discovering by topics");
233
+ const fetchRepoArgs = config.topics.map((topic) => {
234
+ return {
235
+ topic,
236
+ sort: config.sort,
237
+ order: config.order
238
+ };
349
239
  });
350
- logger.debug({ prNumber: gpr.number }, "Gitea-native automerge: success");
351
- } catch (err) {
352
- logger.warn({
353
- err,
354
- prNumber: gpr.number
355
- }, "Gitea-native automerge: fail");
240
+ const repos = await map(fetchRepoArgs, fetchRepositories);
241
+ return deduplicateArray(repos.flat());
242
+ }
243
+ if (config?.namespaces) {
244
+ logger.debug({ namespaces: config.namespaces }, "Auto-discovering by organization");
245
+ const repos = await map(config.namespaces, async (organization) => {
246
+ return (await orgListRepos(http, organization)).filter((r) => !r.mirror && !r.archived).map((r) => r.full_name);
247
+ });
248
+ return deduplicateArray(repos.flat());
356
249
  }
357
- else logger.debug({ prNumber: gpr.number }, `Gitea-native automerge: not supported on this version of ${defaults.isForgejo ? "Forgejo" : "Gitea"}. Use ${defaults.isForgejo ? "10.0.0" : "1.24.0"} or newer.`);
250
+ return await fetchRepositories({
251
+ sort: config?.sort,
252
+ order: config?.order
253
+ });
254
+ } catch (err) {
255
+ logger.error({
256
+ err,
257
+ platform: id
258
+ }, "Error auto-discovering repositories");
259
+ throw err;
358
260
  }
359
- const pr = toRenovatePR(gpr, botUserName);
360
- if (!pr) throw new Error("Can not parse newly created Pull Request");
361
- await GiteaPrCache.setPr(giteaHttp, config.repository, config.ignorePrAuthor, botUserName, pr);
261
+ },
262
+ async setBranchStatus({ branchName, context, description, state, url: target_url }) {
263
+ try {
264
+ const branchCommit = getBranchCommit(branchName);
265
+ await createCommitStatus(http, config.repository, branchCommit, {
266
+ state: toPlatformStatusMapping[state] || "pending",
267
+ context,
268
+ description,
269
+ ...target_url && { target_url }
270
+ });
271
+ await getCombinedCommitStatus(http, config.repository, branchName, { memCache: false });
272
+ } catch (err) {
273
+ logger.warn({ err }, "Failed to set branch status");
274
+ }
275
+ },
276
+ async getBranchStatus(branchName, internalChecksAsSuccess) {
277
+ let ccs;
278
+ try {
279
+ ccs = await getCombinedCommitStatus(http, config.repository, branchName);
280
+ } catch (err) {
281
+ if (err.statusCode === 404) {
282
+ logger.debug("Received 404 when checking branch status, assuming branch deletion");
283
+ throw new Error(REPOSITORY_CHANGED);
284
+ }
285
+ logger.debug("Unknown error when checking branch status");
286
+ throw err;
287
+ }
288
+ logger.debug({ ccs }, "Branch status check result");
289
+ if (!internalChecksAsSuccess && ccs.worstStatus === "success" && ccs.statuses.every((status) => status.context?.startsWith("renovate/"))) {
290
+ logger.debug("Successful checks are all internal renovate/ checks, so returning \"pending\" branch status");
291
+ return "yellow";
292
+ }
293
+ return toRenovateStatusMapping[ccs.worstStatus];
294
+ },
295
+ async getBranchStatusCheck(branchName, context) {
296
+ const cs = (await getCombinedCommitStatus(http, config.repository, branchName)).statuses.find((s) => s.context === context);
297
+ if (!cs) return null;
298
+ return toRenovateStatusMapping[cs.status];
299
+ },
300
+ getPrList() {
301
+ return prCache.getPrs();
302
+ },
303
+ async getPr(number) {
304
+ let pr = (await platform.getPrList()).find((p) => p.number === number) ?? null;
305
+ if (pr) logger.debug("Returning from cached PRs");
306
+ else {
307
+ logger.debug("PR not found in cached PRs - trying to fetch directly");
308
+ const gpr = await getPR(http, config.repository, number);
309
+ pr = toRenovatePR(gpr, botUserName);
310
+ if (pr) await prCache.setPr(pr);
311
+ }
312
+ if (!pr) return null;
362
313
  return pr;
363
- } catch (err) {
364
- if (err.statusCode === 409) {
365
- logger.warn({
366
- prTitle: title,
367
- sourceBranch
368
- }, "Attempting to gracefully recover from 409 Conflict response in createPr()");
369
- GiteaPrCache.forceSync();
370
- const pr = await platform.findPr({
371
- branchName: sourceBranch,
372
- state: "open"
314
+ },
315
+ async findPr({ branchName, prTitle: title, state = "all", includeOtherAuthors, targetBranch }) {
316
+ logger.debug(`findPr(${branchName}, ${title}, ${state})`);
317
+ if (includeOtherAuthors && isString(targetBranch)) {
318
+ const pr = await getPRByBranch(http, config.repository, targetBranch, branchName);
319
+ if (!pr) return null;
320
+ return toRenovatePR(pr, null);
321
+ }
322
+ const pr = (await platform.getPrList()).find((p) => p.sourceRepo === config.repository && p.sourceBranch === branchName && matchesState(p.state, state) && (!title || p.title === title));
323
+ if (pr) logger.debug(`Found PR #${pr.number}`);
324
+ return pr ?? null;
325
+ },
326
+ async createPr({ sourceBranch, targetBranch, prTitle, prBody: rawBody, labels: labelNames, platformPrOptions, draftPR }) {
327
+ let title = prTitle;
328
+ const base = targetBranch;
329
+ const head = sourceBranch;
330
+ const body = sanitize(rawBody);
331
+ if (draftPR) title = DRAFT_PREFIX + title;
332
+ logger.debug(`Creating pull request: ${title} (${head} => ${base})`);
333
+ try {
334
+ const labels = Array.isArray(labelNames) ? await map(labelNames, (name) => lookupLabelByName(http, config, name)) : [];
335
+ const gpr = await createPR(http, config.repository, {
336
+ base,
337
+ head,
338
+ title,
339
+ body,
340
+ labels: labels.filter(isNumber)
373
341
  });
374
- // v8 ignore else -- TODO: add test #40625
375
- if (pr?.bodyStruct) {
376
- if (pr.title !== title || pr.bodyStruct.hash !== hashBody(body)) {
377
- logger.debug(`Recovered from 409 Conflict, but PR for ${sourceBranch} is outdated. Updating...`);
378
- await platform.updatePr({
379
- number: pr.number,
380
- prTitle: title,
381
- prBody: body
342
+ if (platformPrOptions?.usePlatformAutomerge) {
343
+ const unsupportedReason = checkNativeAutomerge(defaults.version);
344
+ if (unsupportedReason === null) try {
345
+ await mergePR(http, config.repository, gpr.number, {
346
+ Do: getMergeMethod(platformPrOptions?.automergeStrategy) ?? config.mergeMethod,
347
+ merge_when_checks_succeed: true,
348
+ delete_branch_after_merge: true
382
349
  });
383
- pr.title = title;
384
- pr.bodyStruct = getPrBodyStruct(body);
385
- } else logger.debug(`Recovered from 409 Conflict and PR for ${sourceBranch} is up-to-date`);
386
- return pr;
350
+ logger.debug({ prNumber: gpr.number }, `${name}-native automerge: success`);
351
+ } catch (err) {
352
+ logger.warn({
353
+ err,
354
+ prNumber: gpr.number,
355
+ platform: id
356
+ }, "Platform-native automerge: fail");
357
+ }
358
+ else logger.debug({ prNumber: gpr.number }, unsupportedReason);
387
359
  }
360
+ const pr = toRenovatePR(gpr, botUserName);
361
+ if (!pr) throw new Error("Can not parse newly created Pull Request");
362
+ await prCache.setPr(pr);
363
+ return pr;
364
+ } catch (err) {
365
+ if (err.statusCode === 409) {
366
+ logger.warn({
367
+ prTitle: title,
368
+ sourceBranch
369
+ }, "Attempting to gracefully recover from 409 Conflict response in createPr()");
370
+ prCache.forceSync();
371
+ const pr = await platform.findPr({
372
+ branchName: sourceBranch,
373
+ state: "open"
374
+ });
375
+ // v8 ignore else -- TODO: add test #40625
376
+ if (pr?.bodyStruct) {
377
+ if (pr.title !== title || pr.bodyStruct.hash !== hashBody(body)) {
378
+ logger.debug(`Recovered from 409 Conflict, but PR for ${sourceBranch} is outdated. Updating...`);
379
+ await platform.updatePr({
380
+ number: pr.number,
381
+ prTitle: title,
382
+ prBody: body
383
+ });
384
+ pr.title = title;
385
+ pr.bodyStruct = getPrBodyStruct(body);
386
+ } else logger.debug(`Recovered from 409 Conflict and PR for ${sourceBranch} is up-to-date`);
387
+ return pr;
388
+ }
389
+ }
390
+ throw err;
388
391
  }
389
- throw err;
390
- }
391
- },
392
- async updatePr({ number, prTitle, prBody: body, labels, state, targetBranch }) {
393
- let title = prTitle;
394
- if ((await getPrList()).find((pr) => pr.number === number)?.isDraft) title = DRAFT_PREFIX + title;
395
- const prUpdateParams = {
396
- title,
397
- ...body && { body },
398
- ...state && { state }
399
- };
400
- if (targetBranch) prUpdateParams.base = targetBranch;
401
- /**
402
- * Update PR labels.
403
- * In the Gitea API, labels are replaced on each update if the field is present.
404
- * If the field is not present (i.e., undefined), labels aren't updated.
405
- * However, the labels array must contain label IDs instead of names,
406
- * so a lookup is performed to fetch the details (including the ID) of each label.
407
- */
408
- if (Array.isArray(labels)) {
409
- prUpdateParams.labels = (await map(labels, lookupLabelByName)).filter(isNumber);
410
- if (labels.length !== prUpdateParams.labels.length) logger.warn("Some labels could not be looked up. Renovate may halt label updates assuming changes by others.");
411
- }
412
- const gpr = await updatePR(config.repository, number, prUpdateParams);
413
- const pr = toRenovatePR(gpr, botUserName);
414
- if (pr) await GiteaPrCache.setPr(giteaHttp, config.repository, config.ignorePrAuthor, botUserName, pr);
415
- },
416
- async mergePr({ id, strategy }) {
417
- try {
418
- await mergePR(config.repository, id, { Do: getMergeMethod(strategy) ?? config.mergeMethod });
419
- return true;
420
- } catch (err) {
421
- logger.warn({
422
- err,
423
- id
424
- }, "Merging of PR failed");
425
- return false;
426
- }
427
- },
428
- getIssueList() {
429
- if (config.hasIssuesEnabled === false) return Promise.resolve([]);
430
- config.issueList ??= searchIssues(config.repository, { state: "all" }, { memCache: false }).then((issues) => {
431
- const issueList = issues.map(toRenovateIssue);
432
- logger.debug(`Retrieved ${issueList.length} Issues`);
433
- return issueList;
434
- });
435
- return config.issueList;
436
- },
437
- async getIssue(number, memCache = true) {
438
- if (config.hasIssuesEnabled === false) return null;
439
- try {
440
- return {
441
- number,
442
- body: (await getIssue$1(config.repository, number, { memCache })).body
392
+ },
393
+ async updatePr({ number, prTitle, prBody: body, labels, state, targetBranch }) {
394
+ let title = prTitle;
395
+ if ((await platform.getPrList()).find((pr) => pr.number === number)?.isDraft) title = DRAFT_PREFIX + title;
396
+ const prUpdateParams = {
397
+ title,
398
+ ...body && { body },
399
+ ...state && { state }
443
400
  };
444
- } catch (err) /* v8 ignore next -- defensive: issue fetch failures are logged and swallowed, not simulated in specs */ {
445
- logger.debug({
446
- err,
447
- number
448
- }, "Error getting issue");
449
- return null;
450
- }
451
- },
452
- async findIssue(title) {
453
- const issue = (await platform.getIssueList()).find((i) => i.state === "open" && i.title === title);
454
- if (!issue) return null;
455
- logger.debug(`Found Issue #${issue.number}`);
456
- return getIssue(issue.number);
457
- },
458
- async ensureIssue({ title, reuseTitle, body: content, labels: labelNames, shouldReOpen, once }) {
459
- logger.debug(`ensureIssue(${title})`);
460
- if (config.hasIssuesEnabled === false) {
461
- logger.info("Cannot ensure issue because issues are disabled in this repository");
462
- return null;
463
- }
464
- try {
465
- const body = smartLinks(content);
466
- const issueList = await platform.getIssueList();
467
- let issues = issueList.filter((i) => i.title === title);
468
- if (!issues.length) issues = issueList.filter((i) => i.title === reuseTitle);
469
- const labels = Array.isArray(labelNames) ? (await Promise.all(labelNames.map(lookupLabelByName))).filter(isNumber) : void 0;
470
- if (issues.length) {
471
- let activeIssue = issues.find((i) => i.state === "open");
472
- if (!activeIssue) {
473
- if (once) {
474
- logger.debug("Issue already closed - skipping update");
401
+ if (targetBranch) prUpdateParams.base = targetBranch;
402
+ /**
403
+ * Update PR labels.
404
+ * In the Gitea API, labels are replaced on each update if the field is present.
405
+ * If the field is not present (i.e., undefined), labels aren't updated.
406
+ * However, the labels array must contain label IDs instead of names,
407
+ * so a lookup is performed to fetch the details (including the ID) of each label.
408
+ */
409
+ if (Array.isArray(labels)) {
410
+ prUpdateParams.labels = (await map(labels, (name) => lookupLabelByName(http, config, name))).filter(isNumber);
411
+ if (labels.length !== prUpdateParams.labels.length) logger.warn("Some labels could not be looked up. Renovate may halt label updates assuming changes by others.");
412
+ }
413
+ const gpr = await updatePR(http, config.repository, number, prUpdateParams);
414
+ const pr = toRenovatePR(gpr, botUserName);
415
+ if (pr) await prCache.setPr(pr);
416
+ },
417
+ async mergePr({ id: prNumber, strategy }) {
418
+ try {
419
+ await mergePR(http, config.repository, prNumber, { Do: getMergeMethod(strategy) ?? config.mergeMethod });
420
+ return true;
421
+ } catch (err) {
422
+ logger.warn({
423
+ err,
424
+ id: prNumber
425
+ }, "Merging of PR failed");
426
+ return false;
427
+ }
428
+ },
429
+ getIssueList() {
430
+ if (config.hasIssuesEnabled === false) return Promise.resolve([]);
431
+ config.issueList ??= fetchIssueList();
432
+ return config.issueList;
433
+ },
434
+ async getIssue(number, memCache = true) {
435
+ if (config.hasIssuesEnabled === false) return null;
436
+ try {
437
+ return {
438
+ number,
439
+ body: (await getIssue$1(http, config.repository, number, { memCache })).body
440
+ };
441
+ } catch (err) {
442
+ logger.debug({
443
+ err,
444
+ number
445
+ }, "Error getting issue");
446
+ return null;
447
+ }
448
+ },
449
+ async findIssue(title) {
450
+ const issue = (await platform.getIssueList()).find((i) => i.state === "open" && i.title === title);
451
+ if (!issue) return null;
452
+ logger.debug(`Found Issue #${issue.number}`);
453
+ return platform.getIssue(issue.number);
454
+ },
455
+ async ensureIssue({ title, reuseTitle, body: content, labels: labelNames, shouldReOpen, once }) {
456
+ logger.debug(`ensureIssue(${title})`);
457
+ if (config.hasIssuesEnabled === false) {
458
+ logger.info("Cannot ensure issue because issues are disabled in this repository");
459
+ return null;
460
+ }
461
+ try {
462
+ const body = smartLinks(content);
463
+ const issueList = await platform.getIssueList();
464
+ let issues = issueList.filter((i) => i.title === title);
465
+ if (!issues.length) issues = issueList.filter((i) => i.title === reuseTitle);
466
+ const labels = Array.isArray(labelNames) ? (await Promise.all(labelNames.map((name) => lookupLabelByName(http, config, name)))).filter(isNumber) : void 0;
467
+ if (issues.length) {
468
+ let activeIssue = issues.find((i) => i.state === "open");
469
+ if (!activeIssue) {
470
+ if (once) {
471
+ logger.debug("Issue already closed - skipping update");
472
+ return null;
473
+ }
474
+ if (shouldReOpen) logger.debug("Reopening previously closed Issue");
475
+ activeIssue = issues.at(-1);
476
+ }
477
+ for (const issue of issues) if (issue.state === "open" && issue.number !== activeIssue.number) {
478
+ logger.warn({ issueNo: issue.number }, "Closing duplicate issue");
479
+ await closeIssue(http, config.repository, issue.number);
480
+ }
481
+ if (activeIssue.title === title && activeIssue.body === body && activeIssue.state === "open") {
482
+ logger.debug(`Issue #${activeIssue.number} is open and up to date - nothing to do`);
475
483
  return null;
476
484
  }
477
- if (shouldReOpen) logger.debug("Reopening previously closed Issue");
478
- activeIssue = issues.at(-1);
479
- }
480
- for (const issue of issues) if (issue.state === "open" && issue.number !== activeIssue.number) {
481
- logger.warn({ issueNo: issue.number }, "Closing duplicate issue");
482
- await closeIssue(config.repository, issue.number);
483
- }
484
- if (activeIssue.title === title && activeIssue.body === body && activeIssue.state === "open") {
485
- logger.debug(`Issue #${activeIssue.number} is open and up to date - nothing to do`);
486
- return null;
485
+ if (shouldReOpen || activeIssue.state === "open") {
486
+ logger.debug(`Updating Issue #${activeIssue.number}`);
487
+ const existingIssue = await updateIssue(http, config.repository, activeIssue.number, {
488
+ body,
489
+ title,
490
+ state: "open"
491
+ });
492
+ const existingLabelIds = coerceArray(existingIssue.labels).map((label) => label.id);
493
+ if (labels && (labels.length !== existingLabelIds.length || labels.filter((labelId) => !existingLabelIds.includes(labelId)).length !== 0)) await updateIssueLabels(http, config.repository, activeIssue.number, { labels });
494
+ return "updated";
495
+ }
487
496
  }
488
- if (shouldReOpen || activeIssue.state === "open") {
489
- logger.debug(`Updating Issue #${activeIssue.number}`);
490
- const existingIssue = await updateIssue(config.repository, activeIssue.number, {
491
- body,
492
- title,
493
- state: "open"
494
- });
495
- const existingLabelIds = coerceArray(existingIssue.labels).map((label) => label.id);
496
- if (labels && (labels.length !== existingLabelIds.length || labels.filter((labelId) => !existingLabelIds.includes(labelId)).length !== 0)) await updateIssueLabels(config.repository, activeIssue.number, { labels });
497
- return "updated";
497
+ const issue = await createIssue(http, config.repository, {
498
+ body,
499
+ title,
500
+ labels
501
+ });
502
+ logger.debug(`Created new Issue #${issue.number}`);
503
+ config.issueList = null;
504
+ return "created";
505
+ } catch (err) {
506
+ logger.warn({ err }, "Could not ensure issue");
507
+ }
508
+ return null;
509
+ },
510
+ async ensureIssueClosing(title) {
511
+ logger.debug(`ensureIssueClosing(${title})`);
512
+ if (config.hasIssuesEnabled === false) return;
513
+ const issueList = await platform.getIssueList();
514
+ for (const issue of issueList) if (issue.state === "open" && issue.title === title) {
515
+ logger.debug(`Closing issue...issueNo: ${issue.number}`);
516
+ await closeIssue(http, config.repository, issue.number);
517
+ }
518
+ },
519
+ async deleteLabel(issue, labelName) {
520
+ logger.debug(`Deleting label ${labelName} from Issue #${issue}`);
521
+ const label = await lookupLabelByName(http, config, labelName);
522
+ if (label) await unassignLabel(http, config.repository, issue, label);
523
+ else logger.warn({
524
+ issue,
525
+ labelName
526
+ }, "Failed to lookup label for deletion");
527
+ },
528
+ async ensureComment({ number: issue, topic, content }) {
529
+ try {
530
+ let body = sanitize(content);
531
+ const commentList = await getComments(http, config.repository, issue);
532
+ let comment = null;
533
+ if (topic) {
534
+ comment = findCommentByTopic(commentList, topic);
535
+ body = `### ${topic}\n\n${body}`;
536
+ } else comment = findCommentByContent(commentList, body);
537
+ if (!comment) {
538
+ comment = await createComment(http, config.repository, issue, body);
539
+ logger.info({
540
+ repository: config.repository,
541
+ issue,
542
+ comment: comment.id
543
+ }, "Comment added");
544
+ } else if (comment.body === body) logger.debug(`Comment #${comment.id} is already up-to-date`);
545
+ else {
546
+ await updateComment(http, config.repository, comment.id, body);
547
+ logger.debug({
548
+ repository: config.repository,
549
+ issue,
550
+ comment: comment.id
551
+ }, "Comment updated");
498
552
  }
553
+ return true;
554
+ } catch (err) {
555
+ logger.warn({
556
+ err,
557
+ issue,
558
+ subject: topic
559
+ }, "Error ensuring comment");
560
+ return false;
499
561
  }
500
- const issue = await createIssue(config.repository, {
501
- body,
502
- title,
503
- labels
504
- });
505
- logger.debug(`Created new Issue #${issue.number}`);
506
- config.issueList = null;
507
- return "created";
508
- } catch (err) {
509
- logger.warn({ err }, "Could not ensure issue");
510
- }
511
- return null;
512
- },
513
- async ensureIssueClosing(title) {
514
- logger.debug(`ensureIssueClosing(${title})`);
515
- if (config.hasIssuesEnabled === false) return;
516
- const issueList = await platform.getIssueList();
517
- for (const issue of issueList) if (issue.state === "open" && issue.title === title) {
518
- logger.debug(`Closing issue...issueNo: ${issue.number}`);
519
- await closeIssue(config.repository, issue.number);
520
- }
521
- },
522
- async deleteLabel(issue, labelName) {
523
- logger.debug(`Deleting label ${labelName} from Issue #${issue}`);
524
- const label = await lookupLabelByName(labelName);
525
- if (label) await unassignLabel(config.repository, issue, label);
526
- else logger.warn({
527
- issue,
528
- labelName
529
- }, "Failed to lookup label for deletion");
530
- },
531
- async ensureComment({ number: issue, topic, content }) {
532
- try {
533
- let body = sanitize(content);
534
- const commentList = await getComments(config.repository, issue);
562
+ },
563
+ async ensureCommentRemoval(deleteConfig) {
564
+ const { number: issue } = deleteConfig;
565
+ const key = deleteConfig.type === "by-topic" ? deleteConfig.topic : deleteConfig.content;
566
+ logger.debug(`Ensuring comment "${key}" in #${issue} is removed`);
567
+ const commentList = await getComments(http, config.repository, issue);
535
568
  let comment = null;
536
- if (topic) {
537
- comment = findCommentByTopic(commentList, topic);
538
- body = `### ${topic}\n\n${body}`;
539
- } else comment = findCommentByContent(commentList, body);
540
- if (!comment) {
541
- comment = await createComment(config.repository, issue, body);
542
- logger.info({
543
- repository: config.repository,
544
- issue,
545
- comment: comment.id
546
- }, "Comment added");
547
- } else if (comment.body === body) logger.debug(`Comment #${comment.id} is already up-to-date`);
548
- else {
549
- await updateComment(config.repository, comment.id, body);
550
- logger.debug({
551
- repository: config.repository,
569
+ // v8 ignore else -- TODO: add test #40625
570
+ if (deleteConfig.type === "by-topic") comment = findCommentByTopic(commentList, deleteConfig.topic);
571
+ else if (deleteConfig.type === "by-content") comment = findCommentByContent(commentList, sanitize(deleteConfig.content));
572
+ if (!comment) return;
573
+ try {
574
+ await deleteComment(http, config.repository, comment.id);
575
+ } catch (err) {
576
+ logger.warn({
577
+ err,
552
578
  issue,
553
- comment: comment.id
554
- }, "Comment updated");
579
+ config: deleteConfig
580
+ }, "Error deleting comment");
555
581
  }
556
- return true;
557
- } catch (err) {
558
- logger.warn({
559
- err,
560
- issue,
561
- subject: topic
562
- }, "Error ensuring comment");
563
- return false;
564
- }
565
- },
566
- async ensureCommentRemoval(deleteConfig) {
567
- const { number: issue } = deleteConfig;
568
- const key = deleteConfig.type === "by-topic" ? deleteConfig.topic : deleteConfig.content;
569
- logger.debug(`Ensuring comment "${key}" in #${issue} is removed`);
570
- const commentList = await getComments(config.repository, issue);
571
- let comment = null;
572
- // v8 ignore else -- TODO: add test #40625
573
- if (deleteConfig.type === "by-topic") comment = findCommentByTopic(commentList, deleteConfig.topic);
574
- else if (deleteConfig.type === "by-content") comment = findCommentByContent(commentList, sanitize(deleteConfig.content));
575
- if (!comment) return;
576
- try {
577
- await deleteComment(config.repository, comment.id);
578
- } catch (err) {
579
- logger.warn({
580
- err,
581
- issue,
582
- config: deleteConfig
583
- }, "Error deleting comment");
584
- }
585
- },
586
- async getBranchPr(branchName) {
587
- logger.debug(`getBranchPr(${branchName})`);
588
- const pr = await platform.findPr({
589
- branchName,
590
- state: "open"
591
- });
592
- return pr ? platform.getPr(pr.number) : null;
593
- },
594
- async addAssignees(number, assignees) {
595
- logger.debug(`Updating assignees '${assignees?.join(", ")}' on Issue #${number}`);
596
- await updateIssue(config.repository, number, { assignees });
597
- },
598
- async addReviewers(number, reviewers) {
599
- logger.debug(`Adding reviewers '${reviewers?.join(", ")}' to #${number}`);
600
- if (semver.lt(defaults.version, "1.14.0")) {
601
- logger.debug({ version: defaults.version }, "Adding reviewer not yet supported.");
602
- return;
603
- }
604
- try {
605
- await requestPrReviewers(config.repository, number, { reviewers });
606
- } catch (err) {
607
- logger.warn({
608
- err,
609
- number,
610
- reviewers
611
- }, "Failed to assign reviewer");
612
- }
613
- },
614
- massageMarkdown(prBody) {
615
- return smartTruncate(smartLinks(prBody), maxBodyLength());
616
- },
617
- maxBodyLength
618
- };
619
- function maxBodyLength() {
620
- return 1e6;
582
+ },
583
+ async getBranchPr(branchName) {
584
+ logger.debug(`getBranchPr(${branchName})`);
585
+ const pr = await platform.findPr({
586
+ branchName,
587
+ state: "open"
588
+ });
589
+ return pr ? platform.getPr(pr.number) : null;
590
+ },
591
+ async addAssignees(number, assignees) {
592
+ logger.debug(`Updating assignees '${assignees?.join(", ")}' on Issue #${number}`);
593
+ await updateIssue(http, config.repository, number, { assignees });
594
+ },
595
+ async addReviewers(number, reviewers) {
596
+ logger.debug(`Adding reviewers '${reviewers?.join(", ")}' to #${number}`);
597
+ if (minReviewerVersion && semver.lt(defaults.version, minReviewerVersion)) {
598
+ logger.debug({ version: defaults.version }, "Adding reviewer not yet supported.");
599
+ return;
600
+ }
601
+ try {
602
+ const teamReviewers = new Set(reviewers.filter((r) => r.startsWith("team:")).map((r) => r.substring(5)));
603
+ const userReviewers = new Set(reviewers.filter((r) => !r.startsWith("team:")));
604
+ await requestPrReviewers(http, config.repository, number, {
605
+ reviewers: [...userReviewers],
606
+ ...teamReviewers.size && { team_reviewers: [...teamReviewers] }
607
+ });
608
+ } catch (err) {
609
+ logger.warn({
610
+ err,
611
+ number,
612
+ reviewers
613
+ }, "Failed to assign reviewer");
614
+ }
615
+ },
616
+ massageMarkdown(prBody) {
617
+ return smartTruncate(smartLinks(prBody), maxBodyLength());
618
+ },
619
+ maxBodyLength
620
+ };
621
+ function maxBodyLength() {
622
+ return 1e6;
623
+ }
624
+ return {
625
+ platform,
626
+ resetPlatform
627
+ };
628
+ }
629
+ /**
630
+ * Forgejo reports the Gitea version it is compatible with as part of its own
631
+ * version, for example `11.0.1-99-c504062+gitea-1.22.0`.
632
+ */
633
+ function isForgejo(version) {
634
+ return version.includes("gitea-");
621
635
  }
622
- const { addAssignees, addReviewers, createPr, deleteLabel, ensureComment, ensureCommentRemoval, ensureIssue, ensureIssueClosing, findIssue, findPr, getBranchPr, getBranchStatus, getBranchStatusCheck, getIssue, getRawFile, getJsonFile, getIssueList, getPr, massageMarkdown, getPrList, getRepos, initPlatform, initRepo, mergePr, setBranchStatus, updatePr } = platform;
636
+ function logDetectedVersion(version) {
637
+ if (isForgejo(version)) logger.info(`Detected Forgejo instance, please use 'forgejo' platform instead`);
638
+ logger.debug(`${isForgejo(version) ? "Forgejo" : "Gitea"} version: ${version}`);
639
+ }
640
+ function checkNativeAutomerge(version) {
641
+ const minVersion = isForgejo(version) ? "10.0.0" : "1.24.0";
642
+ if (semver.gte(version, minVersion)) return null;
643
+ return `Gitea-native automerge: not supported on this version of ${isForgejo(version) ? "Forgejo" : "Gitea"}. Use ${minVersion} or newer.`;
644
+ }
645
+ const { platform, resetPlatform } = createPlatform({
646
+ id,
647
+ defaultEndpoint: "https://gitea.com/",
648
+ http: giteaHttp,
649
+ setBaseUrl,
650
+ logDetectedVersion,
651
+ checkNativeAutomerge,
652
+ minReviewerVersion: "1.14.0"
653
+ });
654
+ const { addAssignees, addReviewers, createPr, deleteLabel, ensureComment, ensureCommentRemoval, ensureIssue, ensureIssueClosing, findIssue, findPr, getBranchPr, getBranchStatus, getBranchStatusCheck, getIssue, getRawFile, getJsonFile, getIssueList, getPr, massageMarkdown, maxBodyLength, getPrList, getRepos, initPlatform, initRepo, mergePr, setBranchStatus, updatePr } = platform;
623
655
  //#endregion
624
- export { addAssignees, addReviewers, createPr, deleteLabel, ensureComment, ensureCommentRemoval, ensureIssue, ensureIssueClosing, findIssue, findPr, getBranchPr, getBranchStatus, getBranchStatusCheck, getIssue, getIssueList, getJsonFile, getPr, getPrList, getRawFile, getRepos, gitea_exports, id, initPlatform, initRepo, massageMarkdown, maxBodyLength, mergePr, resetPlatform, setBranchStatus, updatePr };
656
+ export { addAssignees, addReviewers, createPlatform, createPr, deleteLabel, ensureComment, ensureCommentRemoval, ensureIssue, ensureIssueClosing, findIssue, findPr, getBranchPr, getBranchStatus, getBranchStatusCheck, getIssue, getIssueList, getJsonFile, getPr, getPrList, getRawFile, getRepos, giteaHttp, gitea_exports, id, initPlatform, initRepo, massageMarkdown, maxBodyLength, mergePr, resetPlatform, setBranchStatus, updatePr };
625
657
 
626
658
  //# sourceMappingURL=index.js.map