@semantic-release/gitlab
Version:
semantic-release plugin to publish a GitLab release
206 lines (174 loc) • 7.26 kB
JavaScript
import { readFileSync } from "fs";
import pathlib from "path";
import fs from "fs-extra";
import { isPlainObject, template } from "lodash-es";
import { FormData } from "formdata-node";
import { fileFromPath } from "formdata-node/file-from-path";
import urlJoin from "url-join";
import got from "got";
import _debug from "debug";
const debug = _debug("semantic-release:gitlab");
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";
const isUrlScheme = (value) => /^(https|http|ftp):\/\//.test(value);
export default async (pluginConfig, context) => {
const {
cwd,
options: { repositoryUrl },
nextRelease: { gitTag, gitHead, notes, version },
logger,
} = context;
const { gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones, proxy, retryLimit } = resolveConfig(
pluginConfig,
context
);
const assetsList = [];
const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);
const encodedGitTag = encodeURIComponent(gitTag);
const encodedVersion = encodeURIComponent(version);
const apiOptions = {
headers: {
"PRIVATE-TOKEN": gitlabToken,
},
hooks: {
beforeError: [
(error) => {
const { response } = error;
if (response?.body && response.headers["content-type"] === "application/json") {
const parsedBody = JSON.parse(response.body);
if (parsedBody.message) {
error.message = `Response code ${response.statusCode} (${parsedBody.message})`;
}
}
return error;
},
],
},
retry: { limit: retryLimit },
};
debug("projectPath: %o", projectPath);
debug("release name: %o", gitTag);
debug("release ref: %o", gitHead);
debug("milestones: %o", milestones);
if (assets && assets.length > 0) {
// Skip glob if url is provided
const urlAssets = assets.filter((asset) => asset.url);
debug("url assets: %o", urlAssets);
const globbedAssets = await getAssets(
context,
assets.filter((asset) => !asset.url)
);
debug("globbed assets: %o", globbedAssets);
const allAssets = [...urlAssets, ...globbedAssets];
debug("all assets: %o", allAssets);
await Promise.all(
allAssets.map(async (asset) => {
const path = template((isPlainObject(asset) ? asset : { path: asset }).path)(context);
const _url = asset.url ? template(asset.url)(context) : undefined;
const label = asset.label ? template(asset.label)(context) : undefined;
const type = asset.type ? template(asset.type)(context) : undefined;
const filepath = asset.filepath ? template(asset.filepath)(context) : undefined;
const target = asset.target ? template(asset.target)(context) : undefined;
const status = asset.status ? template(asset.status)(context) : undefined;
if (_url) {
assetsList.push({ label, rawUrl: _url, type, filepath });
debug("use link from release setting: %s", _url);
} else {
const file = pathlib.resolve(cwd, path);
let fileStat;
try {
fileStat = await fs.stat(file);
} catch {
logger.error("The asset %s cannot be read, and will be ignored.", path);
return;
}
if (!fileStat || !fileStat.isFile()) {
logger.error("The asset %s is not a file, and will be ignored.", path);
return;
}
debug("file path: %o", path);
debug("file label: %o", label);
debug("file type: %o", type);
debug("file filepath: %o", filepath);
debug("file target: %o", target);
debug("file status: %o", status);
let uploadEndpoint;
let response;
if (target === "generic_package") {
// Upload generic packages
const encodedLabel = encodeURIComponent(label);
// https://docs.gitlab.com/ee/user/packages/generic_packages/#publish-a-package-file
uploadEndpoint = urlJoin(
projectApiUrl,
`packages/generic/release/${encodedVersion}/${encodedLabel}?${
status ? `status=${status}&` : ""
}select=package_file`
);
debug("PUT-ing the file %s to %s", file, uploadEndpoint);
try {
response = await got.put(uploadEndpoint, { ...apiOptions, ...proxy, body: readFileSync(file) }).json();
} catch (error) {
logger.error("An error occurred while uploading %s to the GitLab generics package API:\n%O", file, error);
throw error;
}
// https://docs.gitlab.com/ee/user/packages/generic_packages/#download-package-file
const url = urlJoin(projectApiUrl, `packages/generic/release/${encodedVersion}/${encodedLabel}`);
assetsList.push({ label, alt: "release", url, type: "package", filepath });
logger.log("Uploaded file: %s (%s)", url, response.file.url);
} else {
// Handle normal assets
uploadEndpoint = urlJoin(projectApiUrl, "uploads");
debug("POST-ing the file %s to %s", file, uploadEndpoint);
try {
const form = new FormData();
form.append("file", await fileFromPath(file));
response = await got.post(uploadEndpoint, { ...apiOptions, ...proxy, body: form }).json();
} catch (error) {
logger.error("An error occurred while uploading %s to the GitLab project uploads API:\n%O", file, error);
throw error;
}
const { alt, full_path } = response;
const url = urlJoin(gitlabUrl, full_path);
assetsList.push({ label, alt, url, type, filepath });
logger.log("Uploaded file: %s", url);
}
}
})
);
}
debug("Create a release for git tag %o with commit %o", gitTag, gitHead);
const createReleaseEndpoint = urlJoin(projectApiUrl, "releases");
const json = {
/* eslint-disable camelcase */
tag_name: gitTag,
description: notes && notes.trim() ? notes : gitTag,
milestones,
assets: {
links: assetsList.map(({ label, alt, url, type, filepath, rawUrl }) => {
return {
name: label || alt,
url: rawUrl || (isUrlScheme(url) ? url : urlJoin(gitlabUrl, projectPath, url)),
link_type: type,
filepath,
};
}),
},
/* eslint-enable camelcase */
};
debug("POST-ing the following JSON to %s:\n%s", createReleaseEndpoint, JSON.stringify(json, null, 2));
try {
await got.post(createReleaseEndpoint, {
...apiOptions,
...proxy,
json,
});
} catch (error) {
logger.error("An error occurred while making a request to the GitLab release API:\n%O", error);
throw error;
}
logger.log("Published GitLab release: %s", gitTag);
const releaseUrl = urlJoin(gitlabUrl, projectPath, `/-/releases/${encodedGitTag}`);
return { name: RELEASE_NAME, url: releaseUrl };
};