UNPKG

@golemio/cli

Version:

Collection of executables intended for use with Golemio services and modules

483 lines 21.2 kB
"use strict"; 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_tabulator_ids_constant_1 = __importDefault(require("../constants/release-tabulator-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_TABULATOR"] = "web-tabulator"; 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 await (0, execa_1.default)("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"], { cwd: dir }); // 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_TABULATOR: ids = release_tabulator_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", "--save-exact", "--ignore-scripts", "--package-lock-only", "--audit=false", "--progress=false", "--fund=false", "--update-notifier=false", ].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