UNPKG

@contentstack/cli-cm-clone

Version:
764 lines (763 loc) 42.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CloneHandler = void 0; const tslib_1 = require("tslib"); const ora_1 = tslib_1.__importDefault(require("ora")); const path = tslib_1.__importStar(require("path")); const inquirer_1 = tslib_1.__importDefault(require("inquirer")); const chalk_1 = tslib_1.__importDefault(require("chalk")); const fs = tslib_1.__importStar(require("fs")); const rimraf_1 = require("rimraf"); const cli_cm_export_1 = tslib_1.__importDefault(require("@contentstack/cli-cm-export")); const cli_cm_import_1 = tslib_1.__importDefault(require("@contentstack/cli-cm-import")); // eslint-disable-next-line @typescript-eslint/no-var-requires const prompt = require('prompt'); const safe_1 = tslib_1.__importDefault(require("@colors/colors/safe")); const cloneDeep_1 = tslib_1.__importDefault(require("lodash/cloneDeep")); const cli_utilities_1 = require("@contentstack/cli-utilities"); const command_helpers_1 = require("../helpers/command-helpers"); const constants_1 = require("../../utils/constants"); // Override prompt's stop method prompt.stop = function () { if (prompt.stopped) { return; } prompt.emit('stop'); prompt.stopped = true; return prompt; }; class CloneHandler { constructor(opt) { this.orgUidList = {}; this.stackUidList = {}; this.masterLocaleList = {}; this.config = opt; this.cloneCommand = new command_helpers_1.Clone(); this.pathDir = opt.pathDir || ''; // Create mutable copy of stack name prompt for dynamic default updates this.stackNamePrompt = { type: constants_1.STACK_NAME_PROMPT.type, name: constants_1.STACK_NAME_PROMPT.name, default: constants_1.STACK_NAME_PROMPT.default, message: constants_1.STACK_NAME_PROMPT.message, }; process.stdin.setMaxListeners(50); cli_utilities_1.log.debug('Initializing CloneHandler', Object.assign(Object.assign({}, this.config.cloneContext), { pathDir: opt.pathDir, cloneType: opt.cloneType })); } setClient(managementSDKClient) { this.client = managementSDKClient; } async getOrganizationChoices(orgMessage) { const orgChoice = { type: 'list', name: 'Organization', message: orgMessage !== undefined ? orgMessage : 'Choose an organization', choices: [], }; return new Promise(async (resolve, reject) => { cli_utilities_1.log.debug('Fetching organization choices', this.config.cloneContext); const spinner = (0, ora_1.default)('Fetching Organization').start(); try { let organizations; const configOrgUid = cli_utilities_1.configHandler.get('oauthOrgUid'); cli_utilities_1.log.debug('Getting organizations', Object.assign(Object.assign({}, this.config.cloneContext), { hasConfigOrgUid: !!configOrgUid })); if (configOrgUid) { organizations = await this.client.organization(configOrgUid).fetch(); } else { organizations = await this.client.organization().fetchAll({ limit: 100 }); } spinner.succeed('Fetched Organization'); cli_utilities_1.log.debug('Fetched organizations', this.config.cloneContext); for (const element of organizations.items || [organizations]) { this.orgUidList[element.name] = element.uid; orgChoice.choices.push(element.name); } return resolve(orgChoice); } catch (e) { spinner.fail(); return reject(e); } }); } async handleOrgSelection(options = {}) { return new Promise(async (resolve, reject) => { var _a; const { msg = '', isSource = true } = options || {}; cli_utilities_1.log.debug('Handling organization selection', this.config.cloneContext); const orgList = await this.getOrganizationChoices(msg).catch(reject); if (orgList) { cli_utilities_1.log.debug(`Found ${((_a = orgList.choices) === null || _a === void 0 ? void 0 : _a.length) || 0} organization(s) to choose from`, this.config.cloneContext); const orgSelected = await inquirer_1.default.prompt(orgList); cli_utilities_1.log.debug(`Organization selected: ${orgSelected.Organization}`, this.config.cloneContext); if (isSource) { this.config.sourceOrg = this.orgUidList[orgSelected.Organization]; cli_utilities_1.log.debug(`Source organization UID: ${this.config.sourceOrg}`, this.config.cloneContext); } else { this.config.targetOrg = this.orgUidList[orgSelected.Organization]; cli_utilities_1.log.debug(`Target organization UID: ${this.config.targetOrg}`, this.config.cloneContext); } resolve(orgSelected); } }); } async getStack(answer, stkMessage, isSource = true) { const stackChoice = { type: 'list', name: 'stack', message: stkMessage !== undefined ? stkMessage : 'Select the stack', choices: [], }; return new Promise(async (resolve, reject) => { cli_utilities_1.log.debug('Fetching stacks', this.config.cloneContext); const spinner = (0, ora_1.default)('Fetching stacks').start(); try { const organization_uid = this.orgUidList[answer.Organization]; cli_utilities_1.log.debug('Querying stacks for organization', Object.assign(Object.assign({}, this.config.cloneContext), { organizationUid: organization_uid })); const stackList = this.client.stack().query({ organization_uid }).find(); stackList .then((stacklist) => { cli_utilities_1.log.debug('Fetched stacks', Object.assign(Object.assign({}, this.config.cloneContext), { count: stacklist.items ? stacklist.items.length : 0 })); for (const element of stacklist.items) { this.stackUidList[element.name] = element.api_key; this.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); } }); } displayBackOptionMessage() { const ui = new inquirer_1.default.ui.BottomBar(); ui.updateBottomBar(chalk_1.default.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; } async handleStackSelection(options = {}) { return new Promise(async (resolve, reject) => { var _a; try { const { org = {}, msg = '', isSource = true } = options || {}; cli_utilities_1.log.debug('Handling stack selection', Object.assign(Object.assign({}, this.config.cloneContext), { isSource, orgName: org.Organization, msg })); const stackList = await this.getStack(org, msg, isSource).catch(reject); if (stackList) { this.displayBackOptionMessage(); cli_utilities_1.log.debug(`Found ${((_a = stackList.choices) === null || _a === void 0 ? void 0 : _a.length) || 0} stack(s) to choose from`, this.config.cloneContext); const selectedStack = await inquirer_1.default.prompt(stackList); cli_utilities_1.log.debug(`Stack selected: ${selectedStack.stack}`, this.config.cloneContext); if (this.executingCommand != 1) { return reject(); } if (isSource) { this.config.sourceStackName = selectedStack.stack; this.master_locale = this.masterLocaleList[selectedStack.stack]; this.config.source_stack = this.stackUidList[selectedStack.stack]; cli_utilities_1.log.debug(`Source stack configured`, this.config.cloneContext); } else { this.config.target_stack = this.stackUidList[selectedStack.stack]; this.config.destinationStackName = selectedStack.stack; cli_utilities_1.log.debug(`Target stack configured`, this.config.cloneContext); } resolve(selectedStack); } } catch (error) { return reject(error); } }); } async validateIfBranchExist(stackAPIClient, isSource) { let spinner; const completeSpinner = (msg, method = 'succeed') => { spinner[method](msg); spinner.stop(); }; try { const branch = isSource ? this.config.sourceStackBranch : this.config.targetStackBranch; cli_utilities_1.log.debug('Validating branch existence', this.config.cloneContext); spinner = (0, ora_1.default)(`Validation if ${isSource ? 'source' : 'target'} branch exist.!`).start(); const isBranchExist = await stackAPIClient .branch(branch) .fetch() .then((data) => data); if (isBranchExist && typeof isBranchExist === 'object') { cli_utilities_1.log.debug('Branch validation successful', this.config.cloneContext); completeSpinner(`${isSource ? 'Source' : 'Target'} branch verified.!`); } else { cli_utilities_1.log.error('Branch not found', this.config.cloneContext); completeSpinner(`${isSource ? 'Source' : 'Target'} branch not found.!`, 'fail'); process.exit(); } } catch (e) { completeSpinner(`${isSource ? 'Source' : 'Target'} branch not found.!`, 'fail'); throw e; } } async resolveBranchAliases(isSource = false) { try { cli_utilities_1.log.debug('Resolving branch aliases', Object.assign(Object.assign({}, this.config.cloneContext), { isSource, alias: isSource ? this.config.sourceStackBranchAlias : this.config.targetStackBranchAlias })); if (isSource) { const sourceStack = this.client.stack({ api_key: this.config.source_stack }); this.config.sourceStackBranch = await (0, cli_utilities_1.getBranchFromAlias)(sourceStack, this.config.sourceStackBranchAlias); cli_utilities_1.log.debug('Source branch alias resolved', Object.assign(Object.assign({}, this.config.cloneContext), { alias: this.config.sourceStackBranchAlias, branch: this.config.sourceStackBranch })); } else { const targetStack = this.client.stack({ api_key: this.config.target_stack }); this.config.targetStackBranch = await (0, cli_utilities_1.getBranchFromAlias)(targetStack, this.config.targetStackBranchAlias); cli_utilities_1.log.debug('Target branch alias resolved', Object.assign(Object.assign({}, this.config.cloneContext), { alias: this.config.targetStackBranchAlias, branch: this.config.targetStackBranch })); } } catch (error) { throw error; } } async handleBranchSelection(options = {}) { const { api_key, isSource = true, returnBranch = false } = options; return new Promise(async (resolve, reject) => { let spinner; try { cli_utilities_1.log.debug('Handling branch selection', Object.assign(Object.assign({}, this.config.cloneContext), { isSource, returnBranch, stackApiKey: isSource ? this.config.source_stack : this.config.target_stack })); const stackAPIClient = this.client.stack({ api_key: isSource ? this.config.source_stack : this.config.target_stack, management_token: this.config.management_token, }); // NOTE validate if source branch is exist if (isSource && this.config.sourceStackBranch) { cli_utilities_1.log.debug('Validating source branch exists', Object.assign(Object.assign({}, this.config.cloneContext), { branch: this.config.sourceStackBranch })); await this.validateIfBranchExist(stackAPIClient, true); return resolve(undefined); } else if (isSource && this.config.sourceStackBranchAlias) { cli_utilities_1.log.debug('Resolving source branch alias', Object.assign(Object.assign({}, this.config.cloneContext), { alias: this.config.sourceStackBranchAlias })); await this.resolveBranchAliases(true); return resolve(undefined); } // NOTE Validate target branch is exist if (!isSource && this.config.targetStackBranch) { cli_utilities_1.log.debug('Validating target branch exists', Object.assign(Object.assign({}, this.config.cloneContext), { branch: this.config.targetStackBranch })); await this.validateIfBranchExist(stackAPIClient, false); return resolve(undefined); } else if (!isSource && this.config.targetStackBranchAlias) { cli_utilities_1.log.debug('Resolving target branch alias', Object.assign(Object.assign({}, this.config.cloneContext), { alias: this.config.targetStackBranchAlias })); await this.resolveBranchAliases(); return resolve(undefined); } spinner = (0, ora_1.default)('Fetching Branches').start(); cli_utilities_1.log.debug(`Querying branches for stack: ${isSource ? this.config.source_stack : this.config.target_stack}`, this.config.cloneContext); const result = await stackAPIClient .branch() .query() .find() .then(({ items }) => items) .catch((_err) => { }); const condition = result && Array.isArray(result) && result.length > 0; cli_utilities_1.log.debug(`Found ${(result === null || result === void 0 ? void 0 : result.length) || 0} branch(es)`, this.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_1.default.prompt({ type: 'list', name: 'branch', message: 'Choose a branch', choices: result.map((row) => row.uid), }); if (this.executingCommand != 2) { return reject(); } if (isSource) { this.config.sourceStackBranch = branch; cli_utilities_1.log.debug(`Source branch selected: ${branch}`, this.config.cloneContext); } else { this.config.targetStackBranch = branch; cli_utilities_1.log.debug(`Target branch selected: ${branch}`, this.config.cloneContext); } } else { spinner.succeed('No branches found.!'); } resolve(undefined); } } catch (e) { if (spinner) spinner.fail(); return reject(e); } }); } async executeStackPrompt(params = {}) { try { this.setExectingCommand(1); const sourceStack = await this.cloneCommand.execute((0, command_helpers_1.HandleStackCommand)(params, this)); if (this.config.source_stack) { await this.executeBranchPrompt(params); } // Update stackName default dynamically this.stackNamePrompt.default = this.config.stackName || `Copy of ${sourceStack.stack || this.config.source_alias || 'ABC'}`; } catch (error) { throw error; } } async executeBranchPrompt(parentParams) { try { this.setExectingCommand(2); await this.cloneCommand.execute((0, command_helpers_1.HandleBranchCommand)({ api_key: this.config.source_stack }, this, this.executeStackPrompt.bind(this, parentParams))); await this.executeExport(); } catch (error) { throw error; } } async executeExport() { try { cli_utilities_1.log.debug('Executing export operation', this.config.cloneContext); const exportRes = await this.cloneCommand.execute((0, command_helpers_1.HandleExportCommand)(null, this)); await this.cloneCommand.execute((0, command_helpers_1.SetBranchCommand)(null, this)); if (exportRes) { cli_utilities_1.log.debug('Export operation completed, proceeding with destination', this.config.cloneContext); this.executeDestination().catch(() => { throw ''; }); } } catch (error) { throw error; } finally { this.removeBackKeyPressHandler(); } } async execute() { return new Promise(async (resolve, reject) => { let keyPressHandler; try { cli_utilities_1.log.debug('Starting clone execution', Object.assign(Object.assign({}, this.config.cloneContext), { sourceStack: this.config.source_stack, targetStack: this.config.target_stack })); if (!this.config.source_stack) { const orgMsg = 'Choose an organization where your source stack exists:'; cli_utilities_1.log.debug('Source stack not provided, prompting for organization', this.config.cloneContext); this.setExectingCommand(0); this.removeBackKeyPressHandler(); const org = await this.cloneCommand.execute((0, command_helpers_1.HandleOrgCommand)({ msg: orgMsg, isSource: true }, this)); const 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); } self.config.source_stack = undefined; self.config.sourceStackBranch = undefined; if (self.executingCommand != 0) { console.clear(); await self.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 { cli_utilities_1.log.debug('Source stack provided, proceeding with branch selection and export', this.config.cloneContext); this.setExectingCommand(2); await this.handleBranchSelection({ api_key: this.config.source_stack }); cli_utilities_1.log.debug('Starting export operation', this.config.cloneContext); const exportRes = await this.cloneCommand.execute((0, command_helpers_1.HandleExportCommand)(null, this)); await this.cloneCommand.execute((0, command_helpers_1.SetBranchCommand)(null, this)); if (exportRes) { cli_utilities_1.log.debug('Export completed, proceeding with destination setup', this.config.cloneContext); this.executeDestination().catch((error) => { return reject(error); }); } } cli_utilities_1.log.debug('Clone execution completed successfully', this.config.cloneContext); return resolve(); } catch (error) { return reject(error); } }); } async executeDestination() { return new Promise(async (resolve, reject) => { let keyPressHandler; try { cli_utilities_1.log.debug('Executing destination setup', this.config.cloneContext); let canCreateStack = false; if (!this.config.target_stack) { cli_utilities_1.log.debug('Target stack not provided, prompting for stack creation', this.config.cloneContext); canCreateStack = await inquirer_1.default.prompt(constants_1.STACK_CREATION_CONFIRMATION); } 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 (!this.config.target_stack) { org = await this.cloneCommand.execute((0, command_helpers_1.HandleOrgCommand)({ msg: !canCreateStack.stackCreate ? orgMsgExistingStack : orgMsgNewStack, isSource: false, }, this)); } const params = { org, canCreateStack }; if (!this.config.target_stack) { const 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(); } self.config.target_stack = undefined; self.config.targetStackBranch = undefined; if (self.executingCommand != 0) { console.clear(); await self.cloneCommand.undo(); } } }; process.stdin.addListener('keypress', keyPressHandler); this.setBackKeyPressHandler(keyPressHandler); await this.executeStackDestinationPrompt(params); } else { await this.executeBranchDestinationPrompt(params); } cli_utilities_1.log.debug('Destination setup completed successfully', this.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 this.cloneCommand.execute((0, command_helpers_1.HandleDestinationStackCommand)({ org, msg: stackMsg, isSource: false }, this)); await this.executeBranchDestinationPrompt(params); } else { const orgUid = this.orgUidList[org.Organization]; await this.cloneCommand.execute((0, command_helpers_1.CreateNewStackCommand)({ orgUid }, this)); this.removeBackKeyPressHandler(); await this.cloneCommand.execute((0, command_helpers_1.CloneTypeSelectionCommand)(null, this)); } } catch (error) { throw error; } } async executeBranchDestinationPrompt(parentParams) { try { this.setExectingCommand(2); await this.cloneCommand.execute((0, command_helpers_1.HandleBranchCommand)({ isSource: false, api_key: this.config.target_stack }, this, this.executeStackDestinationPrompt.bind(this, parentParams))); this.removeBackKeyPressHandler(); await this.cloneCommand.execute((0, command_helpers_1.CloneTypeSelectionCommand)(null, this)); } catch (error) { throw error; } } async cmdExport() { return new Promise((resolve, reject) => { var _a; cli_utilities_1.log.debug('Preparing export command', Object.assign(Object.assign({}, this.config.cloneContext), { sourceStack: this.config.source_stack, cloneType: this.config.cloneType })); // Creating export specific config by merging external configurations let exportConfig = Object.assign({}, (0, cloneDeep_1.default)(this.config), Object.assign({}, (_a = this.config) === null || _a === void 0 ? void 0 : _a.export)); delete exportConfig.import; delete exportConfig.export; // Resolve path to package root - go up 3 levels from __dirname (core/util -> package root) const packageRoot = path.resolve(__dirname, '../../..'); const exportDir = path.join(packageRoot, 'contents'); cli_utilities_1.log.debug(`Export directory: ${exportDir}`, this.config.cloneContext); const cmd = ['-k', exportConfig.source_stack, '-d', exportDir]; if (exportConfig.cloneType === 'a') { exportConfig.filteredModules = ['stack'].concat(constants_1.STRUCTURE_LIST); cli_utilities_1.log.debug(`Filtered modules for structure-only export: ${exportConfig.filteredModules.join(', ')}`, this.config.cloneContext); } if (exportConfig.source_alias) { cmd.push('-a', exportConfig.source_alias); cli_utilities_1.log.debug(`Using source alias: ${exportConfig.source_alias}`, this.config.cloneContext); } if (exportConfig.sourceStackBranch) { cmd.push('--branch', exportConfig.sourceStackBranch); cli_utilities_1.log.debug(`Using source branch: ${exportConfig.sourceStackBranch}`, this.config.cloneContext); } if (exportConfig.forceStopMarketplaceAppsPrompt) { cmd.push('-y'); cli_utilities_1.log.debug('Force stop marketplace apps prompt enabled', this.config.cloneContext); } // dummyConfig.json is in the same directory as this file const configFilePath = path.join(__dirname, 'dummyConfig.json'); cmd.push('-c'); cmd.push(configFilePath); cli_utilities_1.log.debug(`Writing export config to: ${configFilePath}`, this.config.cloneContext); fs.writeFileSync(configFilePath, JSON.stringify(exportConfig)); cli_utilities_1.log.debug('Export command prepared', Object.assign(Object.assign({}, this.config.cloneContext), { cmd: cmd.join(' '), exportDir, sourceStack: exportConfig.source_stack, branch: exportConfig.sourceStackBranch })); cli_utilities_1.log.debug('Running export command', Object.assign(Object.assign({}, this.config.cloneContext), { cmd })); const exportData = cli_cm_export_1.default.run(cmd); exportData.then(() => { cli_utilities_1.log.debug('Export command completed successfully', this.config.cloneContext); resolve(true); }).catch((error) => { reject(error); }); }); } async cmdImport() { return new Promise(async (resolve, _reject) => { var _a; cli_utilities_1.log.debug('Preparing import command', Object.assign(Object.assign({}, this.config.cloneContext), { targetStack: this.config.target_stack, targetBranch: this.config.targetStackBranch })); // Creating export specific config by merging external configurations let importConfig = Object.assign({}, (0, cloneDeep_1.default)(this.config), Object.assign({}, (_a = this.config) === null || _a === void 0 ? void 0 : _a.import)); delete importConfig.import; delete importConfig.export; // dummyConfig.json is in the same directory as this file const configFilePath = path.join(__dirname, 'dummyConfig.json'); const cmd = ['-c', configFilePath]; if (importConfig.destination_alias) { cmd.push('-a', importConfig.destination_alias); cli_utilities_1.log.debug(`Using destination alias: ${importConfig.destination_alias}`, this.config.cloneContext); } if (!importConfig.data && importConfig.sourceStackBranch && importConfig.pathDir) { const dataPath = path.join(importConfig.pathDir, importConfig.sourceStackBranch); cmd.push('-d', dataPath); cli_utilities_1.log.debug(`Import data path: ${dataPath}`, this.config.cloneContext); } if (importConfig.targetStackBranch) { cmd.push('--branch', importConfig.targetStackBranch); cli_utilities_1.log.debug(`Using target branch: ${importConfig.targetStackBranch}`, this.config.cloneContext); } if (importConfig.importWebhookStatus) { cmd.push('--import-webhook-status', importConfig.importWebhookStatus); cli_utilities_1.log.debug(`Import webhook status: ${importConfig.importWebhookStatus}`, this.config.cloneContext); } if (importConfig.skipAudit) { cmd.push('--skip-audit'); cli_utilities_1.log.debug('Skip audit flag enabled', this.config.cloneContext); } if (importConfig.forceStopMarketplaceAppsPrompt) { cmd.push('-y'); cli_utilities_1.log.debug('Force stop marketplace apps prompt enabled', this.config.cloneContext); } cli_utilities_1.log.debug(`Writing import config to: ${configFilePath}`, this.config.cloneContext); fs.writeFileSync(configFilePath, JSON.stringify(importConfig)); cli_utilities_1.log.debug('Import command prepared', Object.assign(Object.assign({}, this.config.cloneContext), { cmd: cmd.join(' '), targetStack: importConfig.target_stack, targetBranch: importConfig.targetStackBranch, dataPath: importConfig.data || (importConfig.pathDir && importConfig.sourceStackBranch ? path.join(importConfig.pathDir, importConfig.sourceStackBranch) : undefined) })); cli_utilities_1.log.debug('Running import command', Object.assign(Object.assign({}, this.config.cloneContext), { cmd })); const importData = cli_cm_import_1.default.run(cmd); importData.then(() => { cli_utilities_1.log.debug('Import command completed successfully', this.config.cloneContext); cli_utilities_1.log.debug('Clearing import config file', this.config.cloneContext); fs.writeFileSync(configFilePath, JSON.stringify({})); resolve(); }).catch((error) => { cli_utilities_1.log.error('Import command failed', Object.assign(Object.assign({}, this.config.cloneContext), { error })); throw error; }); }); } setCreateNewStackPrompt(createNewStackPrompt) { this.createNewStackPrompt = createNewStackPrompt; } async setBranch() { if (!this.config.sourceStackBranch) { try { const branches = await this.client .stack({ api_key: this.config.source_stack }) .branch() .query() .find() .catch((_err) => { }); if (branches && branches.items && branches.items.length) { this.config.sourceStackBranch = 'main'; } } catch (_error) { // Ignore error } } } getNewStackPromptResult() { return new Promise((resolve) => { prompt.get({ properties: { name: { description: safe_1.default.white(this.stackNamePrompt.message), default: safe_1.default.grey(this.stackNamePrompt.default) }, }, }, function (_, result) { if (prompt.stopped) { prompt.stopped = false; resolve(undefined); } else { let _name = result.name.replace(/\[\d+m/g, ''); _name = _name.replace(//g, ''); resolve({ stack: _name }); } }); }); } async createNewStack(options) { return new Promise(async (resolve, reject) => { try { const { orgUid } = options; cli_utilities_1.log.debug('Creating new stack', Object.assign(Object.assign({}, this.config.cloneContext), { orgUid, masterLocale: this.master_locale, stackName: this.config.stackName })); this.displayBackOptionMessage(); let inputvalue; if (!this.config.stackName) { cli_utilities_1.log.debug('Stack name not provided, prompting user', this.config.cloneContext); prompt.start(); prompt.message = ''; this.setCreateNewStackPrompt(prompt); inputvalue = await this.getNewStackPromptResult(); this.setCreateNewStackPrompt(null); } else { inputvalue = { stack: this.config.stackName }; } if (this.executingCommand === 0 || !inputvalue) { cli_utilities_1.log.debug('Stack creation cancelled or invalid input', this.config.cloneContext); return reject(); } let stack = { name: inputvalue.stack, master_locale: this.master_locale }; cli_utilities_1.log.debug('Creating stack with configuration', this.config.cloneContext); const spinner = (0, ora_1.default)('Creating New stack').start(); cli_utilities_1.log.debug('Sending stack creation API request', this.config.cloneContext); const newStack = this.client.stack().create({ stack }, { organization_uid: orgUid }); newStack .then((result) => { cli_utilities_1.log.debug('Stack created successfully', Object.assign(Object.assign({}, this.config.cloneContext), { stackName: result.name })); spinner.succeed('New Stack created Successfully name as ' + result.name); this.config.target_stack = result.api_key; this.config.destinationStackName = result.name; cli_utilities_1.log.debug('Target stack configuration updated', this.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); } }); } async cloneTypeSelection() { console.clear(); return new Promise(async (resolve, reject) => { try { cli_utilities_1.log.debug('Starting clone type selection', this.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 = {}; // Resolve path to package root - go up 3 levels from __dirname (core/util -> package root) const cloneTypePackageRoot = path.resolve(__dirname, '../../..'); this.config.data = path.join(cloneTypePackageRoot, 'contents', this.config.sourceStackBranch || ''); cli_utilities_1.log.debug(`Clone data directory: ${this.config.data}`, this.config.cloneContext); if (!this.config.cloneType) { cli_utilities_1.log.debug('Clone type not specified, prompting user for selection', this.config.cloneContext); selectedValue = await inquirer_1.default.prompt(cloneTypeSelection); } else { cli_utilities_1.log.debug(`Using pre-configured clone type: ${this.config.cloneType}`, this.config.cloneContext); } if (this.config.cloneType === 'a' || selectedValue.type === 'Structure (all modules except entries & assets)') { this.config.modules = constants_1.STRUCTURE_LIST; successMsg = 'Stack clone Structure completed'; cli_utilities_1.log.debug(`Clone type: Structure only. Modules to clone: ${constants_1.STRUCTURE_LIST.join(', ')}`, this.config.cloneContext); } else { successMsg = 'Stack clone completed with structure and content'; cli_utilities_1.log.debug('Clone type: Structure with content (all modules)', this.config.cloneContext); } this.cmdImport() .then(async () => { cli_utilities_1.log.debug('Clone type selection and import completed successfully', this.config.cloneContext); // Clean up contents directory after import completes if (this.config.pathDir) { const resolvedPath = path.resolve(this.config.pathDir); cli_utilities_1.log.debug('Cleaning up contents directory after import', Object.assign(Object.assign({}, this.config.cloneContext), { pathDir: this.config.pathDir, resolvedPath })); try { await (0, rimraf_1.rimraf)(resolvedPath); cli_utilities_1.log.debug('Contents directory cleaned up successfully', Object.assign(Object.assign({}, this.config.cloneContext), { pathDir: this.config.pathDir, resolvedPath })); } catch (cleanupError) { cli_utilities_1.log.debug('Cleanup error (non-fatal)', Object.assign(Object.assign({}, this.config.cloneContext), { pathDir: this.config.pathDir, resolvedPath, error: cleanupError === null || cleanupError === void 0 ? void 0 : cleanupError.message, code: cleanupError === null || cleanupError === void 0 ? void 0 : cleanupError.code })); // Don't fail the clone if cleanup fails } } else { cli_utilities_1.log.debug('No pathDir configured, skipping cleanup', this.config.cloneContext); } resolve(successMsg); }) .catch(reject); } catch (error) { reject(error); } }); } } exports.CloneHandler = CloneHandler;