@kontent-ai/migration-toolkit 1.3.0 → 1.5.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 (88) hide show
  1. package/README.md +79 -67
  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.models.d.ts +9 -9
  9. package/dist/es2022/core/models/migration.schema.d.ts +247 -0
  10. package/dist/es2022/core/models/migration.schema.js +12 -2
  11. package/dist/es2022/core/models/migration.schema.js.map +1 -1
  12. package/dist/es2022/core/utils/array.utils.js.map +1 -1
  13. package/dist/es2022/core/utils/binary-data.utils.js +2 -6
  14. package/dist/es2022/core/utils/binary-data.utils.js.map +1 -1
  15. package/dist/es2022/core/utils/confirm.utils.js +10 -14
  16. package/dist/es2022/core/utils/confirm.utils.js.map +1 -1
  17. package/dist/es2022/core/utils/error.utils.js +35 -27
  18. package/dist/es2022/core/utils/error.utils.js.map +1 -1
  19. package/dist/es2022/core/utils/http.utils.js +7 -11
  20. package/dist/es2022/core/utils/http.utils.js.map +1 -1
  21. package/dist/es2022/core/utils/management-client-utils.d.ts +2 -1
  22. package/dist/es2022/core/utils/management-client-utils.js +12 -2
  23. package/dist/es2022/core/utils/management-client-utils.js.map +1 -1
  24. package/dist/es2022/core/utils/run.utils.js +12 -10
  25. package/dist/es2022/core/utils/run.utils.js.map +1 -1
  26. package/dist/es2022/export/context/export-context-fetcher.js +2 -1
  27. package/dist/es2022/export/context/export-context-fetcher.js.map +1 -1
  28. package/dist/es2022/export/export-manager.js +8 -0
  29. package/dist/es2022/export/export-manager.js.map +1 -1
  30. package/dist/es2022/export/export.models.d.ts +2 -1
  31. package/dist/es2022/import/comparers/asset-comparer.d.ts +2 -1
  32. package/dist/es2022/import/comparers/asset-comparer.js +9 -17
  33. package/dist/es2022/import/comparers/asset-comparer.js.map +1 -1
  34. package/dist/es2022/import/context/import-context-fetcher.js +120 -40
  35. package/dist/es2022/import/context/import-context-fetcher.js.map +1 -1
  36. package/dist/es2022/import/import.models.d.ts +2 -1
  37. package/dist/es2022/import/importers/assets-importer.js +12 -2
  38. package/dist/es2022/import/importers/assets-importer.js.map +1 -1
  39. package/dist/es2022/import/importers/language-variant-importer.js +95 -83
  40. package/dist/es2022/import/importers/language-variant-importer.js.map +1 -1
  41. package/dist/es2022/import/importers/workflow-importer.d.ts +41 -4
  42. package/dist/es2022/import/importers/workflow-importer.js +158 -17
  43. package/dist/es2022/import/importers/workflow-importer.js.map +1 -1
  44. package/dist/es2022/metadata.js +2 -2
  45. package/dist/es2022/node/cli/app.js +8 -12
  46. package/dist/es2022/node/cli/app.js.map +1 -1
  47. package/dist/es2022/node/cli/args/args-fetcher.js +9 -10
  48. package/dist/es2022/node/cli/args/args-fetcher.js.map +1 -1
  49. package/dist/es2022/toolkit/migrate.d.ts +1 -3
  50. package/dist/es2022/toolkit/migrate.js.map +1 -1
  51. package/dist/es2022/translation/extraction/items-extraction.processor.js +17 -17
  52. package/dist/es2022/translation/extraction/items-extraction.processor.js.map +1 -1
  53. package/dist/es2022/translation/transforms/export-transforms.js +1 -1
  54. package/dist/es2022/translation/transforms/export-transforms.js.map +1 -1
  55. package/dist/es2022/zip/zip-packager.js +13 -18
  56. package/dist/es2022/zip/zip-packager.js.map +1 -1
  57. package/dist/es2022/zip/zip-transformer.js +3 -1
  58. package/dist/es2022/zip/zip-transformer.js.map +1 -1
  59. package/lib/core/helpers/workflow-helper.ts +8 -8
  60. package/lib/core/logs/loggers.ts +14 -22
  61. package/lib/core/models/core.models.ts +23 -4
  62. package/lib/core/models/migration.models.ts +13 -12
  63. package/lib/core/models/migration.schema.ts +189 -178
  64. package/lib/core/utils/array.utils.ts +2 -10
  65. package/lib/core/utils/binary-data.utils.ts +2 -7
  66. package/lib/core/utils/confirm.utils.ts +18 -24
  67. package/lib/core/utils/error.utils.ts +50 -30
  68. package/lib/core/utils/http.utils.ts +14 -15
  69. package/lib/core/utils/management-client-utils.ts +188 -182
  70. package/lib/core/utils/run.utils.ts +27 -18
  71. package/lib/export/context/export-context-fetcher.ts +2 -1
  72. package/lib/export/export-manager.ts +14 -3
  73. package/lib/export/export.models.ts +3 -1
  74. package/lib/import/comparers/asset-comparer.ts +16 -24
  75. package/lib/import/context/import-context-fetcher.ts +179 -85
  76. package/lib/import/import.models.ts +2 -0
  77. package/lib/import/importers/assets-importer.ts +19 -12
  78. package/lib/import/importers/language-variant-importer.ts +125 -129
  79. package/lib/import/importers/workflow-importer.ts +228 -20
  80. package/lib/metadata.ts +2 -2
  81. package/lib/node/cli/app.ts +11 -12
  82. package/lib/node/cli/args/args-fetcher.ts +12 -14
  83. package/lib/toolkit/migrate.ts +1 -3
  84. package/lib/translation/extraction/items-extraction.processor.ts +93 -93
  85. package/lib/translation/transforms/export-transforms.ts +1 -1
  86. package/lib/zip/zip-packager.ts +26 -28
  87. package/lib/zip/zip-transformer.ts +3 -1
  88. package/package.json +14 -13
@@ -1,182 +1,188 @@
1
- import {
2
- CollectionModels,
3
- ContentTypeModels,
4
- ContentTypeSnippetModels,
5
- EnvironmentModels,
6
- LanguageModels,
7
- ManagementClient,
8
- TaxonomyModels,
9
- WorkflowModels,
10
- createManagementClient
11
- } from '@kontent-ai/management-sdk';
12
- import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
13
- import { runMapiRequestAsync } from './run.utils.js';
14
- import { LogSpinnerData, Logger } from '../models/log.models.js';
15
- import { FlattenedContentType, FlattenedContentTypeElement } from '../models/core.models.js';
16
- import { isNotUndefined } from './global.utils.js';
17
- import chalk from 'chalk';
18
- import { defaultHttpService, defaultRetryStrategy } from './http.utils.js';
19
- import { findRequired } from './array.utils.js';
20
-
21
- export interface ManagementClientConfig {
22
- readonly environmentId: string;
23
- readonly apiKey: string;
24
- readonly retryStrategy?: Readonly<IRetryStrategyOptions>;
25
- readonly baseUrl?: string;
26
- }
27
-
28
- export function getMigrationManagementClient(config: ManagementClientConfig): ManagementClient {
29
- return createManagementClient({
30
- environmentId: config.environmentId,
31
- retryStrategy: config.retryStrategy ?? defaultRetryStrategy,
32
- httpService: defaultHttpService,
33
- apiKey: config.apiKey
34
- });
35
- }
36
-
37
- export function managementClientUtils(client: Readonly<ManagementClient>, logger: Logger) {
38
- const getEnvironmentAsync = async (): Promise<EnvironmentModels.EnvironmentInformationModel> => {
39
- return (await client.environmentInformation().toPromise()).data.project;
40
- };
41
-
42
- const getAllLanguagesAsync = async (
43
- logSpinner: LogSpinnerData
44
- ): Promise<readonly LanguageModels.LanguageModel[]> => {
45
- return await runMapiRequestAsync({
46
- logSpinner: logSpinner,
47
- logger: logger,
48
- func: async () => (await client.listLanguages().toAllPromise()).data.items,
49
- action: 'list',
50
- type: 'language'
51
- });
52
- };
53
-
54
- const getAllCollectionsAsync = async (
55
- logSpinner: LogSpinnerData
56
- ): Promise<readonly CollectionModels.Collection[]> => {
57
- return await runMapiRequestAsync({
58
- logger: logger,
59
- logSpinner: logSpinner,
60
- func: async () => (await client.listCollections().toPromise()).data.collections,
61
- action: 'list',
62
- type: 'collection'
63
- });
64
- };
65
-
66
- const getAllWorkflowsAsync = async (logSpinner: LogSpinnerData): Promise<readonly WorkflowModels.Workflow[]> => {
67
- return await runMapiRequestAsync({
68
- logger: logger,
69
- logSpinner: logSpinner,
70
- func: async () => (await client.listWorkflows().toPromise()).data,
71
- action: 'list',
72
- type: 'workflow'
73
- });
74
- };
75
-
76
- const getAllTaxonomiesAsync = async (logSpinner: LogSpinnerData): Promise<readonly TaxonomyModels.Taxonomy[]> => {
77
- return await runMapiRequestAsync({
78
- logger: logger,
79
- logSpinner: logSpinner,
80
- func: async () => (await client.listTaxonomies().toAllPromise()).data.items,
81
- action: 'list',
82
- type: 'taxonomy'
83
- });
84
- };
85
-
86
- const getContentTypeElements = (
87
- contentType: ContentTypeModels.ContentType,
88
- contentTypeSnippets: readonly ContentTypeSnippetModels.ContentTypeSnippet[]
89
- ): readonly FlattenedContentTypeElement[] => {
90
- return contentType.elements
91
- .flatMap((element) => {
92
- if (!element.codename || !element.id) {
93
- return undefined;
94
- }
95
-
96
- if (element.type === 'guidelines') {
97
- return undefined;
98
- }
99
-
100
- if (element.type === 'snippet') {
101
- const snippetElement = element;
102
-
103
- // replace snippet element with actual elements
104
- const contentTypeSnippet = findRequired(
105
- contentTypeSnippets,
106
- (snippet) => snippet.id === snippetElement.snippet.id,
107
- `Could not find content type snippet for element. This snippet is referenced in type '${chalk.red(
108
- contentType.codename
109
- )}'`
110
- );
111
-
112
- return contentTypeSnippet.elements.map((snippetElement) => {
113
- if (snippetElement.type === 'guidelines' || snippetElement.type === 'snippet') {
114
- return undefined;
115
- }
116
- if (!snippetElement.codename || !snippetElement.id) {
117
- return undefined;
118
- }
119
-
120
- const flattenedElement: FlattenedContentTypeElement = {
121
- codename: snippetElement.codename,
122
- type: snippetElement.type,
123
- id: snippetElement.id,
124
- element: element
125
- };
126
-
127
- return flattenedElement;
128
- });
129
- }
130
- const flattenedElement: FlattenedContentTypeElement = {
131
- codename: element.codename,
132
- id: element.id,
133
- type: element.type,
134
- element: element
135
- };
136
-
137
- return flattenedElement;
138
- })
139
- .filter(isNotUndefined);
140
- };
141
-
142
- const getFlattenedContentTypesAsync = async (
143
- logSpinner: LogSpinnerData
144
- ): Promise<readonly FlattenedContentType[]> => {
145
- const contentTypes = await runMapiRequestAsync({
146
- logger: logger,
147
- logSpinner: logSpinner,
148
- func: async () => (await client.listContentTypes().toAllPromise()).data.items,
149
- action: 'list',
150
- type: 'contentType'
151
- });
152
-
153
- const contentTypeSnippets = await runMapiRequestAsync({
154
- logger: logger,
155
- logSpinner: logSpinner,
156
- func: async () => (await client.listContentTypeSnippets().toAllPromise()).data.items,
157
- action: 'list',
158
- type: 'contentTypeSnippet'
159
- });
160
-
161
- return [
162
- ...contentTypes.map((contentType) => {
163
- const importType: FlattenedContentType = {
164
- contentTypeCodename: contentType.codename,
165
- contentTypeId: contentType.id,
166
- elements: getContentTypeElements(contentType, contentTypeSnippets)
167
- };
168
-
169
- return importType;
170
- })
171
- ];
172
- };
173
-
174
- return {
175
- getAllCollectionsAsync,
176
- getAllLanguagesAsync,
177
- getAllWorkflowsAsync,
178
- getAllTaxonomiesAsync,
179
- getFlattenedContentTypesAsync,
180
- getEnvironmentAsync
181
- };
182
- }
1
+ import {
2
+ AssetFolderModels,
3
+ CollectionModels,
4
+ ContentTypeModels,
5
+ ContentTypeSnippetModels,
6
+ EnvironmentModels,
7
+ LanguageModels,
8
+ ManagementClient,
9
+ TaxonomyModels,
10
+ WorkflowModels,
11
+ createManagementClient
12
+ } from '@kontent-ai/management-sdk';
13
+ import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
14
+ import { runMapiRequestAsync } from './run.utils.js';
15
+ import { LogSpinnerData, Logger } from '../models/log.models.js';
16
+ import { FlattenedContentType, FlattenedContentTypeElement } from '../models/core.models.js';
17
+ import { isNotUndefined } from './global.utils.js';
18
+ import chalk from 'chalk';
19
+ import { defaultHttpService, defaultRetryStrategy } from './http.utils.js';
20
+ import { findRequired } from './array.utils.js';
21
+
22
+ export interface ManagementClientConfig {
23
+ readonly environmentId: string;
24
+ readonly apiKey: string;
25
+ readonly retryStrategy?: Readonly<IRetryStrategyOptions>;
26
+ readonly baseUrl?: string;
27
+ }
28
+
29
+ export function getMigrationManagementClient(config: ManagementClientConfig): ManagementClient {
30
+ return createManagementClient({
31
+ environmentId: config.environmentId,
32
+ retryStrategy: config.retryStrategy ?? defaultRetryStrategy,
33
+ httpService: defaultHttpService,
34
+ apiKey: config.apiKey
35
+ });
36
+ }
37
+
38
+ export function managementClientUtils(client: Readonly<ManagementClient>, logger: Logger) {
39
+ const getEnvironmentAsync = async (): Promise<EnvironmentModels.EnvironmentInformationModel> => {
40
+ return (await client.environmentInformation().toPromise()).data.project;
41
+ };
42
+
43
+ const getAllLanguagesAsync = async (logSpinner: LogSpinnerData): Promise<readonly LanguageModels.LanguageModel[]> => {
44
+ return await runMapiRequestAsync({
45
+ logSpinner: logSpinner,
46
+ logger: logger,
47
+ func: async () => (await client.listLanguages().toAllPromise()).data.items,
48
+ action: 'list',
49
+ type: 'language'
50
+ });
51
+ };
52
+
53
+ const getAllCollectionsAsync = async (logSpinner: LogSpinnerData): Promise<readonly CollectionModels.Collection[]> => {
54
+ return await runMapiRequestAsync({
55
+ logger: logger,
56
+ logSpinner: logSpinner,
57
+ func: async () => (await client.listCollections().toPromise()).data.collections,
58
+ action: 'list',
59
+ type: 'collection'
60
+ });
61
+ };
62
+
63
+ const getAllWorkflowsAsync = async (logSpinner: LogSpinnerData): Promise<readonly WorkflowModels.Workflow[]> => {
64
+ return await runMapiRequestAsync({
65
+ logger: logger,
66
+ logSpinner: logSpinner,
67
+ func: async () => (await client.listWorkflows().toPromise()).data,
68
+ action: 'list',
69
+ type: 'workflow'
70
+ });
71
+ };
72
+
73
+ const getAllTaxonomiesAsync = async (logSpinner: LogSpinnerData): Promise<readonly TaxonomyModels.Taxonomy[]> => {
74
+ return await runMapiRequestAsync({
75
+ logger: logger,
76
+ logSpinner: logSpinner,
77
+ func: async () => (await client.listTaxonomies().toAllPromise()).data.items,
78
+ action: 'list',
79
+ type: 'taxonomy'
80
+ });
81
+ };
82
+
83
+ const getAllAssetFoldersAsync = async (logSpinner: LogSpinnerData): Promise<readonly AssetFolderModels.AssetFolder[]> => {
84
+ return await runMapiRequestAsync({
85
+ logger: logger,
86
+ logSpinner: logSpinner,
87
+ func: async () => (await client.listAssetFolders().toPromise()).data.items,
88
+ action: 'list',
89
+ type: 'assetFolder'
90
+ });
91
+ };
92
+
93
+ const getContentTypeElements = (
94
+ contentType: ContentTypeModels.ContentType,
95
+ contentTypeSnippets: readonly ContentTypeSnippetModels.ContentTypeSnippet[]
96
+ ): readonly FlattenedContentTypeElement[] => {
97
+ return contentType.elements
98
+ .flatMap((element) => {
99
+ if (!element.codename || !element.id) {
100
+ return undefined;
101
+ }
102
+
103
+ if (element.type === 'guidelines') {
104
+ return undefined;
105
+ }
106
+
107
+ if (element.type === 'snippet') {
108
+ const snippetElement = element;
109
+
110
+ // replace snippet element with actual elements
111
+ const contentTypeSnippet = findRequired(
112
+ contentTypeSnippets,
113
+ (snippet) => snippet.id === snippetElement.snippet.id,
114
+ `Could not find content type snippet for element. This snippet is referenced in type '${chalk.red(
115
+ contentType.codename
116
+ )}'`
117
+ );
118
+
119
+ return contentTypeSnippet.elements.map((snippetElement) => {
120
+ if (snippetElement.type === 'guidelines' || snippetElement.type === 'snippet') {
121
+ return undefined;
122
+ }
123
+ if (!snippetElement.codename || !snippetElement.id) {
124
+ return undefined;
125
+ }
126
+
127
+ const flattenedElement: FlattenedContentTypeElement = {
128
+ codename: snippetElement.codename,
129
+ type: snippetElement.type,
130
+ id: snippetElement.id,
131
+ element: snippetElement
132
+ };
133
+
134
+ return flattenedElement;
135
+ });
136
+ }
137
+ const flattenedElement: FlattenedContentTypeElement = {
138
+ codename: element.codename,
139
+ id: element.id,
140
+ type: element.type,
141
+ element: element
142
+ };
143
+
144
+ return flattenedElement;
145
+ })
146
+ .filter(isNotUndefined);
147
+ };
148
+
149
+ const getFlattenedContentTypesAsync = async (logSpinner: LogSpinnerData): Promise<readonly FlattenedContentType[]> => {
150
+ const contentTypes = await runMapiRequestAsync({
151
+ logger: logger,
152
+ logSpinner: logSpinner,
153
+ func: async () => (await client.listContentTypes().toAllPromise()).data.items,
154
+ action: 'list',
155
+ type: 'contentType'
156
+ });
157
+
158
+ const contentTypeSnippets = await runMapiRequestAsync({
159
+ logger: logger,
160
+ logSpinner: logSpinner,
161
+ func: async () => (await client.listContentTypeSnippets().toAllPromise()).data.items,
162
+ action: 'list',
163
+ type: 'contentTypeSnippet'
164
+ });
165
+
166
+ return [
167
+ ...contentTypes.map((contentType) => {
168
+ const importType: FlattenedContentType = {
169
+ contentTypeCodename: contentType.codename,
170
+ contentTypeId: contentType.id,
171
+ elements: getContentTypeElements(contentType, contentTypeSnippets)
172
+ };
173
+
174
+ return importType;
175
+ })
176
+ ];
177
+ };
178
+
179
+ return {
180
+ getAllCollectionsAsync,
181
+ getAllLanguagesAsync,
182
+ getAllWorkflowsAsync,
183
+ getAllTaxonomiesAsync,
184
+ getFlattenedContentTypesAsync,
185
+ getEnvironmentAsync,
186
+ getAllAssetFoldersAsync
187
+ };
188
+ }
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { MapiAction, MapiType } from '../models/core.models.js';
3
3
  import { LogMessage, LogSpinnerData, Logger } from '../models/log.models.js';
4
+ import { match } from 'ts-pattern';
4
5
 
5
6
  export async function runMapiRequestAsync<TResult>(data: {
6
7
  readonly logger: Logger;
@@ -10,24 +11,32 @@ export async function runMapiRequestAsync<TResult>(data: {
10
11
  readonly func: () => Promise<TResult>;
11
12
  readonly itemName?: string;
12
13
  }): Promise<TResult> {
13
- let logData: LogMessage | undefined;
14
-
15
- if (data.action === 'list') {
16
- logData = {
17
- message: `Fetching objects of type '${chalk.yellow(data.type)}'`,
18
- type: data.action
19
- };
20
- } else if (data.itemName) {
21
- logData = {
22
- message: `${data.type} -> ${data.itemName}`,
23
- type: data.action
24
- };
25
- } else {
26
- logData = {
27
- message: `${data.type}`,
28
- type: data.action
29
- };
30
- }
14
+ const logData: LogMessage = match(data)
15
+ .returnType<LogMessage>()
16
+ .when(
17
+ (data) => data.action === 'list',
18
+ (data) => {
19
+ return {
20
+ message: `Fetching objects of type '${chalk.yellow(data.type)}'`,
21
+ type: data.action
22
+ };
23
+ }
24
+ )
25
+ .when(
26
+ (data) => data.itemName,
27
+ (data) => {
28
+ return {
29
+ message: `${data.type} -> ${data.itemName}`,
30
+ type: data.action
31
+ };
32
+ }
33
+ )
34
+ .otherwise(() => {
35
+ return {
36
+ message: `${data.type}`,
37
+ type: data.action
38
+ };
39
+ });
31
40
 
32
41
  logSpinnerOrDefault({
33
42
  logSpinner: data.logSpinner,
@@ -44,7 +44,8 @@ export async function exportContextFetcherAsync(config: DefaultExportContextConf
44
44
  contentTypes: await mapiUtils.getFlattenedContentTypesAsync(spinnerData),
45
45
  languages: await mapiUtils.getAllLanguagesAsync(spinnerData),
46
46
  workflows: await mapiUtils.getAllWorkflowsAsync(spinnerData),
47
- taxonomies: await mapiUtils.getAllTaxonomiesAsync(spinnerData)
47
+ taxonomies: await mapiUtils.getAllTaxonomiesAsync(spinnerData),
48
+ assetFolders: await mapiUtils.getAllAssetFoldersAsync(spinnerData)
48
49
  };
49
50
 
50
51
  spinnerData({ type: 'info', message: `Environmental data loaded` });
@@ -1,6 +1,6 @@
1
1
  import { ExportContext, ExportConfig, ExportItem } from './export.models.js';
2
2
  import chalk from 'chalk';
3
- import { AssetModels, CollectionModels, ElementModels } from '@kontent-ai/management-sdk';
3
+ import { AssetFolderModels, AssetModels, CollectionModels, ElementModels } from '@kontent-ai/management-sdk';
4
4
  import {
5
5
  MigrationAsset,
6
6
  MigrationItem,
@@ -19,7 +19,8 @@ import {
19
19
  findRequired,
20
20
  Writeable,
21
21
  MigrationItemsSchema,
22
- MigrationAssetsSchema
22
+ MigrationAssetsSchema,
23
+ MigrationItemVersion
23
24
  } from '../core/index.js';
24
25
  import { exportTransforms } from '../translation/index.js';
25
26
  import { exportContextFetcherAsync } from './context/export-context-fetcher.js';
@@ -45,8 +46,14 @@ export function exportManager(config: ExportConfig) {
45
46
  }
46
47
  },
47
48
  versions: exportItem.versions.map((version) => {
48
- return {
49
+ return <MigrationItemVersion>{
49
50
  elements: getMigrationElements(context, exportItem.contentType, version.languageVariant.elements),
51
+ schedule: {
52
+ publish_time: version.languageVariant.schedule.publishTime ?? undefined,
53
+ publish_display_timezone: version.languageVariant.schedule.publishDisplayTimezone ?? undefined,
54
+ unpublish_display_timezone: version.languageVariant.schedule.unpublishDisplayTimezone ?? undefined,
55
+ unpublish_time: version.languageVariant.schedule.unpublishTime ?? undefined
56
+ },
50
57
  workflow_step: {
51
58
  codename: version.workflowStepCodename
52
59
  }
@@ -182,6 +189,9 @@ export function exportManager(config: ExportConfig) {
182
189
  const assetCollection: Readonly<CollectionModels.Collection> | undefined = context.environmentData.collections.find(
183
190
  (m) => m.id === asset.collection?.reference?.id
184
191
  );
192
+ const assetFolder: Readonly<AssetFolderModels.AssetFolder> | undefined = context.environmentData.assetFolders.find(
193
+ (m) => m.id === asset.folder?.id
194
+ );
185
195
 
186
196
  logSpinner({
187
197
  type: 'download',
@@ -196,6 +206,7 @@ export function exportManager(config: ExportConfig) {
196
206
  codename: asset.codename,
197
207
  binaryData: binaryData.data,
198
208
  collection: assetCollection ? { codename: assetCollection.codename } : undefined,
209
+ folder: assetFolder ? { codename: assetFolder.codename } : undefined,
199
210
  descriptions: asset.descriptions.map((description) => {
200
211
  const language = findRequired(
201
212
  context.environmentData.languages,
@@ -18,13 +18,15 @@ import {
18
18
  LanguageModels,
19
19
  TaxonomyModels,
20
20
  ManagementClient,
21
- SharedModels
21
+ SharedModels,
22
+ AssetFolderModels
22
23
  } from '@kontent-ai/management-sdk';
23
24
 
24
25
  export interface ExportContextEnvironmentData {
25
26
  readonly languages: readonly Readonly<LanguageModels.LanguageModel>[];
26
27
  readonly contentTypes: readonly Readonly<FlattenedContentType>[];
27
28
  readonly collections: readonly Readonly<CollectionModels.Collection>[];
29
+ readonly assetFolders: readonly Readonly<AssetFolderModels.AssetFolder>[];
28
30
  readonly workflows: readonly Readonly<WorkflowModels.Workflow>[];
29
31
  readonly taxonomies: readonly Readonly<TaxonomyModels.Taxonomy>[];
30
32
  }
@@ -1,4 +1,4 @@
1
- import { AssetModels, CollectionModels, LanguageModels } from '@kontent-ai/management-sdk';
1
+ import { AssetFolderModels, AssetModels, CollectionModels, LanguageModels } from '@kontent-ai/management-sdk';
2
2
  import { MigrationAsset, MigrationAssetDescription, findRequired, geSizeInBytes } from '../../core/index.js';
3
3
  import deepEqual from 'deep-equal';
4
4
 
@@ -7,35 +7,22 @@ export function shouldUpdateAsset(data: {
7
7
  readonly targetAsset: Readonly<AssetModels.Asset>;
8
8
  readonly collections: readonly Readonly<CollectionModels.Collection>[];
9
9
  readonly languages: readonly Readonly<LanguageModels.LanguageModel>[];
10
+ readonly assetFolders: readonly Readonly<AssetFolderModels.AssetFolder>[];
10
11
  }): boolean {
11
- if (!isInSameCollection(data)) {
12
- return true;
13
- }
14
-
15
- if (!areDescriptionsIdentical(data)) {
16
- return true;
17
- }
18
-
19
- if (!isTitleIdentical(data)) {
20
- return true;
21
- }
22
-
23
- if (!isBinaryFileIdentical(data)) {
24
- return true;
25
- }
26
-
27
- return false;
12
+ return (
13
+ !isInSameCollection(data) ||
14
+ !areDescriptionsIdentical(data) ||
15
+ !isTitleIdentical(data) ||
16
+ !isFolderIdentical(data) ||
17
+ !isBinaryFileIdentical(data)
18
+ );
28
19
  }
29
20
 
30
21
  export function shouldReplaceBinaryFile(data: {
31
22
  readonly migrationAsset: MigrationAsset;
32
23
  readonly targetAsset: Readonly<AssetModels.Asset>;
33
24
  }): boolean {
34
- if (!isBinaryFileIdentical(data)) {
35
- return true;
36
- }
37
-
38
- return false;
25
+ return !isBinaryFileIdentical(data);
39
26
  }
40
27
 
41
28
  function isBinaryFileIdentical(data: {
@@ -51,10 +38,15 @@ function isBinaryFileIdentical(data: {
51
38
  return sourceFileSize === targetFileSize && sourceFilename === targetFilename;
52
39
  }
53
40
 
54
- function isTitleIdentical(data: {
41
+ function isFolderIdentical(data: {
55
42
  readonly migrationAsset: MigrationAsset;
56
43
  readonly targetAsset: Readonly<AssetModels.Asset>;
44
+ readonly assetFolders: readonly Readonly<AssetFolderModels.AssetFolder>[];
57
45
  }): boolean {
46
+ return data.assetFolders.find((m) => m.id === data.targetAsset.folder?.id)?.codename === data.migrationAsset.folder?.codename;
47
+ }
48
+
49
+ function isTitleIdentical(data: { readonly migrationAsset: MigrationAsset; readonly targetAsset: Readonly<AssetModels.Asset> }): boolean {
58
50
  const sourceTitle = data.migrationAsset.title?.length ? data.migrationAsset.title : undefined;
59
51
  const targetTitle = data.targetAsset.title?.length ? data.targetAsset.title : undefined;
60
52