@contentstack/cli-cm-import 1.5.10 → 1.6.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.
Files changed (110) hide show
  1. package/README.md +6 -14
  2. package/bin/dev +17 -0
  3. package/bin/dev.cmd +3 -0
  4. package/bin/run +6 -0
  5. package/bin/run.cmd +3 -0
  6. package/lib/commands/cm/stacks/import.d.ts +10 -0
  7. package/lib/commands/cm/stacks/import.js +111 -0
  8. package/lib/config/index.d.ts +3 -0
  9. package/lib/config/index.js +372 -0
  10. package/lib/import/index.d.ts +1 -0
  11. package/lib/import/index.js +8 -0
  12. package/lib/import/module-importer.d.ts +13 -0
  13. package/lib/import/module-importer.js +70 -0
  14. package/lib/import/modules/assets.d.ts +63 -0
  15. package/lib/import/modules/assets.js +265 -0
  16. package/lib/import/modules/base-class.d.ts +69 -0
  17. package/lib/import/modules/base-class.js +165 -0
  18. package/lib/import/modules/index.d.ts +2 -0
  19. package/lib/import/modules/index.js +19 -0
  20. package/lib/import/modules/locales.d.ts +31 -0
  21. package/lib/import/modules/locales.js +152 -0
  22. package/lib/import/modules-js/assets.d.ts +33 -0
  23. package/lib/import/modules-js/assets.js +415 -0
  24. package/lib/import/modules-js/content-types.d.ts +33 -0
  25. package/lib/import/modules-js/content-types.js +176 -0
  26. package/lib/import/modules-js/custom-roles.d.ts +15 -0
  27. package/lib/import/modules-js/custom-roles.js +141 -0
  28. package/lib/import/modules-js/entries.d.ts +54 -0
  29. package/lib/import/modules-js/entries.js +1260 -0
  30. package/lib/import/modules-js/environments.d.ts +13 -0
  31. package/lib/import/modules-js/environments.js +85 -0
  32. package/lib/import/modules-js/extensions.d.ts +17 -0
  33. package/lib/import/modules-js/extensions.js +86 -0
  34. package/lib/import/modules-js/global-fields.d.ts +13 -0
  35. package/lib/import/modules-js/global-fields.js +109 -0
  36. package/lib/import/modules-js/index.d.ts +1 -0
  37. package/lib/import/modules-js/index.js +33 -0
  38. package/lib/import/modules-js/labels.d.ts +20 -0
  39. package/lib/import/modules-js/labels.js +148 -0
  40. package/lib/import/modules-js/locales.d.ts +24 -0
  41. package/lib/import/modules-js/locales.js +196 -0
  42. package/lib/import/modules-js/marketplace-apps.d.ts +60 -0
  43. package/lib/import/modules-js/marketplace-apps.js +409 -0
  44. package/lib/import/modules-js/webhooks.d.ts +17 -0
  45. package/lib/import/modules-js/webhooks.js +85 -0
  46. package/lib/import/modules-js/workflows.d.ts +18 -0
  47. package/lib/import/modules-js/workflows.js +132 -0
  48. package/lib/types/default-config.d.ts +130 -0
  49. package/lib/types/default-config.js +2 -0
  50. package/lib/types/import-config.d.ts +51 -0
  51. package/lib/types/import-config.js +2 -0
  52. package/lib/types/index.d.ts +30 -0
  53. package/lib/types/index.js +2 -0
  54. package/lib/utils/asset-helper.d.ts +4 -0
  55. package/lib/utils/asset-helper.js +387 -0
  56. package/lib/utils/backup-handler.d.ts +2 -0
  57. package/lib/utils/backup-handler.js +31 -0
  58. package/lib/utils/common-helper.d.ts +20 -0
  59. package/lib/utils/common-helper.js +244 -0
  60. package/lib/utils/content-type-helper.d.ts +49 -0
  61. package/lib/utils/content-type-helper.js +143 -0
  62. package/lib/utils/entries-helper.d.ts +4 -0
  63. package/lib/utils/entries-helper.js +252 -0
  64. package/lib/utils/extension-helper.d.ts +10 -0
  65. package/lib/utils/extension-helper.js +72 -0
  66. package/lib/utils/file-helper.d.ts +14 -0
  67. package/lib/utils/file-helper.js +140 -0
  68. package/lib/utils/import-config-handler.d.ts +3 -0
  69. package/lib/utils/import-config-handler.js +73 -0
  70. package/lib/utils/index.d.ts +12 -0
  71. package/lib/utils/index.js +29 -0
  72. package/lib/utils/interactive.d.ts +2 -0
  73. package/lib/utils/interactive.js +23 -0
  74. package/lib/utils/logger.d.ts +8 -0
  75. package/lib/utils/logger.js +154 -0
  76. package/lib/utils/login-handler.d.ts +8 -0
  77. package/lib/utils/login-handler.js +53 -0
  78. package/lib/utils/marketplace-app-helper.d.ts +4 -0
  79. package/lib/utils/marketplace-app-helper.js +31 -0
  80. package/messages/index.json +1 -7
  81. package/oclif.manifest.json +2 -2
  82. package/package.json +47 -21
  83. package/src/app.js +0 -217
  84. package/src/commands/cm/stacks/import.js +0 -161
  85. package/src/config/default.js +0 -352
  86. package/src/lib/import/assets.js +0 -495
  87. package/src/lib/import/content-types.js +0 -201
  88. package/src/lib/import/custom-roles.js +0 -169
  89. package/src/lib/import/entries.js +0 -1480
  90. package/src/lib/import/environments.js +0 -106
  91. package/src/lib/import/extensions.js +0 -108
  92. package/src/lib/import/global-fields.js +0 -135
  93. package/src/lib/import/labels.js +0 -175
  94. package/src/lib/import/locales.js +0 -216
  95. package/src/lib/import/marketplace-apps.js +0 -542
  96. package/src/lib/import/webhooks.js +0 -113
  97. package/src/lib/import/workflows.js +0 -166
  98. package/src/lib/util/extensionsUidReplace.js +0 -67
  99. package/src/lib/util/fs.js +0 -124
  100. package/src/lib/util/import-flags.js +0 -187
  101. package/src/lib/util/index.js +0 -222
  102. package/src/lib/util/log.js +0 -144
  103. package/src/lib/util/login.js +0 -58
  104. package/src/lib/util/lookupReplaceAssets.js +0 -366
  105. package/src/lib/util/lookupReplaceEntries.js +0 -250
  106. package/src/lib/util/marketplace-app-helper.js +0 -31
  107. package/src/lib/util/removeReferenceFields.js +0 -59
  108. package/src/lib/util/schemaTemplate.js +0 -38
  109. package/src/lib/util/supress-mandatory-fields.js +0 -34
  110. package/src/lib/util/upload.js +0 -56
@@ -0,0 +1,196 @@
1
+ /* eslint-disable no-prototype-builtins */
2
+ /*!
3
+ * Contentstack Import
4
+ * Copyright (c) 2019 Contentstack LLC
5
+ * MIT Licensed
6
+ */
7
+ let fs = require('fs');
8
+ let path = require('path');
9
+ let chalk = require('chalk');
10
+ let mkdirp = require('mkdirp');
11
+ let Promise = require('bluebird');
12
+ let { isEmpty, merge, cloneDeep } = require('lodash');
13
+ const { cliux } = require('@contentstack/cli-utilities');
14
+ let { default: config } = require('../../config');
15
+ const { fileHelper, log, formatError } = require('../../utils');
16
+ module.exports = class ImportLanguages {
17
+ constructor(importConfig, stackAPIClient) {
18
+ var _a;
19
+ this.config = merge(config, importConfig);
20
+ this.stackAPIClient = stackAPIClient;
21
+ this.fails = [];
22
+ this.success = [];
23
+ this.langUidMapper = {};
24
+ this.masterLanguage = (_a = importConfig.master_locale) === null || _a === void 0 ? void 0 : _a.code;
25
+ this.langConfig = importConfig.modules.locales;
26
+ this.sourceMasterLangConfig = config.modules.masterLocale;
27
+ this.reqConcurrency = importConfig.concurrency || importConfig.fetchConcurrency || 1;
28
+ }
29
+ start() {
30
+ log(this.config, 'Migrating languages', 'success');
31
+ const self = this;
32
+ let langMapperPath = path.resolve(this.config.data, 'mapper', 'languages');
33
+ let langFolderPath = path.resolve(this.config.data, this.langConfig.dirName);
34
+ let langFailsPath = path.resolve(this.config.data, 'mapper', 'languages', 'fails.json');
35
+ let langUidMapperPath = path.resolve(this.config.data, 'mapper', 'languages', 'uid-mapper.json');
36
+ self.langSuccessPath = path.resolve(this.config.data, 'mapper', 'languages', 'success.json');
37
+ self.languages = fileHelper.readFileSync(path.resolve(langFolderPath, this.langConfig.fileName));
38
+ self.sourceMasterLanguages = fileHelper.readFileSync(path.resolve(langFolderPath, this.sourceMasterLangConfig.fileName));
39
+ mkdirp.sync(langMapperPath);
40
+ if (fs.existsSync(langUidMapperPath)) {
41
+ self.langUidMapper = fileHelper.readFileSync(langUidMapperPath);
42
+ self.langUidMapper = self.langUidMapper || {};
43
+ }
44
+ return new Promise(async function (resolve, reject) {
45
+ if (self.languages === undefined || isEmpty(self.languages)) {
46
+ log(self.config, chalk.white('No Languages Found'), 'success');
47
+ return resolve({ empty: true });
48
+ }
49
+ let sourceMasterLangDetails = self.sourceMasterLanguages && Object.values(self.sourceMasterLanguages);
50
+ if (sourceMasterLangDetails &&
51
+ sourceMasterLangDetails[0] &&
52
+ sourceMasterLangDetails[0]['code'] &&
53
+ self.masterLanguage['code'] === sourceMasterLangDetails[0]['code']) {
54
+ await self.checkAndUpdateMasterLocaleName(sourceMasterLangDetails).catch((error) => {
55
+ log(self.config, formatError(error), 'warn');
56
+ });
57
+ }
58
+ let langUids = Object.keys(self.languages);
59
+ return Promise.map(langUids, (langUid) => {
60
+ let lang = self.languages[langUid];
61
+ if (!self.langUidMapper.hasOwnProperty(langUid) && lang.code !== self.masterLanguage) {
62
+ let requestOption = {
63
+ locale: {
64
+ code: lang.code,
65
+ name: lang.name,
66
+ },
67
+ };
68
+ return self.stackAPIClient
69
+ .locale()
70
+ .create(requestOption)
71
+ .then((locale) => {
72
+ self.success.push(locale.items);
73
+ self.langUidMapper[langUid] = locale.uid;
74
+ fileHelper.writeFileSync(langUidMapperPath, self.langUidMapper);
75
+ })
76
+ .catch(function (err) {
77
+ var _a;
78
+ let error = JSON.parse(err.message);
79
+ if (error.hasOwnProperty('errorCode') && error.errorCode === 247) {
80
+ if ((_a = error === null || error === void 0 ? void 0 : error.errors) === null || _a === void 0 ? void 0 : _a.code) {
81
+ log(self.config, error.errors.code[0], 'error');
82
+ }
83
+ else {
84
+ log(self.config, err, 'error');
85
+ }
86
+ return err;
87
+ }
88
+ self.fails.push(lang);
89
+ log(self.config, `Language '${lang.code}' failed to import\n`, 'error');
90
+ log(self.config, formatError(err), 'error');
91
+ });
92
+ }
93
+ else {
94
+ // the language has already been created
95
+ log(self.config, `The language '${lang.code}' already exists.`, 'error');
96
+ }
97
+ return Promise.resolve();
98
+ // import 2 languages at a time
99
+ }, { concurrency: self.reqConcurrency })
100
+ .then(function () {
101
+ // languages have imported successfully
102
+ return self
103
+ .updateLocales(langUids)
104
+ .then(() => {
105
+ fileHelper.writeFileSync(self.langSuccessPath, self.success);
106
+ log(self.config, chalk.green('Languages have been imported successfully!'), 'success');
107
+ resolve();
108
+ })
109
+ .catch(function (error) {
110
+ log(self.config, formatError(error), 'error');
111
+ reject(error);
112
+ });
113
+ })
114
+ .catch(function (error) {
115
+ // error while importing languages
116
+ fileHelper.writeFileSync(langFailsPath, self.fails);
117
+ log(self.config, `Language import failed. ${formatError(error)}`, 'error');
118
+ reject('failed to import Languages');
119
+ });
120
+ });
121
+ }
122
+ updateLocales(langUids) {
123
+ let self = this;
124
+ return new Promise(function (resolve, reject) {
125
+ Promise.all(langUids.map(async (langUid) => {
126
+ let lang = {};
127
+ let requireKeys = self.config.modules.locales.requiredKeys;
128
+ let _lang = self.languages[langUid];
129
+ requireKeys.forEach((e) => {
130
+ lang[e] = _lang[e];
131
+ });
132
+ let langobj = self.stackAPIClient.locale(lang.code);
133
+ Object.assign(langobj, cloneDeep(lang));
134
+ langobj.update().then(() => {
135
+ // empty function
136
+ });
137
+ }))
138
+ .then(resolve)
139
+ .catch((error) => {
140
+ log(self.config, formatError(error), 'error');
141
+ reject(error);
142
+ });
143
+ });
144
+ }
145
+ checkAndUpdateMasterLocaleName(sourceMasterLangDetails) {
146
+ let self = this;
147
+ return new Promise(async function (resolve, reject) {
148
+ let masterLangDetails = await self.stackAPIClient
149
+ .locale(self.masterLanguage['code'])
150
+ .fetch()
151
+ .catch((error) => {
152
+ log(self.config, formatError(error), 'warn');
153
+ });
154
+ if (masterLangDetails &&
155
+ masterLangDetails['name'] &&
156
+ sourceMasterLangDetails[0]['name'] &&
157
+ masterLangDetails['name'].toString().toUpperCase() !==
158
+ sourceMasterLangDetails[0]['name'].toString().toUpperCase()) {
159
+ cliux.print('WARNING!!! The master language name for the source and destination is different.', {
160
+ color: 'yellow',
161
+ });
162
+ cliux.print(`Old Master language name: ${masterLangDetails['name']}`, { color: 'red' });
163
+ cliux.print(`New Master language name: ${sourceMasterLangDetails[0]['name']}`, { color: 'green' });
164
+ let confirm = await cliux.inquire({
165
+ type: 'confirm',
166
+ message: 'Are you sure you want to update name of master language?',
167
+ name: 'confirmation',
168
+ });
169
+ if (confirm) {
170
+ let languid = sourceMasterLangDetails[0] && sourceMasterLangDetails[0]['uid'];
171
+ let lang = self.sourceMasterLanguages[languid];
172
+ if (!lang)
173
+ return reject('Locale not found.!');
174
+ const langObj = self.stackAPIClient.locale(lang.code);
175
+ Object.assign(langObj, { name: lang.name });
176
+ langObj
177
+ .update()
178
+ .then(() => {
179
+ fileHelper.writeFileSync(self.langSuccessPath, self.success);
180
+ log(self.config, chalk.green('Master Languages name have been updated successfully!'), 'success');
181
+ resolve();
182
+ })
183
+ .catch(function (error) {
184
+ reject(error);
185
+ });
186
+ }
187
+ else {
188
+ resolve();
189
+ }
190
+ }
191
+ else {
192
+ resolve();
193
+ }
194
+ });
195
+ }
196
+ };
@@ -0,0 +1,60 @@
1
+ export = ImportMarketplaceApps;
2
+ declare class ImportMarketplaceApps {
3
+ constructor(importConfig: any, stackAPIClient: any);
4
+ client: any;
5
+ httpClient: any;
6
+ appOrginalName: any;
7
+ appUidMapping: {};
8
+ appNameMapping: {};
9
+ marketplaceApps: any[];
10
+ installationUidMapping: {};
11
+ developerHubBaseUrl: any;
12
+ marketplaceAppFolderPath: string;
13
+ marketplaceAppConfig: {
14
+ dirName: string;
15
+ fileName: string;
16
+ };
17
+ config: any;
18
+ stackAPIClient: any;
19
+ start(): Promise<void>;
20
+ mapperDirPath: string;
21
+ uidMapperPath: string;
22
+ getOrgUid(): Promise<void>;
23
+ getAndValidateEncryptionKey(defaultValue: any, retry?: number): any;
24
+ nodeCrypto: NodeCrypto;
25
+ /**
26
+ * @method startInstallation
27
+ * @returns {Promise<void>}
28
+ */
29
+ startInstallation(): Promise<void>;
30
+ generateUidMapper(): Promise<{}>;
31
+ /**
32
+ * @method handleAllPrivateAppsCreationProcess
33
+ * @param {Object} options
34
+ * @returns {Promise<void>}
35
+ */
36
+ handleAllPrivateAppsCreationProcess(): Promise<void>;
37
+ getConfirmationToCreateApps(privateApps: any): Promise<boolean>;
38
+ createPrivateApps(app: any, uidCleaned?: boolean, appSuffix?: number): any;
39
+ appCreationCallback(app: any, response: any, appSuffix: any): any;
40
+ handleNameConflict(app: any, appSuffix: any): any;
41
+ updateManifestUILocations(locations: any, type?: string, appSuffix?: number): any[];
42
+ getAppName(name: any, appSuffix?: number): any;
43
+ /**
44
+ * @method installApps
45
+ * @param {Object} options
46
+ * @returns {Void}
47
+ */
48
+ installApps(app: any, installedApps: any): void;
49
+ makeRedirectUrlCall(response: any, appName: any): Promise<void>;
50
+ ifAppAlreadyExist(app: any, currentStackApp: any): Promise<any>;
51
+ confirmToCloseProcess(installation: any): Promise<void>;
52
+ /**
53
+ * @method updateAppsConfig
54
+ * @param {Object<{ data, app }>} param
55
+ * @returns {Promise<void>}
56
+ */
57
+ updateAppsConfig(app: any): Promise<void>;
58
+ validateAppName(name: any): true | "The app name should be within 3-20 characters long.";
59
+ }
60
+ import { NodeCrypto } from "@contentstack/cli-utilities";
@@ -0,0 +1,409 @@
1
+ /*!
2
+ * Contentstack Export
3
+ * Copyright (c) 2019 Contentstack LLC
4
+ * MIT Licensed
5
+ */
6
+ const fs = require('fs');
7
+ const _ = require('lodash');
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+ const mkdirp = require('mkdirp');
11
+ const { cliux, HttpClient, NodeCrypto, managementSDKClient, isAuthenticated, HttpClientDecorator, OauthDecorator, } = require('@contentstack/cli-utilities');
12
+ const { log, fileHelper: { readFileSync, writeFile }, formatError, } = require('../../utils');
13
+ let { default: config } = require('../../config');
14
+ const { getDeveloperHubUrl, getAllStackSpecificApps } = require('../../utils/marketplace-app-helper');
15
+ module.exports = class ImportMarketplaceApps {
16
+ constructor(importConfig, stackAPIClient) {
17
+ this.appUidMapping = {};
18
+ this.appNameMapping = {};
19
+ this.marketplaceApps = [];
20
+ this.installationUidMapping = {};
21
+ this.developerHubBaseUrl = null;
22
+ this.marketplaceAppFolderPath = '';
23
+ this.marketplaceAppConfig = config.modules.marketplace_apps;
24
+ this.config = _.merge(config, importConfig);
25
+ this.stackAPIClient = stackAPIClient;
26
+ }
27
+ async start() {
28
+ this.mapperDirPath = path.resolve(this.config.data, 'mapper', 'marketplace_apps');
29
+ this.uidMapperPath = path.join(this.mapperDirPath, 'uid-mapping.json');
30
+ this.marketplaceAppFolderPath = path.resolve(this.config.data, this.marketplaceAppConfig.dirName);
31
+ this.marketplaceApps = readFileSync(path.resolve(this.marketplaceAppFolderPath, this.marketplaceAppConfig.fileName));
32
+ if (_.isEmpty(this.marketplaceApps)) {
33
+ return Promise.resolve();
34
+ }
35
+ else if (!isAuthenticated()) {
36
+ cliux.print('\nWARNING!!! To import Marketplace apps, you must be logged in. Please check csdx auth:login --help to log in\n', { color: 'yellow' });
37
+ return Promise.resolve();
38
+ }
39
+ this.developerHubBaseUrl = this.config.developerHubBaseUrl || (await getDeveloperHubUrl(this.config));
40
+ this.client = await managementSDKClient({ endpoint: this.developerHubBaseUrl });
41
+ await this.getOrgUid();
42
+ const httpClient = new HttpClient();
43
+ if (!this.config.auth_token) {
44
+ this.httpClient = new OauthDecorator(httpClient);
45
+ const headers = await this.httpClient.preHeadersCheck(this.config);
46
+ this.httpClient = this.httpClient.headers(headers);
47
+ }
48
+ else {
49
+ this.httpClient = new HttpClientDecorator(httpClient);
50
+ this.httpClient.headers(this.config);
51
+ }
52
+ if (!fs.existsSync(this.mapperDirPath)) {
53
+ mkdirp.sync(this.mapperDirPath);
54
+ }
55
+ return this.startInstallation();
56
+ }
57
+ async getOrgUid() {
58
+ const tempAPIClient = await managementSDKClient({ host: this.config.host });
59
+ const tempStackData = await tempAPIClient
60
+ .stack({ api_key: this.config.target_stack })
61
+ .fetch()
62
+ .catch((error) => {
63
+ console.log(error);
64
+ });
65
+ if (tempStackData && tempStackData.org_uid) {
66
+ this.config.org_uid = tempStackData.org_uid;
67
+ }
68
+ }
69
+ async getAndValidateEncryptionKey(defaultValue, retry = 1) {
70
+ let appConfig = _.find(this.marketplaceApps, ({ configuration, server_configuration }) => !_.isEmpty(configuration) || !_.isEmpty(server_configuration));
71
+ if (!appConfig) {
72
+ return defaultValue;
73
+ }
74
+ const encryptionKey = await cliux.inquire({
75
+ type: 'input',
76
+ name: 'name',
77
+ default: defaultValue,
78
+ validate: (key) => {
79
+ if (!key)
80
+ return "Encryption key can't be empty.";
81
+ return true;
82
+ },
83
+ message: 'Enter marketplace app configurations encryption key',
84
+ });
85
+ try {
86
+ appConfig = !_.isEmpty(appConfig.configuration) ? appConfig.configuration : appConfig.server_configuration;
87
+ this.nodeCrypto = new NodeCrypto({ encryptionKey });
88
+ this.nodeCrypto.decrypt(appConfig);
89
+ }
90
+ catch (error) {
91
+ if (retry < this.config.getEncryptionKeyMaxRetry && error.code === 'ERR_OSSL_EVP_BAD_DECRYPT') {
92
+ cliux.print(`Provided encryption key is not valid or your data might be corrupted.! attempt(${retry}/${this.config.getEncryptionKeyMaxRetry})`, { color: 'red' });
93
+ // NOTE max retry limit is 3
94
+ return this.getAndValidateEncryptionKey(encryptionKey, retry + 1);
95
+ }
96
+ else {
97
+ cliux.print(`Maximum retry limit exceeded. Closing the process, please try again.! attempt(${retry}/${this.config.getEncryptionKeyMaxRetry})`, { color: 'red' });
98
+ process.exit(1);
99
+ }
100
+ }
101
+ return encryptionKey;
102
+ }
103
+ /**
104
+ * @method startInstallation
105
+ * @returns {Promise<void>}
106
+ */
107
+ async startInstallation() {
108
+ const cryptoArgs = {};
109
+ if (this.config.marketplaceAppEncryptionKey) {
110
+ cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
111
+ }
112
+ if (this.config.forceStopMarketplaceAppsPrompt) {
113
+ cryptoArgs['encryptionKey'] = this.config.marketplaceAppEncryptionKey;
114
+ this.nodeCrypto = new NodeCrypto(cryptoArgs);
115
+ }
116
+ else {
117
+ await this.getAndValidateEncryptionKey(this.config.marketplaceAppEncryptionKey);
118
+ }
119
+ // NOTE install all private apps which is not available for stack.
120
+ await this.handleAllPrivateAppsCreationProcess();
121
+ const installedApps = await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config);
122
+ log(this.config, 'Starting marketplace app installation', 'success');
123
+ for (let app of this.marketplaceApps) {
124
+ await this.installApps(app, installedApps);
125
+ }
126
+ const uidMapper = await this.generateUidMapper();
127
+ await writeFile(this.uidMapperPath, {
128
+ app_uid: this.appUidMapping,
129
+ extension_uid: uidMapper || {},
130
+ installation_uid: this.installationUidMapping,
131
+ });
132
+ }
133
+ async generateUidMapper() {
134
+ const listOfNewMeta = [];
135
+ const listOfOldMeta = [];
136
+ const extensionUidMapp = {};
137
+ const allInstalledApps = await getAllStackSpecificApps(this.developerHubBaseUrl, this.httpClient, this.config);
138
+ for (const app of this.marketplaceApps) {
139
+ listOfOldMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat());
140
+ }
141
+ for (const app of allInstalledApps) {
142
+ listOfNewMeta.push(..._.map(app.ui_location && app.ui_location.locations, 'meta').flat());
143
+ }
144
+ for (const { extension_uid, name, path } of _.filter(listOfOldMeta, 'name')) {
145
+ const meta = _.find(listOfNewMeta, { name, path }) || _.find(listOfNewMeta, { name: this.appNameMapping[name], path });
146
+ if (meta) {
147
+ extensionUidMapp[extension_uid] = meta.extension_uid;
148
+ }
149
+ }
150
+ return extensionUidMapp;
151
+ }
152
+ /**
153
+ * @method handleAllPrivateAppsCreationProcess
154
+ * @param {Object} options
155
+ * @returns {Promise<void>}
156
+ */
157
+ async handleAllPrivateAppsCreationProcess() {
158
+ const privateApps = _.filter(this.marketplaceApps, { manifest: { visibility: 'private' } });
159
+ if (_.isEmpty(privateApps)) {
160
+ return Promise.resolve();
161
+ }
162
+ await this.getConfirmationToCreateApps(privateApps);
163
+ log(this.config, 'Starting developer hub private apps re-creation', 'success');
164
+ for (let app of privateApps) {
165
+ // NOTE keys can be passed to install new app in the developer hub
166
+ app.manifest = _.pick(app.manifest, ['uid', 'name', 'description', 'icon', 'target_type', 'webhook', 'oauth']);
167
+ this.appOrginalName = app.manifest.name;
168
+ await this.createPrivateApps(Object.assign({ oauth: app.oauth, webhook: app.webhook, ui_location: app.ui_location }, app.manifest));
169
+ }
170
+ this.appOrginalName = undefined;
171
+ }
172
+ async getConfirmationToCreateApps(privateApps) {
173
+ if (!this.config.forceStopMarketplaceAppsPrompt) {
174
+ if (!(await cliux.confirm(chalk.yellow(`WARNING!!! The listed apps are private apps that are not available in the destination stack: \n\n${_.map(privateApps, ({ manifest: { name } }, index) => `${String(index + 1)}) ${name}`).join('\n')}\n\nWould you like to re-create the private app and then proceed with the installation? (y/n)`)))) {
175
+ if (await cliux.confirm(chalk.yellow(`\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)`))) {
176
+ return Promise.resolve(true);
177
+ }
178
+ if (!(await cliux.confirm(chalk.yellow('\nWould you like to re-create the private app and then proceed with the installation? (y/n)')))) {
179
+ process.exit();
180
+ }
181
+ }
182
+ }
183
+ }
184
+ async createPrivateApps(app, uidCleaned = false, appSuffix = 1) {
185
+ let locations = app.ui_location && app.ui_location.locations;
186
+ if (!uidCleaned && !_.isEmpty(locations)) {
187
+ app.ui_location.locations = this.updateManifestUILocations(locations, 'uid');
188
+ }
189
+ else if (uidCleaned && !_.isEmpty(locations)) {
190
+ app.ui_location.locations = this.updateManifestUILocations(locations, 'name', appSuffix);
191
+ }
192
+ if (app.name > 20) {
193
+ app.name = app.name.slice(0, 20);
194
+ }
195
+ const response = await this.client
196
+ .organization(this.config.org_uid)
197
+ .app()
198
+ .create(_.omit(app, ['uid']))
199
+ .catch((error) => error);
200
+ return this.appCreationCallback(app, response, appSuffix);
201
+ }
202
+ async appCreationCallback(app, response, appSuffix) {
203
+ const { statusText, message } = response || {};
204
+ if (message) {
205
+ if (_.toLower(statusText) === 'conflict') {
206
+ return this.handleNameConflict(app, appSuffix);
207
+ }
208
+ else {
209
+ log(this.config, formatError(message), 'error');
210
+ if (this.config.forceStopMarketplaceAppsPrompt)
211
+ return Promise.resolve();
212
+ if (await cliux.confirm(chalk.yellow('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)'))) {
213
+ Promise.resolve();
214
+ }
215
+ else {
216
+ process.exit();
217
+ }
218
+ }
219
+ }
220
+ else if (response.uid) {
221
+ // NOTE new app installation
222
+ log(this.config, `${response.name} app created successfully.!`, 'success');
223
+ this.appUidMapping[app.uid] = response.uid;
224
+ this.appNameMapping[this.appOrginalName] = response.name;
225
+ }
226
+ }
227
+ async handleNameConflict(app, appSuffix) {
228
+ const appName = this.config.forceStopMarketplaceAppsPrompt
229
+ ? this.getAppName(app.name, appSuffix)
230
+ : await cliux.inquire({
231
+ type: 'input',
232
+ name: 'name',
233
+ validate: this.validateAppName,
234
+ default: this.getAppName(app.name, appSuffix),
235
+ message: `${app.name} app already exist. Enter a new name to create an app.?`,
236
+ });
237
+ app.name = appName;
238
+ return this.createPrivateApps(app, true, appSuffix + 1);
239
+ }
240
+ updateManifestUILocations(locations, type = 'uid', appSuffix = 1) {
241
+ switch (type) {
242
+ case 'uid':
243
+ return _.map(locations, (location) => {
244
+ if (location.meta) {
245
+ location.meta = _.map(location.meta, (meta) => _.omit(meta, ['uid']));
246
+ }
247
+ return location;
248
+ });
249
+ case 'name':
250
+ return _.map(locations, (location) => {
251
+ if (location.meta) {
252
+ location.meta = _.map(location.meta, (meta) => {
253
+ if (meta.name) {
254
+ const name = `${_.first(_.split(meta.name, '◈'))}◈${appSuffix}`;
255
+ if (!this.appNameMapping[this.appOrginalName]) {
256
+ this.appNameMapping[this.appOrginalName] = name;
257
+ }
258
+ meta.name = name;
259
+ }
260
+ return meta;
261
+ });
262
+ }
263
+ return location;
264
+ });
265
+ }
266
+ }
267
+ getAppName(name, appSuffix = 1) {
268
+ if (name.length >= 19)
269
+ name = name.slice(0, 18);
270
+ name = `${_.first(_.split(name, '◈'))}◈${appSuffix}`;
271
+ return name;
272
+ }
273
+ /**
274
+ * @method installApps
275
+ * @param {Object} options
276
+ * @returns {Void}
277
+ */
278
+ async installApps(app, installedApps) {
279
+ let updateParam;
280
+ let installation;
281
+ const { configuration, server_configuration } = app;
282
+ const currentStackApp = _.find(installedApps, { manifest: { uid: app.manifest.uid } });
283
+ if (!currentStackApp) {
284
+ // NOTE install new app
285
+ installation = await this.client
286
+ .organization(this.config.org_uid)
287
+ .app(this.appUidMapping[app.manifest.uid] || app.manifest.uid)
288
+ .install({ targetUid: this.config.target_stack, targetType: 'stack' })
289
+ .catch((error) => error);
290
+ if (installation.installation_uid) {
291
+ let appName = this.appNameMapping[app.manifest.name]
292
+ ? this.appNameMapping[app.manifest.name]
293
+ : app.manifest.name;
294
+ log(this.config, `${appName} app installed successfully.!`, 'success');
295
+ await this.makeRedirectUrlCall(installation, app.manifest.name);
296
+ this.installationUidMapping[app.uid] = installation.installation_uid;
297
+ updateParam = Object.assign(Object.assign({ manifest: app.manifest }, installation), { configuration, server_configuration });
298
+ }
299
+ else if (installation.message) {
300
+ log(this.config, formatError(installation.message), 'success');
301
+ await this.confirmToCloseProcess(installation);
302
+ }
303
+ }
304
+ else if (!_.isEmpty(configuration) || !_.isEmpty(server_configuration)) {
305
+ log(this.config, `${app.manifest.name} is already installed`, 'success');
306
+ updateParam = await this.ifAppAlreadyExist(app, currentStackApp);
307
+ }
308
+ if (!this.appUidMapping[app.manifest.uid]) {
309
+ this.appUidMapping[app.manifest.uid] = currentStackApp ? currentStackApp.manifest.uid : app.manifest.uid;
310
+ }
311
+ // NOTE update configurations
312
+ if (updateParam && (!_.isEmpty(updateParam.configuration) || !_.isEmpty(updateParam.server_configuration))) {
313
+ await this.updateAppsConfig(updateParam);
314
+ }
315
+ }
316
+ async makeRedirectUrlCall(response, appName) {
317
+ if (response.redirect_url) {
318
+ log(this.config, `${appName} - OAuth api call started.!`, 'info');
319
+ await new HttpClient({ maxRedirects: 20, maxBodyLength: Infinity })
320
+ .get(response.redirect_url)
321
+ .then(async ({ response }) => {
322
+ if (_.includes([501, 403], response.status)) {
323
+ log(this.config, `${appName} - ${response.statusText}, OAuth api call failed.!`, 'error');
324
+ log(this.config, formatError(response), 'error');
325
+ await this.confirmToCloseProcess({ message: response.data });
326
+ }
327
+ else {
328
+ log(this.config, `${appName} - OAuth api call completed.!`, 'success');
329
+ }
330
+ })
331
+ .catch((error) => {
332
+ if (_.includes([501, 403], error.status)) {
333
+ log(this.config, formatError(error), 'error');
334
+ }
335
+ });
336
+ }
337
+ }
338
+ async ifAppAlreadyExist(app, currentStackApp) {
339
+ let updateParam;
340
+ const { manifest: { name }, configuration, server_configuration, } = app;
341
+ if (!_.isEmpty(configuration) || !_.isEmpty(server_configuration)) {
342
+ cliux.print(`\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`, { color: 'yellow' });
343
+ const configOption = this.config.forceStopMarketplaceAppsPrompt
344
+ ? 'Update it with the new configuration.'
345
+ : await cliux.inquire({
346
+ choices: [
347
+ 'Update it with the new configuration.',
348
+ 'Do not update the configuration (WARNING!!! If you do not update the configuration, there may be some issues with the content which you import).',
349
+ 'Exit',
350
+ ],
351
+ type: 'list',
352
+ name: 'value',
353
+ message: 'Choose the option to proceed',
354
+ });
355
+ if (configOption === 'Exit') {
356
+ process.exit();
357
+ }
358
+ else if (configOption === 'Update it with the new configuration.') {
359
+ updateParam = Object.assign(Object.assign({ manifest: app.manifest }, currentStackApp), { configuration, server_configuration });
360
+ }
361
+ }
362
+ return updateParam;
363
+ }
364
+ async confirmToCloseProcess(installation) {
365
+ cliux.print(`\nWARNING!!! ${formatError(installation.message)}\n`, { color: 'yellow' });
366
+ if (!this.config.forceStopMarketplaceAppsPrompt) {
367
+ if (!(await cliux.confirm(chalk.yellow('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)')))) {
368
+ process.exit();
369
+ }
370
+ }
371
+ }
372
+ /**
373
+ * @method updateAppsConfig
374
+ * @param {Object<{ data, app }>} param
375
+ * @returns {Promise<void>}
376
+ */
377
+ updateAppsConfig(app) {
378
+ const payload = {};
379
+ const { uid, configuration, server_configuration } = app;
380
+ if (!_.isEmpty(configuration)) {
381
+ payload['configuration'] = this.nodeCrypto.decrypt(configuration);
382
+ }
383
+ if (!_.isEmpty(server_configuration)) {
384
+ payload['server_configuration'] = this.nodeCrypto.decrypt(server_configuration);
385
+ }
386
+ if (_.isEmpty(app) || _.isEmpty(payload) || !uid) {
387
+ return Promise.resolve();
388
+ }
389
+ let installation = this.client
390
+ .organization(this.config.org_uid)
391
+ .app(app.manifest.uid)
392
+ .installation(uid);
393
+ installation = Object.assign(installation, payload);
394
+ return installation
395
+ .update()
396
+ .then(async (data) => {
397
+ if (data) {
398
+ log(this.config, `${app.manifest.name} app config updated successfully.!`, 'success');
399
+ }
400
+ })
401
+ .catch((error) => log(this.config, formatError(error), 'error'));
402
+ }
403
+ validateAppName(name) {
404
+ if (name.length < 3 || name.length > 20) {
405
+ return 'The app name should be within 3-20 characters long.';
406
+ }
407
+ return true;
408
+ }
409
+ };
@@ -0,0 +1,17 @@
1
+ export = ImportWebhooks;
2
+ declare class ImportWebhooks {
3
+ constructor(importConfig: any, stackAPIClient: any);
4
+ config: any;
5
+ fails: any[];
6
+ success: any[];
7
+ webUidMapper: {};
8
+ webhooksConfig: {
9
+ dirName: string;
10
+ fileName: string;
11
+ };
12
+ reqConcurrency: number;
13
+ stackAPIClient: any;
14
+ start(): Promise<any>;
15
+ webhooks: any;
16
+ }
17
+ import Promise = require("bluebird");