diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6bd0340172e6d..00db47ccbe8432 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -406,7 +406,7 @@ jobs: merge-multiple: true - name: Codecov - uses: codecov/codecov-action@84508663e988701840491b86de86b666e8a86bed # v4.3.0 + uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} directory: coverage/lcov @@ -558,6 +558,9 @@ jobs: - name: Build run: pnpm build:docs + - name: Test docs + run: pnpm test:docs + - name: Upload uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: @@ -629,7 +632,7 @@ jobs: show-progress: false - name: docker-config - uses: containerbase/internal-tools@75d68da3c8cd3d622cf6d4b8d2dfe7e4dc33dfed # v3.0.78 + uses: containerbase/internal-tools@dbc381e7a824b3777dceafc226804644311ab9a1 # v3.0.82 with: command: docker-config diff --git a/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap b/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap index a0082574098a51..1606ceab4e474e 100644 --- a/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/gitlab/__snapshots__/index.spec.ts.snap @@ -232,6 +232,7 @@ exports[`modules/platform/gitlab/index initRepo should fall back respecting when "defaultBranch": "master", "ignorePrAuthor": undefined, "mergeMethod": "merge", + "mergeRequestRepository": "some%2Frepo%2Fproject", "repository": "some%2Frepo%2Fproject", "url": "http://oauth2:123test@mycompany.com/gitlab/some/repo/project.git", }, @@ -247,6 +248,7 @@ exports[`modules/platform/gitlab/index initRepo should use ssh_url_to_repo if gi "defaultBranch": "master", "ignorePrAuthor": undefined, "mergeMethod": "merge", + "mergeRequestRepository": "some%2Frepo%2Fproject", "repository": "some%2Frepo%2Fproject", "url": "ssh://git@gitlab.com/some%2Frepo%2Fproject.git", }, diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index e5b1e89368c945..c350402bfbfa61 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -83,6 +83,7 @@ let config: { cloneSubmodules: boolean | undefined; ignorePrAuthor: boolean | undefined; squash: boolean; + mergeRequestRepository: string; } = {} as any; const defaults = { @@ -323,7 +324,6 @@ export async function initRepo({ ); throw new Error(REPOSITORY_ARCHIVED); } - if (res.body.mirror && includeMirrors !== true) { logger.debug( 'Repository is a mirror - throwing error to abort renovation', @@ -359,6 +359,8 @@ export async function initRepo({ } logger.debug(`${repository} default branch = ${config.defaultBranch}`); delete config.prList; + config.mergeRequestRepository = + res.body.forked_from_project?.id ?? config.repository; logger.debug('Enabling Git FS'); const url = getRepoUrl(repository, gitUrl, res); await git.initRepo({ @@ -555,7 +557,7 @@ async function fetchPrList(): Promise { searchParams.scope = 'created_by_me'; } const query = getQueryString(searchParams); - const urlString = `projects/${config.repository}/merge_requests?${query}`; + const urlString = `projects/${config.mergeRequestRepository}/merge_requests?${query}`; try { const res = await gitlabApi.getJson< { @@ -593,7 +595,7 @@ export async function getPrList(): Promise { async function ignoreApprovals(pr: number): Promise { try { - const url = `projects/${config.repository}/merge_requests/${pr}/approval_rules`; + const url = `projects/${config.mergeRequestRepository}/merge_requests/${pr}/approval_rules`; const { body: rules } = await gitlabApi.getJson< { name: string; @@ -684,7 +686,7 @@ async function tryPrAutomerge( pipeline: { status: string; }; - }>(`projects/${config.repository}/merge_requests/${pr}`, { + }>(`projects/${config.mergeRequestRepository}/merge_requests/${pr}`, { memCache: false, }); // detailed_merge_status is available with Gitlab >=15.6.0 @@ -713,7 +715,7 @@ async function tryPrAutomerge( for (let attempt = 1; attempt <= retryTimes; attempt += 1) { try { await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${pr}/merge`, + `projects/${config.mergeRequestRepository}/merge_requests/${pr}/merge`, { body: { should_remove_source_branch: true, @@ -739,7 +741,7 @@ async function tryPrAutomerge( async function approvePr(pr: number): Promise { try { await gitlabApi.postJson( - `projects/${config.repository}/merge_requests/${pr}/approve`, + `projects/${config.mergeRequestRepository}/merge_requests/${pr}/approve`, ); } catch (err) { logger.warn({ err }, 'GitLab: Error approving merge request'); @@ -761,6 +763,9 @@ export async function createPr({ } const description = sanitize(rawDescription); logger.debug(`Creating Merge Request: ${title}`); + // the creation is always against the repository with the branch + // but if the target_project_id is forked_from_project, then the Merge + // Request will end up in that repository const res = await gitlabApi.postJson( `projects/${config.repository}/merge_requests`, { @@ -772,6 +777,7 @@ export async function createPr({ description, labels: (labels ?? []).join(','), squash: config.squash, + target_project_id: config.mergeRequestRepository, }, }, ); @@ -794,7 +800,7 @@ export async function createPr({ export async function getPr(iid: number): Promise { logger.debug(`getPr(${iid})`); - const mr = await getMR(config.repository, iid); + const mr = await getMR(config.mergeRequestRepository, iid); // Harmonize fields with GitHub const pr: GitlabPr = { @@ -852,7 +858,7 @@ export async function updatePr({ } await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${iid}`, + `projects/${config.mergeRequestRepository}/merge_requests/${iid}`, { body }, ); @@ -873,7 +879,7 @@ export async function reattemptPlatformAutomerge({ export async function mergePr({ id }: MergePRConfig): Promise { try { await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${id}/merge`, + `projects/${config.mergeRequestRepository}/merge_requests/${id}/merge`, { body: { should_remove_source_branch: true, @@ -1010,11 +1016,12 @@ export async function setBranchStatus({ state: renovateState, url: targetUrl, }: BranchStatusConfig): Promise { + const branchSourceProject = config.repository; // First, get the branch commit SHA const branchSha = git.getBranchCommit(branchName); // Now, check the statuses for that commit // TODO: types (#22198) - const url = `projects/${config.repository}/statuses/${branchSha!}`; + const url = `projects/${branchSourceProject}/statuses/${branchSha!}`; let state = 'success'; if (renovateState === 'yellow') { state = 'pending'; @@ -1076,7 +1083,7 @@ export async function getIssueList(): Promise { }); const res = await gitlabApi.getJson< { iid: number; title: string; labels: string[] }[] - >(`projects/${config.repository}/issues?${query}`, { + >(`projects/${config.mergeRequestRepository}/issues?${query}`, { memCache: false, paginate: true, }); @@ -1101,7 +1108,7 @@ export async function getIssue( try { const issueBody = ( await gitlabApi.getJson<{ description: string }>( - `projects/${config.repository}/issues/${number}`, + `projects/${config.mergeRequestRepository}/issues/${number}`, { memCache: useCache }, ) ).body.description; @@ -1148,13 +1155,13 @@ export async function ensureIssue({ if (issue) { const existingDescription = ( await gitlabApi.getJson<{ description: string }>( - `projects/${config.repository}/issues/${issue.iid}`, + `projects/${config.mergeRequestRepository}/issues/${issue.iid}`, ) ).body.description; if (issue.title !== title || existingDescription !== description) { logger.debug('Updating issue'); await gitlabApi.putJson( - `projects/${config.repository}/issues/${issue.iid}`, + `projects/${config.mergeRequestRepository}/issues/${issue.iid}`, { body: { title, @@ -1167,14 +1174,17 @@ export async function ensureIssue({ return 'updated'; } } else { - await gitlabApi.postJson(`projects/${config.repository}/issues`, { - body: { - title, - description, - labels: (labels ?? []).join(','), - confidential: confidential ?? false, + await gitlabApi.postJson( + `projects/${config.mergeRequestRepository}/issues`, + { + body: { + title, + description, + labels: (labels ?? []).join(','), + confidential: confidential ?? false, + }, }, - }); + ); logger.info('Issue created'); // delete issueList so that it will be refetched as necessary delete config.issueList; @@ -1197,7 +1207,7 @@ export async function ensureIssueClosing(title: string): Promise { if (issue.title === title) { logger.debug({ issue }, 'Closing issue'); await gitlabApi.putJson( - `projects/${config.repository}/issues/${issue.iid}`, + `projects/${config.mergeRequestRepository}/issues/${issue.iid}`, { body: { state_event: 'close' }, }, @@ -1282,7 +1292,7 @@ export async function addReviewers( newReviewerIDs = [...new Set(newReviewerIDs)]; try { - await updateMR(config.repository, iid, { + await updateMR(config.mergeRequestRepository, iid, { reviewer_ids: [...existingReviewerIDs, ...newReviewerIDs], }); } catch (err) { @@ -1301,7 +1311,7 @@ export async function deleteLabel( .filter((l: string) => l !== label) .join(','); await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${issueNo}`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}`, { body: { labels }, }, @@ -1314,7 +1324,7 @@ export async function deleteLabel( async function getComments(issueNo: number): Promise { // GET projects/:owner/:repo/merge_requests/:number/notes logger.debug(`Getting comments for #${issueNo}`); - const url = `projects/${config.repository}/merge_requests/${issueNo}/notes`; + const url = `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes`; const comments = ( await gitlabApi.getJson(url, { paginate: true }) ).body; @@ -1325,7 +1335,7 @@ async function getComments(issueNo: number): Promise { async function addComment(issueNo: number, body: string): Promise { // POST projects/:owner/:repo/merge_requests/:number/notes await gitlabApi.postJson( - `projects/${config.repository}/merge_requests/${issueNo}/notes`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes`, { body: { body }, }, @@ -1339,7 +1349,7 @@ async function editComment( ): Promise { // PUT projects/:owner/:repo/merge_requests/:number/notes/:id await gitlabApi.putJson( - `projects/${config.repository}/merge_requests/${issueNo}/notes/${commentId}`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes/${commentId}`, { body: { body }, }, @@ -1352,7 +1362,7 @@ async function deleteComment( ): Promise { // DELETE projects/:owner/:repo/merge_requests/:number/notes/:id await gitlabApi.deleteJson( - `projects/${config.repository}/merge_requests/${issueNo}/notes/${commentId}`, + `projects/${config.mergeRequestRepository}/merge_requests/${issueNo}/notes/${commentId}`, ); } diff --git a/lib/modules/platform/gitlab/types.ts b/lib/modules/platform/gitlab/types.ts index 897d25175783b4..d33db1956b0bc0 100644 --- a/lib/modules/platform/gitlab/types.ts +++ b/lib/modules/platform/gitlab/types.ts @@ -60,12 +60,14 @@ export interface RepoResponse { empty_repo: boolean; ssh_url_to_repo: string; http_url_to_repo: string; - forked_from_project: boolean; repository_access_level: 'disabled' | 'private' | 'enabled'; merge_requests_access_level: 'disabled' | 'private' | 'enabled'; merge_method: MergeMethod; path_with_namespace: string; squash_option?: 'never' | 'always' | 'default_on' | 'default_off'; + forked_from_project?: { + id: string; + }; } // See https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/user_status_type.rb diff --git a/package.json b/package.json index abdea127ec831d..2f7d5beb47da8f 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "test-e2e:install": "cd test/e2e && npm install --no-package-lock --prod", "test-e2e:run": "cd test/e2e && npm test", "test-schema": "run-s create-json-schema", + "test:docs": "node --test tools/docs/test/", "schedule-test-shards": "SCHEDULE_TEST_SHARDS=true ts-node jest.config.ts", "tsc": "tsc", "type-check": "run-s 'generate:*' 'tsc --noEmit {@}' --", @@ -134,48 +135,44 @@ "homepage": "https://renovatebot.com", "engines": { "node": "^18.12.0 || >=20.0.0", - "pnpm": "^8.0.0" + "pnpm": "^9.0.0" }, "engines-next": { "description": "Versions other than the below are deprecated and a warning will be logged", "node": "^18.12.0 || >=20.0.0" }, "dependencies": { - "@aws-sdk/client-codecommit": "3.563.0", - "@aws-sdk/client-ec2": "3.563.0", - "@aws-sdk/client-ecr": "3.563.0", - "@aws-sdk/client-rds": "3.563.0", - "@aws-sdk/client-s3": "3.563.0", - "@aws-sdk/credential-providers": "3.563.0", + "@aws-sdk/client-codecommit": "3.565.0", + "@aws-sdk/client-ec2": "3.565.0", + "@aws-sdk/client-ecr": "3.565.0", + "@aws-sdk/client-rds": "3.565.0", + "@aws-sdk/client-s3": "3.565.0", + "@aws-sdk/credential-providers": "3.565.0", "@breejs/later": "4.2.0", "@cdktf/hcl2json": "0.20.7", "@opentelemetry/api": "1.8.0", - "@opentelemetry/context-async-hooks": "1.23.0", - "@opentelemetry/exporter-trace-otlp-http": "0.50.0", - "@opentelemetry/instrumentation": "0.50.0", - "@opentelemetry/instrumentation-bunyan": "0.37.0", - "@opentelemetry/instrumentation-http": "0.50.0", - "@opentelemetry/resources": "1.23.0", - "@opentelemetry/sdk-trace-base": "1.23.0", - "@opentelemetry/sdk-trace-node": "1.23.0", - "@opentelemetry/semantic-conventions": "1.23.0", + "@opentelemetry/context-async-hooks": "1.24.0", + "@opentelemetry/exporter-trace-otlp-http": "0.51.0", + "@opentelemetry/instrumentation": "0.51.0", + "@opentelemetry/instrumentation-bunyan": "0.38.0", + "@opentelemetry/instrumentation-http": "0.51.0", + "@opentelemetry/resources": "1.24.0", + "@opentelemetry/sdk-trace-base": "1.24.0", + "@opentelemetry/sdk-trace-node": "1.24.0", + "@opentelemetry/semantic-conventions": "1.24.0", "@qnighy/marshal": "0.1.3", "@renovatebot/kbpgp": "3.0.1", "@renovatebot/osv-offline": "1.5.4", "@renovatebot/pep440": "3.0.20", "@renovatebot/ruby-semver": "3.0.23", "@sindresorhus/is": "4.6.0", - "@types/better-sqlite3": "7.6.9", - "@types/ms": "0.7.34", - "@types/tmp": "0.2.6", "@yarnpkg/core": "4.0.3", "@yarnpkg/parsers": "3.0.0", "agentkeepalive": "4.5.0", "aggregate-error": "3.1.0", "auth-header": "1.0.0", "aws4": "1.12.0", - "azure-devops-node-api": "12.5.0", - "better-sqlite3": "9.5.0", + "azure-devops-node-api": "13.0.0", "bunyan": "1.8.15", "cacache": "18.0.2", "cacheable-lookup": "5.0.4", @@ -202,7 +199,7 @@ "glob": "10.3.12", "global-agent": "3.0.0", "good-enough-parser": "1.1.23", - "google-auth-library": "9.8.0", + "google-auth-library": "9.9.0", "got": "11.8.6", "graph-data-structure": "3.5.0", "handlebars": "4.7.8", @@ -214,9 +211,9 @@ "json5": "2.2.3", "jsonata": "2.0.4", "klona": "2.0.6", - "lru-cache": "10.2.0", + "lru-cache": "10.2.2", "luxon": "3.4.4", - "markdown-it": "13.0.2", + "markdown-it": "14.1.0", "markdown-table": "2.0.0", "minimatch": "9.0.4", "moo": "0.5.2", @@ -251,6 +248,7 @@ "zod": "3.23.4" }, "optionalDependencies": { + "better-sqlite3": "9.6.0", "openpgp": "5.11.1", "re2": "1.20.10" }, @@ -265,9 +263,10 @@ "@openpgp/web-stream-tools": "0.0.14", "@renovate/eslint-plugin": "file:tools/eslint", "@semantic-release/exec": "6.0.3", - "@swc/core": "1.4.16", + "@swc/core": "1.4.17", "@types/auth-header": "1.0.6", "@types/aws4": "1.11.6", + "@types/better-sqlite3": "7.6.10", "@types/breejs__later": "4.1.5", "@types/bunyan": "1.8.11", "@types/cacache": "17.0.2", @@ -276,7 +275,7 @@ "@types/clean-git-ref": "2.0.2", "@types/common-tags": "1.8.4", "@types/conventional-commits-detector": "1.0.2", - "@types/diff": "5.0.9", + "@types/diff": "5.2.0", "@types/eslint": "8.56.10", "@types/fs-extra": "11.0.4", "@types/git-url-parse": "9.0.3", @@ -288,23 +287,26 @@ "@types/linkify-markdown": "1.0.3", "@types/lodash": "4.17.0", "@types/luxon": "3.4.2", - "@types/markdown-it": "13.0.7", + "@types/markdown-it": "14.0.1", "@types/markdown-table": "2.0.0", "@types/marshal": "0.5.3", "@types/mdast": "3.0.15", "@types/moo": "0.5.9", + "@types/ms": "0.7.34", "@types/node": "18.19.31", "@types/parse-link-header": "2.0.3", "@types/semver": "7.5.8", "@types/semver-stable": "3.0.2", "@types/semver-utils": "1.1.3", "@types/tar": "6.1.13", + "@types/tmp": "0.2.6", "@types/traverse": "0.6.36", + "@types/unist": "2.0.10", "@types/url-join": "4.0.3", "@types/validate-npm-package-name": "4.0.2", "@types/xmldoc": "1.1.9", - "@typescript-eslint/eslint-plugin": "7.7.1", - "@typescript-eslint/parser": "7.7.1", + "@typescript-eslint/eslint-plugin": "7.8.0", + "@typescript-eslint/parser": "7.8.0", "aws-sdk-client-mock": "4.0.0", "callsite": "1.0.0", "common-tags": "1.8.2", @@ -329,7 +331,7 @@ "jest-mock-extended": "3.0.6", "jest-snapshot": "29.7.0", "markdownlint-cli2": "0.13.0", - "memfs": "4.8.2", + "memfs": "4.9.2", "nock": "13.5.4", "npm-run-all2": "6.1.2", "nyc": "15.1.0", @@ -340,11 +342,11 @@ "tmp-promise": "3.0.3", "ts-jest": "29.1.2", "ts-node": "10.9.2", - "type-fest": "4.15.0", + "type-fest": "4.18.0", "typescript": "5.4.5", "unified": "9.2.2" }, - "packageManager": "pnpm@8.15.7", + "packageManager": "pnpm@9.0.6", "files": [ "dist", "renovate-schema.json"