@contentstack/cli-cm-clone 0.1.0-beta.6 → 1.1.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,36 @@
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.1.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.1.0",
9
+ "@contentstack/cli-cm-import": "^1.1.0",
10
+ "@contentstack/cli-command": "^1.0.1",
11
+ "@contentstack/cli-utilities": "^1.0.2",
11
12
  "@contentstack/management": "^1.3.0",
12
- "@oclif/command": "^1.8.0",
13
- "@oclif/config": "^1.17.0",
14
- "async": "^3.2.0",
13
+ "@oclif/command": "^1.8.16",
14
+ "@oclif/config": "^1.18.3",
15
+ "chalk": "^4.1.0",
16
+ "async": "^3.2.4",
15
17
  "child_process": "^1.0.2",
16
- "configstore": "^5.0.1",
17
18
  "fancy-test": "^1.4.10",
18
19
  "inquirer": "^7.3.3",
19
20
  "ora": "^5.1.0",
20
21
  "rimraf": "^3.0.2",
21
- "winston": "^3.3.3"
22
+ "winston": "^3.7.2"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@oclif/dev-cli": "^1.22.2",
25
- "@oclif/plugin-help": "^3.2.0",
26
+ "@oclif/plugin-help": "^5.1.12",
26
27
  "@oclif/test": "^1.2.7",
27
28
  "chai": "^4.2.0",
28
- "eslint": "^5.16.0",
29
+ "eslint": "^8.18.0",
29
30
  "eslint-config-oclif": "^3.1.0",
30
31
  "globby": "^10.0.2",
31
32
  "jest": "^26.6.3",
32
- "mocha": "^8.2.1",
33
+ "mocha": "^10.0.0",
33
34
  "nyc": "^14.1.1",
34
35
  "sinon": "^9.2.4"
35
36
  },
@@ -59,5 +60,10 @@
59
60
  "scripts": {
60
61
  "test": "nyc --reporter=html mocha \"test/**/*.test.js\"",
61
62
  "version": "oclif-dev readme && git add README.md"
63
+ },
64
+ "csdxConfig": {
65
+ "expiredCommands": {
66
+ "cm:stack-clone": "csdx cm:stacks:clone"
67
+ }
62
68
  }
63
- }
69
+ }
@@ -0,0 +1,249 @@
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
+ 'master-locale': masterLocale
27
+ } = cloneCommandFlags;
28
+
29
+ const handleClone = async () => {
30
+ const listOfTokens = configHandler.get('tokens');
31
+
32
+ if (cloneType) {
33
+ config.cloneType = cloneType;
34
+ }
35
+ if (stackName) {
36
+ config.stackName = stackName;
37
+ }
38
+ if (sourceStackBranch) {
39
+ config.sourceStackBranch = sourceStackBranch;
40
+ }
41
+ if (targetStackBranch) {
42
+ config.targetStackBranch = targetStackBranch;
43
+ }
44
+ if (sourceStackApiKey) {
45
+ config.source_stack = sourceStackApiKey;
46
+ }
47
+ if (destinationStackApiKey) {
48
+ config.target_stack = destinationStackApiKey;
49
+ }
50
+ if (sourceManagementTokenAlias && listOfTokens[sourceManagementTokenAlias]) {
51
+ config.source_alias = sourceManagementTokenAlias;
52
+ config.source_stack = listOfTokens[sourceManagementTokenAlias].apiKey;
53
+ } else if (sourceManagementTokenAlias) {
54
+ console.log(`Provided source token alias (${sourceManagementTokenAlias}) not found in your config.!`);
55
+ }
56
+ if (destinationManagementTokenAlias && listOfTokens[destinationManagementTokenAlias]) {
57
+ config.destination_alias = destinationManagementTokenAlias;
58
+ config.target_stack = listOfTokens[destinationManagementTokenAlias].apiKey;
59
+ } else if (destinationManagementTokenAlias) {
60
+ console.log(
61
+ `Provided destination token alias (${destinationManagementTokenAlias}) not found in your config.!`,
62
+ );
63
+ }
64
+ if (importWebhookStatus) {
65
+ config.importWebhookStatus = importWebhookStatus;
66
+ }
67
+
68
+ await this.removeContentDirIfNotEmptyBeforeClone(pathdir); // NOTE remove if folder not empty before clone
69
+ this.registerCleanupOnInterrupt(pathdir);
70
+
71
+ if (masterLocale) {
72
+ config.master_locale = { code: masterLocale }
73
+ }
74
+ config.auth_token = _authToken;
75
+ config.host = this.cmaHost;
76
+ config.cdn = this.cdaHost;
77
+ config.pathDir = pathdir;
78
+ const cloneHandler = new CloneHandler(config);
79
+ cloneHandler.execute().catch();
80
+ }
81
+
82
+ if (sourceManagementTokenAlias && destinationManagementTokenAlias) {
83
+ if (sourceStackBranch || targetStackBranch) {
84
+ if (_authToken) {
85
+ handleClone();
86
+ } else {
87
+ console.log('Please login to execute this command, csdx auth:login');
88
+ this.exit(1);
89
+ }
90
+ } else {
91
+ handleClone();
92
+ }
93
+ } else if (_authToken) {
94
+ handleClone();
95
+ } else {
96
+ console.log('Please login to execute this command, csdx auth:login');
97
+ this.exit(1);
98
+ }
99
+ } catch (error) {
100
+ if (error) {
101
+ await this.cleanUp(pathdir);
102
+ // eslint-disable-next-line no-console
103
+ console.log(error.message || error);
104
+ }
105
+ }
106
+ }
107
+
108
+ async removeContentDirIfNotEmptyBeforeClone(dir) {
109
+ try {
110
+ const dirNotEmpty = readdirSync(dir).length;
111
+
112
+ if (dirNotEmpty) {
113
+ await this.cleanUp(dir);
114
+ }
115
+ } catch (error) {
116
+ const omit = ['ENOENT']; // NOTE add emittable error codes in the array
117
+
118
+ if (!omit.includes(error.code)) {
119
+ console.log(error.message);
120
+ }
121
+ }
122
+ }
123
+
124
+ cleanUp(pathDir, message) {
125
+ return new Promise((resolve) => {
126
+ rimraf(pathDir, function (err) {
127
+ if (err) {
128
+ console.log('\nCleaning up');
129
+ const skipCodeArr = ['ENOENT', 'EBUSY', 'EPERM', 'EMFILE', 'ENOTEMPTY'];
130
+
131
+ if (skipCodeArr.includes(err.code)) {
132
+ process.exit();
133
+ }
134
+ }
135
+
136
+ if (message) {
137
+ // eslint-disable-next-line no-console
138
+ console.log(message);
139
+ }
140
+
141
+ resolve();
142
+ });
143
+ });
144
+ }
145
+
146
+ registerCleanupOnInterrupt(pathDir) {
147
+ const interrupt = ['SIGINT', 'SIGQUIT', 'SIGTERM'];
148
+ const exceptions = ['unhandledRejection', 'uncaughtException'];
149
+
150
+ const cleanUp = async (exitOrError) => {
151
+ if (exitOrError) {
152
+ // eslint-disable-next-line no-console
153
+ console.log('\nCleaning up');
154
+ await this.cleanUp(pathDir)
155
+ // eslint-disable-next-line no-console
156
+ console.log('done');
157
+ // eslint-disable-next-line no-process-exit
158
+
159
+ if (exitOrError instanceof Promise) {
160
+ exitOrError.catch((error) => {
161
+ console.log((error && error.message) || '');
162
+ });
163
+ } else if (exitOrError && exitOrError.message) {
164
+ console.log(exitOrError.message);
165
+ } else if (exitOrError && exitOrError.errorMessage) {
166
+ console.log(exitOrError.message);
167
+ }
168
+
169
+ if (exitOrError === true) process.exit();
170
+ }
171
+ };
172
+
173
+ exceptions.forEach((event) => process.on(event, cleanUp));
174
+ interrupt.forEach((signal) => process.on(signal, () => cleanUp(true)));
175
+ }
176
+ }
177
+
178
+ StackCloneCommand.description = `Clone data (structure/content or both) of a stack into another stack
179
+ Use this plugin to automate the process of cloning a stack in few steps.
180
+ `;
181
+
182
+ StackCloneCommand.examples = [
183
+ 'csdx cm:stacks:clone',
184
+ 'csdx cm:stacks:clone --source-branch <source-branch-name> --target-branch <target-branch-name>',
185
+ 'csdx cm:stacks:clone --source-stack-api-key <apiKey> --destination-stack-api-key <apiKey>',
186
+ 'csdx cm:stacks:clone --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>',
187
+ 'csdx cm:stacks:clone --source-branch --target-branch --source-management-token-alias <management token alias> --destination-management-token-alias <management token alias>',
188
+ '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>',
189
+ ];
190
+
191
+ StackCloneCommand.aliases = ['cm:stack-clone'];
192
+
193
+ StackCloneCommand.flags = {
194
+ 'source-branch': flags.string({
195
+ required: false,
196
+ multiple: false,
197
+ description: 'Branch of the source stack.',
198
+ }),
199
+ 'target-branch': flags.string({
200
+ required: false,
201
+ multiple: false,
202
+ description: 'Branch of the target stack.',
203
+ }),
204
+ 'source-management-token-alias': flags.string({
205
+ required: false,
206
+ multiple: false,
207
+ description: 'Source API key of the target stack token alias.',
208
+ }),
209
+ 'destination-management-token-alias': flags.string({
210
+ required: false,
211
+ multiple: false,
212
+ description: 'Source API key of the target stack token alias.',
213
+ }),
214
+ 'stack-name': flags.string({
215
+ char: 'n',
216
+ required: false,
217
+ multiple: false,
218
+ description: 'Name for the new stack to store the cloned content.',
219
+ }),
220
+ type: flags.string({
221
+ required: false,
222
+ multiple: false,
223
+ options: ['a', 'b'],
224
+ description: `Type of data to clone
225
+ a) Structure (all modules except entries & assets)
226
+ b) Structure with content (all modules including entries & assets)
227
+ `,
228
+ }),
229
+ 'source-stack-api-key': flags.string({
230
+ description: 'Source stack API Key',
231
+ }),
232
+ 'destination-stack-api-key': flags.string({
233
+ description: 'Destination stack API Key',
234
+ }),
235
+ 'import-webhook-status': flags.string({
236
+ description: 'Webhook state',
237
+ options: ['disable', 'current'],
238
+ required: false,
239
+ default: 'disable',
240
+ }),
241
+ 'master-locale': flags.string({
242
+ description: 'Master language for stack clone',
243
+ }),
244
+ };
245
+
246
+ StackCloneCommand.usage =
247
+ '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]';
248
+
249
+ module.exports = StackCloneCommand;
@@ -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, null, 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 };