@contentstack/cli-cm-clone 1.0.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@ $ npm install -g @contentstack/cli-cm-clone
15
15
  $ csdx COMMAND
16
16
  running command...
17
17
  $ csdx (-v|--version|version)
18
- @contentstack/cli-cm-clone/0.1.0-beta-1 darwin-x64 node-v13.14.0
18
+ @contentstack/cli-cm-clone/1.1.2 darwin-arm64 node-v18.11.0
19
19
  $ csdx --help [COMMAND]
20
20
  USAGE
21
21
  $ csdx COMMAND
@@ -33,23 +33,63 @@ USAGE
33
33
  ```
34
34
  # Commands
35
35
  <!-- commands -->
36
- * [`csdx cm:stacks:clone`](#csdx-cmstack-clone)
36
+ * [`csdx cm:stacks:clone [--source-branch <value>] [--target-branch <value>] [--source-management-token-alias <value>] [--destination-management-token-alias <value>] [-n <value>] [--type a|b] [--source-stack-api-key <value>] [--destination-stack-api-key <value>] [--import-webhook-status disable|current]`](#csdx-cmstacksclone---source-branch-value---target-branch-value---source-management-token-alias-value---destination-management-token-alias-value--n-value---type-ab---source-stack-api-key-value---destination-stack-api-key-value---import-webhook-status-disablecurrent)
37
37
 
38
- ## `csdx cm:stacks:clone`
38
+ ## `csdx cm:stacks:clone [--source-branch <value>] [--target-branch <value>] [--source-management-token-alias <value>] [--destination-management-token-alias <value>] [-n <value>] [--type a|b] [--source-stack-api-key <value>] [--destination-stack-api-key <value>] [--import-webhook-status disable|current]`
39
39
 
40
- This command allows you to migrate data (structure or content or both) from one stack to another stack (either new or existing)
40
+ Clone data (structure/content or both) of a stack into another stack
41
41
 
42
42
  ```
43
43
  USAGE
44
- $ csdx cm:stacks:clone
44
+ $ csdx cm:stacks:clone [--source-branch <value>] [--target-branch <value>] [--source-management-token-alias <value>]
45
+ [--destination-management-token-alias <value>] [-n <value>] [--type a|b] [--source-stack-api-key <value>]
46
+ [--destination-stack-api-key <value>] [--import-webhook-status disable|current]
47
+
48
+ OPTIONS
49
+ -n, --stack-name=stack-name Name for the new stack to store the cloned
50
+ content.
51
+
52
+ -y, --yes [optional] Override marketplace prompts
53
+
54
+ --destination-management-token-alias=destination-management-token-alias Source API key of the target stack token
55
+ alias.
56
+
57
+ --destination-stack-api-key=destination-stack-api-key Destination stack API Key
58
+
59
+ --import-webhook-status=disable|current [default: disable] Webhook state
60
+
61
+ --source-branch=source-branch Branch of the source stack.
62
+
63
+ --source-management-token-alias=source-management-token-alias Source API key of the target stack token
64
+ alias.
65
+
66
+ --source-stack-api-key=source-stack-api-key Source stack API Key
67
+
68
+ --target-branch=target-branch Branch of the target stack.
69
+
70
+ --type=a|b Type of data to clone
71
+ a) Structure (all modules except entries &
72
+ assets)
73
+ b) Structure with content (all modules
74
+ including entries & assets)
45
75
 
46
76
  DESCRIPTION
47
- ...
48
- Use this plugin to automate the process of cloning a stack in a few steps.
77
+ Use this plugin to automate the process of cloning a stack in few steps.
78
+
79
+ ALIASES
80
+ $ csdx cm:stack-clone
49
81
 
50
- EXAMPLE
82
+ EXAMPLES
51
83
  csdx cm:stacks:clone
84
+ csdx cm:stacks:clone --source-branch <source-branch-name> --target-branch <target-branch-name> --yes
85
+ csdx cm:stacks:clone --source-stack-api-key <apiKey> --destination-stack-api-key <apiKey>
86
+ csdx cm:stacks:clone --source-management-token-alias <management token alias> --destination-management-token-alias
87
+ <management token alias>
88
+ csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias>
89
+ --destination-management-token-alias <management token alias>
90
+ csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias>
91
+ --destination-management-token-alias <management token alias> --type <value a or b>
52
92
  ```
53
93
 
54
- _See code: [src/commands/cm/stacks/clone.js](https://github.com/contentstack/cli/blob/v0.1.0-beta-1/src/commands/cm/stack-clone.js)_
94
+ _See code: [src/commands/cm/stacks/clone.js](https://github.com/contentstack/cli/blob/main/packages/contentstack-clone/src/commands/cm/stacks/clone.js)_
55
95
  <!-- commandsstop -->
@@ -0,0 +1 @@
1
+ {"version":"1.1.2","commands":{"cm:stacks:clone":{"id":"cm:stacks:clone","description":"Clone data (structure/content or both) of a stack into another stack\nUse this plugin to automate the process of cloning a stack in few steps.\n","usage":"cm:stacks:clone [--source-branch <value>] [--target-branch <value>] [--source-management-token-alias <value>] [--destination-management-token-alias <value>] [-n <value>] [--type a|b] [--source-stack-api-key <value>] [--destination-stack-api-key <value>] [--import-webhook-status disable|current]","pluginName":"@contentstack/cli-cm-clone","pluginType":"core","aliases":["cm:stack-clone"],"examples":["csdx cm:stacks:clone","csdx cm:stacks:clone --source-branch <source-branch-name> --target-branch <target-branch-name> --yes","csdx cm:stacks:clone --source-stack-api-key <apiKey> --destination-stack-api-key <apiKey>","csdx cm:stacks:clone --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>","csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>","csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias> --type <value a or b>"],"flags":{"source-branch":{"name":"source-branch","type":"option","description":"Branch of the source stack.","required":false},"target-branch":{"name":"target-branch","type":"option","description":"Branch of the target stack.","required":false},"source-management-token-alias":{"name":"source-management-token-alias","type":"option","description":"Source API key of the target stack token alias.","required":false},"destination-management-token-alias":{"name":"destination-management-token-alias","type":"option","description":"Source API key of the target stack token alias.","required":false},"stack-name":{"name":"stack-name","type":"option","char":"n","description":"Name for the new stack to store the cloned content.","required":false},"type":{"name":"type","type":"option","description":"Type of data to clone\na) Structure (all modules except entries & assets)\nb) Structure with content (all modules including entries & assets)\n ","required":false,"options":["a","b"]},"source-stack-api-key":{"name":"source-stack-api-key","type":"option","description":"Source stack API Key"},"destination-stack-api-key":{"name":"destination-stack-api-key","type":"option","description":"Destination stack API Key"},"import-webhook-status":{"name":"import-webhook-status","type":"option","description":"Webhook state","required":false,"options":["disable","current"],"default":"disable"},"yes":{"name":"yes","type":"boolean","char":"y","description":"[optional] Override marketplace prompts","required":false,"allowNo":false}},"args":[]}}}
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
1
  {
2
2
  "name": "@contentstack/cli-cm-clone",
3
3
  "description": "Contentstack stack clone plugin",
4
- "version": "1.0.0",
4
+ "version": "1.1.2",
5
5
  "author": "Contentstack",
6
6
  "bugs": "https://github.com/rohitmishra209/cli-cm-clone/issues",
7
7
  "dependencies": {
8
- "@contentstack/cli-cm-export": "^1.0.0",
9
- "@contentstack/cli-cm-import": "^1.0.0",
10
- "@contentstack/cli-command": "^1.0.0",
8
+ "@contentstack/cli-cm-export": "^1.2.0",
9
+ "@contentstack/cli-cm-import": "^1.2.0",
10
+ "@contentstack/cli-command": "^1.0.2",
11
+ "@contentstack/cli-utilities": "^1.0.3",
11
12
  "@contentstack/management": "^1.3.0",
12
- "@contentstack/cli-utilities": "^1.0.0",
13
13
  "@oclif/command": "^1.8.16",
14
14
  "@oclif/config": "^1.18.3",
15
+ "chalk": "^4.1.0",
15
16
  "async": "^3.2.4",
16
17
  "child_process": "^1.0.2",
17
18
  "fancy-test": "^1.4.10",
18
- "inquirer": "^7.3.3",
19
+ "inquirer": "^8.2.4",
19
20
  "ora": "^5.1.0",
20
21
  "rimraf": "^3.0.2",
21
22
  "winston": "^3.7.2"
@@ -53,11 +54,15 @@
53
54
  "bin": "csdx",
54
55
  "devPlugins": [
55
56
  "@oclif/plugin-help"
56
- ]
57
+ ],
58
+ "repositoryPrefix": "<%- repo %>/blob/main/packages/contentstack-clone/<%- commandPath %>"
57
59
  },
58
60
  "repository": "https://github.com/contentstack/cli",
59
61
  "scripts": {
60
- "test": "nyc --reporter=html mocha \"test/**/*.test.js\"",
62
+ "postpack": "rm -f oclif.manifest.json",
63
+ "prepack": "oclif-dev manifest && oclif-dev readme",
64
+ "test": "nyc --reporter=html mocha --forbid-only \"test/**/*.test.js\"",
65
+ "posttest": "eslint .",
61
66
  "version": "oclif-dev readme && git add README.md"
62
67
  },
63
68
  "csdxConfig": {
@@ -14,6 +14,7 @@ class StackCloneCommand extends Command {
14
14
  let _authToken = configHandler.get('authtoken');
15
15
  const cloneCommandFlags = self.parse(StackCloneCommand).flags;
16
16
  const {
17
+ yes,
17
18
  type: cloneType,
18
19
  'stack-name': stackName,
19
20
  'source-branch': sourceStackBranch,
@@ -28,6 +29,8 @@ class StackCloneCommand extends Command {
28
29
  const handleClone = async () => {
29
30
  const listOfTokens = configHandler.get('tokens');
30
31
 
32
+ config.forceMarketplaceAppsImport = yes;
33
+
31
34
  if (cloneType) {
32
35
  config.cloneType = cloneType;
33
36
  }
@@ -70,10 +73,9 @@ class StackCloneCommand extends Command {
70
73
  config.auth_token = _authToken;
71
74
  config.host = this.cmaHost;
72
75
  config.cdn = this.cdaHost;
76
+ config.pathDir = pathdir;
73
77
  const cloneHandler = new CloneHandler(config);
74
- await cloneHandler.start();
75
- let successMessage = 'Stack cloning process have been completed successfully';
76
- await this.cleanUp(pathdir, successMessage);
78
+ cloneHandler.execute().catch();
77
79
  };
78
80
 
79
81
  if (sourceManagementTokenAlias && destinationManagementTokenAlias) {
@@ -94,9 +96,11 @@ class StackCloneCommand extends Command {
94
96
  this.exit(1);
95
97
  }
96
98
  } catch (error) {
97
- await this.cleanUp(pathdir);
98
- // eslint-disable-next-line no-console
99
- console.log(error.message || error);
99
+ if (error) {
100
+ await this.cleanUp(pathdir);
101
+ // eslint-disable-next-line no-console
102
+ console.log(error.message || error);
103
+ }
100
104
  }
101
105
  }
102
106
 
@@ -142,25 +146,27 @@ class StackCloneCommand extends Command {
142
146
  const interrupt = ['SIGINT', 'SIGQUIT', 'SIGTERM'];
143
147
  const exceptions = ['unhandledRejection', 'uncaughtException'];
144
148
 
145
- const cleanUp = async (exitOrError = null) => {
146
- // eslint-disable-next-line no-console
147
- console.log('\nCleaning up');
148
- await this.cleanUp(pathDir);
149
- // eslint-disable-next-line no-console
150
- console.log('done');
151
- // eslint-disable-next-line no-process-exit
152
-
153
- if (exitOrError instanceof Promise) {
154
- exitOrError.catch((error) => {
155
- console.log((error && error.message) || '');
156
- });
157
- } else if (exitOrError && exitOrError.message) {
158
- console.log(exitOrError.message);
159
- } else if (exitOrError && exitOrError.errorMessage) {
160
- console.log(exitOrError.message);
161
- }
149
+ const cleanUp = async (exitOrError) => {
150
+ if (exitOrError) {
151
+ // eslint-disable-next-line no-console
152
+ console.log('\nCleaning up');
153
+ await this.cleanUp(pathDir);
154
+ // eslint-disable-next-line no-console
155
+ console.log('done');
156
+ // eslint-disable-next-line no-process-exit
157
+
158
+ if (exitOrError instanceof Promise) {
159
+ exitOrError.catch((error) => {
160
+ console.log((error && error.message) || '');
161
+ });
162
+ } else if (exitOrError.message) {
163
+ console.log(exitOrError.message);
164
+ } else if (exitOrError.errorMessage) {
165
+ console.log(exitOrError.message);
166
+ }
162
167
 
163
- if (exitOrError === true) process.exit();
168
+ if (exitOrError === true) process.exit();
169
+ }
164
170
  };
165
171
 
166
172
  exceptions.forEach((event) => process.on(event, cleanUp));
@@ -174,7 +180,7 @@ Use this plugin to automate the process of cloning a stack in few steps.
174
180
 
175
181
  StackCloneCommand.examples = [
176
182
  'csdx cm:stacks:clone',
177
- 'csdx cm:stacks:clone --source-branch <source-branch-name> --target-branch <target-branch-name>',
183
+ 'csdx cm:stacks:clone --source-branch <source-branch-name> --target-branch <target-branch-name> --yes',
178
184
  'csdx cm:stacks:clone --source-stack-api-key <apiKey> --destination-stack-api-key <apiKey>',
179
185
  'csdx cm:stacks:clone --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>',
180
186
  'csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>',
@@ -231,6 +237,11 @@ b) Structure with content (all modules including entries & assets)
231
237
  required: false,
232
238
  default: 'disable',
233
239
  }),
240
+ yes: flags.boolean({
241
+ char: 'y',
242
+ required: false,
243
+ description: '[optional] Override marketplace prompts',
244
+ }),
234
245
  };
235
246
 
236
247
  StackCloneCommand.usage =
@@ -0,0 +1,67 @@
1
+ const CloneCommand = function (execute, undo, params, parentContext) {
2
+ this.execute = execute.bind(parentContext);
3
+ this.undo = undo && undo.bind(parentContext);
4
+ this.params = params;
5
+ };
6
+
7
+ const HandleOrgCommand = function (params, parentContext) {
8
+ return new CloneCommand(parentContext.handleOrgSelection, null, params, parentContext);
9
+ };
10
+
11
+ const HandleStackCommand = function (params, parentContext) {
12
+ return new CloneCommand(parentContext.handleStackSelection, parentContext.execute, params, parentContext);
13
+ };
14
+
15
+ const HandleBranchCommand = function (params, parentContext) {
16
+ return new CloneCommand(parentContext.handleBranchSelection, parentContext.execute, params, parentContext);
17
+ };
18
+
19
+ const HandleDestinationStackCommand = function (params, parentContext) {
20
+ return new CloneCommand(parentContext.handleStackSelection, parentContext.executeDestination, params, parentContext);
21
+ };
22
+
23
+ const HandleExportCommand = function (params, parentContext) {
24
+ return new CloneCommand(parentContext.cmdExport, null, params, parentContext);
25
+ };
26
+
27
+ const SetBranchCommand = function (params, parentContext) {
28
+ return new CloneCommand(parentContext.setBranch, null, params, parentContext);
29
+ };
30
+
31
+ const CreateNewStackCommand = function (params, parentContext) {
32
+ return new CloneCommand(parentContext.createNewStack, parentContext.executeDestination, params, parentContext);
33
+ };
34
+
35
+ const CloneTypeSelectionCommand = function (params, parentContext) {
36
+ return new CloneCommand(parentContext.cloneTypeSelection, null, params, parentContext);
37
+ };
38
+
39
+ const Clone = function () {
40
+ const commands = [];
41
+
42
+ return {
43
+ execute: async function (command) {
44
+ commands.push(command);
45
+ const result = await command.execute(command.params);
46
+ return result;
47
+ },
48
+ undo: async function () {
49
+ if (commands.length) {
50
+ const command = commands.pop();
51
+ command.undo && await command.undo(command.params);
52
+ }
53
+ },
54
+ };
55
+ };
56
+
57
+ module.exports = {
58
+ HandleOrgCommand,
59
+ HandleStackCommand,
60
+ HandleBranchCommand,
61
+ HandleDestinationStackCommand,
62
+ HandleExportCommand,
63
+ SetBranchCommand,
64
+ CreateNewStackCommand,
65
+ CloneTypeSelectionCommand,
66
+ Clone,
67
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('events');
4
+
5
+ class CustomAbortSignal {
6
+ constructor() {
7
+ this.eventEmitter = new EventEmitter();
8
+ this.onabort = null;
9
+ this.aborted = false;
10
+ }
11
+ toString() {
12
+ return '[object CustomAbortSignal]';
13
+ }
14
+ get [Symbol.toStringTag]() {
15
+ return 'CustomAbortSignal';
16
+ }
17
+ removeEventListener(name, handler) {
18
+ this.eventEmitter.removeListener(name, handler);
19
+ }
20
+ addEventListener(name, handler) {
21
+ this.eventEmitter.on(name, handler);
22
+ }
23
+ dispatchEvent(type) {
24
+ const event = { type, target: this };
25
+ const handlerName = `on${type}`;
26
+
27
+ if (typeof this[handlerName] === 'function') this[handlerName](event);
28
+ }
29
+ }
30
+
31
+ class CustomAbortController {
32
+ constructor() {
33
+ this.signal = new CustomAbortSignal();
34
+ }
35
+ abort() {
36
+ if (this.signal.aborted) return;
37
+
38
+ this.signal.aborted = true;
39
+ this.signal.dispatchEvent('abort');
40
+ }
41
+ toString() {
42
+ return '[object CustomAbortController]';
43
+ }
44
+ get [Symbol.toStringTag]() {
45
+ return 'CustomAbortController';
46
+ }
47
+ }
48
+
49
+ module.exports = { CustomAbortController, CustomAbortSignal };
@@ -1,12 +1,24 @@
1
1
  const ora = require('ora');
2
2
  const path = require('path');
3
3
  const inquirer = require('inquirer');
4
+ const rimraf = require('rimraf');
5
+ const chalk = require('chalk');
4
6
 
5
- let sdkInstance = require('../../lib/util/contentstack-management-sdk');
6
7
  let exportCmd = require('@contentstack/cli-cm-export');
7
8
  let importCmd = require('@contentstack/cli-cm-import');
9
+ const { HttpClient } = require('@contentstack/cli-utilities');
10
+ let sdkInstance = require('../../lib/util/contentstack-management-sdk');
11
+ const defaultConfig = require('@contentstack/cli-cm-export/src/config/default');
12
+ const { CustomAbortController } = require('./abort-controller');
13
+
14
+ const {
15
+ HandleOrgCommand, HandleStackCommand, HandleDestinationStackCommand, HandleExportCommand,
16
+ SetBranchCommand, CreateNewStackCommand, CloneTypeSelectionCommand, Clone, HandleBranchCommand
17
+ } = require('../helpers/command-helpers');
18
+
8
19
  let client = {};
9
20
  let config;
21
+ let cloneCommand;
10
22
 
11
23
  let stackCreationConfirmation = [
12
24
  {
@@ -32,6 +44,7 @@ let structureList = [
32
44
  'locales',
33
45
  'environments',
34
46
  'extensions',
47
+ 'marketplace-apps',
35
48
  'webhooks',
36
49
  'global-fields',
37
50
  'content-types',
@@ -44,9 +57,12 @@ class CloneHandler {
44
57
  constructor(opt) {
45
58
  config = opt;
46
59
  client = sdkInstance.Client(config);
60
+ cloneCommand = new Clone();
61
+ this.pathDir = opt.pathDir;
62
+ process.stdin.setMaxListeners(50);
47
63
  }
48
64
 
49
- #handleOrgSelection(options = {}) {
65
+ handleOrgSelection(options = {}) {
50
66
  return new Promise(async (resolve, reject) => {
51
67
  const { msg = '', isSource = true } = options || {};
52
68
  const orgList = await this.getOrganizationChoices(msg).catch(reject);
@@ -65,113 +81,257 @@ class CloneHandler {
65
81
  });
66
82
  }
67
83
 
68
- #handleStackSelection(options = {}) {
84
+ handleStackSelection(options = {}) {
85
+ let keyPressHandler;
69
86
  return new Promise(async (resolve, reject) => {
70
- const { org = {}, msg = '', isSource = true } = options || {};
71
- const stackList = await this.getStack(org, msg, isSource).catch(reject);
87
+ try {
88
+ const { org = {}, msg = '', isSource = true, stackAbortController } = options || {}
89
+
90
+ keyPressHandler = async function (_ch, key) {
91
+ if (key.name === 'left' && key.shift) {
92
+ stackAbortController.abort();
93
+ console.clear();
94
+ process.stdin.removeListener('keypress', keyPressHandler);
95
+ await cloneCommand.undo();
96
+ }
97
+ };
98
+ process.stdin.addListener('keypress', keyPressHandler);
72
99
 
73
- if (stackList) {
74
- const selectedStack = await inquirer.prompt(stackList);
100
+ const stackList = await this.getStack(org, msg, isSource).catch(reject)
75
101
 
76
- if (isSource) {
77
- config.sourceStackName = selectedStack.stack;
78
- master_locale = masterLocaleList[selectedStack.stack];
79
- config.source_stack = stackUidList[selectedStack.stack];
80
- } else {
81
- config.target_stack = stackUidList[selectedStack.stack];
82
- config.destinationStackName = selectedStack.stack;
83
- }
102
+ if (stackList) {
103
+ const ui = new inquirer.ui.BottomBar();
104
+ ui.updateBottomBar(chalk.cyan('\nPress shift & left arrow together to undo the operation\n'));
84
105
 
85
- resolve(selectedStack);
106
+ const selectedStack = await inquirer.prompt(stackList);
107
+
108
+ if (stackAbortController.signal.aborted) {
109
+ return reject();
110
+ }
111
+ if (isSource) {
112
+ config.sourceStackName = selectedStack.stack;
113
+ master_locale = masterLocaleList[selectedStack.stack];
114
+ config.source_stack = stackUidList[selectedStack.stack];
115
+ } else {
116
+ config.target_stack = stackUidList[selectedStack.stack];
117
+ config.destinationStackName = selectedStack.stack;
118
+ }
119
+
120
+ resolve(selectedStack)
121
+ }
122
+ } catch (error) {
123
+ return reject(error);
124
+ } finally {
125
+ if (keyPressHandler) {
126
+ process.stdin.removeListener('keypress', keyPressHandler);
127
+ }
86
128
  }
87
129
  });
88
130
  }
89
131
 
90
- start() {
132
+ handleBranchSelection = async (options) => {
133
+ const { api_key, isSource = true, returnBranch = false } = options
134
+ const baseUrl = defaultConfig.host.startsWith('http')
135
+ ? defaultConfig.host
136
+ : `https://${defaultConfig.host}/v3`;
137
+
91
138
  return new Promise(async (resolve, reject) => {
92
- let sourceStack = {};
93
- const handleOrgAndStackSelection = (orgMsg, stackMsg, isSource = true) => {
94
- return new Promise(async (_resolve) => {
95
- const org = await this.#handleOrgSelection({ msg: orgMsg, isSource }).catch((error) =>
96
- reject(error.errorMessage),
97
- );
139
+ try {
140
+ const headers = { api_key }
98
141
 
99
- if (org) {
100
- await this.#handleStackSelection({
101
- org,
102
- isSource,
103
- msg: stackMsg,
104
- })
105
- .then(_resolve)
106
- .catch((error) => reject(error.errorMessage));
107
- }
108
- });
109
- };
142
+ if (config.auth_token) {
143
+ headers['authtoken'] = config.auth_token
144
+ } else if (config.management_token) {
145
+ headers['authorization'] = config.management_token
146
+ }
110
147
 
111
- if (!config.source_stack) {
112
- // NOTE Export section
113
- sourceStack = await handleOrgAndStackSelection(
114
- 'Choose an organization where your source stack exists:',
115
- 'Select the source stack',
116
- );
117
- }
148
+ // NOTE validate if source branch is exist
149
+ if (isSource && config.sourceStackBranch) {
150
+ await this.validateIfBranchExist(headers, true)
151
+ return resolve()
152
+ }
118
153
 
119
- if (config.source_stack) {
120
- stackName.default = config.stackName || `Copy of ${sourceStack.stack || config.source_alias}`;
121
- const exportRes = await this.cmdExport().catch(reject);
154
+ // NOTE Validate target branch is exist
155
+ if (!isSource && config.targetStackBranch) {
156
+ await this.validateIfBranchExist(headers, false)
157
+ return resolve()
158
+ }
122
159
 
123
- if (!config.sourceStackBranch) {
124
- try {
125
- const branches = await client.stack({ api_key: config.source_stack }).branch().query().find();
160
+ const spinner = ora('Fetching Branches').start();
161
+ const result = await new HttpClient()
162
+ .headers(headers)
163
+ .get(`${baseUrl}/stacks/branches`)
164
+ .then(({ data: { branches } }) => branches)
165
+
166
+ const condition = (
167
+ result &&
168
+ Array.isArray(result) &&
169
+ result.length > 0
170
+ )
171
+
172
+ // NOTE if want to get only list of branches (Pass param -> returnBranch = true )
173
+ if (returnBranch) {
174
+ resolve(condition ? result : [])
175
+ } else {
176
+ // NOTE list options to use to select branch
177
+ if (condition) {
178
+ spinner.succeed('Fetched Branches');
179
+ const { branch } = await inquirer.prompt({
180
+ type: 'list',
181
+ name: 'branch',
182
+ message: 'Choose a branch',
183
+ choices: result.map(row => row.uid),
184
+ });
126
185
 
127
- if (branches && branches.items && branches.items.length) {
128
- config.sourceStackBranch = 'main';
186
+ if (isSource) {
187
+ config.sourceStackBranch = branch
188
+ } else {
189
+ config.targetStackBranch = branch
129
190
  }
130
- } catch (_error) {}
191
+ } else {
192
+ spinner.succeed('No branches found.!');
193
+ }
194
+
195
+ resolve()
131
196
  }
197
+ } catch (e) {
198
+ spinner.fail();
199
+ console.log(e && e.message)
200
+ resolve()
201
+ }
202
+ })
203
+ }
132
204
 
133
- // NOTE Import section
134
- if (exportRes) {
135
- let canCreateStack = false;
205
+ async validateIfBranchExist(headers, isSource) {
206
+ const branch = isSource ? config.sourceStackBranch : config.targetStackBranch
207
+ const spinner = ora(`Validation if ${isSource ? 'source' : 'target'} branch exist.!`).start();
208
+ const isBranchExist = await HttpClient.create()
209
+ .headers(headers)
210
+ .get(`https://${config.host}/v3/stacks/branches/${branch}`)
211
+ .then(({ data }) => data);
212
+
213
+ const completeSpinner = (msg, method = 'succeed') => {
214
+ spinner[method](msg)
215
+ spinner.stop()
216
+ }
217
+
218
+ if (isBranchExist && typeof isBranchExist === 'object' && typeof isBranchExist.branch === 'object') {
219
+ completeSpinner(`${isSource ? 'Source' : 'Target'} branch verified.!`)
220
+ } else {
221
+ completeSpinner(`${isSource ? 'Source' : 'Target'} branch not found.!`, 'fail')
222
+ process.exit()
223
+ }
224
+ }
136
225
 
137
- if (!config.target_stack) {
138
- canCreateStack = await inquirer.prompt(stackCreationConfirmation);
139
- }
226
+ execute() {
227
+ return new Promise(async (resolve, reject) => {
228
+ let stackAbortController;
140
229
 
141
- if (canCreateStack.stackCreate !== true) {
142
- if (!config.target_stack) {
143
- await handleOrgAndStackSelection(
144
- 'Choose an organization where the destination stack exists: ',
145
- 'Choose the destination stack:',
146
- false,
230
+ try {
231
+ if (!config.source_stack) {
232
+ const orgMsg = 'Choose an organization where your source stack exists:';
233
+ const stackMsg = 'Select the source stack';
234
+
235
+ stackAbortController = new CustomAbortController();
236
+
237
+ const org = await cloneCommand.execute(new HandleOrgCommand({ msg: orgMsg, isSource: true }, this));
238
+ if (org) {
239
+ const sourceStack = await cloneCommand.execute(new HandleStackCommand({ org, isSource: true, msg: stackMsg, stackAbortController }, this));
240
+
241
+ if (config.source_stack) {
242
+ await cloneCommand.execute(
243
+ new HandleBranchCommand({ api_key: config.source_stack }, this)
147
244
  );
148
245
  }
149
246
 
150
- if (config.target_stack) {
151
- this.cloneTypeSelection()
152
- .then(resolve)
153
- .catch((error) => reject(error.errorMessage));
247
+ if (stackAbortController.signal.aborted) {
248
+ return reject();
154
249
  }
250
+ stackName.default = config.stackName || `Copy of ${sourceStack.stack || config.source_alias}`;
155
251
  } else {
156
- const destinationOrg = await this.#handleOrgSelection({
157
- isSource: false,
158
- msg: 'Choose an organization where you want to create a stack: ',
159
- }).catch((error) => reject(error.errorMessage));
160
- const orgUid = orgUidList[destinationOrg.Organization];
161
- await this.createNewStack(orgUid).catch((error) => {
162
- return reject(error.errorMessage + ' Contact the Organization owner for Stack Creation access.');
163
- });
252
+ return reject('Org not found.');
253
+ }
254
+ }
255
+ const exportRes = await cloneCommand.execute(new HandleExportCommand(null, this));
256
+ await cloneCommand.execute(new SetBranchCommand(null, this));
257
+
258
+ if (exportRes) {
259
+ this.executeDestination().catch(() => { reject(); });
260
+ }
261
+ return resolve();
262
+ } catch (error) {
263
+ return reject(error);
264
+ } finally {
265
+ if (stackAbortController) {
266
+ stackAbortController.abort();
267
+ }
268
+ }
269
+ });
270
+ }
271
+
272
+ async executeDestination() {
273
+ return new Promise(async (resolve, reject) => {
274
+ let stackAbortController;
275
+ try {
276
+ stackAbortController = new CustomAbortController();
164
277
 
165
- if (config.target_stack) {
166
- this.cloneTypeSelection().then(resolve).catch(reject);
278
+ let canCreateStack = false;
279
+
280
+ if (!config.target_stack) {
281
+ canCreateStack = await inquirer.prompt(stackCreationConfirmation);
282
+ }
283
+
284
+ if (!canCreateStack.stackCreate) {
285
+ if (!config.target_stack) {
286
+ const orgMsg = 'Choose an organization where the destination stack exists: ';
287
+ const org = await cloneCommand.execute(new HandleOrgCommand({ msg: orgMsg }, this));
288
+
289
+ if (org) {
290
+ const stackMsg = 'Choose the destination stack:';
291
+ await cloneCommand.execute(new HandleDestinationStackCommand({ org, msg: stackMsg, stackAbortController, isSource: false }, this));
167
292
  }
168
293
  }
294
+
295
+ // NOTE GET list of branches if branches enabled
296
+ if (config.target_stack) {
297
+ await cloneCommand.execute(new HandleBranchCommand({ isSource: false, api_key: config.target_stack }, this));
298
+ }
299
+ } else {
300
+ const orgMsg = 'Choose an organization where you want to create a stack: ';
301
+ const destinationOrg = await cloneCommand.execute(new HandleOrgCommand({ msg: orgMsg }, this));
302
+ const orgUid = orgUidList[destinationOrg.Organization];
303
+ await cloneCommand.execute(new CreateNewStackCommand({ orgUid, stackAbortController }, this));
304
+ }
305
+ await cloneCommand.execute(new CloneTypeSelectionCommand(null, this));
306
+ return resolve();
307
+ } catch (error) {
308
+ reject(error);
309
+ } finally {
310
+ // If not aborted and ran successfully
311
+ if (!stackAbortController.signal.aborted) {
312
+ // Call clean dir.
313
+ rimraf(this.pathDir, function () {
314
+ // eslint-disable-next-line no-console
315
+ console.log('Stack cloning process have been completed successfully');
316
+ });
169
317
  }
170
318
  }
171
- });
319
+ })
172
320
  }
173
321
 
174
- getOrganizationChoices = async (orgMessage) => {
322
+ async setBranch() {
323
+ if (!config.sourceStackBranch) {
324
+ try {
325
+ const branches = await client.stack({ api_key: config.source_stack }).branch().query().find();
326
+
327
+ if (branches && branches.items && branches.items.length) {
328
+ config.sourceStackBranch = 'main';
329
+ }
330
+ } catch (_error) { }
331
+ }
332
+ }
333
+
334
+ async getOrganizationChoices(orgMessage) {
175
335
  let orgChoice = {
176
336
  type: 'list',
177
337
  name: 'Organization',
@@ -183,9 +343,9 @@ class CloneHandler {
183
343
  try {
184
344
  let organizations = await client.organization().fetchAll({ limit: 100 });
185
345
  spinner.succeed('Fetched Organization');
186
- for (let i = 0; i < organizations.items.length; i++) {
187
- orgUidList[organizations.items[i].name] = organizations.items[i].uid;
188
- orgChoice.choices.push(organizations.items[i].name);
346
+ for (const element of organizations.items) {
347
+ orgUidList[element.name] = element.uid;
348
+ orgChoice.choices.push(element.name);
189
349
  }
190
350
  return resolve(orgChoice);
191
351
  } catch (e) {
@@ -195,7 +355,7 @@ class CloneHandler {
195
355
  });
196
356
  };
197
357
 
198
- getStack = async (answer, stkMessage) => {
358
+ async getStack(answer, stkMessage) {
199
359
  return new Promise(async (resolve, reject) => {
200
360
  let stackChoice = {
201
361
  type: 'list',
@@ -209,10 +369,10 @@ class CloneHandler {
209
369
  const stackList = client.stack().query({ organization_uid }).find();
210
370
  stackList
211
371
  .then((stacklist) => {
212
- for (let j = 0; j < stacklist.items.length; j++) {
213
- stackUidList[stacklist.items[j].name] = stacklist.items[j].api_key;
214
- masterLocaleList[stacklist.items[j].name] = stacklist.items[j].master_locale;
215
- stackChoice.choices.push(stacklist.items[j].name);
372
+ for (const element of stacklist.items) {
373
+ stackUidList[element.name] = element.api_key;
374
+ masterLocaleList[element.name] = element.master_locale;
375
+ stackChoice.choices.push(element.name);
216
376
  }
217
377
  spinner.succeed('Fetched stack');
218
378
  return resolve(stackChoice);
@@ -228,30 +388,63 @@ class CloneHandler {
228
388
  });
229
389
  };
230
390
 
231
- async createNewStack(orgUid) {
391
+ async createNewStack(options) {
392
+ let keyPressHandler;
232
393
  return new Promise(async (resolve, reject) => {
233
- let inputvalue;
394
+ try {
395
+ const { orgUid, stackAbortController } = options;
396
+ let inputvalue;
397
+ let uiPromise;
398
+
399
+ keyPressHandler = async function (_ch, key) {
400
+ if (key.name === 'left' && key.shift) {
401
+ stackAbortController.abort();
402
+ // We need to close the inquirer promise correctly, otherwise the unclosed question/answer text is displayed in next line.
403
+ if (uiPromise) {
404
+ uiPromise.ui.close();
405
+ }
406
+ console.clear();
407
+ process.stdin.removeListener('keypress', keyPressHandler);
408
+ await cloneCommand.undo();
409
+ }
410
+ };
411
+ process.stdin.addListener('keypress', keyPressHandler);
234
412
 
235
- if (!config.stackName) {
236
- inputvalue = await inquirer.prompt(stackName);
237
- } else {
238
- inputvalue = { stack: config.stackName };
239
- }
413
+ const ui = new inquirer.ui.BottomBar();
414
+ ui.updateBottomBar(chalk.cyan('\nPress shift & left arrow together to undo the operation\n'));
415
+
416
+ if (!config.stackName) {
417
+ uiPromise = inquirer.prompt(stackName);
418
+ inputvalue = await uiPromise;
419
+ } else {
420
+ inputvalue = { stack: config.stackName };
421
+ }
422
+
423
+ if (stackAbortController.signal.aborted) {
424
+ return reject();
425
+ }
240
426
 
241
- let stack = { name: inputvalue.stack, master_locale: master_locale };
242
- const spinner = ora('Creating New stack').start();
243
- let newStack = client.stack().create({ stack }, { organization_uid: orgUid });
244
- newStack
245
- .then((result) => {
246
- spinner.succeed('New Stack created Successfully name as ' + result.name);
247
- config.target_stack = result.api_key;
248
- config.destinationStackName = result.name;
249
- return resolve(result);
250
- })
251
- .catch((error) => {
252
- spinner.fail();
253
- return reject(error);
254
- });
427
+ let stack = { name: inputvalue.stack, master_locale: master_locale };
428
+ const spinner = ora('Creating New stack').start();
429
+ let newStack = client.stack().create({ stack }, { organization_uid: orgUid });
430
+ newStack
431
+ .then((result) => {
432
+ spinner.succeed('New Stack created Successfully name as ' + result.name);
433
+ config.target_stack = result.api_key;
434
+ config.destinationStackName = result.name;
435
+ return resolve(result);
436
+ })
437
+ .catch((error) => {
438
+ spinner.fail();
439
+ return reject(error.errorMessage + ' Contact the Organization owner for Stack Creation access.');
440
+ });
441
+ } catch (error) {
442
+ return reject(error);
443
+ } finally {
444
+ if (keyPressHandler) {
445
+ process.stdin.removeListener('keypress', keyPressHandler);
446
+ }
447
+ }
255
448
  });
256
449
  }
257
450
 
@@ -323,6 +516,8 @@ class CloneHandler {
323
516
  cmd.push('--import-webhook-status', config.importWebhookStatus);
324
517
  }
325
518
 
519
+ if (config.forceMarketplaceAppsImport) cmd.push('-y')
520
+
326
521
  await importCmd.run(cmd);
327
522
  return resolve();
328
523
  });