@kontent-ai/migration-toolkit 1.0.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 (240) hide show
  1. package/.npmignore +13 -0
  2. package/LICENSE.md +9 -0
  3. package/README.md +272 -0
  4. package/dist/es2022/core/helpers/workflow-helper.d.ts +37 -0
  5. package/dist/es2022/core/helpers/workflow-helper.js +67 -0
  6. package/dist/es2022/core/helpers/workflow-helper.js.map +1 -0
  7. package/dist/es2022/core/index.d.ts +15 -0
  8. package/dist/es2022/core/index.js +16 -0
  9. package/dist/es2022/core/index.js.map +1 -0
  10. package/dist/es2022/core/logs/loggers.d.ts +2 -0
  11. package/dist/es2022/core/logs/loggers.js +69 -0
  12. package/dist/es2022/core/logs/loggers.js.map +1 -0
  13. package/dist/es2022/core/models/core.models.d.ts +91 -0
  14. package/dist/es2022/core/models/core.models.js +2 -0
  15. package/dist/es2022/core/models/core.models.js.map +1 -0
  16. package/dist/es2022/core/models/log.models.d.ts +15 -0
  17. package/dist/es2022/core/models/log.models.js +2 -0
  18. package/dist/es2022/core/models/log.models.js.map +1 -0
  19. package/dist/es2022/core/models/migration.models.d.ts +136 -0
  20. package/dist/es2022/core/models/migration.models.js +2 -0
  21. package/dist/es2022/core/models/migration.models.js.map +1 -0
  22. package/dist/es2022/core/utils/array.utils.d.ts +5 -0
  23. package/dist/es2022/core/utils/array.utils.js +27 -0
  24. package/dist/es2022/core/utils/array.utils.js.map +1 -0
  25. package/dist/es2022/core/utils/binary-data.utils.d.ts +5 -0
  26. package/dist/es2022/core/utils/binary-data.utils.js +21 -0
  27. package/dist/es2022/core/utils/binary-data.utils.js.map +1 -0
  28. package/dist/es2022/core/utils/confirm.utils.d.ts +31 -0
  29. package/dist/es2022/core/utils/confirm.utils.js +73 -0
  30. package/dist/es2022/core/utils/confirm.utils.js.map +1 -0
  31. package/dist/es2022/core/utils/error.utils.d.ts +4 -0
  32. package/dist/es2022/core/utils/error.utils.js +50 -0
  33. package/dist/es2022/core/utils/error.utils.js.map +1 -0
  34. package/dist/es2022/core/utils/external-id.utils.d.ts +5 -0
  35. package/dist/es2022/core/utils/external-id.utils.js +5 -0
  36. package/dist/es2022/core/utils/external-id.utils.js.map +1 -0
  37. package/dist/es2022/core/utils/global.utils.d.ts +14 -0
  38. package/dist/es2022/core/utils/global.utils.js +45 -0
  39. package/dist/es2022/core/utils/global.utils.js.map +1 -0
  40. package/dist/es2022/core/utils/http.utils.d.ts +3 -0
  41. package/dist/es2022/core/utils/http.utils.js +29 -0
  42. package/dist/es2022/core/utils/http.utils.js.map +1 -0
  43. package/dist/es2022/core/utils/management-client-utils.d.ts +19 -0
  44. package/dist/es2022/core/utils/management-client-utils.js +129 -0
  45. package/dist/es2022/core/utils/management-client-utils.js.map +1 -0
  46. package/dist/es2022/core/utils/processing-utils.d.ts +12 -0
  47. package/dist/es2022/core/utils/processing-utils.js +39 -0
  48. package/dist/es2022/core/utils/processing-utils.js.map +1 -0
  49. package/dist/es2022/core/utils/run.utils.d.ts +10 -0
  50. package/dist/es2022/core/utils/run.utils.js +37 -0
  51. package/dist/es2022/core/utils/run.utils.js.map +1 -0
  52. package/dist/es2022/export/context/export-context-fetcher.d.ts +4 -0
  53. package/dist/es2022/export/context/export-context-fetcher.js +289 -0
  54. package/dist/es2022/export/context/export-context-fetcher.js.map +1 -0
  55. package/dist/es2022/export/export-manager.d.ts +5 -0
  56. package/dist/es2022/export/export-manager.js +167 -0
  57. package/dist/es2022/export/export-manager.js.map +1 -0
  58. package/dist/es2022/export/export.models.d.ts +55 -0
  59. package/dist/es2022/export/export.models.js +2 -0
  60. package/dist/es2022/export/export.models.js.map +1 -0
  61. package/dist/es2022/export/index.d.ts +2 -0
  62. package/dist/es2022/export/index.js +3 -0
  63. package/dist/es2022/export/index.js.map +1 -0
  64. package/dist/es2022/export/utils/export.utils.d.ts +2 -0
  65. package/dist/es2022/export/utils/export.utils.js +5 -0
  66. package/dist/es2022/export/utils/export.utils.js.map +1 -0
  67. package/dist/es2022/file/file-manager.d.ts +5 -0
  68. package/dist/es2022/file/file-manager.js +29 -0
  69. package/dist/es2022/file/file-manager.js.map +1 -0
  70. package/dist/es2022/file/index.d.ts +1 -0
  71. package/dist/es2022/file/index.js +2 -0
  72. package/dist/es2022/file/index.js.map +1 -0
  73. package/dist/es2022/import/comparers/asset-comparer.d.ts +12 -0
  74. package/dist/es2022/import/comparers/asset-comparer.js +65 -0
  75. package/dist/es2022/import/comparers/asset-comparer.js.map +1 -0
  76. package/dist/es2022/import/context/import-context-fetcher.d.ts +4 -0
  77. package/dist/es2022/import/context/import-context-fetcher.js +235 -0
  78. package/dist/es2022/import/context/import-context-fetcher.js.map +1 -0
  79. package/dist/es2022/import/import-manager.d.ts +4 -0
  80. package/dist/es2022/import/import-manager.js +80 -0
  81. package/dist/es2022/import/import-manager.js.map +1 -0
  82. package/dist/es2022/import/import.models.d.ts +45 -0
  83. package/dist/es2022/import/import.models.js +2 -0
  84. package/dist/es2022/import/import.models.js.map +1 -0
  85. package/dist/es2022/import/importers/assets-importer.d.ts +10 -0
  86. package/dist/es2022/import/importers/assets-importer.js +175 -0
  87. package/dist/es2022/import/importers/assets-importer.js.map +1 -0
  88. package/dist/es2022/import/importers/content-items-importer.d.ts +10 -0
  89. package/dist/es2022/import/importers/content-items-importer.js +97 -0
  90. package/dist/es2022/import/importers/content-items-importer.js.map +1 -0
  91. package/dist/es2022/import/importers/language-variant-importer.d.ts +11 -0
  92. package/dist/es2022/import/importers/language-variant-importer.js +215 -0
  93. package/dist/es2022/import/importers/language-variant-importer.js.map +1 -0
  94. package/dist/es2022/import/importers/workflow-importer.d.ts +18 -0
  95. package/dist/es2022/import/importers/workflow-importer.js +122 -0
  96. package/dist/es2022/import/importers/workflow-importer.js.map +1 -0
  97. package/dist/es2022/import/index.d.ts +2 -0
  98. package/dist/es2022/import/index.js +3 -0
  99. package/dist/es2022/import/index.js.map +1 -0
  100. package/dist/es2022/import/utils/import.utils.d.ts +2 -0
  101. package/dist/es2022/import/utils/import.utils.js +5 -0
  102. package/dist/es2022/import/utils/import.utils.js.map +1 -0
  103. package/dist/es2022/index.d.ts +8 -0
  104. package/dist/es2022/index.js +10 -0
  105. package/dist/es2022/index.js.map +1 -0
  106. package/dist/es2022/metadata.d.ts +5 -0
  107. package/dist/es2022/metadata.js +6 -0
  108. package/dist/es2022/metadata.js.map +1 -0
  109. package/dist/es2022/node/cli/actions/export-action.d.ts +2 -0
  110. package/dist/es2022/node/cli/actions/export-action.js +40 -0
  111. package/dist/es2022/node/cli/actions/export-action.js.map +1 -0
  112. package/dist/es2022/node/cli/actions/import-action.d.ts +2 -0
  113. package/dist/es2022/node/cli/actions/import-action.js +29 -0
  114. package/dist/es2022/node/cli/actions/import-action.js.map +1 -0
  115. package/dist/es2022/node/cli/actions/migrate-action.d.ts +2 -0
  116. package/dist/es2022/node/cli/actions/migrate-action.js +47 -0
  117. package/dist/es2022/node/cli/actions/migrate-action.js.map +1 -0
  118. package/dist/es2022/node/cli/app.d.ts +2 -0
  119. package/dist/es2022/node/cli/app.js +27 -0
  120. package/dist/es2022/node/cli/app.js.map +1 -0
  121. package/dist/es2022/node/cli/args/args-fetcher.d.ts +2 -0
  122. package/dist/es2022/node/cli/args/args-fetcher.js +46 -0
  123. package/dist/es2022/node/cli/args/args-fetcher.js.map +1 -0
  124. package/dist/es2022/node/cli/args/args-setter.d.ts +2 -0
  125. package/dist/es2022/node/cli/args/args-setter.js +31 -0
  126. package/dist/es2022/node/cli/args/args-setter.js.map +1 -0
  127. package/dist/es2022/node/cli/cli.models.d.ts +24 -0
  128. package/dist/es2022/node/cli/cli.models.js +2 -0
  129. package/dist/es2022/node/cli/cli.models.js.map +1 -0
  130. package/dist/es2022/node/cli/commands.d.ts +1 -0
  131. package/dist/es2022/node/cli/commands.js +157 -0
  132. package/dist/es2022/node/cli/commands.js.map +1 -0
  133. package/dist/es2022/toolkit/elements-builder.d.ts +14 -0
  134. package/dist/es2022/toolkit/elements-builder.js +69 -0
  135. package/dist/es2022/toolkit/elements-builder.js.map +1 -0
  136. package/dist/es2022/toolkit/export.d.ts +3 -0
  137. package/dist/es2022/toolkit/export.js +21 -0
  138. package/dist/es2022/toolkit/export.js.map +1 -0
  139. package/dist/es2022/toolkit/file.d.ts +12 -0
  140. package/dist/es2022/toolkit/file.js +50 -0
  141. package/dist/es2022/toolkit/file.js.map +1 -0
  142. package/dist/es2022/toolkit/import.d.ts +2 -0
  143. package/dist/es2022/toolkit/import.js +21 -0
  144. package/dist/es2022/toolkit/import.js.map +1 -0
  145. package/dist/es2022/toolkit/index.d.ts +5 -0
  146. package/dist/es2022/toolkit/index.js +6 -0
  147. package/dist/es2022/toolkit/index.js.map +1 -0
  148. package/dist/es2022/toolkit/migrate.d.ts +21 -0
  149. package/dist/es2022/toolkit/migrate.js +39 -0
  150. package/dist/es2022/toolkit/migrate.js.map +1 -0
  151. package/dist/es2022/translation/extraction/items-extraction.processor.d.ts +24 -0
  152. package/dist/es2022/translation/extraction/items-extraction.processor.js +108 -0
  153. package/dist/es2022/translation/extraction/items-extraction.processor.js.map +1 -0
  154. package/dist/es2022/translation/helpers/rich-text.processor.d.ts +22 -0
  155. package/dist/es2022/translation/helpers/rich-text.processor.js +209 -0
  156. package/dist/es2022/translation/helpers/rich-text.processor.js.map +1 -0
  157. package/dist/es2022/translation/index.d.ts +4 -0
  158. package/dist/es2022/translation/index.js +5 -0
  159. package/dist/es2022/translation/index.js.map +1 -0
  160. package/dist/es2022/translation/transforms/export-transforms.d.ts +3 -0
  161. package/dist/es2022/translation/transforms/export-transforms.js +196 -0
  162. package/dist/es2022/translation/transforms/export-transforms.js.map +1 -0
  163. package/dist/es2022/translation/transforms/import-transforms.d.ts +7 -0
  164. package/dist/es2022/translation/transforms/import-transforms.js +183 -0
  165. package/dist/es2022/translation/transforms/import-transforms.js.map +1 -0
  166. package/dist/es2022/zip/index.d.ts +4 -0
  167. package/dist/es2022/zip/index.js +5 -0
  168. package/dist/es2022/zip/index.js.map +1 -0
  169. package/dist/es2022/zip/zip-manager.d.ts +6 -0
  170. package/dist/es2022/zip/zip-manager.js +27 -0
  171. package/dist/es2022/zip/zip-manager.js.map +1 -0
  172. package/dist/es2022/zip/zip-packager.d.ts +3 -0
  173. package/dist/es2022/zip/zip-packager.js +69 -0
  174. package/dist/es2022/zip/zip-packager.js.map +1 -0
  175. package/dist/es2022/zip/zip-transformer.d.ts +6 -0
  176. package/dist/es2022/zip/zip-transformer.js +82 -0
  177. package/dist/es2022/zip/zip-transformer.js.map +1 -0
  178. package/dist/es2022/zip/zip.models.d.ts +13 -0
  179. package/dist/es2022/zip/zip.models.js +2 -0
  180. package/dist/es2022/zip/zip.models.js.map +1 -0
  181. package/lib/core/helpers/workflow-helper.ts +104 -0
  182. package/lib/core/index.ts +17 -0
  183. package/lib/core/logs/loggers.ts +80 -0
  184. package/lib/core/models/core.models.ts +134 -0
  185. package/lib/core/models/log.models.ts +31 -0
  186. package/lib/core/models/migration.models.ts +165 -0
  187. package/lib/core/utils/array.utils.ts +49 -0
  188. package/lib/core/utils/binary-data.utils.ts +29 -0
  189. package/lib/core/utils/confirm.utils.ts +139 -0
  190. package/lib/core/utils/error.utils.ts +65 -0
  191. package/lib/core/utils/external-id.utils.ts +9 -0
  192. package/lib/core/utils/global.utils.ts +59 -0
  193. package/lib/core/utils/http.utils.ts +35 -0
  194. package/lib/core/utils/management-client-utils.ts +182 -0
  195. package/lib/core/utils/processing-utils.ts +71 -0
  196. package/lib/core/utils/run.utils.ts +51 -0
  197. package/lib/export/context/export-context-fetcher.ts +475 -0
  198. package/lib/export/export-manager.ts +249 -0
  199. package/lib/export/export.models.ts +87 -0
  200. package/lib/export/index.ts +2 -0
  201. package/lib/export/utils/export.utils.ts +10 -0
  202. package/lib/file/file-manager.ts +37 -0
  203. package/lib/file/index.ts +1 -0
  204. package/lib/import/comparers/asset-comparer.ts +113 -0
  205. package/lib/import/context/import-context-fetcher.ts +367 -0
  206. package/lib/import/import-manager.ts +103 -0
  207. package/lib/import/import.models.ts +84 -0
  208. package/lib/import/importers/assets-importer.ts +239 -0
  209. package/lib/import/importers/content-items-importer.ts +142 -0
  210. package/lib/import/importers/language-variant-importer.ts +342 -0
  211. package/lib/import/importers/workflow-importer.ts +159 -0
  212. package/lib/import/index.ts +2 -0
  213. package/lib/import/utils/import.utils.ts +10 -0
  214. package/lib/index.ts +9 -0
  215. package/lib/metadata.ts +6 -0
  216. package/lib/node/cli/actions/export-action.ts +45 -0
  217. package/lib/node/cli/actions/import-action.ts +34 -0
  218. package/lib/node/cli/actions/migrate-action.ts +52 -0
  219. package/lib/node/cli/app.ts +29 -0
  220. package/lib/node/cli/args/args-fetcher.ts +61 -0
  221. package/lib/node/cli/args/args-setter.ts +35 -0
  222. package/lib/node/cli/cli.models.ts +28 -0
  223. package/lib/node/cli/commands.ts +161 -0
  224. package/lib/toolkit/elements-builder.ts +84 -0
  225. package/lib/toolkit/export.ts +21 -0
  226. package/lib/toolkit/file.ts +70 -0
  227. package/lib/toolkit/import.ts +21 -0
  228. package/lib/toolkit/index.ts +5 -0
  229. package/lib/toolkit/migrate.ts +71 -0
  230. package/lib/translation/extraction/items-extraction.processor.ts +181 -0
  231. package/lib/translation/helpers/rich-text.processor.ts +300 -0
  232. package/lib/translation/index.ts +4 -0
  233. package/lib/translation/transforms/export-transforms.ts +253 -0
  234. package/lib/translation/transforms/import-transforms.ts +215 -0
  235. package/lib/zip/index.ts +4 -0
  236. package/lib/zip/zip-manager.ts +33 -0
  237. package/lib/zip/zip-packager.ts +86 -0
  238. package/lib/zip/zip-transformer.ts +105 -0
  239. package/lib/zip/zip.models.ts +12 -0
  240. package/package.json +88 -0
@@ -0,0 +1,253 @@
1
+ import {
2
+ MigrationElementType,
3
+ MigrationReference,
4
+ MigrationRichTextElementValue,
5
+ MigrationUrlSlugElementValue,
6
+ findRequired,
7
+ isNotUndefined
8
+ } from '../../core/index.js';
9
+ import { ContentTypeElements, TaxonomyModels } from '@kontent-ai/management-sdk';
10
+ import { ExportTransformFunc, ExportContext, ExportElement } from '../../export/index.js';
11
+ import { richTextProcessor } from '../helpers/rich-text.processor.js';
12
+ import chalk from 'chalk';
13
+
14
+ export const exportTransforms: Readonly<Record<MigrationElementType, ExportTransformFunc>> = {
15
+ text: (data) => data.exportElement.value?.toString(),
16
+ number: (data) => {
17
+ if (!data.exportElement.value) {
18
+ return undefined;
19
+ }
20
+
21
+ if (Array.isArray(data.exportElement.value)) {
22
+ throw Error(`Expected value to be a number, not array`);
23
+ }
24
+
25
+ if (data.exportElement.value === 0) {
26
+ return 0;
27
+ }
28
+
29
+ return +data.exportElement.value;
30
+ },
31
+ date_time: (data) => data.exportElement.value?.toString(),
32
+ rich_text: (data) => transformRichTextValue(data.exportElement, data.context),
33
+ asset: (data) => {
34
+ if (!data.exportElement.value) {
35
+ return [];
36
+ }
37
+
38
+ if (!Array.isArray(data.exportElement.value)) {
39
+ throw Error(`Expected value to be an array`);
40
+ }
41
+
42
+ // translate asset id to codename
43
+ return data.exportElement.value
44
+ .map((m) => m.id)
45
+ .filter(isNotUndefined)
46
+ .map<MigrationReference>((id) => {
47
+ const assetState = data.context.getAssetStateInSourceEnvironment(id);
48
+
49
+ if (assetState.asset) {
50
+ // reference asset by codename
51
+ return { codename: assetState.asset.codename };
52
+ } else {
53
+ throw Error(`Missing asset with id '${chalk.red(id)}'`);
54
+ }
55
+ });
56
+ },
57
+ taxonomy: (data) => {
58
+ if (!data.exportElement.value) {
59
+ return [];
60
+ }
61
+
62
+ if (!Array.isArray(data.exportElement.value)) {
63
+ throw Error(`Expected value to be an array`);
64
+ }
65
+
66
+ const taxonomyElement = data.typeElement.element as Readonly<ContentTypeElements.ITaxonomyElement>;
67
+ const taxonomyGroupId = taxonomyElement.taxonomy_group.id ?? 'n/a';
68
+
69
+ // get taxonomy group
70
+ const taxonomy = findRequired(
71
+ data.context.environmentData.taxonomies,
72
+ (taxonomy) => taxonomy.id === taxonomyGroupId,
73
+ `Could not find taxonomy group with id '${chalk.red(taxonomyGroupId)}'`
74
+ );
75
+
76
+ // translate taxonomy term to codename
77
+ return data.exportElement.value
78
+ .map((m) => m.id)
79
+ .filter(isNotUndefined)
80
+ .map<MigrationReference>((id) => {
81
+ const taxonomyTerm = findTaxonomy(id, taxonomy);
82
+
83
+ if (taxonomyTerm) {
84
+ // reference taxonomy term by codename
85
+ return { codename: taxonomyTerm.codename };
86
+ } else {
87
+ throw Error(`Missing taxonomy term with id '${chalk.red(id)}'`);
88
+ }
89
+ });
90
+ },
91
+ modular_content: (data) => {
92
+ if (!data.exportElement.value) {
93
+ return [];
94
+ }
95
+
96
+ if (!Array.isArray(data.exportElement.value)) {
97
+ throw Error(`Expected value to be an array`);
98
+ }
99
+
100
+ // translate item id to codename
101
+ return data.exportElement.value
102
+ .map((m) => m.id)
103
+ .filter(isNotUndefined)
104
+ .map<MigrationReference>((id) => {
105
+ const itemState = data.context.getItemStateInSourceEnvironment(id);
106
+
107
+ if (itemState.item) {
108
+ // reference item by codename
109
+ return { codename: itemState.item.codename };
110
+ } else {
111
+ throw Error(`Missing item with id '${chalk.red(id)}'`);
112
+ }
113
+ });
114
+ },
115
+ custom: (data) => data.exportElement.value?.toString(),
116
+ url_slug: (data) => {
117
+ const urlSlugElementValue: MigrationUrlSlugElementValue = {
118
+ mode: data.exportElement.urlSlugMode ?? 'autogenerated',
119
+ value: data.exportElement.value?.toString() ?? undefined
120
+ };
121
+
122
+ return urlSlugElementValue;
123
+ },
124
+ multiple_choice: (data) => {
125
+ if (!data.exportElement.value) {
126
+ return [];
127
+ }
128
+
129
+ if (!Array.isArray(data.exportElement.value)) {
130
+ throw Error(`Expected value to be an array`);
131
+ }
132
+
133
+ // translate multiple choice option id to codename
134
+ const multipleChoiceElement = data.typeElement.element as Readonly<ContentTypeElements.IMultipleChoiceElement>;
135
+
136
+ return data.exportElement.value
137
+ .map((m) => m.id)
138
+ .filter(isNotUndefined)
139
+ .map<MigrationReference>((id) => {
140
+ const option = findRequired(
141
+ multipleChoiceElement.options,
142
+ (option) => option.id === id,
143
+ `Could not find multiple choice element with option id '${chalk.red(id)}'`
144
+ );
145
+
146
+ if (!option.codename) {
147
+ throw Error(`Invalid codename for multiple choice option '${chalk.red(option.name)}'`);
148
+ }
149
+
150
+ return { codename: option.codename };
151
+ });
152
+ },
153
+ subpages: (data) => {
154
+ if (!data.exportElement.value) {
155
+ return [];
156
+ }
157
+ if (!Array.isArray(data.exportElement.value)) {
158
+ throw Error(`Expected value to be an array`);
159
+ }
160
+
161
+ // translate item id to codename
162
+ return data.exportElement.value
163
+ .map((m) => m.id)
164
+ .filter(isNotUndefined)
165
+ .map<MigrationReference>((id) => {
166
+ const itemState = data.context.getItemStateInSourceEnvironment(id);
167
+
168
+ if (itemState.item) {
169
+ // reference item by codename
170
+ return { codename: itemState.item.codename };
171
+ } else {
172
+ throw Error(`Missing item with id '${chalk.red(id)}'`);
173
+ }
174
+ });
175
+ }
176
+ };
177
+
178
+ function findTaxonomy(
179
+ termId: string,
180
+ taxonomy: Readonly<TaxonomyModels.Taxonomy>
181
+ ): Readonly<TaxonomyModels.Taxonomy> | undefined {
182
+ if (taxonomy.id === termId) {
183
+ return taxonomy;
184
+ }
185
+
186
+ if (taxonomy.terms) {
187
+ const foundTerm = taxonomy.terms.find((term) => findTaxonomy(term.id, term));
188
+
189
+ if (foundTerm) {
190
+ return foundTerm;
191
+ }
192
+ }
193
+
194
+ return undefined;
195
+ }
196
+
197
+ function transformRichTextValue(
198
+ exportElement: ExportElement | undefined,
199
+ context: ExportContext
200
+ ): MigrationRichTextElementValue | undefined {
201
+ if (!exportElement || !exportElement.value) {
202
+ return {
203
+ components: [],
204
+ value: ''
205
+ };
206
+ }
207
+
208
+ let richTextHtml: string = exportElement.value.toString();
209
+
210
+ // replace item ids with codenames
211
+ richTextHtml = richTextProcessor().processDataIds(richTextHtml, (id) => {
212
+ const itemInEnv = context.getItemStateInSourceEnvironment(id).item;
213
+
214
+ if (!itemInEnv) {
215
+ throw Error(`Failed to get item with id '${chalk.red(id)}'`);
216
+ }
217
+
218
+ return {
219
+ codename: itemInEnv.codename
220
+ };
221
+ }).html;
222
+
223
+ // replace link item ids with codenames
224
+ richTextHtml = richTextProcessor().processLinkItemIds(richTextHtml, (id) => {
225
+ const itemInEnv = context.getItemStateInSourceEnvironment(id).item;
226
+
227
+ if (!itemInEnv) {
228
+ throw Error(`Failed to get item with id '${chalk.red(id)}'`);
229
+ }
230
+
231
+ return {
232
+ codename: itemInEnv.codename
233
+ };
234
+ }).html;
235
+
236
+ // replace asset ids with codenames
237
+ richTextHtml = richTextProcessor().processAssetIds(richTextHtml, (id) => {
238
+ const assetInEnv = context.getAssetStateInSourceEnvironment(id).asset;
239
+
240
+ if (!assetInEnv) {
241
+ throw Error(`Failed to get asset with id '${chalk.red(id)}'`);
242
+ }
243
+
244
+ return {
245
+ codename: assetInEnv.codename
246
+ };
247
+ }).html;
248
+
249
+ return {
250
+ components: exportElement.components,
251
+ value: richTextHtml
252
+ };
253
+ }
@@ -0,0 +1,215 @@
1
+ import { SharedContracts, LanguageVariantElementsBuilder, LanguageVariantElements } from '@kontent-ai/management-sdk';
2
+ import {
3
+ parseAsMigrationReferencesArray,
4
+ MigrationElementType,
5
+ MigrationRichTextElementValue,
6
+ MigrationItem,
7
+ MigrationUrlSlugElementValue
8
+ } from '../../core/index.js';
9
+ import { ImportContext, ImportTransformFunc } from '../../import/index.js';
10
+ import { richTextProcessor } from '../helpers/rich-text.processor.js';
11
+
12
+ const elementsBuilder = new LanguageVariantElementsBuilder();
13
+
14
+ /**
15
+ * The purpose of import transform is to take the exported value and convert it to a value that
16
+ * Kontent.ai Management API understands
17
+ */
18
+ export const importTransforms: Readonly<Record<MigrationElementType, ImportTransformFunc>> = {
19
+ subpages: (data) => {
20
+ return elementsBuilder.linkedItemsElement({
21
+ element: {
22
+ codename: data.elementCodename
23
+ },
24
+ value: parseAsMigrationReferencesArray(data.value).map((m) => {
25
+ return {
26
+ codename: m.codename
27
+ };
28
+ })
29
+ });
30
+ },
31
+ asset: (data) => {
32
+ const assetReferences = parseAsMigrationReferencesArray(data.value)
33
+ .map((reference) => reference.codename)
34
+ .map<Readonly<SharedContracts.IReferenceObjectContract>>((codename) => {
35
+ const assetState = data.importContext.getAssetStateInTargetEnvironment(codename);
36
+
37
+ // only reference with external_id if item does not exist in target env
38
+ // API currently only supports referencing assets by ids only, not by codenames
39
+ return {
40
+ id: assetState.asset ? assetState.asset.id : undefined,
41
+ external_id: assetState.asset ? undefined : assetState.externalIdToUse
42
+ };
43
+ });
44
+
45
+ return elementsBuilder.assetElement({
46
+ element: {
47
+ codename: data.elementCodename
48
+ },
49
+ value: assetReferences
50
+ });
51
+ },
52
+ custom: (data) => {
53
+ return elementsBuilder.customElement({
54
+ element: {
55
+ codename: data.elementCodename
56
+ },
57
+ value: data.value?.toString() ?? ''
58
+ });
59
+ },
60
+ date_time: (data) => {
61
+ return elementsBuilder.dateTimeElement({
62
+ element: {
63
+ codename: data.elementCodename
64
+ },
65
+ value: data.value?.toString() ?? null
66
+ });
67
+ },
68
+ modular_content: (data) => {
69
+ const linkedItemReferences = parseAsMigrationReferencesArray(data.value)
70
+ .map((reference) => reference.codename)
71
+ .map<Readonly<SharedContracts.IReferenceObjectContract>>((codename) => {
72
+ const itemState = data.importContext.getItemStateInTargetEnvironment(codename);
73
+
74
+ // only reference with external_id if item does not exist in target env
75
+ return {
76
+ codename: itemState.item ? itemState.item.codename : undefined,
77
+ external_id: itemState.item ? undefined : itemState.externalIdToUse
78
+ };
79
+ });
80
+
81
+ return elementsBuilder.linkedItemsElement({
82
+ element: {
83
+ codename: data.elementCodename
84
+ },
85
+ value: linkedItemReferences
86
+ });
87
+ },
88
+ multiple_choice: (data) => {
89
+ return elementsBuilder.multipleChoiceElement({
90
+ element: {
91
+ codename: data.elementCodename
92
+ },
93
+ value: parseAsMigrationReferencesArray(data.value).map((m) => {
94
+ return {
95
+ codename: m.codename
96
+ };
97
+ })
98
+ });
99
+ },
100
+ number: (data) => {
101
+ return elementsBuilder.numberElement({
102
+ element: {
103
+ codename: data.elementCodename
104
+ },
105
+ value: data.value ? +data.value : null
106
+ });
107
+ },
108
+ rich_text: (data) => {
109
+ const rteElementValue = data.value as MigrationRichTextElementValue;
110
+
111
+ return elementsBuilder.richTextElement({
112
+ element: {
113
+ codename: data.elementCodename
114
+ },
115
+ components: mapComponents({
116
+ importContext: data.importContext,
117
+ migrationItems: data.migrationItems,
118
+ rteValue: rteElementValue
119
+ }),
120
+ value:
121
+ processImportRichTextHtmlValue({
122
+ element: rteElementValue,
123
+ importContext: data.importContext,
124
+ migrationItems: data.migrationItems
125
+ }) ?? null
126
+ });
127
+ },
128
+ taxonomy: (data) => {
129
+ return elementsBuilder.taxonomyElement({
130
+ element: {
131
+ codename: data.elementCodename
132
+ },
133
+ value: parseAsMigrationReferencesArray(data.value).map((m) => {
134
+ return {
135
+ codename: m.codename
136
+ };
137
+ })
138
+ });
139
+ },
140
+ text: (data) => {
141
+ return elementsBuilder.textElement({
142
+ element: {
143
+ codename: data.elementCodename
144
+ },
145
+ value: data.value?.toString() ?? null
146
+ });
147
+ },
148
+ url_slug: (data) => {
149
+ const urlSlugElementValue = data.value as Readonly<MigrationUrlSlugElementValue>;
150
+
151
+ return elementsBuilder.urlSlugElement({
152
+ element: {
153
+ codename: data.elementCodename
154
+ },
155
+ value: urlSlugElementValue.value?.toString() ?? '',
156
+ mode: urlSlugElementValue.mode
157
+ });
158
+ }
159
+ };
160
+
161
+ function mapComponents(data: {
162
+ readonly rteValue: MigrationRichTextElementValue;
163
+ readonly importContext: ImportContext;
164
+ readonly migrationItems: readonly MigrationItem[];
165
+ }): LanguageVariantElements.IRichTextComponent[] {
166
+ return data.rteValue.components.map((component) => {
167
+ const mappedComponent: Readonly<LanguageVariantElements.IRichTextComponent> = {
168
+ id: component.system.id,
169
+ type: {
170
+ codename: component.system.type.codename
171
+ },
172
+ elements: Object.entries(component.elements).map(([key, element]) => {
173
+ const transformedElementValue = importTransforms[element.type]({
174
+ elementCodename: key,
175
+ importContext: data.importContext,
176
+ migrationItems: data.migrationItems,
177
+ value: element.value
178
+ });
179
+
180
+ return transformedElementValue;
181
+ })
182
+ };
183
+
184
+ return mappedComponent;
185
+ });
186
+ }
187
+
188
+ function processImportRichTextHtmlValue(data: {
189
+ readonly element: MigrationRichTextElementValue;
190
+ readonly importContext: ImportContext;
191
+ readonly migrationItems: readonly MigrationItem[];
192
+ }): string | undefined {
193
+ if (!data.element) {
194
+ return undefined;
195
+ }
196
+
197
+ let richTextHtml: string = data.element.value ?? '';
198
+
199
+ // replace item codenames with existing codename or external_id
200
+ richTextHtml = richTextProcessor().processItemCodenames(richTextHtml, (codename) => {
201
+ return data.importContext.getItemStateInTargetEnvironment(codename);
202
+ }).html;
203
+
204
+ // replace link item codenames with existing codename or external_id
205
+ richTextHtml = richTextProcessor().processLinkItemCodenames(richTextHtml, (codename) => {
206
+ return data.importContext.getItemStateInTargetEnvironment(codename);
207
+ }).html;
208
+
209
+ // replace asset codenames with existing codename or external_id
210
+ richTextHtml = richTextProcessor().processAssetCodenames(richTextHtml, (codename) => {
211
+ return data.importContext.getAssetStateInTargetEnvironment(codename);
212
+ }).html;
213
+
214
+ return richTextHtml;
215
+ }
@@ -0,0 +1,4 @@
1
+ export * from './zip.models.js';
2
+ export * from './zip-manager.js';
3
+ export * from './zip-packager.js';
4
+ export * from './zip-transformer.js';
@@ -0,0 +1,33 @@
1
+ import JSZip from 'jszip';
2
+ import { Logger, MigrationData, getDefaultLogger } from '../core/index.js';
3
+ import { zipTransformer } from './zip-transformer.js';
4
+ import { zipPackager } from './zip-packager.js';
5
+ import { FileBinaryData } from './zip.models.js';
6
+
7
+ export function zipManager(logger?: Logger) {
8
+ const loggerToUse = logger ?? getDefaultLogger();
9
+
10
+ const createZipAsync = async (migrationData: MigrationData): Promise<FileBinaryData> => {
11
+ loggerToUse.log({
12
+ type: 'info',
13
+ message: `Creating zip package`
14
+ });
15
+
16
+ return await zipTransformer(zipPackager(new JSZip()), loggerToUse).transformAsync(migrationData);
17
+ };
18
+
19
+ const parseZipAsync = async (zipFile: Buffer): Promise<MigrationData> => {
20
+ loggerToUse.log({
21
+ type: 'info',
22
+ message: `Parsing zip file`
23
+ });
24
+
25
+ const zipPackage = zipPackager(await JSZip.loadAsync(zipFile, {}));
26
+ return await zipTransformer(zipPackage, loggerToUse).parseAsync();
27
+ };
28
+
29
+ return {
30
+ createZipAsync,
31
+ parseZipAsync
32
+ };
33
+ }
@@ -0,0 +1,86 @@
1
+ import JSZip from 'jszip';
2
+ import chalk from 'chalk';
3
+ import { Logger, formatBytes, getCurrentEnvironment, exitProgram, getDefaultLogger } from '../core/index.js';
4
+ import { FileBinaryData, ZipCompressionLevel, ZipPackager } from './zip.models.js';
5
+
6
+ export function zipPackager(jsZip: JSZip): ZipPackager {
7
+ const getZipOutputType = (): 'nodebuffer' | 'blob' => {
8
+ const currentEnv = getCurrentEnvironment();
9
+
10
+ if (currentEnv === 'browser') {
11
+ return 'blob';
12
+ }
13
+ if (currentEnv === 'node') {
14
+ return 'nodebuffer';
15
+ }
16
+
17
+ exitProgram({
18
+ message: `Unsupported context`
19
+ });
20
+ };
21
+
22
+ const getZipSizeInBytes = (zipData: FileBinaryData): number => {
23
+ if (zipData instanceof Blob) {
24
+ return zipData.size;
25
+ } else if (zipData instanceof Buffer) {
26
+ return zipData.byteLength;
27
+ }
28
+
29
+ exitProgram({
30
+ message: `Unrecognized zip data type '${typeof zipData}'`
31
+ });
32
+ };
33
+
34
+ return {
35
+ addFile(filePath: string, data: string | FileBinaryData): JSZip {
36
+ return jsZip.file(filePath, data);
37
+ },
38
+ addFolder(name: string): ZipPackager {
39
+ const folder = jsZip.folder(name);
40
+
41
+ if (!folder) {
42
+ throw Error(`Failed to add folder '${name}'`);
43
+ }
44
+
45
+ return zipPackager(folder);
46
+ },
47
+ async generateZipAsync(config: {
48
+ logger?: Logger;
49
+ compressionLevel?: ZipCompressionLevel;
50
+ }): Promise<FileBinaryData> {
51
+ const logger = config.logger ?? getDefaultLogger();
52
+ const zipOutputType = getZipOutputType();
53
+ const compressionLevel: ZipCompressionLevel = config.compressionLevel ?? 9;
54
+
55
+ logger.log({
56
+ type: 'info',
57
+ message: `Creating zip file using '${chalk.yellow(
58
+ zipOutputType
59
+ )}' with compression level '${chalk.yellow(compressionLevel.toString())}'`
60
+ });
61
+
62
+ const result = await jsZip.generateAsync({
63
+ type: zipOutputType,
64
+ compression: 'DEFLATE',
65
+ compressionOptions: {
66
+ level: compressionLevel
67
+ },
68
+ streamFiles: true
69
+ });
70
+
71
+ logger.log({
72
+ type: 'info',
73
+ message: `Zip successfully generated (${chalk.yellow(formatBytes(getZipSizeInBytes(result)))})`
74
+ });
75
+
76
+ return result;
77
+ },
78
+ async getBinaryDataAsync(filePath: string): Promise<FileBinaryData | undefined> {
79
+ const binaryData = await jsZip.file(filePath)?.async(getZipOutputType());
80
+ return binaryData;
81
+ },
82
+ async getFileContentAsync(filePath: string): Promise<string | undefined> {
83
+ return await jsZip.file(filePath)?.async('string');
84
+ }
85
+ };
86
+ }
@@ -0,0 +1,105 @@
1
+ import { FileBinaryData, ZipPackager } from './zip.models.js';
2
+ import { Logger, MigrationAsset, MigrationData, MigrationItem, getDefaultLogger, mapAsync } from '../core/index.js';
3
+ import chalk from 'chalk';
4
+
5
+ type ZipAssetRecord = Omit<MigrationAsset, 'binaryData'> & {
6
+ _zipFilename: string;
7
+ };
8
+
9
+ export function zipTransformer(zip: ZipPackager, logger?: Logger) {
10
+ const loggerToUse: Logger = logger ?? getDefaultLogger();
11
+ const filename: string = 'items.json';
12
+ const assetsFilename: string = 'assets.json';
13
+ const assetsBinaryFolderName: string = 'binary_data';
14
+
15
+ const getAssetFolderConfig = (asset: MigrationAsset): { partialColder: string; fullPath: string } => {
16
+ const codenamePartialFolder: string = asset.codename.slice(0, 2);
17
+ return {
18
+ partialColder: codenamePartialFolder,
19
+ fullPath: `${assetsBinaryFolderName}/${codenamePartialFolder}/${asset.codename}/${asset.filename}`
20
+ };
21
+ };
22
+
23
+ const transformItems = (items: readonly MigrationItem[]): void => {
24
+ zip.addFile(filename, items.length ? JSON.stringify(items) : '[]');
25
+ };
26
+
27
+ const transformAssets = (assets: readonly MigrationAsset[]): void => {
28
+ const assetRecords: ZipAssetRecord[] = [];
29
+ const binaryDataFolder = zip.addFolder(assetsBinaryFolderName);
30
+
31
+ assets.forEach((asset) => {
32
+ const folderConfig = getAssetFolderConfig(asset);
33
+ const partialFolder = binaryDataFolder.addFolder(folderConfig.partialColder);
34
+ const codenameFolder = partialFolder.addFolder(asset.codename);
35
+
36
+ assetRecords.push({
37
+ _zipFilename: folderConfig.fullPath,
38
+ filename: asset.filename,
39
+ title: asset.title,
40
+ codename: asset.codename,
41
+ collection: asset.collection,
42
+ descriptions: asset.descriptions
43
+ });
44
+
45
+ if (asset.binaryData) {
46
+ codenameFolder.addFile(asset.filename, asset.binaryData);
47
+ }
48
+ });
49
+
50
+ zip.addFile(assetsFilename, JSON.stringify(assetRecords));
51
+ };
52
+
53
+ const parseItems = async () => {
54
+ const fileContent = await zip.getFileContentAsync(filename);
55
+ return fileContent ? (JSON.parse(fileContent) as MigrationItem[]) : [];
56
+ };
57
+
58
+ const parseAssets = async () => {
59
+ const text = await zip.getFileContentAsync(assetsFilename);
60
+
61
+ if (!text) {
62
+ return [];
63
+ }
64
+
65
+ const assetRecords = JSON.parse(text) as ZipAssetRecord[];
66
+
67
+ return await mapAsync(assetRecords, async (assetRecord) => {
68
+ const binaryFile = await zip.getBinaryDataAsync(`${assetRecord._zipFilename}`);
69
+
70
+ if (!binaryFile) {
71
+ throw Error(`Could not load binary data for file '${chalk.red(assetRecord._zipFilename)}'`);
72
+ }
73
+
74
+ const migrationAsset: MigrationAsset = {
75
+ codename: assetRecord.codename,
76
+ filename: assetRecord.filename,
77
+ collection: assetRecord.collection,
78
+ title: assetRecord.title,
79
+ descriptions: assetRecord.descriptions,
80
+ binaryData: binaryFile
81
+ };
82
+
83
+ return migrationAsset;
84
+ });
85
+ };
86
+
87
+ return {
88
+ async transformAsync(data: MigrationData): Promise<FileBinaryData> {
89
+ transformItems(data.items);
90
+ transformAssets(data.assets);
91
+
92
+ return await zip.generateZipAsync({ logger: loggerToUse });
93
+ },
94
+ async parseAsync(): Promise<MigrationData> {
95
+ const items = await parseItems();
96
+ const assets = await parseAssets();
97
+
98
+ const migrationData: MigrationData = {
99
+ assets: assets,
100
+ items: items
101
+ };
102
+ return migrationData;
103
+ }
104
+ };
105
+ }