@contentstack/cli-cm-import 1.2.4 → 1.4.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 +1 -1
- package/README.md +84 -24
- package/oclif.manifest.json +129 -1
- package/package.json +11 -11
- package/src/app.js +48 -60
- package/src/commands/cm/stacks/import.js +3 -4
- package/src/config/default.js +6 -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 +76 -13
- package/src/lib/import/marketplace-apps.js +387 -412
- 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/fs.js +4 -0
- package/src/lib/util/index.js +71 -72
- package/src/lib/util/login.js +45 -42
- package/src/lib/util/lookupReplaceAssets.js +9 -98
- 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.mapperDirPath = path.resolve(this.config.data, 'mapper', 'marketplace_apps');
|
|
39
|
+
this.uidMapperPath = path.join(this.mapperDirPath, 'uid-mapping.json');
|
|
34
40
|
this.marketplaceAppFolderPath = path.resolve(this.config.data, this.marketplaceAppConfig.dirName);
|
|
35
|
-
this.marketplaceApps =
|
|
36
|
-
|
|
37
|
-
'app_uid',
|
|
41
|
+
this.marketplaceApps = readFileSync(
|
|
42
|
+
path.resolve(this.marketplaceAppFolderPath, this.marketplaceAppConfig.fileName),
|
|
38
43
|
);
|
|
39
|
-
this.marketplaceAppsUid = _.map(this.marketplaceApps, 'uid');
|
|
40
44
|
|
|
41
|
-
if (
|
|
45
|
+
if (_.isEmpty(this.marketplaceApps)) {
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
} else if (!this.config.auth_token) {
|
|
42
48
|
cliux.print(
|
|
43
|
-
'
|
|
49
|
+
'\nWARNING!!! To import Marketplace apps, you must be logged in. Please check csdx auth:login --help to log in\n',
|
|
44
50
|
{ color: 'yellow' },
|
|
45
51
|
);
|
|
46
52
|
return Promise.resolve();
|
|
47
|
-
} else if (_.isEmpty(this.marketplaceApps)) {
|
|
48
|
-
return Promise.resolve();
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
this.developerHubBaseUrl = this.config.developerHubBaseUrl || (await getDeveloperHubUrl(this.config));
|
|
56
|
+
this.client = contentstack.client({ authtoken: this.config.auth_token, endpoint: this.developerHubBaseUrl });
|
|
57
|
+
|
|
51
58
|
await this.getOrgUid();
|
|
52
|
-
|
|
59
|
+
|
|
60
|
+
this.httpClient = new HttpClient({
|
|
61
|
+
headers: {
|
|
62
|
+
authtoken: this.config.auth_token,
|
|
63
|
+
organization_uid: this.config.org_uid,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(this.mapperDirPath)) {
|
|
68
|
+
mkdirp.sync(this.mapperDirPath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return this.startInstallation();
|
|
53
72
|
}
|
|
54
73
|
|
|
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 })
|
|
74
|
+
async getOrgUid() {
|
|
75
|
+
if (this.config.auth_token) {
|
|
76
|
+
const tempAPIClient = await managementSDKClient({ host: this.config.host });
|
|
77
|
+
const tempStackData = await tempAPIClient
|
|
78
|
+
.stack({ api_key: this.config.target_stack })
|
|
65
79
|
.fetch()
|
|
66
80
|
.catch((error) => {
|
|
67
81
|
console.log(error);
|
|
68
|
-
log(self.config, 'Starting marketplace app installation', 'success');
|
|
69
82
|
});
|
|
70
83
|
|
|
71
|
-
if (
|
|
72
|
-
|
|
84
|
+
if (tempStackData && tempStackData.org_uid) {
|
|
85
|
+
this.config.org_uid = tempStackData.org_uid;
|
|
73
86
|
}
|
|
74
87
|
}
|
|
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
|
-
});
|
|
88
|
+
}
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
cliux.print('Maximum retry limit exceeded. Closing the process, please try again.!', { color: 'red' });
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
90
|
+
async getAndValidateEncryptionKey(defaultValue, retry = 1) {
|
|
91
|
+
let appConfig = _.find(
|
|
92
|
+
this.marketplaceApps,
|
|
93
|
+
({ configuration, server_configuration }) => !_.isEmpty(configuration) || !_.isEmpty(server_configuration),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!appConfig) {
|
|
97
|
+
return defaultValue;
|
|
98
|
+
}
|
|
108
99
|
|
|
109
|
-
|
|
100
|
+
const encryptionKey = await cliux.inquire({
|
|
101
|
+
type: 'input',
|
|
102
|
+
name: 'name',
|
|
103
|
+
default: defaultValue,
|
|
104
|
+
validate: (key) => {
|
|
105
|
+
if (!key) return "Encryption key can't be empty.";
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
},
|
|
109
|
+
message: 'Enter marketplace app configurations encryption key',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
appConfig = !_.isEmpty(appConfig.configuration) ? appConfig.configuration : appConfig.server_configuration;
|
|
114
|
+
this.nodeCrypto = new NodeCrypto({ encryptionKey });
|
|
115
|
+
this.nodeCrypto.decrypt(appConfig);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (retry < this.config.getEncryptionKeyMaxRetry && error.code === 'ERR_OSSL_EVP_BAD_DECRYPT') {
|
|
118
|
+
cliux.print(
|
|
119
|
+
`Provided encryption key is not valid or your data might be corrupted.! attempt(${retry}/${this.config.getEncryptionKeyMaxRetry})`,
|
|
120
|
+
{ color: 'red' },
|
|
121
|
+
);
|
|
122
|
+
// NOTE max retry limit is 3
|
|
123
|
+
return this.getAndValidateEncryptionKey(encryptionKey, retry + 1);
|
|
124
|
+
} else {
|
|
125
|
+
cliux.print(
|
|
126
|
+
`Maximum retry limit exceeded. Closing the process, please try again.! attempt(${retry}/${this.config.getEncryptionKeyMaxRetry})`,
|
|
127
|
+
{ color: 'red' },
|
|
128
|
+
);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
110
131
|
}
|
|
111
132
|
|
|
112
|
-
return
|
|
133
|
+
return encryptionKey;
|
|
113
134
|
}
|
|
114
135
|
|
|
115
136
|
/**
|
|
116
|
-
* @method
|
|
137
|
+
* @method startInstallation
|
|
117
138
|
* @returns {Promise<void>}
|
|
118
139
|
*/
|
|
119
|
-
|
|
120
|
-
const self = this;
|
|
140
|
+
async startInstallation() {
|
|
121
141
|
const cryptoArgs = {};
|
|
122
|
-
const headers = {
|
|
123
|
-
authtoken: self.config.auth_token,
|
|
124
|
-
organization_uid: self.config.org_uid,
|
|
125
|
-
};
|
|
126
142
|
|
|
127
|
-
if (
|
|
128
|
-
cryptoArgs['encryptionKey'] =
|
|
143
|
+
if (this.config.marketplaceAppEncryptionKey) {
|
|
144
|
+
cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
|
|
129
145
|
}
|
|
130
146
|
|
|
131
|
-
if (
|
|
132
|
-
cryptoArgs['encryptionKey'] =
|
|
147
|
+
if (this.config.forceStopMarketplaceAppsPrompt) {
|
|
148
|
+
cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
|
|
149
|
+
this.nodeCrypto = new NodeCrypto(cryptoArgs);
|
|
133
150
|
} else {
|
|
134
|
-
|
|
151
|
+
await this.getAndValidateEncryptionKey(this.config.marketplaceAppEncryptionKey);
|
|
135
152
|
}
|
|
136
153
|
|
|
137
|
-
const httpClient = new HttpClient().headers(headers);
|
|
138
|
-
const nodeCrypto = new NodeCrypto(cryptoArgs);
|
|
139
|
-
|
|
140
154
|
// 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));
|
|
155
|
+
await this.handleAllPrivateAppsCreationProcess();
|
|
156
|
+
const installedApps = await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config);
|
|
149
157
|
|
|
150
|
-
log(
|
|
158
|
+
log(this.config, 'Starting marketplace app installation', 'success');
|
|
151
159
|
|
|
152
|
-
for (let app of
|
|
153
|
-
await
|
|
160
|
+
for (let app of this.marketplaceApps) {
|
|
161
|
+
await this.installApps(app, installedApps);
|
|
154
162
|
}
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
const uidMapper = await this.generateUidMapper();
|
|
165
|
+
await writeFile(this.uidMapperPath, {
|
|
166
|
+
app_uid: this.appUidMapping,
|
|
167
|
+
extension_uid: uidMapper || {},
|
|
168
|
+
installation_uid: this.installationUidMapping,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
159
171
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
async generateUidMapper() {
|
|
173
|
+
const listOfNewMeta = [];
|
|
174
|
+
const listOfOldMeta = [];
|
|
175
|
+
const extensionUidMapp = {};
|
|
176
|
+
const allInstalledApps = await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config);
|
|
163
177
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
178
|
+
for (const app of this.marketplaceApps) {
|
|
179
|
+
listOfOldMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat());
|
|
180
|
+
}
|
|
181
|
+
for (const app of allInstalledApps) {
|
|
182
|
+
listOfNewMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat());
|
|
183
|
+
}
|
|
184
|
+
for (const { extension_uid, name, path } of _.filter(listOfOldMeta, 'name')) {
|
|
185
|
+
const meta =
|
|
186
|
+
_.find(listOfNewMeta, { name, path }) || _.find(listOfNewMeta, { name: this.appNameMapping[name], path });
|
|
168
187
|
|
|
169
|
-
|
|
188
|
+
if (meta) {
|
|
189
|
+
extensionUidMapp[extension_uid] = meta.extension_uid;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
170
192
|
|
|
171
|
-
return
|
|
172
|
-
}
|
|
193
|
+
return extensionUidMapp;
|
|
194
|
+
}
|
|
173
195
|
|
|
174
196
|
/**
|
|
175
197
|
* @method handleAllPrivateAppsCreationProcess
|
|
176
198
|
* @param {Object} options
|
|
177
199
|
* @returns {Promise<void>}
|
|
178
200
|
*/
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const { httpClient } = options;
|
|
182
|
-
const listOfExportedPrivateApps = _.filter(self.marketplaceApps, { visibility: 'private' });
|
|
201
|
+
async handleAllPrivateAppsCreationProcess() {
|
|
202
|
+
const privateApps = _.filter(this.marketplaceApps, { manifest: { visibility: 'private' } });
|
|
183
203
|
|
|
184
|
-
if (_.isEmpty(
|
|
204
|
+
if (_.isEmpty(privateApps)) {
|
|
185
205
|
return Promise.resolve();
|
|
186
206
|
}
|
|
187
207
|
|
|
188
|
-
|
|
189
|
-
const installedDeveloperHubApps =
|
|
190
|
-
(await httpClient
|
|
191
|
-
.get(`${this.developerHubBaseUrl}/apps`)
|
|
192
|
-
.then(({ data: { data } }) => data)
|
|
193
|
-
.catch((err) => {
|
|
194
|
-
console.log(err);
|
|
195
|
-
})) || [];
|
|
196
|
-
const listOfNotInstalledPrivateApps = _.filter(
|
|
197
|
-
listOfExportedPrivateApps,
|
|
198
|
-
(app) => !_.includes(_.map(installedDeveloperHubApps, 'uid'), app.app_uid),
|
|
199
|
-
);
|
|
208
|
+
await this.getConfirmationToCreateApps(privateApps);
|
|
200
209
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
log(this.config, 'Starting developer hub private apps re-creation', 'success');
|
|
211
|
+
|
|
212
|
+
for (let app of privateApps) {
|
|
213
|
+
// NOTE keys can be passed to install new app in the developer hub
|
|
214
|
+
app.manifest = _.pick(app.manifest, ['uid', 'name', 'description', 'icon', 'target_type', 'webhook', 'oauth']);
|
|
215
|
+
this.appOrginalName = app.manifest.name;
|
|
216
|
+
await this.createPrivateApps({
|
|
217
|
+
oauth: app.oauth,
|
|
218
|
+
webhook: app.webhook,
|
|
219
|
+
ui_location: app.ui_location,
|
|
220
|
+
...app.manifest,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
210
223
|
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
this.appOrginalName = undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async getConfirmationToCreateApps(privateApps) {
|
|
228
|
+
if (!this.config.forceStopMarketplaceAppsPrompt) {
|
|
229
|
+
if (
|
|
230
|
+
!(await cliux.confirm(
|
|
213
231
|
chalk.yellow(
|
|
214
|
-
`WARNING!!!
|
|
232
|
+
`WARNING!!! The listed apps are private apps that are not available in the destination stack: \n\n${_.map(
|
|
233
|
+
privateApps,
|
|
234
|
+
({ manifest: { name } }, index) => `${String(index + 1)}) ${name}`,
|
|
235
|
+
).join('\n')}\n\nWould you like to re-create the private app and then proceed with the installation? (y/n)`,
|
|
215
236
|
),
|
|
216
|
-
)
|
|
237
|
+
))
|
|
238
|
+
) {
|
|
239
|
+
if (
|
|
240
|
+
await cliux.confirm(
|
|
241
|
+
chalk.yellow(
|
|
242
|
+
`\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)`,
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
) {
|
|
246
|
+
return Promise.resolve(true);
|
|
247
|
+
}
|
|
217
248
|
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
249
|
+
if (
|
|
250
|
+
!(await cliux.confirm(
|
|
251
|
+
chalk.yellow('\nWould you like to re-create the private app and then proceed with the installation? (y/n)'),
|
|
252
|
+
))
|
|
253
|
+
) {
|
|
221
254
|
process.exit();
|
|
222
255
|
}
|
|
223
256
|
}
|
|
224
257
|
}
|
|
258
|
+
}
|
|
225
259
|
|
|
226
|
-
|
|
260
|
+
async createPrivateApps(app, uidCleaned = false, appSuffix = 1) {
|
|
261
|
+
let locations = app.ui_location && app.ui_location.locations;
|
|
227
262
|
|
|
228
|
-
|
|
229
|
-
|
|
263
|
+
if (!uidCleaned && !_.isEmpty(locations)) {
|
|
264
|
+
app.ui_location.locations = this.updateManifestUILocations(locations, 'uid');
|
|
265
|
+
} else if (uidCleaned && !_.isEmpty(locations)) {
|
|
266
|
+
app.ui_location.locations = this.updateManifestUILocations(locations, 'name', appSuffix);
|
|
230
267
|
}
|
|
231
268
|
|
|
232
|
-
|
|
233
|
-
|
|
269
|
+
if (app.name > 20) {
|
|
270
|
+
app.name = app.name.slice(0, 20);
|
|
271
|
+
}
|
|
234
272
|
|
|
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
|
-
}
|
|
273
|
+
const response = await this.client
|
|
274
|
+
.organization(this.config.org_uid)
|
|
275
|
+
.app()
|
|
276
|
+
.create(_.omit(app, ['uid']))
|
|
277
|
+
.catch((error) => error);
|
|
245
278
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
};
|
|
279
|
+
return this.appCreationCallback(app, response, appSuffix);
|
|
280
|
+
}
|
|
249
281
|
|
|
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}`;
|
|
282
|
+
async appCreationCallback(app, response, appSuffix) {
|
|
283
|
+
const { statusText, message } = response || {};
|
|
260
284
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
285
|
+
if (message) {
|
|
286
|
+
if (_.toLower(statusText) === 'conflict') {
|
|
287
|
+
return this.handleNameConflict(app, appSuffix);
|
|
288
|
+
} else {
|
|
289
|
+
log(this.config, formatError(message), 'error');
|
|
264
290
|
|
|
265
|
-
|
|
266
|
-
});
|
|
267
|
-
};
|
|
291
|
+
if (this.config.forceStopMarketplaceAppsPrompt) return resolve();
|
|
268
292
|
|
|
269
|
-
|
|
270
|
-
|
|
293
|
+
if (
|
|
294
|
+
await cliux.confirm(
|
|
295
|
+
chalk.yellow(
|
|
296
|
+
'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)',
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
) {
|
|
300
|
+
resolve();
|
|
301
|
+
} else {
|
|
302
|
+
process.exit();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} else if (response.uid) {
|
|
306
|
+
// NOTE new app installation
|
|
307
|
+
log(this.config, `${response.name} app created successfully.!`, 'success');
|
|
308
|
+
this.appUidMapping[app.uid] = response.uid;
|
|
309
|
+
this.appNameMapping[this.appOrginalName] = response.name;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
271
312
|
|
|
272
|
-
|
|
313
|
+
async handleNameConflict(app, appSuffix) {
|
|
314
|
+
const appName = this.config.forceStopMarketplaceAppsPrompt
|
|
315
|
+
? this.getAppName(app.name, appSuffix)
|
|
316
|
+
: await cliux.inquire({
|
|
317
|
+
type: 'input',
|
|
318
|
+
name: 'name',
|
|
319
|
+
validate: this.validateAppName,
|
|
320
|
+
default: this.getAppName(app.name, appSuffix),
|
|
321
|
+
message: `${app.name} app already exist. Enter a new name to create an app.?`,
|
|
322
|
+
});
|
|
323
|
+
app.name = appName;
|
|
273
324
|
|
|
274
|
-
return
|
|
275
|
-
}
|
|
325
|
+
return this.createPrivateApps(app, true, appSuffix + 1);
|
|
326
|
+
}
|
|
276
327
|
|
|
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
|
-
}
|
|
328
|
+
updateManifestUILocations(locations, type = 'uid', appSuffix = 1) {
|
|
329
|
+
switch (type) {
|
|
330
|
+
case 'uid':
|
|
331
|
+
return _.map(locations, (location) => {
|
|
332
|
+
if (location.meta) {
|
|
333
|
+
location.meta = _.map(location.meta, (meta) => _.omit(meta, ['uid']));
|
|
334
|
+
}
|
|
296
335
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
336
|
+
return location;
|
|
337
|
+
});
|
|
338
|
+
case 'name':
|
|
339
|
+
return _.map(locations, (location) => {
|
|
340
|
+
if (location.meta) {
|
|
341
|
+
location.meta = _.map(location.meta, (meta) => {
|
|
342
|
+
if (meta.name) {
|
|
343
|
+
const name = `${_.first(_.split(meta.name, '◈'))}◈${appSuffix}`;
|
|
344
|
+
|
|
345
|
+
if (!this.appNameMapping[this.appOrginalName]) {
|
|
346
|
+
this.appNameMapping[this.appOrginalName] = name;
|
|
347
|
+
}
|
|
300
348
|
|
|
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();
|
|
349
|
+
meta.name = name;
|
|
339
350
|
}
|
|
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
351
|
|
|
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');
|
|
352
|
+
return meta;
|
|
353
|
+
});
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
|
|
356
|
+
return location;
|
|
357
357
|
});
|
|
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
358
|
}
|
|
388
|
-
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
getAppName(name, appSuffix = 1) {
|
|
362
|
+
if (name.length >= 19) name = name.slice(0, 18);
|
|
363
|
+
|
|
364
|
+
name = `${_.first(_.split(name, '◈'))}◈${appSuffix}`;
|
|
365
|
+
|
|
366
|
+
return name;
|
|
367
|
+
}
|
|
389
368
|
|
|
390
369
|
/**
|
|
391
370
|
* @method installApps
|
|
392
371
|
* @param {Object} options
|
|
393
372
|
* @returns {Void}
|
|
394
373
|
*/
|
|
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
|
-
choices: [
|
|
426
|
-
'Update it with the new configuration.',
|
|
427
|
-
'Do not update the configuration (WARNING!!! If you do not update the configuration, there may be some issues with the content which you import).',
|
|
428
|
-
'Exit',
|
|
429
|
-
],
|
|
430
|
-
type: 'list',
|
|
431
|
-
name: 'value',
|
|
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
|
-
}
|
|
374
|
+
async installApps(app, installedApps) {
|
|
375
|
+
let updateParam;
|
|
376
|
+
let installation;
|
|
377
|
+
const { configuration, server_configuration } = app;
|
|
378
|
+
const currentStackApp = _.find(installedApps, { manifest: { uid: app.manifest.uid } });
|
|
379
|
+
|
|
380
|
+
if (!currentStackApp) {
|
|
381
|
+
// NOTE install new app
|
|
382
|
+
installation = await this.client
|
|
383
|
+
.organization(this.config.org_uid)
|
|
384
|
+
.app(this.appUidMapping[app.manifest.uid] || app.manifest.uid)
|
|
385
|
+
.install({ targetUid: this.config.target_stack, targetType: 'stack' })
|
|
386
|
+
.catch((error) => error);
|
|
387
|
+
|
|
388
|
+
if (installation.installation_uid) {
|
|
389
|
+
let appName = (this.appNameMapping[app.manifest.name]) ?
|
|
390
|
+
this.appNameMapping[app.manifest.name] :
|
|
391
|
+
app.manifest.name ;
|
|
392
|
+
log(this.config, `${appName} 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
|
+
}
|
|
465
404
|
|
|
466
|
-
|
|
467
|
-
|
|
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
|
+
}
|
|
414
|
+
|
|
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
|
-
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
483
436
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
437
|
+
async ifAppAlreadyExist(app, currentStackApp) {
|
|
438
|
+
let updateParam;
|
|
439
|
+
const {
|
|
440
|
+
manifest: { name },
|
|
441
|
+
configuration,
|
|
442
|
+
server_configuration,
|
|
443
|
+
} = app;
|
|
489
444
|
|
|
490
|
-
if (!_.isEmpty(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
};
|