@contentstack/cli-cm-import 1.2.4 → 1.3.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/README.md +84 -24
- package/oclif.manifest.json +129 -1
- package/package.json +6 -8
- package/src/app.js +47 -59
- package/src/commands/cm/stacks/import.js +1 -1
- package/src/config/default.js +1 -1
- package/src/lib/import/assets.js +261 -247
- package/src/lib/import/content-types.js +19 -21
- package/src/lib/import/custom-roles.js +7 -17
- package/src/lib/import/entries.js +344 -360
- package/src/lib/import/environments.js +5 -7
- package/src/lib/import/extensions.js +6 -9
- package/src/lib/import/global-fields.js +7 -17
- package/src/lib/import/labels.js +5 -8
- package/src/lib/import/locales.js +5 -10
- package/src/lib/import/marketplace-apps.js +389 -414
- package/src/lib/import/webhooks.js +4 -6
- package/src/lib/import/workflows.js +6 -15
- package/src/lib/util/extensionsUidReplace.js +2 -33
- package/src/lib/util/index.js +71 -72
- package/src/lib/util/login.js +45 -42
- package/src/lib/util/lookupReplaceAssets.js +6 -97
- package/src/lib/util/marketplace-app-helper.js +10 -37
- package/src/lib/util/upload.js +30 -23
- package/src/lib/util/contentstack-management-sdk.js +0 -41
|
@@ -8,547 +8,522 @@ const _ = require('lodash');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const chalk = require('chalk');
|
|
10
10
|
const mkdirp = require('mkdirp');
|
|
11
|
-
const
|
|
11
|
+
const contentstack = require('@contentstack/management');
|
|
12
|
+
const { cliux, HttpClient, NodeCrypto, managementSDKClient } = require('@contentstack/cli-utilities');
|
|
12
13
|
|
|
14
|
+
const { formatError } = require('../util');
|
|
13
15
|
let config = require('../../config/default');
|
|
14
16
|
const { addlogs: log } = require('../util/log');
|
|
15
17
|
const { readFileSync, writeFile } = require('../util/fs');
|
|
16
|
-
const
|
|
17
|
-
const { getDeveloperHubUrl, getInstalledExtensions } = require('../util/marketplace-app-helper');
|
|
18
|
+
const { getDeveloperHubUrl, getAllStackSpecificApps } = require('../util/marketplace-app-helper');
|
|
18
19
|
|
|
19
20
|
module.exports = class ImportMarketplaceApps {
|
|
20
21
|
client;
|
|
22
|
+
httpClient;
|
|
23
|
+
appOrginalName;
|
|
24
|
+
appUidMapping = {};
|
|
25
|
+
appNameMapping = {};
|
|
21
26
|
marketplaceApps = [];
|
|
22
|
-
|
|
27
|
+
installationUidMapping = {};
|
|
23
28
|
developerHubBaseUrl = null;
|
|
24
29
|
marketplaceAppFolderPath = '';
|
|
25
30
|
marketplaceAppConfig = config.modules.marketplace_apps;
|
|
26
31
|
|
|
27
|
-
constructor(
|
|
28
|
-
this.config = _.merge(config,
|
|
32
|
+
constructor(importConfig, stackAPIClient) {
|
|
33
|
+
this.config = _.merge(config, importConfig);
|
|
34
|
+
this.stackAPIClient = stackAPIClient;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
async start() {
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
38
|
+
this.developerHubBaseUrl = this.config.developerHubBaseUrl || (await getDeveloperHubUrl(this.config));
|
|
39
|
+
this.client = contentstack.client({ authtoken: this.config.auth_token, endpoint: this.developerHubBaseUrl });
|
|
40
|
+
this.mapperDirPath = path.resolve(this.config.data, 'mapper', 'marketplace_apps');
|
|
41
|
+
this.uidMapperPath = path.join(this.mapperDirPath, 'uid-mapping.json');
|
|
34
42
|
this.marketplaceAppFolderPath = path.resolve(this.config.data, this.marketplaceAppConfig.dirName);
|
|
35
|
-
this.marketplaceApps =
|
|
36
|
-
|
|
37
|
-
'app_uid',
|
|
43
|
+
this.marketplaceApps = readFileSync(
|
|
44
|
+
path.resolve(this.marketplaceAppFolderPath, this.marketplaceAppConfig.fileName),
|
|
38
45
|
);
|
|
39
|
-
this.marketplaceAppsUid = _.map(this.marketplaceApps, 'uid');
|
|
40
46
|
|
|
41
|
-
if (
|
|
47
|
+
if (_.isEmpty(this.marketplaceApps)) {
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
} else if (!this.config.auth_token) {
|
|
42
50
|
cliux.print(
|
|
43
|
-
'
|
|
51
|
+
'\nWARNING!!! To import Marketplace apps, you must be logged in. Please check csdx auth:login --help to log in\n',
|
|
44
52
|
{ color: 'yellow' },
|
|
45
53
|
);
|
|
46
54
|
return Promise.resolve();
|
|
47
|
-
} else if (_.isEmpty(this.marketplaceApps)) {
|
|
48
|
-
return Promise.resolve();
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
await this.getOrgUid();
|
|
52
|
-
|
|
58
|
+
|
|
59
|
+
this.httpClient = new HttpClient({
|
|
60
|
+
headers: {
|
|
61
|
+
authtoken: this.config.auth_token,
|
|
62
|
+
organization_uid: this.config.org_uid,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!fs.existsSync(this.mapperDirPath)) {
|
|
67
|
+
mkdirp.sync(this.mapperDirPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return this.startInstallation();
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const self = this;
|
|
61
|
-
// NOTE get org uid
|
|
62
|
-
if (self.config.auth_token) {
|
|
63
|
-
const stack = await this.client
|
|
64
|
-
.stack({ api_key: self.config.target_stack, authtoken: self.config.auth_token })
|
|
73
|
+
async getOrgUid() {
|
|
74
|
+
if (this.config.auth_token) {
|
|
75
|
+
const tempAPIClient = await managementSDKClient({ host: this.config.host });
|
|
76
|
+
const tempStackData = await tempAPIClient
|
|
77
|
+
.stack({ api_key: this.config.target_stack })
|
|
65
78
|
.fetch()
|
|
66
79
|
.catch((error) => {
|
|
67
80
|
console.log(error);
|
|
68
|
-
log(self.config, 'Starting marketplace app installation', 'success');
|
|
69
81
|
});
|
|
70
82
|
|
|
71
|
-
if (
|
|
72
|
-
|
|
83
|
+
if (tempStackData && tempStackData.org_uid) {
|
|
84
|
+
this.config.org_uid = tempStackData.org_uid;
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async getEncryptionKeyAndValidate(defaultValue, retry = 1) {
|
|
78
|
-
const appConfig =
|
|
79
|
-
_.find(this.marketplaceApps, 'configuration') ||
|
|
80
|
-
_.find(this.marketplaceApps, 'server_configuration.configuration');
|
|
81
|
-
|
|
82
|
-
if (appConfig) {
|
|
83
|
-
const encryptionKey = await cliux.inquire({
|
|
84
|
-
type: 'input',
|
|
85
|
-
name: 'name',
|
|
86
|
-
default: defaultValue,
|
|
87
|
-
validate: (key) => {
|
|
88
|
-
if (!key) return "Encryption key can't be empty.";
|
|
89
|
-
|
|
90
|
-
return true;
|
|
91
|
-
},
|
|
92
|
-
message: 'Enter marketplace app configurations encryption key',
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const nodeCrypto = new NodeCrypto({ encryptionKey });
|
|
97
|
-
nodeCrypto.decrypt(appConfig.configuration || appConfig.server_configuration);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
if (retry < this.config.getEncryptionKeyMaxRetry && error.code === 'ERR_OSSL_EVP_BAD_DECRYPT') {
|
|
100
|
-
cliux.print('Provided encryption key is not valid or your data might be corrupted.!', { color: 'red' });
|
|
101
|
-
// NOTE max retry limit is 3
|
|
102
|
-
return this.getEncryptionKeyAndValidate(encryptionKey, retry + 1);
|
|
103
|
-
} else {
|
|
104
|
-
cliux.print('Maximum retry limit exceeded. Closing the process, please try again.!', { color: 'red' });
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
87
|
+
}
|
|
108
88
|
|
|
109
|
-
|
|
89
|
+
async getAndValidateEncryptionKey(defaultValue, retry = 1) {
|
|
90
|
+
let appConfig = _.find(
|
|
91
|
+
this.marketplaceApps,
|
|
92
|
+
({ configuration, server_configuration }) => !_.isEmpty(configuration) || !_.isEmpty(server_configuration),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (!appConfig) {
|
|
96
|
+
return defaultValue;
|
|
110
97
|
}
|
|
111
98
|
|
|
112
|
-
|
|
99
|
+
const encryptionKey = await cliux.inquire({
|
|
100
|
+
type: 'input',
|
|
101
|
+
name: 'name',
|
|
102
|
+
default: defaultValue,
|
|
103
|
+
validate: (key) => {
|
|
104
|
+
if (!key) return "Encryption key can't be empty.";
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
},
|
|
108
|
+
message: 'Enter marketplace app configurations encryption key',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
appConfig = !_.isEmpty(appConfig.configuration) ? appConfig.configuration : appConfig.server_configuration;
|
|
113
|
+
this.nodeCrypto = new NodeCrypto({ encryptionKey });
|
|
114
|
+
this.nodeCrypto.decrypt(appConfig);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (retry < this.config.getEncryptionKeyMaxRetry && error.code === 'ERR_OSSL_EVP_BAD_DECRYPT') {
|
|
117
|
+
cliux.print(
|
|
118
|
+
`Provided encryption key is not valid or your data might be corrupted.! attempt(${retry}/${this.config.getEncryptionKeyMaxRetry})`,
|
|
119
|
+
{ color: 'red' },
|
|
120
|
+
);
|
|
121
|
+
// NOTE max retry limit is 3
|
|
122
|
+
return this.getAndValidateEncryptionKey(encryptionKey, retry + 1);
|
|
123
|
+
} else {
|
|
124
|
+
cliux.print(
|
|
125
|
+
`Maximum retry limit exceeded. Closing the process, please try again.! attempt(${retry}/${this.config.getEncryptionKeyMaxRetry})`,
|
|
126
|
+
{ color: 'red' },
|
|
127
|
+
);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return encryptionKey;
|
|
113
133
|
}
|
|
114
134
|
|
|
115
135
|
/**
|
|
116
|
-
* @method
|
|
136
|
+
* @method startInstallation
|
|
117
137
|
* @returns {Promise<void>}
|
|
118
138
|
*/
|
|
119
|
-
|
|
120
|
-
const self = this;
|
|
139
|
+
async startInstallation() {
|
|
121
140
|
const cryptoArgs = {};
|
|
122
|
-
const headers = {
|
|
123
|
-
authtoken: self.config.auth_token,
|
|
124
|
-
organization_uid: self.config.org_uid,
|
|
125
|
-
};
|
|
126
141
|
|
|
127
|
-
if (
|
|
128
|
-
cryptoArgs['encryptionKey'] =
|
|
142
|
+
if (this.config.marketplaceAppEncryptionKey) {
|
|
143
|
+
cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
|
|
129
144
|
}
|
|
130
145
|
|
|
131
|
-
if (
|
|
132
|
-
cryptoArgs['encryptionKey'] =
|
|
146
|
+
if (this.config.forceStopMarketplaceAppsPrompt) {
|
|
147
|
+
cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
|
|
148
|
+
this.nodeCrypto = new NodeCrypto(cryptoArgs);
|
|
133
149
|
} else {
|
|
134
|
-
|
|
150
|
+
await this.getAndValidateEncryptionKey(this.config.marketplaceAppEncryptionKey);
|
|
135
151
|
}
|
|
136
152
|
|
|
137
|
-
const httpClient = new HttpClient().headers(headers);
|
|
138
|
-
const nodeCrypto = new NodeCrypto(cryptoArgs);
|
|
139
|
-
|
|
140
153
|
// NOTE install all private apps which is not available for stack.
|
|
141
|
-
await this.handleAllPrivateAppsCreationProcess(
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
// NOTE after private app installation, refetch marketplace apps from file
|
|
145
|
-
const marketplaceAppsFromFile = readFileSync(
|
|
146
|
-
path.resolve(this.marketplaceAppFolderPath, self.marketplaceAppConfig.fileName),
|
|
147
|
-
);
|
|
148
|
-
this.marketplaceApps = _.filter(marketplaceAppsFromFile, ({ uid }) => _.includes(this.marketplaceAppsUid, uid));
|
|
154
|
+
await this.handleAllPrivateAppsCreationProcess();
|
|
155
|
+
const installedApps = await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config);
|
|
149
156
|
|
|
150
|
-
log(
|
|
157
|
+
log(this.config, 'Starting marketplace app installation', 'success');
|
|
151
158
|
|
|
152
|
-
for (let app of
|
|
153
|
-
await
|
|
159
|
+
for (let app of this.marketplaceApps) {
|
|
160
|
+
await this.installApps(app, installedApps);
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
const uidMapper = await this.generateUidMapper();
|
|
164
|
+
await writeFile(this.uidMapperPath, {
|
|
165
|
+
app_uid: this.appUidMapping,
|
|
166
|
+
extension_uid: uidMapper || {},
|
|
167
|
+
installation_uid: this.installationUidMapping,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
159
170
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
171
|
+
async generateUidMapper() {
|
|
172
|
+
const listOfNewMeta = [];
|
|
173
|
+
const listOfOldMeta = [];
|
|
174
|
+
const extensionUidMapp = {};
|
|
175
|
+
const allInstalledApps = await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config);
|
|
163
176
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
177
|
+
for (const app of this.marketplaceApps) {
|
|
178
|
+
listOfOldMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat());
|
|
179
|
+
}
|
|
180
|
+
for (const app of allInstalledApps) {
|
|
181
|
+
listOfNewMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat());
|
|
182
|
+
}
|
|
183
|
+
for (const { extension_uid, name, path } of _.filter(listOfOldMeta, 'name')) {
|
|
184
|
+
const meta =
|
|
185
|
+
_.find(listOfNewMeta, { name, path }) || _.find(listOfNewMeta, { name: this.appNameMapping[name], path });
|
|
168
186
|
|
|
169
|
-
|
|
187
|
+
if (meta) {
|
|
188
|
+
extensionUidMapp[extension_uid] = meta.extension_uid;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
170
191
|
|
|
171
|
-
return
|
|
172
|
-
}
|
|
192
|
+
return extensionUidMapp;
|
|
193
|
+
}
|
|
173
194
|
|
|
174
195
|
/**
|
|
175
196
|
* @method handleAllPrivateAppsCreationProcess
|
|
176
197
|
* @param {Object} options
|
|
177
198
|
* @returns {Promise<void>}
|
|
178
199
|
*/
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const { httpClient } = options;
|
|
182
|
-
const listOfExportedPrivateApps = _.filter(self.marketplaceApps, { visibility: 'private' });
|
|
200
|
+
async handleAllPrivateAppsCreationProcess() {
|
|
201
|
+
const privateApps = _.filter(this.marketplaceApps, { manifest: { visibility: 'private' } });
|
|
183
202
|
|
|
184
|
-
if (_.isEmpty(
|
|
203
|
+
if (_.isEmpty(privateApps)) {
|
|
185
204
|
return Promise.resolve();
|
|
186
205
|
}
|
|
187
206
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
207
|
+
await this.getConfirmationToCreateApps(privateApps);
|
|
208
|
+
|
|
209
|
+
log(this.config, 'Starting developer hub private apps re-creation', 'success');
|
|
210
|
+
|
|
211
|
+
for (let app of privateApps) {
|
|
212
|
+
// NOTE keys can be passed to install new app in the developer hub
|
|
213
|
+
app.manifest = _.pick(app.manifest, [
|
|
214
|
+
'uid',
|
|
215
|
+
'name',
|
|
216
|
+
'description',
|
|
217
|
+
'icon',
|
|
218
|
+
'target_type',
|
|
219
|
+
'ui_location',
|
|
220
|
+
'webhook',
|
|
221
|
+
'oauth',
|
|
222
|
+
]);
|
|
223
|
+
this.appOrginalName = app.manifest.name;
|
|
224
|
+
await this.createPrivateApps(app.manifest);
|
|
225
|
+
}
|
|
200
226
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
chalk.yellow(
|
|
204
|
-
`WARNING!!! The listed apps are private apps that are not available in the destination stack: \n\n${_.map(
|
|
205
|
-
listOfNotInstalledPrivateApps,
|
|
206
|
-
({ manifest: { name } }, index) => `${String(index + 1)}) ${name}`,
|
|
207
|
-
).join('\n')}\n\nWould you like to re-create the private app and then proceed with the installation? (y/n)`,
|
|
208
|
-
),
|
|
209
|
-
);
|
|
227
|
+
this.appOrginalName = undefined;
|
|
228
|
+
}
|
|
210
229
|
|
|
211
|
-
|
|
212
|
-
|
|
230
|
+
async getConfirmationToCreateApps(privateApps) {
|
|
231
|
+
if (!this.config.forceStopMarketplaceAppsPrompt) {
|
|
232
|
+
if (
|
|
233
|
+
!(await cliux.confirm(
|
|
213
234
|
chalk.yellow(
|
|
214
|
-
`WARNING!!!
|
|
235
|
+
`WARNING!!! The listed apps are private apps that are not available in the destination stack: \n\n${_.map(
|
|
236
|
+
privateApps,
|
|
237
|
+
({ manifest: { name } }, index) => `${String(index + 1)}) ${name}`,
|
|
238
|
+
).join('\n')}\n\nWould you like to re-create the private app and then proceed with the installation? (y/n)`,
|
|
215
239
|
),
|
|
216
|
-
)
|
|
240
|
+
))
|
|
241
|
+
) {
|
|
242
|
+
if (
|
|
243
|
+
await cliux.confirm(
|
|
244
|
+
chalk.yellow(
|
|
245
|
+
`\nWARNING!!! Canceling the app re-creation may break the content type and entry import. Would you like to proceed without re-create the private app? (y/n)`,
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
) {
|
|
249
|
+
return Promise.resolve(true);
|
|
250
|
+
}
|
|
217
251
|
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
252
|
+
if (
|
|
253
|
+
!(await cliux.confirm(
|
|
254
|
+
chalk.yellow('\nWould you like to re-create the private app and then proceed with the installation? (y/n)'),
|
|
255
|
+
))
|
|
256
|
+
) {
|
|
221
257
|
process.exit();
|
|
222
258
|
}
|
|
223
259
|
}
|
|
224
260
|
}
|
|
261
|
+
}
|
|
225
262
|
|
|
226
|
-
|
|
263
|
+
async createPrivateApps(app, uidCleaned = false, appSuffix = 1) {
|
|
264
|
+
let locations = app.ui_location && app.ui_location.locations;
|
|
227
265
|
|
|
228
|
-
|
|
229
|
-
|
|
266
|
+
if (!uidCleaned && !_.isEmpty(locations)) {
|
|
267
|
+
app.ui_location.locations = this.uodateManifestUILocations(locations, 'uid');
|
|
268
|
+
} else if (uidCleaned && !_.isEmpty(locations)) {
|
|
269
|
+
app.ui_location.locations = this.uodateManifestUILocations(locations, 'name', appSuffix);
|
|
230
270
|
}
|
|
231
271
|
|
|
232
|
-
|
|
233
|
-
|
|
272
|
+
if (app.name > 20) {
|
|
273
|
+
app.name = app.name.slice(0, 20);
|
|
274
|
+
}
|
|
234
275
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
removeUidFromManifestUILocations = (locations) => {
|
|
241
|
-
return _.map(locations, (location) => {
|
|
242
|
-
if (location.meta) {
|
|
243
|
-
location.meta = _.map(location.meta, (meta) => _.omit(meta, ['uid']));
|
|
244
|
-
}
|
|
276
|
+
const response = await this.client
|
|
277
|
+
.organization(this.config.org_uid)
|
|
278
|
+
.app()
|
|
279
|
+
.create(_.omit(app, ['uid']))
|
|
280
|
+
.catch((error) => error);
|
|
245
281
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
};
|
|
282
|
+
return this.appCreationCallback(app, response, appSuffix);
|
|
283
|
+
}
|
|
249
284
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
* @param {Array<Object>} locations
|
|
253
|
-
* @returns {Array<Object>}
|
|
254
|
-
*/
|
|
255
|
-
updateNameInManifestUILocations = (locations, appSuffix = 1) => {
|
|
256
|
-
return _.map(locations, (location) => {
|
|
257
|
-
if (location.meta) {
|
|
258
|
-
location.meta = _.map(location.meta, (meta) => {
|
|
259
|
-
meta.name = `${_.first(_.split(meta.name, '◈'))}◈${appSuffix}`;
|
|
285
|
+
async appCreationCallback(app, response, appSuffix) {
|
|
286
|
+
const { statusText, message } = response || {};
|
|
260
287
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
288
|
+
if (message) {
|
|
289
|
+
if (_.toLower(statusText) === 'conflict') {
|
|
290
|
+
return this.handleNameConflict(app, appSuffix);
|
|
291
|
+
} else {
|
|
292
|
+
log(this.config, formatError(message), 'error');
|
|
264
293
|
|
|
265
|
-
|
|
266
|
-
});
|
|
267
|
-
};
|
|
294
|
+
if (this.config.forceStopMarketplaceAppsPrompt) return resolve();
|
|
268
295
|
|
|
269
|
-
|
|
270
|
-
|
|
296
|
+
if (
|
|
297
|
+
await cliux.confirm(
|
|
298
|
+
chalk.yellow(
|
|
299
|
+
'WARNING!!! The above error may have an impact if the failed app is referenced in entries/content type. Would you like to proceed? (y/n)',
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
) {
|
|
303
|
+
resolve();
|
|
304
|
+
} else {
|
|
305
|
+
process.exit();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} else if (response.uid) {
|
|
309
|
+
// NOTE new app installation
|
|
310
|
+
log(this.config, `${response.name} app created successfully.!`, 'success');
|
|
311
|
+
this.appUidMapping[app.uid] = response.uid;
|
|
312
|
+
this.appNameMapping[this.appOrginalName] = response.name;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
271
315
|
|
|
272
|
-
|
|
316
|
+
async handleNameConflict(app, appSuffix) {
|
|
317
|
+
const appName = this.config.forceStopMarketplaceAppsPrompt
|
|
318
|
+
? this.getAppName(app.name, appSuffix)
|
|
319
|
+
: await cliux.inquire({
|
|
320
|
+
type: 'input',
|
|
321
|
+
name: 'name',
|
|
322
|
+
validate: this.validateAppName,
|
|
323
|
+
default: this.getAppName(app.name, appSuffix),
|
|
324
|
+
message: `${app.name} app already exist. Enter a new name to create an app.?`,
|
|
325
|
+
});
|
|
326
|
+
app.name = appName;
|
|
273
327
|
|
|
274
|
-
return
|
|
275
|
-
}
|
|
328
|
+
return this.createPrivateApps(app, true, appSuffix + 1);
|
|
329
|
+
}
|
|
276
330
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const { app, httpClient } = options;
|
|
285
|
-
|
|
286
|
-
return new Promise((resolve) => {
|
|
287
|
-
if (!uidCleaned && app.manifest.ui_location && !_.isEmpty(app.manifest.ui_location.locations)) {
|
|
288
|
-
app.manifest.ui_location.locations = this.removeUidFromManifestUILocations(app.manifest.ui_location.locations);
|
|
289
|
-
} else if (isRecursive && app.manifest.ui_location && !_.isEmpty(app.manifest.ui_location.locations)) {
|
|
290
|
-
app.manifest.ui_location.locations = this.updateNameInManifestUILocations(
|
|
291
|
-
app.manifest.ui_location.locations,
|
|
292
|
-
appSuffix - 1 || 1,
|
|
293
|
-
isRecursive,
|
|
294
|
-
);
|
|
295
|
-
}
|
|
331
|
+
uodateManifestUILocations(locations, type = 'uid', appSuffix = 1) {
|
|
332
|
+
switch (type) {
|
|
333
|
+
case 'uid':
|
|
334
|
+
return _.map(locations, (location) => {
|
|
335
|
+
if (location.meta) {
|
|
336
|
+
location.meta = _.map(location.meta, (meta) => _.omit(meta, ['uid']));
|
|
337
|
+
}
|
|
296
338
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
339
|
+
return location;
|
|
340
|
+
});
|
|
341
|
+
case 'name':
|
|
342
|
+
return _.map(locations, (location) => {
|
|
343
|
+
if (location.meta) {
|
|
344
|
+
location.meta = _.map(location.meta, (meta) => {
|
|
345
|
+
if (meta.name) {
|
|
346
|
+
const name = `${_.first(_.split(meta.name, '◈'))}◈${appSuffix}`;
|
|
347
|
+
|
|
348
|
+
if (!this.appNameMapping[this.appOrginalName]) {
|
|
349
|
+
this.appNameMapping[this.appOrginalName] = name;
|
|
350
|
+
}
|
|
300
351
|
|
|
301
|
-
|
|
302
|
-
.post(`${this.developerHubBaseUrl}/apps`, app.manifest)
|
|
303
|
-
.then(async ({ data: result }) => {
|
|
304
|
-
const { name } = app.manifest;
|
|
305
|
-
const { data, error, message } = result || {};
|
|
306
|
-
|
|
307
|
-
if (error) {
|
|
308
|
-
log(self.config, message, 'error');
|
|
309
|
-
|
|
310
|
-
if (_.toLower(error) === 'conflict') {
|
|
311
|
-
const appName = self.config.forceStopMarketplaceAppsPrompt
|
|
312
|
-
? self.getAppName(app.manifest.name, appSuffix)
|
|
313
|
-
: await cliux.inquire({
|
|
314
|
-
type: 'input',
|
|
315
|
-
name: 'name',
|
|
316
|
-
default: self.getAppName(app.manifest.name, appSuffix),
|
|
317
|
-
validate: this.validateAppName,
|
|
318
|
-
message: `${message}. Enter a new name to create an app.?`,
|
|
319
|
-
});
|
|
320
|
-
app.manifest.name = appName;
|
|
321
|
-
|
|
322
|
-
await self
|
|
323
|
-
.createAllPrivateAppsInDeveloperHub({ app, httpClient }, true, appSuffix + 1, true)
|
|
324
|
-
.then(resolve)
|
|
325
|
-
.catch(resolve);
|
|
326
|
-
} else {
|
|
327
|
-
if (self.config.forceStopMarketplaceAppsPrompt) return resolve();
|
|
328
|
-
|
|
329
|
-
const confirmation = await cliux.confirm(
|
|
330
|
-
chalk.yellow(
|
|
331
|
-
'WARNING!!! The above error may have an impact if the failed app is referenced in entries/content type. Would you like to proceed? (y/n)',
|
|
332
|
-
),
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
if (confirmation) {
|
|
336
|
-
resolve();
|
|
337
|
-
} else {
|
|
338
|
-
process.exit();
|
|
352
|
+
meta.name = name;
|
|
339
353
|
}
|
|
340
|
-
}
|
|
341
|
-
} else if (data) {
|
|
342
|
-
// NOTE new app installation
|
|
343
|
-
log(self.config, `${name} app created successfully.!`, 'success');
|
|
344
|
-
this.updatePrivateAppUid(app, data, app.manifest);
|
|
345
|
-
}
|
|
346
354
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
.catch((error) => {
|
|
350
|
-
if (error && (error.message || error.error_message)) {
|
|
351
|
-
log(self.config, error.message || error.error_message, 'error');
|
|
352
|
-
} else {
|
|
353
|
-
log(self.config, 'Something went wrong.!', 'error');
|
|
355
|
+
return meta;
|
|
356
|
+
});
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
|
|
359
|
+
return location;
|
|
357
360
|
});
|
|
358
|
-
});
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* @method updatePrivateAppUid
|
|
363
|
-
* @param {Object} app
|
|
364
|
-
* @param {Object} data
|
|
365
|
-
*/
|
|
366
|
-
updatePrivateAppUid = (app, data, manifest) => {
|
|
367
|
-
const self = this;
|
|
368
|
-
const allMarketplaceApps = readFileSync(
|
|
369
|
-
path.resolve(self.marketplaceAppFolderPath, self.marketplaceAppConfig.fileName),
|
|
370
|
-
);
|
|
371
|
-
const index = _.findIndex(allMarketplaceApps, { uid: app.uid, visibility: 'private' });
|
|
372
|
-
|
|
373
|
-
if (index > -1) {
|
|
374
|
-
allMarketplaceApps[index] = {
|
|
375
|
-
...allMarketplaceApps[index],
|
|
376
|
-
manifest,
|
|
377
|
-
title: data.name,
|
|
378
|
-
app_uid: data.uid,
|
|
379
|
-
old_title: allMarketplaceApps[index].title,
|
|
380
|
-
previous_data: [
|
|
381
|
-
...(allMarketplaceApps[index].old_data || []),
|
|
382
|
-
{ [`v${(allMarketplaceApps[index].old_data || []).length}`]: allMarketplaceApps[index] },
|
|
383
|
-
],
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
writeFile(path.join(self.marketplaceAppFolderPath, self.marketplaceAppConfig.fileName), allMarketplaceApps);
|
|
387
361
|
}
|
|
388
|
-
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
getAppName(name, appSuffix = 1) {
|
|
365
|
+
if (name.length >= 19) name = name.slice(0, 18);
|
|
366
|
+
|
|
367
|
+
name = `${_.first(_.split(name, '◈'))}◈${appSuffix}`;
|
|
368
|
+
|
|
369
|
+
return name;
|
|
370
|
+
}
|
|
389
371
|
|
|
390
372
|
/**
|
|
391
373
|
* @method installApps
|
|
392
374
|
* @param {Object} options
|
|
393
375
|
* @returns {Void}
|
|
394
376
|
*/
|
|
395
|
-
installApps
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
message: 'Choose the option to proceed',
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
if (configOption === 'Exit') {
|
|
436
|
-
process.exit();
|
|
437
|
-
} else if (configOption === 'Update it with the new configuration.') {
|
|
438
|
-
updateParam = {
|
|
439
|
-
app,
|
|
440
|
-
nodeCrypto,
|
|
441
|
-
httpClient,
|
|
442
|
-
data: { ...ext, installation_uid: ext.app_installation_uid },
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
} else {
|
|
447
|
-
if (!self.config.forceStopMarketplaceAppsPrompt) {
|
|
448
|
-
cliux.print(`WARNING!!! ${message || error_message}`, { color: 'yellow' });
|
|
449
|
-
const confirmation = await cliux.confirm(
|
|
450
|
-
chalk.yellow(
|
|
451
|
-
'WARNING!!! The above error may have an impact if the failed app is referenced in entries/content type. Would you like to proceed? (y/n)',
|
|
452
|
-
),
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
if (!confirmation) {
|
|
456
|
-
process.exit();
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
} else if (data) {
|
|
461
|
-
// NOTE new app installation
|
|
462
|
-
log(self.config, `${title} app installed successfully.!`, 'success');
|
|
463
|
-
updateParam = { data, app, nodeCrypto, httpClient };
|
|
464
|
-
}
|
|
377
|
+
async installApps(app, installedApps) {
|
|
378
|
+
let updateParam;
|
|
379
|
+
let installation;
|
|
380
|
+
const { configuration, server_configuration } = app;
|
|
381
|
+
const currentStackApp = _.find(installedApps, { manifest: { uid: app.manifest.uid } });
|
|
382
|
+
|
|
383
|
+
if (!currentStackApp) {
|
|
384
|
+
// NOTE install new app
|
|
385
|
+
installation = await this.client
|
|
386
|
+
.organization(this.config.org_uid)
|
|
387
|
+
.app(this.appUidMapping[app.manifest.uid] || app.manifest.uid)
|
|
388
|
+
.install({ targetUid: this.config.target_stack, targetType: 'stack' })
|
|
389
|
+
.catch((error) => error);
|
|
390
|
+
|
|
391
|
+
if (installation.installation_uid) {
|
|
392
|
+
log(this.config, `${app.manifest.name} app installed successfully.!`, 'success');
|
|
393
|
+
await this.makeRedirectUrlCall(installation, app.manifest.name);
|
|
394
|
+
this.installationUidMapping[app.uid] = installation.installation_uid;
|
|
395
|
+
updateParam = { manifest: app.manifest, ...installation, configuration, server_configuration };
|
|
396
|
+
} else if (installation.message) {
|
|
397
|
+
log(this.config, formatError(installation.message), 'success');
|
|
398
|
+
await this.confirmToCloseProcess(installation);
|
|
399
|
+
}
|
|
400
|
+
} else if (!_.isEmpty(configuration) || !_.isEmpty(server_configuration)) {
|
|
401
|
+
log(this.config, `${app.manifest.name} is already installed`, 'success');
|
|
402
|
+
updateParam = await this.ifAppAlreadyExist(app, currentStackApp);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!this.appUidMapping[app.manifest.uid]) {
|
|
406
|
+
this.appUidMapping[app.manifest.uid] = currentStackApp ? currentStackApp.manifest.uid : app.manifest.uid;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// NOTE update configurations
|
|
410
|
+
if (updateParam && (!_.isEmpty(updateParam.configuration) || !_.isEmpty(updateParam.server_configuration))) {
|
|
411
|
+
await this.updateAppsConfig(updateParam);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
465
414
|
|
|
466
|
-
|
|
467
|
-
|
|
415
|
+
async makeRedirectUrlCall(response, appName) {
|
|
416
|
+
if (response.redirect_url) {
|
|
417
|
+
log(this.config, `${appName} - OAuth api call started.!`, 'info');
|
|
418
|
+
await new HttpClient({ maxRedirects: 20, maxBodyLength: Infinity })
|
|
419
|
+
.get(response.redirect_url)
|
|
420
|
+
.then(async ({ response }) => {
|
|
421
|
+
if (_.includes([501, 403], response.status)) {
|
|
422
|
+
log(this.config, `${appName} - ${response.statusText}, OAuth api call failed.!`, 'error');
|
|
423
|
+
log(this.config, formatError(response), 'error');
|
|
424
|
+
await this.confirmToCloseProcess({ message: response.data });
|
|
468
425
|
} else {
|
|
469
|
-
|
|
426
|
+
log(this.config, `${appName} - OAuth api call completed.!`, 'success');
|
|
470
427
|
}
|
|
471
428
|
})
|
|
472
429
|
.catch((error) => {
|
|
473
|
-
if (
|
|
474
|
-
log(
|
|
475
|
-
} else {
|
|
476
|
-
log(self.config, 'Something went wrong.!', 'error');
|
|
430
|
+
if (_.includes([501, 403], error.status)) {
|
|
431
|
+
log(this.config, formatError(error), 'error');
|
|
477
432
|
}
|
|
478
|
-
|
|
479
|
-
reject();
|
|
480
433
|
});
|
|
481
|
-
});
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
updateConfigData(config) {
|
|
485
|
-
const configKeyMapper = {};
|
|
486
|
-
const serverConfigKeyMapper = {
|
|
487
|
-
cmsApiKey: this.config.target_stack,
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
if (!_.isEmpty(config.configuration) && !_.isEmpty(configKeyMapper)) {
|
|
491
|
-
_.forEach(_.keys(configKeyMapper), (key) => {
|
|
492
|
-
config.configuration[key] = configKeyMapper[key];
|
|
493
|
-
});
|
|
494
434
|
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async ifAppAlreadyExist(app, currentStackApp) {
|
|
438
|
+
let updateParam;
|
|
439
|
+
const {
|
|
440
|
+
manifest: { name },
|
|
441
|
+
configuration,
|
|
442
|
+
server_configuration,
|
|
443
|
+
} = app;
|
|
444
|
+
|
|
445
|
+
if (!_.isEmpty(configuration) || !_.isEmpty(server_configuration)) {
|
|
446
|
+
cliux.print(
|
|
447
|
+
`\nWARNING!!! The ${name} app already exists and it may have its own configuration. But the current app you install has its own configuration which is used internally to manage content.\n`,
|
|
448
|
+
{ color: 'yellow' },
|
|
449
|
+
);
|
|
495
450
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
451
|
+
const configOption = this.config.forceStopMarketplaceAppsPrompt
|
|
452
|
+
? 'Update it with the new configuration.'
|
|
453
|
+
: await cliux.inquire({
|
|
454
|
+
choices: [
|
|
455
|
+
'Update it with the new configuration.',
|
|
456
|
+
'Do not update the configuration (WARNING!!! If you do not update the configuration, there may be some issues with the content which you import).',
|
|
457
|
+
'Exit',
|
|
458
|
+
],
|
|
459
|
+
type: 'list',
|
|
460
|
+
name: 'value',
|
|
461
|
+
message: 'Choose the option to proceed',
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
if (configOption === 'Exit') {
|
|
465
|
+
process.exit();
|
|
466
|
+
} else if (configOption === 'Update it with the new configuration.') {
|
|
467
|
+
updateParam = { manifest: app.manifest, ...currentStackApp, configuration, server_configuration };
|
|
468
|
+
}
|
|
500
469
|
}
|
|
501
470
|
|
|
502
|
-
return
|
|
471
|
+
return updateParam;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async confirmToCloseProcess(installation) {
|
|
475
|
+
cliux.print(`\nWARNING!!! ${formatError(installation.message)}\n`, { color: 'yellow' });
|
|
476
|
+
|
|
477
|
+
if (!this.config.forceStopMarketplaceAppsPrompt) {
|
|
478
|
+
if (
|
|
479
|
+
!(await cliux.confirm(
|
|
480
|
+
chalk.yellow(
|
|
481
|
+
'WARNING!!! The above error may have an impact if the failed app is referenced in entries/content type. Would you like to proceed? (y/n)',
|
|
482
|
+
),
|
|
483
|
+
))
|
|
484
|
+
) {
|
|
485
|
+
process.exit();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
503
488
|
}
|
|
504
489
|
|
|
505
490
|
/**
|
|
506
491
|
* @method updateAppsConfig
|
|
507
|
-
* @param {Object<{ data, app
|
|
492
|
+
* @param {Object<{ data, app }>} param
|
|
508
493
|
* @returns {Promise<void>}
|
|
509
494
|
*/
|
|
510
|
-
updateAppsConfig
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
let payload = {};
|
|
514
|
-
const { title, configuration, server_configuration } = app;
|
|
515
|
-
|
|
516
|
-
if (!_.isEmpty(configuration)) {
|
|
517
|
-
payload['configuration'] = nodeCrypto.decrypt(configuration);
|
|
518
|
-
}
|
|
519
|
-
if (!_.isEmpty(server_configuration)) {
|
|
520
|
-
payload['server_configuration'] = nodeCrypto.decrypt(server_configuration);
|
|
521
|
-
}
|
|
495
|
+
updateAppsConfig(app) {
|
|
496
|
+
const payload = {};
|
|
497
|
+
const { uid, configuration, server_configuration } = app;
|
|
522
498
|
|
|
523
|
-
|
|
499
|
+
if (!_.isEmpty(configuration)) {
|
|
500
|
+
payload['configuration'] = this.nodeCrypto.decrypt(configuration);
|
|
501
|
+
}
|
|
502
|
+
if (!_.isEmpty(server_configuration)) {
|
|
503
|
+
payload['server_configuration'] = this.nodeCrypto.decrypt(server_configuration);
|
|
504
|
+
}
|
|
524
505
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
httpClient
|
|
529
|
-
.put(`${this.developerHubBaseUrl}/installations/${data.installation_uid}`, payload)
|
|
530
|
-
.then(() => {
|
|
531
|
-
log(self.config, `${title} app config updated successfully.!`, 'success');
|
|
532
|
-
})
|
|
533
|
-
.then(resolve)
|
|
534
|
-
.catch((error) => {
|
|
535
|
-
if (error && (error.message || error.error_message)) {
|
|
536
|
-
log(self.config, error.message || error.error_message, 'error');
|
|
537
|
-
} else {
|
|
538
|
-
log(self.config, 'Something went wrong.!', 'error');
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
reject();
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
};
|
|
506
|
+
if (_.isEmpty(app) || _.isEmpty(payload) || !uid) {
|
|
507
|
+
return Promise.resolve();
|
|
508
|
+
}
|
|
546
509
|
|
|
547
|
-
|
|
510
|
+
return this.httpClient
|
|
511
|
+
.put(`${this.developerHubBaseUrl}/installations/${uid}`, payload)
|
|
512
|
+
.then(({ data }) => {
|
|
513
|
+
if (data.message) {
|
|
514
|
+
log(this.config, formatError(data.message), 'success');
|
|
515
|
+
} else {
|
|
516
|
+
log(this.config, `${app.manifest.name} app config updated successfully.!`, 'success');
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
.catch((error) => log(this.config, formatError(error), 'error'));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
validateAppName(name) {
|
|
548
523
|
if (name.length < 3 || name.length > 20) {
|
|
549
524
|
return 'The app name should be within 3-20 characters long.';
|
|
550
525
|
}
|
|
551
526
|
|
|
552
527
|
return true;
|
|
553
|
-
}
|
|
528
|
+
}
|
|
554
529
|
};
|