@contentstack/cli-cm-clone 0.1.0-beta.6 → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Contentstack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -33,23 +33,23 @@ USAGE
33
33
  ```
34
34
  # Commands
35
35
  <!-- commands -->
36
- * [`csdx cm:stack-clone`](#csdx-cmstack-clone)
36
+ * [`csdx cm:stacks:clone`](#csdx-cmstack-clone)
37
37
 
38
- ## `csdx cm:stack-clone`
38
+ ## `csdx cm:stacks:clone`
39
39
 
40
40
  This command allows you to migrate data (structure or content or both) from one stack to another stack (either new or existing)
41
41
 
42
42
  ```
43
43
  USAGE
44
- $ csdx cm:stack-clone
44
+ $ csdx cm:stacks:clone
45
45
 
46
46
  DESCRIPTION
47
47
  ...
48
48
  Use this plugin to automate the process of cloning a stack in a few steps.
49
49
 
50
50
  EXAMPLE
51
- csdx cm:stack-clone
51
+ csdx cm:stacks:clone
52
52
  ```
53
53
 
54
- _See code: [src/commands/cm/stack-clone.js](https://github.com/contentstack/cli/blob/v0.1.0-beta-1/src/commands/cm/stack-clone.js)_
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)_
55
55
  <!-- commandsstop -->
package/package.json CHANGED
@@ -1,35 +1,35 @@
1
1
  {
2
2
  "name": "@contentstack/cli-cm-clone",
3
3
  "description": "Contentstack stack clone plugin",
4
- "version": "0.1.0-beta.6",
4
+ "version": "1.0.0",
5
5
  "author": "Contentstack",
6
6
  "bugs": "https://github.com/rohitmishra209/cli-cm-clone/issues",
7
7
  "dependencies": {
8
- "@contentstack/cli-cm-export": "^0.1.1-beta.15",
9
- "@contentstack/cli-cm-import": "^0.1.1-beta.18",
10
- "@contentstack/cli-command": "^0.1.1-beta.6",
8
+ "@contentstack/cli-cm-export": "^1.0.0",
9
+ "@contentstack/cli-cm-import": "^1.0.0",
10
+ "@contentstack/cli-command": "^1.0.0",
11
11
  "@contentstack/management": "^1.3.0",
12
- "@oclif/command": "^1.8.0",
13
- "@oclif/config": "^1.17.0",
14
- "async": "^3.2.0",
12
+ "@contentstack/cli-utilities": "^1.0.0",
13
+ "@oclif/command": "^1.8.16",
14
+ "@oclif/config": "^1.18.3",
15
+ "async": "^3.2.4",
15
16
  "child_process": "^1.0.2",
16
- "configstore": "^5.0.1",
17
17
  "fancy-test": "^1.4.10",
18
18
  "inquirer": "^7.3.3",
19
19
  "ora": "^5.1.0",
20
20
  "rimraf": "^3.0.2",
21
- "winston": "^3.3.3"
21
+ "winston": "^3.7.2"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@oclif/dev-cli": "^1.22.2",
25
- "@oclif/plugin-help": "^3.2.0",
25
+ "@oclif/plugin-help": "^5.1.12",
26
26
  "@oclif/test": "^1.2.7",
27
27
  "chai": "^4.2.0",
28
- "eslint": "^5.16.0",
28
+ "eslint": "^8.18.0",
29
29
  "eslint-config-oclif": "^3.1.0",
30
30
  "globby": "^10.0.2",
31
31
  "jest": "^26.6.3",
32
- "mocha": "^8.2.1",
32
+ "mocha": "^10.0.0",
33
33
  "nyc": "^14.1.1",
34
34
  "sinon": "^9.2.4"
35
35
  },
@@ -59,5 +59,10 @@
59
59
  "scripts": {
60
60
  "test": "nyc --reporter=html mocha \"test/**/*.test.js\"",
61
61
  "version": "oclif-dev readme && git add README.md"
62
+ },
63
+ "csdxConfig": {
64
+ "expiredCommands": {
65
+ "cm:stack-clone": "csdx cm:stacks:clone"
66
+ }
62
67
  }
63
68
  }
@@ -0,0 +1,239 @@
1
+ const { Command, flags } = require('@contentstack/cli-command');
2
+ const { configHandler } = require('@contentstack/cli-utilities');
3
+ const { CloneHandler } = require('../../../lib/util/clone-handler');
4
+ let config = require('../../../lib/util/dummyConfig.json');
5
+ const path = require('path');
6
+ const rimraf = require('rimraf');
7
+ let pathdir = path.join(__dirname.split('src')[0], 'contents');
8
+ const { readdirSync } = require('fs');
9
+
10
+ class StackCloneCommand extends Command {
11
+ async run() {
12
+ try {
13
+ let self = this;
14
+ let _authToken = configHandler.get('authtoken');
15
+ const cloneCommandFlags = self.parse(StackCloneCommand).flags;
16
+ const {
17
+ type: cloneType,
18
+ 'stack-name': stackName,
19
+ 'source-branch': sourceStackBranch,
20
+ 'target-branch': targetStackBranch,
21
+ 'source-stack-api-key': sourceStackApiKey,
22
+ 'destination-stack-api-key': destinationStackApiKey,
23
+ 'source-management-token-alias': sourceManagementTokenAlias,
24
+ 'destination-management-token-alias': destinationManagementTokenAlias,
25
+ 'import-webhook-status': importWebhookStatus,
26
+ } = cloneCommandFlags;
27
+
28
+ const handleClone = async () => {
29
+ const listOfTokens = configHandler.get('tokens');
30
+
31
+ if (cloneType) {
32
+ config.cloneType = cloneType;
33
+ }
34
+ if (stackName) {
35
+ config.stackName = stackName;
36
+ }
37
+ if (sourceStackBranch) {
38
+ config.sourceStackBranch = sourceStackBranch;
39
+ }
40
+ if (targetStackBranch) {
41
+ config.targetStackBranch = targetStackBranch;
42
+ }
43
+ if (sourceStackApiKey) {
44
+ config.source_stack = sourceStackApiKey;
45
+ }
46
+ if (destinationStackApiKey) {
47
+ config.target_stack = destinationStackApiKey;
48
+ }
49
+ if (sourceManagementTokenAlias && listOfTokens[sourceManagementTokenAlias]) {
50
+ config.source_alias = sourceManagementTokenAlias;
51
+ config.source_stack = listOfTokens[sourceManagementTokenAlias].apiKey;
52
+ } else if (sourceManagementTokenAlias) {
53
+ console.log(`Provided source token alias (${sourceManagementTokenAlias}) not found in your config.!`);
54
+ }
55
+ if (destinationManagementTokenAlias && listOfTokens[destinationManagementTokenAlias]) {
56
+ config.destination_alias = destinationManagementTokenAlias;
57
+ config.target_stack = listOfTokens[destinationManagementTokenAlias].apiKey;
58
+ } else if (destinationManagementTokenAlias) {
59
+ console.log(
60
+ `Provided destination token alias (${destinationManagementTokenAlias}) not found in your config.!`,
61
+ );
62
+ }
63
+ if (importWebhookStatus) {
64
+ config.importWebhookStatus = importWebhookStatus;
65
+ }
66
+
67
+ await this.removeContentDirIfNotEmptyBeforeClone(pathdir); // NOTE remove if folder not empty before clone
68
+ this.registerCleanupOnInterrupt(pathdir);
69
+
70
+ config.auth_token = _authToken;
71
+ config.host = this.cmaHost;
72
+ config.cdn = this.cdaHost;
73
+ 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);
77
+ };
78
+
79
+ if (sourceManagementTokenAlias && destinationManagementTokenAlias) {
80
+ if (sourceStackBranch || targetStackBranch) {
81
+ if (_authToken) {
82
+ handleClone();
83
+ } else {
84
+ console.log('Please login to execute this command, csdx auth:login');
85
+ this.exit(1);
86
+ }
87
+ } else {
88
+ handleClone();
89
+ }
90
+ } else if (_authToken) {
91
+ handleClone();
92
+ } else {
93
+ console.log('Please login to execute this command, csdx auth:login');
94
+ this.exit(1);
95
+ }
96
+ } catch (error) {
97
+ await this.cleanUp(pathdir);
98
+ // eslint-disable-next-line no-console
99
+ console.log(error.message || error);
100
+ }
101
+ }
102
+
103
+ async removeContentDirIfNotEmptyBeforeClone(dir) {
104
+ try {
105
+ const dirNotEmpty = readdirSync(dir).length;
106
+
107
+ if (dirNotEmpty) {
108
+ await this.cleanUp(dir);
109
+ }
110
+ } catch (error) {
111
+ const omit = ['ENOENT']; // NOTE add emittable error codes in the array
112
+
113
+ if (!omit.includes(error.code)) {
114
+ console.log(error.message);
115
+ }
116
+ }
117
+ }
118
+
119
+ cleanUp(pathDir, message) {
120
+ return new Promise((resolve) => {
121
+ rimraf(pathDir, function (err) {
122
+ if (err) {
123
+ console.log('\nCleaning up');
124
+ const skipCodeArr = ['ENOENT', 'EBUSY', 'EPERM', 'EMFILE', 'ENOTEMPTY'];
125
+
126
+ if (skipCodeArr.includes(err.code)) {
127
+ process.exit();
128
+ }
129
+ }
130
+
131
+ if (message) {
132
+ // eslint-disable-next-line no-console
133
+ console.log(message);
134
+ }
135
+
136
+ resolve();
137
+ });
138
+ });
139
+ }
140
+
141
+ registerCleanupOnInterrupt(pathDir) {
142
+ const interrupt = ['SIGINT', 'SIGQUIT', 'SIGTERM'];
143
+ const exceptions = ['unhandledRejection', 'uncaughtException'];
144
+
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
+ }
162
+
163
+ if (exitOrError === true) process.exit();
164
+ };
165
+
166
+ exceptions.forEach((event) => process.on(event, cleanUp));
167
+ interrupt.forEach((signal) => process.on(signal, () => cleanUp(true)));
168
+ }
169
+ }
170
+
171
+ StackCloneCommand.description = `Clone data (structure/content or both) of a stack into another stack
172
+ Use this plugin to automate the process of cloning a stack in few steps.
173
+ `;
174
+
175
+ StackCloneCommand.examples = [
176
+ 'csdx cm:stacks:clone',
177
+ 'csdx cm:stacks:clone --source-branch <source-branch-name> --target-branch <target-branch-name>',
178
+ 'csdx cm:stacks:clone --source-stack-api-key <apiKey> --destination-stack-api-key <apiKey>',
179
+ 'csdx cm:stacks:clone --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>',
180
+ 'csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>',
181
+ '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>',
182
+ ];
183
+
184
+ StackCloneCommand.aliases = ['cm:stack-clone'];
185
+
186
+ StackCloneCommand.flags = {
187
+ 'source-branch': flags.string({
188
+ required: false,
189
+ multiple: false,
190
+ description: 'Branch of the source stack.',
191
+ }),
192
+ 'target-branch': flags.string({
193
+ required: false,
194
+ multiple: false,
195
+ description: 'Branch of the target stack.',
196
+ }),
197
+ 'source-management-token-alias': flags.string({
198
+ required: false,
199
+ multiple: false,
200
+ description: 'Source API key of the target stack token alias.',
201
+ }),
202
+ 'destination-management-token-alias': flags.string({
203
+ required: false,
204
+ multiple: false,
205
+ description: 'Source API key of the target stack token alias.',
206
+ }),
207
+ 'stack-name': flags.string({
208
+ char: 'n',
209
+ required: false,
210
+ multiple: false,
211
+ description: 'Name for the new stack to store the cloned content.',
212
+ }),
213
+ type: flags.string({
214
+ required: false,
215
+ multiple: false,
216
+ options: ['a', 'b'],
217
+ description: `Type of data to clone
218
+ a) Structure (all modules except entries & assets)
219
+ b) Structure with content (all modules including entries & assets)
220
+ `,
221
+ }),
222
+ 'source-stack-api-key': flags.string({
223
+ description: 'Source stack API Key',
224
+ }),
225
+ 'destination-stack-api-key': flags.string({
226
+ description: 'Destination stack API Key',
227
+ }),
228
+ 'import-webhook-status': flags.string({
229
+ description: 'Webhook state',
230
+ options: ['disable', 'current'],
231
+ required: false,
232
+ default: 'disable',
233
+ }),
234
+ };
235
+
236
+ StackCloneCommand.usage =
237
+ '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]';
238
+
239
+ module.exports = StackCloneCommand;
@@ -1,255 +1,335 @@
1
- let inquirer = require('inquirer')
2
- const Configstore = require('configstore')
3
- const _ = require('lodash')
4
- const fs = require('fs')
5
- let ora = require('ora')
6
- const async = require("async");
7
- const path = require('path')
8
-
9
- let sdkInstance = require('../../lib/util/contentstack-management-sdk')
10
- let exportCmd = require('@contentstack/cli-cm-export')
11
- let importCmd = require('@contentstack/cli-cm-import')
12
- let client = {}
13
- let config
14
-
15
- let stackCreationConfirmation = [{
16
- type: 'confirm',
17
- name: 'stackCreate',
18
- message: 'Want to clone content into a new stack ?',
19
- initial: true
20
- }]
1
+ const ora = require('ora');
2
+ const path = require('path');
3
+ const inquirer = require('inquirer');
4
+
5
+ let sdkInstance = require('../../lib/util/contentstack-management-sdk');
6
+ let exportCmd = require('@contentstack/cli-cm-export');
7
+ let importCmd = require('@contentstack/cli-cm-import');
8
+ let client = {};
9
+ let config;
10
+
11
+ let stackCreationConfirmation = [
12
+ {
13
+ type: 'confirm',
14
+ name: 'stackCreate',
15
+ message: 'Want to clone content into a new stack ?',
16
+ initial: true,
17
+ },
18
+ ];
21
19
 
22
20
  let stackName = {
23
21
  type: 'input',
24
22
  name: 'stack',
23
+ default: 'ABC',
25
24
  message: 'Enter name for the new stack to store the cloned content ?',
26
- default: "ABC"
27
- }
25
+ };
28
26
 
29
- let orgUidList = {}
30
- let stackUidList = {}
31
- let masterLocaleList = {}
27
+ let orgUidList = {};
28
+ let stackUidList = {};
29
+ let masterLocaleList = {};
32
30
 
33
- let structureList = ['locales',
31
+ let structureList = [
32
+ 'locales',
34
33
  'environments',
35
34
  'extensions',
36
35
  'webhooks',
37
36
  'global-fields',
38
37
  'content-types',
39
38
  'workflows',
40
- 'labels']
41
- var oraMessage
42
- let master_locale
39
+ 'labels',
40
+ ];
41
+ let master_locale;
43
42
 
44
43
  class CloneHandler {
45
44
  constructor(opt) {
46
- config = opt
47
- client = sdkInstance.Client(config)
45
+ config = opt;
46
+ client = sdkInstance.Client(config);
47
+ }
48
+
49
+ #handleOrgSelection(options = {}) {
50
+ return new Promise(async (resolve, reject) => {
51
+ const { msg = '', isSource = true } = options || {};
52
+ const orgList = await this.getOrganizationChoices(msg).catch(reject);
53
+
54
+ if (orgList) {
55
+ const orgSelected = await inquirer.prompt(orgList);
56
+
57
+ if (isSource) {
58
+ config.sourceOrg = orgUidList[orgSelected.Organization];
59
+ } else {
60
+ config.targetOrg = orgUidList[orgSelected.Organization];
61
+ }
62
+
63
+ resolve(orgSelected);
64
+ }
65
+ });
66
+ }
67
+
68
+ #handleStackSelection(options = {}) {
69
+ return new Promise(async (resolve, reject) => {
70
+ const { org = {}, msg = '', isSource = true } = options || {};
71
+ const stackList = await this.getStack(org, msg, isSource).catch(reject);
72
+
73
+ if (stackList) {
74
+ const selectedStack = await inquirer.prompt(stackList);
75
+
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
+ }
84
+
85
+ resolve(selectedStack);
86
+ }
87
+ });
48
88
  }
49
89
 
50
- async start() {
90
+ start() {
51
91
  return new Promise(async (resolve, reject) => {
52
- oraMessage = "Choose an organization where your source stack exists:"
53
- // export section starts from here
54
- let orgdetails = this.getOrganizationChoices(oraMessage)
55
- orgdetails
56
- .then(async (orgList)=>{
57
- var stackMessage = 'Select the source stack'
58
- var orgSelected = await inquirer.prompt(orgList)
59
- let stackDetails = this.getStack(orgSelected, stackMessage)
60
- stackDetails
61
- .then(async (stackList)=> {
62
- let stackSelected = await inquirer.prompt(stackList)
63
- config.source_stack = stackUidList[stackSelected.stack]
64
- master_locale = masterLocaleList[stackSelected.stack]
65
- config.sourceStackName = stackSelected.stack
66
- stackName.default = "Copy of " + stackSelected.stack
67
- let cmdExport = this.cmdExport()
68
- cmdExport.then(async () => {
69
- //Import section starts from here
70
- var stackCreateConfirmation = await inquirer.prompt(stackCreationConfirmation)
71
- if (stackCreateConfirmation.stackCreate !== true) {
72
- oraMessage = 'Choose an organization where the destination stack exists: '
73
- let orgdetails = this.getOrganizationChoices(oraMessage)
74
- orgdetails
75
- .then(async (orgList)=>{
76
- var stackMessage = 'Choose the destination stack:'
77
- var orgSelected = await inquirer.prompt(orgList)
78
- let stackDetails = this.getStack(orgSelected, stackMessage)
79
- stackDetails
80
- .then(async (stackList)=> {
81
- let stackSelected = await inquirer.prompt(stackList)
82
- config.target_stack = stackUidList[stackSelected.stack]
83
- config.destinationStackName = stackSelected.stack
84
- this.cloneTypeSelection()
85
- .then((msgData)=>{
86
- return resolve(msgData)
87
- }).catch((error)=>{
88
- return reject(error.errorMessage)
89
- })
90
- }).catch((error) => {
91
- return reject( error.errorMessage)
92
- })
93
- }).catch((error)=>{
94
- return reject(error.errorMessage)
95
- })
96
- } else {
97
- oraMessage = 'Choose an organization where you want to create a stack: '
98
- let orgdetails = this.getOrganizationChoices(oraMessage)
99
- orgdetails
100
- .then(async (orgList)=>{
101
- var orgSelected = await inquirer.prompt(orgList)
102
- let orgUid = orgUidList[orgSelected.Organization]
103
- this.createNewStack(orgUid)
104
- .then(()=>{
105
- this.cloneTypeSelection()
106
- .then((msgData)=>{
107
- return resolve(msgData)
108
- }).catch((error) => {
109
- return reject(error)
110
- })
111
- }).catch((error)=>{
112
- return reject(error.errorMessage + ' Contact the Organization owner for Stack Creation access.')
113
- })
114
- }).catch((error) => {
115
- return reject(error.errorMessage)
116
- })
117
- }
118
- }).catch((error) => {
119
- return reject(error)
120
- })
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
+ );
98
+
99
+ if (org) {
100
+ await this.#handleStackSelection({
101
+ org,
102
+ isSource,
103
+ msg: stackMsg,
121
104
  })
122
- })
123
- })
105
+ .then(_resolve)
106
+ .catch((error) => reject(error.errorMessage));
107
+ }
108
+ });
109
+ };
110
+
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
+ }
118
+
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);
122
+
123
+ if (!config.sourceStackBranch) {
124
+ try {
125
+ const branches = await client.stack({ api_key: config.source_stack }).branch().query().find();
126
+
127
+ if (branches && branches.items && branches.items.length) {
128
+ config.sourceStackBranch = 'main';
129
+ }
130
+ } catch (_error) {}
131
+ }
132
+
133
+ // NOTE Import section
134
+ if (exportRes) {
135
+ let canCreateStack = false;
136
+
137
+ if (!config.target_stack) {
138
+ canCreateStack = await inquirer.prompt(stackCreationConfirmation);
139
+ }
140
+
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,
147
+ );
148
+ }
149
+
150
+ if (config.target_stack) {
151
+ this.cloneTypeSelection()
152
+ .then(resolve)
153
+ .catch((error) => reject(error.errorMessage));
154
+ }
155
+ } 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
+ });
164
+
165
+ if (config.target_stack) {
166
+ this.cloneTypeSelection().then(resolve).catch(reject);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ });
124
172
  }
125
173
 
126
- getOrganizationChoices = async (oraMessage) => {
174
+ getOrganizationChoices = async (orgMessage) => {
127
175
  let orgChoice = {
128
176
  type: 'list',
129
177
  name: 'Organization',
130
- message: oraMessage !== undefined ? oraMessage : "Choose an organization",
178
+ message: orgMessage !== undefined ? orgMessage : 'Choose an organization',
131
179
  choices: [],
132
- }
180
+ };
133
181
  return new Promise(async (resolve, reject) => {
134
- const spinner = ora("Fetching Organization").start()
182
+ const spinner = ora('Fetching Organization').start();
135
183
  try {
136
- let organizations = await client.organization().fetchAll({ limit: 100 })
137
- spinner.succeed("Fetched Organization")
184
+ let organizations = await client.organization().fetchAll({ limit: 100 });
185
+ spinner.succeed('Fetched Organization');
138
186
  for (let i = 0; i < organizations.items.length; i++) {
139
- orgUidList[organizations.items[i].name] = organizations.items[i].uid
140
- orgChoice.choices.push(organizations.items[i].name)
187
+ orgUidList[organizations.items[i].name] = organizations.items[i].uid;
188
+ orgChoice.choices.push(organizations.items[i].name);
141
189
  }
142
- return resolve(orgChoice)
190
+ return resolve(orgChoice);
143
191
  } catch (e) {
144
- spinner.fail()
145
- return reject(e)
192
+ spinner.fail();
193
+ return reject(e);
146
194
  }
147
- })
148
- }
195
+ });
196
+ };
149
197
 
150
198
  getStack = async (answer, stkMessage) => {
151
199
  return new Promise(async (resolve, reject) => {
152
200
  let stackChoice = {
153
201
  type: 'list',
154
202
  name: 'stack',
155
- message: stkMessage,
156
- message: stkMessage !== undefined ? stkMessage : "Select the stack",
203
+ message: stkMessage !== undefined ? stkMessage : 'Select the stack',
157
204
  choices: [],
158
- }
159
- const spinner = ora('Fetching stacks').start()
205
+ };
206
+ const spinner = ora('Fetching stacks').start();
160
207
  try {
161
- let orgUid = orgUidList[answer.Organization]
162
- let stackList = client.stack().query({ organization_uid: orgUid }).find()
208
+ const organization_uid = orgUidList[answer.Organization];
209
+ const stackList = client.stack().query({ organization_uid }).find();
163
210
  stackList
164
- .then(async stacklist => {
211
+ .then((stacklist) => {
165
212
  for (let j = 0; j < stacklist.items.length; j++) {
166
- stackUidList[stacklist.items[j].name] = stacklist.items[j].api_key
167
- masterLocaleList[stacklist.items[j].name] = stacklist.items[j].master_locale
168
- stackChoice.choices.push(stacklist.items[j].name)
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);
169
216
  }
170
- spinner.succeed("Fetched stack")
171
- return resolve(stackChoice)
172
- }).catch(error => {
173
- spinner.fail()
174
- return reject(error)
217
+ spinner.succeed('Fetched stack');
218
+ return resolve(stackChoice);
175
219
  })
220
+ .catch((error) => {
221
+ spinner.fail();
222
+ return reject(error);
223
+ });
176
224
  } catch (e) {
177
- spinner.fail()
178
- return reject(e)
225
+ spinner.fail();
226
+ return reject(e);
179
227
  }
180
- })
181
- }
228
+ });
229
+ };
182
230
 
183
231
  async createNewStack(orgUid) {
184
232
  return new Promise(async (resolve, reject) => {
185
- let inputvalue = await inquirer.prompt(stackName)
186
- let stack = { name: inputvalue.stack, master_locale: master_locale }
187
- const spinner = ora('Creating New stack').start()
188
- let newStack = client.stack().create({ stack }, { organization_uid: orgUid })
233
+ let inputvalue;
234
+
235
+ if (!config.stackName) {
236
+ inputvalue = await inquirer.prompt(stackName);
237
+ } else {
238
+ inputvalue = { stack: config.stackName };
239
+ }
240
+
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 });
189
244
  newStack
190
- .then(result => {
191
- spinner.succeed("New Stack created Successfully name as " + result.name)
192
- config.target_stack = result.api_key
193
- config.destinationStackName = result.name
194
- return resolve(result)
195
- }).catch(error => {
196
- spinner.fail()
197
- return reject(error)
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);
198
250
  })
199
- })
251
+ .catch((error) => {
252
+ spinner.fail();
253
+ return reject(error);
254
+ });
255
+ });
200
256
  }
201
257
 
202
258
  async cloneTypeSelection() {
203
259
  return new Promise(async (resolve, reject) => {
204
- let cloneTypeSelection = [{
205
- type: 'list',
206
- name: 'type',
207
- message: 'Choose the type of data to clone:',
208
- choices: ["Structure (all modules except entries & assets)",
209
- "Structure with content (all modules including entries & assets)"
210
- ]
211
- }]
212
- var selectedValue = await inquirer.prompt(cloneTypeSelection)
213
- let cloneType = selectedValue.type
214
- config['data'] = path.join(__dirname.split("src")[0], 'contents')
215
- if (cloneType === "Structure (all modules except entries & assets)") {
216
- config['modules'] = structureList
217
- let cmdImport = this.cmdImport()
218
- cmdImport.then(() => {
219
- return resolve("Stack clone Structure completed")
220
- }).catch((error) => {
221
- return reject(error)
222
- })
260
+ const choices = [
261
+ 'Structure (all modules except entries & assets)',
262
+ 'Structure with content (all modules including entries & assets)',
263
+ ];
264
+ const cloneTypeSelection = [
265
+ {
266
+ choices,
267
+ type: 'list',
268
+ name: 'type',
269
+ message: 'Choose the type of data to clone:',
270
+ },
271
+ ];
272
+ let successMsg;
273
+ let selectedValue = {};
274
+ config['data'] = path.join(__dirname.split('src')[0], 'contents', config.sourceStackBranch || '');
275
+
276
+ if (!config.cloneType) {
277
+ selectedValue = await inquirer.prompt(cloneTypeSelection);
278
+ }
279
+
280
+ if (config.cloneType === 'a' || selectedValue.type === 'Structure (all modules except entries & assets)') {
281
+ config['modules'] = structureList;
282
+ successMsg = 'Stack clone Structure completed';
223
283
  } else {
224
- let cmdImport = this.cmdImport()
225
- cmdImport.then(() => {
226
- return resolve("Stack clone completed with structure and content")
227
- }).catch((error) => {
228
- return reject(error)
229
- })
284
+ successMsg = 'Stack clone completed with structure and content';
230
285
  }
231
- })
286
+
287
+ this.cmdImport()
288
+ .then(() => resolve(successMsg))
289
+ .catch(reject);
290
+ });
232
291
  }
233
292
 
234
293
  async cmdExport() {
235
294
  return new Promise((resolve, reject) => {
236
- let exportData = exportCmd.run(['-A', '-s', config.source_stack, '-d', __dirname.split("src")[0]+'contents'])
237
- exportData.then(async () => {
238
- return resolve()
239
- }).catch(error => {
240
- return reject(error)
241
- })
242
- })
295
+ const cmd = ['-k', config.source_stack, '-d', __dirname.split('src')[0] + 'contents'];
296
+
297
+ if (config.source_alias) {
298
+ cmd.push('-a', config.source_alias);
299
+ }
300
+ if (config.sourceStackBranch) {
301
+ cmd.push('--branch', config.sourceStackBranch);
302
+ }
303
+
304
+ let exportData = exportCmd.run(cmd);
305
+ exportData.then(() => resolve(true)).catch(reject);
306
+ });
243
307
  }
244
308
 
245
309
  async cmdImport() {
246
- return new Promise(async (resolve, reject) => {
247
- await importCmd.run(['-A', '-c', path.join(__dirname, 'dummyConfig.json')])
248
- return resolve()
249
- })
310
+ return new Promise(async (resolve, _reject) => {
311
+ const cmd = ['-c', path.join(__dirname, 'dummyConfig.json')];
312
+
313
+ if (config.destination_alias) {
314
+ cmd.push('-a', config.destination_alias);
315
+ }
316
+ if (config.sourceStackBranch) {
317
+ cmd.push('-d', path.join(__dirname, config.sourceStackBranch));
318
+ }
319
+ if (config.targetStackBranch) {
320
+ cmd.push('--branch', config.targetStackBranch);
321
+ }
322
+ if (config.importWebhookStatus) {
323
+ cmd.push('--import-webhook-status', config.importWebhookStatus);
324
+ }
325
+
326
+ await importCmd.run(cmd);
327
+ return resolve();
328
+ });
250
329
  }
251
330
  }
252
331
 
253
332
  module.exports = {
254
- CloneHandler, client,
255
- }
333
+ CloneHandler,
334
+ client,
335
+ };
@@ -1,22 +1,21 @@
1
- const contentstacksdk = require('@contentstack/management')
2
-
3
-
4
- exports.Client = function (config) {
1
+ const contentstacksdk = require('@contentstack/management');
5
2
 
3
+ exports.Client = function (config) {
6
4
  const option = {
7
5
  host: config.host,
8
6
  authtoken: config.auth_token,
9
7
  maxContentLength: 100000000,
10
8
  maxBodyLength: 1000000000,
11
- logHandler: (level, data) => {}
12
- }
9
+ logHandler: (_level, _data) => {
10
+ // empty log handler
11
+ },
12
+ };
13
13
 
14
14
  if (config.target_stack) {
15
- option.api_key = config.target_stack
15
+ option.api_key = config.target_stack;
16
16
  }
17
17
  if (config.source_stack) {
18
- option.api_key = config.source_stack
18
+ option.api_key = config.source_stack;
19
19
  }
20
- const client = contentstacksdk.client(option)
21
- return client
22
- }
20
+ return contentstacksdk.client(option);
21
+ };
@@ -9,15 +9,18 @@ var path = require('path');
9
9
  var mkdirp = require('mkdirp');
10
10
  var slice = Array.prototype.slice;
11
11
 
12
- function returnString (args) {
12
+ function returnString(args) {
13
13
  var returnStr = '';
14
14
  if (args && args.length) {
15
- returnStr = args.map(function (item) {
16
- if (item && typeof (item) === 'object') {
17
- return JSON.stringify(item);
18
- }
19
- return item;
20
- }).join(' ').trim();
15
+ returnStr = args
16
+ .map(function (item) {
17
+ if (item && typeof item === 'object') {
18
+ return JSON.stringify(item);
19
+ }
20
+ return item;
21
+ })
22
+ .join(' ')
23
+ .trim();
21
24
  }
22
25
  return returnStr;
23
26
  }
@@ -27,35 +30,37 @@ var myCustomLevels = {
27
30
  error: 0,
28
31
  warn: 1,
29
32
  info: 2,
30
- debug: 3
33
+ debug: 3,
31
34
  },
32
35
  colors: {
33
36
  info: 'blue',
34
37
  debug: 'green',
35
38
  warn: 'yellow',
36
- error: 'red'
37
- }
39
+ error: 'red',
40
+ },
38
41
  };
39
42
 
40
- function init (_logPath, logfileName) {
41
- var logsDir = path.resolve(_logPath, 'logs', 'import')
43
+ function init(_logPath, logfileName) {
44
+ var logsDir = path.resolve(_logPath, 'logs', 'import');
42
45
  // Create dir if doesn't already exist
43
- mkdirp.sync(logsDir)
46
+ mkdirp.sync(logsDir);
44
47
  var logPath = path.join(logsDir, logfileName + '.log');
45
48
 
46
- var transports = [new(winston.transports.File)({
47
- filename: logPath,
48
- maxFiles: 20,
49
- maxsize: 1000000,
50
- tailable: true,
51
- json: true
52
- })];
49
+ var transports = [
50
+ new winston.transports.File({
51
+ filename: logPath,
52
+ maxFiles: 20,
53
+ maxsize: 1000000,
54
+ tailable: true,
55
+ json: true,
56
+ }),
57
+ ];
53
58
 
54
- transports.push(new(winston.transports.Console)());
59
+ transports.push(new winston.transports.Console());
55
60
 
56
- var logger = new(winston.Logger)({
61
+ var logger = winston.createLogger({
57
62
  transports: transports,
58
- levels: myCustomLevels.levels
63
+ levels: myCustomLevels.levels,
59
64
  });
60
65
 
61
66
  return {
@@ -86,14 +91,14 @@ function init (_logPath, logfileName) {
86
91
  if (logString) {
87
92
  logger.log('debug', logString);
88
93
  }
89
- }
94
+ },
90
95
  };
91
96
  }
92
97
 
93
98
  exports.addlogs = async (config, message, type) => {
94
99
  if (type !== 'error') {
95
- init(config.oldPath, type).log(message)
100
+ init(config.oldPath, type).log(message);
96
101
  } else {
97
- init(config.oldPath, type).error(message)
102
+ init(config.oldPath, type).error(message);
98
103
  }
99
- }
104
+ };
@@ -1,102 +0,0 @@
1
- const {Command} = require('@contentstack/cli-command')
2
- const Configstore = require('configstore')
3
- const credStore = new Configstore('contentstack_cli')
4
- const {CloneHandler} = require('../../lib/util/clone-handler')
5
- let config = require('../../lib/util/dummyConfig.json')
6
- const path = require('path')
7
- const rimraf = require('rimraf')
8
- let pathdir = path.join(__dirname.split('src')[0], 'contents')
9
- const { readdirSync } = require('fs')
10
-
11
- class StackCloneCommand extends Command {
12
- async run() {
13
- try {
14
- await this.removeContentDirIfNotEmptyBeforeClone(pathdir) // NOTE remove if folder not empty before clone
15
-
16
- this.registerCleanupOnInterrupt(pathdir)
17
- let _authToken = credStore.get('authtoken')
18
- if (_authToken && _authToken !== undefined) {
19
- config.auth_token = _authToken
20
- config.host = this.cmaHost
21
- config.cdn = this.cdaHost
22
- const cloneHandler = new CloneHandler(config)
23
- await cloneHandler.start()
24
- let successMessage = 'Stack cloning process have been completed successfully'
25
- await this.cleanUp(pathdir, successMessage)
26
- } else {
27
- console.log("AuthToken is not present in local drive, Hence use 'csdx auth:login' command for login");
28
- }
29
- } catch (error) {
30
- await this.cleanUp(pathdir)
31
- // eslint-disable-next-line no-console
32
- console.log(error.message || error)
33
- }
34
- }
35
-
36
- async removeContentDirIfNotEmptyBeforeClone(dir) {
37
- try {
38
- const dirNotEmpty = readdirSync(dir).length
39
-
40
- if (dirNotEmpty) {
41
- await this.cleanUp(dir)
42
- }
43
- } catch (error) {
44
- const omit = ['ENOENT'] // NOTE add emittable error codes in the array
45
-
46
- if (!omit.includes(error.code)) {
47
- console.log(error.message)
48
- }
49
- }
50
- }
51
-
52
- async cleanUp(pathDir, message) {
53
- return new Promise(resolve => {
54
- rimraf(pathDir, function (err) {
55
- if (err)
56
- throw err
57
- if (message) {
58
- // eslint-disable-next-line no-console
59
- console.log(message)
60
- }
61
- resolve()
62
- })
63
- })
64
- }
65
-
66
- registerCleanupOnInterrupt(pathDir) {
67
- const interrupt = ['SIGINT', 'SIGQUIT', 'SIGTERM']
68
- const exceptions = ['unhandledRejection', 'uncaughtException']
69
-
70
- const cleanUp = async (exitOrError = null) => {
71
- // eslint-disable-next-line no-console
72
- console.log('\nCleaning up')
73
- await this.cleanUp(pathDir)
74
- // eslint-disable-next-line no-console
75
- console.log('done')
76
- // eslint-disable-next-line no-process-exit
77
-
78
- if (exitOrError instanceof Promise) {
79
- exitOrError.catch(error => {
80
- console.log(error && error.message || '')
81
- })
82
- } else if (exitOrError && exitOrError.message) {
83
- console.log(exitOrError.message)
84
- }
85
-
86
- if (exitOrError === true) process.exit()
87
- }
88
-
89
- exceptions.forEach(event => process.on(event, cleanUp))
90
- interrupt.forEach(signal => process.on(signal, () => cleanUp(true)))
91
- }
92
- }
93
-
94
- StackCloneCommand.description = `Clone data (structure or content or both) of a stack into another stack
95
- Use this plugin to automate the process of cloning a stack in a few steps.
96
- `
97
-
98
- StackCloneCommand.examples = [
99
- 'csdx cm:stack-clone',
100
- ]
101
-
102
- module.exports = StackCloneCommand