@contentstack/cli-cm-import 1.0.1 → 1.2.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 +4 -3
- package/oclif.manifest.json +1 -1
- package/package.json +8 -7
- package/src/app.js +92 -108
- package/src/commands/cm/stacks/import.js +8 -1
- package/src/config/default.js +24 -3
- package/src/lib/import/assets.js +291 -296
- package/src/lib/import/content-types.js +175 -239
- package/src/lib/import/custom-roles.js +176 -0
- package/src/lib/import/entries.js +237 -175
- package/src/lib/import/environments.js +40 -50
- package/src/lib/import/extensions.js +35 -41
- package/src/lib/import/global-fields.js +66 -65
- package/src/lib/import/labels.js +62 -61
- package/src/lib/import/locales.js +61 -63
- package/src/lib/import/marketplace-apps.js +443 -0
- package/src/lib/import/webhooks.js +45 -51
- package/src/lib/import/workflows.js +102 -62
- package/src/lib/util/extensionsUidReplace.js +22 -7
- package/src/lib/util/fs.js +91 -12
- package/src/lib/util/index.js +39 -3
- package/src/lib/util/log.js +7 -5
- package/src/lib/util/login.js +2 -1
- package/src/lib/util/lookupReplaceAssets.js +115 -12
- package/src/lib/util/lookupReplaceEntries.js +90 -89
- package/src/lib/util/marketplace-app-helper.js +57 -0
|
@@ -0,0 +1,443 @@
|
|
|
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 } = require('@contentstack/cli-utilities');
|
|
12
|
+
|
|
13
|
+
let config = require('../../config/default');
|
|
14
|
+
const { addlogs: log } = require('../util/log');
|
|
15
|
+
const { readFileSync, writeFile } = require('../util/fs');
|
|
16
|
+
const sdk = require('../util/contentstack-management-sdk');
|
|
17
|
+
const { getDeveloperHubUrl, getInstalledExtensions } = require('../util/marketplace-app-helper');
|
|
18
|
+
|
|
19
|
+
module.exports = class ImportMarketplaceApps {
|
|
20
|
+
client;
|
|
21
|
+
marketplaceApps = [];
|
|
22
|
+
marketplaceAppsUid = [];
|
|
23
|
+
developerHubBaseUrl = null;
|
|
24
|
+
marketplaceAppFolderPath = '';
|
|
25
|
+
marketplaceAppConfig = config.modules.marketplace_apps;
|
|
26
|
+
|
|
27
|
+
constructor(credentialConfig) {
|
|
28
|
+
this.config = _.merge(config, credentialConfig);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async start() {
|
|
32
|
+
this.client = sdk.Client(this.config);
|
|
33
|
+
this.developerHubBaseUrl = await getDeveloperHubUrl();
|
|
34
|
+
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',
|
|
38
|
+
);
|
|
39
|
+
this.marketplaceAppsUid = _.map(this.marketplaceApps, 'uid');
|
|
40
|
+
|
|
41
|
+
if (!this.config.auth_token && !_.isEmpty(this.marketplaceApps)) {
|
|
42
|
+
cliux.print(
|
|
43
|
+
'WARNING!!! To import Marketplace apps, you must be logged in. Please check csdx auth:login --help to log in',
|
|
44
|
+
{ color: 'yellow' },
|
|
45
|
+
);
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
} else if (_.isEmpty(this.marketplaceApps)) {
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await this.getOrgUid();
|
|
52
|
+
return this.handleInstallationProcess();
|
|
53
|
+
}
|
|
54
|
+
|
|
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 })
|
|
65
|
+
.fetch()
|
|
66
|
+
.catch((error) => {
|
|
67
|
+
console.log(error);
|
|
68
|
+
log(self.config, 'Starting marketplace app installation', 'success');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (stack && stack.org_uid) {
|
|
72
|
+
self.config.org_uid = stack.org_uid;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @method handleInstallationProcess
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
handleInstallationProcess = async () => {
|
|
82
|
+
const self = this;
|
|
83
|
+
const headers = {
|
|
84
|
+
authtoken: self.config.auth_token,
|
|
85
|
+
organization_uid: self.config.org_uid,
|
|
86
|
+
};
|
|
87
|
+
const httpClient = new HttpClient().headers(headers);
|
|
88
|
+
const nodeCrypto = new NodeCrypto();
|
|
89
|
+
|
|
90
|
+
// NOTE install all private apps which is not available for stack.
|
|
91
|
+
await this.handleAllPrivateAppsCreationProcess({ httpClient });
|
|
92
|
+
const installedExtensions = await getInstalledExtensions(self.config);
|
|
93
|
+
|
|
94
|
+
// NOTE after private app installation, refetch marketplace apps from file
|
|
95
|
+
const marketplaceAppsFromFile = readFileSync(
|
|
96
|
+
path.resolve(this.marketplaceAppFolderPath, self.marketplaceAppConfig.fileName),
|
|
97
|
+
);
|
|
98
|
+
this.marketplaceApps = _.filter(marketplaceAppsFromFile, ({ uid }) => _.includes(this.marketplaceAppsUid, uid));
|
|
99
|
+
|
|
100
|
+
log(self.config, 'Starting marketplace app installation', 'success');
|
|
101
|
+
|
|
102
|
+
for (let app of self.marketplaceApps) {
|
|
103
|
+
await self.installApps({ app, installedExtensions, httpClient, nodeCrypto });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// NOTE get all the extension again after all apps installed (To manage uid mapping in content type, entries)
|
|
107
|
+
const extensions = await getInstalledExtensions(self.config);
|
|
108
|
+
const mapperFolderPath = path.join(self.config.data, 'mapper', 'marketplace_apps');
|
|
109
|
+
|
|
110
|
+
if (!fs.existsSync(mapperFolderPath)) {
|
|
111
|
+
mkdirp.sync(mapperFolderPath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const appUidMapperPath = path.join(mapperFolderPath, 'marketplace-apps.json');
|
|
115
|
+
const installedExt = _.map(extensions, (row) =>
|
|
116
|
+
_.pick(row, ['uid', 'title', 'type', 'app_uid', 'app_installation_uid']),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
writeFile(appUidMapperPath, installedExt);
|
|
120
|
+
|
|
121
|
+
return Promise.resolve();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @method handleAllPrivateAppsCreationProcess
|
|
126
|
+
* @param {Object} options
|
|
127
|
+
* @returns {Promise<void>}
|
|
128
|
+
*/
|
|
129
|
+
handleAllPrivateAppsCreationProcess = async (options) => {
|
|
130
|
+
const self = this;
|
|
131
|
+
const { httpClient } = options;
|
|
132
|
+
const listOfExportedPrivateApps = _.filter(self.marketplaceApps, { visibility: 'private' });
|
|
133
|
+
|
|
134
|
+
if (_.isEmpty(listOfExportedPrivateApps)) {
|
|
135
|
+
return Promise.resolve();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// NOTE get list of developer-hub installed apps (private)
|
|
139
|
+
const installedDeveloperHubApps =
|
|
140
|
+
(await httpClient
|
|
141
|
+
.get(`${this.developerHubBaseUrl}/apps`)
|
|
142
|
+
.then(({ data: { data } }) => data)
|
|
143
|
+
.catch((err) => {
|
|
144
|
+
console.log(err);
|
|
145
|
+
})) || [];
|
|
146
|
+
const listOfNotInstalledPrivateApps = _.filter(
|
|
147
|
+
listOfExportedPrivateApps,
|
|
148
|
+
(app) => !_.includes(_.map(installedDeveloperHubApps, 'uid'), app.app_uid),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (!_.isEmpty(listOfNotInstalledPrivateApps) && !self.config.forceMarketplaceAppsImport) {
|
|
152
|
+
const confirmation = await cliux.confirm(
|
|
153
|
+
chalk.yellow(
|
|
154
|
+
`WARNING!!! The listed apps are private apps that are not available in the destination stack: \n\n${_.map(
|
|
155
|
+
listOfNotInstalledPrivateApps,
|
|
156
|
+
({ manifest: { name } }, index) => `${String(index + 1)}) ${name}`,
|
|
157
|
+
).join('\n')}\n\nWould you like to re-create the private app and then proceed with the installation? (y/n)`,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (!confirmation) {
|
|
162
|
+
const continueProcess = await cliux.confirm(
|
|
163
|
+
chalk.yellow(
|
|
164
|
+
`WARNING!!! Canceling the app re-creation may break the content type and entry import. Would you like to proceed? (y/n)`,
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (continueProcess) {
|
|
169
|
+
return resolve();
|
|
170
|
+
} else {
|
|
171
|
+
process.exit();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
log(self.config, 'Starting developer hub private apps re-creation', 'success');
|
|
177
|
+
|
|
178
|
+
for (let app of listOfNotInstalledPrivateApps) {
|
|
179
|
+
await self.createAllPrivateAppsInDeveloperHub({ app, httpClient });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return Promise.resolve();
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @method removeUidFromManifestUILocations
|
|
187
|
+
* @param {Array<Object>} locations
|
|
188
|
+
* @returns {Array<Object>}
|
|
189
|
+
*/
|
|
190
|
+
removeUidFromManifestUILocations = (locations) => {
|
|
191
|
+
return _.map(locations, (location) => {
|
|
192
|
+
if (location.meta) {
|
|
193
|
+
location.meta = _.map(location.meta, (meta) => _.omit(meta, ['uid']));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return location;
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @method createAllPrivateAppsInDeveloperHub
|
|
202
|
+
* @param {Object} options
|
|
203
|
+
* @returns {Promise<void>}
|
|
204
|
+
*/
|
|
205
|
+
createAllPrivateAppsInDeveloperHub = async (options, uidCleaned = false) => {
|
|
206
|
+
const self = this;
|
|
207
|
+
const { app, httpClient } = options;
|
|
208
|
+
|
|
209
|
+
return new Promise((resolve) => {
|
|
210
|
+
if (!uidCleaned && app.manifest.ui_location && !_.isEmpty(app.manifest.ui_location.locations)) {
|
|
211
|
+
app.manifest.ui_location.locations = this.removeUidFromManifestUILocations(app.manifest.ui_location.locations);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
httpClient
|
|
215
|
+
.post(`${this.developerHubBaseUrl}/apps`, app.manifest)
|
|
216
|
+
.then(async ({ data: result }) => {
|
|
217
|
+
const { name } = app.manifest;
|
|
218
|
+
const { data, error, message } = result || {};
|
|
219
|
+
|
|
220
|
+
if (error) {
|
|
221
|
+
log(self.config, message, 'error');
|
|
222
|
+
|
|
223
|
+
if (_.toLower(error) === 'conflict') {
|
|
224
|
+
const appName = self.config.forceMarketplaceAppsImport
|
|
225
|
+
? self.getAppName(app.manifest.name)
|
|
226
|
+
: await cliux.inquire({
|
|
227
|
+
type: 'input',
|
|
228
|
+
name: 'name',
|
|
229
|
+
default: `${app.manifest.name}-1`,
|
|
230
|
+
validate: this.validateAppName,
|
|
231
|
+
message: `${message}. Enter a new name to create an app.?`,
|
|
232
|
+
});
|
|
233
|
+
app.manifest.name = appName;
|
|
234
|
+
|
|
235
|
+
await self.createAllPrivateAppsInDeveloperHub({ app, httpClient }, true).then(resolve).catch(resolve);
|
|
236
|
+
} else {
|
|
237
|
+
if (self.config.forceMarketplaceAppsImport) return resolve();
|
|
238
|
+
|
|
239
|
+
const confirmation = await cliux.confirm(
|
|
240
|
+
chalk.yellow(
|
|
241
|
+
'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)',
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (confirmation) {
|
|
246
|
+
resolve();
|
|
247
|
+
} else {
|
|
248
|
+
process.exit();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} else if (data) {
|
|
252
|
+
// NOTE new app installation
|
|
253
|
+
log(self.config, `${name} app created successfully.!`, 'success');
|
|
254
|
+
this.updatePrivateAppUid(app, data, app.manifest.name);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
resolve();
|
|
258
|
+
})
|
|
259
|
+
.catch((error) => {
|
|
260
|
+
if (error && (error.message || error.error_message)) {
|
|
261
|
+
log(self.config, error.message || error.error_message, 'error');
|
|
262
|
+
} else {
|
|
263
|
+
log(self.config, 'Something went wrong.!', 'error');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
resolve();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @method updatePrivateAppUid
|
|
273
|
+
* @param {Object} app
|
|
274
|
+
* @param {Object} data
|
|
275
|
+
*/
|
|
276
|
+
updatePrivateAppUid = (app, data, appName) => {
|
|
277
|
+
const self = this;
|
|
278
|
+
const allMarketplaceApps = readFileSync(
|
|
279
|
+
path.resolve(self.marketplaceAppFolderPath, self.marketplaceAppConfig.fileName),
|
|
280
|
+
);
|
|
281
|
+
const index = _.findIndex(allMarketplaceApps, { uid: app.uid, visibility: 'private' });
|
|
282
|
+
|
|
283
|
+
if (index > -1) {
|
|
284
|
+
allMarketplaceApps[index] = {
|
|
285
|
+
...allMarketplaceApps[index],
|
|
286
|
+
title: data.name,
|
|
287
|
+
app_uid: data.uid,
|
|
288
|
+
old_title: allMarketplaceApps[index].title,
|
|
289
|
+
previous_data: [
|
|
290
|
+
...(allMarketplaceApps[index].old_data || []),
|
|
291
|
+
{ [`v${(allMarketplaceApps[index].old_data || []).length}`]: allMarketplaceApps[index] },
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// NOTE Update app name
|
|
296
|
+
allMarketplaceApps[index].manifest.name = appName;
|
|
297
|
+
|
|
298
|
+
writeFile(path.join(self.marketplaceAppFolderPath, self.marketplaceAppConfig.fileName), allMarketplaceApps);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @method installApps
|
|
304
|
+
* @param {Object} options
|
|
305
|
+
* @returns {Void}
|
|
306
|
+
*/
|
|
307
|
+
installApps = (options) => {
|
|
308
|
+
const self = this;
|
|
309
|
+
const { app, installedExtensions, httpClient, nodeCrypto } = options;
|
|
310
|
+
|
|
311
|
+
return new Promise((resolve, reject) => {
|
|
312
|
+
httpClient
|
|
313
|
+
.post(`${self.developerHubBaseUrl}/apps/${app.app_uid}/install`, {
|
|
314
|
+
target_type: 'stack',
|
|
315
|
+
target_uid: self.config.target_stack,
|
|
316
|
+
})
|
|
317
|
+
.then(async ({ data: result }) => {
|
|
318
|
+
let updateParam;
|
|
319
|
+
const { title } = app;
|
|
320
|
+
const { data, error, message, error_code, error_message } = result;
|
|
321
|
+
|
|
322
|
+
if (error || error_code) {
|
|
323
|
+
// NOTE if already installed copy only config data
|
|
324
|
+
log(self.config, `${message || error_message} - ${title}`, 'success');
|
|
325
|
+
const ext = _.find(installedExtensions, { app_uid: app.app_uid });
|
|
326
|
+
|
|
327
|
+
if (ext) {
|
|
328
|
+
if (!_.isEmpty(app.configuration) || !_.isEmpty(app.server_configuration)) {
|
|
329
|
+
cliux.print(
|
|
330
|
+
`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.`,
|
|
331
|
+
{ color: 'yellow' },
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const configOption = self.config.forceMarketplaceAppsImport
|
|
335
|
+
? 'Update it with the new configuration.'
|
|
336
|
+
: await cliux.inquire({
|
|
337
|
+
choices: [
|
|
338
|
+
'Update it with the new configuration.',
|
|
339
|
+
'Do not update the configuration (WARNING!!! If you do not update the configuration, there may be some issues with the content which you import).',
|
|
340
|
+
'Exit',
|
|
341
|
+
],
|
|
342
|
+
type: 'list',
|
|
343
|
+
name: 'value',
|
|
344
|
+
message: 'Choose the option to proceed',
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
if (configOption === 'Exit') {
|
|
348
|
+
process.exit();
|
|
349
|
+
} else if (configOption === 'Update it with the new configuration.') {
|
|
350
|
+
updateParam = {
|
|
351
|
+
app,
|
|
352
|
+
nodeCrypto,
|
|
353
|
+
httpClient,
|
|
354
|
+
data: { ...ext, installation_uid: ext.app_installation_uid },
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
if (!self.config.forceMarketplaceAppsImport) {
|
|
360
|
+
cliux.print(`WARNING!!! ${message || error_message}`, { color: 'yellow' });
|
|
361
|
+
const confirmation = await cliux.confirm(
|
|
362
|
+
chalk.yellow(
|
|
363
|
+
'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)',
|
|
364
|
+
),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (!confirmation) {
|
|
368
|
+
process.exit();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} else if (data) {
|
|
373
|
+
// NOTE new app installation
|
|
374
|
+
log(self.config, `${title} app installed successfully.!`, 'success');
|
|
375
|
+
updateParam = { data, app, nodeCrypto, httpClient };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (updateParam) {
|
|
379
|
+
self.updateAppsConfig(updateParam).then(resolve).catch(reject);
|
|
380
|
+
} else {
|
|
381
|
+
resolve();
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
.catch((error) => {
|
|
385
|
+
if (error && (error.message || error.error_message)) {
|
|
386
|
+
log(self.config, error.message || error.error_message, 'error');
|
|
387
|
+
} else {
|
|
388
|
+
log(self.config, 'Something went wrong.!', 'error');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
reject();
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @method updateAppsConfig
|
|
398
|
+
* @param {Object<{ data, app, httpClient, nodeCrypto }>} param
|
|
399
|
+
* @returns {Promise<void>}
|
|
400
|
+
*/
|
|
401
|
+
updateAppsConfig = ({ data, app, httpClient, nodeCrypto }) => {
|
|
402
|
+
const self = this;
|
|
403
|
+
return new Promise((resolve, reject) => {
|
|
404
|
+
const payload = {};
|
|
405
|
+
const { title, configuration, server_configuration } = app;
|
|
406
|
+
|
|
407
|
+
if (!_.isEmpty(configuration)) {
|
|
408
|
+
payload['configuration'] = nodeCrypto.decrypt(configuration);
|
|
409
|
+
}
|
|
410
|
+
if (!_.isEmpty(server_configuration)) {
|
|
411
|
+
payload['server_configuration'] = nodeCrypto.decrypt(server_configuration);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (_.isEmpty(data) || _.isEmpty(payload) || !data.installation_uid) {
|
|
415
|
+
resolve();
|
|
416
|
+
} else {
|
|
417
|
+
httpClient
|
|
418
|
+
.put(`${this.developerHubBaseUrl}/installations/${data.installation_uid}`, payload)
|
|
419
|
+
.then(() => {
|
|
420
|
+
log(self.config, `${title} app config updated successfully.!`, 'success');
|
|
421
|
+
})
|
|
422
|
+
.then(resolve)
|
|
423
|
+
.catch((error) => {
|
|
424
|
+
if (error && (error.message || error.error_message)) {
|
|
425
|
+
log(self.config, error.message || error.error_message, 'error');
|
|
426
|
+
} else {
|
|
427
|
+
log(self.config, 'Something went wrong.!', 'error');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
reject();
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
validateAppName = (name) => {
|
|
437
|
+
if (name.length < 3 || name.length > 20) {
|
|
438
|
+
return 'The app name should be within 3-20 characters long.';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return true;
|
|
442
|
+
};
|
|
443
|
+
};
|
|
@@ -9,113 +9,107 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const Promise = require('bluebird');
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const {isEmpty} = require('lodash');
|
|
12
|
+
const { isEmpty, merge } = require('lodash');
|
|
13
13
|
|
|
14
14
|
const helper = require('../util/fs');
|
|
15
|
+
const { formatError } = require('../util');
|
|
15
16
|
const { addlogs } = require('../util/log');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let reqConcurrency = config.concurrency;
|
|
19
|
-
let webhooksConfig = config.modules.webhooks;
|
|
17
|
+
const config = require('../../config/default');
|
|
18
|
+
const stack = require('../util/contentstack-management-sdk');
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
module.exports = class ImportWebhooks {
|
|
21
|
+
config;
|
|
22
|
+
fails = [];
|
|
23
|
+
success = [];
|
|
24
|
+
webUidMapper = {};
|
|
25
|
+
webhooksConfig = config.modules.webhooks;
|
|
26
|
+
reqConcurrency = config.concurrency || config.fetchConcurrency;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
constructor(credentialConfig) {
|
|
29
|
+
this.config = merge(config, credentialConfig);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
start() {
|
|
33
|
+
addlogs(this.config, chalk.white('Migrating webhooks'), 'success');
|
|
34
|
+
|
|
35
|
+
const self = this;
|
|
36
|
+
const client = stack.Client(this.config);
|
|
37
|
+
|
|
38
|
+
let webMapperPath = path.resolve(this.config.data, 'mapper', 'webhooks');
|
|
39
|
+
let webFailsPath = path.resolve(this.config.data, 'mapper', 'webhooks', 'fails.json');
|
|
40
|
+
let webSuccessPath = path.resolve(this.config.data, 'mapper', 'webhooks', 'success.json');
|
|
41
|
+
let webUidMapperPath = path.resolve(this.config.data, 'mapper', 'webhooks', 'uid-mapping.json');
|
|
42
|
+
|
|
43
|
+
let webhooksFolderPath = path.resolve(this.config.data, this.webhooksConfig.dirName);
|
|
44
|
+
this.webhooks = helper.readFileSync(path.resolve(webhooksFolderPath, this.webhooksConfig.fileName));
|
|
33
45
|
|
|
34
|
-
importWebhooks.prototype = {
|
|
35
|
-
start: function (credentialConfig) {
|
|
36
|
-
let self = this;
|
|
37
|
-
config = credentialConfig;
|
|
38
|
-
addlogs(config, chalk.white('Migrating webhooks'), 'success');
|
|
39
|
-
client = stack.Client(config);
|
|
40
|
-
webhooksFolderPath = path.resolve(config.data, webhooksConfig.dirName);
|
|
41
|
-
webMapperPath = path.resolve(config.data, 'mapper', 'webhooks');
|
|
42
|
-
webUidMapperPath = path.resolve(config.data, 'mapper', 'webhooks', 'uid-mapping.json');
|
|
43
|
-
webSuccessPath = path.resolve(config.data, 'mapper', 'webhooks', 'success.json');
|
|
44
|
-
webFailsPath = path.resolve(config.data, 'mapper', 'webhooks', 'fails.json');
|
|
45
|
-
self.webhooks = helper.readFile(path.resolve(webhooksFolderPath, webhooksConfig.fileName));
|
|
46
46
|
if (fs.existsSync(webUidMapperPath)) {
|
|
47
|
-
self.webUidMapper = helper.
|
|
47
|
+
self.webUidMapper = helper.readFileSync(webUidMapperPath);
|
|
48
48
|
self.webUidMapper = self.webUidMapper || {};
|
|
49
49
|
}
|
|
50
|
+
|
|
50
51
|
mkdirp.sync(webMapperPath);
|
|
51
52
|
|
|
52
53
|
return new Promise(function (resolve, reject) {
|
|
53
54
|
if (self.webhooks == undefined || isEmpty(self.webhooks)) {
|
|
54
|
-
addlogs(config, chalk.white('No Webhooks Found'), 'success');
|
|
55
|
+
addlogs(self.config, chalk.white('No Webhooks Found'), 'success');
|
|
55
56
|
return resolve({ empty: true });
|
|
56
57
|
}
|
|
58
|
+
|
|
57
59
|
let webUids = Object.keys(self.webhooks);
|
|
58
60
|
return Promise.map(
|
|
59
61
|
webUids,
|
|
60
62
|
function (webUid) {
|
|
61
63
|
let web = self.webhooks[webUid];
|
|
62
|
-
if (config.importWebhookStatus !== 'current' || config.importWebhookStatus === 'disable') {
|
|
64
|
+
if (self.config.importWebhookStatus !== 'current' || self.config.importWebhookStatus === 'disable') {
|
|
63
65
|
web.disabled = true;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
if (!self.webUidMapper.hasOwnProperty(webUid)) {
|
|
67
|
-
let requestOption = {
|
|
68
|
-
json: {
|
|
69
|
-
webhook: web,
|
|
70
|
-
},
|
|
71
|
-
};
|
|
69
|
+
let requestOption = { json: { webhook: web } };
|
|
72
70
|
|
|
73
71
|
return client
|
|
74
|
-
.stack({ api_key: config.target_stack, management_token: config.management_token })
|
|
72
|
+
.stack({ api_key: self.config.target_stack, management_token: self.config.management_token })
|
|
75
73
|
.webhook()
|
|
76
74
|
.create(requestOption.json)
|
|
77
75
|
.then(function (response) {
|
|
78
76
|
self.success.push(response);
|
|
79
77
|
self.webUidMapper[webUid] = response.uid;
|
|
80
|
-
helper.
|
|
78
|
+
helper.writeFileSync(webUidMapperPath, self.webUidMapper);
|
|
81
79
|
})
|
|
82
80
|
.catch(function (err) {
|
|
83
81
|
let error = JSON.parse(err.message);
|
|
84
82
|
self.fails.push(web);
|
|
85
83
|
addlogs(
|
|
86
|
-
config,
|
|
87
|
-
chalk.red("Webhooks: '" + web.name + "' failed to be import\n" +
|
|
84
|
+
self.config,
|
|
85
|
+
chalk.red("Webhooks: '" + web.name + "' failed to be import\n" + formatError(error)),
|
|
88
86
|
'error',
|
|
89
87
|
);
|
|
90
88
|
});
|
|
91
89
|
} else {
|
|
92
90
|
// the webhooks has already been created
|
|
93
91
|
addlogs(
|
|
94
|
-
config,
|
|
92
|
+
self.config,
|
|
95
93
|
chalk.white("The Webhooks: '" + web.name + "' already exists. Skipping it to avoid duplicates!"),
|
|
96
94
|
'success',
|
|
97
95
|
);
|
|
98
96
|
}
|
|
99
97
|
// import 2 webhooks at a time
|
|
100
98
|
},
|
|
101
|
-
{
|
|
102
|
-
concurrency: reqConcurrency,
|
|
103
|
-
},
|
|
99
|
+
{ concurrency: self.reqConcurrency },
|
|
104
100
|
)
|
|
105
101
|
.then(function () {
|
|
106
102
|
// webhooks have imported successfully
|
|
107
|
-
helper.
|
|
108
|
-
addlogs(config, chalk.green('Webhooks have been imported successfully!'), 'success');
|
|
103
|
+
helper.writeFileSync(webSuccessPath, self.success);
|
|
104
|
+
addlogs(self.config, chalk.green('Webhooks have been imported successfully!'), 'success');
|
|
109
105
|
return resolve();
|
|
110
106
|
})
|
|
111
107
|
.catch(function (error) {
|
|
112
108
|
// error while importing environments
|
|
113
|
-
helper.
|
|
114
|
-
addlogs(config, chalk.red('Webhooks import failed'), 'error');
|
|
109
|
+
helper.writeFileSync(webFailsPath, self.fails);
|
|
110
|
+
addlogs(self.config, chalk.red('Webhooks import failed'), 'error');
|
|
115
111
|
return reject(error);
|
|
116
112
|
});
|
|
117
113
|
});
|
|
118
|
-
}
|
|
114
|
+
}
|
|
119
115
|
};
|
|
120
|
-
|
|
121
|
-
module.exports = new importWebhooks();
|