@golemio/cli
Version:
Collection of executables intended for use with Golemio services and modules
470 lines • 20.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.upgradeGolemioDependencies = exports.printCommandHelp = exports.filterIds = exports.printReleaseCandidateRepos = exports.printReleaseCandidateIssues = exports.createTagAndRelease = exports.syncBranches = exports.checkMergedPipeline = exports.checkOpenMRPipeline = exports.createMR = exports.commitChanges = exports.bumpVersionAndChangelog = exports.getVersions = exports.check = exports.checkModulesIntegrity = exports.removeRepoDir = exports.cloneRepo = exports.getGitlabRepo = exports.ReleaseScopeEnum = exports.VersionTagEnum = void 0;
const node_1 = require("@gitbeaker/node");
const changelog_parser_1 = __importDefault(require("changelog-parser"));
const execa_1 = __importDefault(require("execa"));
const fs_1 = __importDefault(require("fs"));
const gluegun_1 = require("gluegun");
const open_1 = __importDefault(require("open"));
const path_1 = __importDefault(require("path"));
const simple_git_1 = require("simple-git");
const release_core_ids_constant_1 = __importDefault(require("../constants/release-core-ids.constant"));
const release_module_ids_constant_1 = __importDefault(require("../constants/release-module-ids.constant"));
const release_service_ids_constant_1 = __importDefault(require("../constants/release-service-ids.constant"));
const release_datapraha_ids_constant_1 = __importDefault(require("../constants/release-datapraha-ids.constant"));
const release_golemiobi_ids_constant_1 = __importDefault(require("../constants/release-golemiobi-ids.constant"));
const release_golemiocz_ids_constant_1 = __importDefault(require("../constants/release-golemiocz-ids.constant"));
const release_lkod_ids_constant_1 = __importDefault(require("../constants/release-lkod-ids.constant"));
const release_vymi_ids_constant_1 = __importDefault(require("../constants/release-vymi-ids.constant"));
const TMP_DIR = path_1.default.resolve(process.cwd(), "tmp");
var VersionTagEnum;
(function (VersionTagEnum) {
VersionTagEnum["LATEST"] = "latest";
VersionTagEnum["DEV"] = "dev";
VersionTagEnum["RC"] = "rc";
})(VersionTagEnum || (exports.VersionTagEnum = VersionTagEnum = {}));
var ReleaseScopeEnum;
(function (ReleaseScopeEnum) {
ReleaseScopeEnum["CORE"] = "core";
ReleaseScopeEnum["MODULES"] = "modules";
ReleaseScopeEnum["SERVICES"] = "services";
ReleaseScopeEnum["WEB_DATAPRAHA"] = "web-datapraha";
ReleaseScopeEnum["WEB_GOLEMIOBI"] = "web-golemiobi";
ReleaseScopeEnum["WEB_GOLEMIOCZ"] = "web-golemiocz";
ReleaseScopeEnum["WEB_LKOD"] = "web-lkod";
ReleaseScopeEnum["WEB_VYMI"] = "web-vymi";
})(ReleaseScopeEnum || (exports.ReleaseScopeEnum = ReleaseScopeEnum = {}));
/**
* Get data about repo from GitLab
*/
const getGitlabRepo = async (repoId) => {
const gitlab = new node_1.Gitlab({ token: process.env.GITLAB_API_TOKEN });
const repo = await gitlab.Projects.show(repoId);
return repo;
};
exports.getGitlabRepo = getGitlabRepo;
/**
* Clone repository
*/
const cloneRepo = async (repo) => {
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
const id = repo.id;
const name = repo.name;
const url = repo.web_url.replace("https://", `https://gitlab-ci-token:${process.env.GITLAB_API_TOKEN}@`);
const dir = fs_1.default.mkdtempSync(path_1.default.join(TMP_DIR, `repo-${name}-`));
const git = (0, simple_git_1.simpleGit)(dir);
// clone repo and pull target branch (master)
await git.clone(url, ".", { "--branch": branchName });
return {
id,
name,
url,
dir,
git,
};
};
exports.cloneRepo = cloneRepo;
/**
* Remove tmp directory with repository code
*/
const removeRepoDir = (dir) => {
fs_1.default.rmSync(dir, { recursive: true, force: true });
};
exports.removeRepoDir = removeRepoDir;
/**
* Check GitLab modules group
* and returns repos which are missing in constants
*
* group id: 10431325
* group url: https://gitlab.com/operator-ict/golemio/code/modules
*/
const checkModulesIntegrity = async () => {
const gitlab = new node_1.Gitlab({ token: process.env.GITLAB_API_TOKEN });
const allModulesInGroup = (await gitlab.Groups.projects("10431325"))
.filter((proj) => !proj.archived)
.sort((a, b) => a.name.localeCompare(b.name));
const missing = [];
for (const m of allModulesInGroup) {
if (!release_core_ids_constant_1.default.includes(`${m.id}`) && !release_module_ids_constant_1.default.includes(`${m.id}`)) {
missing.push({ id: `${m.id}`, name: m.name });
}
}
return missing;
};
exports.checkModulesIntegrity = checkModulesIntegrity;
/**
* Check repository if has any changes
*/
const check = async (repo) => {
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
const targetBranchName = process.env.TARGET_BRANCH_NAME || "master";
let result;
try {
// checkout to release branch and diff with master
await repo.git.checkout(`origin/${branchName}`);
await repo.git.pull("origin", branchName);
const diff = await repo.git.diffSummary(`origin/${targetBranchName}`);
// some changes are ready to release
if (diff.changed > 0 || diff.deletions > 0 || diff.insertions > 0 || diff.files.length > 0) {
const changelog = await getChangelogMessages(repo.dir);
const unreleasedMessage = typeof changelog === "string" ? changelog : changelog.unreleased;
result = {
id: repo.id,
name: repo.name,
webUrl: repo.url,
someDiffs: true,
diff,
changelog: unreleasedMessage,
};
}
else {
result = {
id: repo.id,
name: repo.name,
webUrl: repo.url,
someDiffs: false,
};
}
}
catch (err) {
result = {
id: repo.id,
name: repo.name,
webUrl: repo.url,
someDiffs: false,
errorMessage: err.message,
};
}
return result;
};
exports.check = check;
/**
* Return project versions in target and current (release) branch
*/
const getVersions = async (dir, url, git) => {
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
const targetBranchName = process.env.TARGET_BRANCH_NAME || "master";
// clone repo and pull target branch (master)
await git.checkout(`origin/${targetBranchName}`);
const targetVersion = JSON.parse(fs_1.default.readFileSync(`${dir}/package.json`).toString("utf-8")).version;
// checkout to release branch and diff with master
await git.checkout(`origin/${branchName}`);
await git.pull("origin", branchName);
const currentVersion = JSON.parse(fs_1.default.readFileSync(`${dir}/package.json`).toString("utf-8")).version;
return {
target: targetVersion,
current: currentVersion,
};
};
exports.getVersions = getVersions;
/**
* Write version to package.json
*/
const bumpVersionAndChangelog = async (version, dir, git) => {
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
await git.checkout(`origin/${branchName}`);
// version bump
const packagejson = JSON.parse(fs_1.default.readFileSync(`${dir}/package.json`).toString("utf-8"));
fs_1.default.writeFileSync(`${dir}/package.json`, JSON.stringify(Object.assign(Object.assign({}, packagejson), { version }), null, 4));
// changelog bump
try {
if (!fs_1.default.existsSync(`${dir}/CHANGELOG.md`)) {
throw new Error("CHANGELOG.md was not found.");
}
const parsedChangelog = await (0, changelog_parser_1.default)(`${dir}/CHANGELOG.md`);
const unreleased = Object.assign(Object.assign(Object.assign({}, parsedChangelog.versions[0]), { version, title: `[${version}] - ${new Date().toISOString().split("T")[0]}`, date: new Date().toISOString().split("T")[0] }), (!parsedChangelog.versions[0].body ? { body: "- No changelog", parsed: { _: ["No changelog"] } } : {}));
parsedChangelog.versions.shift();
const newChangelog = Object.assign(Object.assign({}, parsedChangelog), { versions: [
{
version: null,
title: "[Unreleased]",
date: null,
body: "",
parsed: {
_: [],
},
},
unreleased,
...parsedChangelog.versions,
] });
fs_1.default.writeFileSync(`${dir}/CHANGELOG.md`, stringifyChangelog(newChangelog));
}
catch (err) {
gluegun_1.print.warning("CHANGELOG.md was not found.");
}
};
exports.bumpVersionAndChangelog = bumpVersionAndChangelog;
/**
* Commit the changes to current (release) branch
*
* Commit message is simple (chore: ...)
*/
const commitChanges = async (git) => {
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
await git.checkout(`origin/${branchName}`);
await git.pull("origin", branchName);
await git.add(".");
await git.commit("chore: bump version and update changelog");
await git.push(["origin", `HEAD:refs/heads/${branchName}`]);
};
exports.commitChanges = commitChanges;
/**
* Create merge request from current (release) to target (master) branch
* @param id Repo id
* @param name Repo name
*/
const createMR = async (id, name) => {
const gitlab = new node_1.Gitlab({ token: process.env.GITLAB_API_TOKEN });
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
const targetBranchName = process.env.TARGET_BRANCH_NAME || "master";
const description = `This MR was created by Golemio CLI release command`;
return gitlab.MergeRequests.create(id, branchName, targetBranchName, `[${name}] Golemio CLI Auto Release`, {
description,
});
};
exports.createMR = createMR;
/**
* Return pipeline status of the open merge request
* @param id Repo id
*/
const checkOpenMRPipeline = async (id) => {
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
const gitlab = new node_1.Gitlab({ token: process.env.GITLAB_API_TOKEN });
const mrs = await gitlab.MergeRequests.all({ projectId: id, state: "opened", source_branch: branchName, sort: "desc" });
if (mrs.length > 0) {
const pipelines = await gitlab.MergeRequests.pipelines(id, mrs[0].iid);
return pipelines.map((p) => (Object.assign(Object.assign({}, p), { web_url: p.web_url })));
}
};
exports.checkOpenMRPipeline = checkOpenMRPipeline;
/**
* Return status of the last five pipelines
* @param id Repo id
*/
const checkMergedPipeline = async (id) => {
const targetBranchName = process.env.TARGET_BRANCH_NAME || "master";
const gitlab = new node_1.Gitlab({ token: process.env.GITLAB_API_TOKEN });
const pipelines = await gitlab.Pipelines.all(id, { page: 1, perPage: 5 });
const onlyToTarget = pipelines.filter((p) => p.ref === targetBranchName);
if (onlyToTarget.length > 0) {
return onlyToTarget[0];
}
};
exports.checkMergedPipeline = checkMergedPipeline;
/**
* Do a "back-merge"
*
* Fast-forward git pull of the target (master) to the current (release) branch
* and git pull of the target (master) to the develoment branch. If no conflict was found than bump version and push
*/
const syncBranches = async (repo, dir, git, showChangelogBeforecommit = false) => {
const branchName = process.env.RELEASE_BRANCH_NAME || "release";
const targetBranchName = process.env.TARGET_BRANCH_NAME || "master";
// current (release) branch sync
gluegun_1.print.info(`Synchronizing ${branchName} branch with ${targetBranchName} branch`);
await git.checkout(`origin/${branchName}`);
await git.pull("origin", branchName);
await git.pull("origin", targetBranchName, ["--ff"]);
await git.push(["origin", `HEAD:refs/heads/${branchName}`]);
gluegun_1.print.success(`${branchName} branch successfully synchronized`);
// development branch sync (try)
gluegun_1.print.info(`Synchronizing development branch with ${targetBranchName} branch`);
await git.checkout(`origin/development`);
try {
await git.pull("origin", targetBranchName, ["--ff"]);
const status = await git.status();
if (status.conflicted.length > 0) {
throw new Error("Conflicts!");
}
}
catch (err) {
gluegun_1.print.warning(`Merge target branch (${targetBranchName}) to development branch contains some conflicts or fails. ` +
`Please sync development branch manually.\n${repo.web_url}`);
return;
}
gluegun_1.print.info("✔ Bump version in package.json");
await (0, execa_1.default)("npm", ["version", "patch", "--no-git-tag-version"], { cwd: dir });
gluegun_1.print.info(`✔ Upgrade @golemio dependencies to dev version`);
await (0, exports.upgradeGolemioDependencies)(dir, VersionTagEnum.DEV);
if (showChangelogBeforecommit) {
gluegun_1.print.info(`Please double check the changelog for merge inconsistencies and close the editor after.`);
await (0, open_1.default)(`${dir}/CHANGELOG.md`, { wait: true });
await gluegun_1.prompt.confirm("After closing changelog press any key to continue...");
}
await git.add(".");
await git.commit("chore: bump version");
await git.push(["origin", `HEAD:development`]);
gluegun_1.print.success(`development branch successfully synchronized`);
};
exports.syncBranches = syncBranches;
/**
* Create tag and release in repo. The last message from changelog is used as release note.
*
* @param id Repo id
*/
const createTagAndRelease = async (id, dir, url, git) => {
const targetBranchName = process.env.TARGET_BRANCH_NAME || "master";
const gitlab = new node_1.Gitlab({ token: process.env.GITLAB_API_TOKEN });
const version = (await (0, exports.getVersions)(dir, url, git)).target;
const changelog = await getChangelogMessages(dir);
const tag = await gitlab.Tags.create(id, `v${version}`, targetBranchName);
const release = await gitlab.Releases.create(id, {
tag_name: tag.name,
description: typeof changelog === "string" ? "" : changelog.lastVersion,
});
gluegun_1.print.success(`Tag and Release name ${release.name} was created`);
};
exports.createTagAndRelease = createTagAndRelease;
/**
* Print issues in Golemio group with state To Deploy
*/
const printReleaseCandidateIssues = async () => {
const gitlab = new node_1.Gitlab({ token: process.env.GITLAB_API_TOKEN });
let issues = [];
gluegun_1.print.info(`**Related issues**\n`);
for (let i = 1, imax = 5000 / 100; i <= imax; i++) {
const issuesPartial = (await gitlab.Issues.all({
groupId: 5666320,
page: i,
perPage: 100, // max 100
})).filter((issue) => issue.state === "opened" &&
issue.labels.includes("team/vyvoj") &&
issue.labels.includes("state::To Deploy"));
issues = [...issues, ...issuesPartial];
}
for (const issue of issues) {
gluegun_1.print.info(`- [${issue.title}](${issue.web_url})`);
}
gluegun_1.print.info(``);
};
exports.printReleaseCandidateIssues = printReleaseCandidateIssues;
/**
* Print table with release cantitdate repos
*/
const printReleaseCandidateRepos = async (scope, ids) => {
gluegun_1.print.info(`**${scope}**\n`);
gluegun_1.print.info(`| Name | Changelog | Published |`);
gluegun_1.print.info(`| --- | --- | --- |`);
for (const id of ids) {
const info = await (0, exports.getGitlabRepo)(id);
gluegun_1.print.info(`| [${info.name}](${info.web_url}) | [Link](${info.web_url}/-/blob/development/CHANGELOG.md) | |`);
}
gluegun_1.print.info(``);
};
exports.printReleaseCandidateRepos = printReleaseCandidateRepos;
/**
* Filter repositories by args
*/
const filterIds = (scope, { skip, only }) => {
let ids = [];
switch (scope) {
case ReleaseScopeEnum.CORE:
ids = release_core_ids_constant_1.default;
break;
case ReleaseScopeEnum.MODULES:
ids = release_module_ids_constant_1.default;
break;
case ReleaseScopeEnum.SERVICES:
ids = release_service_ids_constant_1.default;
break;
case ReleaseScopeEnum.WEB_DATAPRAHA:
ids = release_datapraha_ids_constant_1.default;
break;
case ReleaseScopeEnum.WEB_GOLEMIOBI:
ids = release_golemiobi_ids_constant_1.default;
break;
case ReleaseScopeEnum.WEB_GOLEMIOCZ:
ids = release_golemiocz_ids_constant_1.default;
break;
case ReleaseScopeEnum.WEB_LKOD:
ids = release_lkod_ids_constant_1.default;
break;
case ReleaseScopeEnum.WEB_VYMI:
ids = release_vymi_ids_constant_1.default;
break;
}
if (skip && !only) {
return ids.filter((id) => { var _a; return (Array.isArray(skip) ? !((_a = `${skip}`) === null || _a === void 0 ? void 0 : _a.includes(id)) : `${skip}` !== id); });
}
if (only) {
return ids.filter((id) => { var _a; return (Array.isArray(only) ? (_a = `${only}`) === null || _a === void 0 ? void 0 : _a.includes(id) : `${only}` === id); });
}
return ids;
};
exports.filterIds = filterIds;
/**
* Print command help for migrate-db
*/
const printCommandHelp = () => {
gluegun_1.print.info(gluegun_1.print.colors.blue("\ngolemio release (rls)"));
gluegun_1.print.info(" Commands\n");
gluegun_1.print.info(" check-modules-integrity\t Check module group if new repo was added");
gluegun_1.print.info(" print-release-issue\t\t Print issues and release candidate modules");
gluegun_1.print.info(" check\t\t\t\t Check repos that are ready to release");
gluegun_1.print.info(" merge\t\t\t\t Merge repos marked as release candidate by `check` command");
gluegun_1.print.info(" check-mr-pipelines\t\t Show pipelines of merged repos");
gluegun_1.print.info(" sync-branches\t\t\t Synchronize branches after release");
gluegun_1.print.info(" create-tags\t\t\t Create tags and releases of merged repos");
gluegun_1.print.info(" help (h)\t\t\t -\n");
gluegun_1.print.info(" Flags\n");
gluegun_1.print.info(` --scope <${Object.values(ReleaseScopeEnum).join("|")}>\t The scope of repos`);
gluegun_1.print.info(" --skip <id>\t\t\t\t Repo id to be skipped (zero or many)");
gluegun_1.print.info(" --only <id>\t\t\t\t Repo id to be prefered (zero or many)");
gluegun_1.print.info(" --interactive\t\t\t\t Script waits for some action between repos");
};
exports.printCommandHelp = printCommandHelp;
/**
* Get changelog messages
*/
const getChangelogMessages = async (dir) => {
try {
if (!fs_1.default.existsSync(`${dir}/CHANGELOG.md`)) {
return "CHANGELOG.md was not found.";
}
const changelog = await (0, changelog_parser_1.default)({ filePath: `${dir}/CHANGELOG.md` });
return { unreleased: changelog.versions[0].body, lastVersion: changelog.versions[1].body };
}
catch (err) {
return "CHANGELOG.md was not found.";
}
};
/**
* Stringify pased changelog
*/
const stringifyChangelog = (changelog) => {
let changelogString = `# ${changelog.title}\n\n${changelog.description}\n\n`;
for (const version of changelog.versions) {
changelogString += `## ${version.title}\n\n${version.body ? `${version.body}\n\n` : ""}`;
}
return changelogString;
};
/**
* Upgrade @golemio dependencies to latest version by versionTag
*/
const upgradeGolemioDependencies = async (dir, versionTag) => {
var _a;
const npmLsParams = ["ls", "-p", "--depth=0", "--package-lock-only"];
if (versionTag !== VersionTagEnum.LATEST) {
npmLsParams.push("--omit=dev");
}
const { stdout: dependencyPaths } = await (0, execa_1.default)("npm", npmLsParams, {
cwd: dir,
});
const dependencies = (_a = dependencyPaths
.match(/(@golemio(\\|\/).+)/gm)) === null || _a === void 0 ? void 0 : _a.map((dependency) => `${dependency}@${versionTag}`.replace("\\", "/"));
const { stdout, stderr } = await (0, execa_1.default)("npm", ["install", "--ignore-scripts", "--save-exact"].concat(dependencies !== null && dependencies !== void 0 ? dependencies : []), {
cwd: dir,
});
if (stderr) {
gluegun_1.print.error(stderr);
}
else {
gluegun_1.print.muted(stdout);
}
};
exports.upgradeGolemioDependencies = upgradeGolemioDependencies;
//# sourceMappingURL=release.utils.js.map