From 457241f8a1f6d9fec1326eae2cd181d075ac1c9d Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Fri, 16 May 2025 16:47:41 +0200 Subject: [PATCH] feat: raise error when GitLab CLI is required but not installed --- lib/definitions/errors.js | 4 ++++ lib/glab.js | 5 +++++ lib/publish.js | 4 ++-- lib/verify.js | 17 ++++++++++++++++- test/verify.test.js | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 lib/glab.js diff --git a/lib/definitions/errors.js b/lib/definitions/errors.js index 51508538..7c9e8fcc 100644 --- a/lib/definitions/errors.js +++ b/lib/definitions/errors.js @@ -87,4 +87,8 @@ Please make sure the GitLab user associated with the token has the [permission t Please make sure to create a [GitLab personal access token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) and to set it in the \`GL_TOKEN\` or \`GITLAB_TOKEN\` environment variable on your CI environment. The token must allow to push to the repository ${repositoryUrl}.`, }), + EGLABNOTINSTALLED: () => ({ + message: 'GitLab CLI not installed.', + details: 'The [GitLab CLI needs to be installed](https://gitlab.com/gitlab-org/cli#installation) so that the project\'s CI components can be published.', + }), }; diff --git a/lib/glab.js b/lib/glab.js new file mode 100644 index 00000000..82fb2b90 --- /dev/null +++ b/lib/glab.js @@ -0,0 +1,5 @@ +import { execa } from "execa"; + +export default async (args, options) => { + return execa("glab", args, options); +}; diff --git a/lib/publish.js b/lib/publish.js index 379cf68b..379f299d 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -12,7 +12,7 @@ import resolveConfig from "./resolve-config.js"; import getAssets from "./glob-assets.js"; import { RELEASE_NAME } from "./definitions/constants.js"; import getProjectContext from "./get-project-context.js"; -import { execa } from "execa"; +import glab from "./glab.js"; const isUrlScheme = (value) => /^(https|http|ftp):\/\//.test(value); export default async (pluginConfig, context) => { @@ -195,7 +195,7 @@ export default async (pluginConfig, context) => { if (publishToCatalog) { try { - await execa("glab", ["repo", "publish", "catalog", gitTag], { + await glab(["repo", "publish", "catalog", gitTag], { cwd, timeout: 30 * 1000, env: { GITLAB_TOKEN: gitlabToken }, diff --git a/lib/verify.js b/lib/verify.js index fdfc3b7f..58b1c7cb 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -30,7 +30,10 @@ export default async (pluginConfig, context) => { options: { repositoryUrl }, logger, } = context; - const { gitlabToken, gitlabUrl, gitlabApiUrl, proxy, ...options } = resolveConfig(pluginConfig, context); + const { gitlabToken, gitlabUrl, gitlabApiUrl, proxy, publishToCatalog, ...options } = resolveConfig( + pluginConfig, + context + ); const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl); debug("apiUrl: %o", gitlabApiUrl); @@ -89,6 +92,18 @@ export default async (pluginConfig, context) => { } } + if (publishToCatalog === true) { + try { + logger.log("Verifying that the GitLab CLI is installed"); + await glab(["version"], { + timeout: 5 * 1000, + }); + } catch (error) { + logger.error("The GitLab CLI is required but failed to run:\n%O", error); + errors.push(getError("EGLABNOTINSTALLED")); + } + } + if (errors.length > 0) { throw new AggregateError(errors); } diff --git a/test/verify.test.js b/test/verify.test.js index 48f3004f..5fdb6f27 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -3,6 +3,7 @@ import nock from "nock"; import { stub } from "sinon"; import verify from "../lib/verify.js"; import authenticate from "./helpers/mock-gitlab.js"; +import * as td from "testdouble"; /* eslint camelcase: ["error", {properties: "never"}] */ @@ -988,3 +989,34 @@ test.serial( t.true(gitlab.isDone()); } ); + +test.serial( + 'Throw SemanticReleaseError if "publishToCatalog" option is set and the GitLab CLI is not installed.', + async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const gitlab = authenticate(env) + .get(`/projects/${owner}%2F${repo}`) + .reply(200, { permissions: { project_access: { access_level: 40 } } }); + + const execa = (await td.replaceEsm("execa")).execa; + td.when( + execa("glab", ["version"], { + timeout: 5000, + }) + ).thenReject(); + const verifyWithMockExeca = (await import("../lib/verify.js")).default; + const { + errors: [error], + } = await t.throwsAsync( + verifyWithMockExeca( + { publishToCatalog: true }, + { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } + ) + ); + t.is(error.name, "SemanticReleaseError"); + t.is(error.code, "EGLABNOTINSTALLED"); + t.true(gitlab.isDone()); + } +);