@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,1260 @@
1
+ /*!
2
+ * Contentstack Import
3
+ * Copyright (c) 2019 Contentstack LLC
4
+ * MIT Licensed
5
+ */
6
+ const Promise = require('bluebird');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const _ = require('lodash');
10
+ const mkdirp = require('mkdirp');
11
+ const chalk = require('chalk');
12
+ const { fileHelper, log, formatError, lookupExtension, suppressSchemaReference, lookupAssets, lookupEntries, } = require('../../utils');
13
+ const { default: config } = require('../../config');
14
+ const addlogs = log;
15
+ module.exports = class ImportEntries {
16
+ constructor(importConfig, stackAPIClient) {
17
+ this.skipFiles = ['__master.json', '__priority.json', 'schema.json'];
18
+ this.config = _.merge(config, importConfig);
19
+ this.stackAPIClient = stackAPIClient;
20
+ this.mappedAssetUidPath = path.resolve(this.config.data, 'mapper', 'assets', 'uid-mapping.json');
21
+ this.mappedAssetUrlPath = path.resolve(this.config.data, 'mapper', 'assets', 'url-mapping.json');
22
+ this.entryMapperPath = path.resolve(this.config.data, 'mapper', 'entries');
23
+ this.environmentPath = path.resolve(this.config.data, 'environments', 'environments.json');
24
+ mkdirp.sync(this.entryMapperPath);
25
+ this.entryUidMapperPath = path.join(this.entryMapperPath, 'uid-mapping.json');
26
+ this.uniqueUidMapperPath = path.join(this.entryMapperPath, 'unique-mapping.json');
27
+ this.modifiedSchemaPath = path.join(this.entryMapperPath, 'modified-schemas.json');
28
+ this.createdEntriesWOUidPath = path.join(this.entryMapperPath, 'created-entries-wo-uid.json');
29
+ this.failedWOPath = path.join(this.entryMapperPath, 'failedWO.json');
30
+ this.reqConcurrency = this.config.concurrency;
31
+ this.eConfig = this.config.modules.entries;
32
+ this.ePath = path.resolve(this.config.data, this.eConfig.dirName);
33
+ this.ctPath = path.resolve(this.config.data, this.config.modules.content_types.dirName);
34
+ this.lPath = path.resolve(this.config.data, this.config.modules.locales.dirName, this.config.modules.locales.fileName);
35
+ this.importConcurrency = this.eConfig.importConcurrency || this.config.importConcurrency;
36
+ // Object of Schemas, referred to by their content type uid
37
+ this.ctSchemas = {};
38
+ // Array of content type uids, that have reference fields
39
+ this.refSchemas = [];
40
+ // map of content types uids and their json-rte fields
41
+ this.ctJsonRte = [];
42
+ // map of content types uids and their json-rte fields
43
+ this.ctJsonRteWithEntryRefs = [];
44
+ // Entry refs that are held back to resolve after all entries have been created
45
+ this.jsonRteEntryRefs = {};
46
+ // Collection of entries, that were not created, as they already exist on Stack
47
+ this.createdEntriesWOUid = [];
48
+ // Collection of entry uids, mapped to the language they exist in
49
+ this.uniqueUids = {};
50
+ // Map of old entry uid to new
51
+ this.mappedUids = {};
52
+ // Entries that were created successfully
53
+ this.success = [];
54
+ // Entries that failed to get created OR updated
55
+ this.fails = [];
56
+ // List of installed extensions to replace uid
57
+ this.installedExtensions = [];
58
+ let files = fs.readdirSync(this.ctPath);
59
+ this.environment = fileHelper.readFileSync(this.environmentPath);
60
+ for (let index in files) {
61
+ if (index) {
62
+ try {
63
+ if (this.skipFiles.indexOf(files[index]) === -1) {
64
+ if (files[index] != 'field_rules_uid.json') {
65
+ let schema = require(path.resolve(path.join(this.ctPath, files[index])));
66
+ this.ctSchemas[schema.uid] = schema;
67
+ }
68
+ }
69
+ }
70
+ catch (error) {
71
+ addlogs(this.config, `Failed to read the content types to import entries ${formatError(error)}`, 'error');
72
+ process.exit(0);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ async start() {
78
+ let self = this;
79
+ this.masterLanguage = this.config.master_locale;
80
+ log(this.config, 'Migrating entries', 'success');
81
+ let languages = fileHelper.readFileSync(this.lPath);
82
+ const appMapperPath = path.join(this.config.data, 'mapper', 'marketplace_apps', 'uid-mapping.json');
83
+ this.installedExtensions = ((await fileHelper.readFileSync(appMapperPath)) || { extension_uid: {} }).extension_uid;
84
+ return new Promise((resolve, reject) => {
85
+ let langs = [self.masterLanguage.code];
86
+ for (let i in languages) {
87
+ if (i) {
88
+ langs.push(languages[i].code);
89
+ }
90
+ }
91
+ // Step 1: Removes field rules from content type
92
+ // This allows to handle cases like self references and circular reference
93
+ // if mandatory reference fields are not filed in entries then avoids the error
94
+ // Also remove field visibility rules
95
+ return self
96
+ .supressFields()
97
+ .then(async () => {
98
+ log(this.config, 'Completed suppressing content type reference fields', 'success');
99
+ let mappedAssetUids = fileHelper.readFileSync(this.mappedAssetUidPath) || {};
100
+ let mappedAssetUrls = fileHelper.readFileSync(this.mappedAssetUrlPath) || {};
101
+ // Step 2: Iterate over available languages to create entries in each.
102
+ let counter = 0;
103
+ return Promise.map(langs, async () => {
104
+ let lang = langs[counter];
105
+ if ((self.config.hasOwnProperty('onlylocales') && self.config.onlylocales.indexOf(lang) !== -1) ||
106
+ !self.config.hasOwnProperty('onlylocales')) {
107
+ addlogs(self.config, `Starting to create entries ${lang} locale`, 'info');
108
+ await self.createEntries(lang, mappedAssetUids, mappedAssetUrls);
109
+ log(this.config, 'Entries created successfully', 'info');
110
+ try {
111
+ await self.getCreatedEntriesWOUid();
112
+ }
113
+ catch (error) {
114
+ addlogs(self.config, `Failed get the existing entries to update the mapper ${formatError(error)}, 'error`);
115
+ }
116
+ log(this.config, 'Starting to update entries with references', 'info');
117
+ await self.repostEntries(lang);
118
+ log(this.config, "Successfully imported '" + lang + "' entries!", 'success');
119
+ counter++;
120
+ }
121
+ else {
122
+ addlogs(self.config, `'${lang}' has not been configured for import, thus skipping it`, 'success');
123
+ counter++;
124
+ }
125
+ }, {
126
+ concurrency: 1,
127
+ }).then(async () => {
128
+ // Step 3: Revert all the changes done in content type in step 1
129
+ log(this.config, 'Restoring content type changes', 'info');
130
+ await self.unSuppressFields();
131
+ log(this.config, 'Removing entries from master language which got created by default', 'info');
132
+ await self.removeBuggedEntries();
133
+ log(this.config, 'Updating the field rules of content type', 'info');
134
+ let ct_field_visibility_uid = fileHelper.readFileSync(path.join(this.ctPath + '/field_rules_uid.json'));
135
+ let ct_files = fs.readdirSync(this.ctPath);
136
+ if (ct_field_visibility_uid && ct_field_visibility_uid != 'undefined') {
137
+ for (const element of ct_field_visibility_uid) {
138
+ if (ct_files.indexOf(element + '.json') > -1) {
139
+ let schema = require(path.resolve(this.ctPath, element));
140
+ try {
141
+ await self.field_rules_update(schema);
142
+ }
143
+ catch (error) {
144
+ addlogs(self.config, `Failed to update the field rules for content type '${schema.uid}' ${formatError(error)}`, 'error');
145
+ }
146
+ }
147
+ }
148
+ }
149
+ log(this.config, chalk.green('Entries have been imported successfully!'), 'success');
150
+ if (this.config.entriesPublish) {
151
+ log(this.config, chalk.green('Publishing entries'), 'success');
152
+ return self
153
+ .publish(langs)
154
+ .then(() => {
155
+ log(this.config, chalk.green('All the entries have been published successfully'), 'success');
156
+ return resolve();
157
+ })
158
+ .catch((error) => {
159
+ log(this.config, `Error in publishing entries ${formatError(error)}`, 'error');
160
+ });
161
+ }
162
+ return resolve();
163
+ });
164
+ })
165
+ .catch((error) => {
166
+ log(self.config, formatError(error), 'error');
167
+ reject('Failed import entries');
168
+ });
169
+ });
170
+ }
171
+ createEntries(lang, mappedAssetUids, mappedAssetUrls) {
172
+ let self = this;
173
+ return new Promise(async (resolve, reject) => {
174
+ let contentTypeUids = Object.keys(self.ctSchemas);
175
+ if (fs.existsSync(this.entryUidMapperPath)) {
176
+ self.mappedUids = await fileHelper.readLargeFile(this.entryUidMapperPath);
177
+ }
178
+ self.mappedUids = self.mappedUids || {};
179
+ return Promise.map(contentTypeUids, async (ctUid) => {
180
+ let eLangFolderPath = path.join(this.entryMapperPath, lang);
181
+ let eLogFolderPath = path.join(this.entryMapperPath, lang, ctUid);
182
+ mkdirp.sync(eLogFolderPath);
183
+ // entry file path
184
+ let eFilePath = path.resolve(this.ePath, ctUid, lang + '.json');
185
+ // log created/updated entries
186
+ let successEntryLogPath = path.join(eLogFolderPath, 'success.json');
187
+ let failedEntryLogPath = path.join(eLogFolderPath, 'fails.json');
188
+ let createdEntriesPath = path.join(eLogFolderPath, 'created-entries.json');
189
+ let createdEntries = {};
190
+ if (fs.existsSync(createdEntriesPath)) {
191
+ createdEntries = await fileHelper.readLargeFile(createdEntriesPath);
192
+ createdEntries = createdEntries || {};
193
+ }
194
+ if (fs.existsSync(eFilePath)) {
195
+ let entries = await fileHelper.readLargeFile(eFilePath);
196
+ if (!_.isPlainObject(entries) || _.isEmpty(entries)) {
197
+ log(this.config, chalk.white("No entries were found for Content type:'" + ctUid + "' in '" + lang + "' language!"), 'success');
198
+ }
199
+ else {
200
+ addlogs(this.config, `Creating entries for content type ${ctUid} in language ${lang} ...`, 'success');
201
+ for (let eUid in entries) {
202
+ if (eUid) {
203
+ try {
204
+ // check ctUid in self.ctJsonRte array, if ct exists there... only then remove entry references for json rte
205
+ // also with json rte, api creates the json-rte field with the same uid as passed in the payload.
206
+ if (self.ctJsonRte.indexOf(ctUid) > -1) {
207
+ entries[eUid] = self.removeUidsFromJsonRteFields(entries[eUid], self.ctSchemas[ctUid].schema);
208
+ }
209
+ // remove entry references from json-rte fields
210
+ if (self.ctJsonRteWithEntryRefs.indexOf(ctUid) > -1) {
211
+ entries[eUid] = self.removeEntryRefsFromJSONRTE(entries[eUid], self.ctSchemas[ctUid].schema);
212
+ }
213
+ // will replace all old asset uid/urls with new ones
214
+ entries[eUid] = lookupAssets({
215
+ content_type: self.ctSchemas[ctUid],
216
+ entry: entries[eUid],
217
+ }, mappedAssetUids, mappedAssetUrls, eLangFolderPath, self.installedExtensions);
218
+ }
219
+ catch (error) {
220
+ addlogs(this.config, 'Failed to update entry while creating entry id ' + eUid);
221
+ addlogs(this.config, formatError(error), 'error');
222
+ }
223
+ }
224
+ }
225
+ let eUids = Object.keys(entries);
226
+ let batches = [];
227
+ let entryBatchLimit = this.eConfig.batchLimit || 10;
228
+ let batchSize = Math.round(entryBatchLimit / 3);
229
+ // Run entry creation in batches of ~16~ entries
230
+ for (let i = 0; i < eUids.length; i += batchSize) {
231
+ batches.push(eUids.slice(i, i + batchSize));
232
+ }
233
+ return Promise.map(batches, async (batch) => {
234
+ return Promise.map(batch, async (eUid, batchIndex) => {
235
+ // if entry is already created
236
+ if (createdEntries.hasOwnProperty(eUid)) {
237
+ log(this.config, 'Skipping ' +
238
+ JSON.stringify({
239
+ content_type: ctUid,
240
+ locale: lang,
241
+ oldEntryUid: eUid,
242
+ newEntryUid: createdEntries[eUid],
243
+ }) +
244
+ ' as it is already created', 'success');
245
+ self.success[ctUid] = createdEntries[eUid];
246
+ // if its a non-master language, i.e. the entry isn't present in the master language
247
+ if (lang !== this.masterLanguage.code) {
248
+ self.uniqueUids[eUid] = self.uniqueUids[eUid] || {};
249
+ if (self.uniqueUids[eUid].locales) {
250
+ self.uniqueUids[eUid].locales.push(lang);
251
+ }
252
+ else {
253
+ self.uniqueUids[eUid].locales = [lang];
254
+ }
255
+ self.uniqueUids[eUid].content_type = ctUid;
256
+ }
257
+ return;
258
+ }
259
+ let requestObject = {
260
+ qs: {
261
+ locale: lang,
262
+ },
263
+ json: {
264
+ entry: entries[eUid],
265
+ },
266
+ };
267
+ if (self.mappedUids.hasOwnProperty(eUid)) {
268
+ let entryToUpdate = self.stackAPIClient.contentType(ctUid).entry(self.mappedUids[eUid]);
269
+ Object.assign(entryToUpdate, _.cloneDeep(entries[eUid]));
270
+ return entryToUpdate
271
+ .update({ locale: entryToUpdate.locale })
272
+ .then(async (entryResponse) => {
273
+ self.success[ctUid] = self.success[ctUid] || [];
274
+ self.success[ctUid].push(entries[eUid]);
275
+ if (!self.mappedUids.hasOwnProperty(eUid)) {
276
+ self.mappedUids[eUid] = entryResponse.uid;
277
+ createdEntries = entryResponse;
278
+ // if its a non-master language, i.e. the entry isn't present in the master language
279
+ if (lang !== this.masterLanguage.code) {
280
+ self.uniqueUids[eUid] = self.uniqueUids[eUid] || {};
281
+ if (self.uniqueUids[eUid].locales) {
282
+ self.uniqueUids[eUid].locales.push(lang);
283
+ }
284
+ else {
285
+ self.uniqueUids[eUid].locales = [lang];
286
+ }
287
+ self.uniqueUids[eUid].content_type = ctUid;
288
+ }
289
+ }
290
+ })
291
+ .catch((error) => {
292
+ log(this.config, `Failed to update an entry ${eUid} ${formatError(error)}`, 'error');
293
+ self.fails.push({
294
+ content_type: ctUid,
295
+ locale: lang,
296
+ entry: entries[eUid],
297
+ error: formatError(error),
298
+ });
299
+ return error;
300
+ });
301
+ }
302
+ delete requestObject.json.entry.publish_details;
303
+ return self.stackAPIClient
304
+ .contentType(ctUid)
305
+ .entry()
306
+ .create(requestObject.json, { locale: lang })
307
+ .then(async (entryResponse) => {
308
+ self.success[ctUid] = self.success[ctUid] || [];
309
+ self.success[ctUid].push(entries[eUid]);
310
+ if (!self.mappedUids.hasOwnProperty(eUid)) {
311
+ self.mappedUids[eUid] = entryResponse.uid;
312
+ createdEntries = entryResponse;
313
+ // if its a non-master language, i.e. the entry isn't present in the master language
314
+ if (lang !== this.masterLanguage.code) {
315
+ self.uniqueUids[eUid] = self.uniqueUids[eUid] || {};
316
+ if (self.uniqueUids[eUid].locales) {
317
+ self.uniqueUids[eUid].locales.push(lang);
318
+ }
319
+ else {
320
+ self.uniqueUids[eUid].locales = [lang];
321
+ }
322
+ self.uniqueUids[eUid].content_type = ctUid;
323
+ }
324
+ }
325
+ })
326
+ .catch((error) => {
327
+ if (error.hasOwnProperty('error_code') && error.error_code === 119) {
328
+ if (error.errors.title) {
329
+ log(this.config, 'Entry ' + eUid + ' already exist, skip to avoid creating a duplicate entry', 'error');
330
+ }
331
+ else {
332
+ log(this.config, `Failed to create an entry ${eUid} ${formatError(error)}`, 'error');
333
+ }
334
+ self.createdEntriesWOUid.push({
335
+ content_type: ctUid,
336
+ locale: lang,
337
+ entry: entries[eUid],
338
+ error: error,
339
+ });
340
+ fileHelper.writeFileSync(this.createdEntriesWOUidPath, self.createdEntriesWOUid);
341
+ return;
342
+ }
343
+ // TODO: if status code: 422, check the reason
344
+ // 429 for rate limit
345
+ log(this.config, `Failed to create an entry ${eUid} ${formatError(error)}`, 'error');
346
+ self.fails.push({
347
+ content_type: ctUid,
348
+ locale: lang,
349
+ entry: entries[eUid],
350
+ error: error,
351
+ });
352
+ });
353
+ // create/update 5 entries at a time
354
+ }, {
355
+ concurrency: this.importConcurrency,
356
+ }).then(() => {
357
+ fileHelper.writeFileSync(successEntryLogPath, self.success[ctUid]);
358
+ fileHelper.writeFileSync(failedEntryLogPath, self.fails[ctUid]);
359
+ fileHelper.writeFileSync(this.entryUidMapperPath, self.mappedUids);
360
+ fileHelper.writeFileSync(this.uniqueUidMapperPath, self.uniqueUids);
361
+ fileHelper.writeFileSync(createdEntriesPath, createdEntries);
362
+ });
363
+ // process one batch at a time
364
+ }, {
365
+ concurrency: 1,
366
+ }).then(() => {
367
+ if (self.success && self.success[ctUid] && self.success[ctUid].length > 0)
368
+ log(this.config, self.success[ctUid].length +
369
+ ' entries created successfully in ' +
370
+ ctUid +
371
+ ' content type in ' +
372
+ lang +
373
+ ' locale!', 'success');
374
+ if (self.fails && self.fails[ctUid] && self.fails[ctUid].length > 0)
375
+ log(this.config, self.fails[ctUid].length +
376
+ ' entries failed to create in ' +
377
+ ctUid +
378
+ ' content type in ' +
379
+ lang +
380
+ ' locale!', 'error');
381
+ self.success[ctUid] = [];
382
+ self.fails[ctUid] = [];
383
+ });
384
+ }
385
+ }
386
+ else {
387
+ log(this.config, `Unable to find entry file path for '${ctUid}' content type!\nThe file '${eFilePath}' does not exist!`, 'error');
388
+ }
389
+ }, {
390
+ concurrency: 1,
391
+ })
392
+ .then(() => {
393
+ log(this.config, chalk.green("Entries created successfully in '" + lang + "' language"), 'success');
394
+ return resolve();
395
+ })
396
+ .catch((error) => {
397
+ addlogs(this.config, chalk.red("Failed to create entries in '" + lang + "' language"), 'error');
398
+ return reject(error);
399
+ });
400
+ });
401
+ }
402
+ getCreatedEntriesWOUid() {
403
+ let self = this;
404
+ return new Promise((resolve) => {
405
+ self.createdEntriesWOUid = fileHelper.readFileSync(this.createdEntriesWOUidPath);
406
+ self.failedWO = [];
407
+ if (_.isArray(self.createdEntriesWOUid) && self.createdEntriesWOUid.length > 0) {
408
+ return Promise.map(self.createdEntriesWOUid, (entry) => {
409
+ return self.fetchEntry(entry);
410
+ }, {
411
+ concurrency: this.importConcurrency,
412
+ }).then(() => {
413
+ fileHelper.writeFileSync(this.failedWOPath, self.failedWO);
414
+ log(this.config, 'Mapped entries without mapped uid successfully!', 'success');
415
+ return resolve();
416
+ });
417
+ }
418
+ log(this.config, 'No entries without mapped uid found!', 'success');
419
+ return resolve();
420
+ });
421
+ }
422
+ repostEntries(lang) {
423
+ let self = this;
424
+ return new Promise(async (resolve, reject) => {
425
+ let _mapped_ = await fileHelper.readLargeFile(path.join(this.entryMapperPath, 'uid-mapping.json'));
426
+ if (_.isPlainObject(_mapped_)) {
427
+ self.mappedUids = _.merge(_mapped_, self.mappedUids);
428
+ }
429
+ return Promise.map(self.refSchemas, async (ctUid) => {
430
+ let eFolderPath = path.join(this.entryMapperPath, lang, ctUid);
431
+ let eSuccessFilePath = path.join(eFolderPath, 'success.json');
432
+ let eFilePath = path.resolve(this.ePath, ctUid, lang + '.json');
433
+ let sourceStackEntries = await fileHelper.readLargeFile(eFilePath);
434
+ if (!fs.existsSync(eSuccessFilePath)) {
435
+ log(this.config, 'Success file was not found at: ' + eSuccessFilePath, 'success');
436
+ return;
437
+ }
438
+ let entries = await fileHelper.readLargeFile(eSuccessFilePath, { type: 'array' }); // TBD LARGE
439
+ entries = entries || [];
440
+ if (entries.length === 0) {
441
+ log(this.config, "No entries were created to be updated in '" + lang + "' language!", 'success');
442
+ return;
443
+ }
444
+ // Keep track of entries that have their references updated
445
+ let refsUpdatedUids = fileHelper.readFileSync(path.join(eFolderPath, 'refsUpdatedUids.json'));
446
+ let refsUpdateFailed = fileHelper.readFileSync(path.join(eFolderPath, 'refsUpdateFailed.json'));
447
+ let schema = self.ctSchemas[ctUid];
448
+ let batches = [];
449
+ refsUpdatedUids = refsUpdatedUids || [];
450
+ refsUpdateFailed = refsUpdateFailed || [];
451
+ // map reference uids @mapper/language/mapped-uids.json
452
+ // map failed reference uids @mapper/language/unmapped-uids.json
453
+ let refUidMapperPath = path.join(this.entryMapperPath, lang);
454
+ addlogs(this.config, 'staring to update the entry for reposting');
455
+ entries = _.map(entries, (entry) => {
456
+ try {
457
+ let uid = entry.uid;
458
+ let updatedEntry;
459
+ // restores json rte entry refs if they exist
460
+ if (self.ctJsonRte.indexOf(ctUid) > -1) {
461
+ // the entries stored in eSuccessFilePath, have the same uids as the entries from source data
462
+ updatedEntry = self.restoreJsonRteEntryRefs(entry, sourceStackEntries[entry.uid], schema.schema);
463
+ }
464
+ else {
465
+ updatedEntry = entry;
466
+ }
467
+ let _entry = lookupEntries({
468
+ content_type: schema,
469
+ entry: updatedEntry,
470
+ }, _.clone(self.mappedUids), refUidMapperPath);
471
+ // if there's self references, the uid gets replaced
472
+ _entry.uid = uid;
473
+ return _entry;
474
+ }
475
+ catch (error) {
476
+ addlogs(this.config, `Failed to update the entry ${uid} references while reposting ${formatError(error)}`, 'error');
477
+ }
478
+ });
479
+ log(this.config, 'Starting the reposting process for entries');
480
+ const entryBatchLimit = this.eConfig.batchLimit || 10;
481
+ const batchSize = Math.round(entryBatchLimit / 3);
482
+ // Run entry creation in batches
483
+ for (let i = 0; i < entries.length; i += batchSize) {
484
+ batches.push(entries.slice(i, i + batchSize));
485
+ }
486
+ return Promise.map(batches, async (batch, index) => {
487
+ return Promise.map(batch, async (entry) => {
488
+ entry.uid = self.mappedUids[entry.uid];
489
+ if (refsUpdatedUids.indexOf(entry.uid) !== -1) {
490
+ log(this.config, 'Entry: ' +
491
+ entry.uid +
492
+ ' in Content Type: ' +
493
+ ctUid +
494
+ ' in lang: ' +
495
+ lang +
496
+ ' references fields are already updated.', 'success');
497
+ return;
498
+ }
499
+ let promiseResult = new Promise((resolveUpdatedUids, rejectUpdatedUids) => {
500
+ let entryResponse = self.stackAPIClient.contentType(ctUid).entry(entry.uid);
501
+ Object.assign(entryResponse, entry);
502
+ delete entryResponse.publish_details;
503
+ return entryResponse
504
+ .update({ locale: lang })
505
+ .then((response) => {
506
+ refsUpdatedUids.push(response.uid);
507
+ return resolveUpdatedUids();
508
+ })
509
+ .catch((error) => {
510
+ log(this.config, `Entry Uid '${entry.uid}' of Content Type '${ctUid}' failed to update in locale '${lang}'`, 'error');
511
+ log(this.config, formatError(error), 'error');
512
+ refsUpdateFailed.push({
513
+ content_type: ctUid,
514
+ entry: entry,
515
+ locale: lang,
516
+ error: error,
517
+ });
518
+ return rejectUpdatedUids(error);
519
+ });
520
+ });
521
+ await promiseResult;
522
+ }, {
523
+ concurrency: this.importConcurrency,
524
+ })
525
+ .then(() => {
526
+ // batch completed successfully
527
+ fileHelper.writeFileSync(path.join(eFolderPath, 'success.json'), entries);
528
+ fileHelper.writeFileSync(path.join(eFolderPath, 'refsUpdatedUids.json'), refsUpdatedUids);
529
+ fileHelper.writeFileSync(path.join(eFolderPath, 'refsUpdateFailed.json'), refsUpdateFailed);
530
+ log(this.config, 'Completed re-post entries batch no: ' + (index + 1) + ' successfully!', 'success');
531
+ })
532
+ .catch((error) => {
533
+ // error while executing entry in batch
534
+ addlogs(this.config, `Failed re-post entries at batch no: '${index + 1}`, 'error');
535
+ addlogs(this.config, formatError(error), 'error');
536
+ // throw error;
537
+ });
538
+ }, {
539
+ concurrency: 1,
540
+ })
541
+ .then(() => {
542
+ // finished updating entries with references
543
+ log(this.config, "Imported entries of Content Type: '" + ctUid + "' in language: '" + lang + "' successfully!", 'success');
544
+ })
545
+ .catch((error) => {
546
+ // error while updating entries with references
547
+ addlogs(this.config, `Failed re-post entries of content type ${ctUid} locale ${lang}`, 'error');
548
+ addlogs(this.config, formatError(error), 'error');
549
+ // throw error;
550
+ });
551
+ }, {
552
+ concurrency: 1,
553
+ })
554
+ .then(() => {
555
+ // completed updating entry references
556
+ log(this.config, chalk.green("Imported entries in '" + lang + "' language successfully!"), 'success');
557
+ return resolve();
558
+ })
559
+ .catch((error) => {
560
+ // error while updating entry references
561
+ addlogs(this.config, chalk.red('Failed to re post entries in ' + lang + ' language'), 'error');
562
+ return reject(error);
563
+ });
564
+ });
565
+ }
566
+ supressFields() {
567
+ // it should be spelled as suppressFields
568
+ log(this.config, 'Suppressing content type reference fields', 'success');
569
+ let self = this;
570
+ return new Promise(async (resolve, reject) => {
571
+ let modifiedSchemas = [];
572
+ let suppressedSchemas = [];
573
+ for (let uid in self.ctSchemas) {
574
+ if (uid) {
575
+ let contentTypeSchema = _.cloneDeep(self.ctSchemas[uid]);
576
+ let flag = {
577
+ suppressed: false,
578
+ references: false,
579
+ jsonRte: false,
580
+ jsonRteEmbeddedEntries: false,
581
+ };
582
+ if (contentTypeSchema.field_rules) {
583
+ delete contentTypeSchema.field_rules;
584
+ }
585
+ // Set mandatory or unique flag to false
586
+ suppressSchemaReference(contentTypeSchema.schema, flag);
587
+ // Check if suppress modified flag
588
+ if (flag.suppressed) {
589
+ suppressedSchemas.push(contentTypeSchema);
590
+ modifiedSchemas.push(self.ctSchemas[uid]);
591
+ }
592
+ if (flag.references) {
593
+ self.refSchemas.push(uid);
594
+ }
595
+ if (flag.jsonRte) {
596
+ self.ctJsonRte.push(uid);
597
+ if (flag.jsonRteEmbeddedEntries) {
598
+ self.ctJsonRteWithEntryRefs.push(uid);
599
+ // pushing ct uid to refSchemas, because
600
+ // repostEntries uses refSchemas content types for
601
+ // reposting entries
602
+ if (self.refSchemas.indexOf(uid) === -1) {
603
+ self.refSchemas.push(uid);
604
+ }
605
+ }
606
+ }
607
+ // Replace extensions with new UID
608
+ lookupExtension(this.config, contentTypeSchema.schema, this.config.preserveStackVersion, self.installedExtensions);
609
+ }
610
+ }
611
+ // write modified schema in backup file
612
+ fileHelper.writeFileSync(this.modifiedSchemaPath, modifiedSchemas);
613
+ return Promise.map(suppressedSchemas, async (schema) => {
614
+ let contentTypeResponse = self.stackAPIClient.contentType(schema.uid);
615
+ Object.assign(contentTypeResponse, _.cloneDeep(schema));
616
+ return contentTypeResponse
617
+ .update()
618
+ .then((_updatedcontentType) => {
619
+ // empty function
620
+ })
621
+ .catch((_error) => {
622
+ addlogs(this.config, formatError(error), 'error');
623
+ reject(`Failed suppress content type ${schema.uid} reference fields`);
624
+ });
625
+ // update 5 content types at a time
626
+ }, {
627
+ // update reqConcurrency content types at a time
628
+ concurrency: this.importConcurrency,
629
+ })
630
+ .then(() => {
631
+ return resolve();
632
+ })
633
+ .catch((error) => {
634
+ log(this.config, formatError(error), 'error');
635
+ return reject('Failed to suppress reference fields in content type');
636
+ });
637
+ });
638
+ }
639
+ fetchEntry(query) {
640
+ let self = this;
641
+ return new Promise((resolve, _reject) => {
642
+ let requestObject = {
643
+ qs: {
644
+ query: {
645
+ title: query.entry.title,
646
+ },
647
+ locale: query.locale,
648
+ },
649
+ };
650
+ return self.stackAPIClient
651
+ .contentType(query.content_type)
652
+ .entry()
653
+ .query(requestObject.qs)
654
+ .find()
655
+ .then((response) => {
656
+ if (response.body.entries.length <= 0) {
657
+ log(this.config, 'Unable to map entry WO uid: ' + query.entry.uid, 'error');
658
+ self.failedWO.push(query);
659
+ return resolve();
660
+ }
661
+ self.mappedUids[query.entry.uid] = response.body.entries[0].uid;
662
+ let _ePath = path.join(this.entryMapperPath, query.locale, query.content_type, 'success.json');
663
+ let entries = fileHelper.readFileSync(_ePath);
664
+ entries.push(query.entry);
665
+ fileHelper.writeFileSync(_ePath, entries);
666
+ log(this.config, 'Completed mapping entry wo uid: ' + query.entry.uid + ': ' + response.body.entries[0].uid, 'clientsuccess');
667
+ return resolve();
668
+ })
669
+ .catch((_error) => {
670
+ return resolve();
671
+ });
672
+ });
673
+ }
674
+ unSuppressFields() {
675
+ let self = this;
676
+ return new Promise(async (resolve, reject) => {
677
+ let modifiedSchemas = fileHelper.readFileSync(this.modifiedSchemaPath);
678
+ let modifiedSchemasUids = [];
679
+ let updatedExtensionUidsSchemas = [];
680
+ for (let uid in modifiedSchemas) {
681
+ if (uid) {
682
+ let _contentTypeSchema = _.cloneDeep(modifiedSchemas[uid]);
683
+ if (_contentTypeSchema.field_rules) {
684
+ delete _contentTypeSchema.field_rules;
685
+ }
686
+ lookupExtension(this.config, _contentTypeSchema.schema, this.config.preserveStackVersion, self.installedExtensions);
687
+ updatedExtensionUidsSchemas.push(_contentTypeSchema);
688
+ }
689
+ }
690
+ return Promise.map(updatedExtensionUidsSchemas, async (schema) => {
691
+ let promise = new Promise((resolveContentType, rejectContentType) => {
692
+ self.stackAPIClient
693
+ .contentType(schema.uid)
694
+ .fetch()
695
+ .then((contentTypeResponse) => {
696
+ contentTypeResponse.schema = schema.schema;
697
+ contentTypeResponse
698
+ .update()
699
+ .then((_updatedcontentType) => {
700
+ modifiedSchemasUids.push(schema.uid);
701
+ log(this.config, chalk.white("Content type: '" + schema.uid + "' has been restored to its previous glory!"));
702
+ return resolveContentType();
703
+ })
704
+ .catch((error) => {
705
+ addlogs(this.config, chalk.red('Failed to re-update ' + schema.uid), 'error');
706
+ addlogs(this.config, error, 'error');
707
+ return rejectContentType(error);
708
+ });
709
+ })
710
+ .catch((error) => {
711
+ log(this.config, error, 'error');
712
+ return rejectContentType(error);
713
+ });
714
+ });
715
+ await promise;
716
+ }, {
717
+ concurrency: this.reqConcurrency,
718
+ })
719
+ .then(() => {
720
+ for (let i = 0; i < modifiedSchemas.length; i++) {
721
+ if (modifiedSchemasUids.indexOf(modifiedSchemas[i].uid) !== -1) {
722
+ modifiedSchemas.splice(i, 1);
723
+ i--;
724
+ }
725
+ }
726
+ // re-write, in case some schemas failed to update
727
+ fileHelper.writeFileSync(this.modifiedSchemaPath, _.compact(modifiedSchemas));
728
+ log(this.config, 'Re-modified content type schemas to their original form!', 'success');
729
+ return resolve();
730
+ })
731
+ .catch((error) => {
732
+ // failed to update modified schemas back to their original form
733
+ return reject(error);
734
+ });
735
+ });
736
+ }
737
+ removeBuggedEntries() {
738
+ let self = this;
739
+ return new Promise((resolve, reject) => {
740
+ let entries = fileHelper.readFileSync(this.uniqueUidMapperPath);
741
+ let bugged = [];
742
+ let removed = [];
743
+ for (let uid in entries) {
744
+ if (entries[uid].locales.indexOf(this.masterLanguage.code) === -1) {
745
+ bugged.push({
746
+ content_type: entries[uid].content_type,
747
+ uid: uid,
748
+ });
749
+ }
750
+ }
751
+ return Promise.map(bugged, (entry) => {
752
+ return self.stackAPIClient
753
+ .contentType(entry.content_type)
754
+ .entry(self.mappedUids[entry.uid])
755
+ .delete({ locale: this.masterLanguage.code })
756
+ .then(() => {
757
+ removed.push(self.mappedUids[entry.uid]);
758
+ log(this.config, 'Removed bugged entry from master ' + JSON.stringify(entry), 'success');
759
+ })
760
+ .catch((error) => {
761
+ addlogs(this.config, chalk.red('Failed to remove bugged entry from master language'), 'error');
762
+ addlogs(this.config, formatError(error), 'error');
763
+ });
764
+ }, {
765
+ concurrency: this.importConcurrency,
766
+ })
767
+ .then(() => {
768
+ for (let i = 0; i < bugged.length; i++) {
769
+ if (removed.indexOf(bugged[i].uid) !== -1) {
770
+ bugged.splice(i, 1);
771
+ i--;
772
+ }
773
+ }
774
+ fileHelper.writeFileSync(path.join(this.entryMapperPath, 'removed-uids.json'), removed);
775
+ fileHelper.writeFileSync(path.join(this.entryMapperPath, 'pending-uids.json'), bugged);
776
+ log(this.config, chalk.green('The stack has been eradicated from bugged entries!'), 'success');
777
+ return resolve();
778
+ })
779
+ .catch((error) => {
780
+ // error while removing bugged entries from stack
781
+ addlogs(this.config, formatError(error), 'error');
782
+ });
783
+ });
784
+ }
785
+ field_rules_update(schema) {
786
+ return new Promise((resolve, reject) => {
787
+ if (schema.field_rules) {
788
+ let fieldRuleLength = schema.field_rules.length;
789
+ for (let k = 0; k < fieldRuleLength; k++) {
790
+ let fieldRuleConditionLength = schema.field_rules[k].conditions.length;
791
+ for (let i = 0; i < fieldRuleConditionLength; i++) {
792
+ if (schema.field_rules[k].conditions[i].operand_field === 'reference') {
793
+ let fieldRulesValue = schema.field_rules[k].conditions[i].value;
794
+ let fieldRulesArray = fieldRulesValue.split('.');
795
+ let updatedValue = [];
796
+ for (const element of fieldRulesArray) {
797
+ let splitedFieldRulesValue = element;
798
+ let oldUid = fileHelper.readFileSync(path.join(this.entryUidMapperPath));
799
+ if (oldUid.hasOwnProperty(splitedFieldRulesValue)) {
800
+ updatedValue.push(oldUid[splitedFieldRulesValue]);
801
+ }
802
+ else {
803
+ updatedValue.push(element);
804
+ }
805
+ }
806
+ schema.field_rules[k].conditions[i].value = updatedValue.join('.');
807
+ }
808
+ }
809
+ }
810
+ }
811
+ else {
812
+ log(this.config, 'field_rules is not available', 'error');
813
+ }
814
+ this.stackAPIClient
815
+ .contentType(schema.uid)
816
+ .fetch()
817
+ .then((contentTypeResponse) => {
818
+ // Object.assign(ctObj, _.cloneDeep(schema))
819
+ contentTypeResponse.field_rules = schema.field_rules;
820
+ return contentTypeResponse.update();
821
+ })
822
+ .then(() => {
823
+ return resolve();
824
+ })
825
+ .catch((error) => {
826
+ log(this.config, `failed to update the field rules ${formatError(error)}`);
827
+ });
828
+ });
829
+ }
830
+ publish(langs) {
831
+ let self = this;
832
+ let requestObject = {
833
+ entry: {},
834
+ };
835
+ let contentTypeUids = Object.keys(self.ctSchemas);
836
+ let entryMapper = fileHelper.readFileSync(this.entryUidMapperPath);
837
+ return new Promise((resolve, reject) => {
838
+ return Promise.map(langs, (_lang, counter) => {
839
+ let lang = langs[counter];
840
+ return Promise.map(contentTypeUids, async (ctUid) => {
841
+ let eFilePath = path.resolve(this.ePath, ctUid, lang + '.json');
842
+ let entries = await fileHelper.readLargeFile(eFilePath);
843
+ if (entries === undefined) {
844
+ addlogs(this.config, `No entries were found for Content type: ${ctUid} in language: ${lang}`, 'info');
845
+ }
846
+ else {
847
+ let eUids = Object.keys(entries);
848
+ let batches = [];
849
+ let batchSize;
850
+ if (eUids.length > 0) {
851
+ let entryBatchLimit = this.eConfig.batchLimit || 10;
852
+ batchSize = Math.round(entryBatchLimit / 3);
853
+ // Run entry creation in batches
854
+ for (let i = 0; i < eUids.length; i += batchSize) {
855
+ batches.push(eUids.slice(i, i + batchSize));
856
+ }
857
+ }
858
+ else {
859
+ return;
860
+ }
861
+ return Promise.map(batches, async (batch, index) => {
862
+ return Promise.map(batch, async (eUid) => {
863
+ let entry = entries[eUid];
864
+ let envId = [];
865
+ let locales = [];
866
+ if (entry.publish_details && entry.publish_details.length > 0) {
867
+ _.forEach(entries[eUid].publish_details, (pubObject) => {
868
+ if (self.environment.hasOwnProperty(pubObject.environment) &&
869
+ _.indexOf(envId, self.environment[pubObject.environment].name) === -1) {
870
+ envId.push(self.environment[pubObject.environment].name);
871
+ }
872
+ if (pubObject.locale) {
873
+ let idx = _.indexOf(locales, pubObject.locale);
874
+ if (idx === -1) {
875
+ locales.push(pubObject.locale);
876
+ }
877
+ }
878
+ });
879
+ let entryUid = entryMapper[eUid];
880
+ if (entryUid) {
881
+ requestObject.entry.environments = envId;
882
+ requestObject.entry.locales = locales;
883
+ return new Promise((resolveEntryPublished, rejectEntryPublished) => {
884
+ self.stackAPIClient
885
+ .contentType(ctUid)
886
+ .entry(entryUid)
887
+ .publish({ publishDetails: requestObject.entry, locale: lang })
888
+ // eslint-disable-next-line max-nested-callbacks
889
+ .then((result) => {
890
+ // addlogs(this.config, 'Entry ' + eUid + ' published successfully in ' + ctUid + ' content type', 'success')
891
+ addlogs(this.config, `Entry '${eUid}' published successfully in '${ctUid}' content type`, 'success');
892
+ return resolveEntryPublished(result);
893
+ // eslint-disable-next-line max-nested-callbacks
894
+ })
895
+ .catch((err) => {
896
+ addlogs(this.config, `failed to publish entry '${eUid}' content type '${ctUid}' ${formatError(err)}`, 'error');
897
+ return resolveEntryPublished('');
898
+ });
899
+ });
900
+ }
901
+ }
902
+ else {
903
+ return {};
904
+ }
905
+ }, {
906
+ concurrency: 1,
907
+ })
908
+ .then(() => {
909
+ // empty function
910
+ })
911
+ .catch((error) => {
912
+ // error while executing entry in batch
913
+ addlogs(this.config, formatError(error), 'error');
914
+ addlogs(this.config, error, 'error');
915
+ });
916
+ }, {
917
+ concurrency: 1,
918
+ })
919
+ .then(() => {
920
+ // addlogs(this.config, 'Entries published successfully in ' + ctUid + ' content type', 'success')
921
+ addlogs(this.config, `Entries published successfully in '${ctUid}' content type`, 'info');
922
+ })
923
+ .catch((error) => {
924
+ console.log(error);
925
+ addlogs(this.config, `failed to publish entry in content type '${ctUid}' ${formatError(error)}`, 'error');
926
+ });
927
+ }
928
+ }, {
929
+ concurrency: 1,
930
+ })
931
+ .then(() => {
932
+ // empty function
933
+ // log('Published entries successfully in ' +);
934
+ })
935
+ .catch((error) => {
936
+ addlogs(this.config, `Failed to publish few entries in ${lang} ${formatError(error)}`, 'error');
937
+ });
938
+ }, {
939
+ concurrency: 1,
940
+ })
941
+ .then(() => {
942
+ return resolve();
943
+ })
944
+ .catch((error) => {
945
+ addlogs(this.config, `Failed to publish entries ${formatError(error)}`, 'error');
946
+ });
947
+ });
948
+ }
949
+ removeEntryRefsFromJSONRTE(entry, ctSchema) {
950
+ for (const element of ctSchema) {
951
+ switch (element.data_type) {
952
+ case 'blocks': {
953
+ if (entry[element.uid]) {
954
+ if (element.multiple) {
955
+ entry[element.uid] = entry[element.uid].map((e) => {
956
+ let key = Object.keys(e).pop();
957
+ let subBlock = element.blocks.filter((block) => block.uid === key).pop();
958
+ e[key] = this.removeEntryRefsFromJSONRTE(e[key], subBlock.schema);
959
+ return e;
960
+ });
961
+ }
962
+ }
963
+ break;
964
+ }
965
+ case 'global_field':
966
+ case 'group': {
967
+ if (entry[element.uid]) {
968
+ if (element.multiple) {
969
+ entry[element.uid] = entry[element.uid].map((e) => {
970
+ e = this.removeEntryRefsFromJSONRTE(e, element.schema);
971
+ return e;
972
+ });
973
+ }
974
+ else {
975
+ entry[element.uid] = this.removeEntryRefsFromJSONRTE(entry[element.uid], element.schema);
976
+ }
977
+ }
978
+ break;
979
+ }
980
+ case 'json': {
981
+ if (entry[element.uid] && element.field_metadata.rich_text_type) {
982
+ if (element.multiple) {
983
+ entry[element.uid] = entry[element.uid].map((jsonRteData) => {
984
+ // repeated code from else block, will abstract later
985
+ let entryReferences = jsonRteData.children.filter((e) => this.doEntryReferencesExist(e));
986
+ if (entryReferences.length > 0) {
987
+ jsonRteData.children = jsonRteData.children.filter((e) => !this.doEntryReferencesExist(e));
988
+ return jsonRteData; // return jsonRteData without entry references
989
+ }
990
+ else {
991
+ return jsonRteData; // return jsonRteData as it is, because there are no entry references
992
+ }
993
+ });
994
+ }
995
+ else {
996
+ let entryReferences = entry[element.uid].children.filter((e) => this.doEntryReferencesExist(e));
997
+ if (entryReferences.length > 0) {
998
+ entry[element.uid].children = entry[element.uid].children.filter((e) => !this.doEntryReferencesExist(e));
999
+ }
1000
+ }
1001
+ }
1002
+ break;
1003
+ }
1004
+ }
1005
+ }
1006
+ return entry;
1007
+ }
1008
+ doEntryReferencesExist(element) {
1009
+ // checks if the children of p element contain any references
1010
+ // only checking one level deep, not recursive
1011
+ if (element.length) {
1012
+ for (const item of element) {
1013
+ if ((item.type === 'p' || item.type === 'a') && item.children && item.children.length > 0) {
1014
+ return this.doEntryReferencesExist(item.children);
1015
+ }
1016
+ else if (this.isEntryRef(item)) {
1017
+ return true;
1018
+ }
1019
+ }
1020
+ }
1021
+ else {
1022
+ if (this.isEntryRef(element)) {
1023
+ return true;
1024
+ }
1025
+ if ((element.type === 'p' || element.type === 'a') && element.children && element.children.length > 0) {
1026
+ return this.doEntryReferencesExist(element.children);
1027
+ }
1028
+ }
1029
+ return false;
1030
+ }
1031
+ restoreJsonRteEntryRefs(entry, sourceStackEntry, ctSchema) {
1032
+ let mappedAssetUids = fileHelper.readFileSync(this.mappedAssetUidPath) || {};
1033
+ let mappedAssetUrls = fileHelper.readFileSync(this.mappedAssetUrlPath) || {};
1034
+ for (const element of ctSchema) {
1035
+ switch (element.data_type) {
1036
+ case 'blocks': {
1037
+ if (entry[element.uid]) {
1038
+ if (element.multiple) {
1039
+ entry[element.uid] = entry[element.uid].map((e, eIndex) => {
1040
+ let key = Object.keys(e).pop();
1041
+ let subBlock = element.blocks.filter((block) => block.uid === key).pop();
1042
+ let sourceStackElement = sourceStackEntry[element.uid][eIndex][key];
1043
+ e[key] = this.restoreJsonRteEntryRefs(e[key], sourceStackElement, subBlock.schema);
1044
+ return e;
1045
+ });
1046
+ }
1047
+ }
1048
+ break;
1049
+ }
1050
+ case 'global_field':
1051
+ case 'group': {
1052
+ if (entry[element.uid]) {
1053
+ if (element.multiple) {
1054
+ entry[element.uid] = entry[element.uid].map((e, eIndex) => {
1055
+ let sourceStackElement = sourceStackEntry[element.uid][eIndex];
1056
+ e = this.restoreJsonRteEntryRefs(e, sourceStackElement, element.schema);
1057
+ return e;
1058
+ });
1059
+ }
1060
+ else {
1061
+ let sourceStackElement = sourceStackEntry[element.uid];
1062
+ entry[element.uid] = this.restoreJsonRteEntryRefs(entry[element.uid], sourceStackElement, element.schema);
1063
+ }
1064
+ }
1065
+ break;
1066
+ }
1067
+ case 'json': {
1068
+ if (entry[element.uid] && element.field_metadata.rich_text_type) {
1069
+ if (element.multiple) {
1070
+ entry[element.uid] = entry[element.uid].map((field, index) => {
1071
+ // i am facing a Maximum call stack exceeded issue,
1072
+ // probably because of this loop operation
1073
+ let entryRefs = sourceStackEntry[element.uid][index].children
1074
+ .map((e, i) => {
1075
+ return { index: i, value: e };
1076
+ })
1077
+ .filter((e) => this.doEntryReferencesExist(e.value))
1078
+ .map((e) => {
1079
+ // commenting the line below resolved the maximum call stack exceeded issue
1080
+ // e.value = this.setDirtyTrue(e.value)
1081
+ this.setDirtyTrue(e.value);
1082
+ return e;
1083
+ })
1084
+ .map((e) => {
1085
+ // commenting the line below resolved the maximum call stack exceeded issue
1086
+ // e.value = this.resolveAssetRefsInEntryRefsForJsonRte(e, mappedAssetUids, mappedAssetUrls)
1087
+ this.resolveAssetRefsInEntryRefsForJsonRte(e.value, mappedAssetUids, mappedAssetUrls);
1088
+ return e;
1089
+ });
1090
+ if (entryRefs.length > 0) {
1091
+ entryRefs.forEach((entryRef) => {
1092
+ field.children.splice(entryRef.index, 0, entryRef.value);
1093
+ });
1094
+ }
1095
+ return field;
1096
+ });
1097
+ }
1098
+ else {
1099
+ let entryRefs = sourceStackEntry[element.uid].children
1100
+ .map((e, index) => {
1101
+ return { index: index, value: e };
1102
+ })
1103
+ .filter((e) => this.doEntryReferencesExist(e.value))
1104
+ .map((e) => {
1105
+ this.setDirtyTrue(e.value);
1106
+ return e;
1107
+ })
1108
+ .map((e) => {
1109
+ this.resolveAssetRefsInEntryRefsForJsonRte(e.value, mappedAssetUids, mappedAssetUrls);
1110
+ return e;
1111
+ });
1112
+ if (entryRefs.length > 0) {
1113
+ entryRefs.forEach((entryRef) => {
1114
+ if (!_.isEmpty(entry[element.uid]) && entry[element.uid].children) {
1115
+ entry[element.uid].children.splice(entryRef.index, 0, entryRef.value);
1116
+ }
1117
+ });
1118
+ }
1119
+ }
1120
+ }
1121
+ break;
1122
+ }
1123
+ }
1124
+ }
1125
+ return entry;
1126
+ }
1127
+ isEntryRef(element) {
1128
+ return element.type === 'reference' && element.attrs.type === 'entry';
1129
+ }
1130
+ removeUidsFromJsonRteFields(entry, ctSchema) {
1131
+ for (const element of ctSchema) {
1132
+ switch (element.data_type) {
1133
+ case 'blocks': {
1134
+ if (entry[element.uid]) {
1135
+ if (element.multiple) {
1136
+ entry[element.uid] = entry[element.uid].map((e) => {
1137
+ let key = Object.keys(e).pop();
1138
+ let subBlock = element.blocks.filter((block) => block.uid === key).pop();
1139
+ e[key] = this.removeUidsFromJsonRteFields(e[key], subBlock.schema);
1140
+ return e;
1141
+ });
1142
+ }
1143
+ }
1144
+ break;
1145
+ }
1146
+ case 'global_field':
1147
+ case 'group': {
1148
+ if (entry[element.uid]) {
1149
+ if (element.multiple) {
1150
+ entry[element.uid] = entry[element.uid].map((e) => {
1151
+ e = this.removeUidsFromJsonRteFields(e, element.schema);
1152
+ return e;
1153
+ });
1154
+ }
1155
+ else {
1156
+ entry[element.uid] = this.removeUidsFromJsonRteFields(entry[element.uid], element.schema);
1157
+ }
1158
+ }
1159
+ break;
1160
+ }
1161
+ case 'json': {
1162
+ if (entry[element.uid] && element.field_metadata.rich_text_type) {
1163
+ if (element.multiple) {
1164
+ entry[element.uid] = entry[element.uid].map((jsonRteData) => {
1165
+ delete jsonRteData.uid; // remove uid
1166
+ if (_.isObject(jsonRteData.attrs)) {
1167
+ jsonRteData.attrs.dirty = true;
1168
+ }
1169
+ if (!_.isEmpty(jsonRteData.children)) {
1170
+ jsonRteData.children = _.map(jsonRteData.children, (child) => this.removeUidsFromChildren(child));
1171
+ }
1172
+ return jsonRteData;
1173
+ });
1174
+ }
1175
+ else {
1176
+ delete entry[element.uid].uid; // remove uid
1177
+ if (entry[element.uid] && _.isObject(entry[element.uid].attrs)) {
1178
+ entry[element.uid].attrs.dirty = true;
1179
+ }
1180
+ if (entry[element.uid] && !_.isEmpty(entry[element.uid].children)) {
1181
+ entry[element.uid].children = _.map(entry[element.uid].children, (child) => this.removeUidsFromChildren(child));
1182
+ }
1183
+ }
1184
+ }
1185
+ break;
1186
+ }
1187
+ }
1188
+ }
1189
+ return entry;
1190
+ }
1191
+ removeUidsFromChildren(children) {
1192
+ if (children.length && children.length > 0) {
1193
+ return children.map((child) => {
1194
+ if (child.type && child.type.length > 0) {
1195
+ delete child.uid; // remove uid
1196
+ if (_.isObject(child.attrs)) {
1197
+ child.attrs.dirty = true;
1198
+ }
1199
+ }
1200
+ if (child.children && child.children.length > 0) {
1201
+ child.children = this.removeUidsFromChildren(child.children);
1202
+ }
1203
+ return child;
1204
+ });
1205
+ }
1206
+ else {
1207
+ if (children.type && children.type.length > 0) {
1208
+ delete children.uid; // remove uid
1209
+ if (_.isObject(children.attrs)) {
1210
+ children.attrs.dirty = true;
1211
+ }
1212
+ }
1213
+ if (children.children && children.children.length > 0) {
1214
+ children.children = this.removeUidsFromChildren(children.children);
1215
+ }
1216
+ return children;
1217
+ }
1218
+ }
1219
+ setDirtyTrue(jsonRteChild) {
1220
+ // also removing uids in this function
1221
+ if (jsonRteChild.type) {
1222
+ if (_.isObject(jsonRteChild.attrs)) {
1223
+ jsonRteChild.attrs['dirty'] = true;
1224
+ }
1225
+ delete jsonRteChild.uid;
1226
+ if (jsonRteChild.children && jsonRteChild.children.length > 0) {
1227
+ jsonRteChild.children = jsonRteChild.children.map((subElement) => this.setDirtyTrue(subElement));
1228
+ }
1229
+ }
1230
+ return jsonRteChild;
1231
+ }
1232
+ resolveAssetRefsInEntryRefsForJsonRte(jsonRteChild, mappedAssetUids, mappedAssetUrls) {
1233
+ if (jsonRteChild.type) {
1234
+ if (jsonRteChild.attrs.type === 'asset') {
1235
+ let assetUrl;
1236
+ if (mappedAssetUids[jsonRteChild.attrs['asset-uid']]) {
1237
+ jsonRteChild.attrs['asset-uid'] = mappedAssetUids[jsonRteChild.attrs['asset-uid']];
1238
+ }
1239
+ if (jsonRteChild.attrs['display-type'] !== 'link') {
1240
+ assetUrl = jsonRteChild.attrs['asset-link'];
1241
+ }
1242
+ else {
1243
+ assetUrl = jsonRteChild.attrs['href'];
1244
+ }
1245
+ if (mappedAssetUrls[assetUrl]) {
1246
+ if (jsonRteChild.attrs['display-type'] !== 'link') {
1247
+ jsonRteChild.attrs['asset-link'] = mappedAssetUrls[assetUrl];
1248
+ }
1249
+ else {
1250
+ jsonRteChild.attrs['href'] = mappedAssetUrls[assetUrl];
1251
+ }
1252
+ }
1253
+ }
1254
+ if (jsonRteChild.children && jsonRteChild.children.length > 0) {
1255
+ jsonRteChild.children = jsonRteChild.children.map((subElement) => this.resolveAssetRefsInEntryRefsForJsonRte(subElement, mappedAssetUids, mappedAssetUrls));
1256
+ }
1257
+ }
1258
+ return jsonRteChild;
1259
+ }
1260
+ };