@contentstack/cli-cm-clone
Version:
Contentstack stack clone plugin
816 lines (747 loc) • 30.6 kB
JavaScript
const ora = require('ora');
const path = require('path');
const inquirer = require('inquirer');
const chalk = require('chalk');
const fs = require('fs');
let { default: exportCmd } = require('@contentstack/cli-cm-export');
let { default: importCmd } = require('@contentstack/cli-cm-import');
const { CustomAbortController } = require('./abort-controller');
const prompt = require('prompt');
const colors = require('@colors/colors/safe');
const cloneDeep = require('lodash/cloneDeep');
const {
HandleOrgCommand,
HandleStackCommand,
HandleDestinationStackCommand,
HandleExportCommand,
SetBranchCommand,
CreateNewStackCommand,
CloneTypeSelectionCommand,
Clone,
HandleBranchCommand,
} = require('../helpers/command-helpers');
const { configHandler, getBranchFromAlias, log } = require('@contentstack/cli-utilities');
let client = {};
let config;
let cloneCommand;
let stackCreationConfirmation = [
{
type: 'confirm',
name: 'stackCreate',
message: 'Want to clone content into a new stack ?',
initial: true,
},
];
let stackName = {
type: 'input',
name: 'stack',
default: 'ABC',
message: 'Enter name for the new stack to store the cloned content ?',
};
let orgUidList = {};
let stackUidList = {};
let masterLocaleList = {};
let structureList = [
'locales',
'environments',
'extensions',
'marketplace-apps',
'webhooks',
'global-fields',
'content-types',
'workflows',
'labels',
];
let master_locale;
// Overrides prompt's stop method
prompt.stop = function () {
if (prompt.stopped) {
return;
}
prompt.emit('stop');
prompt.stopped = true;
return prompt;
};
class CloneHandler {
constructor(opt) {
config = opt;
cloneCommand = new Clone();
this.pathDir = opt.pathDir;
process.stdin.setMaxListeners(50);
log.debug('Initializing CloneHandler', config.cloneContext, { pathDir: opt.pathDir, cloneType: opt.cloneType });
}
setClient(managementSDKClient) {
client = managementSDKClient;
}
handleOrgSelection(options = {}) {
return new Promise(async (resolve, reject) => {
const { msg = '', isSource = true } = options || {};
log.debug('Handling organization selection', config.cloneContext);
const orgList = await this.getOrganizationChoices(msg).catch(reject);
if (orgList) {
log.debug(`Found ${orgList.choices?.length || 0} organization(s) to choose from`, config.cloneContext);
const orgSelected = await inquirer.prompt(orgList);
log.debug(`Organization selected: ${orgSelected.Organization}`, config.cloneContext);
if (isSource) {
config.sourceOrg = orgUidList[orgSelected.Organization];
log.debug(`Source organization UID: ${config.sourceOrg}`, config.cloneContext);
} else {
config.targetOrg = orgUidList[orgSelected.Organization];
log.debug(`Target organization UID: ${config.targetOrg}`, config.cloneContext);
}
resolve(orgSelected);
}
});
}
handleStackSelection(options = {}) {
return new Promise(async (resolve, reject) => {
try {
const { org = {}, msg = '', isSource = true } = options || {};
log.debug('Handling stack selection', config.cloneContext, { isSource, orgName: org.Organization, msg });
const stackList = await this.getStack(org, msg, isSource).catch(reject);
if (stackList) {
this.displayBackOptionMessage();
log.debug(`Found ${stackList.choices?.length || 0} stack(s) to choose from`, config.cloneContext);
const selectedStack = await inquirer.prompt(stackList);
log.debug(`Stack selected: ${selectedStack.stack}`, config.cloneContext);
if (this.executingCommand != 1) {
return reject();
}
if (isSource) {
config.sourceStackName = selectedStack.stack;
master_locale = masterLocaleList[selectedStack.stack];
config.source_stack = stackUidList[selectedStack.stack];
log.debug(`Source stack configured`, config.cloneContext);
} else {
config.target_stack = stackUidList[selectedStack.stack];
config.destinationStackName = selectedStack.stack;
log.debug(`Target stack configured`, config.cloneContext);
}
resolve(selectedStack);
}
} catch (error) {
return reject(error);
}
});
}
handleBranchSelection = async (options) => {
const { api_key, isSource = true, returnBranch = false } = options;
return new Promise(async (resolve, reject) => {
let spinner;
try {
log.debug('Handling branch selection', config.cloneContext, { isSource, returnBranch, stackApiKey: isSource ? config.source_stack : config.target_stack });
const stackAPIClient = client.stack({
api_key: isSource ? config.source_stack : config.target_stack,
management_token: config.management_token,
});
// NOTE validate if source branch is exist
if (isSource && config.sourceStackBranch) {
log.debug('Validating source branch exists', { ...config.cloneContext, branch: config.sourceStackBranch });
await this.validateIfBranchExist(stackAPIClient, true);
return resolve();
} else if(isSource && config.sourceStackBranchAlias) {
log.debug('Resolving source branch alias', { ...config.cloneContext, alias: config.sourceStackBranchAlias });
await this.resolveBranchAliases(true);
return resolve();
}
// NOTE Validate target branch is exist
if (!isSource && config.targetStackBranch) {
log.debug('Validating target branch exists', { ...config.cloneContext, branch: config.targetStackBranch });
await this.validateIfBranchExist(stackAPIClient, false);
return resolve();
} else if (!isSource && config.targetStackBranchAlias) {
log.debug('Resolving target branch alias', { ...config.cloneContext, alias: config.targetStackBranchAlias });
await this.resolveBranchAliases();
return resolve();
}
spinner = ora('Fetching Branches').start();
log.debug(`Querying branches for stack: ${isSource ? config.source_stack : config.target_stack}`, config.cloneContext);
const result = await stackAPIClient
.branch()
.query()
.find()
.then(({ items }) => items)
.catch((_err) => {});
const condition = result && Array.isArray(result) && result.length > 0;
log.debug(`Found ${result?.length || 0} branch(es)`, config.cloneContext);
// NOTE if want to get only list of branches (Pass param -> returnBranch = true )
if (returnBranch) {
resolve(condition ? result : []);
} else {
if (condition) {
spinner.succeed('Fetched Branches');
const { branch } = await inquirer.prompt({
type: 'list',
name: 'branch',
message: 'Choose a branch',
choices: result.map((row) => row.uid),
});
if (this.executingCommand != 2) {
return reject();
}
if (isSource) {
config.sourceStackBranch = branch;
log.debug(`Source branch selected: ${branch}`, config.cloneContext);
} else {
config.targetStackBranch = branch;
log.debug(`Target branch selected: ${branch}`, config.cloneContext);
}
} else {
spinner.succeed('No branches found.!');
}
resolve();
}
} catch (e) {
if (spinner) spinner.fail();
return reject(e);
}
});
};
async validateIfBranchExist(stackAPIClient, isSource) {
let spinner;
const completeSpinner = (msg, method = 'succeed') => {
spinner[method](msg);
spinner.stop();
};
try {
const branch = isSource ? config.sourceStackBranch : config.targetStackBranch;
log.debug('Validating branch existence', config.cloneContext);
spinner = ora(`Validation if ${isSource ? 'source' : 'target'} branch exist.!`).start();
const isBranchExist = await stackAPIClient
.branch(branch)
.fetch()
.then((data) => data);
if (isBranchExist && typeof isBranchExist === 'object') {
log.debug('Branch validation successful', config.cloneContext);
completeSpinner(`${isSource ? 'Source' : 'Target'} branch verified.!`);
} else {
log.error('Branch not found', config.cloneContext);
completeSpinner(`${isSource ? 'Source' : 'Target'} branch not found.!`, 'fail');
process.exit();
}
} catch (e) {
completeSpinner(`${isSource ? 'Source' : 'Target'} branch not found.!`, 'fail');
throw e;
}
}
displayBackOptionMessage() {
const ui = new inquirer.ui.BottomBar();
ui.updateBottomBar(chalk.cyan('\nPress shift & left arrow together to undo the operation\n'));
}
setBackKeyPressHandler(backKeyPressHandler) {
this.backKeyPressHandler = backKeyPressHandler;
}
removeBackKeyPressHandler() {
if (this.backKeyPressHandler) {
process.stdin.removeListener('keypress', this.backKeyPressHandler);
}
}
setExectingCommand(command) {
// 0 for org, 1 for stack, 1 for branch, 3 stack cancelled, 4 branch cancelled
this.executingCommand = command;
}
execute() {
return new Promise(async (resolve, reject) => {
let keyPressHandler;
try {
log.debug('Starting clone execution', { ...config.cloneContext, sourceStack: config.source_stack, targetStack: config.target_stack });
if (!config.source_stack) {
const orgMsg = 'Choose an organization where your source stack exists:';
log.debug('Source stack not provided, prompting for organization', config.cloneContext);
this.setExectingCommand(0);
this.removeBackKeyPressHandler();
const org = await cloneCommand.execute(new HandleOrgCommand({ msg: orgMsg, isSource: true }, this));
let self = this;
if (org) {
keyPressHandler = async function (_ch, key) {
// executingCommand is a tracking property to determine which method invoked this key press.
if (key.name === 'left' && key.shift) {
if (self.executingCommand === 1) {
self.setExectingCommand(3);
} else if (self.executingCommand === 2) {
self.setExectingCommand(4);
}
config.source_stack = null;
config.sourceStackBranch = null;
if (self.executingCommand != 0) {
console.clear();
await cloneCommand.undo();
}
}
};
process.stdin.addListener('keypress', keyPressHandler);
this.setBackKeyPressHandler(keyPressHandler);
await this.executeStackPrompt({ org, isSource: true, msg: 'Select the source stack' });
} else {
return reject('Org not found.');
}
} else {
log.debug('Source stack provided, proceeding with branch selection and export', config.cloneContext);
this.setExectingCommand(2);
await this.handleBranchSelection({ api_key: config.sourceStack });
log.debug('Starting export operation', config.cloneContext);
const exportRes = await cloneCommand.execute(new HandleExportCommand(null, this));
await cloneCommand.execute(new SetBranchCommand(null, this));
if (exportRes) {
log.debug('Export completed, proceeding with destination setup', config.cloneContext);
this.executeDestination().catch((error) => {
return reject(error);
});
}
}
log.debug('Clone execution completed successfully', config.cloneContext);
return resolve();
} catch (error) {
return reject(error);
}
});
}
async executeStackPrompt(params = {}) {
try {
this.setExectingCommand(1);
const sourceStack = await cloneCommand.execute(new HandleStackCommand(params, this));
if (config.source_stack) {
await this.executeBranchPrompt(params);
}
stackName.default = config.stackName || `Copy of ${sourceStack.stack || config.source_alias}`;
} catch (error) {
throw error;
}
}
async executeBranchPrompt(parentParams) {
try {
this.setExectingCommand(2);
await cloneCommand.execute(
new HandleBranchCommand(
{ api_key: config.source_stack },
this,
this.executeStackPrompt.bind(this, parentParams),
),
);
await this.executeExport();
} catch (error) {
throw error;
}
}
async executeExport() {
try {
log.debug('Executing export operation', config.cloneContext);
const exportRes = await cloneCommand.execute(new HandleExportCommand(null, this));
await cloneCommand.execute(new SetBranchCommand(null, this));
if (exportRes) {
log.debug('Export operation completed, proceeding with destination', config.cloneContext);
this.executeDestination().catch(() => {
throw '';
});
}
} catch (error) {
throw error;
} finally {
this.removeBackKeyPressHandler();
}
}
async executeDestination() {
return new Promise(async (resolve, reject) => {
let keyPressHandler;
try {
log.debug('Executing destination setup', config.cloneContext);
let canCreateStack = false;
if (!config.target_stack) {
log.debug('Target stack not provided, prompting for stack creation', config.cloneContext);
canCreateStack = await inquirer.prompt(stackCreationConfirmation);
}
this.setExectingCommand(0);
this.removeBackKeyPressHandler();
const orgMsgExistingStack = 'Choose an organization where the destination stack exists: ';
const orgMsgNewStack = 'Choose an organization where you want to create a stack: ';
let org;
if (!config.target_stack) {
org = await cloneCommand.execute(
new HandleOrgCommand(
{
msg: !canCreateStack.stackCreate ? orgMsgExistingStack : orgMsgNewStack,
},
this,
),
);
}
const params = { org, canCreateStack };
if (!config.target_stack) {
let self = this;
keyPressHandler = async function (_ch, key) {
if (key.name === 'left' && key.shift) {
if (self.executingCommand === 1) {
self.setExectingCommand(3);
} else if (self.executingCommand === 2) {
self.setExectingCommand(4);
}
if (self.createNewStackPrompt) {
self.createNewStackPrompt.stop();
}
config.target_stack = null;
config.targetStackBranch = null;
if (self.executingCommand != 0) {
console.clear();
await cloneCommand.undo();
}
}
};
process.stdin.addListener('keypress', keyPressHandler);
this.setBackKeyPressHandler(keyPressHandler);
await this.executeStackDestinationPrompt(params);
} else {
await this.executeBranchDestinationPrompt(params);
}
log.debug('Destination setup completed successfully', config.cloneContext);
return resolve();
} catch (error) {
reject(error);
}
});
}
async executeStackDestinationPrompt(params) {
try {
this.setExectingCommand(1);
const { org, canCreateStack } = params;
if (!canCreateStack.stackCreate) {
const stackMsg = 'Choose the destination stack:';
await cloneCommand.execute(new HandleDestinationStackCommand({ org, msg: stackMsg, isSource: false }, this));
this.executeBranchDestinationPrompt(params);
} else {
const orgUid = orgUidList[org.Organization];
await cloneCommand.execute(new CreateNewStackCommand({ orgUid }, this));
this.removeBackKeyPressHandler();
await cloneCommand.execute(new CloneTypeSelectionCommand(null, this));
}
} catch (error) {
throw error;
}
}
async executeBranchDestinationPrompt(parentParams) {
try {
this.setExectingCommand(2);
await cloneCommand.execute(
new HandleBranchCommand(
{ isSource: false, api_key: config.target_stack },
this,
this.executeStackDestinationPrompt.bind(this, parentParams),
),
);
this.removeBackKeyPressHandler();
await cloneCommand.execute(new CloneTypeSelectionCommand(null, this));
} catch (error) {
throw error;
}
}
setCreateNewStackPrompt(createNewStackPrompt) {
this.createNewStackPrompt = createNewStackPrompt;
}
async setBranch() {
if (!config.sourceStackBranch) {
try {
const branches = await client
.stack({ api_key: config.source_stack })
.branch()
.query()
.find()
.catch((_err) => {});
if (branches && branches.items && branches.items.length) {
config.sourceStackBranch = 'main';
}
} catch (_error) {}
}
}
async getOrganizationChoices(orgMessage) {
let orgChoice = {
type: 'list',
name: 'Organization',
message: orgMessage !== undefined ? orgMessage : 'Choose an organization',
choices: [],
};
return new Promise(async (resolve, reject) => {
log.debug('Fetching organization choices', config.cloneContext);
const spinner = ora('Fetching Organization').start();
try {
let organizations;
const configOrgUid = configHandler.get('oauthOrgUid');
log.debug('Getting organizations', config.cloneContext, { hasConfigOrgUid: !!configOrgUid });
if (configOrgUid) {
organizations = await client.organization(configOrgUid).fetch();
} else {
organizations = await client.organization().fetchAll({ limit: 100 });
}
spinner.succeed('Fetched Organization');
log.debug('Fetched organizations', config.cloneContext);
for (const element of organizations.items || [organizations]) {
orgUidList[element.name] = element.uid;
orgChoice.choices.push(element.name);
}
return resolve(orgChoice);
} catch (e) {
spinner.fail();
return reject(e);
}
});
}
async getStack(answer, stkMessage) {
return new Promise(async (resolve, reject) => {
let stackChoice = {
type: 'list',
name: 'stack',
message: stkMessage !== undefined ? stkMessage : 'Select the stack',
choices: [],
};
log.debug('Fetching stacks', config.cloneContext);
const spinner = ora('Fetching stacks').start();
try {
const organization_uid = orgUidList[answer.Organization];
log.debug('Querying stacks for organization', config.cloneContext, { organizationUid: organization_uid });
const stackList = client.stack().query({ organization_uid }).find();
stackList
.then((stacklist) => {
log.debug('Fetched stacks', config.cloneContext, { count: stacklist.items ? stacklist.items.length : 0 });
for (const element of stacklist.items) {
stackUidList[element.name] = element.api_key;
masterLocaleList[element.name] = element.master_locale;
stackChoice.choices.push(element.name);
}
spinner.succeed('Fetched stack');
return resolve(stackChoice);
})
.catch((error) => {
spinner.fail();
return reject(error);
});
} catch (e) {
spinner.fail();
return reject(e);
}
});
}
async createNewStack(options) {
return new Promise(async (resolve, reject) => {
try {
const { orgUid } = options;
log.debug('Creating new stack', config.cloneContext, { orgUid, masterLocale: master_locale, stackName: config.stackName });
this.displayBackOptionMessage();
let inputvalue;
if (!config.stackName) {
log.debug('Stack name not provided, prompting user', config.cloneContext);
prompt.start();
prompt.message = '';
this.setCreateNewStackPrompt(prompt);
inputvalue = await this.getNewStackPromptResult();
this.setCreateNewStackPrompt(null);
} else {
inputvalue = { stack: config.stackName };
}
if (this.executingCommand === 0 || !inputvalue) {
log.debug('Stack creation cancelled or invalid input', config.cloneContext);
return reject();
}
let stack = { name: inputvalue.stack, master_locale: master_locale };
log.debug('Creating stack with configuration', config.cloneContext);
const spinner = ora('Creating New stack').start();
log.debug('Sending stack creation API request', config.cloneContext);
let newStack = client.stack().create({ stack }, { organization_uid: orgUid });
newStack
.then((result) => {
log.debug('Stack created successfully', config.cloneContext, {
stackName: result.name,
});
spinner.succeed('New Stack created Successfully name as ' + result.name);
config.target_stack = result.api_key;
config.destinationStackName = result.name;
log.debug('Target stack configuration updated', config.cloneContext);
return resolve(result);
})
.catch((error) => {
spinner.fail();
return reject(error.errorMessage + ' Contact the Organization owner for Stack Creation access.');
});
} catch (error) {
return reject(error);
}
});
}
getNewStackPromptResult() {
return new Promise((resolve) => {
prompt.get(
{
properties: {
name: { description: colors.white(stackName.message), default: colors.grey(stackName.default) },
},
},
function (_, result) {
if (prompt.stopped) {
prompt.stopped = false;
resolve();
} else {
let _name = result.name.replace(/\[\d+m/g, '');
_name = _name.replace(//g, '');
resolve({ stack: _name });
}
},
);
});
}
async resolveBranchAliases(isSource = false) {
try {
log.debug('Resolving branch aliases', { ...config.cloneContext, isSource, alias: isSource ? config.sourceStackBranchAlias : config.targetStackBranchAlias });
if (isSource) {
const sourceStack = client.stack({ api_key: config.source_stack });
config.sourceStackBranch = await getBranchFromAlias(sourceStack, config.sourceStackBranchAlias);
log.debug('Source branch alias resolved', { ...config.cloneContext, alias: config.sourceStackBranchAlias, branch: config.sourceStackBranch });
} else {
const targetStack = client.stack({ api_key: config.target_stack });
config.targetStackBranch = await getBranchFromAlias(targetStack, config.targetStackBranchAlias);
log.debug('Target branch alias resolved', { ...config.cloneContext, alias: config.targetStackBranchAlias, branch: config.targetStackBranch });
}
} catch (error) {
throw error;
}
}
async cloneTypeSelection() {
console.clear();
return new Promise(async (resolve, reject) => {
log.debug('Starting clone type selection', config.cloneContext);
const choices = [
'Structure (all modules except entries & assets)',
'Structure with content (all modules including entries & assets)',
];
const cloneTypeSelection = [
{
choices,
type: 'list',
name: 'type',
message: 'Choose the type of data to clone:',
},
];
let successMsg;
let selectedValue = {};
config['data'] = path.join(__dirname.split('src')[0], 'contents', config.sourceStackBranch || '');
log.debug(`Clone data directory: ${config['data']}`, config.cloneContext);
if (!config.cloneType) {
log.debug('Clone type not specified, prompting user for selection', config.cloneContext);
selectedValue = await inquirer.prompt(cloneTypeSelection);
} else {
log.debug(`Using pre-configured clone type: ${config.cloneType}`, config.cloneContext);
}
if (config.cloneType === 'a' || selectedValue.type === 'Structure (all modules except entries & assets)') {
config['modules'] = structureList;
successMsg = 'Stack clone Structure completed';
log.debug(`Clone type: Structure only. Modules to clone: ${structureList.join(', ')}`, config.cloneContext);
} else {
successMsg = 'Stack clone completed with structure and content';
log.debug('Clone type: Structure with content (all modules)', config.cloneContext);
}
this.cmdImport()
.then(() => {
log.debug('Clone type selection and import completed successfully', config.cloneContext);
resolve(successMsg);
})
.catch(reject);
});
}
async cmdExport() {
return new Promise((resolve, reject) => {
log.debug('Preparing export command', { ...config.cloneContext, sourceStack: config.source_stack, cloneType: config.cloneType });
// Creating export specific config by merging external configurations
let exportConfig = Object.assign({}, cloneDeep(config), { ...config?.export });
delete exportConfig.import;
delete exportConfig.export;
const exportDir = __dirname.split('src')[0] + 'contents';
log.debug(`Export directory: ${exportDir}`, config.cloneContext);
const cmd = ['-k', exportConfig.source_stack, '-d', exportDir];
if (exportConfig.cloneType === 'a') {
exportConfig.filteredModules = ['stack'].concat(structureList);
log.debug(`Filtered modules for structure-only export: ${exportConfig.filteredModules.join(', ')}`, config.cloneContext);
}
if (exportConfig.source_alias) {
cmd.push('-a', exportConfig.source_alias);
log.debug(`Using source alias: ${exportConfig.source_alias}`, config.cloneContext);
}
if (exportConfig.sourceStackBranch) {
cmd.push('--branch', exportConfig.sourceStackBranch);
log.debug(`Using source branch: ${exportConfig.sourceStackBranch}`, config.cloneContext);
}
if (exportConfig.forceStopMarketplaceAppsPrompt) {
cmd.push('-y');
log.debug('Force stop marketplace apps prompt enabled', config.cloneContext);
}
const configFilePath = path.join(__dirname, 'dummyConfig.json');
cmd.push('-c');
cmd.push(configFilePath);
log.debug(`Writing export config to: ${configFilePath}`, config.cloneContext);
fs.writeFileSync(configFilePath, JSON.stringify(exportConfig));
log.debug('Export command prepared', config.cloneContext, {
cmd: cmd.join(' '),
exportDir,
sourceStack: exportConfig.source_stack,
branch: exportConfig.sourceStackBranch
});
log.debug('Running export command', config.cloneContext, { cmd });
let exportData = exportCmd.run(cmd);
exportData.then(() => {
log.debug('Export command completed successfully', config.cloneContext);
resolve(true);
}).catch((error) => {
reject(error);
});
});
}
async cmdImport() {
return new Promise(async (resolve, _reject) => {
log.debug('Preparing import command', { ...config.cloneContext, targetStack: config.target_stack, targetBranch: config.targetStackBranch });
// Creating export specific config by merging external configurations
let importConfig = Object.assign({}, cloneDeep(config), { ...config?.import });
delete importConfig.import;
delete importConfig.export;
const configFilePath = path.join(__dirname, 'dummyConfig.json');
const cmd = ['-c', configFilePath];
if (importConfig.destination_alias) {
cmd.push('-a', importConfig.destination_alias);
log.debug(`Using destination alias: ${importConfig.destination_alias}`, config.cloneContext);
}
if (!importConfig.data && importConfig.sourceStackBranch) {
const dataPath = path.join(importConfig.pathDir, importConfig.sourceStackBranch);
cmd.push('-d', dataPath);
log.debug(`Import data path: ${dataPath}`, config.cloneContext);
}
if (importConfig.targetStackBranch) {
cmd.push('--branch', importConfig.targetStackBranch);
log.debug(`Using target branch: ${importConfig.targetStackBranch}`, config.cloneContext);
}
if (importConfig.importWebhookStatus) {
cmd.push('--import-webhook-status', importConfig.importWebhookStatus);
log.debug(`Import webhook status: ${importConfig.importWebhookStatus}`, config.cloneContext);
}
if (importConfig.skipAudit) {
cmd.push('--skip-audit');
log.debug('Skip audit flag enabled', config.cloneContext);
}
if (importConfig.forceStopMarketplaceAppsPrompt) {
cmd.push('-y');
log.debug('Force stop marketplace apps prompt enabled', config.cloneContext);
}
log.debug(`Writing import config to: ${configFilePath}`, config.cloneContext);
fs.writeFileSync(configFilePath, JSON.stringify(importConfig));
log.debug('Import command prepared', config.cloneContext, {
cmd: cmd.join(' '),
targetStack: importConfig.target_stack,
targetBranch: importConfig.targetStackBranch,
dataPath: importConfig.data || path.join(importConfig.pathDir, importConfig.sourceStackBranch)
});
log.debug('Running import command', config.cloneContext, { cmd });
await importCmd.run(cmd);
log.debug('Import command completed successfully', config.cloneContext);
log.debug('Clearing import config file', config.cloneContext);
fs.writeFileSync(configFilePath, JSON.stringify({}));
return resolve();
});
}
}
module.exports = {
CloneHandler,
client,
};