From a1d8d9fbcee3663e8c01e776a4fa31f5b6d73200 Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Fri, 10 Jan 2025 17:44:49 +0100
Subject: [PATCH 1/9] refactor: rename repo ID to project path for clarity

---
 lib/definitions/errors.js                   | 26 +++---
 lib/fail.js                                 | 10 +--
 lib/{get-repo-id.js => get-project-path.js} |  0
 lib/publish.js                              | 20 ++---
 lib/success.js                              |  8 +-
 lib/verify.js                               | 20 ++---
 test/fail.test.js                           | 28 +++----
 test/get-project-path.test.js               | 61 ++++++++++++++
 test/get-repo-id.test.js                    | 61 --------------
 test/integration.test.js                    | 12 +--
 test/publish.test.js                        | 92 ++++++++++-----------
 test/success.test.js                        | 44 +++++-----
 12 files changed, 191 insertions(+), 191 deletions(-)
 rename lib/{get-repo-id.js => get-project-path.js} (100%)
 create mode 100644 test/get-project-path.test.js
 delete mode 100644 test/get-repo-id.test.js

diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js
index 7df6336a..51508538 100644
--- a/lib/definitions/errors.js
+++ b/lib/definitions/errors.js
@@ -41,20 +41,20 @@ Your configuration for the \`labels\` option is \`${stringify(labels)}\`.`,
   }),
   EINVALIDGITLABURL: () => ({
     message: 'The git repository URL is not a valid GitLab URL.',
-    details: `The **semantic-release** \`repositoryUrl\` option must a valid GitLab URL with the format \`<GitLab_URL>/<repoId>.git\`.
+    details: `The **semantic-release** \`repositoryUrl\` option must a valid GitLab URL with the format \`<GitLab_URL>/<projectPath>.git\`.
 
 By default the \`repositoryUrl\` option is retrieved from the \`repository\` property of your \`package.json\` or the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) of the repository cloned by your CI environment.`,
   }),
-  EINVALIDGLTOKEN: ({repoId}) => ({
+  EINVALIDGLTOKEN: ({projectPath}) => ({
     message: 'Invalid GitLab token.',
     details: `The [GitLab token](${linkify(
       'README.md#gitlab-authentication'
-    )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must be a valid [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) allowing to push to the repository ${repoId}.
+    )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must be a valid [personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) allowing to push to the repository ${projectPath}.
 
 Please make sure to set the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable in your CI with the exact value of the GitLab personal token.`,
   }),
-  EMISSINGREPO: ({repoId}) => ({
-    message: `The repository ${repoId} doesn't exist.`,
+  EMISSINGREPO: ({projectPath}) => ({
+    message: `The repository ${projectPath} doesn't exist.`,
     details: `The **semantic-release** \`repositoryUrl\` option must refer to your GitLab repository. The repository must be accessible with the [GitLab API](https://docs.gitlab.com/ce/api/README.html).
 
 By default the \`repositoryUrl\` option is retrieved from the \`repository\` property of your \`package.json\` or the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) of the repository cloned by your CI environment.
@@ -63,21 +63,21 @@ If you are using [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee)
       'README.md#options'
     )}).`,
   }),
-  EGLNOPUSHPERMISSION: ({repoId}) => ({
-    message: `The GitLab token doesn't allow to push on the repository ${repoId}.`,
+  EGLNOPUSHPERMISSION: ({projectPath}) => ({
+    message: `The GitLab token doesn't allow to push on the repository ${projectPath}.`,
     details: `The user associated with the [GitLab token](${linkify(
       'README.md#gitlab-authentication'
-    )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must allows to push to the repository ${repoId}.
+    )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must allows to push to the repository ${projectPath}.
 
-Please make sure the GitLab user associated with the token has the [permission to push](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions) to the repository ${repoId}.`,
+Please make sure the GitLab user associated with the token has the [permission to push](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions) to the repository ${projectPath}.`,
   }),
-  EGLNOPULLPERMISSION: ({repoId}) => ({
-    message: `The GitLab token doesn't allow to pull from the repository ${repoId}.`,
+  EGLNOPULLPERMISSION: ({projectPath}) => ({
+    message: `The GitLab token doesn't allow to pull from the repository ${projectPath}.`,
     details: `The user associated with the [GitLab token](${linkify(
       'README.md#gitlab-authentication'
-    )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must allow pull from the repository ${repoId}.
+    )}) configured in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable must allow pull from the repository ${projectPath}.
 
-Please make sure the GitLab user associated with the token has the [permission to push](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions) to the repository ${repoId}.`,
+Please make sure the GitLab user associated with the token has the [permission to push](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions) to the repository ${projectPath}.`,
   }),
   ENOGLTOKEN: ({repositoryUrl}) => ({
     message: 'No GitLab token specified.',
diff --git a/lib/fail.js b/lib/fail.js
index d33a0c90..d997d9f9 100644
--- a/lib/fail.js
+++ b/lib/fail.js
@@ -4,7 +4,7 @@ import got from "got";
 import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import resolveConfig from "./resolve-config.js";
-import getRepoId from "./get-repo-id.js";
+import getProjectPath from "./get-project-path.js";
 import getFailComment from "./get-fail-comment.js";
 
 export default async (pluginConfig, context) => {
@@ -25,8 +25,8 @@ export default async (pluginConfig, context) => {
     assignee,
     retryLimit,
   } = resolveConfig(pluginConfig, context);
-  const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
-  const encodedRepoId = encodeURIComponent(repoId);
+  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
+  const encodedProjectPath = encodeURIComponent(projectPath);
   const apiOptions = {
     headers: { "PRIVATE-TOKEN": gitlabToken },
     retry: { limit: retryLimit },
@@ -42,7 +42,7 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
     const encodedFailTitle = encodeURIComponent(failTitle);
     const description = failComment ? template(failComment)({ branch, errors }) : getFailComment(branch, errors);
 
-    const issuesEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/issues`);
+    const issuesEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}/issues`);
     const openFailTitleIssueEndpoint = urlJoin(issuesEndpoint, `?state=opened&search=${encodedFailTitle}`);
 
     const openFailTitleIssues = await got(openFailTitleIssueEndpoint, { ...apiOptions }).json();
@@ -67,7 +67,7 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
         const { id, web_url } = existingIssue;
         logger.log("Commented on issue #%d: %s.", id, web_url);
       } else {
-        const newIssue = { id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee };
+        const newIssue = { id: encodedProjectPath, description, labels, title: failTitle, assignee_id: assignee };
         debug("create issue: %O", newIssue);
 
         /* eslint camelcase: off */
diff --git a/lib/get-repo-id.js b/lib/get-project-path.js
similarity index 100%
rename from lib/get-repo-id.js
rename to lib/get-project-path.js
diff --git a/lib/publish.js b/lib/publish.js
index f3a1cc5c..d09ef27d 100644
--- a/lib/publish.js
+++ b/lib/publish.js
@@ -9,7 +9,7 @@ import got from "got";
 import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import resolveConfig from "./resolve-config.js";
-import getRepoId from "./get-repo-id.js";
+import getProjectPath from "./get-project-path.js";
 import getAssets from "./glob-assets.js";
 import { RELEASE_NAME } from "./definitions/constants.js";
 
@@ -27,8 +27,8 @@ export default async (pluginConfig, context) => {
     context
   );
   const assetsList = [];
-  const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
-  const encodedRepoId = encodeURIComponent(repoId);
+  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
+  const encodedProjectPath = encodeURIComponent(projectPath);
   const encodedGitTag = encodeURIComponent(gitTag);
   const encodedVersion = encodeURIComponent(version);
   const apiOptions = {
@@ -52,7 +52,7 @@ export default async (pluginConfig, context) => {
     retry: { limit: retryLimit },
   };
 
-  debug("repoId: %o", repoId);
+  debug("projectPath: %o", projectPath);
   debug("release name: %o", gitTag);
   debug("release ref: %o", gitHead);
   debug("milestones: %o", milestones);
@@ -115,7 +115,7 @@ export default async (pluginConfig, context) => {
             // https://docs.gitlab.com/ee/user/packages/generic_packages/#publish-a-package-file
             uploadEndpoint = urlJoin(
               gitlabApiUrl,
-              `/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}?${
+              `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}?${
                 status ? `status=${status}&` : ""
               }select=package_file`
             );
@@ -132,7 +132,7 @@ export default async (pluginConfig, context) => {
             // https://docs.gitlab.com/ee/user/packages/generic_packages/#download-package-file
             const url = urlJoin(
               gitlabApiUrl,
-              `/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}`
+              `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`
             );
 
             assetsList.push({ label, alt: "release", url, type: "package", filepath });
@@ -140,7 +140,7 @@ export default async (pluginConfig, context) => {
             logger.log("Uploaded file: %s (%s)", url, response.file.url);
           } else {
             // Handle normal assets
-            uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/uploads`);
+            uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}/uploads`);
 
             debug("POST-ing the file %s to %s", file, uploadEndpoint);
 
@@ -167,7 +167,7 @@ export default async (pluginConfig, context) => {
 
   debug("Create a release for git tag %o with commit %o", gitTag, gitHead);
 
-  const createReleaseEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedRepoId}/releases`);
+  const createReleaseEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}/releases`);
 
   const json = {
     /* eslint-disable camelcase */
@@ -178,7 +178,7 @@ export default async (pluginConfig, context) => {
       links: assetsList.map(({ label, alt, url, type, filepath, rawUrl }) => {
         return {
           name: label || alt,
-          url: rawUrl || (isUrlScheme(url) ? url : urlJoin(gitlabUrl, repoId, url)),
+          url: rawUrl || (isUrlScheme(url) ? url : urlJoin(gitlabUrl, projectPath, url)),
           link_type: type,
           filepath,
         };
@@ -202,7 +202,7 @@ export default async (pluginConfig, context) => {
 
   logger.log("Published GitLab release: %s", gitTag);
 
-  const releaseUrl = urlJoin(gitlabUrl, repoId, `/-/releases/${encodedGitTag}`);
+  const releaseUrl = urlJoin(gitlabUrl, projectPath, `/-/releases/${encodedGitTag}`);
 
   return { name: RELEASE_NAME, url: releaseUrl };
 };
diff --git a/lib/success.js b/lib/success.js
index f7ed11f9..2444fe8f 100644
--- a/lib/success.js
+++ b/lib/success.js
@@ -4,7 +4,7 @@ import got from "got";
 import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import resolveConfig from "./resolve-config.js";
-import getRepoId from "./get-repo-id.js";
+import getProjectPath from "./get-project-path.js";
 import getSuccessComment from "./get-success-comment.js";
 
 export default async (pluginConfig, context) => {
@@ -17,8 +17,8 @@ export default async (pluginConfig, context) => {
   } = context;
   const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, successCommentCondition, proxy, retryLimit } =
     resolveConfig(pluginConfig, context);
-  const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
-  const encodedRepoId = encodeURIComponent(repoId);
+  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
+  const encodedProjectPath = encodeURIComponent(projectPath);
   const apiOptions = {
     headers: { "PRIVATE-TOKEN": gitlabToken },
     retry: { limit: retryLimit },
@@ -79,7 +79,7 @@ Using 'false' for 'successComment' is deprecated and will be removed in a future
       const getRelatedMergeRequests = async (commitHash) => {
         const relatedMergeRequestsEndpoint = urlJoin(
           gitlabApiUrl,
-          `/projects/${encodedRepoId}/repository/commits/${commitHash}/merge_requests`
+          `/projects/${encodedProjectPath}/repository/commits/${commitHash}/merge_requests`
         );
         debug("Getting MRs from %s", relatedMergeRequestsEndpoint);
         const relatedMergeRequests = await got
diff --git a/lib/verify.js b/lib/verify.js
index e7091ef1..535df659 100644
--- a/lib/verify.js
+++ b/lib/verify.js
@@ -5,7 +5,7 @@ import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import AggregateError from "aggregate-error";
 import resolveConfig from "./resolve-config.js";
-import getRepoId from "./get-repo-id.js";
+import getProjectPath from "./get-project-path.js";
 import getError from "./get-error.js";
 
 const isNonEmptyString = (value) => isString(value) && value.trim();
@@ -32,10 +32,10 @@ export default async (pluginConfig, context) => {
     logger,
   } = context;
   const { gitlabToken, gitlabUrl, gitlabApiUrl, proxy, ...options } = resolveConfig(pluginConfig, context);
-  const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
+  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
 
   debug("apiUrl: %o", gitlabApiUrl);
-  debug("repoId: %o", repoId);
+  debug("projectPath: %o", projectPath);
 
   const isValid = (option, value) => {
     const validator = VALIDATORS[option];
@@ -46,7 +46,7 @@ export default async (pluginConfig, context) => {
     .filter(([option, value]) => !isValid(option, value))
     .map(([option, value]) => getError(`EINVALID${option.toUpperCase()}`, { [option]: value }));
 
-  if (!repoId) {
+  if (!projectPath) {
     errors.push(getError("EINVALIDGITLABURL"));
   }
 
@@ -54,7 +54,7 @@ export default async (pluginConfig, context) => {
     errors.push(getError("ENOGLTOKEN", { repositoryUrl }));
   }
 
-  if (gitlabToken && repoId) {
+  if (gitlabToken && projectPath) {
     let projectAccess;
     let groupAccess;
 
@@ -64,7 +64,7 @@ export default async (pluginConfig, context) => {
       ({
         permissions: { project_access: projectAccess, group_access: groupAccess },
       } = await got
-        .get(urlJoin(gitlabApiUrl, `/projects/${encodeURIComponent(repoId)}`), {
+        .get(urlJoin(gitlabApiUrl, `/projects/${encodeURIComponent(projectPath)}`), {
           headers: { "PRIVATE-TOKEN": gitlabToken },
           ...proxy,
         })
@@ -73,17 +73,17 @@ export default async (pluginConfig, context) => {
         context.options.dryRun &&
         !((projectAccess && projectAccess.access_level >= 10) || (groupAccess && groupAccess.access_level >= 10))
       ) {
-        errors.push(getError("EGLNOPULLPERMISSION", { repoId }));
+        errors.push(getError("EGLNOPULLPERMISSION", { projectPath }));
       } else if (
         !((projectAccess && projectAccess.access_level >= 30) || (groupAccess && groupAccess.access_level >= 30))
       ) {
-        errors.push(getError("EGLNOPUSHPERMISSION", { repoId }));
+        errors.push(getError("EGLNOPUSHPERMISSION", { projectPath }));
       }
     } catch (error) {
       if (error.response && error.response.statusCode === 401) {
-        errors.push(getError("EINVALIDGLTOKEN", { repoId }));
+        errors.push(getError("EINVALIDGLTOKEN", { projectPath }));
       } else if (error.response && error.response.statusCode === 404) {
-        errors.push(getError("EMISSINGREPO", { repoId }));
+        errors.push(getError("EMISSINGREPO", { projectPath }));
       } else {
         throw error;
       }
diff --git a/test/fail.test.js b/test/fail.test.js
index 9c6f52df..59de52cb 100644
--- a/test/fail.test.js
+++ b/test/fail.test.js
@@ -26,10 +26,10 @@ test.serial("Post new issue if none exists yet", async (t) => {
   const branch = { name: "main" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
   const errors = [{ message: "An error occured" }];
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
+    .get(`/projects/${encodedProjectPath}/issues?state=opened&&search=${encodedFailTitle}`)
     .reply(200, [
       {
         id: 2,
@@ -39,7 +39,7 @@ test.serial("Post new issue if none exists yet", async (t) => {
         title: "API should implemented authentication",
       },
     ])
-    .post(`/projects/${encodedRepoId}/issues`, {
+    .post(`/projects/${encodedProjectPath}/issues`, {
       id: "test_user%2Ftest_repo",
       description: `## :rotating_light: The automated release from the \`main\` branch failed. :rotating_light:
 
@@ -92,10 +92,10 @@ test.serial("Post comments to existing issue", async (t) => {
   const branch = { name: "main" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
   const errors = [{ message: "An error occured" }];
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/issues?state=opened&search=${encodedFailTitle}`)
+    .get(`/projects/${encodedProjectPath}/issues?state=opened&search=${encodedFailTitle}`)
     .reply(200, [
       {
         id: 1,
@@ -160,10 +160,10 @@ test.serial("Post comments to existing issue with custom template", async (t) =>
   const branch = { name: "main" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
   const errors = [{ message: "An error occured" }];
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedFailTitle = encodeURIComponent("Semantic Release Failure");
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/issues?state=opened&search=${encodedFailTitle}`)
+    .get(`/projects/${encodedProjectPath}/issues?state=opened&search=${encodedFailTitle}`)
     .reply(200, [
       {
         id: 1,
@@ -252,10 +252,10 @@ test.serial("Does not post comments when failCommentCondition disables it", asyn
   const branch = { name: "main" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
   const errors = [{ message: "An error occured" }];
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
+    .get(`/projects/${encodedProjectPath}/issues?state=opened&&search=${encodedFailTitle}`)
     .reply(200, [
       {
         id: 2,
@@ -279,10 +279,10 @@ test.serial("Does not post comments on existing issues when failCommentCondition
   const branch = { name: "main" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
   const errors = [{ message: "An error occured" }];
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
+    .get(`/projects/${encodedProjectPath}/issues?state=opened&&search=${encodedFailTitle}`)
     .reply(200, [
       {
         id: 1,
@@ -316,10 +316,10 @@ test.serial("Post new issue if none exists yet with disabled comment on existing
   const branch = { name: "main" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
   const errors = [{ message: "An error occured" }];
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedFailTitle = encodeURIComponent("The automated release is failing 🚨");
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/issues?state=opened&&search=${encodedFailTitle}`)
+    .get(`/projects/${encodedProjectPath}/issues?state=opened&&search=${encodedFailTitle}`)
     .reply(200, [
       {
         id: 2,
@@ -329,7 +329,7 @@ test.serial("Post new issue if none exists yet with disabled comment on existing
         title: "API should implemented authentication",
       },
     ])
-    .post(`/projects/${encodedRepoId}/issues`, {
+    .post(`/projects/${encodedProjectPath}/issues`, {
       id: "test_user%2Ftest_repo",
       description: `Error: Release for branch main failed with error: An error occured`,
       labels: "semantic-release",
diff --git a/test/get-project-path.test.js b/test/get-project-path.test.js
new file mode 100644
index 00000000..88379aff
--- /dev/null
+++ b/test/get-project-path.test.js
@@ -0,0 +1,61 @@
+import test from "ava";
+import getProjectPath from "../lib/get-project-path.js";
+
+test("Parse repo id with https URL", (t) => {
+  t.is(getProjectPath({ env: {} }, "https://gitlbab.com", "https://gitlab.com/owner/repo.git"), "owner/repo");
+  t.is(getProjectPath({ env: {} }, "https://gitlbab.com", "https://gitlab.com/owner/repo"), "owner/repo");
+});
+
+test("Parse repo id with git URL", (t) => {
+  t.is(getProjectPath({ env: {} }, "https://gitlab.com", "git+ssh://git@gitlab.com/owner/repo.git"), "owner/repo");
+  t.is(getProjectPath({ env: {} }, "https://gitlab.com", "git+ssh://git@gitlab.com/owner/repo"), "owner/repo");
+});
+
+test("Parse repo id with context in repo URL", (t) => {
+  t.is(
+    getProjectPath({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/context/owner/repo.git"),
+    "owner/repo"
+  );
+  t.is(
+    getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/context/owner/repo.git"),
+    "owner/repo"
+  );
+});
+
+test("Parse repo id with context not in repo URL", (t) => {
+  t.is(getProjectPath({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/owner/repo.git"), "owner/repo");
+  t.is(getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/owner/repo.git"), "owner/repo");
+});
+
+test("Parse repo id with organization and subgroup", (t) => {
+  t.is(
+    getProjectPath({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/orga/subgroup/owner/repo.git"),
+    "orga/subgroup/owner/repo"
+  );
+  t.is(
+    getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/orga/subgroup/owner/repo.git"),
+    "orga/subgroup/owner/repo"
+  );
+});
+
+test("Get repo id from GitLab CI", (t) => {
+  t.is(
+    getProjectPath(
+      { envCi: { service: "gitlab" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
+      "https://gitlbab.com",
+      "https://gitlab.com/owner/repo.git"
+    ),
+    "other-owner/other-repo"
+  );
+});
+
+test("Ignore CI_PROJECT_PATH if not on GitLab CI", (t) => {
+  t.is(
+    getProjectPath(
+      { envCi: { service: "travis" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
+      "https://gitlbab.com",
+      "https://gitlab.com/owner/repo.git"
+    ),
+    "owner/repo"
+  );
+});
diff --git a/test/get-repo-id.test.js b/test/get-repo-id.test.js
deleted file mode 100644
index f7f09fc1..00000000
--- a/test/get-repo-id.test.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import test from "ava";
-import getRepoId from "../lib/get-repo-id.js";
-
-test("Parse repo id with https URL", (t) => {
-  t.is(getRepoId({ env: {} }, "https://gitlbab.com", "https://gitlab.com/owner/repo.git"), "owner/repo");
-  t.is(getRepoId({ env: {} }, "https://gitlbab.com", "https://gitlab.com/owner/repo"), "owner/repo");
-});
-
-test("Parse repo id with git URL", (t) => {
-  t.is(getRepoId({ env: {} }, "https://gitlab.com", "git+ssh://git@gitlab.com/owner/repo.git"), "owner/repo");
-  t.is(getRepoId({ env: {} }, "https://gitlab.com", "git+ssh://git@gitlab.com/owner/repo"), "owner/repo");
-});
-
-test("Parse repo id with context in repo URL", (t) => {
-  t.is(
-    getRepoId({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/context/owner/repo.git"),
-    "owner/repo"
-  );
-  t.is(
-    getRepoId({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/context/owner/repo.git"),
-    "owner/repo"
-  );
-});
-
-test("Parse repo id with context not in repo URL", (t) => {
-  t.is(getRepoId({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/owner/repo.git"), "owner/repo");
-  t.is(getRepoId({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/owner/repo.git"), "owner/repo");
-});
-
-test("Parse repo id with organization and subgroup", (t) => {
-  t.is(
-    getRepoId({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/orga/subgroup/owner/repo.git"),
-    "orga/subgroup/owner/repo"
-  );
-  t.is(
-    getRepoId({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/orga/subgroup/owner/repo.git"),
-    "orga/subgroup/owner/repo"
-  );
-});
-
-test("Get repo id from GitLab CI", (t) => {
-  t.is(
-    getRepoId(
-      { envCi: { service: "gitlab" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
-      "https://gitlbab.com",
-      "https://gitlab.com/owner/repo.git"
-    ),
-    "other-owner/other-repo"
-  );
-});
-
-test("Ignore CI_PROJECT_PATH if not on GitLab CI", (t) => {
-  t.is(
-    getRepoId(
-      { envCi: { service: "travis" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
-      "https://gitlbab.com",
-      "https://gitlab.com/owner/repo.git"
-    ),
-    "owner/repo"
-  );
-});
diff --git a/test/integration.test.js b/test/integration.test.js
index 5e18cd43..dbfd9f2c 100644
--- a/test/integration.test.js
+++ b/test/integration.test.js
@@ -62,12 +62,12 @@ test.serial("Publish a release", async (t) => {
   const env = { GL_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { branch: "master", repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
 
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}`)
+    .get(`/projects/${encodedProjectPath}`)
     .reply(200, { permissions: { project_access: { access_level: 30 } } })
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -89,13 +89,13 @@ test.serial("Verify Github auth and release", async (t) => {
   const owner = "test_user";
   const repo = "test_repo";
   const options = { repositoryUrl: `https://github.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
 
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}`)
+    .get(`/projects/${encodedProjectPath}`)
     .reply(200, { permissions: { project_access: { access_level: 30 } } })
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
diff --git a/test/publish.test.js b/test/publish.test.js
index 8771b051..249e8bcf 100644
--- a/test/publish.test.js
+++ b/test/publish.test.js
@@ -26,10 +26,10 @@ test.serial("Publish a release", async (t) => {
   const pluginConfig = {};
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -52,7 +52,7 @@ test.serial("Publish a release with templated path", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token", FIXTURE: "upload" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const generic = { path: "${env.FIXTURE}.txt", filepath: "/upload.txt" };
   const assets = [generic];
@@ -62,7 +62,7 @@ test.serial("Publish a release with templated path", async (t) => {
     full_path: "/-/project/4/66dbcd21ec5d24ed6ea225176098d52b/upload.txt",
   };
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -77,7 +77,7 @@ test.serial("Publish a release with templated path", async (t) => {
     })
     .reply(200);
   const gitlabUpload = authenticate(env)
-    .post(`/projects/${encodedRepoId}/uploads`, /Content-Disposition/g)
+    .post(`/projects/${encodedProjectPath}/uploads`, /Content-Disposition/g)
     .reply(200, uploaded);
 
   const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });
@@ -95,7 +95,7 @@ test.serial("Publish a release with assets", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const uploaded = {
     url: "/uploads/file.css",
@@ -104,7 +104,7 @@ test.serial("Publish a release with assets", async (t) => {
   };
   const assets = [["**", "!**/*.txt", "!.dotfile"]];
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -118,7 +118,7 @@ test.serial("Publish a release with assets", async (t) => {
     })
     .reply(200);
   const gitlabUpload = authenticate(env)
-    .post(`/projects/${encodedRepoId}/uploads`, /filename="file.css"/gm)
+    .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm)
     .reply(200, uploaded);
 
   const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });
@@ -137,16 +137,16 @@ test.serial("Publish a release with generics", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const encodedVersion = encodeURIComponent(nextRelease.version);
   const uploaded = { file: { url: "/uploads/file.css" } };
   const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" };
   const assets = [generic];
   const encodedLabel = encodeURIComponent(generic.label);
-  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
+  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -162,7 +162,7 @@ test.serial("Publish a release with generics", async (t) => {
     .reply(200);
   const gitlabUpload = authenticate(env)
     .put(
-      `/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
+      `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
       /\.test\s\{\}/gm
     )
     .reply(200, uploaded);
@@ -183,16 +183,16 @@ test.serial("Publish a release with generics and external storage provider (http
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const encodedVersion = encodeURIComponent(nextRelease.version);
   const uploaded = { file: { url: "http://aws.example.com/bucket/gitlab/file.css" } };
   const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" };
   const assets = [generic];
   const encodedLabel = encodeURIComponent(generic.label);
-  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
+  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -208,7 +208,7 @@ test.serial("Publish a release with generics and external storage provider (http
     .reply(200);
   const gitlabUpload = authenticate(env)
     .put(
-      `/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
+      `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
       /\.test\s\{\}/gm
     )
     .reply(200, uploaded);
@@ -229,16 +229,16 @@ test.serial("Publish a release with generics and external storage provider (http
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const encodedVersion = encodeURIComponent(nextRelease.version);
   const uploaded = { file: { url: "https://aws.example.com/bucket/gitlab/file.css" } };
   const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" };
   const assets = [generic];
   const encodedLabel = encodeURIComponent(generic.label);
-  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
+  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -254,7 +254,7 @@ test.serial("Publish a release with generics and external storage provider (http
     .reply(200);
   const gitlabUpload = authenticate(env)
     .put(
-      `/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
+      `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
       /\.test\s\{\}/gm
     )
     .reply(200, uploaded);
@@ -275,16 +275,16 @@ test.serial("Publish a release with generics and external storage provider (ftp)
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const encodedVersion = encodeURIComponent(nextRelease.version);
   const uploaded = { file: { url: "ftp://drive.example.com/gitlab/file.css" } };
   const generic = { path: "file.css", label: "Style package", target: "generic_package", status: "hidden" };
   const assets = [generic];
   const encodedLabel = encodeURIComponent(generic.label);
-  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
+  const expectedUrl = `https://gitlab.com/api/v4/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`;
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -300,7 +300,7 @@ test.serial("Publish a release with generics and external storage provider (ftp)
     .reply(200);
   const gitlabUpload = authenticate(env)
     .put(
-      `/projects/${encodedRepoId}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
+      `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}?status=${generic.status}&select=package_file`,
       /\.test\s\{\}/gm
     )
     .reply(200, uploaded);
@@ -321,7 +321,7 @@ test.serial("Publish a release with asset type and permalink", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const uploaded = {
     url: "/uploads/file.css",
@@ -338,7 +338,7 @@ test.serial("Publish a release with asset type and permalink", async (t) => {
     },
   ];
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -354,7 +354,7 @@ test.serial("Publish a release with asset type and permalink", async (t) => {
     })
     .reply(200);
   const gitlabUpload = authenticate(env)
-    .post(`/projects/${encodedRepoId}/uploads`, /filename="file.css"/gm)
+    .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm)
     .reply(200, uploaded);
 
   const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });
@@ -373,7 +373,7 @@ test.serial("Publish a release with an asset with a template label", async (t) =
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const uploaded = {
     url: "/uploads/file.css",
@@ -389,7 +389,7 @@ test.serial("Publish a release with an asset with a template label", async (t) =
     },
   ];
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -405,7 +405,7 @@ test.serial("Publish a release with an asset with a template label", async (t) =
     })
     .reply(200);
   const gitlabUpload = authenticate(env)
-    .post(`/projects/${encodedRepoId}/uploads`, /filename="file.css"/gm)
+    .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm)
     .reply(200, uploaded);
 
   const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });
@@ -426,7 +426,7 @@ test.serial("Publish a release (with an link) with variables", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body", version: "1.0.0" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const uploaded = {
     url: "/uploads/file.css",
@@ -447,7 +447,7 @@ test.serial("Publish a release (with an link) with variables", async (t) => {
     },
   ];
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -469,7 +469,7 @@ test.serial("Publish a release (with an link) with variables", async (t) => {
     .reply(200);
 
   const gitlabUpload = authenticate(env)
-    .post(`/projects/${encodedRepoId}/uploads`, /filename="file.css"/gm)
+    .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm)
     .reply(200, uploaded);
   const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });
 
@@ -490,10 +490,10 @@ test.serial("Publish a release with a milestone", async (t) => {
   const pluginConfig = { milestones: ["1.2.3"] };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -517,12 +517,12 @@ test.serial("Publish a release with array of missing assets", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const emptyDirectory = tempy.directory();
   const assets = [emptyDirectory, { path: "missing.txt", label: "missing.txt" }];
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -544,7 +544,7 @@ test.serial("Publish a release with one asset and custom label", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const uploaded = {
     url: "/uploads/upload.txt",
@@ -553,7 +553,7 @@ test.serial("Publish a release with one asset and custom label", async (t) => {
   const assetLabel = "Custom Label";
   const assets = [{ path: "upload.txt", label: assetLabel }];
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -567,7 +567,7 @@ test.serial("Publish a release with one asset and custom label", async (t) => {
     })
     .reply(200);
   const gitlabUpload = authenticate(env)
-    .post(`/projects/${encodedRepoId}/uploads`, /filename="upload.txt"/gm)
+    .post(`/projects/${encodedProjectPath}/uploads`, /filename="upload.txt"/gm)
     .reply(200, uploaded);
 
   const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger });
@@ -586,10 +586,10 @@ test.serial("Publish a release with missing release notes", async (t) => {
   const pluginConfig = {};
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.gitTag,
       assets: {
@@ -612,7 +612,7 @@ test.serial("Publish a release with an asset link", async (t) => {
   const env = { GITLAB_TOKEN: "gitlab_token" };
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const link = {
     label: "README.md",
@@ -621,7 +621,7 @@ test.serial("Publish a release with an asset link", async (t) => {
   };
   const assets = [link];
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
@@ -650,10 +650,10 @@ test.serial("Publish a release with error response", async (t) => {
   const pluginConfig = {};
   const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" };
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const encodedGitTag = encodeURIComponent(nextRelease.gitTag);
   const gitlab = authenticate(env)
-    .post(`/projects/${encodedRepoId}/releases`, {
+    .post(`/projects/${encodedProjectPath}/releases`, {
       tag_name: nextRelease.gitTag,
       description: nextRelease.notes,
       assets: {
diff --git a/test/success.test.js b/test/success.test.js
index 2a3f2d4e..5394b87a 100644
--- a/test/success.test.js
+++ b/test/success.test.js
@@ -27,16 +27,16 @@ test.serial("Post comments to related issues and MRs", async (t) => {
   const nextRelease = { version: "1.0.0" };
   const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }];
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const commits = [{ hash: "abcdef" }, { hash: "fedcba" }];
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .reply(200, [
       { project_id: 100, iid: 1, state: "merged" },
       { project_id: 200, iid: 2, state: "closed" },
       { project_id: 300, iid: 3, state: "merged" },
     ])
-    .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/fedcba/merge_requests`)
     .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
     .get(`/projects/100/merge_requests/1/closes_issues`)
     .reply(200, [
@@ -74,10 +74,10 @@ test.serial("Post comments with custom template", async (t) => {
   const nextRelease = { version: "1.0.0" };
   const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }];
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const commits = [{ hash: "abcdef" }];
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
     .get(`/projects/100/merge_requests/1/closes_issues`)
     .reply(200, [{ project_id: 100, iid: 11, state: "closed" }])
@@ -106,10 +106,10 @@ test.serial("Post comments for multiple releases", async (t) => {
     { name: "Other release" },
   ];
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const commits = [{ hash: "abcdef" }];
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
     .get(`/projects/100/merge_requests/1/closes_issues`)
     .reply(200, [])
@@ -147,16 +147,16 @@ test.serial("Does not post comments when successCommentCondition disables it", a
   const nextRelease = { version: "1.0.0" };
   const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }];
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const commits = [{ hash: "abcdef" }, { hash: "fedcba" }];
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .reply(200, [
       { project_id: 100, iid: 1, state: "merged" },
       { project_id: 200, iid: 2, state: "closed" },
       { project_id: 300, iid: 3, state: "merged" },
     ])
-    .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/fedcba/merge_requests`)
     .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
     .get(`/projects/100/merge_requests/1/closes_issues`)
     .reply(200, [
@@ -180,16 +180,16 @@ test.serial("Does not post comments on issues when successCommentCondition disab
   const nextRelease = { version: "1.0.0" };
   const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }];
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const commits = [{ hash: "abcdef" }, { hash: "fedcba" }];
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .reply(200, [
       { project_id: 100, iid: 1, state: "merged" },
       { project_id: 200, iid: 2, state: "closed" },
       { project_id: 300, iid: 3, state: "merged" },
     ])
-    .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/fedcba/merge_requests`)
     .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
     .get(`/projects/100/merge_requests/1/closes_issues`)
     .reply(200, [
@@ -219,16 +219,16 @@ test.serial("Only posts comments on issues which are found using the successComm
   const nextRelease = { version: "1.0.0" };
   const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }];
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const commits = [{ hash: "abcdef" }, { hash: "fedcba" }];
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .reply(200, [
       { project_id: 100, iid: 1, state: "merged" },
       { project_id: 200, iid: 2, state: "closed" },
       { project_id: 300, iid: 3, state: "merged" },
     ])
-    .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/fedcba/merge_requests`)
     .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
     .get(`/projects/100/merge_requests/1/closes_issues`)
     .reply(200, [
@@ -258,16 +258,16 @@ test.serial(
     const nextRelease = { version: "1.0.0" };
     const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }];
     const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-    const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+    const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
     const commits = [{ hash: "abcdef" }, { hash: "fedcba" }];
     const gitlab = authenticate(env)
-      .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+      .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
       .reply(200, [
         { project_id: 100, iid: 1, state: "merged" },
         { project_id: 200, iid: 2, state: "closed" },
         { project_id: 300, iid: 3, state: "merged" },
       ])
-      .get(`/projects/${encodedRepoId}/repository/commits/fedcba/merge_requests`)
+      .get(`/projects/${encodedProjectPath}/repository/commits/fedcba/merge_requests`)
       .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
       .get(`/projects/100/merge_requests/1/closes_issues`)
       .reply(200, [
@@ -314,14 +314,14 @@ test.serial("Retries requests when rate limited", async (t) => {
   const nextRelease = { version: "1.0.0" };
   const releases = [{ name: RELEASE_NAME, url: "https://gitlab.com/test_user/test_repo/-/releases/v1.0.0" }];
   const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` };
-  const encodedRepoId = encodeURIComponent(`${owner}/${repo}`);
+  const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`);
   const commits = [{ hash: "abcdef" }];
   const retryLimit = 3;
   const gitlab = authenticate(env)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .times(retryLimit)
     .reply(429)
-    .get(`/projects/${encodedRepoId}/repository/commits/abcdef/merge_requests`)
+    .get(`/projects/${encodedProjectPath}/repository/commits/abcdef/merge_requests`)
     .reply(200, [{ project_id: 100, iid: 1, state: "merged" }])
     .get(`/projects/100/merge_requests/1/closes_issues`)
     .reply(200, [])

From af2958640ab1feb64fca33c50a3d35585da9c231 Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Fri, 10 Jan 2025 20:47:47 +0100
Subject: [PATCH 2/9] refactor: introduce projectApiUrl abstraction

---
 lib/fail.js    |  3 ++-
 lib/publish.js | 13 +++++++------
 lib/success.js |  5 +++--
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/lib/fail.js b/lib/fail.js
index d997d9f9..6353b70e 100644
--- a/lib/fail.js
+++ b/lib/fail.js
@@ -27,6 +27,7 @@ export default async (pluginConfig, context) => {
   } = resolveConfig(pluginConfig, context);
   const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
   const encodedProjectPath = encodeURIComponent(projectPath);
+  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
   const apiOptions = {
     headers: { "PRIVATE-TOKEN": gitlabToken },
     retry: { limit: retryLimit },
@@ -42,7 +43,7 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
     const encodedFailTitle = encodeURIComponent(failTitle);
     const description = failComment ? template(failComment)({ branch, errors }) : getFailComment(branch, errors);
 
-    const issuesEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}/issues`);
+    const issuesEndpoint = urlJoin(projectApiUrl, `issues`);
     const openFailTitleIssueEndpoint = urlJoin(issuesEndpoint, `?state=opened&search=${encodedFailTitle}`);
 
     const openFailTitleIssues = await got(openFailTitleIssueEndpoint, { ...apiOptions }).json();
diff --git a/lib/publish.js b/lib/publish.js
index d09ef27d..816fc216 100644
--- a/lib/publish.js
+++ b/lib/publish.js
@@ -29,6 +29,7 @@ export default async (pluginConfig, context) => {
   const assetsList = [];
   const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
   const encodedProjectPath = encodeURIComponent(projectPath);
+  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
   const encodedGitTag = encodeURIComponent(gitTag);
   const encodedVersion = encodeURIComponent(version);
   const apiOptions = {
@@ -114,8 +115,8 @@ export default async (pluginConfig, context) => {
             const encodedLabel = encodeURIComponent(label);
             // https://docs.gitlab.com/ee/user/packages/generic_packages/#publish-a-package-file
             uploadEndpoint = urlJoin(
-              gitlabApiUrl,
-              `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}?${
+              projectApiUrl,
+              `packages/generic/release/${encodedVersion}/${encodedLabel}?${
                 status ? `status=${status}&` : ""
               }select=package_file`
             );
@@ -131,8 +132,8 @@ export default async (pluginConfig, context) => {
 
             // https://docs.gitlab.com/ee/user/packages/generic_packages/#download-package-file
             const url = urlJoin(
-              gitlabApiUrl,
-              `/projects/${encodedProjectPath}/packages/generic/release/${encodedVersion}/${encodedLabel}`
+              projectApiUrl,
+              `packages/generic/release/${encodedVersion}/${encodedLabel}`
             );
 
             assetsList.push({ label, alt: "release", url, type: "package", filepath });
@@ -140,7 +141,7 @@ export default async (pluginConfig, context) => {
             logger.log("Uploaded file: %s (%s)", url, response.file.url);
           } else {
             // Handle normal assets
-            uploadEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}/uploads`);
+            uploadEndpoint = urlJoin(projectApiUrl, "uploads");
 
             debug("POST-ing the file %s to %s", file, uploadEndpoint);
 
@@ -167,7 +168,7 @@ export default async (pluginConfig, context) => {
 
   debug("Create a release for git tag %o with commit %o", gitTag, gitHead);
 
-  const createReleaseEndpoint = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}/releases`);
+  const createReleaseEndpoint = urlJoin(projectApiUrl, "releases");
 
   const json = {
     /* eslint-disable camelcase */
diff --git a/lib/success.js b/lib/success.js
index 2444fe8f..6edfee8a 100644
--- a/lib/success.js
+++ b/lib/success.js
@@ -19,6 +19,7 @@ export default async (pluginConfig, context) => {
     resolveConfig(pluginConfig, context);
   const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
   const encodedProjectPath = encodeURIComponent(projectPath);
+  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
   const apiOptions = {
     headers: { "PRIVATE-TOKEN": gitlabToken },
     retry: { limit: retryLimit },
@@ -78,8 +79,8 @@ Using 'false' for 'successComment' is deprecated and will be removed in a future
 
       const getRelatedMergeRequests = async (commitHash) => {
         const relatedMergeRequestsEndpoint = urlJoin(
-          gitlabApiUrl,
-          `/projects/${encodedProjectPath}/repository/commits/${commitHash}/merge_requests`
+          projectApiUrl,
+          `repository/commits/${commitHash}/merge_requests`
         );
         debug("Getting MRs from %s", relatedMergeRequestsEndpoint);
         const relatedMergeRequests = await got

From bc3280656ac916feb1f28297d8eabff74ea3a9e7 Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Fri, 10 Jan 2025 21:04:59 +0100
Subject: [PATCH 3/9] refactor: introduce getProjectContext abstraction

---
 lib/fail.js                |  7 +++----
 lib/get-project-context.js | 14 ++++++++++++++
 lib/publish.js             | 12 ++++--------
 lib/success.js             | 11 +++--------
 lib/verify.js              |  7 +++----
 5 files changed, 27 insertions(+), 24 deletions(-)
 create mode 100644 lib/get-project-context.js

diff --git a/lib/fail.js b/lib/fail.js
index 6353b70e..05516313 100644
--- a/lib/fail.js
+++ b/lib/fail.js
@@ -4,8 +4,8 @@ import got from "got";
 import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import resolveConfig from "./resolve-config.js";
-import getProjectPath from "./get-project-path.js";
 import getFailComment from "./get-fail-comment.js";
+import getProjectContext from "./get-project-context.js";
 
 export default async (pluginConfig, context) => {
   const {
@@ -25,9 +25,8 @@ export default async (pluginConfig, context) => {
     assignee,
     retryLimit,
   } = resolveConfig(pluginConfig, context);
-  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
-  const encodedProjectPath = encodeURIComponent(projectPath);
-  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
+  const { encodedProjectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);
+
   const apiOptions = {
     headers: { "PRIVATE-TOKEN": gitlabToken },
     retry: { limit: retryLimit },
diff --git a/lib/get-project-context.js b/lib/get-project-context.js
new file mode 100644
index 00000000..f66dda4b
--- /dev/null
+++ b/lib/get-project-context.js
@@ -0,0 +1,14 @@
+import urlJoin from "url-join";
+
+import getProjectPath from "./get-project-path.js";
+
+export default (context, gitlabUrl, gitlabApiUrl, repositoryUrl) => {
+  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
+  const encodedProjectPath = encodeURIComponent(projectPath);
+  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
+  return {
+    projectPath,
+    encodedProjectPath,
+    projectApiUrl,
+  };
+};
diff --git a/lib/publish.js b/lib/publish.js
index 816fc216..3f92f7e4 100644
--- a/lib/publish.js
+++ b/lib/publish.js
@@ -9,9 +9,9 @@ import got from "got";
 import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import resolveConfig from "./resolve-config.js";
-import getProjectPath from "./get-project-path.js";
 import getAssets from "./glob-assets.js";
 import { RELEASE_NAME } from "./definitions/constants.js";
+import getProjectContext from "./get-project-context.js";
 
 const isUrlScheme = (value) => /^(https|http|ftp):\/\//.test(value);
 
@@ -27,9 +27,8 @@ export default async (pluginConfig, context) => {
     context
   );
   const assetsList = [];
-  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
-  const encodedProjectPath = encodeURIComponent(projectPath);
-  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
+  const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);
+
   const encodedGitTag = encodeURIComponent(gitTag);
   const encodedVersion = encodeURIComponent(version);
   const apiOptions = {
@@ -131,10 +130,7 @@ export default async (pluginConfig, context) => {
             }
 
             // https://docs.gitlab.com/ee/user/packages/generic_packages/#download-package-file
-            const url = urlJoin(
-              projectApiUrl,
-              `packages/generic/release/${encodedVersion}/${encodedLabel}`
-            );
+            const url = urlJoin(projectApiUrl, `packages/generic/release/${encodedVersion}/${encodedLabel}`);
 
             assetsList.push({ label, alt: "release", url, type: "package", filepath });
 
diff --git a/lib/success.js b/lib/success.js
index 6edfee8a..929b6b9e 100644
--- a/lib/success.js
+++ b/lib/success.js
@@ -4,7 +4,7 @@ import got from "got";
 import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import resolveConfig from "./resolve-config.js";
-import getProjectPath from "./get-project-path.js";
+import getProjectContext from "./get-project-context.js";
 import getSuccessComment from "./get-success-comment.js";
 
 export default async (pluginConfig, context) => {
@@ -17,9 +17,7 @@ export default async (pluginConfig, context) => {
   } = context;
   const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, successCommentCondition, proxy, retryLimit } =
     resolveConfig(pluginConfig, context);
-  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
-  const encodedProjectPath = encodeURIComponent(projectPath);
-  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
+  const { projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);
   const apiOptions = {
     headers: { "PRIVATE-TOKEN": gitlabToken },
     retry: { limit: retryLimit },
@@ -78,10 +76,7 @@ Using 'false' for 'successComment' is deprecated and will be removed in a future
       };
 
       const getRelatedMergeRequests = async (commitHash) => {
-        const relatedMergeRequestsEndpoint = urlJoin(
-          projectApiUrl,
-          `repository/commits/${commitHash}/merge_requests`
-        );
+        const relatedMergeRequestsEndpoint = urlJoin(projectApiUrl, `repository/commits/${commitHash}/merge_requests`);
         debug("Getting MRs from %s", relatedMergeRequestsEndpoint);
         const relatedMergeRequests = await got
           .get(relatedMergeRequestsEndpoint, {
diff --git a/lib/verify.js b/lib/verify.js
index 535df659..fdfc3b7f 100644
--- a/lib/verify.js
+++ b/lib/verify.js
@@ -1,11 +1,10 @@
 import { isString, isPlainObject, isNil, isArray } from "lodash-es";
-import urlJoin from "url-join";
 import got from "got";
 import _debug from "debug";
 const debug = _debug("semantic-release:gitlab");
 import AggregateError from "aggregate-error";
 import resolveConfig from "./resolve-config.js";
-import getProjectPath from "./get-project-path.js";
+import getProjectContext from "./get-project-context.js";
 import getError from "./get-error.js";
 
 const isNonEmptyString = (value) => isString(value) && value.trim();
@@ -32,7 +31,7 @@ export default async (pluginConfig, context) => {
     logger,
   } = context;
   const { gitlabToken, gitlabUrl, gitlabApiUrl, proxy, ...options } = resolveConfig(pluginConfig, context);
-  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
+  const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);
 
   debug("apiUrl: %o", gitlabApiUrl);
   debug("projectPath: %o", projectPath);
@@ -64,7 +63,7 @@ export default async (pluginConfig, context) => {
       ({
         permissions: { project_access: projectAccess, group_access: groupAccess },
       } = await got
-        .get(urlJoin(gitlabApiUrl, `/projects/${encodeURIComponent(projectPath)}`), {
+        .get(projectApiUrl, {
           headers: { "PRIVATE-TOKEN": gitlabToken },
           ...proxy,
         })

From 6b96943371c0e6e8b0a46c33b2ebcc73552c7a41 Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Fri, 10 Jan 2025 21:09:58 +0100
Subject: [PATCH 4/9] fix: use project ID for API calls if available

---
 lib/get-project-context.js | 4 +++-
 lib/get-project-id.js      | 4 ++++
 2 files changed, 7 insertions(+), 1 deletion(-)
 create mode 100644 lib/get-project-id.js

diff --git a/lib/get-project-context.js b/lib/get-project-context.js
index f66dda4b..5844df0b 100644
--- a/lib/get-project-context.js
+++ b/lib/get-project-context.js
@@ -1,11 +1,13 @@
 import urlJoin from "url-join";
 
 import getProjectPath from "./get-project-path.js";
+import getProjectId from "./get-project-id.js";
 
 export default (context, gitlabUrl, gitlabApiUrl, repositoryUrl) => {
+  const projectId = getProjectId(context);
   const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
   const encodedProjectPath = encodeURIComponent(projectPath);
-  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${encodedProjectPath}`);
+  const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${projectId ?? encodedProjectPath}`);
   return {
     projectPath,
     encodedProjectPath,
diff --git a/lib/get-project-id.js b/lib/get-project-id.js
new file mode 100644
index 00000000..fc43432d
--- /dev/null
+++ b/lib/get-project-id.js
@@ -0,0 +1,4 @@
+export default ({ envCi: { service } = {}, env: { CI_PROJECT_ID } }) =>
+  service === "gitlab" && CI_PROJECT_ID
+    ? CI_PROJECT_ID
+    : null;

From 743ab0a41d9bda3547d82bad1b9a9e6f72be839d Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Fri, 10 Jan 2025 21:10:35 +0100
Subject: [PATCH 5/9] style: format code

---
 lib/get-project-id.js         | 4 +---
 test/get-project-path.test.js | 5 ++++-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/lib/get-project-id.js b/lib/get-project-id.js
index fc43432d..8062acff 100644
--- a/lib/get-project-id.js
+++ b/lib/get-project-id.js
@@ -1,4 +1,2 @@
 export default ({ envCi: { service } = {}, env: { CI_PROJECT_ID } }) =>
-  service === "gitlab" && CI_PROJECT_ID
-    ? CI_PROJECT_ID
-    : null;
+  service === "gitlab" && CI_PROJECT_ID ? CI_PROJECT_ID : null;
diff --git a/test/get-project-path.test.js b/test/get-project-path.test.js
index 88379aff..23825973 100644
--- a/test/get-project-path.test.js
+++ b/test/get-project-path.test.js
@@ -24,7 +24,10 @@ test("Parse repo id with context in repo URL", (t) => {
 
 test("Parse repo id with context not in repo URL", (t) => {
   t.is(getProjectPath({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/owner/repo.git"), "owner/repo");
-  t.is(getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/owner/repo.git"), "owner/repo");
+  t.is(
+    getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/owner/repo.git"),
+    "owner/repo"
+  );
 });
 
 test("Parse repo id with organization and subgroup", (t) => {

From 2102ec3d46a68f51c9e457eeec7abca8bbf8d980 Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Sun, 12 Jan 2025 16:33:09 +0100
Subject: [PATCH 6/9] refactor: inline getProjectId / getProjectPath functions

---
 lib/get-project-context.js       |  24 +++--
 lib/get-project-id.js            |   2 -
 lib/get-project-path.js          |  11 --
 test/get-project-context.test.js | 170 +++++++++++++++++++++++++++++++
 test/get-project-path.test.js    |  64 ------------
 5 files changed, 188 insertions(+), 83 deletions(-)
 delete mode 100644 lib/get-project-id.js
 delete mode 100644 lib/get-project-path.js
 create mode 100644 test/get-project-context.test.js
 delete mode 100644 test/get-project-path.test.js

diff --git a/lib/get-project-context.js b/lib/get-project-context.js
index 5844df0b..52184a05 100644
--- a/lib/get-project-context.js
+++ b/lib/get-project-context.js
@@ -1,14 +1,26 @@
+import escapeStringRegexp from "escape-string-regexp";
+import parseUrl from "parse-url";
 import urlJoin from "url-join";
 
-import getProjectPath from "./get-project-path.js";
-import getProjectId from "./get-project-id.js";
-
-export default (context, gitlabUrl, gitlabApiUrl, repositoryUrl) => {
-  const projectId = getProjectId(context);
-  const projectPath = getProjectPath(context, gitlabUrl, repositoryUrl);
+export default (
+  { envCi: { service } = {}, env: { CI_PROJECT_ID, CI_PROJECT_PATH } },
+  gitlabUrl,
+  gitlabApiUrl,
+  repositoryUrl
+) => {
+  const projectId = service === "gitlab" && CI_PROJECT_ID ? CI_PROJECT_ID : null;
+  const projectPath =
+    service === "gitlab" && CI_PROJECT_PATH
+      ? CI_PROJECT_PATH
+      : parseUrl(repositoryUrl)
+          .pathname.replace(new RegExp(`^${escapeStringRegexp(parseUrl(gitlabUrl).pathname)}`), "")
+          .replace(/^\//, "")
+          .replace(/\/$/, "")
+          .replace(/\.git$/, "");
   const encodedProjectPath = encodeURIComponent(projectPath);
   const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${projectId ?? encodedProjectPath}`);
   return {
+    projectId,
     projectPath,
     encodedProjectPath,
     projectApiUrl,
diff --git a/lib/get-project-id.js b/lib/get-project-id.js
deleted file mode 100644
index 8062acff..00000000
--- a/lib/get-project-id.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export default ({ envCi: { service } = {}, env: { CI_PROJECT_ID } }) =>
-  service === "gitlab" && CI_PROJECT_ID ? CI_PROJECT_ID : null;
diff --git a/lib/get-project-path.js b/lib/get-project-path.js
deleted file mode 100644
index f56cf82b..00000000
--- a/lib/get-project-path.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import parseUrl from "parse-url";
-import escapeStringRegexp from "escape-string-regexp";
-
-export default ({ envCi: { service } = {}, env: { CI_PROJECT_PATH } }, gitlabUrl, repositoryUrl) =>
-  service === "gitlab" && CI_PROJECT_PATH
-    ? CI_PROJECT_PATH
-    : parseUrl(repositoryUrl)
-        .pathname.replace(new RegExp(`^${escapeStringRegexp(parseUrl(gitlabUrl).pathname)}`), "")
-        .replace(/^\//, "")
-        .replace(/\/$/, "")
-        .replace(/\.git$/, "");
diff --git a/test/get-project-context.test.js b/test/get-project-context.test.js
new file mode 100644
index 00000000..f631016a
--- /dev/null
+++ b/test/get-project-context.test.js
@@ -0,0 +1,170 @@
+import test from "ava";
+import getProjectContext from "../lib/get-project-context.js";
+
+test("Parse project path with https URL", (t) => {
+  t.is(
+    getProjectContext({ env: {} }, "https://gitlbab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo.git")
+      .projectPath,
+    "owner/repo"
+  );
+  t.is(
+    getProjectContext({ env: {} }, "https://gitlbab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo").projectPath,
+    "owner/repo"
+  );
+});
+
+test("Parse project path with git URL", (t) => {
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlab.com",
+      "https://api.gitlab.com",
+      "git+ssh://git@gitlab.com/owner/repo.git"
+    ).projectPath,
+    "owner/repo"
+  );
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlab.com",
+      "https://api.gitlab.com",
+      "git+ssh://git@gitlab.com/owner/repo"
+    ).projectPath,
+    "owner/repo"
+  );
+});
+
+test("Parse project path with context in repo URL", (t) => {
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlbab.com/context",
+      "https://api.gitlab.com",
+      "https://gitlab.com/context/owner/repo.git"
+    ).projectPath,
+    "owner/repo"
+  );
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlbab.com/context",
+      "https://api.gitlab.com",
+      "git+ssh://git@gitlab.com/context/owner/repo.git"
+    ).projectPath,
+    "owner/repo"
+  );
+});
+
+test("Parse project path with context not in repo URL", (t) => {
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlbab.com/context",
+      "https://api.gitlab.com",
+      "https://gitlab.com/owner/repo.git"
+    ).projectPath,
+    "owner/repo"
+  );
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlbab.com/context",
+      "https://api.gitlab.com",
+      "git+ssh://git@gitlab.com/owner/repo.git"
+    ).projectPath,
+    "owner/repo"
+  );
+});
+
+test("Parse project path with organization and subgroup", (t) => {
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlbab.com/context",
+      "https://api.gitlab.com",
+      "https://gitlab.com/orga/subgroup/owner/repo.git"
+    ).projectPath,
+    "orga/subgroup/owner/repo"
+  );
+  t.is(
+    getProjectContext(
+      { env: {} },
+      "https://gitlbab.com/context",
+      "https://api.gitlab.com",
+      "git+ssh://git@gitlab.com/orga/subgroup/owner/repo.git"
+    ).projectPath,
+    "orga/subgroup/owner/repo"
+  );
+});
+
+test("Get project path from GitLab CI", (t) => {
+  t.is(
+    getProjectContext(
+      { envCi: { service: "gitlab" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
+      "https://gitlbab.com",
+      "https://api.gitlab.com",
+      "https://gitlab.com/owner/repo.git"
+    ).projectPath,
+    "other-owner/other-repo"
+  );
+});
+
+test("Ignore CI_PROJECT_PATH if not on GitLab CI", (t) => {
+  t.is(
+    getProjectContext(
+      { envCi: { service: "travis" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
+      "https://gitlbab.com",
+      "https://api.gitlab.com",
+      "https://gitlab.com/owner/repo.git"
+    ).projectPath,
+    "owner/repo"
+  );
+});
+
+test("Get project ID from GitLab CI", (t) => {
+  t.is(
+    getProjectContext(
+      { envCi: { service: "gitlab" }, env: { CI_PROJECT_ID: "42" } },
+      "https://gitlbab.com",
+      "https://api.gitlab.com",
+      "https://gitlab.com/owner/repo.git"
+    ).projectId,
+    "42"
+  );
+});
+
+test("Ignore CI_PROJECT_ID if not on GitLab CI", (t) => {
+  t.is(
+    getProjectContext(
+      { envCi: { service: "travis" }, env: { CI_PROJECT_ID: "42" } },
+      "https://gitlbab.com",
+      "https://api.gitlab.com",
+      "https://gitlab.com/owner/repo.git"
+    ).projectId,
+    null
+  );
+});
+
+test("Uses project API URL with project path", (t) => {
+  t.is(
+    getProjectContext(
+      { envCi: { service: "gitlab" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
+      "https://gitlab.com",
+      "https://api.gitlab.com",
+      "https://gitlab.com/owner/repo.git"
+    ).projectApiUrl,
+    "https://api.gitlab.com/projects/other-owner%2Fother-repo"
+  );
+});
+
+test("Uses project API URL with project ID", (t) => {
+  t.is(
+    getProjectContext(
+      { envCi: { service: "gitlab" }, env: { CI_PROJECT_ID: "42" } },
+      "https://gitlab.com",
+      "https://api.gitlab.com",
+      "https://gitlab.com/owner/repo.git"
+    ).projectApiUrl,
+    "https://api.gitlab.com/projects/42"
+  );
+});
diff --git a/test/get-project-path.test.js b/test/get-project-path.test.js
deleted file mode 100644
index 23825973..00000000
--- a/test/get-project-path.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import test from "ava";
-import getProjectPath from "../lib/get-project-path.js";
-
-test("Parse repo id with https URL", (t) => {
-  t.is(getProjectPath({ env: {} }, "https://gitlbab.com", "https://gitlab.com/owner/repo.git"), "owner/repo");
-  t.is(getProjectPath({ env: {} }, "https://gitlbab.com", "https://gitlab.com/owner/repo"), "owner/repo");
-});
-
-test("Parse repo id with git URL", (t) => {
-  t.is(getProjectPath({ env: {} }, "https://gitlab.com", "git+ssh://git@gitlab.com/owner/repo.git"), "owner/repo");
-  t.is(getProjectPath({ env: {} }, "https://gitlab.com", "git+ssh://git@gitlab.com/owner/repo"), "owner/repo");
-});
-
-test("Parse repo id with context in repo URL", (t) => {
-  t.is(
-    getProjectPath({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/context/owner/repo.git"),
-    "owner/repo"
-  );
-  t.is(
-    getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/context/owner/repo.git"),
-    "owner/repo"
-  );
-});
-
-test("Parse repo id with context not in repo URL", (t) => {
-  t.is(getProjectPath({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/owner/repo.git"), "owner/repo");
-  t.is(
-    getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/owner/repo.git"),
-    "owner/repo"
-  );
-});
-
-test("Parse repo id with organization and subgroup", (t) => {
-  t.is(
-    getProjectPath({ env: {} }, "https://gitlbab.com/context", "https://gitlab.com/orga/subgroup/owner/repo.git"),
-    "orga/subgroup/owner/repo"
-  );
-  t.is(
-    getProjectPath({ env: {} }, "https://gitlab.com/context", "git+ssh://git@gitlab.com/orga/subgroup/owner/repo.git"),
-    "orga/subgroup/owner/repo"
-  );
-});
-
-test("Get repo id from GitLab CI", (t) => {
-  t.is(
-    getProjectPath(
-      { envCi: { service: "gitlab" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
-      "https://gitlbab.com",
-      "https://gitlab.com/owner/repo.git"
-    ),
-    "other-owner/other-repo"
-  );
-});
-
-test("Ignore CI_PROJECT_PATH if not on GitLab CI", (t) => {
-  t.is(
-    getProjectPath(
-      { envCi: { service: "travis" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
-      "https://gitlbab.com",
-      "https://gitlab.com/owner/repo.git"
-    ),
-    "owner/repo"
-  );
-});

From 38ac050cebf8016b7d080aa630f7bb7ca89639ef Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Sun, 12 Jan 2025 16:55:22 +0100
Subject: [PATCH 7/9] style: fix formatting

---
 test/get-project-context.test.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/get-project-context.test.js b/test/get-project-context.test.js
index f631016a..0b4c7841 100644
--- a/test/get-project-context.test.js
+++ b/test/get-project-context.test.js
@@ -8,7 +8,8 @@ test("Parse project path with https URL", (t) => {
     "owner/repo"
   );
   t.is(
-    getProjectContext({ env: {} }, "https://gitlbab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo").projectPath,
+    getProjectContext({ env: {} }, "https://gitlbab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo")
+      .projectPath,
     "owner/repo"
   );
 });

From beb7ac69e558a6ed1afd65e86749c8224b202e68 Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Mon, 13 Jan 2025 16:50:00 +0100
Subject: [PATCH 8/9] refactor: don't expose project ID

---
 lib/get-project-context.js       |  1 -
 test/get-project-context.test.js | 24 ------------------------
 2 files changed, 25 deletions(-)

diff --git a/lib/get-project-context.js b/lib/get-project-context.js
index 52184a05..ad7bd049 100644
--- a/lib/get-project-context.js
+++ b/lib/get-project-context.js
@@ -20,7 +20,6 @@ export default (
   const encodedProjectPath = encodeURIComponent(projectPath);
   const projectApiUrl = urlJoin(gitlabApiUrl, `/projects/${projectId ?? encodedProjectPath}`);
   return {
-    projectId,
     projectPath,
     encodedProjectPath,
     projectApiUrl,
diff --git a/test/get-project-context.test.js b/test/get-project-context.test.js
index 0b4c7841..fa450eff 100644
--- a/test/get-project-context.test.js
+++ b/test/get-project-context.test.js
@@ -122,30 +122,6 @@ test("Ignore CI_PROJECT_PATH if not on GitLab CI", (t) => {
   );
 });
 
-test("Get project ID from GitLab CI", (t) => {
-  t.is(
-    getProjectContext(
-      { envCi: { service: "gitlab" }, env: { CI_PROJECT_ID: "42" } },
-      "https://gitlbab.com",
-      "https://api.gitlab.com",
-      "https://gitlab.com/owner/repo.git"
-    ).projectId,
-    "42"
-  );
-});
-
-test("Ignore CI_PROJECT_ID if not on GitLab CI", (t) => {
-  t.is(
-    getProjectContext(
-      { envCi: { service: "travis" }, env: { CI_PROJECT_ID: "42" } },
-      "https://gitlbab.com",
-      "https://api.gitlab.com",
-      "https://gitlab.com/owner/repo.git"
-    ).projectId,
-    null
-  );
-});
-
 test("Uses project API URL with project path", (t) => {
   t.is(
     getProjectContext(

From ba2d06331325bbeb223a966fee8878d5cb4e294e Mon Sep 17 00:00:00 2001
From: Florian Greinacher <florian.greinacher@siemens.com>
Date: Mon, 13 Jan 2025 16:50:27 +0100
Subject: [PATCH 9/9] style: fix typo

---
 test/get-project-context.test.js | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/test/get-project-context.test.js b/test/get-project-context.test.js
index fa450eff..2ef3dfb4 100644
--- a/test/get-project-context.test.js
+++ b/test/get-project-context.test.js
@@ -3,12 +3,12 @@ import getProjectContext from "../lib/get-project-context.js";
 
 test("Parse project path with https URL", (t) => {
   t.is(
-    getProjectContext({ env: {} }, "https://gitlbab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo.git")
+    getProjectContext({ env: {} }, "https://gitlab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo.git")
       .projectPath,
     "owner/repo"
   );
   t.is(
-    getProjectContext({ env: {} }, "https://gitlbab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo")
+    getProjectContext({ env: {} }, "https://gitlab.com", "https://api.gitlab.com", "https://gitlab.com/owner/repo")
       .projectPath,
     "owner/repo"
   );
@@ -39,7 +39,7 @@ test("Parse project path with context in repo URL", (t) => {
   t.is(
     getProjectContext(
       { env: {} },
-      "https://gitlbab.com/context",
+      "https://gitlab.com/context",
       "https://api.gitlab.com",
       "https://gitlab.com/context/owner/repo.git"
     ).projectPath,
@@ -48,7 +48,7 @@ test("Parse project path with context in repo URL", (t) => {
   t.is(
     getProjectContext(
       { env: {} },
-      "https://gitlbab.com/context",
+      "https://gitlab.com/context",
       "https://api.gitlab.com",
       "git+ssh://git@gitlab.com/context/owner/repo.git"
     ).projectPath,
@@ -60,7 +60,7 @@ test("Parse project path with context not in repo URL", (t) => {
   t.is(
     getProjectContext(
       { env: {} },
-      "https://gitlbab.com/context",
+      "https://gitlab.com/context",
       "https://api.gitlab.com",
       "https://gitlab.com/owner/repo.git"
     ).projectPath,
@@ -69,7 +69,7 @@ test("Parse project path with context not in repo URL", (t) => {
   t.is(
     getProjectContext(
       { env: {} },
-      "https://gitlbab.com/context",
+      "https://gitlab.com/context",
       "https://api.gitlab.com",
       "git+ssh://git@gitlab.com/owner/repo.git"
     ).projectPath,
@@ -81,7 +81,7 @@ test("Parse project path with organization and subgroup", (t) => {
   t.is(
     getProjectContext(
       { env: {} },
-      "https://gitlbab.com/context",
+      "https://gitlab.com/context",
       "https://api.gitlab.com",
       "https://gitlab.com/orga/subgroup/owner/repo.git"
     ).projectPath,
@@ -90,7 +90,7 @@ test("Parse project path with organization and subgroup", (t) => {
   t.is(
     getProjectContext(
       { env: {} },
-      "https://gitlbab.com/context",
+      "https://gitlab.com/context",
       "https://api.gitlab.com",
       "git+ssh://git@gitlab.com/orga/subgroup/owner/repo.git"
     ).projectPath,
@@ -102,7 +102,7 @@ test("Get project path from GitLab CI", (t) => {
   t.is(
     getProjectContext(
       { envCi: { service: "gitlab" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
-      "https://gitlbab.com",
+      "https://gitlab.com",
       "https://api.gitlab.com",
       "https://gitlab.com/owner/repo.git"
     ).projectPath,
@@ -114,7 +114,7 @@ test("Ignore CI_PROJECT_PATH if not on GitLab CI", (t) => {
   t.is(
     getProjectContext(
       { envCi: { service: "travis" }, env: { CI_PROJECT_PATH: "other-owner/other-repo" } },
-      "https://gitlbab.com",
+      "https://gitlab.com",
       "https://api.gitlab.com",
       "https://gitlab.com/owner/repo.git"
     ).projectPath,