diff --git a/lib/publish.js b/lib/publish.js index a2ecec7e..f1110fb0 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -13,6 +13,8 @@ import getAssets from "./glob-assets.js"; import { RELEASE_NAME } from "./definitions/constants.js"; import getProjectContext from "./get-project-context.js"; import glab from "./glab.js"; +import shouldPublishToCatalog from "./should-publish-to-catalog.js"; + const isUrlScheme = (value) => /^(https|http|ftp):\/\//.test(value); export default async (pluginConfig, context) => { @@ -22,17 +24,8 @@ export default async (pluginConfig, context) => { nextRelease: { gitTag, gitHead, notes, version }, logger, } = context; - const { - gitlabToken, - gitlabUrl, - gitlabApiUrl, - assets, - milestones, - proxy, - retryLimit, - retryStatusCodes, - publishToCatalog, - } = resolveConfig(pluginConfig, context); + const config = resolveConfig(pluginConfig, context); + const { gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones, proxy, retryLimit, retryStatusCodes } = config; const assetsList = []; const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl); @@ -202,7 +195,7 @@ export default async (pluginConfig, context) => { const releaseUrl = urlJoin(gitlabUrl, projectPath, `/-/releases/${encodedGitTag}`); - if (publishToCatalog) { + if (await shouldPublishToCatalog({ ...config, projectPath }, context)) { try { await glab(["repo", "publish", "catalog", gitTag], { cwd, diff --git a/lib/resolve-config.js b/lib/resolve-config.js index fab271ab..486c70c4 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -33,6 +33,7 @@ export default ( HTTP_PROXY, HTTPS_PROXY, NO_PROXY, + CI_API_GRAPHQL_URL, }, } ) => { @@ -72,7 +73,13 @@ export default ( assignee, retryLimit: retryLimit ?? DEFAULT_RETRY_LIMIT, retryStatusCodes: DEFAULT_RETRY_STATUS_CODES, - publishToCatalog: publishToCatalog ?? false, + publishToCatalog, + gitlabGraphQlApiUrl: + userGitlabUrl + ? urlJoin(userGitlabUrl, "/graphql") + : service === "gitlab" && CI_API_GRAPHQL_URL + ? CI_API_GRAPHQL_URL + : urlJoin(defaultedGitlabUrl, "/graphql"), }; }; diff --git a/lib/should-publish-to-catalog.js b/lib/should-publish-to-catalog.js new file mode 100644 index 00000000..4b70be11 --- /dev/null +++ b/lib/should-publish-to-catalog.js @@ -0,0 +1,30 @@ +import got from "got"; + +export default async ({ gitlabGraphQlApiUrl, gitlabToken, projectPath, publishToCatalog }, { logger }) => { + if (publishToCatalog !== undefined) { + return publishToCatalog; + } + try { + const query = ` + query { + project(fullPath: "${projectPath}") { + isCatalogResource + } + } + `; + const response = await got + .post(gitlabGraphQlApiUrl, { + headers: { + "Private-Token": gitlabToken, + "Content-Type": "application/json", + Accept: "application/graphql-response+json", + }, + json: { query }, + }) + .json(); + return !!response.data.project.isCatalogResource; + } catch (error) { + logger.error("Error making GraphQL request:", error.message); + throw error; + } +}; diff --git a/lib/verify.js b/lib/verify.js index 0a0f7df7..5c274b53 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -7,6 +7,7 @@ import resolveConfig from "./resolve-config.js"; import getProjectContext from "./get-project-context.js"; import getError from "./get-error.js"; import glab from "./glab.js"; +import shouldPublishToCatalog from "./should-publish-to-catalog.js"; const isNonEmptyString = (value) => isString(value) && value.trim(); const isStringOrStringArray = (value) => @@ -31,10 +32,8 @@ export default async (pluginConfig, context) => { options: { repositoryUrl }, logger, } = context; - const { gitlabToken, gitlabUrl, gitlabApiUrl, proxy, publishToCatalog, ...options } = resolveConfig( - pluginConfig, - context - ); + const config = resolveConfig(pluginConfig, context); + const { gitlabToken, gitlabUrl, gitlabApiUrl, proxy, publishToCatalog, ...options } = config; const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl); debug("apiUrl: %o", gitlabApiUrl); @@ -93,7 +92,15 @@ export default async (pluginConfig, context) => { } } - if (publishToCatalog === true) { + let finalPublishToCatalog = false; + try { + finalPublishToCatalog = await shouldPublishToCatalog({ ...config, projectPath }, context); + } catch (error) { + logger.error("Could not access \n%O", error); + errors.push(getError("EGLABNOTINSTALLED")); + } + + if (finalPublishToCatalog) { try { logger.log("Verifying that the GitLab CLI is installed"); await glab(["version"], { diff --git a/test/publish.test.js b/test/publish.test.js index 97ef06fb..f6bef151 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -24,7 +24,7 @@ test.serial("Publish a release", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITLAB_TOKEN: "gitlab_token" }; - const pluginConfig = {}; + const pluginConfig = { publishToCatalog: false }; const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" }; const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`); @@ -81,7 +81,10 @@ test.serial("Publish a release with templated path", async (t) => { .post(`/projects/${encodedProjectPath}/uploads`, /Content-Disposition/g) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", `https://gitlab.com${uploaded.full_path}`]); @@ -122,7 +125,10 @@ test.serial("Publish a release with assets", async (t) => { .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", `https://gitlab.com${uploaded.full_path}`]); @@ -168,7 +174,10 @@ test.serial("Publish a release with generics", async (t) => { ) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s (%s)", expectedUrl, uploaded.file.url]); @@ -214,7 +223,10 @@ test.serial("Publish a release with generics and external storage provider (http ) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s (%s)", expectedUrl, uploaded.file.url]); @@ -260,7 +272,10 @@ test.serial("Publish a release with generics and external storage provider (http ) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s (%s)", expectedUrl, uploaded.file.url]); @@ -306,7 +321,10 @@ test.serial("Publish a release with generics and external storage provider (ftp) ) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s (%s)", expectedUrl, uploaded.file.url]); @@ -358,7 +376,10 @@ test.serial("Publish a release with asset type and permalink", async (t) => { .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", `https://gitlab.com${uploaded.full_path}`]); @@ -409,7 +430,10 @@ test.serial("Publish a release with an asset with a template label", async (t) = .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", `https://gitlab.com${uploaded.full_path}`]); @@ -472,7 +496,10 @@ test.serial("Publish a release (with an link) with variables", async (t) => { const gitlabUpload = authenticate(env) .post(`/projects/${encodedProjectPath}/uploads`, /filename="file.css"/gm) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", `https://gitlab.com${uploaded.full_path}`]); @@ -488,7 +515,7 @@ test.serial("Publish a release with a milestone", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITLAB_TOKEN: "gitlab_token" }; - const pluginConfig = { milestones: ["1.2.3"] }; + const pluginConfig = { publishToCatalog: false, 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 encodedProjectPath = encodeURIComponent(`${owner}/${repo}`); @@ -531,7 +558,10 @@ test.serial("Publish a release with array of missing assets", async (t) => { }, }) .reply(200); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Published GitLab release: %s", nextRelease.gitTag]); @@ -571,7 +601,10 @@ test.serial("Publish a release with one asset and custom label", async (t) => { .post(`/projects/${encodedProjectPath}/uploads`, /filename="upload.txt"/gm) .reply(200, uploaded); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Uploaded file: %s", `https://gitlab.com${uploaded.full_path}`]); @@ -584,7 +617,7 @@ test.serial("Publish a release with missing release notes", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITLAB_TOKEN: "gitlab_token" }; - const pluginConfig = {}; + const pluginConfig = { publishToCatalog: false }; const nextRelease = { gitHead: "123", gitTag: "v1.0.0" }; const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`); @@ -637,7 +670,10 @@ test.serial("Publish a release with an asset link", async (t) => { }) .reply(200); - const result = await publish({ assets }, { env, cwd, options, nextRelease, logger: t.context.logger }); + const result = await publish( + { assets, publishToCatalog: false }, + { env, cwd, options, nextRelease, logger: t.context.logger } + ); t.is(result.url, `https://gitlab.com/${owner}/${repo}/-/releases/${encodedGitTag}`); t.deepEqual(t.context.log.args[0], ["Published GitLab release: %s", nextRelease.gitTag]); @@ -648,7 +684,7 @@ test.serial("Publish a release with error response", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITLAB_TOKEN: "gitlab_token" }; - const pluginConfig = {}; + const pluginConfig = { publishToCatalog: false }; const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" }; const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; const encodedProjectPath = encodeURIComponent(`${owner}/${repo}`); @@ -668,7 +704,7 @@ test.serial("Publish a release with error response", async (t) => { t.true(gitlab.isDone()); }); -test.serial("Publish a release to CI catalog", async (t) => { +test.serial("Publish a release to CI catalog (user-configured)", async (t) => { const owner = "test_user"; const repo = "test_repo"; const env = { GITLAB_TOKEN: "gitlab_token" }; @@ -700,6 +736,47 @@ test.serial("Publish a release to CI catalog", async (t) => { t.true(gitlab.isDone()); }); +test.serial("Publish a release to CI catalog (auto-detected)", async (t) => { + const owner = "test_user"; + const repo = "test_repo"; + const env = { GITLAB_TOKEN: "gitlab_token" }; + const pluginConfig = { publishToCatalog: undefined }; + const nextRelease = { gitHead: "123", gitTag: "v1.0.0", notes: "Test release note body" }; + const options = { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }; + const projectPath = `${owner}/${repo}`; + const encodedProjectPath = encodeURIComponent(projectPath); + const gitlab = authenticate(env) + .post(`/projects/${encodedProjectPath}/releases`, { + tag_name: nextRelease.gitTag, + description: nextRelease.notes, + assets: { + links: [], + }, + }) + .reply(200); + + const gitlabGraphQl = nock("https://gitlab.com", { reqheaders: { "Private-Token": "gitlab_token" } }) + .post("/graphql", { + query: + '\n query {\n project(fullPath: "test_user/test_repo") {\n isCatalogResource\n }\n }\n ', + }) + .reply(200, { data: { project: { isCatalogResource: true } } }); + + const execa = (await td.replaceEsm("execa")).execa; + td.when( + execa("glab", ["repo", "publish", "catalog", nextRelease.gitTag], { + cwd: undefined, + timeout: 30000, + env: { GITLAB_TOKEN: env.GITLAB_TOKEN }, + }) + ).thenResolve(); + const publishWithMockExeca = (await import("../lib/publish.js")).default; + await publishWithMockExeca(pluginConfig, { env, options, nextRelease, logger: t.context.logger }); + t.true(gitlab.isDone()); + t.true(gitlabGraphQl.isDone()); + t.deepEqual(t.context.log.args[1], ["Published tag %s to the CI catalog", nextRelease.gitTag]); +}); + test.serial("Publish a release to CI catalog with error", async (t) => { const owner = "test_user"; const repo = "test_repo"; diff --git a/test/resolve-config.test.js b/test/resolve-config.test.js index 97e892b9..57232340 100644 --- a/test/resolve-config.test.js +++ b/test/resolve-config.test.js @@ -7,6 +7,7 @@ const defaultOptions = { gitlabToken: undefined, gitlabUrl: "https://gitlab.com", gitlabApiUrl: urlJoin("https://gitlab.com", "/api/v4"), + gitlabGraphQlApiUrl: urlJoin("https://gitlab.com", "/graphql"), assets: undefined, milestones: undefined, successComment: undefined, @@ -42,6 +43,7 @@ test("Returns user config", (t) => { gitlabToken, gitlabUrl, gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), assets, labels: false, retryLimit, @@ -55,6 +57,7 @@ test("Returns user config", (t) => { gitlabToken, gitlabUrl, gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), assets, proxy, } @@ -78,6 +81,7 @@ test("Returns user config via environment variables", (t) => { gitlabToken, gitlabUrl, gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), assets, milestones, } @@ -97,6 +101,7 @@ test("Returns user config via alternative environment variables", (t) => { gitlabToken, gitlabUrl, gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), assets, milestones: undefined, successComment: undefined, @@ -224,6 +229,7 @@ test("Returns user config via alternative environment variables with mismatching gitlabToken: "TOKEN", gitlabUrl: "http://host.com", gitlabApiUrl: "http://host.com/api/prefix", + gitlabGraphQlApiUrl: "http://host.com/graphql", assets: ["file.js"], } ); @@ -246,6 +252,7 @@ test("Returns user config via alternative environment variables with mismatching gitlabToken: "TOKEN", gitlabUrl: "https://host.com", gitlabApiUrl: "https://host.com/api/prefix", + gitlabGraphQlApiUrl: "https://host.com/graphql", assets: ["file.js"], } ); @@ -366,6 +373,7 @@ test("Returns default config", (t) => { gitlabToken, gitlabUrl: "https://gitlab.com", gitlabApiUrl: urlJoin(gitlabUrl, "/api/v4"), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), }); }); @@ -374,13 +382,14 @@ test("Returns default config via GitLab CI/CD environment variables", (t) => { const CI_PROJECT_URL = "http://ci-host.com/ci-owner/ci-repo"; const CI_PROJECT_PATH = "ci-owner/ci-repo"; const CI_API_V4_URL = "http://ci-host-api.com/prefix"; + const CI_API_GRAPHQL_URL = "http://ci-host-api.com/graphql"; t.deepEqual( resolveConfig( {}, { envCi: { service: "gitlab" }, - env: { GL_TOKEN: gitlabToken, CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL }, + env: { GL_TOKEN: gitlabToken, CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL, CI_API_GRAPHQL_URL }, } ), { @@ -388,6 +397,7 @@ test("Returns default config via GitLab CI/CD environment variables", (t) => { gitlabToken, gitlabUrl: "http://ci-host.com", gitlabApiUrl: CI_API_V4_URL, + gitlabGraphQlApiUrl: CI_API_GRAPHQL_URL, } ); }); @@ -402,13 +412,14 @@ test("Returns user config over GitLab CI/CD environment variables", (t) => { const CI_PROJECT_URL = "http://ci-host.com/ci-owner/ci-repo"; const CI_PROJECT_PATH = "ci-owner/ci-repo"; const CI_API_V4_URL = "http://ci-host-api.com/prefix"; + const CI_API_GRAPHQL_URL = "http://ci-host-api.com/graphql"; t.deepEqual( resolveConfig( { gitlabUrl, gitlabApiPathPrefix, assets, failTitle, labels }, { envCi: { service: "gitlab" }, - env: { GL_TOKEN: gitlabToken, CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL }, + env: { GL_TOKEN: gitlabToken, CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL, CI_API_GRAPHQL_URL }, } ), { @@ -416,6 +427,7 @@ test("Returns user config over GitLab CI/CD environment variables", (t) => { gitlabToken, gitlabUrl, gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), assets, failTitle: "The automated release unfortunately failed!", labels: "bot,release-failed", @@ -430,6 +442,7 @@ test("Returns user config via environment variables over GitLab CI/CD environmen const CI_PROJECT_URL = "http://ci-host.com/ci-owner/ci-repo"; const CI_PROJECT_PATH = "ci-owner/ci-repo"; const CI_API_V4_URL = "http://ci-host-api.com/prefix"; + const CI_API_GRAPHQL_URL = "http://ci-host-api.com/graphql"; t.deepEqual( resolveConfig( @@ -443,6 +456,7 @@ test("Returns user config via environment variables over GitLab CI/CD environmen CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL, + CI_API_GRAPHQL_URL, }, } ), @@ -451,6 +465,7 @@ test("Returns user config via environment variables over GitLab CI/CD environmen gitlabToken, gitlabUrl, gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), } ); }); @@ -462,6 +477,7 @@ test("Returns user config via alternative environment variables over GitLab CI/C const CI_PROJECT_URL = "http://ci-host.com/ci-owner/ci-repo"; const CI_PROJECT_PATH = "ci-owner/ci-repo"; const CI_API_V4_URL = "http://ci-host-api.com/prefix"; + const CI_API_GRAPHQL_URL = "http://ci-host-api.com/graphql"; t.deepEqual( resolveConfig( @@ -475,6 +491,7 @@ test("Returns user config via alternative environment variables over GitLab CI/C CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL, + CI_API_GRAPHQL_URL, }, } ), @@ -483,6 +500,7 @@ test("Returns user config via alternative environment variables over GitLab CI/C gitlabToken, gitlabUrl, gitlabApiUrl: urlJoin(gitlabUrl, gitlabApiPathPrefix), + gitlabGraphQlApiUrl: urlJoin(gitlabUrl, "/graphql"), } ); }); @@ -509,3 +527,67 @@ test("Ignore GitLab CI/CD environment variables if not running on GitLab CI/CD", } ); }); + +// test("Considers configured GitLab URL when determining GraphQL API URL", (t) => { +// const gitlabToken = "TOKEN"; +// const CI_PROJECT_URL = "http://ci-host.com/owner/repo"; +// const CI_PROJECT_PATH = "ci-owner/ci-repo"; +// const CI_API_V4_URL = "http://ci-host-api.com/prefix"; + +// t.deepEqual( +// resolveConfig( +// {}, +// { +// env: { GL_TOKEN: gitlabToken, CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL }, +// } +// ), +// { +// ...defaultOptions, +// gitlabToken, +// gitlabUrl: "https://gitlab.com", +// gitlabApiUrl: urlJoin("https://gitlab.com", "/api/v4"), +// } +// ); +// }); + +// test("Considers configured GraphQL API prefix when determining GraphQL API URL", (t) => { +// const gitlabToken = "TOKEN"; + +// t.deepEqual( +// resolveConfig( +// {}, +// { +// env: { GL_TOKEN: gitlabToken, CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL }, +// } +// ), +// { +// ...defaultOptions, +// gitlabToken, +// gitlabUrl: "https://gitlab.com", +// gitlabApiUrl: urlJoin("https://gitlab.com", "/api/v4"), +// } +// ); +// }); + +// test("Detects GraphQL API URL from GitLab CI/CD environment variables", (t) => { +// const gitlabToken = "TOKEN"; +// const CI_PROJECT_URL = "http://ci-host.com/owner/repo"; +// const CI_PROJECT_PATH = "ci-owner/ci-repo"; +// const CI_API_V4_URL = "http://ci-host-api.com/prefix"; + +// t.deepEqual( +// resolveConfig( +// {}, +// { +// envCi: { service: "travis" }, +// env: { GL_TOKEN: gitlabToken, CI_PROJECT_URL, CI_PROJECT_PATH, CI_API_V4_URL }, +// } +// ), +// { +// ...defaultOptions, +// gitlabToken, +// gitlabUrl: "https://gitlab.com", +// gitlabApiUrl: urlJoin("https://gitlab.com", "/api/v4"), +// } +// ); +// }); diff --git a/test/verify.test.js b/test/verify.test.js index 5fdb6f27..d78d13b4 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -29,7 +29,7 @@ test.serial("Verify token and repository access (project_access 30)", async (t) await t.notThrowsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: `git+https://gitalb.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -46,7 +46,7 @@ test.serial("Verify token and repository access (project_access 40)", async (t) await t.notThrowsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: `git+https://gitalb.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -63,7 +63,7 @@ test.serial("Verify token and repository access (group_access 30)", async (t) => await t.notThrowsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: `git+https://gitalb.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -80,7 +80,7 @@ test.serial("Verify token and repository access (group_access 40)", async (t) => await t.notThrowsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: `git+https://gitalb.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -93,13 +93,13 @@ test.serial("Verify token and repository access and custom URL with prefix", asy const env = { GL_TOKEN: "gitlab_token" }; const gitlabUrl = "https://othertesturl.com:9090"; const gitlabApiPathPrefix = "prefix"; - const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix }) + const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }) .get(`/projects/${owner}%2F${repo}`) .reply(200, { permissions: { project_access: { access_level: 40 } } }); await t.notThrowsAsync( verify( - { gitlabUrl, gitlabApiPathPrefix }, + { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -119,7 +119,7 @@ test.serial("Verify token and repository access and custom URL without prefix", await t.notThrowsAsync( verify( - { gitlabUrl }, + { gitlabUrl, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -133,13 +133,13 @@ test.serial("Verify token and repository access with subgroup git URL", async (t const env = { GL_TOKEN: "gitlab_token" }; const gitlabUrl = "https://customurl.com:9090/context"; const gitlabApiPathPrefix = "prefix"; - const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix }) + const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }) .get(`/projects/${encodeURIComponent(repoUri)}`) .reply(200, { permissions: { project_access: { access_level: 40 } } }); await t.notThrowsAsync( verify( - { gitlabUrl, gitlabApiPathPrefix }, + { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }, { env, options: { repositoryUrl: `git@customurl.com:${repoUri}.git` }, logger: t.context.logger } ) ); @@ -156,13 +156,13 @@ test.serial("Verify token and repository access with subgroup http URL", async ( const env = { GL_TOKEN: "gitlab_token" }; const gitlabUrl = "https://customurl.com:9090/context"; const gitlabApiPathPrefix = "prefix"; - const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix }) + const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }) .get(`/projects/${encodeURIComponent(repoUri)}`) .reply(200, { permissions: { project_access: { access_level: 40 } } }); await t.notThrowsAsync( verify( - { gitlabUrl, gitlabApiPathPrefix }, + { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }, { env, options: { repositoryUrl: `http://customurl.com/${repoUri}.git` }, logger: t.context.logger } ) ); @@ -180,13 +180,13 @@ test.serial("Verify token and repository access with empty gitlabApiPathPrefix", const env = { GL_TOKEN: "gitlab_token" }; const gitlabUrl = "https://othertesturl.com:9090"; const gitlabApiPathPrefix = ""; - const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix }) + const gitlab = authenticate(env, { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }) .get(`/projects/${owner}%2F${repo}`) .reply(200, { permissions: { project_access: { access_level: 40 } } }); await t.notThrowsAsync( verify( - { gitlabUrl, gitlabApiPathPrefix }, + { gitlabUrl, gitlabApiPathPrefix, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -205,7 +205,7 @@ test.serial("Verify token and repository with environment variables", async (t) await t.notThrowsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -224,7 +224,7 @@ test.serial("Verify token and repository access with alternative environment var await t.notThrowsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -242,7 +242,7 @@ test.serial('Verify "assets" is a String', async (t) => { await t.notThrowsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -261,7 +261,7 @@ test.serial('Verify "assets" is an Object with a path property', async (t) => { await t.notThrowsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -280,7 +280,7 @@ test.serial('Verify "assets" is an Array of Object with a path property', async await t.notThrowsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -299,7 +299,7 @@ test.serial('Verify "assets" is an Array of glob Arrays', async (t) => { await t.notThrowsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -318,7 +318,7 @@ test.serial('Verify "assets" is an Array of Object with a glob Arrays in path pr await t.notThrowsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `git@othertesturl.com:${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -339,7 +339,7 @@ test.serial('Throw SemanticReleaseError if "assets" option is not a String or an errors: [error, ...errors], } = await t.throwsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -363,7 +363,7 @@ test.serial('Throw SemanticReleaseError if "assets" option is an Array with inva errors: [error, ...errors], } = await t.throwsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -387,7 +387,7 @@ test.serial('Throw SemanticReleaseError if "assets" option is an Object missing errors: [error, ...errors], } = await t.throwsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -413,7 +413,7 @@ test.serial( errors: [error, ...errors], } = await t.throwsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -431,7 +431,7 @@ test("Throw SemanticReleaseError for missing GitLab token", async (t) => { errors: [error, ...errors], } = await t.throwsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: "https://gitlab.com/semantic-release/gitlab.git" }, logger: t.context.logger } ) ); @@ -450,7 +450,10 @@ test.serial("Throw SemanticReleaseError for invalid token", async (t) => { const { errors: [error, ...errors], } = await t.throwsAsync( - verify({}, { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger }) + verify( + { publishToCatalog: false }, + { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger } + ) ); t.is(errors.length, 0); @@ -467,7 +470,7 @@ test.serial("Throw SemanticReleaseError for invalid repositoryUrl", async (t) => errors: [error, ...errors], } = await t.throwsAsync( verify( - { gitlabUrl }, + { gitlabUrl, publishToCatalog: false }, { env, options: { repositoryUrl: "git+ssh://git@gitlab.com/context.git" }, logger: t.context.logger } ) ); @@ -485,7 +488,7 @@ test.serial("Throw AggregateError if multiple verification fails", async (t) => errors: [invalidAssetsError, invalidUrlError, noTokenError, ...errors], } = await t.throwsAsync( verify( - { assets, gitlabUrl }, + { assets, gitlabUrl, publishToCatalog: false }, { env, options: { repositoryUrl: "git+ssh://git@gitlab.com/context.git" }, logger: t.context.logger } ) ); @@ -510,7 +513,10 @@ test.serial("Throw SemanticReleaseError if token doesn't have the push permissio const { errors: [error, ...errors], } = await t.throwsAsync( - verify({}, { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger }) + verify( + { publishToCatalog: false }, + { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger } + ) ); t.is(errors.length, 0); @@ -531,7 +537,7 @@ test.serial("Throw SemanticReleaseError if token doesn't have the pull permissio errors: [error, ...errors], } = await t.throwsAsync( verify( - {}, + { publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git`, dryRun: true }, @@ -555,7 +561,10 @@ test.serial("Throw SemanticReleaseError if the repository doesn't exist", async const { errors: [error, ...errors], } = await t.throwsAsync( - verify({}, { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger }) + verify( + { publishToCatalog: false }, + { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger } + ) ); t.is(errors.length, 0); @@ -571,7 +580,10 @@ test.serial("Throw error if GitLab API return any other errors", async (t) => { const gitlab = authenticate(env).get(`/projects/${owner}%2F${repo}`).times(3).reply(500); const error = await t.throwsAsync( - verify({}, { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger }) + verify( + { publishToCatalog: false }, + { env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger } + ) ); t.is(error.response.statusCode, 500); @@ -591,7 +603,7 @@ test.serial('Throw SemanticReleaseError if "failTitle" option is not a String', errors: [error, ...errors], } = await t.throwsAsync( verify( - { failTitle }, + { failTitle, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -615,7 +627,7 @@ test.serial('Throw SemanticReleaseError if "failTitle" option is an empty String errors: [error, ...errors], } = await t.throwsAsync( verify( - { failTitle }, + { failTitle, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -639,7 +651,7 @@ test.serial('Throw SemanticReleaseError if "failTitle" option is a whitespace St errors: [error, ...errors], } = await t.throwsAsync( verify( - { failTitle }, + { failTitle, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -663,7 +675,7 @@ test.serial('Throw SemanticReleaseError if "failComment" option is not a String' errors: [error, ...errors], } = await t.throwsAsync( verify( - { failComment }, + { failComment, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -687,7 +699,7 @@ test.serial('Throw SemanticReleaseError if "failComment" option is an empty Stri errors: [error, ...errors], } = await t.throwsAsync( verify( - { failComment }, + { failComment, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -711,7 +723,7 @@ test.serial('Throw SemanticReleaseError if "failComment" option is a whitespace errors: [error, ...errors], } = await t.throwsAsync( verify( - { failComment }, + { failComment, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -733,7 +745,7 @@ test.serial('Does not throw SemanticReleaseError if "labels" option is a valid S await t.notThrowsAsync( verify( - { labels }, + { labels, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -752,7 +764,7 @@ test.serial('Does not throw SemanticReleaseError if "labels" option is "false"', await t.notThrowsAsync( verify( - { labels }, + { labels, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -773,7 +785,7 @@ test.serial('Throw SemanticReleaseError if "labels" option is not a String', asy errors: [error, ...errors], } = await t.throwsAsync( verify( - { labels }, + { labels, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -797,7 +809,7 @@ test.serial('Throw SemanticReleaseError if "labels" option is an empty String', errors: [error, ...errors], } = await t.throwsAsync( verify( - { labels }, + { labels, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -821,7 +833,7 @@ test.serial('Throw SemanticReleaseError if "labels" option is a whitespace Strin errors: [error, ...errors], } = await t.throwsAsync( verify( - { labels }, + { labels, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -845,7 +857,7 @@ test.serial('Throw SemanticReleaseError if "assignee" option is not a String', a errors: [error, ...errors], } = await t.throwsAsync( verify( - { assignee }, + { assignee, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -869,7 +881,7 @@ test.serial('Throw SemanticReleaseError if "assignee" option is an empty String' errors: [error, ...errors], } = await t.throwsAsync( verify( - { assignee }, + { assignee, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -893,7 +905,7 @@ test.serial('Throw SemanticReleaseError if "assignee" option is a whitespace Str errors: [error, ...errors], } = await t.throwsAsync( verify( - { assignee }, + { assignee, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -916,6 +928,7 @@ test.serial("Does not throw an error for option without validator", async (t) => verify( { someOption: 42, + publishToCatalog: false, }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) @@ -936,7 +949,7 @@ test.serial( await t.notThrowsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -957,7 +970,7 @@ test.serial( await t.notThrowsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -980,7 +993,7 @@ test.serial( errors: [error], } = await t.throwsAsync( verify( - { assets }, + { assets, publishToCatalog: false }, { env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger } ) ); @@ -1020,3 +1033,32 @@ test.serial( t.true(gitlab.isDone()); } ); + +test.serial("Throw SemanticReleaseError if GraphQL API request fails.", 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 gitlabGraphQl = nock("https://gitlab.com", { reqheaders: { "Private-Token": "gitlab_token" } }) + .post("/graphql") + .reply(400); + + const { + errors: [error], + } = await t.throwsAsync( + verify( + { publishToCatalog: undefined }, + { + 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()); + t.true(gitlabGraphQl.isDone()); +});