renovate 41.40.0 → 41.41.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 (81) hide show
  1. package/dist/config/options/index.js +25 -11
  2. package/dist/config/options/index.js.map +1 -1
  3. package/dist/config/presets/forgejo/index.d.ts +5 -0
  4. package/dist/config/presets/forgejo/index.js +43 -0
  5. package/dist/config/presets/forgejo/index.js.map +1 -0
  6. package/dist/config/presets/index.js +6 -4
  7. package/dist/config/presets/index.js.map +1 -1
  8. package/dist/config/presets/local/index.js +2 -0
  9. package/dist/config/presets/local/index.js.map +1 -1
  10. package/dist/config/presets/parse.js +4 -0
  11. package/dist/config/presets/parse.js.map +1 -1
  12. package/dist/constants/platforms.d.ts +2 -1
  13. package/dist/constants/platforms.js +8 -1
  14. package/dist/constants/platforms.js.map +1 -1
  15. package/dist/modules/datasource/api.js +4 -0
  16. package/dist/modules/datasource/api.js.map +1 -1
  17. package/dist/modules/datasource/forgejo-releases/index.d.ts +17 -0
  18. package/dist/modules/datasource/forgejo-releases/index.js +80 -0
  19. package/dist/modules/datasource/forgejo-releases/index.js.map +1 -0
  20. package/dist/modules/datasource/forgejo-releases/schema.d.ts +39 -0
  21. package/dist/modules/datasource/forgejo-releases/schema.js +14 -0
  22. package/dist/modules/datasource/forgejo-releases/schema.js.map +1 -0
  23. package/dist/modules/datasource/forgejo-tags/index.d.ts +21 -0
  24. package/dist/modules/datasource/forgejo-tags/index.js +96 -0
  25. package/dist/modules/datasource/forgejo-tags/index.js.map +1 -0
  26. package/dist/modules/datasource/forgejo-tags/schema.d.ts +65 -0
  27. package/dist/modules/datasource/forgejo-tags/schema.js +19 -0
  28. package/dist/modules/datasource/forgejo-tags/schema.js.map +1 -0
  29. package/dist/modules/manager/fingerprint.generated.js +1 -1
  30. package/dist/modules/manager/fingerprint.generated.js.map +1 -1
  31. package/dist/modules/manager/github-actions/extract.js +8 -2
  32. package/dist/modules/manager/github-actions/extract.js.map +1 -1
  33. package/dist/modules/platform/api.d.ts +1 -1
  34. package/dist/modules/platform/api.js +2 -0
  35. package/dist/modules/platform/api.js.map +1 -1
  36. package/dist/modules/platform/forgejo/forgejo-helper.d.ts +36 -0
  37. package/dist/modules/platform/forgejo/forgejo-helper.js +282 -0
  38. package/dist/modules/platform/forgejo/forgejo-helper.js.map +1 -0
  39. package/dist/modules/platform/forgejo/index.d.ts +6 -0
  40. package/dist/modules/platform/forgejo/index.js +749 -0
  41. package/dist/modules/platform/forgejo/index.js.map +1 -0
  42. package/dist/modules/platform/forgejo/pr-cache.d.ts +23 -0
  43. package/dist/modules/platform/forgejo/pr-cache.js +134 -0
  44. package/dist/modules/platform/forgejo/pr-cache.js.map +1 -0
  45. package/dist/modules/platform/forgejo/types.d.ts +185 -0
  46. package/dist/modules/platform/forgejo/types.js +3 -0
  47. package/dist/modules/platform/forgejo/types.js.map +1 -0
  48. package/dist/modules/platform/forgejo/utils.d.ts +20 -0
  49. package/dist/modules/platform/forgejo/utils.js +142 -0
  50. package/dist/modules/platform/forgejo/utils.js.map +1 -0
  51. package/dist/modules/platform/gitea/index.js +1 -0
  52. package/dist/modules/platform/gitea/index.js.map +1 -1
  53. package/dist/modules/platform/scm.d.ts +1 -1
  54. package/dist/util/cache/package/namespaces.d.ts +1 -1
  55. package/dist/util/cache/package/namespaces.js +4 -0
  56. package/dist/util/cache/package/namespaces.js.map +1 -1
  57. package/dist/util/cache/repository/impl/base.d.ts +1 -1
  58. package/dist/util/cache/repository/types.d.ts +3 -0
  59. package/dist/util/cache/repository/types.js.map +1 -1
  60. package/dist/util/common.d.ts +1 -1
  61. package/dist/util/common.js +10 -3
  62. package/dist/util/common.js.map +1 -1
  63. package/dist/util/http/forgejo.d.ts +11 -0
  64. package/dist/util/http/forgejo.js +57 -0
  65. package/dist/util/http/forgejo.js.map +1 -0
  66. package/dist/workers/repository/update/pr/changelog/api.js +8 -6
  67. package/dist/workers/repository/update/pr/changelog/api.js.map +1 -1
  68. package/dist/workers/repository/update/pr/changelog/forgejo/index.d.ts +4 -0
  69. package/dist/workers/repository/update/pr/changelog/forgejo/index.js +62 -0
  70. package/dist/workers/repository/update/pr/changelog/forgejo/index.js.map +1 -0
  71. package/dist/workers/repository/update/pr/changelog/forgejo/source.d.ts +8 -0
  72. package/dist/workers/repository/update/pr/changelog/forgejo/source.js +20 -0
  73. package/dist/workers/repository/update/pr/changelog/forgejo/source.js.map +1 -0
  74. package/dist/workers/repository/update/pr/changelog/release-notes.js +17 -12
  75. package/dist/workers/repository/update/pr/changelog/release-notes.js.map +1 -1
  76. package/dist/workers/repository/update/pr/changelog/source.d.ts +1 -1
  77. package/dist/workers/repository/update/pr/changelog/source.js.map +1 -1
  78. package/dist/workers/repository/update/pr/changelog/types.d.ts +1 -1
  79. package/dist/workers/repository/update/pr/changelog/types.js.map +1 -1
  80. package/package.json +1 -1
  81. package/renovate-schema.json +1 -0
@@ -0,0 +1,749 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updatePr = exports.setBranchStatus = exports.mergePr = exports.initRepo = exports.initPlatform = exports.getRepos = exports.getPrList = exports.massageMarkdown = exports.getPr = exports.getIssueList = exports.getJsonFile = exports.getRawFile = exports.getIssue = exports.getBranchStatusCheck = exports.getBranchStatus = exports.getBranchPr = exports.findPr = exports.findIssue = exports.ensureIssueClosing = exports.ensureIssue = exports.ensureCommentRemoval = exports.ensureComment = exports.deleteLabel = exports.createPr = exports.addReviewers = exports.addAssignees = exports.id = void 0;
4
+ exports.resetPlatform = resetPlatform;
5
+ exports.maxBodyLength = maxBodyLength;
6
+ const tslib_1 = require("tslib");
7
+ const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
8
+ const semver_1 = tslib_1.__importDefault(require("semver"));
9
+ const error_messages_1 = require("../../../constants/error-messages");
10
+ const logger_1 = require("../../../logger");
11
+ const array_1 = require("../../../util/array");
12
+ const common_1 = require("../../../util/common");
13
+ const env_1 = require("../../../util/env");
14
+ const git = tslib_1.__importStar(require("../../../util/git"));
15
+ const forgejo_1 = require("../../../util/http/forgejo");
16
+ const promises_1 = require("../../../util/promises");
17
+ const sanitize_1 = require("../../../util/sanitize");
18
+ const url_1 = require("../../../util/url");
19
+ const pr_body_1 = require("../pr-body");
20
+ const util_1 = require("../util");
21
+ const pr_body_2 = require("../utils/pr-body");
22
+ const helper = tslib_1.__importStar(require("./forgejo-helper"));
23
+ const forgejo_helper_1 = require("./forgejo-helper");
24
+ const pr_cache_1 = require("./pr-cache");
25
+ const utils_1 = require("./utils");
26
+ exports.id = 'forgejo';
27
+ const defaults = {
28
+ hostType: 'forgejo',
29
+ endpoint: 'https://code.forgejo.org/',
30
+ version: '0.0.0',
31
+ };
32
+ let config = {};
33
+ let botUserID;
34
+ let botUserName;
35
+ function resetPlatform() {
36
+ config = {};
37
+ botUserID = undefined;
38
+ botUserName = undefined;
39
+ defaults.hostType = 'forgejo';
40
+ defaults.endpoint = 'https://code.forgejo.org/';
41
+ defaults.version = '0.0.0';
42
+ (0, forgejo_1.setBaseUrl)(defaults.endpoint);
43
+ }
44
+ function toRenovateIssue(data) {
45
+ return {
46
+ number: data.number,
47
+ state: data.state,
48
+ title: data.title,
49
+ body: data.body,
50
+ };
51
+ }
52
+ function matchesState(actual, expected) {
53
+ if (expected === 'all') {
54
+ return true;
55
+ }
56
+ if (expected.startsWith('!')) {
57
+ return actual !== expected.substring(1);
58
+ }
59
+ return actual === expected;
60
+ }
61
+ function findCommentByTopic(comments, topic) {
62
+ return comments.find((c) => c.body.startsWith(`### ${topic}\n\n`)) ?? null;
63
+ }
64
+ function findCommentByContent(comments, content) {
65
+ return comments.find((c) => c.body.trim() === content) ?? null;
66
+ }
67
+ function getLabelList() {
68
+ if (config.labelList === null) {
69
+ const repoLabels = helper
70
+ .getRepoLabels(config.repository, {
71
+ memCache: false,
72
+ })
73
+ .then((labels) => {
74
+ logger_1.logger.debug(`Retrieved ${labels.length} repo labels`);
75
+ return labels;
76
+ });
77
+ const orgLabels = helper
78
+ .getOrgLabels(config.repository.split('/')[0], {
79
+ memCache: false,
80
+ })
81
+ .then((labels) => {
82
+ logger_1.logger.debug(`Retrieved ${labels.length} org labels`);
83
+ return labels;
84
+ })
85
+ .catch((err) => {
86
+ // Will fail if owner of repo is not org or Forgejo version < 1.12
87
+ logger_1.logger.debug(`Unable to fetch organization labels`);
88
+ return [];
89
+ });
90
+ config.labelList = Promise.all([repoLabels, orgLabels]).then((labels) => [].concat(...labels));
91
+ }
92
+ return config.labelList;
93
+ }
94
+ async function lookupLabelByName(name) {
95
+ logger_1.logger.debug(`lookupLabelByName(${name})`);
96
+ const labelList = await getLabelList();
97
+ return labelList.find((l) => l.name === name)?.id ?? null;
98
+ }
99
+ async function fetchRepositories({ topic, sort, order, }) {
100
+ const repos = await helper.searchRepos({
101
+ uid: botUserID,
102
+ archived: false,
103
+ ...(topic && {
104
+ topic: true,
105
+ q: topic,
106
+ }),
107
+ ...(sort && {
108
+ sort,
109
+ }),
110
+ ...(order && {
111
+ order,
112
+ }),
113
+ });
114
+ return repos.filter(utils_1.usableRepo).map((r) => r.full_name);
115
+ }
116
+ const platform = {
117
+ async initPlatform({ endpoint, token, }) {
118
+ if (!token) {
119
+ throw new Error('Init: You must configure a Forgejo personal access token');
120
+ }
121
+ if (endpoint) {
122
+ let baseEndpoint = (0, utils_1.trimTrailingApiPath)(endpoint);
123
+ baseEndpoint = (0, url_1.ensureTrailingSlash)(baseEndpoint);
124
+ defaults.endpoint = baseEndpoint;
125
+ }
126
+ else {
127
+ logger_1.logger.debug('Using default Forgejo endpoint: ' + defaults.endpoint);
128
+ }
129
+ (0, forgejo_1.setBaseUrl)(defaults.endpoint);
130
+ let gitAuthor;
131
+ try {
132
+ const user = await helper.getCurrentUser({ token });
133
+ gitAuthor = `${user.full_name ?? user.username} <${user.email}>`;
134
+ botUserID = user.id;
135
+ botUserName = user.username;
136
+ const env = (0, env_1.getEnv)();
137
+ /* v8 ignore start: experimental feature */
138
+ if (semver_1.default.valid(env.RENOVATE_X_PLATFORM_VERSION)) {
139
+ defaults.version = env.RENOVATE_X_PLATFORM_VERSION;
140
+ } /* v8 ignore stop */
141
+ else {
142
+ defaults.version = await helper.getVersion({ token });
143
+ }
144
+ logger_1.logger.debug(`Forgejo version: ${defaults.version}`);
145
+ }
146
+ catch (err) {
147
+ logger_1.logger.debug({ err }, 'Error authenticating with Forgejo. Check your token');
148
+ throw new Error('Init: Authentication failure');
149
+ }
150
+ return {
151
+ endpoint: defaults.endpoint,
152
+ gitAuthor,
153
+ };
154
+ },
155
+ async getRawFile(fileName, repoName, branchOrTag) {
156
+ const repo = repoName ?? config.repository;
157
+ const contents = await helper.getRepoContents(repo, fileName, branchOrTag);
158
+ return contents.contentString ?? null;
159
+ },
160
+ async getJsonFile(fileName, repoName, branchOrTag) {
161
+ // TODO #22198
162
+ const raw = await platform.getRawFile(fileName, repoName, branchOrTag);
163
+ return (0, common_1.parseJson)(raw, fileName);
164
+ },
165
+ async initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, gitUrl, ignorePrAuthor, }) {
166
+ let repo;
167
+ config = {};
168
+ config.repository = repository;
169
+ config.cloneSubmodules = !!cloneSubmodules;
170
+ config.cloneSubmodulesFilter = cloneSubmodulesFilter;
171
+ config.ignorePrAuthor = !!ignorePrAuthor;
172
+ // Try to fetch information about repository
173
+ try {
174
+ repo = await helper.getRepo(repository);
175
+ }
176
+ catch (err) {
177
+ logger_1.logger.debug({ err }, 'Unknown Forgejo initRepo error');
178
+ throw err;
179
+ }
180
+ // Ensure appropriate repository state and permissions
181
+ if (repo.archived) {
182
+ logger_1.logger.debug('Repository is archived - aborting renovation');
183
+ throw new Error(error_messages_1.REPOSITORY_ARCHIVED);
184
+ }
185
+ if (repo.mirror) {
186
+ logger_1.logger.debug('Repository is a mirror - aborting renovation');
187
+ throw new Error(error_messages_1.REPOSITORY_MIRRORED);
188
+ }
189
+ if (repo.permissions.pull === false || repo.permissions.push === false) {
190
+ logger_1.logger.debug('Repository does not permit pull or push - aborting renovation');
191
+ throw new Error(error_messages_1.REPOSITORY_ACCESS_FORBIDDEN);
192
+ }
193
+ if (repo.empty) {
194
+ logger_1.logger.debug('Repository is empty - aborting renovation');
195
+ throw new Error(error_messages_1.REPOSITORY_EMPTY);
196
+ }
197
+ if (repo.has_pull_requests === false) {
198
+ logger_1.logger.debug('Repo has disabled pull requests - aborting renovation');
199
+ throw new Error(error_messages_1.REPOSITORY_BLOCKED);
200
+ }
201
+ if (repo.allow_rebase && repo.default_merge_style === 'rebase') {
202
+ config.mergeMethod = 'rebase';
203
+ }
204
+ else if (repo.allow_rebase_explicit &&
205
+ repo.default_merge_style === 'rebase-merge') {
206
+ config.mergeMethod = 'rebase-merge';
207
+ }
208
+ else if (repo.allow_squash_merge &&
209
+ repo.default_merge_style === 'squash') {
210
+ config.mergeMethod = 'squash';
211
+ }
212
+ else if (repo.allow_merge_commits &&
213
+ repo.default_merge_style === 'merge') {
214
+ config.mergeMethod = 'merge';
215
+ }
216
+ else if (repo.allow_fast_forward_only_merge &&
217
+ repo.default_merge_style === 'fast-forward-only') {
218
+ config.mergeMethod = 'fast-forward-only';
219
+ }
220
+ else {
221
+ logger_1.logger.debug('Repository has no allowed merge methods - aborting renovation');
222
+ throw new Error(error_messages_1.REPOSITORY_BLOCKED);
223
+ }
224
+ // Determine author email and branches
225
+ config.defaultBranch = repo.default_branch;
226
+ logger_1.logger.debug(`${repository} default branch = ${config.defaultBranch}`);
227
+ const url = (0, utils_1.getRepoUrl)(repo, gitUrl, defaults.endpoint);
228
+ // Initialize Git storage
229
+ await git.initRepo({
230
+ ...config,
231
+ url,
232
+ });
233
+ // Reset cached resources
234
+ config.issueList = null;
235
+ config.labelList = null;
236
+ config.hasIssuesEnabled = !repo.external_tracker && repo.has_issues;
237
+ return {
238
+ defaultBranch: config.defaultBranch,
239
+ isFork: !!repo.fork,
240
+ repoFingerprint: (0, util_1.repoFingerprint)(repo.id, defaults.endpoint),
241
+ };
242
+ },
243
+ async getRepos(config) {
244
+ logger_1.logger.debug('Auto-discovering Forgejo repositories');
245
+ try {
246
+ if (config?.topics) {
247
+ logger_1.logger.debug({ topics: config.topics }, 'Auto-discovering by topics');
248
+ const fetchRepoArgs = config.topics.map((topic) => {
249
+ return {
250
+ topic,
251
+ sort: config.sort,
252
+ order: config.order,
253
+ };
254
+ });
255
+ const repos = await (0, promises_1.map)(fetchRepoArgs, fetchRepositories);
256
+ return (0, array_1.deduplicateArray)(repos.flat());
257
+ }
258
+ else if (config?.namespaces) {
259
+ logger_1.logger.debug({ namespaces: config.namespaces }, 'Auto-discovering by organization');
260
+ const repos = await (0, promises_1.map)(config.namespaces, async (organization) => {
261
+ const orgRepos = await helper.orgListRepos(organization);
262
+ return orgRepos
263
+ .filter((r) => !r.mirror && !r.archived)
264
+ .map((r) => r.full_name);
265
+ });
266
+ return (0, array_1.deduplicateArray)(repos.flat());
267
+ }
268
+ else {
269
+ return await fetchRepositories({
270
+ sort: config?.sort,
271
+ order: config?.order,
272
+ });
273
+ }
274
+ }
275
+ catch (err) {
276
+ logger_1.logger.error({ err }, 'Forgejo getRepos() error');
277
+ throw err;
278
+ }
279
+ },
280
+ async setBranchStatus({ branchName, context, description, state, url: target_url, }) {
281
+ try {
282
+ // Create new status for branch commit
283
+ const branchCommit = git.getBranchCommit(branchName);
284
+ // TODO: check branchCommit
285
+ await helper.createCommitStatus(config.repository, branchCommit, {
286
+ state: helper.renovateToForgejoStatusMapping[state] || 'pending',
287
+ context,
288
+ description,
289
+ ...(target_url && { target_url }),
290
+ });
291
+ // Refresh caches by re-fetching commit status for branch
292
+ await helper.getCombinedCommitStatus(config.repository, branchName, {
293
+ memCache: false,
294
+ });
295
+ }
296
+ catch (err) {
297
+ logger_1.logger.warn({ err }, 'Failed to set branch status');
298
+ }
299
+ },
300
+ async getBranchStatus(branchName, internalChecksAsSuccess) {
301
+ let ccs;
302
+ try {
303
+ ccs = await helper.getCombinedCommitStatus(config.repository, branchName);
304
+ }
305
+ catch (err) {
306
+ if (err.statusCode === 404) {
307
+ logger_1.logger.debug('Received 404 when checking branch status, assuming branch deletion');
308
+ throw new Error(error_messages_1.REPOSITORY_CHANGED);
309
+ }
310
+ logger_1.logger.debug('Unknown error when checking branch status');
311
+ throw err;
312
+ }
313
+ logger_1.logger.debug({ ccs }, 'Branch status check result');
314
+ if (!internalChecksAsSuccess &&
315
+ ccs.worstStatus === 'success' &&
316
+ ccs.statuses.every((status) => status.context?.startsWith('renovate/'))) {
317
+ logger_1.logger.debug('Successful checks are all internal renovate/ checks, so returning "pending" branch status');
318
+ return 'yellow';
319
+ }
320
+ /* v8 ignore next */
321
+ return helper.forgejoToRenovateStatusMapping[ccs.worstStatus] ?? 'yellow';
322
+ },
323
+ async getBranchStatusCheck(branchName, context) {
324
+ const ccs = await helper.getCombinedCommitStatus(config.repository, branchName);
325
+ const cs = ccs.statuses.find((s) => s.context === context);
326
+ if (!cs) {
327
+ return null;
328
+ } // no status check exists
329
+ const status = helper.forgejoToRenovateStatusMapping[cs.status];
330
+ if (status) {
331
+ return status;
332
+ }
333
+ logger_1.logger.warn({ check: cs }, 'Could not map Forgejo status value to Renovate status');
334
+ return 'yellow';
335
+ },
336
+ getPrList() {
337
+ return pr_cache_1.ForgejoPrCache.getPrs(forgejo_helper_1.forgejoHttp, config.repository, config.ignorePrAuthor, botUserName);
338
+ },
339
+ async getPr(number) {
340
+ // Search for pull request in cached list or attempt to query directly
341
+ const prList = await platform.getPrList();
342
+ let pr = prList.find((p) => p.number === number) ?? null;
343
+ if (pr) {
344
+ logger_1.logger.debug('Returning from cached PRs');
345
+ }
346
+ else {
347
+ logger_1.logger.debug('PR not found in cached PRs - trying to fetch directly');
348
+ const gpr = await helper.getPR(config.repository, number);
349
+ pr = (0, utils_1.toRenovatePR)(gpr, botUserName);
350
+ // Add pull request to cache for further lookups / queries
351
+ if (pr) {
352
+ await pr_cache_1.ForgejoPrCache.setPr(forgejo_helper_1.forgejoHttp, config.repository, config.ignorePrAuthor, botUserName, pr);
353
+ }
354
+ }
355
+ // Abort and return null if no match was found
356
+ if (!pr) {
357
+ return null;
358
+ }
359
+ return pr;
360
+ },
361
+ async findPr({ branchName, prTitle: title, state = 'all', includeOtherAuthors, targetBranch, }) {
362
+ logger_1.logger.debug(`findPr(${branchName}, ${title}, ${state})`);
363
+ if (includeOtherAuthors && is_1.default.string(targetBranch)) {
364
+ // do not use pr cache as it only fetches prs created by the bot account
365
+ const pr = await helper.getPRByBranch(config.repository, targetBranch, branchName);
366
+ if (!pr) {
367
+ return null;
368
+ }
369
+ return (0, utils_1.toRenovatePR)(pr, null);
370
+ }
371
+ const prList = await platform.getPrList();
372
+ const pr = prList.find((p) => p.sourceRepo === config.repository &&
373
+ p.sourceBranch === branchName &&
374
+ matchesState(p.state, state) &&
375
+ (!title || p.title === title));
376
+ if (pr) {
377
+ logger_1.logger.debug(`Found PR #${pr.number}`);
378
+ }
379
+ return pr ?? null;
380
+ },
381
+ async createPr({ sourceBranch, targetBranch, prTitle, prBody: rawBody, labels: labelNames, platformPrOptions, draftPR, }) {
382
+ let title = prTitle;
383
+ const base = targetBranch;
384
+ const head = sourceBranch;
385
+ const body = (0, sanitize_1.sanitize)(rawBody);
386
+ if (draftPR) {
387
+ title = utils_1.DRAFT_PREFIX + title;
388
+ }
389
+ logger_1.logger.debug(`Creating pull request: ${title} (${head} => ${base})`);
390
+ try {
391
+ const labels = Array.isArray(labelNames)
392
+ ? await (0, promises_1.map)(labelNames, lookupLabelByName)
393
+ : [];
394
+ const gpr = await helper.createPR(config.repository, {
395
+ base,
396
+ head,
397
+ title,
398
+ body,
399
+ labels: labels.filter(is_1.default.number),
400
+ });
401
+ if (platformPrOptions?.usePlatformAutomerge) {
402
+ // Only Forgejo v10.0.0+ support delete_branch_after_merge.
403
+ // This is required to not have undesired behavior when renovate finds existing branches on next run.
404
+ // Codeberg usees git versioning like `11.0.1-99-c504062+gitea-1.22.0` so allow any version >= 10.0.0-0.
405
+ if (semver_1.default.gte(defaults.version, '10.0.0-0')) {
406
+ try {
407
+ await helper.mergePR(config.repository, gpr.number, {
408
+ Do: (0, utils_1.getMergeMethod)(platformPrOptions?.automergeStrategy) ??
409
+ config.mergeMethod,
410
+ merge_when_checks_succeed: true,
411
+ delete_branch_after_merge: true,
412
+ });
413
+ logger_1.logger.debug({ prNumber: gpr.number }, 'Forgejo-native automerge: success');
414
+ }
415
+ catch (err) {
416
+ logger_1.logger.warn({ err, prNumber: gpr.number }, 'Forgejo-native automerge: fail');
417
+ }
418
+ }
419
+ else {
420
+ logger_1.logger.debug({ prNumber: gpr.number }, `Forgejo-native automerge: not supported on this version of Forgejo. Use 10.0.0 or newer.`);
421
+ }
422
+ }
423
+ const pr = (0, utils_1.toRenovatePR)(gpr, botUserName);
424
+ if (!pr) {
425
+ throw new Error('Can not parse newly created Pull Request');
426
+ }
427
+ await pr_cache_1.ForgejoPrCache.setPr(forgejo_helper_1.forgejoHttp, config.repository, config.ignorePrAuthor, botUserName, pr);
428
+ return pr;
429
+ }
430
+ catch (err) {
431
+ // When the user manually deletes a branch from Renovate, the PR remains but is no longer linked to any branch. In
432
+ // the most recent versions of Forgejo, the PR gets automatically closed when that happens, but older versions do
433
+ // not handle this properly and keep the PR open. As pushing a branch with the same name resurrects the PR, this
434
+ // would cause a HTTP 409 conflict error, which we hereby gracefully handle.
435
+ if (err.statusCode === 409) {
436
+ logger_1.logger.warn({ prTitle: title, sourceBranch }, 'Attempting to gracefully recover from 409 Conflict response in createPr()');
437
+ // Refresh cached PR list and search for pull request with matching information
438
+ pr_cache_1.ForgejoPrCache.forceSync();
439
+ const pr = await platform.findPr({
440
+ branchName: sourceBranch,
441
+ state: 'open',
442
+ });
443
+ // If a valid PR was found, return and gracefully recover from the error. Otherwise, abort and throw error.
444
+ if (pr?.bodyStruct) {
445
+ if (pr.title !== title || pr.bodyStruct.hash !== (0, pr_body_1.hashBody)(body)) {
446
+ logger_1.logger.debug(`Recovered from 409 Conflict, but PR for ${sourceBranch} is outdated. Updating...`);
447
+ await platform.updatePr({
448
+ number: pr.number,
449
+ prTitle: title,
450
+ prBody: body,
451
+ });
452
+ pr.title = title;
453
+ pr.bodyStruct = (0, pr_body_1.getPrBodyStruct)(body);
454
+ }
455
+ else {
456
+ logger_1.logger.debug(`Recovered from 409 Conflict and PR for ${sourceBranch} is up-to-date`);
457
+ }
458
+ return pr;
459
+ }
460
+ }
461
+ throw err;
462
+ }
463
+ },
464
+ async updatePr({ number, prTitle, prBody: body, labels, state, targetBranch, }) {
465
+ let title = prTitle;
466
+ if ((await (0, exports.getPrList)()).find((pr) => pr.number === number)?.isDraft) {
467
+ title = utils_1.DRAFT_PREFIX + title;
468
+ }
469
+ const prUpdateParams = {
470
+ title,
471
+ ...(body && { body }),
472
+ ...(state && { state }),
473
+ };
474
+ if (targetBranch) {
475
+ prUpdateParams.base = targetBranch;
476
+ }
477
+ /**
478
+ * Update PR labels.
479
+ * In the Forgejo API, labels are replaced on each update if the field is present.
480
+ * If the field is not present (i.e., undefined), labels aren't updated.
481
+ * However, the labels array must contain label IDs instead of names,
482
+ * so a lookup is performed to fetch the details (including the ID) of each label.
483
+ */
484
+ if (Array.isArray(labels)) {
485
+ prUpdateParams.labels = (await (0, promises_1.map)(labels, lookupLabelByName)).filter(is_1.default.number);
486
+ if (labels.length !== prUpdateParams.labels.length) {
487
+ logger_1.logger.warn('Some labels could not be looked up. Renovate may halt label updates assuming changes by others.');
488
+ }
489
+ }
490
+ const gpr = await helper.updatePR(config.repository, number, prUpdateParams);
491
+ const pr = (0, utils_1.toRenovatePR)(gpr, botUserName);
492
+ if (pr) {
493
+ await pr_cache_1.ForgejoPrCache.setPr(forgejo_helper_1.forgejoHttp, config.repository, config.ignorePrAuthor, botUserName, pr);
494
+ }
495
+ },
496
+ async mergePr({ id, strategy }) {
497
+ try {
498
+ await helper.mergePR(config.repository, id, {
499
+ Do: (0, utils_1.getMergeMethod)(strategy) ?? config.mergeMethod,
500
+ });
501
+ return true;
502
+ }
503
+ catch (err) {
504
+ logger_1.logger.warn({ err, id }, 'Merging of PR failed');
505
+ return false;
506
+ }
507
+ },
508
+ getIssueList() {
509
+ if (config.hasIssuesEnabled === false) {
510
+ return Promise.resolve([]);
511
+ }
512
+ config.issueList ??= helper
513
+ .searchIssues(config.repository, { state: 'all' }, { memCache: false })
514
+ .then((issues) => {
515
+ const issueList = issues.map(toRenovateIssue);
516
+ logger_1.logger.debug(`Retrieved ${issueList.length} Issues`);
517
+ return issueList;
518
+ });
519
+ return config.issueList;
520
+ },
521
+ async getIssue(number, memCache = true) {
522
+ if (config.hasIssuesEnabled === false) {
523
+ return null;
524
+ }
525
+ try {
526
+ const body = (await helper.getIssue(config.repository, number, { memCache })).body;
527
+ return {
528
+ number,
529
+ body,
530
+ };
531
+ }
532
+ catch (err) /* v8 ignore start */ {
533
+ logger_1.logger.debug({ err, number }, 'Error getting issue');
534
+ return null;
535
+ } /* v8 ignore stop */
536
+ },
537
+ async findIssue(title) {
538
+ const issueList = await platform.getIssueList();
539
+ const issue = issueList.find((i) => i.state === 'open' && i.title === title);
540
+ if (!issue) {
541
+ return null;
542
+ }
543
+ // TODO: types (#22198)
544
+ logger_1.logger.debug(`Found Issue #${issue.number}`);
545
+ // TODO #22198
546
+ return exports.getIssue(issue.number);
547
+ },
548
+ async ensureIssue({ title, reuseTitle, body: content, labels: labelNames, shouldReOpen, once, }) {
549
+ logger_1.logger.debug(`ensureIssue(${title})`);
550
+ if (config.hasIssuesEnabled === false) {
551
+ logger_1.logger.info('Cannot ensure issue because issues are disabled in this repository');
552
+ return null;
553
+ }
554
+ try {
555
+ const body = (0, utils_1.smartLinks)(content);
556
+ const issueList = await platform.getIssueList();
557
+ let issues = issueList.filter((i) => i.title === title);
558
+ if (!issues.length) {
559
+ issues = issueList.filter((i) => i.title === reuseTitle);
560
+ }
561
+ const labels = Array.isArray(labelNames)
562
+ ? (await Promise.all(labelNames.map(lookupLabelByName))).filter(is_1.default.number)
563
+ : undefined;
564
+ // Update any matching issues which currently exist
565
+ if (issues.length) {
566
+ let activeIssue = issues.find((i) => i.state === 'open');
567
+ // If no active issue was found, decide if it shall be skipped, re-opened or updated without state change
568
+ if (!activeIssue) {
569
+ if (once) {
570
+ logger_1.logger.debug('Issue already closed - skipping update');
571
+ return null;
572
+ }
573
+ if (shouldReOpen) {
574
+ logger_1.logger.debug('Reopening previously closed Issue');
575
+ }
576
+ // Pick the last issue in the list as the active one
577
+ activeIssue = issues[issues.length - 1];
578
+ }
579
+ // Close any duplicate issues
580
+ for (const issue of issues) {
581
+ if (issue.state === 'open' && issue.number !== activeIssue.number) {
582
+ // TODO: types (#22198)
583
+ logger_1.logger.warn({ issueNo: issue.number }, 'Closing duplicate issue');
584
+ // TODO #22198
585
+ await helper.closeIssue(config.repository, issue.number);
586
+ }
587
+ }
588
+ // Check if issue has already correct state
589
+ if (activeIssue.title === title &&
590
+ activeIssue.body === body &&
591
+ activeIssue.state === 'open') {
592
+ logger_1.logger.debug(
593
+ // TODO: types (#22198)
594
+ `Issue #${activeIssue.number} is open and up to date - nothing to do`);
595
+ return null;
596
+ }
597
+ // Update issue body and re-open if enabled
598
+ // TODO: types (#22198)
599
+ logger_1.logger.debug(`Updating Issue #${activeIssue.number}`);
600
+ const existingIssue = await helper.updateIssue(config.repository,
601
+ // TODO #22198
602
+ activeIssue.number, {
603
+ body,
604
+ title,
605
+ state: shouldReOpen ? 'open' : activeIssue.state,
606
+ });
607
+ // Test whether the issues need to be updated
608
+ const existingLabelIds = (existingIssue.labels ?? []).map((label) => label.id);
609
+ if (labels &&
610
+ (labels.length !== existingLabelIds.length ||
611
+ labels.filter((labelId) => !existingLabelIds.includes(labelId))
612
+ .length !== 0)) {
613
+ await helper.updateIssueLabels(config.repository,
614
+ // TODO #22198
615
+ activeIssue.number, {
616
+ labels,
617
+ });
618
+ }
619
+ return 'updated';
620
+ }
621
+ // Create new issue and reset cache
622
+ const issue = await helper.createIssue(config.repository, {
623
+ body,
624
+ title,
625
+ labels,
626
+ });
627
+ logger_1.logger.debug(`Created new Issue #${issue.number}`);
628
+ config.issueList = null;
629
+ return 'created';
630
+ }
631
+ catch (err) {
632
+ logger_1.logger.warn({ err }, 'Could not ensure issue');
633
+ }
634
+ return null;
635
+ },
636
+ async ensureIssueClosing(title) {
637
+ logger_1.logger.debug(`ensureIssueClosing(${title})`);
638
+ if (config.hasIssuesEnabled === false) {
639
+ return;
640
+ }
641
+ const issueList = await platform.getIssueList();
642
+ for (const issue of issueList) {
643
+ if (issue.state === 'open' && issue.title === title) {
644
+ logger_1.logger.debug(`Closing issue...issueNo: ${issue.number}`);
645
+ // TODO #22198
646
+ await helper.closeIssue(config.repository, issue.number);
647
+ }
648
+ }
649
+ },
650
+ async deleteLabel(issue, labelName) {
651
+ logger_1.logger.debug(`Deleting label ${labelName} from Issue #${issue}`);
652
+ const label = await lookupLabelByName(labelName);
653
+ if (label) {
654
+ await helper.unassignLabel(config.repository, issue, label);
655
+ }
656
+ else {
657
+ logger_1.logger.warn({ issue, labelName }, 'Failed to lookup label for deletion');
658
+ }
659
+ },
660
+ async ensureComment({ number: issue, topic, content, }) {
661
+ try {
662
+ let body = (0, sanitize_1.sanitize)(content);
663
+ const commentList = await helper.getComments(config.repository, issue);
664
+ // Search comment by either topic or exact body
665
+ let comment = null;
666
+ if (topic) {
667
+ comment = findCommentByTopic(commentList, topic);
668
+ body = `### ${topic}\n\n${body}`;
669
+ }
670
+ else {
671
+ comment = findCommentByContent(commentList, body);
672
+ }
673
+ // Create a new comment if no match has been found, otherwise update if necessary
674
+ if (!comment) {
675
+ comment = await helper.createComment(config.repository, issue, body);
676
+ logger_1.logger.info({ repository: config.repository, issue, comment: comment.id }, 'Comment added');
677
+ }
678
+ else if (comment.body === body) {
679
+ logger_1.logger.debug(`Comment #${comment.id} is already up-to-date`);
680
+ }
681
+ else {
682
+ await helper.updateComment(config.repository, comment.id, body);
683
+ logger_1.logger.debug({ repository: config.repository, issue, comment: comment.id }, 'Comment updated');
684
+ }
685
+ return true;
686
+ }
687
+ catch (err) {
688
+ logger_1.logger.warn({ err, issue, subject: topic }, 'Error ensuring comment');
689
+ return false;
690
+ }
691
+ },
692
+ async ensureCommentRemoval(deleteConfig) {
693
+ const { number: issue } = deleteConfig;
694
+ const key = deleteConfig.type === 'by-topic'
695
+ ? deleteConfig.topic
696
+ : deleteConfig.content;
697
+ logger_1.logger.debug(`Ensuring comment "${key}" in #${issue} is removed`);
698
+ const commentList = await helper.getComments(config.repository, issue);
699
+ let comment = null;
700
+ if (deleteConfig.type === 'by-topic') {
701
+ comment = findCommentByTopic(commentList, deleteConfig.topic);
702
+ }
703
+ else if (deleteConfig.type === 'by-content') {
704
+ const body = (0, sanitize_1.sanitize)(deleteConfig.content);
705
+ comment = findCommentByContent(commentList, body);
706
+ }
707
+ // Abort and do nothing if no matching comment was found
708
+ if (!comment) {
709
+ return;
710
+ }
711
+ // Try to delete comment
712
+ try {
713
+ await helper.deleteComment(config.repository, comment.id);
714
+ }
715
+ catch (err) {
716
+ logger_1.logger.warn({ err, issue, config: deleteConfig }, 'Error deleting comment');
717
+ }
718
+ },
719
+ async getBranchPr(branchName) {
720
+ logger_1.logger.debug(`getBranchPr(${branchName})`);
721
+ const pr = await platform.findPr({ branchName, state: 'open' });
722
+ return pr ? platform.getPr(pr.number) : null;
723
+ },
724
+ async addAssignees(number, assignees) {
725
+ logger_1.logger.debug(`Updating assignees '${assignees?.join(', ')}' on Issue #${number}`);
726
+ await helper.updateIssue(config.repository, number, {
727
+ assignees,
728
+ });
729
+ },
730
+ async addReviewers(number, reviewers) {
731
+ logger_1.logger.debug(`Adding reviewers '${reviewers?.join(', ')}' to #${number}`);
732
+ try {
733
+ await helper.requestPrReviewers(config.repository, number, { reviewers });
734
+ }
735
+ catch (err) {
736
+ logger_1.logger.warn({ err, number, reviewers }, 'Failed to assign reviewer');
737
+ }
738
+ },
739
+ massageMarkdown(prBody) {
740
+ return (0, pr_body_2.smartTruncate)((0, utils_1.smartLinks)(prBody), maxBodyLength());
741
+ },
742
+ maxBodyLength,
743
+ };
744
+ function maxBodyLength() {
745
+ return 1000000;
746
+ }
747
+ /* eslint-disable @typescript-eslint/unbound-method */
748
+ exports.addAssignees = platform.addAssignees, exports.addReviewers = platform.addReviewers, exports.createPr = platform.createPr, exports.deleteLabel = platform.deleteLabel, exports.ensureComment = platform.ensureComment, exports.ensureCommentRemoval = platform.ensureCommentRemoval, exports.ensureIssue = platform.ensureIssue, exports.ensureIssueClosing = platform.ensureIssueClosing, exports.findIssue = platform.findIssue, exports.findPr = platform.findPr, exports.getBranchPr = platform.getBranchPr, exports.getBranchStatus = platform.getBranchStatus, exports.getBranchStatusCheck = platform.getBranchStatusCheck, exports.getIssue = platform.getIssue, exports.getRawFile = platform.getRawFile, exports.getJsonFile = platform.getJsonFile, exports.getIssueList = platform.getIssueList, exports.getPr = platform.getPr, exports.massageMarkdown = platform.massageMarkdown, exports.getPrList = platform.getPrList, exports.getRepos = platform.getRepos, exports.initPlatform = platform.initPlatform, exports.initRepo = platform.initRepo, exports.mergePr = platform.mergePr, exports.setBranchStatus = platform.setBranchStatus, exports.updatePr = platform.updatePr;
749
+ //# sourceMappingURL=index.js.map