@contentstack/cli-cm-clone 2.0.0-beta.2 → 2.0.0-beta.4

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
@@ -16,7 +16,7 @@ $ npm install -g @contentstack/cli-cm-clone
16
16
  $ csdx COMMAND
17
17
  running command...
18
18
  $ csdx (--version)
19
- @contentstack/cli-cm-clone/2.0.0-beta.1 darwin-arm64 node-v22.14.0
19
+ @contentstack/cli-cm-clone/2.0.0-beta.4 darwin-arm64 node-v24.12.0
20
20
  $ csdx --help [COMMAND]
21
21
  USAGE
22
22
  $ csdx COMMAND
@@ -38,62 +38,6 @@ USAGE
38
38
 
39
39
  <!-- commands -->
40
40
  * [`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)
41
- * [`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)
42
-
43
- ## `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]`
44
-
45
- Clone data (structure/content or both) of a stack into another stack
46
-
47
- ```
48
- USAGE
49
- $ csdx cm:stack-clone cm:stacks:clone [--source-branch <value>] [--target-branch <value>]
50
- [--source-management-token-alias <value>] [--destination-management-token-alias <value>] [-n <value>] [--type a|b]
51
- [--source-stack-api-key <value>] [--destination-stack-api-key <value>] [--import-webhook-status disable|current]
52
-
53
- FLAGS
54
- -c, --config=<value> Path for the external configuration
55
- -n, --stack-name=<value> Provide a name for the new stack to store the cloned content.
56
- -y, --yes Force override all Marketplace prompts.
57
- --destination-management-token-alias=<value> Destination management token alias.
58
- --destination-stack-api-key=<value> Destination stack API key
59
- --import-webhook-status=<option> [default: disable] [default: disable] (optional) The status of the
60
- import webhook. <options: disable|current>
61
- <options: disable|current>
62
- --skip-audit (optional) Skips the audit fix that occurs during an import
63
- operation.
64
- --source-branch=<value> Branch of the source stack.
65
- --source-branch-alias=<value> Alias of Branch of the source stack.
66
- --source-management-token-alias=<value> Source management token alias.
67
- --source-stack-api-key=<value> Source stack API key
68
- --target-branch=<value> Branch of the target stack.
69
- --target-branch-alias=<value> Alias of Branch of the target stack.
70
- --type=<option> Type of data to clone. You can select option a or b.
71
- a) Structure (all modules except entries & assets).
72
- b) Structure with content (all modules including entries & assets).
73
-
74
- <options: a|b>
75
-
76
- DESCRIPTION
77
- Clone data (structure/content or both) of a stack into another stack
78
- Use this plugin to automate the process of cloning a stack in few steps.
79
-
80
-
81
- ALIASES
82
- $ csdx cm:stack-clone
83
-
84
- EXAMPLES
85
- $ csdx cm:stacks:clone
86
-
87
- $ csdx cm:stacks:clone --source-branch <source-branch-name> --target-branch <target-branch-name> --yes
88
-
89
- $ csdx cm:stacks:clone --source-stack-api-key <apiKey> --destination-stack-api-key <apiKey>
90
-
91
- $ csdx cm:stacks:clone --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>
92
-
93
- $ csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>
94
-
95
- $ 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>
96
- ```
97
41
 
98
42
  ## `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]`
99
43
 
@@ -133,9 +77,6 @@ DESCRIPTION
133
77
  Use this plugin to automate the process of cloning a stack in few steps.
134
78
 
135
79
 
136
- ALIASES
137
- $ csdx cm:stack-clone
138
-
139
80
  EXAMPLES
140
81
  $ csdx cm:stacks:clone
141
82
 
package/package.json CHANGED
@@ -1,27 +1,24 @@
1
1
  {
2
2
  "name": "@contentstack/cli-cm-clone",
3
3
  "description": "Contentstack stack clone plugin",
4
- "version": "2.0.0-beta.2",
4
+ "version": "2.0.0-beta.4",
5
5
  "author": "Contentstack",
6
6
  "bugs": "https://github.com/rohitmishra209/cli-cm-clone/issues",
7
7
  "dependencies": {
8
8
  "@colors/colors": "^1.6.0",
9
- "@contentstack/cli-cm-export": "~2.0.0-beta.2",
10
- "@contentstack/cli-cm-import": "~2.0.0-beta.2",
11
- "@contentstack/cli-command": "~1.6.1",
9
+ "@contentstack/cli-cm-export": "~2.0.0-beta.4",
10
+ "@contentstack/cli-cm-import": "~2.0.0-beta.3",
11
+ "@contentstack/cli-command": "~1.7.0",
12
12
  "@contentstack/cli-utilities": "~1.15.0",
13
13
  "@oclif/core": "^4.3.0",
14
14
  "@oclif/plugin-help": "^6.2.28",
15
15
  "chalk": "^4.1.2",
16
- "inquirer": "8.2.6",
17
- "inquirer-search-checkbox": "^1.0.0",
18
- "inquirer-search-list": "^1.2.6",
16
+ "inquirer": "8.2.7",
19
17
  "lodash": "^4.17.21",
20
18
  "merge": "^2.1.1",
21
19
  "ora": "^5.4.1",
22
20
  "prompt": "^1.3.0",
23
- "rimraf": "^5.0.10",
24
- "winston": "^3.17.0"
21
+ "rimraf": "^6.1.0"
25
22
  },
26
23
  "devDependencies": {
27
24
  "@oclif/test": "^4.1.13",
@@ -63,12 +60,8 @@
63
60
  "clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo"
64
61
  },
65
62
  "csdxConfig": {
66
- "expiredCommands": {
67
- "cm:stack-clone": "csdx cm:stacks:clone"
68
- },
69
63
  "shortCommandName": {
70
- "cm:stacks:clone": "CLN",
71
- "cm:stack-clone": "O-CLN"
64
+ "cm:stacks:clone": "CLN"
72
65
  }
73
66
  }
74
67
  }
@@ -1,5 +1,5 @@
1
1
  const { Command } = require('@contentstack/cli-command');
2
- const { configHandler, flags, isAuthenticated, managementSDKClient } = require('@contentstack/cli-utilities');
2
+ const { configHandler, flags, isAuthenticated, managementSDKClient, log, handleAndLogError } = require('@contentstack/cli-utilities');
3
3
  const { CloneHandler } = require('../../../lib/util/clone-handler');
4
4
  const path = require('path');
5
5
  const { rimraf } = require('rimraf');
@@ -9,6 +9,44 @@ const { readdirSync, readFileSync } = require('fs');
9
9
  let config = {};
10
10
 
11
11
  class StackCloneCommand extends Command {
12
+ /**
13
+ * Determine authentication method based on user preference
14
+ */
15
+ determineAuthenticationMethod(sourceManagementTokenAlias, destinationManagementTokenAlias) {
16
+ // Track authentication method
17
+ let authenticationMethod = 'unknown';
18
+
19
+ // Determine authentication method based on user preference
20
+ if (sourceManagementTokenAlias || destinationManagementTokenAlias) {
21
+ authenticationMethod = 'Management Token';
22
+ } else if (isAuthenticated()) {
23
+ // Check if user is authenticated via OAuth
24
+ const isOAuthUser = configHandler.get('authorisationType') === 'OAUTH' || false;
25
+ if (isOAuthUser) {
26
+ authenticationMethod = 'OAuth';
27
+ } else {
28
+ authenticationMethod = 'Basic Auth';
29
+ }
30
+ } else {
31
+ authenticationMethod = 'Basic Auth';
32
+ }
33
+
34
+ return authenticationMethod;
35
+ }
36
+
37
+ /**
38
+ * Create clone context object for logging
39
+ */
40
+ createCloneContext(authenticationMethod) {
41
+ return {
42
+ command: this.context?.info?.command || 'cm:stacks:clone',
43
+ module: 'clone',
44
+ email: configHandler.get('email') || '',
45
+ sessionId: this.context?.sessionId || '',
46
+ authenticationMethod: authenticationMethod || 'Basic Auth',
47
+ };
48
+ }
49
+
12
50
  async run() {
13
51
  try {
14
52
  let self = this;
@@ -31,14 +69,27 @@ class StackCloneCommand extends Command {
31
69
 
32
70
  const handleClone = async () => {
33
71
  const listOfTokens = configHandler.get('tokens');
72
+ const authenticationMethod = this.determineAuthenticationMethod(
73
+ sourceManagementTokenAlias,
74
+ destinationManagementTokenAlias,
75
+ );
76
+ const cloneContext = this.createCloneContext(authenticationMethod);
77
+ log.debug('Starting clone operation setup', cloneContext);
34
78
 
35
79
  if (externalConfigPath) {
80
+ log.debug(`Loading external configuration from: ${externalConfigPath}`, cloneContext);
36
81
  let externalConfig = readFileSync(externalConfigPath, 'utf-8');
37
82
  externalConfig = JSON.parse(externalConfig);
38
83
  config = merge.recursive(config, externalConfig);
39
84
  }
40
85
  config.forceStopMarketplaceAppsPrompt = yes;
41
86
  config.skipAudit = cloneCommandFlags['skip-audit'];
87
+ log.debug('Clone configuration prepared', {
88
+ ...cloneContext,
89
+ cloneType: config.cloneType,
90
+ skipAudit: config.skipAudit,
91
+ forceStopMarketplaceAppsPrompt: config.forceStopMarketplaceAppsPrompt
92
+ });
42
93
 
43
94
  if (cloneType) {
44
95
  config.cloneType = cloneType;
@@ -55,7 +106,7 @@ class StackCloneCommand extends Command {
55
106
  if (targetStackBranch) {
56
107
  config.targetStackBranch = targetStackBranch;
57
108
  }
58
- if (targetStackBranchAlias) {
109
+ if (targetStackBranchAlias) {
59
110
  config.targetStackBranchAlias = targetStackBranchAlias;
60
111
  }
61
112
  if (sourceStackApiKey) {
@@ -67,15 +118,18 @@ class StackCloneCommand extends Command {
67
118
  if (sourceManagementTokenAlias && listOfTokens[sourceManagementTokenAlias]) {
68
119
  config.source_alias = sourceManagementTokenAlias;
69
120
  config.source_stack = listOfTokens[sourceManagementTokenAlias].apiKey;
121
+ log.debug(`Using source token alias: ${sourceManagementTokenAlias}`, cloneContext);
70
122
  } else if (sourceManagementTokenAlias) {
71
- console.log(`Provided source token alias (${sourceManagementTokenAlias}) not found in your config.!`);
123
+ log.warn(`Provided source token alias (${sourceManagementTokenAlias}) not found in your config.!`, cloneContext);
72
124
  }
73
125
  if (destinationManagementTokenAlias && listOfTokens[destinationManagementTokenAlias]) {
74
126
  config.destination_alias = destinationManagementTokenAlias;
75
127
  config.target_stack = listOfTokens[destinationManagementTokenAlias].apiKey;
128
+ log.debug(`Using destination token alias: ${destinationManagementTokenAlias}`, cloneContext);
76
129
  } else if (destinationManagementTokenAlias) {
77
- console.log(
130
+ log.warn(
78
131
  `Provided destination token alias (${destinationManagementTokenAlias}) not found in your config.!`,
132
+ cloneContext,
79
133
  );
80
134
  }
81
135
  if (importWebhookStatus) {
@@ -83,18 +137,23 @@ class StackCloneCommand extends Command {
83
137
  }
84
138
 
85
139
  const managementAPIClient = await managementSDKClient(config);
140
+ log.debug('Management API client initialized successfully', cloneContext);
86
141
 
87
- await this.removeContentDirIfNotEmptyBeforeClone(pathdir); // NOTE remove if folder not empty before clone
88
- this.registerCleanupOnInterrupt(pathdir);
142
+ log.debug(`Content directory path: ${pathdir}`, cloneContext);
143
+ await this.removeContentDirIfNotEmptyBeforeClone(pathdir, cloneContext); // NOTE remove if folder not empty before clone
144
+ this.registerCleanupOnInterrupt(pathdir, cloneContext);
89
145
 
90
146
  config.auth_token = configHandler.get('authtoken');
91
147
  config.host = this.cmaHost;
92
148
  config.cdn = this.cdaHost;
93
149
  config.pathDir = pathdir;
150
+ config.cloneContext = cloneContext;
151
+ log.debug('Clone configuration finalized', cloneContext);
94
152
  const cloneHandler = new CloneHandler(config);
95
153
  cloneHandler.setClient(managementAPIClient);
154
+ log.debug('Starting clone operation', cloneContext);
96
155
  cloneHandler.execute().catch((error) => {
97
- console.log(error);
156
+ handleAndLogError(error, cloneContext);
98
157
  });
99
158
  };
100
159
 
@@ -103,7 +162,7 @@ class StackCloneCommand extends Command {
103
162
  if (isAuthenticated()) {
104
163
  handleClone();
105
164
  } else {
106
- console.log('Please login to execute this command, csdx auth:login');
165
+ log.error('Log in to execute this command,csdx auth:login', cloneContext);
107
166
  this.exit(1);
108
167
  }
109
168
  } else {
@@ -112,76 +171,76 @@ class StackCloneCommand extends Command {
112
171
  } else if (isAuthenticated()) {
113
172
  handleClone();
114
173
  } else {
115
- console.log('Please login to execute this command, csdx auth:login');
174
+ log.error('Please login to execute this command, csdx auth:login', cloneContext);
116
175
  this.exit(1);
117
176
  }
118
177
  } catch (error) {
119
178
  if (error) {
120
- await this.cleanUp(pathdir);
121
- // eslint-disable-next-line no-console
122
- console.log(error.message || error);
179
+ await this.cleanUp(pathdir, null, cloneContext);
180
+ log.error('Stack clone command failed', { ...cloneContext, error: error?.message || error });
123
181
  }
124
182
  }
125
183
  }
126
184
 
127
185
 
128
186
 
129
- async removeContentDirIfNotEmptyBeforeClone(dir) {
187
+ async removeContentDirIfNotEmptyBeforeClone(dir, cloneContext) {
130
188
  try {
189
+ log.debug('Checking if content directory is empty', { ...cloneContext, dir });
131
190
  const dirNotEmpty = readdirSync(dir).length;
132
191
 
133
192
  if (dirNotEmpty) {
134
- await this.cleanUp(dir);
193
+ log.debug('Content directory is not empty, cleaning up', { ...cloneContext, dir });
194
+ await this.cleanUp(dir, null, cloneContext);
135
195
  }
136
196
  } catch (error) {
137
197
  const omit = ['ENOENT']; // NOTE add emittable error codes in the array
138
198
 
139
199
  if (!omit.includes(error.code)) {
140
- console.log(error.message);
200
+ log.error('Error checking content directory', { ...cloneContext, error: error?.message, code: error.code });
141
201
  }
142
202
  }
143
203
  }
144
204
 
145
- async cleanUp(pathDir, message) {
205
+ async cleanUp(pathDir, message, cloneContext) {
146
206
  try {
207
+ log.debug('Starting cleanup', { ...cloneContext, pathDir });
147
208
  await rimraf(pathDir);
148
209
  if (message) {
149
- // eslint-disable-next-line no-console
150
- console.log(message);
210
+ log.info(message, cloneContext);
151
211
  }
212
+ log.debug('Cleanup completed', { ...cloneContext, pathDir });
152
213
  } catch (err) {
153
214
  if (err) {
154
- console.log('\nCleaning up');
215
+ log.debug('Cleaning up', cloneContext);
155
216
  const skipCodeArr = ['ENOENT', 'EBUSY', 'EPERM', 'EMFILE', 'ENOTEMPTY'];
156
217
 
157
218
  if (skipCodeArr.includes(err.code)) {
219
+ log.debug('Cleanup error code is in skip list, exiting', { ...cloneContext, code: err?.code });
158
220
  process.exit();
159
221
  }
160
222
  }
161
223
  }
162
224
  }
163
225
 
164
- registerCleanupOnInterrupt(pathDir) {
226
+ registerCleanupOnInterrupt(pathDir, cloneContext) {
165
227
  const interrupt = ['SIGINT', 'SIGQUIT', 'SIGTERM'];
166
228
  const exceptions = ['unhandledRejection', 'uncaughtException'];
167
229
 
168
230
  const cleanUp = async (exitOrError) => {
169
231
  if (exitOrError) {
170
- // eslint-disable-next-line no-console
171
- console.log('\nCleaning up');
172
- await this.cleanUp(pathDir);
173
- // eslint-disable-next-line no-console
174
- console.log('done');
175
- // eslint-disable-next-line no-process-exit
232
+ log.debug('Cleaning up on interrupt', cloneContext);
233
+ await this.cleanUp(pathDir, null, cloneContext);
234
+ log.info('Cleanup done', cloneContext);
176
235
 
177
236
  if (exitOrError instanceof Promise) {
178
237
  exitOrError.catch((error) => {
179
- console.log((error && error.message) || '');
238
+ log.error('Error during cleanup', { ...cloneContext, error: (error && error?.message) || '' });
180
239
  });
181
240
  } else if (exitOrError.message) {
182
- console.log(exitOrError.message);
241
+ log.error('Cleanup error', { ...cloneContext, error: exitOrError?.message });
183
242
  } else if (exitOrError.errorMessage) {
184
- console.log(exitOrError.message);
243
+ log.error('Cleanup error', { ...cloneContext, error: exitOrError?.errorMessage });
185
244
  }
186
245
 
187
246
  if (exitOrError === true) process.exit();
@@ -206,32 +265,32 @@ StackCloneCommand.examples = [
206
265
  '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>',
207
266
  ];
208
267
 
209
- StackCloneCommand.aliases = ['cm:stack-clone'];
268
+ StackCloneCommand.aliases = [];
210
269
 
211
270
  StackCloneCommand.flags = {
212
271
  'source-branch': flags.string({
213
272
  required: false,
214
273
  multiple: false,
215
274
  description: 'Branch of the source stack.',
216
- exclusive: ['source-branch-alias']
275
+ exclusive: ['source-branch-alias'],
217
276
  }),
218
277
  'source-branch-alias': flags.string({
219
278
  required: false,
220
279
  multiple: false,
221
280
  description: 'Alias of Branch of the source stack.',
222
- exclusive: ['source-branch']
281
+ exclusive: ['source-branch'],
223
282
  }),
224
283
  'target-branch': flags.string({
225
284
  required: false,
226
285
  multiple: false,
227
286
  description: 'Branch of the target stack.',
228
- exclusive: ['target-branch-alias']
287
+ exclusive: ['target-branch-alias'],
229
288
  }),
230
289
  'target-branch-alias': flags.string({
231
290
  required: false,
232
291
  multiple: false,
233
292
  description: 'Alias of Branch of the target stack.',
234
- exclusive: ['target-branch']
293
+ exclusive: ['target-branch'],
235
294
  }),
236
295
  'source-management-token-alias': flags.string({
237
296
  required: false,
@@ -21,7 +21,7 @@ const {
21
21
  Clone,
22
22
  HandleBranchCommand,
23
23
  } = require('../helpers/command-helpers');
24
- const { configHandler, getBranchFromAlias } = require('@contentstack/cli-utilities');
24
+ const { configHandler, getBranchFromAlias, log } = require('@contentstack/cli-utilities');
25
25
 
26
26
  let client = {};
27
27
  let config;
@@ -76,6 +76,7 @@ class CloneHandler {
76
76
  cloneCommand = new Clone();
77
77
  this.pathDir = opt.pathDir;
78
78
  process.stdin.setMaxListeners(50);
79
+ log.debug('Initializing CloneHandler', config.cloneContext, { pathDir: opt.pathDir, cloneType: opt.cloneType });
79
80
  }
80
81
  setClient(managementSDKClient) {
81
82
  client = managementSDKClient;
@@ -84,19 +85,24 @@ class CloneHandler {
84
85
  handleOrgSelection(options = {}) {
85
86
  return new Promise(async (resolve, reject) => {
86
87
  const { msg = '', isSource = true } = options || {};
88
+ log.debug('Handling organization selection', config.cloneContext);
87
89
  const orgList = await this.getOrganizationChoices(msg).catch(reject);
88
90
 
89
- if (orgList) {
90
- const orgSelected = await inquirer.prompt(orgList);
91
+ if (orgList) {
92
+ log.debug(`Found ${orgList.choices?.length || 0} organization(s) to choose from`, config.cloneContext);
93
+ const orgSelected = await inquirer.prompt(orgList);
94
+ log.debug(`Organization selected: ${orgSelected.Organization}`, config.cloneContext);
91
95
 
92
- if (isSource) {
93
- config.sourceOrg = orgUidList[orgSelected.Organization];
94
- } else {
95
- config.targetOrg = orgUidList[orgSelected.Organization];
96
- }
96
+ if (isSource) {
97
+ config.sourceOrg = orgUidList[orgSelected.Organization];
98
+ log.debug(`Source organization UID: ${config.sourceOrg}`, config.cloneContext);
99
+ } else {
100
+ config.targetOrg = orgUidList[orgSelected.Organization];
101
+ log.debug(`Target organization UID: ${config.targetOrg}`, config.cloneContext);
102
+ }
97
103
 
98
- resolve(orgSelected);
99
- }
104
+ resolve(orgSelected);
105
+ }
100
106
  });
101
107
  }
102
108
 
@@ -104,13 +110,16 @@ class CloneHandler {
104
110
  return new Promise(async (resolve, reject) => {
105
111
  try {
106
112
  const { org = {}, msg = '', isSource = true } = options || {};
113
+ log.debug('Handling stack selection', config.cloneContext, { isSource, orgName: org.Organization, msg });
107
114
 
108
115
  const stackList = await this.getStack(org, msg, isSource).catch(reject);
109
116
 
110
117
  if (stackList) {
111
118
  this.displayBackOptionMessage();
112
119
 
120
+ log.debug(`Found ${stackList.choices?.length || 0} stack(s) to choose from`, config.cloneContext);
113
121
  const selectedStack = await inquirer.prompt(stackList);
122
+ log.debug(`Stack selected: ${selectedStack.stack}`, config.cloneContext);
114
123
  if (this.executingCommand != 1) {
115
124
  return reject();
116
125
  }
@@ -118,9 +127,11 @@ class CloneHandler {
118
127
  config.sourceStackName = selectedStack.stack;
119
128
  master_locale = masterLocaleList[selectedStack.stack];
120
129
  config.source_stack = stackUidList[selectedStack.stack];
130
+ log.debug(`Source stack configured`, config.cloneContext);
121
131
  } else {
122
132
  config.target_stack = stackUidList[selectedStack.stack];
123
133
  config.destinationStackName = selectedStack.stack;
134
+ log.debug(`Target stack configured`, config.cloneContext);
124
135
  }
125
136
 
126
137
  resolve(selectedStack);
@@ -136,6 +147,7 @@ class CloneHandler {
136
147
  return new Promise(async (resolve, reject) => {
137
148
  let spinner;
138
149
  try {
150
+ log.debug('Handling branch selection', config.cloneContext, { isSource, returnBranch, stackApiKey: isSource ? config.source_stack : config.target_stack });
139
151
  const stackAPIClient = client.stack({
140
152
  api_key: isSource ? config.source_stack : config.target_stack,
141
153
  management_token: config.management_token,
@@ -143,22 +155,27 @@ class CloneHandler {
143
155
 
144
156
  // NOTE validate if source branch is exist
145
157
  if (isSource && config.sourceStackBranch) {
158
+ log.debug('Validating source branch exists', { ...config.cloneContext, branch: config.sourceStackBranch });
146
159
  await this.validateIfBranchExist(stackAPIClient, true);
147
160
  return resolve();
148
161
  } else if(isSource && config.sourceStackBranchAlias) {
162
+ log.debug('Resolving source branch alias', { ...config.cloneContext, alias: config.sourceStackBranchAlias });
149
163
  await this.resolveBranchAliases(true);
150
164
  return resolve();
151
165
  }
152
166
 
153
167
  // NOTE Validate target branch is exist
154
168
  if (!isSource && config.targetStackBranch) {
169
+ log.debug('Validating target branch exists', { ...config.cloneContext, branch: config.targetStackBranch });
155
170
  await this.validateIfBranchExist(stackAPIClient, false);
156
171
  return resolve();
157
172
  } else if (!isSource && config.targetStackBranchAlias) {
173
+ log.debug('Resolving target branch alias', { ...config.cloneContext, alias: config.targetStackBranchAlias });
158
174
  await this.resolveBranchAliases();
159
175
  return resolve();
160
176
  }
161
177
  spinner = ora('Fetching Branches').start();
178
+ log.debug(`Querying branches for stack: ${isSource ? config.source_stack : config.target_stack}`, config.cloneContext);
162
179
  const result = await stackAPIClient
163
180
  .branch()
164
181
  .query()
@@ -167,6 +184,7 @@ class CloneHandler {
167
184
  .catch((_err) => {});
168
185
 
169
186
  const condition = result && Array.isArray(result) && result.length > 0;
187
+ log.debug(`Found ${result?.length || 0} branch(es)`, config.cloneContext);
170
188
 
171
189
  // NOTE if want to get only list of branches (Pass param -> returnBranch = true )
172
190
  if (returnBranch) {
@@ -185,8 +203,10 @@ class CloneHandler {
185
203
  }
186
204
  if (isSource) {
187
205
  config.sourceStackBranch = branch;
206
+ log.debug(`Source branch selected: ${branch}`, config.cloneContext);
188
207
  } else {
189
208
  config.targetStackBranch = branch;
209
+ log.debug(`Target branch selected: ${branch}`, config.cloneContext);
190
210
  }
191
211
  } else {
192
212
  spinner.succeed('No branches found.!');
@@ -196,7 +216,6 @@ class CloneHandler {
196
216
  }
197
217
  } catch (e) {
198
218
  if (spinner) spinner.fail();
199
- console.error(e && e.message);
200
219
  return reject(e);
201
220
  }
202
221
  });
@@ -210,6 +229,7 @@ class CloneHandler {
210
229
  };
211
230
  try {
212
231
  const branch = isSource ? config.sourceStackBranch : config.targetStackBranch;
232
+ log.debug('Validating branch existence', config.cloneContext);
213
233
  spinner = ora(`Validation if ${isSource ? 'source' : 'target'} branch exist.!`).start();
214
234
  const isBranchExist = await stackAPIClient
215
235
  .branch(branch)
@@ -217,8 +237,10 @@ class CloneHandler {
217
237
  .then((data) => data);
218
238
 
219
239
  if (isBranchExist && typeof isBranchExist === 'object') {
240
+ log.debug('Branch validation successful', config.cloneContext);
220
241
  completeSpinner(`${isSource ? 'Source' : 'Target'} branch verified.!`);
221
242
  } else {
243
+ log.error('Branch not found', config.cloneContext);
222
244
  completeSpinner(`${isSource ? 'Source' : 'Target'} branch not found.!`, 'fail');
223
245
  process.exit();
224
246
  }
@@ -247,8 +269,10 @@ class CloneHandler {
247
269
  return new Promise(async (resolve, reject) => {
248
270
  let keyPressHandler;
249
271
  try {
272
+ log.debug('Starting clone execution', { ...config.cloneContext, sourceStack: config.source_stack, targetStack: config.target_stack });
250
273
  if (!config.source_stack) {
251
274
  const orgMsg = 'Choose an organization where your source stack exists:';
275
+ log.debug('Source stack not provided, prompting for organization', config.cloneContext);
252
276
  this.setExectingCommand(0);
253
277
  this.removeBackKeyPressHandler();
254
278
  const org = await cloneCommand.execute(new HandleOrgCommand({ msg: orgMsg, isSource: true }, this));
@@ -278,17 +302,21 @@ class CloneHandler {
278
302
  return reject('Org not found.');
279
303
  }
280
304
  } else {
305
+ log.debug('Source stack provided, proceeding with branch selection and export', config.cloneContext);
281
306
  this.setExectingCommand(2);
282
307
  await this.handleBranchSelection({ api_key: config.sourceStack });
308
+ log.debug('Starting export operation', config.cloneContext);
283
309
  const exportRes = await cloneCommand.execute(new HandleExportCommand(null, this));
284
310
  await cloneCommand.execute(new SetBranchCommand(null, this));
285
311
 
286
312
  if (exportRes) {
313
+ log.debug('Export completed, proceeding with destination setup', config.cloneContext);
287
314
  this.executeDestination().catch((error) => {
288
315
  return reject(error);
289
316
  });
290
317
  }
291
318
  }
319
+ log.debug('Clone execution completed successfully', config.cloneContext);
292
320
  return resolve();
293
321
  } catch (error) {
294
322
  return reject(error);
@@ -327,10 +355,12 @@ class CloneHandler {
327
355
 
328
356
  async executeExport() {
329
357
  try {
358
+ log.debug('Executing export operation', config.cloneContext);
330
359
  const exportRes = await cloneCommand.execute(new HandleExportCommand(null, this));
331
360
  await cloneCommand.execute(new SetBranchCommand(null, this));
332
361
 
333
362
  if (exportRes) {
363
+ log.debug('Export operation completed, proceeding with destination', config.cloneContext);
334
364
  this.executeDestination().catch(() => {
335
365
  throw '';
336
366
  });
@@ -346,8 +376,10 @@ class CloneHandler {
346
376
  return new Promise(async (resolve, reject) => {
347
377
  let keyPressHandler;
348
378
  try {
379
+ log.debug('Executing destination setup', config.cloneContext);
349
380
  let canCreateStack = false;
350
381
  if (!config.target_stack) {
382
+ log.debug('Target stack not provided, prompting for stack creation', config.cloneContext);
351
383
  canCreateStack = await inquirer.prompt(stackCreationConfirmation);
352
384
  }
353
385
 
@@ -397,6 +429,7 @@ class CloneHandler {
397
429
  await this.executeBranchDestinationPrompt(params);
398
430
  }
399
431
 
432
+ log.debug('Destination setup completed successfully', config.cloneContext);
400
433
  return resolve();
401
434
  } catch (error) {
402
435
  reject(error);
@@ -469,10 +502,12 @@ class CloneHandler {
469
502
  choices: [],
470
503
  };
471
504
  return new Promise(async (resolve, reject) => {
505
+ log.debug('Fetching organization choices', config.cloneContext);
472
506
  const spinner = ora('Fetching Organization').start();
473
507
  try {
474
508
  let organizations;
475
509
  const configOrgUid = configHandler.get('oauthOrgUid');
510
+ log.debug('Getting organizations', config.cloneContext, { hasConfigOrgUid: !!configOrgUid });
476
511
 
477
512
  if (configOrgUid) {
478
513
  organizations = await client.organization(configOrgUid).fetch();
@@ -481,6 +516,7 @@ class CloneHandler {
481
516
  }
482
517
 
483
518
  spinner.succeed('Fetched Organization');
519
+ log.debug('Fetched organizations', config.cloneContext);
484
520
  for (const element of organizations.items || [organizations]) {
485
521
  orgUidList[element.name] = element.uid;
486
522
  orgChoice.choices.push(element.name);
@@ -501,12 +537,15 @@ class CloneHandler {
501
537
  message: stkMessage !== undefined ? stkMessage : 'Select the stack',
502
538
  choices: [],
503
539
  };
540
+ log.debug('Fetching stacks', config.cloneContext);
504
541
  const spinner = ora('Fetching stacks').start();
505
542
  try {
506
543
  const organization_uid = orgUidList[answer.Organization];
544
+ log.debug('Querying stacks for organization', config.cloneContext, { organizationUid: organization_uid });
507
545
  const stackList = client.stack().query({ organization_uid }).find();
508
546
  stackList
509
547
  .then((stacklist) => {
548
+ log.debug('Fetched stacks', config.cloneContext, { count: stacklist.items ? stacklist.items.length : 0 });
510
549
  for (const element of stacklist.items) {
511
550
  stackUidList[element.name] = element.api_key;
512
551
  masterLocaleList[element.name] = element.master_locale;
@@ -530,9 +569,11 @@ class CloneHandler {
530
569
  return new Promise(async (resolve, reject) => {
531
570
  try {
532
571
  const { orgUid } = options;
572
+ log.debug('Creating new stack', config.cloneContext, { orgUid, masterLocale: master_locale, stackName: config.stackName });
533
573
  this.displayBackOptionMessage();
534
574
  let inputvalue;
535
575
  if (!config.stackName) {
576
+ log.debug('Stack name not provided, prompting user', config.cloneContext);
536
577
  prompt.start();
537
578
  prompt.message = '';
538
579
  this.setCreateNewStackPrompt(prompt);
@@ -542,17 +583,24 @@ class CloneHandler {
542
583
  inputvalue = { stack: config.stackName };
543
584
  }
544
585
  if (this.executingCommand === 0 || !inputvalue) {
586
+ log.debug('Stack creation cancelled or invalid input', config.cloneContext);
545
587
  return reject();
546
588
  }
547
589
 
548
590
  let stack = { name: inputvalue.stack, master_locale: master_locale };
591
+ log.debug('Creating stack with configuration', config.cloneContext);
549
592
  const spinner = ora('Creating New stack').start();
593
+ log.debug('Sending stack creation API request', config.cloneContext);
550
594
  let newStack = client.stack().create({ stack }, { organization_uid: orgUid });
551
595
  newStack
552
596
  .then((result) => {
597
+ log.debug('Stack created successfully', config.cloneContext, {
598
+ stackName: result.name,
599
+ });
553
600
  spinner.succeed('New Stack created Successfully name as ' + result.name);
554
601
  config.target_stack = result.api_key;
555
602
  config.destinationStackName = result.name;
603
+ log.debug('Target stack configuration updated', config.cloneContext);
556
604
  return resolve(result);
557
605
  })
558
606
  .catch((error) => {
@@ -589,12 +637,15 @@ class CloneHandler {
589
637
 
590
638
  async resolveBranchAliases(isSource = false) {
591
639
  try {
640
+ log.debug('Resolving branch aliases', { ...config.cloneContext, isSource, alias: isSource ? config.sourceStackBranchAlias : config.targetStackBranchAlias });
592
641
  if (isSource) {
593
642
  const sourceStack = client.stack({ api_key: config.source_stack });
594
643
  config.sourceStackBranch = await getBranchFromAlias(sourceStack, config.sourceStackBranchAlias);
644
+ log.debug('Source branch alias resolved', { ...config.cloneContext, alias: config.sourceStackBranchAlias, branch: config.sourceStackBranch });
595
645
  } else {
596
646
  const targetStack = client.stack({ api_key: config.target_stack });
597
647
  config.targetStackBranch = await getBranchFromAlias(targetStack, config.targetStackBranchAlias);
648
+ log.debug('Target branch alias resolved', { ...config.cloneContext, alias: config.targetStackBranchAlias, branch: config.targetStackBranch });
598
649
  }
599
650
  } catch (error) {
600
651
  throw error;
@@ -604,6 +655,7 @@ class CloneHandler {
604
655
  async cloneTypeSelection() {
605
656
  console.clear();
606
657
  return new Promise(async (resolve, reject) => {
658
+ log.debug('Starting clone type selection', config.cloneContext);
607
659
  const choices = [
608
660
  'Structure (all modules except entries & assets)',
609
661
  'Structure with content (all modules including entries & assets)',
@@ -619,83 +671,139 @@ class CloneHandler {
619
671
  let successMsg;
620
672
  let selectedValue = {};
621
673
  config['data'] = path.join(__dirname.split('src')[0], 'contents', config.sourceStackBranch || '');
674
+ log.debug(`Clone data directory: ${config['data']}`, config.cloneContext);
622
675
 
623
676
  if (!config.cloneType) {
677
+ log.debug('Clone type not specified, prompting user for selection', config.cloneContext);
624
678
  selectedValue = await inquirer.prompt(cloneTypeSelection);
679
+ } else {
680
+ log.debug(`Using pre-configured clone type: ${config.cloneType}`, config.cloneContext);
625
681
  }
626
682
 
627
683
  if (config.cloneType === 'a' || selectedValue.type === 'Structure (all modules except entries & assets)') {
628
684
  config['modules'] = structureList;
629
685
  successMsg = 'Stack clone Structure completed';
686
+ log.debug(`Clone type: Structure only. Modules to clone: ${structureList.join(', ')}`, config.cloneContext);
630
687
  } else {
631
688
  successMsg = 'Stack clone completed with structure and content';
689
+ log.debug('Clone type: Structure with content (all modules)', config.cloneContext);
632
690
  }
633
691
 
634
692
  this.cmdImport()
635
- .then(() => resolve(successMsg))
693
+ .then(() => {
694
+ log.debug('Clone type selection and import completed successfully', config.cloneContext);
695
+ resolve(successMsg);
696
+ })
636
697
  .catch(reject);
637
698
  });
638
699
  }
639
700
 
640
701
  async cmdExport() {
641
702
  return new Promise((resolve, reject) => {
703
+ log.debug('Preparing export command', { ...config.cloneContext, sourceStack: config.source_stack, cloneType: config.cloneType });
642
704
  // Creating export specific config by merging external configurations
643
705
  let exportConfig = Object.assign({}, cloneDeep(config), { ...config?.export });
644
706
  delete exportConfig.import;
645
707
  delete exportConfig.export;
646
708
 
647
- const cmd = ['-k', exportConfig.source_stack, '-d', __dirname.split('src')[0] + 'contents'];
709
+ const exportDir = __dirname.split('src')[0] + 'contents';
710
+ log.debug(`Export directory: ${exportDir}`, config.cloneContext);
711
+ const cmd = ['-k', exportConfig.source_stack, '-d', exportDir];
712
+
648
713
  if (exportConfig.cloneType === 'a') {
649
714
  exportConfig.filteredModules = ['stack'].concat(structureList);
715
+ log.debug(`Filtered modules for structure-only export: ${exportConfig.filteredModules.join(', ')}`, config.cloneContext);
650
716
  }
651
717
 
652
718
  if (exportConfig.source_alias) {
653
719
  cmd.push('-a', exportConfig.source_alias);
720
+ log.debug(`Using source alias: ${exportConfig.source_alias}`, config.cloneContext);
654
721
  }
655
722
  if (exportConfig.sourceStackBranch) {
656
723
  cmd.push('--branch', exportConfig.sourceStackBranch);
724
+ log.debug(`Using source branch: ${exportConfig.sourceStackBranch}`, config.cloneContext);
657
725
  }
658
726
 
659
- if (exportConfig.forceStopMarketplaceAppsPrompt) cmd.push('-y');
727
+ if (exportConfig.forceStopMarketplaceAppsPrompt) {
728
+ cmd.push('-y');
729
+ log.debug('Force stop marketplace apps prompt enabled', config.cloneContext);
730
+ }
660
731
 
732
+ const configFilePath = path.join(__dirname, 'dummyConfig.json');
661
733
  cmd.push('-c');
662
- cmd.push(path.join(__dirname, 'dummyConfig.json'));
663
-
664
- fs.writeFileSync(path.join(__dirname, 'dummyConfig.json'), JSON.stringify(exportConfig));
734
+ cmd.push(configFilePath);
735
+ log.debug(`Writing export config to: ${configFilePath}`, config.cloneContext);
736
+
737
+ fs.writeFileSync(configFilePath, JSON.stringify(exportConfig));
738
+ log.debug('Export command prepared', config.cloneContext, {
739
+ cmd: cmd.join(' '),
740
+ exportDir,
741
+ sourceStack: exportConfig.source_stack,
742
+ branch: exportConfig.sourceStackBranch
743
+ });
744
+ log.debug('Running export command', config.cloneContext, { cmd });
665
745
  let exportData = exportCmd.run(cmd);
666
- exportData.then(() => resolve(true)).catch(reject);
746
+ exportData.then(() => {
747
+ log.debug('Export command completed successfully', config.cloneContext);
748
+ resolve(true);
749
+ }).catch((error) => {
750
+ reject(error);
751
+ });
667
752
  });
668
753
  }
669
754
 
670
755
  async cmdImport() {
671
756
  return new Promise(async (resolve, _reject) => {
757
+ log.debug('Preparing import command', { ...config.cloneContext, targetStack: config.target_stack, targetBranch: config.targetStackBranch });
672
758
  // Creating export specific config by merging external configurations
673
759
  let importConfig = Object.assign({}, cloneDeep(config), { ...config?.import });
674
760
  delete importConfig.import;
675
761
  delete importConfig.export;
676
762
 
677
- const cmd = ['-c', path.join(__dirname, 'dummyConfig.json')];
763
+ const configFilePath = path.join(__dirname, 'dummyConfig.json');
764
+ const cmd = ['-c', configFilePath];
678
765
 
679
766
  if (importConfig.destination_alias) {
680
767
  cmd.push('-a', importConfig.destination_alias);
768
+ log.debug(`Using destination alias: ${importConfig.destination_alias}`, config.cloneContext);
681
769
  }
682
770
  if (!importConfig.data && importConfig.sourceStackBranch) {
683
- cmd.push('-d', path.join(importConfig.pathDir, importConfig.sourceStackBranch));
771
+ const dataPath = path.join(importConfig.pathDir, importConfig.sourceStackBranch);
772
+ cmd.push('-d', dataPath);
773
+ log.debug(`Import data path: ${dataPath}`, config.cloneContext);
684
774
  }
685
775
  if (importConfig.targetStackBranch) {
686
776
  cmd.push('--branch', importConfig.targetStackBranch);
777
+ log.debug(`Using target branch: ${importConfig.targetStackBranch}`, config.cloneContext);
687
778
  }
688
779
  if (importConfig.importWebhookStatus) {
689
780
  cmd.push('--import-webhook-status', importConfig.importWebhookStatus);
781
+ log.debug(`Import webhook status: ${importConfig.importWebhookStatus}`, config.cloneContext);
690
782
  }
691
783
 
692
- if (importConfig.skipAudit) cmd.push('--skip-audit');
784
+ if (importConfig.skipAudit) {
785
+ cmd.push('--skip-audit');
786
+ log.debug('Skip audit flag enabled', config.cloneContext);
787
+ }
693
788
 
694
- if (importConfig.forceStopMarketplaceAppsPrompt) cmd.push('-y');
789
+ if (importConfig.forceStopMarketplaceAppsPrompt) {
790
+ cmd.push('-y');
791
+ log.debug('Force stop marketplace apps prompt enabled', config.cloneContext);
792
+ }
695
793
 
696
- fs.writeFileSync(path.join(__dirname, 'dummyConfig.json'), JSON.stringify(importConfig));
794
+ log.debug(`Writing import config to: ${configFilePath}`, config.cloneContext);
795
+ fs.writeFileSync(configFilePath, JSON.stringify(importConfig));
796
+ log.debug('Import command prepared', config.cloneContext, {
797
+ cmd: cmd.join(' '),
798
+ targetStack: importConfig.target_stack,
799
+ targetBranch: importConfig.targetStackBranch,
800
+ dataPath: importConfig.data || path.join(importConfig.pathDir, importConfig.sourceStackBranch)
801
+ });
802
+ log.debug('Running import command', config.cloneContext, { cmd });
697
803
  await importCmd.run(cmd);
698
- fs.writeFileSync(path.join(__dirname, 'dummyConfig.json'), JSON.stringify({}));
804
+ log.debug('Import command completed successfully', config.cloneContext);
805
+ log.debug('Clearing import config file', config.cloneContext);
806
+ fs.writeFileSync(configFilePath, JSON.stringify({}));
699
807
  return resolve();
700
808
  });
701
809
  }
@@ -1,105 +0,0 @@
1
- /*!
2
- * Contentstack Import
3
- * Copyright (c) 2024 Contentstack LLC
4
- * MIT Licensed
5
- */
6
-
7
- var winston = require('winston');
8
- var path = require('path');
9
- var mkdirp = require('mkdirp');
10
- const { pathValidator, sanitizePath } = require('@contentstack/cli-utilities');
11
- var slice = Array.prototype.slice;
12
-
13
- function returnString(args) {
14
- var returnStr = '';
15
- if (args && args.length) {
16
- returnStr = args
17
- .map(function (item) {
18
- if (item && typeof item === 'object') {
19
- return JSON.stringify(item);
20
- }
21
- return item;
22
- })
23
- .join(' ')
24
- .trim();
25
- }
26
- return returnStr;
27
- }
28
-
29
- var myCustomLevels = {
30
- levels: {
31
- error: 0,
32
- warn: 1,
33
- info: 2,
34
- debug: 3,
35
- },
36
- colors: {
37
- info: 'blue',
38
- debug: 'green',
39
- warn: 'yellow',
40
- error: 'red',
41
- },
42
- };
43
-
44
- function init(_logPath, logfileName) {
45
- var logsDir = pathValidator(path.resolve(sanitizePath(_logPath), 'logs', 'import'));
46
- // Create dir if doesn't already exist
47
- mkdirp.sync(logsDir);
48
- var logPath = path.join(sanitizePath(logsDir), pathValidator(sanitizePath(logfileName)) + '.log');
49
-
50
- var transports = [
51
- new winston.transports.File({
52
- filename: logPath,
53
- maxFiles: 20,
54
- maxsize: 1000000,
55
- tailable: true,
56
- json: true,
57
- }),
58
- ];
59
-
60
- transports.push(new winston.transports.Console());
61
-
62
- var logger = winston.createLogger({
63
- transports: transports,
64
- levels: myCustomLevels.levels,
65
- });
66
-
67
- return {
68
- log: function () {
69
- var args = slice.call(arguments);
70
- var logString = returnString(args);
71
- if (logString) {
72
- logger.log('info', logString);
73
- }
74
- },
75
- warn: function () {
76
- var args = slice.call(arguments);
77
- var logString = returnString(args);
78
- if (logString) {
79
- logger.log('warn', logString);
80
- }
81
- },
82
- error: function () {
83
- var args = slice.call(arguments);
84
- var logString = returnString(args);
85
- if (logString) {
86
- logger.log('error', logString);
87
- }
88
- },
89
- debug: function () {
90
- var args = slice.call(arguments);
91
- var logString = returnString(args);
92
- if (logString) {
93
- logger.log('debug', logString);
94
- }
95
- },
96
- };
97
- }
98
-
99
- exports.addlogs = async (config, message, type) => {
100
- if (type !== 'error') {
101
- init(config.oldPath, type).log(message);
102
- } else {
103
- init(config.oldPath, type).error(message);
104
- }
105
- };