@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.
- package/README.md +10 -9
- package/dist/es2022/core/logs/loggers.js +4 -1
- package/dist/es2022/core/logs/loggers.js.map +1 -1
- package/dist/es2022/core/models/core.models.d.ts +12 -0
- package/dist/es2022/core/models/log.models.d.ts +1 -1
- package/dist/es2022/core/models/migration.schema.d.ts +12 -12
- package/dist/es2022/core/models/migration.schema.js +2 -2
- package/dist/es2022/core/models/migration.schema.js.map +1 -1
- package/dist/es2022/core/utils/binary-data.utils.d.ts +4 -4
- package/dist/es2022/core/utils/binary-data.utils.js +2 -2
- package/dist/es2022/core/utils/binary-data.utils.js.map +1 -1
- package/dist/es2022/core/utils/error.utils.js +1 -1
- package/dist/es2022/core/utils/error.utils.js.map +1 -1
- package/dist/es2022/core/utils/global.utils.d.ts +3 -3
- package/dist/es2022/core/utils/global.utils.js +1 -1
- package/dist/es2022/core/utils/global.utils.js.map +1 -1
- package/dist/es2022/core/utils/http.utils.js +0 -3
- package/dist/es2022/core/utils/http.utils.js.map +1 -1
- package/dist/es2022/core/utils/processing-utils.d.ts +3 -3
- package/dist/es2022/core/utils/processing-utils.js +31 -4
- package/dist/es2022/core/utils/processing-utils.js.map +1 -1
- package/dist/es2022/export/context/export-context-fetcher.js +13 -7
- package/dist/es2022/export/context/export-context-fetcher.js.map +1 -1
- package/dist/es2022/export/export-manager.js +5 -4
- package/dist/es2022/export/export-manager.js.map +1 -1
- package/dist/es2022/file/file-manager.d.ts +3 -2
- package/dist/es2022/file/file-manager.js.map +1 -1
- package/dist/es2022/import/context/import-context-fetcher.js +14 -8
- package/dist/es2022/import/context/import-context-fetcher.js.map +1 -1
- package/dist/es2022/import/import-manager.d.ts +4 -0
- package/dist/es2022/import/import-manager.js +133 -4
- package/dist/es2022/import/import-manager.js.map +1 -1
- package/dist/es2022/import/import.models.d.ts +15 -5
- package/dist/es2022/import/importers/assets-importer.js.map +1 -1
- package/dist/es2022/import/importers/content-items-importer.d.ts +3 -3
- package/dist/es2022/import/importers/content-items-importer.js +20 -14
- package/dist/es2022/import/importers/content-items-importer.js.map +1 -1
- package/dist/es2022/import/importers/language-variant-importer.d.ts +4 -4
- package/dist/es2022/import/importers/language-variant-importer.js +4 -4
- package/dist/es2022/import/importers/language-variant-importer.js.map +1 -1
- package/dist/es2022/metadata.js +2 -2
- package/dist/es2022/node/cli/actions/import-action.js +3 -1
- package/dist/es2022/node/cli/actions/import-action.js.map +1 -1
- package/dist/es2022/toolkit/file.js +2 -2
- package/dist/es2022/toolkit/file.js.map +1 -1
- package/dist/es2022/toolkit/import.js +14 -2
- package/dist/es2022/toolkit/import.js.map +1 -1
- package/dist/es2022/translation/extraction/items-extraction.processor.d.ts +1 -1
- package/dist/es2022/translation/extraction/items-extraction.processor.js.map +1 -1
- package/dist/es2022/translation/helpers/rich-text.processor.d.ts +2 -2
- package/dist/es2022/translation/helpers/rich-text.processor.js +1 -1
- package/dist/es2022/translation/helpers/rich-text.processor.js.map +1 -1
- package/dist/es2022/zip/zip-manager.d.ts +2 -2
- package/dist/es2022/zip/zip-manager.js.map +1 -1
- package/dist/es2022/zip/zip-packager.js +3 -4
- package/dist/es2022/zip/zip-packager.js.map +1 -1
- package/dist/es2022/zip/zip-transformer.js.map +1 -1
- package/dist/es2022/zip/zip.models.d.ts +4 -4
- package/lib/core/logs/loggers.ts +5 -1
- package/lib/core/models/core.models.ts +16 -0
- package/lib/core/models/log.models.ts +1 -1
- package/lib/core/models/migration.schema.ts +2 -2
- package/lib/core/utils/binary-data.utils.ts +6 -6
- package/lib/core/utils/error.utils.ts +2 -2
- package/lib/core/utils/global.utils.ts +8 -5
- package/lib/core/utils/http.utils.ts +1 -4
- package/lib/core/utils/processing-utils.ts +48 -18
- package/lib/export/context/export-context-fetcher.ts +67 -60
- package/lib/export/export-manager.ts +58 -56
- package/lib/file/file-manager.ts +4 -3
- package/lib/import/context/import-context-fetcher.ts +18 -13
- package/lib/import/import-manager.ts +174 -10
- package/lib/import/import.models.ts +20 -4
- package/lib/import/importers/assets-importer.ts +3 -9
- package/lib/import/importers/content-items-importer.ts +27 -31
- package/lib/import/importers/language-variant-importer.ts +32 -34
- package/lib/metadata.ts +2 -2
- package/lib/node/cli/actions/import-action.ts +3 -1
- package/lib/toolkit/file.ts +2 -2
- package/lib/toolkit/import.ts +16 -2
- package/lib/translation/extraction/items-extraction.processor.ts +3 -3
- package/lib/translation/helpers/rich-text.processor.ts +4 -4
- package/lib/zip/zip-manager.ts +2 -2
- package/lib/zip/zip-packager.ts +3 -4
- package/lib/zip/zip-transformer.ts +1 -1
- package/lib/zip/zip.models.ts +3 -3
- package/package.json +19 -20
|
@@ -1,12 +1,42 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
|
50
|
-
): Promise<readonly
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
83
|
-
readonly editedAssets: readonly
|
|
84
|
-
readonly contentItems: readonly
|
|
85
|
-
readonly languageVariants: readonly
|
|
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
|
|
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
|
|
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 {
|
|
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.
|
|
25
|
-
`Invalid collection '${
|
|
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
|
-
|
|
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:
|
|
41
|
+
name: migrationItem.system.name,
|
|
57
42
|
type: {
|
|
58
|
-
codename:
|
|
43
|
+
codename: migrationItem.system.type.codename
|
|
59
44
|
},
|
|
60
45
|
external_id: itemStateInTargetEnv.externalIdToUse,
|
|
61
|
-
codename:
|
|
46
|
+
codename: migrationItem.system.codename,
|
|
62
47
|
collection: {
|
|
63
|
-
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: `${
|
|
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
|
|
115
|
-
|
|
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.
|
|
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 {
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
@@ -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` });
|
package/lib/toolkit/file.ts
CHANGED
|
@@ -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
|
|
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`);
|
package/lib/toolkit/import.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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>
|