@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.
- package/README.md +6 -14
- package/bin/dev +17 -0
- package/bin/dev.cmd +3 -0
- package/bin/run +6 -0
- package/bin/run.cmd +3 -0
- package/lib/commands/cm/stacks/import.d.ts +10 -0
- package/lib/commands/cm/stacks/import.js +111 -0
- package/lib/config/index.d.ts +3 -0
- package/lib/config/index.js +372 -0
- package/lib/import/index.d.ts +1 -0
- package/lib/import/index.js +8 -0
- package/lib/import/module-importer.d.ts +13 -0
- package/lib/import/module-importer.js +70 -0
- package/lib/import/modules/assets.d.ts +63 -0
- package/lib/import/modules/assets.js +265 -0
- package/lib/import/modules/base-class.d.ts +69 -0
- package/lib/import/modules/base-class.js +165 -0
- package/lib/import/modules/index.d.ts +2 -0
- package/lib/import/modules/index.js +19 -0
- package/lib/import/modules/locales.d.ts +31 -0
- package/lib/import/modules/locales.js +152 -0
- package/lib/import/modules-js/assets.d.ts +33 -0
- package/lib/import/modules-js/assets.js +415 -0
- package/lib/import/modules-js/content-types.d.ts +33 -0
- package/lib/import/modules-js/content-types.js +176 -0
- package/lib/import/modules-js/custom-roles.d.ts +15 -0
- package/lib/import/modules-js/custom-roles.js +141 -0
- package/lib/import/modules-js/entries.d.ts +54 -0
- package/lib/import/modules-js/entries.js +1260 -0
- package/lib/import/modules-js/environments.d.ts +13 -0
- package/lib/import/modules-js/environments.js +85 -0
- package/lib/import/modules-js/extensions.d.ts +17 -0
- package/lib/import/modules-js/extensions.js +86 -0
- package/lib/import/modules-js/global-fields.d.ts +13 -0
- package/lib/import/modules-js/global-fields.js +109 -0
- package/lib/import/modules-js/index.d.ts +1 -0
- package/lib/import/modules-js/index.js +33 -0
- package/lib/import/modules-js/labels.d.ts +20 -0
- package/lib/import/modules-js/labels.js +148 -0
- package/lib/import/modules-js/locales.d.ts +24 -0
- package/lib/import/modules-js/locales.js +196 -0
- package/lib/import/modules-js/marketplace-apps.d.ts +60 -0
- package/lib/import/modules-js/marketplace-apps.js +409 -0
- package/lib/import/modules-js/webhooks.d.ts +17 -0
- package/lib/import/modules-js/webhooks.js +85 -0
- package/lib/import/modules-js/workflows.d.ts +18 -0
- package/lib/import/modules-js/workflows.js +132 -0
- package/lib/types/default-config.d.ts +130 -0
- package/lib/types/default-config.js +2 -0
- package/lib/types/import-config.d.ts +51 -0
- package/lib/types/import-config.js +2 -0
- package/lib/types/index.d.ts +30 -0
- package/lib/types/index.js +2 -0
- package/lib/utils/asset-helper.d.ts +4 -0
- package/lib/utils/asset-helper.js +387 -0
- package/lib/utils/backup-handler.d.ts +2 -0
- package/lib/utils/backup-handler.js +31 -0
- package/lib/utils/common-helper.d.ts +20 -0
- package/lib/utils/common-helper.js +244 -0
- package/lib/utils/content-type-helper.d.ts +49 -0
- package/lib/utils/content-type-helper.js +143 -0
- package/lib/utils/entries-helper.d.ts +4 -0
- package/lib/utils/entries-helper.js +252 -0
- package/lib/utils/extension-helper.d.ts +10 -0
- package/lib/utils/extension-helper.js +72 -0
- package/lib/utils/file-helper.d.ts +14 -0
- package/lib/utils/file-helper.js +140 -0
- package/lib/utils/import-config-handler.d.ts +3 -0
- package/lib/utils/import-config-handler.js +73 -0
- package/lib/utils/index.d.ts +12 -0
- package/lib/utils/index.js +29 -0
- package/lib/utils/interactive.d.ts +2 -0
- package/lib/utils/interactive.js +23 -0
- package/lib/utils/logger.d.ts +8 -0
- package/lib/utils/logger.js +154 -0
- package/lib/utils/login-handler.d.ts +8 -0
- package/lib/utils/login-handler.js +53 -0
- package/lib/utils/marketplace-app-helper.d.ts +4 -0
- package/lib/utils/marketplace-app-helper.js +31 -0
- package/messages/index.json +1 -7
- package/oclif.manifest.json +2 -2
- package/package.json +47 -21
- package/src/app.js +0 -217
- package/src/commands/cm/stacks/import.js +0 -161
- package/src/config/default.js +0 -352
- package/src/lib/import/assets.js +0 -495
- package/src/lib/import/content-types.js +0 -201
- package/src/lib/import/custom-roles.js +0 -169
- package/src/lib/import/entries.js +0 -1480
- package/src/lib/import/environments.js +0 -106
- package/src/lib/import/extensions.js +0 -108
- package/src/lib/import/global-fields.js +0 -135
- package/src/lib/import/labels.js +0 -175
- package/src/lib/import/locales.js +0 -216
- package/src/lib/import/marketplace-apps.js +0 -542
- package/src/lib/import/webhooks.js +0 -113
- package/src/lib/import/workflows.js +0 -166
- package/src/lib/util/extensionsUidReplace.js +0 -67
- package/src/lib/util/fs.js +0 -124
- package/src/lib/util/import-flags.js +0 -187
- package/src/lib/util/index.js +0 -222
- package/src/lib/util/log.js +0 -144
- package/src/lib/util/login.js +0 -58
- package/src/lib/util/lookupReplaceAssets.js +0 -366
- package/src/lib/util/lookupReplaceEntries.js +0 -250
- package/src/lib/util/marketplace-app-helper.js +0 -31
- package/src/lib/util/removeReferenceFields.js +0 -59
- package/src/lib/util/schemaTemplate.js +0 -38
- package/src/lib/util/supress-mandatory-fields.js +0 -34
- 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");
|