UNPKG

@copado/copado-cli

Version:

Copado Developer CLI

490 lines 24.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const simple_git_1 = __importDefault(require("simple-git")); const fs_1 = require("fs"); const path = __importStar(require("path")); const core_1 = require("@salesforce/core"); const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve"); const theme_1 = __importDefault(require("../../../service/theme")); const pathmatcher_1 = require("../../../service/pathmatcher"); const restConnections_1 = require("../../../service/restConnections"); const filesystem_1 = require("../../../copado_commons/filesystem"); const sfdxfilesystem_1 = require("../../../copado_commons/sfdxfilesystem"); const date_1 = require("../../../service/date"); const extensionConfiguration_1 = require("../../../service/extensionConfiguration"); const userStories_1 = __importDefault(require("../../../selector/userStories")); const sf_plugins_core_1 = require("@salesforce/sf-plugins-core"); const git = (0, simple_git_1.default)(); core_1.Messages.importMessagesDirectory(__dirname); const messages = core_1.Messages.loadMessages('@copado/copado-cli', 'copado_work'); const copadoMessages = core_1.Messages.loadMessages('@copado/copado-cli', 'copado'); class WorkPush extends sf_plugins_core_1.SfCommand { async run() { var _a, _b, _c; const { flags } = await this.parse(WorkPush); const ux = new sf_plugins_core_1.Ux({ jsonEnabled: this.jsonEnabled() }); this.localConfig = (await filesystem_1.CopadoFiles.getLocalConfig()); if (this.localConfig) { this.work = this.localConfig.get('work'); } if (!((_a = this.work) === null || _a === void 0 ? void 0 : _a.copadouserstoryid)) { throw new core_1.SfError(copadoMessages.getMessage('copadoUserStoryNotSet')); } const userStories = await new userStories_1.default().byId((_b = this.work) === null || _b === void 0 ? void 0 : _b.copadouserstoryid); if (!userStories.length) { throw new core_1.SfError(`Invalid User story id ${(_c = this.work) === null || _c === void 0 ? void 0 : _c.copadouserstoryid}`); } this.userStory = userStories[0]; this.gitStatus = await git.status(); this.branchCommits = await this.currentCommits(); const commits = await this.commits(); if (!commits.length) { ux.log(messages.getMessage('push.noCommits')); } else { this.hasSfdxProjectJsonFile(); this.showChangedFiles(commits, ux); await this.pushToRemoteBranch(ux); await this.pushToEnvironmentBranch(ux, flags.force); await this.pushToCopado(commits, ux); } } hasSfdxProjectJsonFile() { if (this.isSFDX() && !(0, fs_1.existsSync)('sfdx-project.json')) { throw new Error(messages.getMessage('push.projectJsonMissing')); } } async commits() { var _a, _b; const commits = []; const lastCommit = this.work.copadolastcommit; const forceignorePattern = await filesystem_1.CopadoFiles.getForceIgnoreFile(); const pendingCommits = await this.pendingCommits(); const localConfig = (await filesystem_1.CopadoFiles.getLocalConfig()).get('config'); const bypass = ((_b = (_a = localConfig === null || localConfig === void 0 ? void 0 : localConfig.bypassforceignore) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === null || _b === void 0 ? void 0 : _b.valueOf()) === 'true'; let relevantCommits = lastCommit ? pendingCommits.all : pendingCommits.all.slice(0, this.branchCommits.total); let filePaths = []; for (const commit of relevantCommits) { const files = []; const commitFiles = []; const commitFileNames = await git.diffSummary([commit.hash + '^!']); for (const cmfn of commitFileNames.files) { const excludePath = await pathmatcher_1.AntPathMatcher.excludePath(cmfn.file, forceignorePattern); if (!excludePath || bypass) { const name = cmfn.file; const exists = (0, fs_1.existsSync)(name); const state = exists ? 'Add' : 'Delete'; filePaths = filePaths.concat(this.getNormalizedPath(name)); files.push({ name, state }); commitFiles.push(name); } } commits.push({ commitId: commit.hash, commitMessage: commit.message, commitDate: date_1.DateFormatter.format(commit.date), commitAuthor: commit.author_name, commitFiles, files }); } // Note: populate virtualTreeContainer with file paths of desctructive changes this.virtualTreeContainer = source_deploy_retrieve_1.VirtualTreeContainer.fromFilePaths(filePaths); return commits; } getNormalizedPath(fileName) { return process.platform === 'win32' ? fileName.replaceAll('/', `\\`) : fileName; } async pushToRemoteBranch(ux) { const currBranch = this.gitStatus.current; const status = await git.log({ from: `origin/${currBranch}`, to: 'HEAD' }).catch(() => { return { total: -1 }; }); if (status.total != 0 && this.branchCommits.total > 0) { ux.log(`${theme_1.default.status.Info(messages.getMessage('push.pushingToFeature'))}: ${currBranch}`); await git.push('origin', currBranch); } } async pushToEnvironmentBranch(ux, force) { var _a; if (!(await this.isScratchOrg()) && !(await this.isEnvironmentDisabled())) { try { const envBranch = (_a = this.work) === null || _a === void 0 ? void 0 : _a.copadoenvbranch; ux.log(`${theme_1.default.status.Info(messages.getMessage('push.pushingToEnv'))}: ${envBranch}`); await git.checkout(envBranch); await git.mergeFromTo(this.gitStatus.current, envBranch, ['-X', 'theirs']); if (force) { await git.push('origin', envBranch, ['-f']); } else { await git.push('origin', envBranch); } await git.checkout(this.gitStatus.current); } catch (err) { if (err.conflicts) { ux.log(theme_1.default.status.Failed(messages.getMessage('push.pushingToEnv'))); ux.log(theme_1.default.status.Error(err.message)); } else { ux.log(theme_1.default.status.Failed(err.message)); await git.checkout(this.gitStatus.current); } } } } async pushToCopado(commitList, ux) { var _a; ux.log(`${theme_1.default.status.Info(messages.getMessage('push.inProgress'))}`); const pendingCommits = await this.pendingCommits(); const copadoUser = await filesystem_1.CopadoFiles.getCurrentUser(); const projectJson = await filesystem_1.CopadoFiles.getProjectJson(); const userStory = (_a = this.work) === null || _a === void 0 ? void 0 : _a.copadouserstoryid; const extensionConfigFilePath = path.join(filesystem_1.FILE_DIR_PATHS.LOCAL_DIR, filesystem_1.FILE_DIR_PATHS.DOT_COPADO_DIR, filesystem_1.FILE_DIR_PATHS.EXTENSION_CONFIG_FILE); if ((0, fs_1.existsSync)(extensionConfigFilePath)) { extensionConfiguration_1.ExtensionConfiguration.resolveConfigurationFile(extensionConfigFilePath); } const changes = this.isMulticloud() ? await this.changes(commitList) : []; const pushResult = await restConnections_1.RestConnections.restServiceCall('work', 'push', '', { userStory, projectJson, commitList, changes }, copadoUser); if (pushResult.errors) { this.showWarningTable(pushResult, ux); } await filesystem_1.CopadoFiles.setInGroup(this.localConfig, new Map([['copadolastcommit', pendingCommits.all[0].hash]]), 'work'); ux.log(`${theme_1.default.status.Success(messages.getMessage('push.success'))}`); } async pendingCommits() { var _a; const lastCommit = (_a = this.work) === null || _a === void 0 ? void 0 : _a.copadolastcommit; return lastCommit ? this.branchCommits : await git.log({ '--max-count': (this.branchCommits.total + 1).toString() }); } async currentCommits() { var _a, _b, _c, _d, _e, _f; let lastCommithash = ((_b = (_a = this.work) === null || _a === void 0 ? void 0 : _a.copadolastcommit) === null || _b === void 0 ? void 0 : _b.includes('_')) ? (_d = (_c = this.work) === null || _c === void 0 ? void 0 : _c.copadolastcommit) === null || _d === void 0 ? void 0 : _d.split('_')[1] : (_e = this.work) === null || _e === void 0 ? void 0 : _e.copadolastcommit; const from = lastCommithash || ((_f = this.work) === null || _f === void 0 ? void 0 : _f.copadobasebranch); return await git.log({ from, to: this.gitStatus.current, symmetric: false }); } async isScratchOrg() { let result = null; try { result = await sfdxfilesystem_1.SfdxFileSystem.getDefaultUserName() .then((defaultUserName) => sfdxfilesystem_1.SfdxFileSystem.getUserName(defaultUserName)) .then(username => sfdxfilesystem_1.SfdxFileSystem.getUserInfo(username)) .then(authInfo => authInfo.getFields()) .then(authInfoFields => authInfoFields.devHubUsername); } catch (error) { } return Boolean(result); } async isEnvironmentDisabled() { let result; try { const environment = this.userStory.Environment__r || this.userStory.copado__Environment__r; const connectionBehavior = environment.Connection_Behavior__r || environment.copado__Connection_Behavior__r; result = Boolean(connectionBehavior.DisableEnvironmentBranch__c || connectionBehavior.copado__DisableEnvironmentBranch__c); } catch (error) { result = false; } return result; } showWarningTable(pushResult, ux) { ux.styledHeader(''); const columns = {}; ['errorKey', 'filePath', 'errorMessage'].forEach(key => columns[key] = {}); ux.table(pushResult.errors, columns); } showChangedFiles(commits, ux) { const uniqueFiles = {}; const files = []; commits.forEach((commit) => { commit.files.forEach((file) => { if (!uniqueFiles[file.name]) { uniqueFiles[file.name] = true; files.push(file); } }); }); ux.styledHeader('Commit Content'); const columns = { 'state': {}, 'name': {} }; ux.table(files, columns); } async changes(commits) { var _a, e_1, _b, _c, _d, e_2, _e, _f; const result = new Map(); try { for (var _g = true, commits_1 = __asyncValues(commits), commits_1_1; commits_1_1 = await commits_1.next(), _a = commits_1_1.done, !_a; _g = true) { _c = commits_1_1.value; _g = false; const commit = _c; try { for (var _h = true, _j = (e_2 = void 0, __asyncValues(commit.files)), _k; _k = await _j.next(), _d = _k.done, !_d; _h = true) { _f = _k.value; _h = false; const file = _f; const category = await this.getCategory(file.name); const { name, type, directory, jsonInformation } = this.getDetails(category, file.name); const recordInfo = file.state + category + directory + type + name; if (!result.has(recordInfo)) { result.set(recordInfo, { a: file.state, c: category, m: directory, t: type, n: name, j: jsonInformation }); } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_h && !_d && (_e = _j.return)) await _e.call(_j); } finally { if (e_2) throw e_2.error; } } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_g && !_a && (_b = commits_1.return)) await _b.call(commits_1); } finally { if (e_1) throw e_1.error; } } return [...result.values()]; } /** * Handles different logic according to different categories and returns name, type and directory accordingly. * @param category * @param fileName * @returns name, type and directory of the commited file */ getDetails(category, fileName) { let result; switch (category) { case 'SFDX': result = this.getSFDXNameAndType(fileName); break; case 'Vlocity': result = this.getVlocityNameAndType(fileName); break; default: result = this.getMCNameAndType(fileName); break; } return result; } getVlocityNameAndType(fileName) { let result; try { const directories = fileName.replaceAll('\\', '/').split('/'); const name = directories[directories.length - 2], type = directories[directories.length - 3]; result = { name, type, directory: fileName.substring(0, fileName.lastIndexOf('/')), jsonInformation: JSON.stringify({ vk: `${type}/${name}` }) }; } catch (error) { result = this.getMCNameAndType(fileName); } return result; } /** * Check if extension has the configuration file which contains categorization with patterns and assign category accordingly. * If file not present, will return platform as category. * @param directory * @returns category type */ async getCategory(directory) { var _a; let result = this.platform(); const extensionConfigFile = path.join(filesystem_1.FILE_DIR_PATHS.LOCAL_DIR, filesystem_1.FILE_DIR_PATHS.DOT_COPADO_DIR, filesystem_1.FILE_DIR_PATHS.EXTENSION_CONFIG_FILE); if ((0, fs_1.existsSync)(extensionConfigFile)) { let categories; try { categories = (_a = JSON.parse((0, fs_1.readFileSync)(extensionConfigFile, 'utf8'))) === null || _a === void 0 ? void 0 : _a.categories; } catch (err) { throw new Error(messages.getMessage('push.parsingCliConfigurationFailed')); } if (categories) { result = this.setDefaultCategory(categories, result); for (const [categoryType, criteria] of Object.entries(categories)) { if (await this.hasMatchedCategoryCriteria(directory, criteria)) { result = categoryType; break; } } } } return result; } /** * Searches if extension has provided any default category to be set from Configuration file and returns that default category. * @param fileContent * @param category * @returns category */ setDefaultCategory(fileContent, category) { let result = category; const defaultIndex = Object.values(fileContent).findIndex((criteria) => (criteria === null || criteria === void 0 ? void 0 : criteria.default) === true); if (defaultIndex !== -1) { result = Object.keys(fileContent)[defaultIndex]; } return result; } /** * Returns true/false by checking if the initial file path of metadata is present in rootfolder or not * It checks if directory path matches the criteria mentioned in extension configuration file. * @param directory * @param criteria * @returns boolean stating if directory path is matching the given criteria */ async hasMatchedCategoryCriteria(directory, criteria) { var _a; let result = false; const rootFolderFromConfiguration = this.getRootFolder(criteria); if (rootFolderFromConfiguration === null || rootFolderFromConfiguration === void 0 ? void 0 : rootFolderFromConfiguration.length) { /** * Configuration file has node for each category and each node has rootfolders which states all files coming under that root folder will apply the respective category. * The below condition marked flag true because the criteria is matched for the category. */ if (await pathmatcher_1.AntPathMatcher.excludePath(directory, rootFolderFromConfiguration)) { result = true; } /** * Configuration file has node for each category and each node also supports exclusion of some file/patterns. * The below condition is making the flag false because the filepatterns present in exclude key within criteria is matching with the director path */ if (((_a = criteria === null || criteria === void 0 ? void 0 : criteria.exclude) === null || _a === void 0 ? void 0 : _a.filePattern) && (await pathmatcher_1.AntPathMatcher.excludePath(directory, criteria.exclude.filePattern))) { result = false; } } return result; } /** * Returns the root folders list * @param criteria * @returns list of root folder/s */ getRootFolder(criteria) { var _a, _b; let result; if (Array.isArray(criteria === null || criteria === void 0 ? void 0 : criteria.rootfolder) && ((_a = criteria === null || criteria === void 0 ? void 0 : criteria.rootfolder) === null || _a === void 0 ? void 0 : _a.length)) { result = criteria.rootfolder; } else if (!Array.isArray(criteria === null || criteria === void 0 ? void 0 : criteria.rootfolder) && ((_b = criteria === null || criteria === void 0 ? void 0 : criteria.rootfolder) === null || _b === void 0 ? void 0 : _b.split('/')[0])) { result = criteria.rootfolder; } else if (criteria === null || criteria === void 0 ? void 0 : criteria.defaultFolder) { result = [criteria.defaultFolder]; } result = [].concat(result).filter((folder) => folder); const sanitized = result.map(root => this._sanitizePath(root.replaceAll('\\', '/'))); return sanitized !== null && sanitized !== void 0 ? sanitized : result; } isMulticloud() { const platform = this.platform(); return platform && platform !== 'Salesforce'; } isSFDX() { return this.platform() === 'SFDX'; } platform() { return this.userStory.copado__Platform__c || this.userStory.Platform__c || {}; } getSFDXNameAndType(fullName) { try { let sanitizeFullName = this._sanitizePath(fullName.replaceAll('\\', '/')); let normalizedPath = process.platform === 'win32' ? sanitizeFullName.replaceAll('/', '\\') : sanitizeFullName; const parsedFile = new source_deploy_retrieve_1.MetadataResolver(undefined, this.virtualTreeContainer).getComponentsFromPath(normalizedPath)[0]; const name = parsedFile.fullName; const type = parsedFile.type.name; return { name, type, directory: fullName }; } catch (error) { // Note: Non-sfdx files can not resolved using MetadataResolver lib, and needs to be resolved using file extension return this.getMCNameAndType(fullName); } } getMCNameAndType(fullName) { const sanitizedFullName = this._sanitizePath(fullName.replaceAll('\\', '/')); const type = this.getFileExtension(sanitizedFullName); const name = this.getFileName(sanitizedFullName) || type; return { name, type, directory: this._getDirectory(sanitizedFullName) }; } getFileName(fullName) { const lastIndex = fullName.includes('.') ? fullName.lastIndexOf('.') : fullName.length; return fullName.substring(fullName.lastIndexOf('/') + 1, lastIndex); } getFileExtension(fullName) { const extension = fullName.includes('.') ? fullName.substring(fullName.lastIndexOf('.') + 1) : ''; return (extension === null || extension === void 0 ? void 0 : extension.trim()) === '' && this.isSFDX() ? 'None' : extension; } _getDirectory(fullName) { let normalizedFullName = this._sanitizePath(fullName.replaceAll('\\', '/')); return normalizedFullName.substring(0, normalizedFullName.lastIndexOf('/')); } _sanitizePath(dirPath) { if (!dirPath) { return dirPath; } let sanitized = dirPath.trim(); // "./force-app" → "force-app" if (sanitized.startsWith('./')) { sanitized = sanitized.slice(2); } return sanitized; } } WorkPush.examples = [ messages.getMessage('push.example.1'), messages.getMessage('push.example.2') ]; WorkPush.description = messages.getMessage('push.description'); WorkPush.flags = { force: sf_plugins_core_1.Flags.boolean({ char: 'f', description: messages.getMessage('push.flags.force') }) }; exports.default = WorkPush; //# sourceMappingURL=push.js.map