@copado/copado-cli
Version:
Copado Developer CLI
475 lines • 23.3 kB
JavaScript
"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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__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 });
}
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;) {
_c = commits_1_1.value;
_g = false;
try {
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;) {
_f = _k.value;
_h = false;
try {
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 });
}
}
finally {
_h = true;
}
}
}
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; }
}
}
finally {
_g = true;
}
}
}
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.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);
return 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 {
const normalizedPath = process.platform === 'win32' ? fullName.replaceAll('/', `\\`) : fullName;
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 type = this.getFileExtension(fullName);
const name = this.getFileName(fullName) || type;
return { name, type, directory: this._getDirectory(fullName) };
}
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) {
return fullName.substring(0, fullName.lastIndexOf('/'));
}
}
exports.default = WorkPush;
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') })
};
//# sourceMappingURL=push.js.map