@kontent-ai/migration-toolkit 2.2.2 → 2.4.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 (87) hide show
  1. package/README.md +10 -9
  2. package/dist/es2022/core/logs/loggers.js +4 -1
  3. package/dist/es2022/core/logs/loggers.js.map +1 -1
  4. package/dist/es2022/core/models/core.models.d.ts +12 -0
  5. package/dist/es2022/core/models/log.models.d.ts +1 -1
  6. package/dist/es2022/core/models/migration.schema.d.ts +12 -12
  7. package/dist/es2022/core/models/migration.schema.js +2 -2
  8. package/dist/es2022/core/models/migration.schema.js.map +1 -1
  9. package/dist/es2022/core/utils/binary-data.utils.d.ts +4 -4
  10. package/dist/es2022/core/utils/binary-data.utils.js +2 -2
  11. package/dist/es2022/core/utils/binary-data.utils.js.map +1 -1
  12. package/dist/es2022/core/utils/error.utils.js +1 -1
  13. package/dist/es2022/core/utils/error.utils.js.map +1 -1
  14. package/dist/es2022/core/utils/global.utils.d.ts +3 -3
  15. package/dist/es2022/core/utils/global.utils.js +1 -1
  16. package/dist/es2022/core/utils/global.utils.js.map +1 -1
  17. package/dist/es2022/core/utils/http.utils.js +0 -3
  18. package/dist/es2022/core/utils/http.utils.js.map +1 -1
  19. package/dist/es2022/core/utils/processing-utils.d.ts +3 -3
  20. package/dist/es2022/core/utils/processing-utils.js +31 -4
  21. package/dist/es2022/core/utils/processing-utils.js.map +1 -1
  22. package/dist/es2022/export/context/export-context-fetcher.js +13 -7
  23. package/dist/es2022/export/context/export-context-fetcher.js.map +1 -1
  24. package/dist/es2022/export/export-manager.js +5 -4
  25. package/dist/es2022/export/export-manager.js.map +1 -1
  26. package/dist/es2022/file/file-manager.d.ts +3 -2
  27. package/dist/es2022/file/file-manager.js.map +1 -1
  28. package/dist/es2022/import/context/import-context-fetcher.js +14 -8
  29. package/dist/es2022/import/context/import-context-fetcher.js.map +1 -1
  30. package/dist/es2022/import/import-manager.d.ts +4 -0
  31. package/dist/es2022/import/import-manager.js +133 -4
  32. package/dist/es2022/import/import-manager.js.map +1 -1
  33. package/dist/es2022/import/import.models.d.ts +15 -5
  34. package/dist/es2022/import/importers/assets-importer.js.map +1 -1
  35. package/dist/es2022/import/importers/content-items-importer.d.ts +3 -3
  36. package/dist/es2022/import/importers/content-items-importer.js +20 -14
  37. package/dist/es2022/import/importers/content-items-importer.js.map +1 -1
  38. package/dist/es2022/import/importers/language-variant-importer.d.ts +4 -4
  39. package/dist/es2022/import/importers/language-variant-importer.js +4 -4
  40. package/dist/es2022/import/importers/language-variant-importer.js.map +1 -1
  41. package/dist/es2022/metadata.js +2 -2
  42. package/dist/es2022/node/cli/actions/import-action.js +3 -1
  43. package/dist/es2022/node/cli/actions/import-action.js.map +1 -1
  44. package/dist/es2022/toolkit/file.js +2 -2
  45. package/dist/es2022/toolkit/file.js.map +1 -1
  46. package/dist/es2022/toolkit/import.js +14 -2
  47. package/dist/es2022/toolkit/import.js.map +1 -1
  48. package/dist/es2022/translation/extraction/items-extraction.processor.d.ts +1 -1
  49. package/dist/es2022/translation/extraction/items-extraction.processor.js.map +1 -1
  50. package/dist/es2022/translation/helpers/rich-text.processor.d.ts +2 -2
  51. package/dist/es2022/translation/helpers/rich-text.processor.js +1 -1
  52. package/dist/es2022/translation/helpers/rich-text.processor.js.map +1 -1
  53. package/dist/es2022/zip/zip-manager.d.ts +2 -2
  54. package/dist/es2022/zip/zip-manager.js.map +1 -1
  55. package/dist/es2022/zip/zip-packager.js +3 -4
  56. package/dist/es2022/zip/zip-packager.js.map +1 -1
  57. package/dist/es2022/zip/zip-transformer.js.map +1 -1
  58. package/dist/es2022/zip/zip.models.d.ts +4 -4
  59. package/lib/core/logs/loggers.ts +5 -1
  60. package/lib/core/models/core.models.ts +16 -0
  61. package/lib/core/models/log.models.ts +1 -1
  62. package/lib/core/models/migration.schema.ts +2 -2
  63. package/lib/core/utils/binary-data.utils.ts +6 -6
  64. package/lib/core/utils/error.utils.ts +2 -2
  65. package/lib/core/utils/global.utils.ts +8 -5
  66. package/lib/core/utils/http.utils.ts +1 -4
  67. package/lib/core/utils/processing-utils.ts +48 -18
  68. package/lib/export/context/export-context-fetcher.ts +67 -60
  69. package/lib/export/export-manager.ts +58 -56
  70. package/lib/file/file-manager.ts +4 -3
  71. package/lib/import/context/import-context-fetcher.ts +18 -13
  72. package/lib/import/import-manager.ts +174 -10
  73. package/lib/import/import.models.ts +20 -4
  74. package/lib/import/importers/assets-importer.ts +3 -9
  75. package/lib/import/importers/content-items-importer.ts +27 -31
  76. package/lib/import/importers/language-variant-importer.ts +32 -34
  77. package/lib/metadata.ts +2 -2
  78. package/lib/node/cli/actions/import-action.ts +3 -1
  79. package/lib/toolkit/file.ts +2 -2
  80. package/lib/toolkit/import.ts +16 -2
  81. package/lib/translation/extraction/items-extraction.processor.ts +3 -3
  82. package/lib/translation/helpers/rich-text.processor.ts +4 -4
  83. package/lib/zip/zip-manager.ts +2 -2
  84. package/lib/zip/zip-packager.ts +3 -4
  85. package/lib/zip/zip-transformer.ts +1 -1
  86. package/lib/zip/zip.models.ts +3 -3
  87. package/package.json +19 -20
@@ -1,12 +1,42 @@
1
- import { ContentItemModels, LanguageVariantModels, ManagementClient } from '@kontent-ai/management-sdk';
2
-
3
- import { defaultExternalIdGenerator, getDefaultLogger, getMigrationManagementClient, Logger } from '../core/index.js';
1
+ import { ManagementClient } from '@kontent-ai/management-sdk';
2
+ import chalk from 'chalk';
3
+ import { defaultExternalIdGenerator, extractErrorData, getDefaultLogger, getMigrationManagementClient, Logger } from '../core/index.js';
4
4
  import { importContextFetcherAsync } from './context/import-context-fetcher.js';
5
- import { ImportConfig, ImportContext, ImportResult } from './import.models.js';
5
+ import { ImportConfig, ImportContext, ImportedItem, ImportedLanguageVariant, ImportResult } from './import.models.js';
6
6
  import { assetsImporter } from './importers/assets-importer.js';
7
7
  import { contentItemsImporter } from './importers/content-items-importer.js';
8
8
  import { languageVariantImporter } from './importers/language-variant-importer.js';
9
9
 
10
+ const reportFilename: string = `import-report.json`;
11
+
12
+ type ReportResult = {
13
+ readonly errorsCount: number;
14
+ readonly assets: {
15
+ readonly count: number;
16
+ readonly successful: Array<{ readonly codename: string }>;
17
+ readonly failed: Array<{ readonly codename: string; readonly error: string }>;
18
+ };
19
+ readonly languageVariants: {
20
+ readonly count: number;
21
+ readonly successful: Array<{
22
+ readonly codename: string;
23
+ readonly language: { readonly codename: string };
24
+ readonly type: { readonly codename: string };
25
+ }>;
26
+ readonly failed: Array<{
27
+ readonly codename: string;
28
+ readonly language: { readonly codename: string };
29
+ readonly type: { readonly codename: string };
30
+ readonly error: string;
31
+ }>;
32
+ };
33
+ readonly contentItems: {
34
+ readonly count: number;
35
+ readonly successful: Array<{ readonly codename: string }>;
36
+ readonly failed: Array<{ readonly codename: string; readonly type: { readonly codename: string }; readonly error: string }>;
37
+ };
38
+ };
39
+
10
40
  export function importManager(config: ImportConfig) {
11
41
  const logger: Logger = config.logger ?? getDefaultLogger();
12
42
  const targetEnvironmentClient: Readonly<ManagementClient> = getMigrationManagementClient(config);
@@ -29,7 +59,8 @@ export function importManager(config: ImportConfig) {
29
59
  logger: logger
30
60
  }).importAsync();
31
61
  };
32
- const importContentItemsAsync = async (importContext: ImportContext): Promise<readonly Readonly<ContentItemModels.ContentItem>[]> => {
62
+
63
+ const importContentItemsAsync = async (importContext: ImportContext): Promise<readonly ImportedItem[]> => {
33
64
  if (!importContext.categorizedImportData.contentItems.length) {
34
65
  logger.log({
35
66
  type: 'info',
@@ -46,8 +77,8 @@ export function importManager(config: ImportConfig) {
46
77
 
47
78
  const importLanguageVariantsAsync = async (
48
79
  importContext: ImportContext,
49
- contentItems: readonly Readonly<ContentItemModels.ContentItem>[]
50
- ): Promise<readonly Readonly<LanguageVariantModels.ContentItemLanguageVariant>[]> => {
80
+ contentItems: readonly ImportedItem[]
81
+ ): Promise<readonly ImportedLanguageVariant[]> => {
51
82
  if (!importContext.categorizedImportData.contentItems.length) {
52
83
  logger.log({
53
84
  type: 'info',
@@ -63,6 +94,116 @@ export function importManager(config: ImportConfig) {
63
94
  }).importAsync();
64
95
  };
65
96
 
97
+ const getReportResult = (importResult: ImportResult): ReportResult => {
98
+ return {
99
+ errorsCount:
100
+ importResult.editedAssets.filter((m) => m.state === 'error').length +
101
+ importResult.uploadedAssets.filter((m) => m.state === 'error').length +
102
+ importResult.contentItems.filter((m) => m.state === 'error').length +
103
+ importResult.languageVariants.filter((m) => m.state === 'error').length,
104
+ assets: {
105
+ count: importResult.uploadedAssets.length + importResult.editedAssets.length,
106
+ successful: [
107
+ ...importResult.uploadedAssets.filter((m) => m.state === 'valid').map((m) => m.inputItem.codename),
108
+ ...importResult.editedAssets.filter((m) => m.state === 'valid').map((m) => m.inputItem.migrationAsset.codename)
109
+ ].map((m) => {
110
+ return {
111
+ codename: m
112
+ };
113
+ }),
114
+ failed: [
115
+ ...importResult.uploadedAssets
116
+ .filter((m) => m.state === 'error')
117
+ .map((m) => {
118
+ return {
119
+ codename: m.inputItem.codename,
120
+ error: extractErrorData(m.state === 'error').message
121
+ };
122
+ }),
123
+ ...importResult.editedAssets
124
+ .filter((m) => m.state === 'error')
125
+ .map((m) => {
126
+ return {
127
+ codename: m.inputItem.migrationAsset.codename,
128
+ error: extractErrorData(m.state === 'error').message
129
+ };
130
+ })
131
+ ]
132
+ },
133
+ contentItems: {
134
+ count: importResult.contentItems.length,
135
+ successful: importResult.contentItems
136
+ .filter((m) => m.state === 'valid')
137
+ .map((m) => {
138
+ return {
139
+ codename: m.inputItem.system.codename
140
+ };
141
+ }),
142
+ failed: importResult.contentItems
143
+ .filter((m) => m.state === 'error')
144
+ .map((m) => {
145
+ return {
146
+ codename: m.inputItem.system.codename,
147
+ type: m.inputItem.system.type,
148
+ error: extractErrorData(m.state === 'error').message
149
+ };
150
+ })
151
+ },
152
+ languageVariants: {
153
+ count: importResult.languageVariants.length,
154
+ successful: importResult.languageVariants
155
+ .filter((m) => m.state === 'valid')
156
+ .map((m) => {
157
+ return {
158
+ codename: m.inputItem.system.codename,
159
+ language: m.inputItem.system.language,
160
+ type: m.inputItem.system.type
161
+ };
162
+ }),
163
+ failed: importResult.languageVariants
164
+ .filter((m) => m.state === 'error')
165
+ .map((m) => {
166
+ return {
167
+ codename: m.inputItem.system.codename,
168
+ language: m.inputItem.system.language,
169
+ type: m.inputItem.system.type,
170
+ error: extractErrorData(m.state === 'error').message
171
+ };
172
+ })
173
+ }
174
+ };
175
+ };
176
+
177
+ const printReportToConsole = (reportResult: ReportResult, logger: Logger): void => {
178
+ const errors = [
179
+ ...reportResult.assets.failed.map((m) => [
180
+ `Object type: ${chalk.yellow('Asset')}`,
181
+ `Codename: ${chalk.yellow(m.codename)}`,
182
+ `${chalk.red(m.error)}`
183
+ ]),
184
+ ...reportResult.contentItems.failed.map((m) => [
185
+ `Object type: ${chalk.yellow('Content item')}`,
186
+ `Codename:${chalk.yellow(m.codename)}`,
187
+ `Content Type: ${chalk.yellow(m.type.codename)}`,
188
+ `${chalk.red(m.error)}`
189
+ ]),
190
+ ...reportResult.languageVariants.failed.map((m) => [
191
+ `Object type: ${chalk.yellow('Language variant')}`,
192
+ `Codename: ${chalk.yellow(m.codename)}`,
193
+ `Language: ${chalk.yellow(m.language.codename)}`,
194
+ `Content Type: ${chalk.yellow(m.type.codename)}`,
195
+ `${chalk.red(m.error)}`
196
+ ])
197
+ ];
198
+
199
+ errors.forEach((error, index) => {
200
+ logger.log({ message: `${chalk.red(`\nError #${index + 1}`)}` });
201
+ error.forEach((m) => {
202
+ logger.log({ message: m });
203
+ });
204
+ });
205
+ };
206
+
66
207
  return {
67
208
  async importAsync(): Promise<ImportResult> {
68
209
  const importContext = await (
@@ -83,17 +224,40 @@ export function importManager(config: ImportConfig) {
83
224
  // #3 Language variants
84
225
  const languageVariants = await importLanguageVariantsAsync(importContext, contentItems);
85
226
 
86
- logger.log({
87
- type: 'completed',
88
- message: `Finished import`
227
+ const reportResult = getReportResult({
228
+ contentItems,
229
+ editedAssets,
230
+ languageVariants,
231
+ uploadedAssets
89
232
  });
90
233
 
234
+ if (reportResult.errorsCount) {
235
+ printReportToConsole(reportResult, logger);
236
+ logger.log({
237
+ type: 'completed',
238
+ message: `Finished import with '${chalk.red(reportResult.errorsCount)}' ${
239
+ reportResult.errorsCount === 1 ? 'error' : 'errors'
240
+ }`
241
+ });
242
+ } else {
243
+ logger.log({
244
+ type: 'completed',
245
+ message: `Finished import`
246
+ });
247
+ }
248
+
91
249
  return {
92
250
  editedAssets,
93
251
  uploadedAssets,
94
252
  contentItems,
95
253
  languageVariants
96
254
  };
255
+ },
256
+ getReportFile(importResult: ImportResult): { readonly filename: string; readonly content: string } {
257
+ return {
258
+ filename: reportFilename,
259
+ content: JSON.stringify(getReportResult(importResult))
260
+ };
97
261
  }
98
262
  };
99
263
  }
@@ -14,6 +14,7 @@ import {
14
14
  ExternalIdGenerator,
15
15
  FlattenedContentType,
16
16
  FlattenedContentTypeElement,
17
+ ItemProcessingResult,
17
18
  ItemStateInTargetEnvironmentByCodename,
18
19
  LanguageVariantStateInTargetEnvironmentByCodename,
19
20
  Logger,
@@ -75,12 +76,27 @@ export type ImportTransformFunc = (data: {
75
76
  export interface ImportConfig extends ManagementClientConfig {
76
77
  readonly data: MigrationData;
77
78
  readonly externalIdGenerator?: ExternalIdGenerator;
79
+ readonly createReportFile?: boolean;
78
80
  readonly logger?: Logger;
79
81
  }
80
82
 
83
+ export interface AssetToEdit {
84
+ readonly migrationAsset: MigrationAsset;
85
+ readonly targetAsset: Readonly<AssetModels.Asset>;
86
+ readonly replaceBinaryFile: boolean;
87
+ }
88
+
89
+ export type ImportedItem = ItemProcessingResult<MigrationItem, Readonly<ContentItemModels.ContentItem>>;
90
+ export type ImportedLanguageVariant = ItemProcessingResult<
91
+ MigrationItem,
92
+ readonly Readonly<LanguageVariantModels.ContentItemLanguageVariant>[]
93
+ >;
94
+ export type EditedAsset = ItemProcessingResult<AssetToEdit, Readonly<AssetModels.Asset>>;
95
+ export type ImportedAsset = ItemProcessingResult<MigrationAsset, Readonly<AssetModels.Asset>>;
96
+
81
97
  export interface ImportResult {
82
- readonly uploadedAssets: readonly Readonly<AssetModels.Asset>[];
83
- readonly editedAssets: readonly Readonly<AssetModels.Asset>[];
84
- readonly contentItems: readonly Readonly<ContentItemModels.ContentItem>[];
85
- readonly languageVariants: readonly Readonly<LanguageVariantModels.ContentItemLanguageVariant>[];
98
+ readonly uploadedAssets: readonly ImportedAsset[];
99
+ readonly editedAssets: readonly EditedAsset[];
100
+ readonly contentItems: readonly ImportedItem[];
101
+ readonly languageVariants: readonly ImportedLanguageVariant[];
86
102
  }
@@ -13,13 +13,7 @@ import {
13
13
  runMapiRequestAsync
14
14
  } from '../../core/index.js';
15
15
  import { shouldReplaceBinaryFile, shouldUpdateAsset } from '../comparers/asset-comparer.js';
16
- import { ImportContext, ImportResult } from '../import.models.js';
17
-
18
- interface AssetToEdit {
19
- migrationAsset: MigrationAsset;
20
- targetAsset: Readonly<AssetModels.Asset>;
21
- replaceBinaryFile: boolean;
22
- }
16
+ import { AssetToEdit, EditedAsset, ImportContext, ImportedAsset, ImportResult } from '../import.models.js';
23
17
 
24
18
  export function assetsImporter(data: {
25
19
  readonly logger: Logger;
@@ -70,7 +64,7 @@ export function assetsImporter(data: {
70
64
  .filter(isNotUndefined);
71
65
  };
72
66
 
73
- const editAssets = async (assetsToEdit: readonly AssetToEdit[]): Promise<readonly Readonly<AssetModels.Asset>[]> => {
67
+ const editAssets = async (assetsToEdit: readonly AssetToEdit[]): Promise<readonly EditedAsset[]> => {
74
68
  data.logger.log({
75
69
  type: 'upsert',
76
70
  message: `Upserting '${chalk.yellow(assetsToEdit.length.toString())}' assets`
@@ -189,7 +183,7 @@ export function assetsImporter(data: {
189
183
  });
190
184
  };
191
185
 
192
- const uploadAssetsAsync = async (assetsToUpload: readonly MigrationAsset[]): Promise<readonly Readonly<AssetModels.Asset>[]> => {
186
+ const uploadAssetsAsync = async (assetsToUpload: readonly MigrationAsset[]): Promise<readonly ImportedAsset[]> => {
193
187
  data.logger.log({
194
188
  type: 'upload',
195
189
  message: `Uploading '${chalk.yellow(assetsToUpload.length.toString())}' assets`
@@ -1,43 +1,28 @@
1
1
  import { ContentItemModels, ManagementClient } from '@kontent-ai/management-sdk';
2
- import {
3
- Logger,
4
- processItemsAsync,
5
- runMapiRequestAsync,
6
- MigrationItem,
7
- LogSpinnerData,
8
- findRequired
9
- } from '../../core/index.js';
10
2
  import chalk from 'chalk';
11
- import { ImportContext } from '../import.models.js';
3
+ import { LogSpinnerData, Logger, MigrationItem, findRequired, processItemsAsync, runMapiRequestAsync } from '../../core/index.js';
4
+ import { ImportContext, ImportedItem } from '../import.models.js';
12
5
 
13
6
  export function contentItemsImporter(data: {
14
7
  readonly logger: Logger;
15
8
  readonly client: Readonly<ManagementClient>;
16
9
  readonly importContext: ImportContext;
17
10
  }) {
18
- const shouldUpdateContentItem = (
19
- migrationContentItem: MigrationItem,
20
- contentItem: Readonly<ContentItemModels.ContentItem>
21
- ): boolean => {
11
+ const shouldUpdateContentItem = (migrationItem: MigrationItem, contentItem: Readonly<ContentItemModels.ContentItem>): boolean => {
22
12
  const collection = findRequired(
23
13
  data.importContext.environmentData.collections,
24
- (collection) => collection.codename === migrationContentItem.system.collection.codename,
25
- `Invalid collection '${migrationContentItem.system.collection.codename}'`
14
+ (collection) => collection.id === contentItem.collection.id,
15
+ `Invalid collection with id '${contentItem.collection.id}' for content item '${contentItem.codename}'`
26
16
  );
27
17
 
28
- return (
29
- migrationContentItem.system.name !== contentItem.name ||
30
- migrationContentItem.system.collection.codename !== collection.codename
31
- );
18
+ return migrationItem.system.name !== contentItem.name || migrationItem.system.collection.codename !== collection.codename;
32
19
  };
33
20
 
34
21
  const prepareContentItemAsync = async (
35
22
  logSpinner: LogSpinnerData,
36
- migrationContentItem: MigrationItem
23
+ migrationItem: MigrationItem
37
24
  ): Promise<{ contentItem: Readonly<ContentItemModels.ContentItem>; status: 'created' | 'itemAlreadyExists' }> => {
38
- const itemStateInTargetEnv = data.importContext.getItemStateInTargetEnvironment(
39
- migrationContentItem.system.codename
40
- );
25
+ const itemStateInTargetEnv = data.importContext.getItemStateInTargetEnvironment(migrationItem.system.codename);
41
26
 
42
27
  if (itemStateInTargetEnv.state === 'exists' && itemStateInTargetEnv.item) {
43
28
  return {
@@ -53,14 +38,14 @@ export function contentItemsImporter(data: {
53
38
  await data.client
54
39
  .addContentItem()
55
40
  .withData({
56
- name: migrationContentItem.system.name,
41
+ name: migrationItem.system.name,
57
42
  type: {
58
- codename: migrationContentItem.system.type.codename
43
+ codename: migrationItem.system.type.codename
59
44
  },
60
45
  external_id: itemStateInTargetEnv.externalIdToUse,
61
- codename: migrationContentItem.system.codename,
46
+ codename: migrationItem.system.codename,
62
47
  collection: {
63
- codename: migrationContentItem.system.collection.codename
48
+ codename: migrationItem.system.collection.codename
64
49
  }
65
50
  })
66
51
  .toPromise()
@@ -68,7 +53,7 @@ export function contentItemsImporter(data: {
68
53
  action: 'create',
69
54
  type: 'contentItem',
70
55
  logSpinner: logSpinner,
71
- itemName: `${migrationContentItem.system.codename} (${migrationContentItem.system.language.codename})`
56
+ itemName: `${migrationItem.system.codename} (${migrationItem.system.language.codename})`
72
57
  });
73
58
 
74
59
  return {
@@ -111,8 +96,19 @@ export function contentItemsImporter(data: {
111
96
  return preparedContentItemResult.contentItem;
112
97
  };
113
98
 
114
- const importAsync = async (): Promise<readonly Readonly<ContentItemModels.ContentItem>[]> => {
115
- const contentItemsToImport = data.importContext.categorizedImportData.contentItems;
99
+ const importAsync = async (): Promise<readonly ImportedItem[]> => {
100
+ // Only import unique content items based on their codename. The input may contain items in various language variants which share
101
+ // the same underlying content item.
102
+ const contentItemsToImport = data.importContext.categorizedImportData.contentItems.reduce<Readonly<MigrationItem[]>>(
103
+ (filteredItems, item) => {
104
+ if (filteredItems.some((m) => m.system.codename === item.system.codename)) {
105
+ return filteredItems;
106
+ }
107
+
108
+ return [...filteredItems, item];
109
+ },
110
+ []
111
+ );
116
112
 
117
113
  data.logger.log({
118
114
  type: 'info',
@@ -127,7 +123,7 @@ export function contentItemsImporter(data: {
127
123
  itemInfo: (item) => {
128
124
  return {
129
125
  itemType: 'contentItem',
130
- title: `${item.system.codename} -> ${item.system.language.codename}`
126
+ title: `${item.system.codename} -> ${item.system.type.codename}`
131
127
  };
132
128
  },
133
129
  processAsync: async (item, logSpinner) => {
@@ -1,4 +1,4 @@
1
- import { ContentItemModels, ElementContracts, LanguageVariantModels, ManagementClient, WorkflowModels } from '@kontent-ai/management-sdk';
1
+ import { ElementContracts, LanguageVariantModels, ManagementClient, WorkflowModels } from '@kontent-ai/management-sdk';
2
2
  import chalk from 'chalk';
3
3
  import { match } from 'ts-pattern';
4
4
  import {
@@ -16,13 +16,13 @@ import {
16
16
  workflowHelper
17
17
  } from '../../core/index.js';
18
18
  import { importTransforms } from '../../translation/index.js';
19
- import { ImportContext } from '../import.models.js';
19
+ import { ImportContext, ImportedItem, ImportedLanguageVariant } from '../import.models.js';
20
20
  import { throwErrorForMigrationItem } from '../utils/import.utils.js';
21
21
  import { workflowImporter as workflowImporterInit } from './workflow-importer.js';
22
22
 
23
23
  export function languageVariantImporter(config: {
24
24
  readonly logger: Logger;
25
- readonly preparedContentItems: readonly ContentItemModels.ContentItem[];
25
+ readonly preparedContentItems: readonly ImportedItem[];
26
26
  readonly importContext: ImportContext;
27
27
  readonly client: Readonly<ManagementClient>;
28
28
  }) {
@@ -37,7 +37,7 @@ export function languageVariantImporter(config: {
37
37
  readonly logSpinner: LogSpinnerData;
38
38
  readonly migrationItem: MigrationItem;
39
39
  readonly migrationItemVersion: MigrationItemVersion;
40
- readonly preparedContentItem: Readonly<ContentItemModels.ContentItem>;
40
+ readonly preparedContentItem: ImportedItem;
41
41
  }): Promise<Readonly<LanguageVariantModels.ContentItemLanguageVariant>> => {
42
42
  return await runMapiRequestAsync({
43
43
  logger: config.logger,
@@ -45,7 +45,7 @@ export function languageVariantImporter(config: {
45
45
  return (
46
46
  await config.client
47
47
  .upsertLanguageVariant()
48
- .byItemCodename(data.preparedContentItem.codename)
48
+ .byItemCodename(data.preparedContentItem.inputItem.system.codename)
49
49
  .byLanguageCodename(data.migrationItem.system.language.codename)
50
50
  .withData(() => {
51
51
  return {
@@ -110,7 +110,7 @@ export function languageVariantImporter(config: {
110
110
  readonly logSpinner: LogSpinnerData;
111
111
  readonly migrationItem: MigrationItem;
112
112
  readonly migrationItemVersion: MigrationItemVersion;
113
- readonly preparedContentItem: Readonly<ContentItemModels.ContentItem>;
113
+ readonly preparedContentItem: ImportedItem;
114
114
  readonly createNewVersion?: boolean;
115
115
  }): Promise<Readonly<LanguageVariantModels.ContentItemLanguageVariant>> => {
116
116
  // validate workflow
@@ -158,7 +158,7 @@ export function languageVariantImporter(config: {
158
158
  const importLanguageVariantAsync = async (
159
159
  logSpinner: LogSpinnerData,
160
160
  migrationItem: MigrationItem,
161
- preparedContentItem: Readonly<ContentItemModels.ContentItem>
161
+ preparedContentItem: ImportedItem
162
162
  ): Promise<readonly LanguageVariantModels.ContentItemLanguageVariant[]> => {
163
163
  const { draftVersion, publishedVersion } = categorizeVersions(migrationItem);
164
164
 
@@ -297,7 +297,7 @@ export function languageVariantImporter(config: {
297
297
  return importTransformResult;
298
298
  };
299
299
 
300
- const importAsync = async (): Promise<readonly Readonly<LanguageVariantModels.ContentItemLanguageVariant>[]> => {
300
+ const importAsync = async (): Promise<readonly ImportedLanguageVariant[]> => {
301
301
  config.logger.log({
302
302
  type: 'info',
303
303
  message: `Importing '${chalk.yellow(
@@ -305,32 +305,30 @@ export function languageVariantImporter(config: {
305
305
  )}' language variants`
306
306
  });
307
307
 
308
- return (
309
- await processItemsAsync<MigrationItem, readonly Readonly<LanguageVariantModels.ContentItemLanguageVariant>[]>({
310
- action: 'Importing language variants',
311
- logger: config.logger,
312
- parallelLimit: 1,
313
- items: config.importContext.categorizedImportData.contentItems,
314
- itemInfo: (input) => {
315
- return {
316
- itemType: 'languageVariant',
317
- title: input.system.name,
318
- partA: input.system.language.codename
319
- };
320
- },
321
- processAsync: async (migrationItem, logSpinner) => {
322
- const contentItem = findRequired(
323
- config.preparedContentItems,
324
- (item) => item.codename === migrationItem.system.codename,
325
- `Missing content item with codename '${chalk.red(
326
- migrationItem.system.codename
327
- )}'. Content item should have been prepepared.`
328
- );
329
-
330
- return await importLanguageVariantAsync(logSpinner, migrationItem, contentItem);
331
- }
332
- })
333
- ).flatMap((m) => m.map((s) => s));
308
+ return await processItemsAsync<MigrationItem, readonly Readonly<LanguageVariantModels.ContentItemLanguageVariant>[]>({
309
+ action: 'Importing language variants',
310
+ logger: config.logger,
311
+ parallelLimit: 1,
312
+ items: config.importContext.categorizedImportData.contentItems,
313
+ itemInfo: (input) => {
314
+ return {
315
+ itemType: 'languageVariant',
316
+ title: input.system.name,
317
+ partA: input.system.language.codename
318
+ };
319
+ },
320
+ processAsync: async (migrationItem, logSpinner) => {
321
+ const contentItem = findRequired(
322
+ config.preparedContentItems,
323
+ (item) => item.inputItem.system.codename === migrationItem.system.codename,
324
+ `Missing content item with codename '${chalk.red(
325
+ migrationItem.system.codename
326
+ )}'. Content item should have been prepepared.`
327
+ );
328
+
329
+ return await importLanguageVariantAsync(logSpinner, migrationItem, contentItem);
330
+ }
331
+ });
334
332
  };
335
333
 
336
334
  return {
package/lib/metadata.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  export const libMetadata = {
3
3
  name: '@kontent-ai/migration-toolkit',
4
- timestamp: 'Thu, 07 Nov 2024 07:54:04 GMT',
5
- version: '2.2.2'
4
+ timestamp: 'Thu, 02 Jan 2025 12:45:27 GMT',
5
+ version: '2.4.0'
6
6
  };
@@ -7,6 +7,7 @@ export async function importActionAsync(argsFetcher: CliArgumentsFetcher): Promi
7
7
  const environmentId = argsFetcher.getRequiredArgumentValue('targetEnvironmentId');
8
8
  const apiKey = argsFetcher.getRequiredArgumentValue('targetApiKey');
9
9
  const baseUrl = argsFetcher.getOptionalArgumentValue('baseUrl');
10
+ const createReportFile = argsFetcher.getBooleanArgumentValue('createReportFile', false);
10
11
  const force = argsFetcher.getBooleanArgumentValue('force', false);
11
12
  const filename = argsFetcher.getOptionalArgumentValue('filename') ?? defaultZipFilename;
12
13
 
@@ -27,7 +28,8 @@ export async function importActionAsync(argsFetcher: CliArgumentsFetcher): Promi
27
28
  data: importData,
28
29
  baseUrl: baseUrl,
29
30
  environmentId: environmentId,
30
- apiKey: apiKey
31
+ apiKey: apiKey,
32
+ createReportFile: createReportFile
31
33
  });
32
34
 
33
35
  log.log({ type: 'completed', message: `Import has been successful` });
@@ -1,4 +1,4 @@
1
- import { Buffer } from 'buffer';
1
+ import { Buffer as BufferProxy } from 'buffer';
2
2
  import { defaultZipFilename, executeWithTrackingAsync, getDefaultLogger, Logger, MigrationData } from '../core/index.js';
3
3
  import { fileManager } from '../file/index.js';
4
4
  import { libMetadata } from '../metadata.js';
@@ -33,7 +33,7 @@ export async function storeAsync(config: StoreConfig): Promise<void> {
33
33
  func: async () => {
34
34
  const zipData = await zipManager(logger).createZipAsync(config.data);
35
35
 
36
- if (zipData instanceof Buffer) {
36
+ if (zipData instanceof BufferProxy) {
37
37
  await fileManager(logger).writeFileAsync(filename, zipData);
38
38
  } else {
39
39
  throw Error(`Cannot store '${filename}' on File system because the provided zip is not a Buffer`);
@@ -1,5 +1,7 @@
1
+ import chalk from 'chalk';
2
+ import { writeFile } from 'fs/promises';
1
3
  import { executeWithTrackingAsync } from '../core/index.js';
2
- import { ImportConfig, ImportResult, importManager } from '../import/index.js';
4
+ import { ImportConfig, ImportResult, importManager as _importManager } from '../import/index.js';
3
5
  import { libMetadata } from '../metadata.js';
4
6
 
5
7
  export async function importAsync(config: ImportConfig): Promise<ImportResult> {
@@ -15,7 +17,19 @@ export async function importAsync(config: ImportConfig): Promise<ImportResult> {
15
17
  details: {}
16
18
  },
17
19
  func: async () => {
18
- return await importManager(config).importAsync();
20
+ const importManager = _importManager(config);
21
+ const importResult = await importManager.importAsync();
22
+
23
+ if (config.createReportFile) {
24
+ const reportFile = importManager.getReportFile(importResult);
25
+ await writeFile(reportFile.filename, reportFile.content);
26
+ config.logger?.log({
27
+ type: 'writeFs',
28
+ message: `Report '${chalk.yellow(reportFile.filename)}' was created`
29
+ });
30
+ }
31
+
32
+ return importResult;
19
33
  },
20
34
  logger: config.logger
21
35
  });
@@ -12,7 +12,7 @@ import { GetFlattenedElementByCodenames } from '../../import/index.js';
12
12
  import { richTextProcessor } from '../index.js';
13
13
 
14
14
  export interface ExtractItemById {
15
- readonly elements: Readonly<ElementModels.ContentItemElement>[];
15
+ readonly elements: readonly Readonly<ElementModels.ContentItemElement>[];
16
16
  readonly contentTypeId: string;
17
17
  }
18
18
 
@@ -95,7 +95,7 @@ export function itemsExtractionProcessor() {
95
95
  { itemIds: new Set(), assetIds: new Set() }
96
96
  );
97
97
 
98
- return <ReferencedDataInLanguageVariants>{
98
+ return {
99
99
  itemIds: extractedIds.itemIds,
100
100
  assetIds: extractedIds.assetIds
101
101
  };
@@ -175,7 +175,7 @@ export function itemsExtractionProcessor() {
175
175
  }
176
176
  );
177
177
 
178
- return <ReferencedDataInMigrationItems>{
178
+ return {
179
179
  itemCodenames: extractedCodenames.itemCodenames,
180
180
  assetCodenames: extractedCodenames.assetCodenames
181
181
  };
@@ -1,7 +1,7 @@
1
1
  import { AssetStateInTargetEnvironmentByCodename, ItemStateInTargetEnvironmentByCodename } from 'lib/core/index.js';
2
2
 
3
- type CodenameReplaceFunc<T> = (codename: string) => T;
4
- type IdReplaceFunc = (id: string) => { codename: string };
3
+ export type CodenameReplaceFunc<T> = (codename: string) => T;
4
+ export type IdReplaceFunc = (id: string) => { codename: string };
5
5
 
6
6
  interface ProcessCodenamesResult {
7
7
  readonly codenames: ReadonlySet<string>;
@@ -35,7 +35,7 @@ const rteRegexes = {
35
35
  objectTagRegex: new RegExp(`<object(.+?)</object>`, 'g'),
36
36
  imgTagRegex: new RegExp(`<img(.+?)</img>`, 'g'),
37
37
  figureTagRegex: new RegExp(`<figure(.+?)</figure>`, 'g'),
38
- linkTagRegex: new RegExp(`<a(.+?)</a>`, 'g')
38
+ linkTagRegex: new RegExp(`<a([\\s\\S]*)</a>`, 'gm')
39
39
  },
40
40
  ids: {
41
41
  dataItemIdAttrRegex: new RegExp(`${attributes.ids.itemIdAttributeName}=\\"(.+?)\\"`),
@@ -324,7 +324,7 @@ export function richTextProcessor() {
324
324
  html: richTextHtml
325
325
  };
326
326
  };
327
-
327
+
328
328
  const processLinkAssetCodenames = (
329
329
  richTextHtml: string,
330
330
  replaceFunc?: CodenameReplaceFunc<AssetStateInTargetEnvironmentByCodename>