@kontent-ai/migration-toolkit 1.4.0 → 1.5.1

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 (83) hide show
  1. package/README.md +1 -4
  2. package/dist/es2022/core/helpers/workflow-helper.d.ts +1 -0
  3. package/dist/es2022/core/helpers/workflow-helper.js +6 -4
  4. package/dist/es2022/core/helpers/workflow-helper.js.map +1 -1
  5. package/dist/es2022/core/logs/loggers.js +13 -22
  6. package/dist/es2022/core/logs/loggers.js.map +1 -1
  7. package/dist/es2022/core/models/core.models.d.ts +16 -6
  8. package/dist/es2022/core/models/migration.schema.d.ts +247 -0
  9. package/dist/es2022/core/models/migration.schema.js +12 -2
  10. package/dist/es2022/core/models/migration.schema.js.map +1 -1
  11. package/dist/es2022/core/utils/array.utils.js.map +1 -1
  12. package/dist/es2022/core/utils/binary-data.utils.js +2 -6
  13. package/dist/es2022/core/utils/binary-data.utils.js.map +1 -1
  14. package/dist/es2022/core/utils/confirm.utils.js +10 -14
  15. package/dist/es2022/core/utils/confirm.utils.js.map +1 -1
  16. package/dist/es2022/core/utils/error.utils.js +35 -27
  17. package/dist/es2022/core/utils/error.utils.js.map +1 -1
  18. package/dist/es2022/core/utils/http.utils.js +7 -11
  19. package/dist/es2022/core/utils/http.utils.js.map +1 -1
  20. package/dist/es2022/core/utils/management-client-utils.d.ts +2 -1
  21. package/dist/es2022/core/utils/management-client-utils.js +11 -1
  22. package/dist/es2022/core/utils/management-client-utils.js.map +1 -1
  23. package/dist/es2022/core/utils/run.utils.js +12 -10
  24. package/dist/es2022/core/utils/run.utils.js.map +1 -1
  25. package/dist/es2022/export/context/export-context-fetcher.js +2 -1
  26. package/dist/es2022/export/context/export-context-fetcher.js.map +1 -1
  27. package/dist/es2022/export/export-manager.js +8 -0
  28. package/dist/es2022/export/export-manager.js.map +1 -1
  29. package/dist/es2022/export/export.models.d.ts +2 -1
  30. package/dist/es2022/import/comparers/asset-comparer.d.ts +2 -1
  31. package/dist/es2022/import/comparers/asset-comparer.js +9 -17
  32. package/dist/es2022/import/comparers/asset-comparer.js.map +1 -1
  33. package/dist/es2022/import/context/import-context-fetcher.js +120 -40
  34. package/dist/es2022/import/context/import-context-fetcher.js.map +1 -1
  35. package/dist/es2022/import/import.models.d.ts +2 -1
  36. package/dist/es2022/import/importers/assets-importer.js +25 -7
  37. package/dist/es2022/import/importers/assets-importer.js.map +1 -1
  38. package/dist/es2022/import/importers/language-variant-importer.js +95 -87
  39. package/dist/es2022/import/importers/language-variant-importer.js.map +1 -1
  40. package/dist/es2022/import/importers/workflow-importer.d.ts +41 -4
  41. package/dist/es2022/import/importers/workflow-importer.js +158 -17
  42. package/dist/es2022/import/importers/workflow-importer.js.map +1 -1
  43. package/dist/es2022/metadata.js +2 -2
  44. package/dist/es2022/node/cli/app.js +8 -12
  45. package/dist/es2022/node/cli/app.js.map +1 -1
  46. package/dist/es2022/node/cli/args/args-fetcher.js +9 -10
  47. package/dist/es2022/node/cli/args/args-fetcher.js.map +1 -1
  48. package/dist/es2022/toolkit/migrate.d.ts +1 -3
  49. package/dist/es2022/toolkit/migrate.js.map +1 -1
  50. package/dist/es2022/translation/extraction/items-extraction.processor.js +17 -17
  51. package/dist/es2022/translation/extraction/items-extraction.processor.js.map +1 -1
  52. package/dist/es2022/zip/zip-packager.js +13 -18
  53. package/dist/es2022/zip/zip-packager.js.map +1 -1
  54. package/dist/es2022/zip/zip-transformer.js +3 -1
  55. package/dist/es2022/zip/zip-transformer.js.map +1 -1
  56. package/lib/core/helpers/workflow-helper.ts +8 -8
  57. package/lib/core/logs/loggers.ts +14 -22
  58. package/lib/core/models/core.models.ts +23 -4
  59. package/lib/core/models/migration.schema.ts +13 -2
  60. package/lib/core/utils/array.utils.ts +2 -10
  61. package/lib/core/utils/binary-data.utils.ts +2 -7
  62. package/lib/core/utils/confirm.utils.ts +18 -24
  63. package/lib/core/utils/error.utils.ts +50 -30
  64. package/lib/core/utils/http.utils.ts +14 -15
  65. package/lib/core/utils/management-client-utils.ts +188 -176
  66. package/lib/core/utils/run.utils.ts +27 -18
  67. package/lib/export/context/export-context-fetcher.ts +2 -1
  68. package/lib/export/export-manager.ts +14 -3
  69. package/lib/export/export.models.ts +3 -1
  70. package/lib/import/comparers/asset-comparer.ts +16 -24
  71. package/lib/import/context/import-context-fetcher.ts +179 -85
  72. package/lib/import/import.models.ts +2 -0
  73. package/lib/import/importers/assets-importer.ts +41 -25
  74. package/lib/import/importers/language-variant-importer.ts +119 -111
  75. package/lib/import/importers/workflow-importer.ts +228 -20
  76. package/lib/metadata.ts +2 -2
  77. package/lib/node/cli/app.ts +11 -12
  78. package/lib/node/cli/args/args-fetcher.ts +12 -14
  79. package/lib/toolkit/migrate.ts +1 -3
  80. package/lib/translation/extraction/items-extraction.processor.ts +93 -93
  81. package/lib/zip/zip-packager.ts +26 -28
  82. package/lib/zip/zip-transformer.ts +3 -1
  83. package/package.json +13 -12
@@ -2,27 +2,30 @@ import { AssetModels, ContentItemModels, LanguageVariantModels } from '@kontent-
2
2
  import {
3
3
  AssetStateInTargetEnvironmentByCodename,
4
4
  ItemStateInTargetEnvironmentByCodename,
5
+ LanguageVariantSchedulesStateValues,
6
+ LanguageVariantStateData,
5
7
  LanguageVariantStateInTargetEnvironmentByCodename,
8
+ LanguageVariantWorkflowState,
9
+ LanguageVariantWorkflowStateValues,
10
+ LogSpinnerData,
6
11
  MigrationItem,
12
+ WorkflowStep,
7
13
  findRequired,
8
14
  is404Error,
9
15
  isNotUndefined,
10
16
  managementClientUtils,
11
17
  processItemsAsync,
12
18
  runMapiRequestAsync,
13
- workflowHelper
19
+ workflowHelper as workflowHelperInit
14
20
  } from '../../core/index.js';
15
- import {
16
- GetFlattenedElementByCodenames,
17
- ImportContext,
18
- ImportContextConfig,
19
- ImportContextEnvironmentData
20
- } from '../import.models.js';
21
+ import { GetFlattenedElementByCodenames, ImportContext, ImportContextConfig, ImportContextEnvironmentData } from '../import.models.js';
21
22
  import { ExtractItemByCodename, itemsExtractionProcessor } from '../../translation/index.js';
22
23
  import chalk from 'chalk';
24
+ import { match } from 'ts-pattern';
23
25
 
24
26
  interface LanguageVariantWrapper {
25
- readonly languageVariant: Readonly<LanguageVariantModels.ContentItemLanguageVariant>;
27
+ readonly draftLanguageVariant: Readonly<LanguageVariantModels.ContentItemLanguageVariant> | undefined;
28
+ readonly publishedLanguageVariant: Readonly<LanguageVariantModels.ContentItemLanguageVariant> | undefined;
26
29
  readonly migrationItem: MigrationItem;
27
30
  }
28
31
 
@@ -37,7 +40,8 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
37
40
  collections: await mapiUtils.getAllCollectionsAsync(spinnerData),
38
41
  languages: await mapiUtils.getAllLanguagesAsync(spinnerData),
39
42
  workflows: await mapiUtils.getAllWorkflowsAsync(spinnerData),
40
- types: await mapiUtils.getFlattenedContentTypesAsync(spinnerData)
43
+ types: await mapiUtils.getFlattenedContentTypesAsync(spinnerData),
44
+ assetFolders: await mapiUtils.getAllAssetFoldersAsync(spinnerData)
41
45
  };
42
46
 
43
47
  spinnerData({ type: 'info', message: `Environmental data loaded` });
@@ -47,13 +51,10 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
47
51
  };
48
52
 
49
53
  const environmentData = await getEnvironmentDataAsync();
54
+ const workflowHelper = workflowHelperInit(environmentData.workflows);
50
55
 
51
56
  const getElement = () => {
52
- const getFlattenedElement: GetFlattenedElementByCodenames = (
53
- contentTypeCodename,
54
- elementCodename,
55
- sourceType
56
- ) => {
57
+ const getFlattenedElement: GetFlattenedElementByCodenames = (contentTypeCodename, elementCodename, sourceType) => {
57
58
  const contentType = findRequired(
58
59
  environmentData.types,
59
60
  (type) => type.contentTypeCodename === contentTypeCodename,
@@ -65,9 +66,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
65
66
  (element) => element.codename === elementCodename,
66
67
  `Element type with codename '${chalk.red(elementCodename)}' was not found in content type '${chalk.red(
67
68
  contentTypeCodename
68
- )}'. Available elements are '${contentType.elements
69
- .map((element) => chalk.yellow(element.codename))
70
- .join(', ')}'`
69
+ )}'. Available elements are '${contentType.elements.map((element) => chalk.yellow(element.codename)).join(', ')}'`
71
70
  );
72
71
 
73
72
  if (sourceType !== element.type) {
@@ -84,7 +83,70 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
84
83
  return getFlattenedElement;
85
84
  };
86
85
 
87
- const getLanguageVariantsAsync = async (
86
+ const getLatestLanguageVariantAsync = async (
87
+ logSpinner: LogSpinnerData,
88
+ migrationItem: MigrationItem
89
+ ): Promise<Readonly<LanguageVariantModels.ContentItemLanguageVariant> | undefined> => {
90
+ try {
91
+ const latestLanguageVariant = await runMapiRequestAsync({
92
+ logger: config.logger,
93
+ func: async () =>
94
+ (
95
+ await config.managementClient
96
+ .viewLanguageVariant()
97
+ .byItemCodename(migrationItem.system.codename)
98
+ .byLanguageCodename(migrationItem.system.language.codename)
99
+ .toPromise()
100
+ ).data,
101
+ action: 'view',
102
+ type: 'languageVariant',
103
+ logSpinner: logSpinner,
104
+ itemName: `latest -> codename -> ${migrationItem.system.codename} (${migrationItem.system.language.codename})`
105
+ });
106
+
107
+ return latestLanguageVariant;
108
+ } catch (error) {
109
+ if (!is404Error(error)) {
110
+ throw error;
111
+ }
112
+
113
+ return undefined;
114
+ }
115
+ };
116
+
117
+ const getPublishedLanguageVariantAsync = async (
118
+ logSpinner: LogSpinnerData,
119
+ migrationItem: MigrationItem
120
+ ): Promise<Readonly<LanguageVariantModels.ContentItemLanguageVariant> | undefined> => {
121
+ try {
122
+ const draftLanguageVariant = await runMapiRequestAsync({
123
+ logger: config.logger,
124
+ func: async () =>
125
+ (
126
+ await config.managementClient
127
+ .viewLanguageVariant()
128
+ .byItemCodename(migrationItem.system.codename)
129
+ .byLanguageCodename(migrationItem.system.language.codename)
130
+ .published()
131
+ .toPromise()
132
+ ).data,
133
+ action: 'view',
134
+ type: 'languageVariant',
135
+ logSpinner: logSpinner,
136
+ itemName: `published -> codename -> ${migrationItem.system.codename} (${migrationItem.system.language.codename})`
137
+ });
138
+
139
+ return draftLanguageVariant;
140
+ } catch (error) {
141
+ if (!is404Error(error)) {
142
+ throw error;
143
+ }
144
+
145
+ return undefined;
146
+ }
147
+ };
148
+
149
+ const getLanguageVariantWrappersAsync = async (
88
150
  migrationItems: readonly MigrationItem[]
89
151
  ): Promise<readonly LanguageVariantWrapper[]> => {
90
152
  return (
@@ -100,34 +162,28 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
100
162
  };
101
163
  },
102
164
  processAsync: async (item, logSpinner) => {
103
- try {
104
- const languageVariant = await runMapiRequestAsync({
105
- logger: config.logger,
106
- func: async () =>
107
- (
108
- await config.managementClient
109
- .viewLanguageVariant()
110
- .byItemCodename(item.system.codename)
111
- .byLanguageCodename(item.system.language.codename)
112
- .toPromise()
113
- ).data,
114
- action: 'view',
115
- type: 'languageVariant',
116
- logSpinner: logSpinner,
117
- itemName: `codename -> ${item.system.codename} (${item.system.language.codename})`
118
- });
165
+ const latestLanguageVariant = await getLatestLanguageVariantAsync(logSpinner, item);
119
166
 
167
+ if (!latestLanguageVariant) {
168
+ // there is neither published or draft version as latest version does not exist at all
169
+ return undefined;
170
+ }
171
+
172
+ if (workflowHelper.isPublishedStepById(latestLanguageVariant.workflow.stepIdentifier.id ?? '')) {
173
+ // if latest version is published, it means that there is no draft, but there is published version
120
174
  return {
121
- languageVariant: languageVariant,
122
- migrationItem: item
175
+ migrationItem: item,
176
+ draftLanguageVariant: undefined,
177
+ publishedLanguageVariant: latestLanguageVariant
123
178
  };
124
- } catch (error) {
125
- if (!is404Error(error)) {
126
- throw error;
127
- }
128
-
129
- return undefined;
130
179
  }
180
+
181
+ // if latest version is a draft version, check if there is published version as well
182
+ return {
183
+ migrationItem: item,
184
+ draftLanguageVariant: latestLanguageVariant,
185
+ publishedLanguageVariant: await getPublishedLanguageVariantAsync(logSpinner, item)
186
+ };
131
187
  }
132
188
  })
133
189
  ).filter(isNotUndefined);
@@ -152,10 +208,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
152
208
  try {
153
209
  return await runMapiRequestAsync({
154
210
  logger: config.logger,
155
- func: async () =>
156
- (
157
- await config.managementClient.viewContentItem().byItemCodename(codename).toPromise()
158
- ).data,
211
+ func: async () => (await config.managementClient.viewContentItem().byItemCodename(codename).toPromise()).data,
159
212
  action: 'view',
160
213
  type: 'contentItem',
161
214
  logSpinner: logSpinner,
@@ -173,9 +226,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
173
226
  ).filter(isNotUndefined);
174
227
  };
175
228
 
176
- const getAssetsByCodenamesAsync = async (
177
- assetCodenames: ReadonlySet<string>
178
- ): Promise<readonly AssetModels.Asset[]> => {
229
+ const getAssetsByCodenamesAsync = async (assetCodenames: ReadonlySet<string>): Promise<readonly AssetModels.Asset[]> => {
179
230
  return (
180
231
  await processItemsAsync<string, Readonly<AssetModels.Asset> | undefined>({
181
232
  action: 'Fetching assets',
@@ -192,10 +243,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
192
243
  try {
193
244
  return await runMapiRequestAsync({
194
245
  logger: config.logger,
195
- func: async () =>
196
- (
197
- await config.managementClient.viewAsset().byAssetCodename(codename).toPromise()
198
- ).data,
246
+ func: async () => (await config.managementClient.viewAsset().byAssetCodename(codename).toPromise()).data,
199
247
  action: 'view',
200
248
  type: 'asset',
201
249
  logSpinner: logSpinner,
@@ -213,44 +261,98 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
213
261
  ).filter(isNotUndefined);
214
262
  };
215
263
 
264
+ const getVariantState = (languageVariant: Readonly<LanguageVariantModels.ContentItemLanguageVariant>): LanguageVariantStateData => {
265
+ const variantWorkflowId = languageVariant.workflow.workflowIdentifier.id;
266
+ const variantStepId = languageVariant.workflow.stepIdentifier.id;
267
+ const { workflow, step } = workflowHelper.getWorkflowAndStep({
268
+ workflowMatcher: {
269
+ errorMessage: `Could not workflow with id '${chalk.red(variantWorkflowId)}' in target project`,
270
+ match: (workflow) => workflow.id == variantWorkflowId
271
+ },
272
+ stepMatcher: {
273
+ errorMessage: `Could not workflow step with id '${chalk.red(variantStepId)}' in workflow '${chalk.yellow(
274
+ variantWorkflowId
275
+ )}'`,
276
+ match: (step) => step.id == variantStepId
277
+ }
278
+ });
279
+
280
+ return {
281
+ languageVariant: languageVariant,
282
+ workflow: workflow,
283
+ workflowState: getWorkflowState(step, languageVariant)
284
+ };
285
+ };
286
+
216
287
  const getVariantStatesAsync = async (
217
288
  migrationItems: readonly MigrationItem[]
218
289
  ): Promise<readonly LanguageVariantStateInTargetEnvironmentByCodename[]> => {
219
- const variants = await getLanguageVariantsAsync(migrationItems);
290
+ const variantWrappers = await getLanguageVariantWrappersAsync(migrationItems);
220
291
 
221
292
  return migrationItems.map<LanguageVariantStateInTargetEnvironmentByCodename>((migrationItem) => {
222
- const variant = variants.find(
293
+ const variantWrapper = variantWrappers.find(
223
294
  (m) =>
224
295
  m.migrationItem.system.codename === migrationItem.system.codename &&
225
296
  m.migrationItem.system.language === migrationItem.system.language
226
297
  );
227
298
 
228
- const variantWorkflowId = variant?.languageVariant?.workflow?.workflowIdentifier?.id;
229
- const variantStepId = variant?.languageVariant?.workflow?.stepIdentifier?.id;
230
-
231
- const workflow = environmentData.workflows.find((workflow) => workflow.id === variantWorkflowId);
232
-
233
299
  return {
234
300
  itemCodename: migrationItem.system.codename,
235
301
  languageCodename: migrationItem.system.language.codename,
236
- languageVariant: variant?.languageVariant,
237
- workflow: workflow,
238
- step: workflow
239
- ? workflowHelper(environmentData.workflows).getWorkflowStep(workflow, {
240
- errorMessage: `Could not workflow step with id '${chalk.red(
241
- variantStepId
242
- )}' in workflow '${chalk.yellow(workflow.codename)}'`,
243
- match: (step) => step.id == variantStepId
244
- })
302
+ draftLanguageVariant: variantWrapper?.draftLanguageVariant
303
+ ? getVariantState(variantWrapper.draftLanguageVariant)
245
304
  : undefined,
246
- state: variant ? 'exists' : 'doesNotExists'
305
+ publishedLanguageVariant: variantWrapper?.publishedLanguageVariant
306
+ ? getVariantState(variantWrapper.publishedLanguageVariant)
307
+ : undefined,
308
+ state: variantWrapper ? 'exists' : 'doesNotExists'
247
309
  };
248
310
  });
249
311
  };
250
312
 
251
- const getItemStatesAsync = async (
252
- itemCodenames: ReadonlySet<string>
253
- ): Promise<readonly ItemStateInTargetEnvironmentByCodename[]> => {
313
+ const getWorkflowState = (
314
+ step: Readonly<WorkflowStep>,
315
+ variant: Readonly<LanguageVariantModels.ContentItemLanguageVariant> | undefined
316
+ ): LanguageVariantWorkflowState => {
317
+ if (!variant) {
318
+ return undefined;
319
+ }
320
+
321
+ const getScheduledState = (): LanguageVariantSchedulesStateValues => {
322
+ return match(variant.schedule)
323
+ .returnType<LanguageVariantSchedulesStateValues>()
324
+ .when(
325
+ (schedule) => schedule.publishTime && schedule.publishDisplayTimezone,
326
+ () => 'scheduledPublish'
327
+ )
328
+ .when(
329
+ (schedule) => schedule.unpublishTime && schedule.unpublishDisplayTimezone,
330
+ () => 'scheduledUnpublish'
331
+ )
332
+ .otherwise(() => 'n/a');
333
+ };
334
+
335
+ const getWorkflowState = (): LanguageVariantWorkflowStateValues => {
336
+ return match(step.codename)
337
+ .returnType<LanguageVariantWorkflowStateValues>()
338
+ .when(
339
+ (stepCodename) => workflowHelper.isPublishedStepByCodename(stepCodename),
340
+ () => 'published'
341
+ )
342
+ .when(
343
+ (stepCodename) => workflowHelper.isArchivedStepByCodename(stepCodename),
344
+ () => 'archived'
345
+ )
346
+ .otherwise(() => 'draft');
347
+ };
348
+
349
+ return {
350
+ workflowState: getWorkflowState(),
351
+ scheduledState: getScheduledState()
352
+ };
353
+ };
354
+
355
+ const getItemStatesAsync = async (itemCodenames: ReadonlySet<string>): Promise<readonly ItemStateInTargetEnvironmentByCodename[]> => {
254
356
  const items = await getContentItemsByCodenamesAsync(itemCodenames);
255
357
 
256
358
  return Array.from(itemCodenames).map<ItemStateInTargetEnvironmentByCodename>((codename) => {
@@ -311,17 +413,13 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
311
413
  ]);
312
414
 
313
415
  // prepare state of objects in target environment
314
- const itemStates: readonly ItemStateInTargetEnvironmentByCodename[] = await getItemStatesAsync(
315
- itemCodenamesToCheckInTargetEnv
316
- );
416
+ const itemStates: readonly ItemStateInTargetEnvironmentByCodename[] = await getItemStatesAsync(itemCodenamesToCheckInTargetEnv);
317
417
 
318
418
  const variantStates: readonly LanguageVariantStateInTargetEnvironmentByCodename[] = await getVariantStatesAsync(
319
419
  config.migrationData.items
320
420
  );
321
421
 
322
- const assetStates: readonly AssetStateInTargetEnvironmentByCodename[] = await getAssetStatesAsync(
323
- assetCodenamesToCheckInTargetEnv
324
- );
422
+ const assetStates: readonly AssetStateInTargetEnvironmentByCodename[] = await getAssetStatesAsync(assetCodenamesToCheckInTargetEnv);
325
423
 
326
424
  return {
327
425
  environmentData,
@@ -334,9 +432,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
334
432
  return findRequired(
335
433
  itemStates,
336
434
  (state) => state.itemCodename === itemCodename,
337
- `Invalid state for item '${chalk.red(
338
- itemCodename
339
- )}'. It is expected that all item states will be initialized`
435
+ `Invalid state for item '${chalk.red(itemCodename)}'. It is expected that all item states will be initialized`
340
436
  );
341
437
  },
342
438
  getLanguageVariantStateInTargetEnvironment: (itemCodename, languageCodename) => {
@@ -352,9 +448,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
352
448
  return findRequired(
353
449
  assetStates,
354
450
  (state) => state.assetCodename === assetCodename,
355
- `Invalid state for asset '${chalk.red(
356
- assetCodename
357
- )}'. It is expected that all asset states will be initialized`
451
+ `Invalid state for asset '${chalk.red(assetCodename)}'. It is expected that all asset states will be initialized`
358
452
  );
359
453
  },
360
454
  getElement: getElementByCodenames
@@ -15,6 +15,7 @@ import {
15
15
  FlattenedContentType
16
16
  } from '../core/index.js';
17
17
  import {
18
+ AssetFolderModels,
18
19
  AssetModels,
19
20
  CollectionModels,
20
21
  ContentItemModels,
@@ -34,6 +35,7 @@ export interface ImportContextConfig {
34
35
 
35
36
  export interface ImportContextEnvironmentData {
36
37
  readonly languages: readonly Readonly<LanguageModels.LanguageModel>[];
38
+ readonly assetFolders: readonly Readonly<AssetFolderModels.AssetFolder>[];
37
39
  readonly collections: readonly Readonly<CollectionModels.Collection>[];
38
40
  readonly workflows: readonly Readonly<WorkflowModels.Workflow>[];
39
41
  readonly types: readonly Readonly<FlattenedContentType>[];
@@ -1,19 +1,19 @@
1
- import { AssetModels, ManagementClient } from '@kontent-ai/management-sdk';
1
+ import { AssetModels, LanguageModels, ManagementClient, SharedContracts } from '@kontent-ai/management-sdk';
2
+ import chalk from 'chalk';
3
+ import mime from 'mime';
2
4
  import {
3
- MigrationAsset,
5
+ geSizeInBytes,
6
+ isNotUndefined,
4
7
  Logger,
5
- processItemsAsync,
6
- runMapiRequestAsync,
8
+ LogSpinnerData,
9
+ MigrationAsset,
7
10
  MigrationAssetDescription,
8
11
  MigrationReference,
9
- geSizeInBytes,
10
- isNotUndefined,
11
- LogSpinnerData
12
+ processItemsAsync,
13
+ runMapiRequestAsync
12
14
  } from '../../core/index.js';
13
- import mime from 'mime';
14
- import chalk from 'chalk';
15
- import { ImportContext, ImportResult } from '../import.models.js';
16
15
  import { shouldReplaceBinaryFile, shouldUpdateAsset } from '../comparers/asset-comparer.js';
16
+ import { ImportContext, ImportResult } from '../import.models.js';
17
17
 
18
18
  interface AssetToEdit {
19
19
  migrationAsset: MigrationAsset;
@@ -27,9 +27,11 @@ export function assetsImporter(data: {
27
27
  readonly importContext: ImportContext;
28
28
  }) {
29
29
  const getAssetsToUpload = (): readonly MigrationAsset[] => {
30
- return data.importContext.categorizedImportData.assets.filter((asset) => {
31
- return data.importContext.getAssetStateInTargetEnvironment(asset.codename).state === 'doesNotExists';
32
- });
30
+ return data.importContext.categorizedImportData.assets
31
+ .filter((asset) => {
32
+ return data.importContext.getAssetStateInTargetEnvironment(asset.codename).state === 'doesNotExists';
33
+ })
34
+ .map((asset) => ({ ...asset, descriptions: asset.descriptions?.filter(filterInvalidLanguages) }));
33
35
  };
34
36
 
35
37
  const getAssetsToEdit = (): readonly AssetToEdit[] => {
@@ -45,6 +47,7 @@ export function assetsImporter(data: {
45
47
  !shouldUpdateAsset({
46
48
  collections: data.importContext.environmentData.collections,
47
49
  languages: data.importContext.environmentData.languages,
50
+ assetFolders: data.importContext.environmentData.assetFolders,
48
51
  migrationAsset: migrationAsset,
49
52
  targetAsset: assetState.asset
50
53
  })
@@ -53,7 +56,10 @@ export function assetsImporter(data: {
53
56
  }
54
57
 
55
58
  return {
56
- migrationAsset: migrationAsset,
59
+ migrationAsset: {
60
+ ...migrationAsset,
61
+ descriptions: migrationAsset.descriptions?.filter(filterInvalidLanguages)
62
+ },
57
63
  replaceBinaryFile: shouldReplaceBinaryFile({
58
64
  migrationAsset: migrationAsset,
59
65
  targetAsset: assetState.asset
@@ -64,9 +70,7 @@ export function assetsImporter(data: {
64
70
  .filter(isNotUndefined);
65
71
  };
66
72
 
67
- const editAssets = async (
68
- assetsToEdit: readonly AssetToEdit[]
69
- ): Promise<readonly Readonly<AssetModels.Asset>[]> => {
73
+ const editAssets = async (assetsToEdit: readonly AssetToEdit[]): Promise<readonly Readonly<AssetModels.Asset>[]> => {
70
74
  data.logger.log({
71
75
  type: 'upsert',
72
76
  message: `Upserting '${chalk.yellow(assetsToEdit.length.toString())}' assets`
@@ -100,11 +104,10 @@ export function assetsImporter(data: {
100
104
  .withData(() => {
101
105
  return {
102
106
  title: assetEditRequest.migrationAsset.title,
103
- descriptions: mapAssetDescriptions(
104
- assetEditRequest.migrationAsset.descriptions
105
- ),
107
+ descriptions: mapAssetDescriptions(assetEditRequest.migrationAsset.descriptions),
106
108
  collection: mapAssetCollection(assetEditRequest.migrationAsset.collection),
107
- file_reference: uploadedBinaryFile ?? undefined
109
+ file_reference: uploadedBinaryFile ?? undefined,
110
+ folder: mapAssetFolder(assetEditRequest.migrationAsset.folder)
108
111
  };
109
112
  })
110
113
  .toPromise()
@@ -119,6 +122,10 @@ export function assetsImporter(data: {
119
122
  });
120
123
  };
121
124
 
125
+ const filterInvalidLanguages = (description: MigrationAssetDescription): Readonly<LanguageModels.LanguageModel> | undefined => {
126
+ return data.importContext.environmentData.languages.find((lang) => lang.codename === description.language.codename);
127
+ };
128
+
122
129
  const mapAssetCollection = (
123
130
  migrationCollection: MigrationReference | undefined
124
131
  ): Readonly<AssetModels.IAssetCollectionReferenceObject> | undefined => {
@@ -131,6 +138,16 @@ export function assetsImporter(data: {
131
138
  : undefined;
132
139
  };
133
140
 
141
+ const mapAssetFolder = (
142
+ migrationFolder: MigrationReference | undefined
143
+ ): Readonly<SharedContracts.IReferenceObjectContract> | undefined => {
144
+ return migrationFolder
145
+ ? {
146
+ codename: migrationFolder.codename
147
+ }
148
+ : undefined;
149
+ };
150
+
134
151
  const mapAssetDescriptions = (
135
152
  migrationDescription: readonly MigrationAssetDescription[] | undefined
136
153
  ): Readonly<AssetModels.IAssetFileDescription>[] => {
@@ -172,9 +189,7 @@ export function assetsImporter(data: {
172
189
  });
173
190
  };
174
191
 
175
- const uploadAssetsAsync = async (
176
- assetsToUpload: readonly MigrationAsset[]
177
- ): Promise<readonly Readonly<AssetModels.Asset>[]> => {
192
+ const uploadAssetsAsync = async (assetsToUpload: readonly MigrationAsset[]): Promise<readonly Readonly<AssetModels.Asset>[]> => {
178
193
  data.logger.log({
179
194
  type: 'upload',
180
195
  message: `Uploading '${chalk.yellow(assetsToUpload.length.toString())}' assets`
@@ -211,7 +226,8 @@ export function assetsImporter(data: {
211
226
  title: migrationAsset.title,
212
227
  external_id: assetStateInTargetEnv.externalIdToUse,
213
228
  collection: mapAssetCollection(migrationAsset.collection),
214
- descriptions: mapAssetDescriptions(migrationAsset.descriptions)
229
+ descriptions: mapAssetDescriptions(migrationAsset.descriptions),
230
+ folder: mapAssetFolder(migrationAsset.folder)
215
231
  };
216
232
  return assetRequestData;
217
233
  })