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