@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.
@@ -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 { cliux, HttpClient, NodeCrypto } = require('@contentstack/cli-utilities');
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 sdk = require('../util/contentstack-management-sdk');
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
- marketplaceAppsUid = [];
27
+ installationUidMapping = {};
23
28
  developerHubBaseUrl = null;
24
29
  marketplaceAppFolderPath = '';
25
30
  marketplaceAppConfig = config.modules.marketplace_apps;
26
31
 
27
- constructor(credentialConfig) {
28
- this.config = _.merge(config, credentialConfig);
32
+ constructor(importConfig, stackAPIClient) {
33
+ this.config = _.merge(config, importConfig);
34
+ this.stackAPIClient = stackAPIClient;
29
35
  }
30
36
 
31
37
  async start() {
32
- this.client = sdk.Client(this.config);
33
- this.developerHubBaseUrl = this.config.developerHubBaseUrl || (await getDeveloperHubUrl());
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 = _.uniqBy(
36
- readFileSync(path.resolve(this.marketplaceAppFolderPath, this.marketplaceAppConfig.fileName)),
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 (!this.config.auth_token && !_.isEmpty(this.marketplaceApps)) {
45
+ if (_.isEmpty(this.marketplaceApps)) {
46
+ return Promise.resolve();
47
+ } else if (!this.config.auth_token) {
42
48
  cliux.print(
43
- 'WARNING!!! To import Marketplace apps, you must be logged in. Please check csdx auth:login --help to log in',
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
- return this.handleInstallationProcess();
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
- * @method getOrgUid
57
- * @returns {Void}
58
- */
59
- getOrgUid = async () => {
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 (stack && stack.org_uid) {
72
- self.config.org_uid = stack.org_uid;
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
- 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
- }
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
- return encryptionKey;
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 defaultValue;
133
+ return encryptionKey;
113
134
  }
114
135
 
115
136
  /**
116
- * @method handleInstallationProcess
137
+ * @method startInstallation
117
138
  * @returns {Promise<void>}
118
139
  */
119
- handleInstallationProcess = async () => {
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 (self.config.marketplaceAppEncryptionKey) {
128
- cryptoArgs['encryptionKey'] = self.config.marketplaceAppEncryptionKey;
143
+ if (this.config.marketplaceAppEncryptionKey) {
144
+ cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
129
145
  }
130
146
 
131
- if (self.config.forceStopMarketplaceAppsPrompt) {
132
- cryptoArgs['encryptionKey'] = self.config.marketplaceAppEncryptionKey;
147
+ if (this.config.forceStopMarketplaceAppsPrompt) {
148
+ cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
149
+ this.nodeCrypto = new NodeCrypto(cryptoArgs);
133
150
  } else {
134
- cryptoArgs['encryptionKey'] = await self.getEncryptionKeyAndValidate(self.config.marketplaceAppEncryptionKey);
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({ httpClient });
142
- const installedExtensions = await getInstalledExtensions(self.config);
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(self.config, 'Starting marketplace app installation', 'success');
158
+ log(this.config, 'Starting marketplace app installation', 'success');
151
159
 
152
- for (let app of self.marketplaceApps) {
153
- await self.installApps({ app, installedExtensions, httpClient, nodeCrypto });
160
+ for (let app of this.marketplaceApps) {
161
+ await this.installApps(app, installedApps);
154
162
  }
155
163
 
156
- // NOTE get all the extension again after all apps installed (To manage uid mapping in content type, entries)
157
- const extensions = await getInstalledExtensions(self.config);
158
- const mapperFolderPath = path.join(self.config.data, 'mapper', 'marketplace_apps');
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
- if (!fs.existsSync(mapperFolderPath)) {
161
- mkdirp.sync(mapperFolderPath);
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 appUidMapperPath = path.join(mapperFolderPath, 'marketplace-apps.json');
165
- const installedExt = _.map(extensions, (row) =>
166
- _.pick(row, ['uid', 'title', 'type', 'app_uid', 'app_installation_uid']),
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
- writeFile(appUidMapperPath, installedExt);
188
+ if (meta) {
189
+ extensionUidMapp[extension_uid] = meta.extension_uid;
190
+ }
191
+ }
170
192
 
171
- return Promise.resolve();
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
- handleAllPrivateAppsCreationProcess = async (options) => {
180
- const self = this;
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(listOfExportedPrivateApps)) {
204
+ if (_.isEmpty(privateApps)) {
185
205
  return Promise.resolve();
186
206
  }
187
207
 
188
- // NOTE get list of developer-hub installed apps (private)
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
- if (!_.isEmpty(listOfNotInstalledPrivateApps) && !self.config.forceStopMarketplaceAppsPrompt) {
202
- const confirmation = await cliux.confirm(
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
- );
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
- if (!confirmation) {
212
- const continueProcess = await cliux.confirm(
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!!! Canceling the app re-creation may break the content type and entry import. Would you like to proceed? (y/n)`,
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 (continueProcess) {
219
- return Promise.resolve();
220
- } else {
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
- log(self.config, 'Starting developer hub private apps re-creation', 'success');
260
+ async createPrivateApps(app, uidCleaned = false, appSuffix = 1) {
261
+ let locations = app.ui_location && app.ui_location.locations;
227
262
 
228
- for (let app of listOfNotInstalledPrivateApps) {
229
- await self.createAllPrivateAppsInDeveloperHub({ app, httpClient });
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
- return Promise.resolve();
233
- };
269
+ if (app.name > 20) {
270
+ app.name = app.name.slice(0, 20);
271
+ }
234
272
 
235
- /**
236
- * @method removeUidFromManifestUILocations
237
- * @param {Array<Object>} locations
238
- * @returns {Array<Object>}
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
- return location;
247
- });
248
- };
279
+ return this.appCreationCallback(app, response, appSuffix);
280
+ }
249
281
 
250
- /**
251
- * @method updateNameInManifestUILocations
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
- return meta;
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
- return location;
266
- });
267
- };
291
+ if (this.config.forceStopMarketplaceAppsPrompt) return resolve();
268
292
 
269
- getAppName = (name, appSuffix = 1) => {
270
- if (name.length >= 19) name = name.slice(0, 18);
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
- name = `${_.first(_.split(name, '◈'))}◈${appSuffix}`;
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 name;
275
- };
325
+ return this.createPrivateApps(app, true, appSuffix + 1);
326
+ }
276
327
 
277
- /**
278
- * @method createAllPrivateAppsInDeveloperHub
279
- * @param {Object} options
280
- * @returns {Promise<void>}
281
- */
282
- createAllPrivateAppsInDeveloperHub = async (options, uidCleaned = false, appSuffix = 1, isRecursive = false) => {
283
- const self = this;
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
- if (app.manifest.name > 20) {
298
- app.manifest.name = app.manifest.name.slice(0, 20);
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
- httpClient
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
- resolve();
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
- resolve();
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 = (options) => {
396
- const self = this;
397
- const { app, installedExtensions, httpClient, nodeCrypto } = options;
398
-
399
- return new Promise((resolve, reject) => {
400
- httpClient
401
- .post(`${self.developerHubBaseUrl}/apps/${app.app_uid}/install`, {
402
- target_type: 'stack',
403
- target_uid: self.config.target_stack,
404
- })
405
- .then(async ({ data: result }) => {
406
- let updateParam;
407
- const { title } = app;
408
- const { data, error, message, error_code, error_message } = result;
409
-
410
- if (error || error_code) {
411
- // NOTE if already installed copy only config data
412
- log(self.config, `${message || error_message} - ${title}`, 'success');
413
- const ext = _.find(installedExtensions, { app_uid: app.app_uid });
414
-
415
- if (ext) {
416
- if (!_.isEmpty(app.configuration) || !_.isEmpty(app.server_configuration)) {
417
- cliux.print(
418
- `WARNING!!! The ${title} 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.`,
419
- { color: 'yellow' },
420
- );
421
-
422
- const configOption = self.config.forceStopMarketplaceAppsPrompt
423
- ? 'Update it with the new configuration.'
424
- : await cliux.inquire({
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
- if (updateParam) {
467
- self.updateAppsConfig(updateParam).then(resolve).catch(reject);
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
- resolve();
426
+ log(this.config, `${appName} - OAuth api call completed.!`, 'success');
470
427
  }
471
428
  })
472
429
  .catch((error) => {
473
- if (error && (error.message || error.error_message)) {
474
- log(self.config, error.message || error.error_message, 'error');
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
- updateConfigData(config) {
485
- const configKeyMapper = {};
486
- const serverConfigKeyMapper = {
487
- cmsApiKey: this.config.target_stack,
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(config.configuration) && !_.isEmpty(configKeyMapper)) {
491
- _.forEach(_.keys(configKeyMapper), (key) => {
492
- config.configuration[key] = configKeyMapper[key];
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
- if (!_.isEmpty(config.server_configuration) && !_.isEmpty(serverConfigKeyMapper)) {
497
- _.forEach(_.keys(serverConfigKeyMapper), (key) => {
498
- config.server_configuration[key] = serverConfigKeyMapper[key];
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 config;
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, httpClient, nodeCrypto }>} param
492
+ * @param {Object<{ data, app }>} param
508
493
  * @returns {Promise<void>}
509
494
  */
510
- updateAppsConfig = ({ data, app, httpClient, nodeCrypto }) => {
511
- const self = this;
512
- return new Promise((resolve, reject) => {
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
- payload = self.updateConfigData(payload);
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
- if (_.isEmpty(data) || _.isEmpty(payload) || !data.installation_uid) {
526
- resolve();
527
- } else {
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
- validateAppName = (name) => {
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
  };