@contentstack/cli-variants 0.0.1-alpha

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 (124) hide show
  1. package/lib/export/attributes.d.ts +15 -0
  2. package/lib/export/attributes.js +62 -0
  3. package/lib/export/audiences.d.ts +15 -0
  4. package/lib/export/audiences.js +63 -0
  5. package/lib/export/events.d.ts +15 -0
  6. package/lib/export/events.js +63 -0
  7. package/lib/export/experiences.d.ts +9 -0
  8. package/lib/export/experiences.js +99 -0
  9. package/lib/export/index.d.ts +9 -0
  10. package/lib/export/index.js +21 -0
  11. package/lib/export/projects.d.ts +9 -0
  12. package/lib/export/projects.js +73 -0
  13. package/lib/export/variant-entries.d.ts +18 -0
  14. package/lib/export/variant-entries.js +109 -0
  15. package/lib/import/attribute.d.ts +17 -0
  16. package/lib/import/attribute.js +64 -0
  17. package/lib/import/audiences.d.ts +19 -0
  18. package/lib/import/audiences.js +71 -0
  19. package/lib/import/events.d.ts +17 -0
  20. package/lib/import/events.js +62 -0
  21. package/lib/import/experiences.d.ts +46 -0
  22. package/lib/import/experiences.js +214 -0
  23. package/lib/import/index.d.ts +14 -0
  24. package/lib/import/index.js +21 -0
  25. package/lib/import/project.d.ts +13 -0
  26. package/lib/import/project.js +74 -0
  27. package/lib/import/variant-entries.d.ts +98 -0
  28. package/lib/import/variant-entries.js +407 -0
  29. package/lib/index.d.ts +5 -0
  30. package/lib/index.js +21 -0
  31. package/lib/messages/index.d.ts +35 -0
  32. package/lib/messages/index.js +55 -0
  33. package/lib/types/adapter-helper.d.ts +8 -0
  34. package/lib/types/adapter-helper.js +2 -0
  35. package/lib/types/content-types.d.ts +19 -0
  36. package/lib/types/content-types.js +2 -0
  37. package/lib/types/export-config.d.ts +264 -0
  38. package/lib/types/export-config.js +2 -0
  39. package/lib/types/import-config.d.ts +92 -0
  40. package/lib/types/import-config.js +2 -0
  41. package/lib/types/index.d.ts +8 -0
  42. package/lib/types/index.js +24 -0
  43. package/lib/types/personalization-api-adapter.d.ts +152 -0
  44. package/lib/types/personalization-api-adapter.js +2 -0
  45. package/lib/types/utils.d.ts +7 -0
  46. package/lib/types/utils.js +2 -0
  47. package/lib/types/variant-api-adapter.d.ts +49 -0
  48. package/lib/types/variant-api-adapter.js +2 -0
  49. package/lib/types/variant-entry.d.ts +47 -0
  50. package/lib/types/variant-entry.js +2 -0
  51. package/lib/utils/adapter-helper.d.ts +30 -0
  52. package/lib/utils/adapter-helper.js +95 -0
  53. package/lib/utils/attributes-helper.d.ts +7 -0
  54. package/lib/utils/attributes-helper.js +37 -0
  55. package/lib/utils/audiences-helper.d.ts +8 -0
  56. package/lib/utils/audiences-helper.js +49 -0
  57. package/lib/utils/error-helper.d.ts +6 -0
  58. package/lib/utils/error-helper.js +27 -0
  59. package/lib/utils/events-helper.d.ts +8 -0
  60. package/lib/utils/events-helper.js +27 -0
  61. package/lib/utils/helper.d.ts +4 -0
  62. package/lib/utils/helper.js +51 -0
  63. package/lib/utils/index.d.ts +9 -0
  64. package/lib/utils/index.js +25 -0
  65. package/lib/utils/logger.d.ts +3 -0
  66. package/lib/utils/logger.js +175 -0
  67. package/lib/utils/personalization-api-adapter.d.ts +73 -0
  68. package/lib/utils/personalization-api-adapter.js +184 -0
  69. package/lib/utils/variant-api-adapter.d.ts +79 -0
  70. package/lib/utils/variant-api-adapter.js +263 -0
  71. package/package.json +38 -0
  72. package/src/export/attributes.ts +55 -0
  73. package/src/export/audiences.ts +57 -0
  74. package/src/export/events.ts +57 -0
  75. package/src/export/experiences.ts +80 -0
  76. package/src/export/index.ts +11 -0
  77. package/src/export/projects.ts +45 -0
  78. package/src/export/variant-entries.ts +88 -0
  79. package/src/import/attribute.ts +60 -0
  80. package/src/import/audiences.ts +69 -0
  81. package/src/import/events.ts +58 -0
  82. package/src/import/experiences.ts +224 -0
  83. package/src/import/index.ts +16 -0
  84. package/src/import/project.ts +71 -0
  85. package/src/import/variant-entries.ts +483 -0
  86. package/src/index.ts +5 -0
  87. package/src/messages/index.ts +63 -0
  88. package/src/types/adapter-helper.ts +10 -0
  89. package/src/types/content-types.ts +41 -0
  90. package/src/types/export-config.ts +292 -0
  91. package/src/types/import-config.ts +95 -0
  92. package/src/types/index.ts +8 -0
  93. package/src/types/personalization-api-adapter.ts +197 -0
  94. package/src/types/utils.ts +8 -0
  95. package/src/types/variant-api-adapter.ts +56 -0
  96. package/src/types/variant-entry.ts +61 -0
  97. package/src/utils/adapter-helper.ts +79 -0
  98. package/src/utils/attributes-helper.ts +31 -0
  99. package/src/utils/audiences-helper.ts +50 -0
  100. package/src/utils/error-helper.ts +26 -0
  101. package/src/utils/events-helper.ts +26 -0
  102. package/src/utils/helper.ts +34 -0
  103. package/src/utils/index.ts +9 -0
  104. package/src/utils/logger.ts +160 -0
  105. package/src/utils/personalization-api-adapter.ts +188 -0
  106. package/src/utils/variant-api-adapter.ts +326 -0
  107. package/test/unit/export/variant-entries.test.ts +80 -0
  108. package/test/unit/import/variant-entries.test.ts +200 -0
  109. package/test/unit/mock/contents/content_types/CT-1.json +7 -0
  110. package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-1/9b0da6xd7et72y-6gv7he23.json +12 -0
  111. package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-1/index.json +3 -0
  112. package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-2/9b0da6xd7et72y-6gv7he23.json +12 -0
  113. package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-2/index.json +3 -0
  114. package/test/unit/mock/contents/mapper/assets/uid-mapping.json +6 -0
  115. package/test/unit/mock/contents/mapper/assets/url-mapping.json +6 -0
  116. package/test/unit/mock/contents/mapper/entries/data-for-variant-entry.json +6 -0
  117. package/test/unit/mock/contents/mapper/entries/empty-data/data-for-variant-entry.json +1 -0
  118. package/test/unit/mock/contents/mapper/entries/uid-mapping.json +6 -0
  119. package/test/unit/mock/contents/mapper/marketplace_apps/uid-mapping.json +3 -0
  120. package/test/unit/mock/contents/mapper/personalization/experiences/variants-uid-mapping.json +5 -0
  121. package/test/unit/mock/contents/mapper/taxonomies/terms/success.json +1 -0
  122. package/test/unit/mock/export-config.json +48 -0
  123. package/test/unit/mock/import-config.json +63 -0
  124. package/tsconfig.json +19 -0
@@ -0,0 +1,483 @@
1
+ import omit from 'lodash/omit';
2
+ import chunk from 'lodash/chunk';
3
+ import entries from 'lodash/entries';
4
+ import isEmpty from 'lodash/isEmpty';
5
+ import forEach from 'lodash/forEach';
6
+ import indexOf from 'lodash/indexOf';
7
+ import { join, resolve } from 'path';
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { FsUtility, HttpResponse, sanitizePath } from '@contentstack/cli-utilities';
10
+
11
+ import VariantAdapter, { VariantHttpClient } from '../utils/variant-api-adapter';
12
+ import {
13
+ LogType,
14
+ APIConfig,
15
+ AdapterType,
16
+ AnyProperty,
17
+ ImportConfig,
18
+ ContentTypeStruct,
19
+ VariantEntryStruct,
20
+ ImportHelperMethodsConfig,
21
+ EntryDataForVariantEntries,
22
+ CreateVariantEntryDto,
23
+ PublishVariantEntryDto,
24
+ } from '../types';
25
+ import { formatError, fsUtil, log } from '../utils';
26
+
27
+ export default class VariantEntries extends VariantAdapter<VariantHttpClient<ImportConfig>> {
28
+ public entriesDirPath: string;
29
+ public entriesMapperPath: string;
30
+ public variantEntryBasePath!: string;
31
+ public variantIdList!: Record<string, unknown>;
32
+ public personalizationConfig: ImportConfig['modules']['personalization'];
33
+
34
+ public taxonomies!: Record<string, unknown>;
35
+ public assetUrlMapper!: Record<string, any>;
36
+ public assetUidMapper!: Record<string, any>;
37
+ public entriesUidMapper!: Record<string, any>;
38
+ private installedExtensions!: Record<string, any>[];
39
+ private failedVariantPath!: string;
40
+ private failedVariantEntries!: Record<string, any>;
41
+ private environments!: Record<string, any>;
42
+
43
+ constructor(readonly config: ImportConfig & { helpers?: ImportHelperMethodsConfig }) {
44
+ const conf: APIConfig & AdapterType<VariantHttpClient<ImportConfig>, APIConfig> = {
45
+ config,
46
+ httpClient: true,
47
+ baseURL: config.host,
48
+ Adapter: VariantHttpClient<ImportConfig>,
49
+ headers: {
50
+ api_key: config.apiKey,
51
+ branch: config.branchName,
52
+ authtoken: config.auth_token,
53
+ organization_uid: config.org_uid,
54
+ 'X-Project-Uid': config.modules.personalization.project_id,
55
+ },
56
+ };
57
+ super(Object.assign(omit(config, ['helpers']), conf));
58
+ this.entriesMapperPath = resolve(sanitizePath(config.backupDir), sanitizePath(config.branchName || ''), 'mapper', 'entries');
59
+ this.personalizationConfig = this.config.modules.personalization;
60
+ this.entriesDirPath = resolve(sanitizePath(config.backupDir), sanitizePath(config.branchName || ''), sanitizePath(config.modules.entries.dirName));
61
+ this.failedVariantPath = resolve(sanitizePath(this.entriesMapperPath), 'failed-entry-variants.json');
62
+ this.failedVariantEntries = new Map();
63
+ }
64
+
65
+ /**
66
+ * This TypeScript function asynchronously imports backupDir from a JSON file and processes the entries
67
+ * for variant entries.
68
+ * @returns If the file at the specified file path exists and contains backupDir, the function will parse
69
+ * the JSON backupDir and iterate over each entry to import variant entries using the
70
+ * `importVariantEntries` method. If the `entriesForVariants` array is empty, the function will log a
71
+ * message indicating that no entries were found and return.
72
+ */
73
+ async import() {
74
+ const filePath = resolve(sanitizePath(this.entriesMapperPath), 'data-for-variant-entry.json');
75
+ const variantIdPath = resolve(
76
+ sanitizePath(this.config.backupDir),
77
+ 'mapper',
78
+ sanitizePath(this.personalizationConfig.dirName),
79
+ sanitizePath(this.personalizationConfig.experiences.dirName),
80
+ 'variants-uid-mapping.json',
81
+ );
82
+
83
+ if (!existsSync(filePath)) {
84
+ log(this.config, this.messages.IMPORT_ENTRY_NOT_FOUND, 'info');
85
+ return;
86
+ }
87
+
88
+ if (!existsSync(variantIdPath)) {
89
+ log(this.config, this.messages.EMPTY_VARIANT_UID_DATA, 'error');
90
+ return;
91
+ }
92
+
93
+ const entriesForVariants = fsUtil.readFile(filePath, true) as EntryDataForVariantEntries[];
94
+
95
+ if (isEmpty(entriesForVariants)) {
96
+ log(this.config, this.messages.IMPORT_ENTRY_NOT_FOUND, 'info');
97
+ return;
98
+ }
99
+
100
+ const entriesUidMapperPath = join(sanitizePath(this.entriesMapperPath), 'uid-mapping.json');
101
+ const assetUidMapperPath = resolve(sanitizePath(this.config.backupDir), 'mapper', 'assets', 'uid-mapping.json');
102
+ const assetUrlMapperPath = resolve(sanitizePath(this.config.backupDir), 'mapper', 'assets', 'url-mapping.json');
103
+ const taxonomiesPath = resolve(
104
+ sanitizePath(this.config.backupDir),
105
+ 'mapper',
106
+ sanitizePath(this.config.modules.taxonomies.dirName),
107
+ 'terms',
108
+ 'success.json',
109
+ );
110
+ const marketplaceAppMapperPath = resolve(sanitizePath(this.config.backupDir), 'mapper', 'marketplace_apps', 'uid-mapping.json');
111
+ const envPath = resolve(sanitizePath(this.config.backupDir), 'environments', 'environments.json');
112
+ // NOTE Read and store list of variant IDs
113
+ this.variantIdList = (fsUtil.readFile(variantIdPath, true) || {}) as Record<string, unknown>;
114
+ if (isEmpty(this.variantIdList)) {
115
+ log(this.config, this.messages.EMPTY_VARIANT_UID_DATA, 'info');
116
+ return;
117
+ }
118
+
119
+ // NOTE entry relational data lookup dependencies.
120
+ this.entriesUidMapper = (fsUtil.readFile(entriesUidMapperPath, true) || {}) as Record<string, any>;
121
+ this.installedExtensions = ((fsUtil.readFile(marketplaceAppMapperPath) as any) || { extension_uid: {} })
122
+ .extension_uid as Record<string, any>[];
123
+ this.taxonomies = (fsUtil.readFile(taxonomiesPath, true) || {}) as Record<string, unknown>;
124
+ this.assetUidMapper = (fsUtil.readFile(assetUidMapperPath, true) || {}) as Record<string, any>;
125
+ this.assetUrlMapper = (fsUtil.readFile(assetUrlMapperPath, true) || {}) as Record<string, any>;
126
+ this.environments = (fsUtil.readFile(envPath, true) || {}) as Record<string, any>;
127
+
128
+ for (const entriesForVariant of entriesForVariants) {
129
+ await this.importVariantEntries(entriesForVariant);
130
+ }
131
+ log(this.config, 'All the entries variants have been imported & published successfully', 'success');
132
+ }
133
+
134
+ /**
135
+ * The function `importVariantEntries` asynchronously imports variant entries using file system
136
+ * utility in TypeScript.
137
+ * @param {EntryDataForVariantEntries} entriesForVariant - EntryDataForVariantEntries {
138
+ */
139
+ async importVariantEntries(entriesForVariant: EntryDataForVariantEntries) {
140
+ const variantEntry = this.config.modules.variantEntry;
141
+ const { content_type, locale, entry_uid } = entriesForVariant;
142
+ const ctConfig = this.config.modules['content-types'];
143
+ const contentType: ContentTypeStruct = JSON.parse(
144
+ readFileSync(resolve(sanitizePath(this.config.backupDir), sanitizePath(ctConfig.dirName), `${sanitizePath(content_type)}.json`), 'utf8'),
145
+ );
146
+ const variantEntryBasePath = join(sanitizePath(this.entriesDirPath), sanitizePath(content_type), sanitizePath(locale), sanitizePath(variantEntry.dirName), sanitizePath(entry_uid));
147
+ const fs = new FsUtility({ basePath: variantEntryBasePath });
148
+
149
+ for (const _ in fs.indexFileContent) {
150
+ try {
151
+ const variantEntries = (await fs.readChunkFiles.next()) as VariantEntryStruct[];
152
+ if (variantEntries?.length) {
153
+ await this.handleConcurrency(contentType, variantEntries, entriesForVariant);
154
+ }
155
+ } catch (error) {
156
+ log(this.config, error, 'error');
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * The function `handleConcurrency` processes variant entries in batches with a specified concurrency
163
+ * level and handles API calls for creating variant entries.
164
+ * @param {VariantEntryStruct[]} variantEntries - The `variantEntries` parameter is an array of
165
+ * `VariantEntryStruct` objects. It seems like this function is handling concurrency for processing
166
+ * these entries in batches. The function chunks the `variantEntries` array into smaller batches and
167
+ * then processes each batch asynchronously using `Promise.allSettled`. It also
168
+ * @param {EntryDataForVariantEntries} entriesForVariant - The `entriesForVariant` parameter seems to
169
+ * be an object containing the following properties:
170
+ * @returns The `handleConcurrency` function processes variant entries in batches, creating variant
171
+ * entries using the `createVariantEntry` method and handling the API response using
172
+ * `Promise.allSettled`. The function also includes logic to handle variant IDs and delays between
173
+ * batch processing.
174
+ */
175
+ async handleConcurrency(
176
+ contentType: ContentTypeStruct,
177
+ variantEntries: VariantEntryStruct[],
178
+ entriesForVariant: EntryDataForVariantEntries,
179
+ ) {
180
+ let batchNo = 0;
181
+ const variantEntryConfig = this.config.modules.variantEntry;
182
+ const { content_type, locale, entry_uid } = entriesForVariant;
183
+ const entryUid = this.entriesUidMapper[entry_uid];
184
+ const batches = chunk(variantEntries, variantEntryConfig.apiConcurrency || 5);
185
+
186
+ if (isEmpty(batches)) return;
187
+
188
+ for (const [, batch] of entries(batches)) {
189
+ batchNo += 1;
190
+ const allPromise = [];
191
+ const start = Date.now();
192
+
193
+ for (let [, variantEntry] of entries(batch)) {
194
+ const onSuccess = ({ response, apiData: { entryUid, variantUid }, log }: any) => {
195
+ log(this.config, `Created entry variant: '${variantUid}' of entry uid ${entryUid}`, 'info');
196
+ };
197
+
198
+ const onReject = ({ error, apiData, log }: any) => {
199
+ const { entryUid, variantUid } = apiData;
200
+ this.failedVariantEntries.set(variantUid, apiData);
201
+ log(this.config, `Failed to create entry variant: '${variantUid}' of entry uid ${entryUid}`, 'error');
202
+ log(this.config, error, 'error');
203
+ };
204
+ // NOTE Find new variant Id by old Id
205
+ const variant_id = this.variantIdList[variantEntry.variant_id] as string;
206
+ // NOTE Replace all the relation data UID's
207
+ variantEntry = this.handleVariantEntryRelationalData(contentType, variantEntry);
208
+ const changeSet = this.serializeChangeSet(variantEntry);
209
+ const createVariantReq: CreateVariantEntryDto = {
210
+ _variant: variantEntry._variant,
211
+ ...changeSet,
212
+ };
213
+
214
+ if (variant_id) {
215
+ const promise = this.variantInstance.createVariantEntry(
216
+ createVariantReq,
217
+ {
218
+ locale,
219
+ entry_uid: entryUid,
220
+ variant_id,
221
+ content_type_uid: content_type,
222
+ },
223
+ {
224
+ reject: onReject.bind(this),
225
+ resolve: onSuccess.bind(this),
226
+ variantUid: variantEntry.uid,
227
+ log: log,
228
+ },
229
+ );
230
+
231
+ allPromise.push(promise);
232
+ } else {
233
+ log(this.config, this.messages.VARIANT_ID_NOT_FOUND, 'error');
234
+ }
235
+ }
236
+
237
+ // NOTE Handle the API response here
238
+ await Promise.allSettled(allPromise);
239
+ // NOTE publish all the entries
240
+ await this.publishVariantEntries(batch, entryUid, content_type);
241
+ const end = Date.now();
242
+ const exeTime = end - start;
243
+ this.variantInstance.delay(1000 - exeTime);
244
+ }
245
+
246
+ fsUtil.writeFile(this.failedVariantPath, this.failedVariantEntries);
247
+ }
248
+
249
+ /**
250
+ * Serializes the change set of a entry variant.
251
+ * @param variantEntry - The entry variant to serialize.
252
+ * @returns The serialized change set as a record.
253
+ */
254
+ serializeChangeSet(variantEntry: VariantEntryStruct) {
255
+ let changeSet: Record<string, any> = {};
256
+ if (variantEntry?._variant?._change_set?.length) {
257
+ variantEntry._variant._change_set.forEach((key: string) => {
258
+ key = key.split('.')[0];
259
+ if (variantEntry[key]) {
260
+ changeSet[key] = variantEntry[key];
261
+ }
262
+ });
263
+ }
264
+ return changeSet;
265
+ }
266
+
267
+ /**
268
+ * The function `handleVariantEntryRelationalData` processes relational data for a entry variant
269
+ * based on the provided content type and configuration helpers.
270
+ * @param {ContentTypeStruct} contentType - The `contentType` parameter in the
271
+ * `handleVariantEntryRelationalData` function is of type `ContentTypeStruct`. It is used to define
272
+ * the structure of the content type being processed within the function. This parameter likely
273
+ * contains information about the schema and configuration of the content type.
274
+ * @param {VariantEntryStruct} variantEntry - The `variantEntry` parameter in the
275
+ * `handleVariantEntryRelationalData` function is a data structure that represents a entry variant.
276
+ * It is of type `VariantEntryStruct` and contains information related to a specific entry variant.
277
+ * This function is responsible for performing various operations on the `variantEntry`
278
+ * @returns The function `handleVariantEntryRelationalData` returns the `variantEntry` after
279
+ * performing various lookups and replacements on it based on the provided `contentType` and
280
+ * `config.helpers`.
281
+ */
282
+ handleVariantEntryRelationalData(
283
+ contentType: ContentTypeStruct,
284
+ variantEntry: VariantEntryStruct,
285
+ ): VariantEntryStruct {
286
+ if (this.config.helpers) {
287
+ const { lookUpTerms, lookupAssets, lookupExtension, lookupEntries, restoreJsonRteEntryRefs } =
288
+ this.config.helpers;
289
+
290
+ // FIXME Not sure why do we even need lookupExtension in entries [Ref taken from entries import]
291
+ // Feel free to remove this flow if it's not valid
292
+ // NOTE Find and replace extension's UID
293
+ if (lookupExtension) {
294
+ lookupExtension(this.config, contentType.schema, this.config.preserveStackVersion, this.installedExtensions);
295
+ }
296
+
297
+ // NOTE Find and replace RTE Ref UIDs
298
+ variantEntry = restoreJsonRteEntryRefs(variantEntry, variantEntry, contentType.schema, {
299
+ uidMapper: this.entriesUidMapper,
300
+ mappedAssetUids: this.assetUidMapper,
301
+ mappedAssetUrls: this.assetUrlMapper,
302
+ }) as VariantEntryStruct;
303
+
304
+ // NOTE Find and replace Entry Ref UIDs
305
+ variantEntry = lookupEntries(
306
+ {
307
+ entry: variantEntry,
308
+ content_type: contentType,
309
+ },
310
+ this.entriesUidMapper,
311
+ resolve(sanitizePath(this.entriesMapperPath), sanitizePath(contentType.uid), sanitizePath(variantEntry.locale)),
312
+ );
313
+
314
+ // NOTE: will remove term if term doesn't exists in taxonomy
315
+ // FIXME: Validate if taxonomy support available for variant entries,
316
+ // if not, feel free to remove this lookup flow.
317
+ lookUpTerms(contentType.schema, variantEntry, this.taxonomies, this.config);
318
+
319
+ // update file fields of entry variants to support lookup asset logic
320
+ this.updateFileFields(variantEntry);
321
+
322
+ // NOTE Find and replace asset's UID
323
+ variantEntry = lookupAssets(
324
+ {
325
+ entry: variantEntry,
326
+ content_type: contentType,
327
+ },
328
+ this.assetUidMapper,
329
+ this.assetUrlMapper,
330
+ join(sanitizePath(this.entriesDirPath), sanitizePath(contentType.uid)),
331
+ this.installedExtensions,
332
+ );
333
+ }
334
+
335
+ return variantEntry;
336
+ }
337
+
338
+ /**
339
+ * Updates the file fields of a entry variant to support lookup asset logic.
340
+ * Lookup asset expects file fields to be an object instead of a string. So here we are updating the file fields to be an object. Object has two keys: `uid` and `filename`. `uid` is the asset UID and `filename` is the name of the file. Used a dummy value for the filename. This is a temporary fix and will be updated in the future.
341
+ * @param variantEntry - The entry variant to update.
342
+ */
343
+ updateFileFields(variantEntry: VariantEntryStruct) {
344
+ const setValue = (currentObj: VariantEntryStruct, keys: Array<string>) => {
345
+ if (!currentObj || keys.length === 0) return;
346
+
347
+ const [firstKey, ...restKeys] = keys;
348
+
349
+ if (Array.isArray(currentObj)) {
350
+ for (const item of currentObj) {
351
+ setValue(item, [firstKey, ...restKeys]);
352
+ }
353
+ } else if (currentObj && typeof currentObj === 'object') {
354
+ if (firstKey in currentObj) {
355
+ if (keys.length === 1) {
356
+ currentObj[firstKey] = { uid: currentObj[firstKey], filename: 'dummy.jpeg' };
357
+ } else {
358
+ setValue(currentObj[firstKey], restKeys);
359
+ }
360
+ }
361
+ }
362
+ };
363
+
364
+ const pathsToUpdate = variantEntry?._metadata?.references
365
+ .filter((ref: any) => ref._content_type_uid === 'sys_assets')
366
+ .map((ref: any) => ref.path);
367
+
368
+ if (pathsToUpdate) {
369
+ pathsToUpdate.forEach((path: string) => setValue(variantEntry, path.split('.')));
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Publishes variant entries in batch for a given entry UID and content type.
375
+ * @param batch - An array of VariantEntryStruct objects representing the variant entries to be published.
376
+ * @param entryUid - The UID of the entry for which the variant entries are being published.
377
+ * @param content_type - The UID of the content type of the entry.
378
+ * @returns A Promise that resolves when all variant entries have been published.
379
+ */
380
+ async publishVariantEntries(batch: VariantEntryStruct[], entryUid: string, content_type: string) {
381
+ const allPromise = [];
382
+ for (let [, variantEntry] of entries(batch)) {
383
+ const variantUid = variantEntry.uid;
384
+ const oldVariantUid = variantEntry.variant_id || '';
385
+ const newVariantUid = this.variantIdList[oldVariantUid] as string;
386
+
387
+ if (!newVariantUid) {
388
+ log(
389
+ this.config,
390
+ `${this.messages.VARIANT_ID_NOT_FOUND}. Skipping entry variant publish for ${variantUid}`,
391
+ 'info',
392
+ );
393
+ continue;
394
+ }
395
+
396
+ if (this.failedVariantEntries.has(variantUid)) {
397
+ log(
398
+ this.config,
399
+ `${this.messages.VARIANT_UID_NOT_FOUND}. Skipping entry variant publish for ${variantUid}`,
400
+ 'info',
401
+ );
402
+ continue;
403
+ }
404
+
405
+ if (this.environments?.length) {
406
+ log(this.config, 'No environment found! Skipping entry variant publishing...', 'info');
407
+ return;
408
+ }
409
+
410
+ const onSuccess = ({ response, apiData: { entryUid, variantUid }, log }: any) => {
411
+ log(this.config, `Entry variant: '${variantUid}' of entry uid ${entryUid} published successfully!`, 'info');
412
+ };
413
+ const onReject = ({ error, apiData: { entryUid, variantUid }, log }: any) => {
414
+ log(this.config, `Failed to publish entry variant: '${variantUid}' of entry uid ${entryUid}`, 'error');
415
+ log(this.config, formatError(error), 'error');
416
+ };
417
+
418
+ const { environments, locales } = this.serializePublishEntries(variantEntry);
419
+ if (environments?.length === 0 || locales?.length === 0) {
420
+ continue;
421
+ }
422
+ const publishReq: PublishVariantEntryDto = {
423
+ entry: {
424
+ environments,
425
+ locales,
426
+ publish_with_base_entry: false,
427
+ variants: [{ uid: newVariantUid, version: 1 }],
428
+ },
429
+ locale: variantEntry.locale,
430
+ version: 1,
431
+ };
432
+
433
+ const promise = this.variantInstance.publishVariantEntry(
434
+ publishReq,
435
+ {
436
+ entry_uid: entryUid,
437
+ content_type_uid: content_type,
438
+ },
439
+ {
440
+ reject: onReject.bind(this),
441
+ resolve: onSuccess.bind(this),
442
+ log: log,
443
+ variantUid,
444
+ },
445
+ );
446
+
447
+ allPromise.push(promise);
448
+ }
449
+ await Promise.allSettled(allPromise);
450
+ }
451
+
452
+ /**
453
+ * Serializes the publish entries of a variant.
454
+ * @param variantEntry - The entry variant to serialize.
455
+ * @returns An object containing the serialized publish entries.
456
+ */
457
+ serializePublishEntries(variantEntry: VariantEntryStruct): {
458
+ environments: Array<string>;
459
+ locales: Array<string>;
460
+ } {
461
+ const requestObject: {
462
+ environments: Array<string>;
463
+ locales: Array<string>;
464
+ } = {
465
+ environments: [],
466
+ locales: [],
467
+ };
468
+ if (variantEntry.publish_details && variantEntry.publish_details?.length > 0) {
469
+ forEach(variantEntry.publish_details, (pubObject) => {
470
+ if (
471
+ this.environments.hasOwnProperty(pubObject.environment) &&
472
+ indexOf(requestObject.environments, this.environments[pubObject.environment].name) === -1
473
+ ) {
474
+ requestObject.environments.push(this.environments[pubObject.environment].name);
475
+ }
476
+ if (pubObject.locale && indexOf(requestObject.locales, pubObject.locale) === -1) {
477
+ requestObject.locales.push(pubObject.locale);
478
+ }
479
+ });
480
+ }
481
+ return requestObject;
482
+ }
483
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './utils';
3
+ export * from './export';
4
+ export * from './import';
5
+ export * from './messages'
@@ -0,0 +1,63 @@
1
+ import memoize from 'lodash/memoize';
2
+
3
+ const errors = {
4
+ CREATE_FAILURE: '{module} creation failed!',
5
+ };
6
+
7
+ const commonMsg = {
8
+ CREATE_SUCCESS: '{module} created successfully!',
9
+ };
10
+
11
+ const migrationMsg = {
12
+ IMPORT_MSG: 'Migrating {module}...',
13
+ };
14
+
15
+ const variantEntry = {
16
+ IMPORT_ENTRY_NOT_FOUND: 'Entries data not found to import variant entries',
17
+ EMPTY_VARIANT_UID_DATA: 'Empty variants entry mapper found!',
18
+ VARIANT_ID_NOT_FOUND: 'Variant ID not found',
19
+ VARIANT_UID_NOT_FOUND: 'Entry Variant UID not found',
20
+ };
21
+
22
+ const expImportMsg = {
23
+ UPDATING_CT_IN_EXP: 'Updating content types in experiences...',
24
+ UPDATED_CT_IN_EXP: 'Successfully updated content types in experiences!',
25
+ VALIDATE_VARIANT_AND_VARIANT_GRP: 'Validating variant group and variants creation...',
26
+ PERSONALIZATION_JOB_FAILURE: 'Something went wrong with personalization background job! Failed to fetch some variant & variant groups',
27
+ };
28
+
29
+ const messages: typeof errors & typeof commonMsg & typeof migrationMsg & typeof variantEntry & typeof expImportMsg = {
30
+ ...errors,
31
+ ...commonMsg,
32
+ ...migrationMsg,
33
+ ...variantEntry,
34
+ ...expImportMsg
35
+ };
36
+
37
+ /**
38
+ * The function `` is a TypeScript function that replaces placeholders in a message string with
39
+ * values from a provided object.
40
+ * @param {string} msg - The `msg` parameter is a string that represents the message template with
41
+ * placeholders for dynamic values.
42
+ * @param args - The `args` parameter is an object that contains key-value pairs where the key is a
43
+ * string and the value is also a string. These key-value pairs are used to replace placeholders in the
44
+ * `msg` string with the corresponding values.
45
+ * @returns the formatted message with the provided arguments replaced in the placeholders.
46
+ */
47
+ function $t(msg: string, args: Record<string, string>): string {
48
+ const transfer = memoize(function (msg: string, args: Record<string, string>) {
49
+ if (!msg) return '';
50
+
51
+ for (const key of Object.keys(args)) {
52
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
53
+ msg = msg.replace(new RegExp(`{${escapedKey}}`, 'g'), args[key] || escapedKey);
54
+ }
55
+
56
+ return msg;
57
+ });
58
+
59
+ return transfer(msg, args);
60
+ }
61
+
62
+ export default messages;
63
+ export { $t, errors, commonMsg };
@@ -0,0 +1,10 @@
1
+ import messages from "../messages";
2
+
3
+ export interface AdapterHelperInterface<T, Client> {
4
+ readonly config: T;
5
+ readonly apiClient: Client;
6
+ readonly messages: typeof messages;
7
+
8
+ delay(ms: number): Promise<void>;
9
+ constructQuery(query: Record<string, any>): string | void;
10
+ }
@@ -0,0 +1,41 @@
1
+ import { AnyProperty } from './utils';
2
+ import { ImportConfig } from './import-config';
3
+
4
+ export type ContentTypeStruct = {
5
+ uid: string;
6
+ title: string;
7
+ description: string;
8
+ schema: AnyProperty[];
9
+ options: AnyProperty;
10
+ } & AnyProperty;
11
+
12
+ export type ImportHelperMethodsConfig = {
13
+ lookupAssets: (
14
+ data: Record<string, any>,
15
+ mappedAssetUids: Record<string, any>,
16
+ mappedAssetUrls: Record<string, any>,
17
+ assetUidMapperPath: string,
18
+ installedExtensions: Record<string, any>[],
19
+ ) => any;
20
+ lookupExtension?: (config: ImportConfig, schema: any, preserveStackVersion: any, installedExtensions: any) => void;
21
+ restoreJsonRteEntryRefs: (
22
+ entry: Record<string, any>,
23
+ sourceStackEntry: any,
24
+ ctSchema: any,
25
+ { uidMapper, mappedAssetUids, mappedAssetUrls }: any,
26
+ ) => Record<string, any>;
27
+ lookupEntries: (
28
+ data: {
29
+ content_type: any;
30
+ entry: any;
31
+ },
32
+ mappedUids: Record<string, any>,
33
+ uidMapperPath: string,
34
+ ) => any;
35
+ lookUpTerms: (
36
+ ctSchema: Record<string, any>[],
37
+ entry: any,
38
+ taxonomiesAndTermData: Record<string, any>,
39
+ importConfig: ImportConfig,
40
+ ) => void;
41
+ };