@kontent-ai/migration-toolkit 2.2.1 → 2.3.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 (84) hide show
  1. package/README.md +10 -9
  2. package/dist/es2022/core/logs/loggers.js +4 -1
  3. package/dist/es2022/core/logs/loggers.js.map +1 -1
  4. package/dist/es2022/core/models/core.models.d.ts +12 -0
  5. package/dist/es2022/core/models/log.models.d.ts +2 -2
  6. package/dist/es2022/core/models/migration.models.d.ts +3 -3
  7. package/dist/es2022/core/models/migration.schema.d.ts +12 -12
  8. package/dist/es2022/core/models/migration.schema.js +2 -2
  9. package/dist/es2022/core/models/migration.schema.js.map +1 -1
  10. package/dist/es2022/core/utils/binary-data.utils.d.ts +4 -4
  11. package/dist/es2022/core/utils/binary-data.utils.js +2 -2
  12. package/dist/es2022/core/utils/binary-data.utils.js.map +1 -1
  13. package/dist/es2022/core/utils/global.utils.d.ts +3 -3
  14. package/dist/es2022/core/utils/global.utils.js +1 -1
  15. package/dist/es2022/core/utils/global.utils.js.map +1 -1
  16. package/dist/es2022/core/utils/http.utils.js +0 -3
  17. package/dist/es2022/core/utils/http.utils.js.map +1 -1
  18. package/dist/es2022/core/utils/management-client-utils.d.ts +1 -1
  19. package/dist/es2022/core/utils/processing-utils.d.ts +3 -3
  20. package/dist/es2022/core/utils/processing-utils.js +31 -4
  21. package/dist/es2022/core/utils/processing-utils.js.map +1 -1
  22. package/dist/es2022/export/context/export-context-fetcher.js +13 -7
  23. package/dist/es2022/export/context/export-context-fetcher.js.map +1 -1
  24. package/dist/es2022/export/export-manager.js +5 -4
  25. package/dist/es2022/export/export-manager.js.map +1 -1
  26. package/dist/es2022/file/file-manager.d.ts +3 -2
  27. package/dist/es2022/file/file-manager.js.map +1 -1
  28. package/dist/es2022/import/context/import-context-fetcher.js +14 -8
  29. package/dist/es2022/import/context/import-context-fetcher.js.map +1 -1
  30. package/dist/es2022/import/import-manager.d.ts +4 -0
  31. package/dist/es2022/import/import-manager.js +134 -5
  32. package/dist/es2022/import/import-manager.js.map +1 -1
  33. package/dist/es2022/import/import.models.d.ts +15 -5
  34. package/dist/es2022/import/importers/assets-importer.js.map +1 -1
  35. package/dist/es2022/import/importers/content-items-importer.d.ts +3 -3
  36. package/dist/es2022/import/importers/content-items-importer.js +11 -5
  37. package/dist/es2022/import/importers/content-items-importer.js.map +1 -1
  38. package/dist/es2022/import/importers/language-variant-importer.d.ts +4 -4
  39. package/dist/es2022/import/importers/language-variant-importer.js +4 -4
  40. package/dist/es2022/import/importers/language-variant-importer.js.map +1 -1
  41. package/dist/es2022/metadata.js +2 -2
  42. package/dist/es2022/node/cli/actions/import-action.js +3 -1
  43. package/dist/es2022/node/cli/actions/import-action.js.map +1 -1
  44. package/dist/es2022/toolkit/file.js +2 -2
  45. package/dist/es2022/toolkit/file.js.map +1 -1
  46. package/dist/es2022/toolkit/import.js +14 -2
  47. package/dist/es2022/toolkit/import.js.map +1 -1
  48. package/dist/es2022/translation/extraction/items-extraction.processor.d.ts +1 -1
  49. package/dist/es2022/translation/extraction/items-extraction.processor.js.map +1 -1
  50. package/dist/es2022/zip/zip-manager.d.ts +2 -2
  51. package/dist/es2022/zip/zip-manager.js.map +1 -1
  52. package/dist/es2022/zip/zip-packager.js +3 -4
  53. package/dist/es2022/zip/zip-packager.js.map +1 -1
  54. package/dist/es2022/zip/zip-transformer.js.map +1 -1
  55. package/dist/es2022/zip/zip.models.d.ts +4 -4
  56. package/lib/core/logs/loggers.ts +5 -1
  57. package/lib/core/models/core.models.ts +16 -0
  58. package/lib/core/models/log.models.ts +2 -2
  59. package/lib/core/models/migration.models.ts +3 -3
  60. package/lib/core/models/migration.schema.ts +2 -2
  61. package/lib/core/utils/binary-data.utils.ts +6 -6
  62. package/lib/core/utils/global.utils.ts +8 -5
  63. package/lib/core/utils/http.utils.ts +1 -4
  64. package/lib/core/utils/management-client-utils.ts +1 -1
  65. package/lib/core/utils/processing-utils.ts +48 -18
  66. package/lib/export/context/export-context-fetcher.ts +67 -60
  67. package/lib/export/export-manager.ts +58 -56
  68. package/lib/file/file-manager.ts +4 -3
  69. package/lib/import/context/import-context-fetcher.ts +18 -13
  70. package/lib/import/import-manager.ts +176 -12
  71. package/lib/import/import.models.ts +20 -4
  72. package/lib/import/importers/assets-importer.ts +3 -9
  73. package/lib/import/importers/content-items-importer.ts +18 -17
  74. package/lib/import/importers/language-variant-importer.ts +32 -34
  75. package/lib/metadata.ts +2 -2
  76. package/lib/node/cli/actions/import-action.ts +3 -1
  77. package/lib/toolkit/file.ts +2 -2
  78. package/lib/toolkit/import.ts +16 -2
  79. package/lib/translation/extraction/items-extraction.processor.ts +3 -3
  80. package/lib/zip/zip-manager.ts +2 -2
  81. package/lib/zip/zip-packager.ts +3 -4
  82. package/lib/zip/zip-transformer.ts +1 -1
  83. package/lib/zip/zip.models.ts +3 -3
  84. package/package.json +13 -13
@@ -1,23 +1,22 @@
1
1
  import {
2
- WorkflowModels,
3
- LanguageVariantModels,
4
- ContentItemModels,
5
2
  AssetModels,
6
3
  CollectionModels,
7
- LanguageModels
4
+ ContentItemModels,
5
+ LanguageModels,
6
+ LanguageVariantModels,
7
+ WorkflowModels
8
8
  } from '@kontent-ai/management-sdk';
9
9
  import chalk from 'chalk';
10
10
  import {
11
- processItemsAsync,
12
- runMapiRequestAsync,
13
- is404Error,
14
- ItemStateInSourceEnvironmentById,
15
11
  AssetStateInSourceEnvironmentById,
12
+ findRequired,
16
13
  FlattenedContentType,
17
- isNotUndefined,
18
- managementClientUtils,
14
+ is404Error,
15
+ ItemStateInSourceEnvironmentById,
19
16
  LogSpinnerData,
20
- findRequired,
17
+ managementClientUtils,
18
+ processItemsAsync,
19
+ runMapiRequestAsync,
21
20
  workflowHelper
22
21
  } from '../../core/index.js';
23
22
  import { itemsExtractionProcessor } from '../../translation/index.js';
@@ -25,10 +24,10 @@ import {
25
24
  DefaultExportContextConfig,
26
25
  ExportContext,
27
26
  ExportContextEnvironmentData,
28
- SourceExportItem,
29
27
  ExportItem,
28
+ ExportItemVersion,
30
29
  GetFlattenedElementByIds,
31
- ExportItemVersion
30
+ SourceExportItem
32
31
  } from '../export.models.js';
33
32
  import { throwErrorForItemRequest } from '../utils/export.utils.js';
34
33
 
@@ -173,11 +172,11 @@ export async function exportContextFetcherAsync(config: DefaultExportContextConf
173
172
  readonly contentItem: Readonly<ContentItemModels.ContentItem>;
174
173
  readonly languageVariant: Readonly<LanguageVariantModels.ContentItemLanguageVariant>;
175
174
  }): {
176
- collection: Readonly<CollectionModels.Collection>;
177
- language: Readonly<LanguageModels.LanguageModel>;
178
- workflow: Readonly<WorkflowModels.Workflow>;
179
- contentType: Readonly<FlattenedContentType>;
180
- workflowStepCodename: string;
175
+ readonly collection: Readonly<CollectionModels.Collection>;
176
+ readonly language: Readonly<LanguageModels.LanguageModel>;
177
+ readonly workflow: Readonly<WorkflowModels.Workflow>;
178
+ readonly contentType: Readonly<FlattenedContentType>;
179
+ readonly workflowStepCodename: string;
181
180
  } => {
182
181
  const collection = findRequired(
183
182
  environmentData.collections,
@@ -234,49 +233,53 @@ export async function exportContextFetcherAsync(config: DefaultExportContextConf
234
233
  message: `Preparing '${chalk.yellow(config.exportItems.length.toString())}' items for export`
235
234
  });
236
235
 
237
- return await processItemsAsync<SourceExportItem, ExportItem>({
238
- logger: config.logger,
239
- action: 'Preparing content items & language variants',
240
- parallelLimit: 1,
241
- itemInfo: (input) => {
242
- return {
243
- title: `${input.itemCodename} (${input.languageCodename})`,
244
- itemType: 'exportItem'
245
- };
246
- },
247
- items: exportItems,
248
- processAsync: async (requestItem, logSpinner) => {
249
- const contentItem = await getContentItemAsync(requestItem, logSpinner);
250
- const versions = await getExportItemVersionsAsync(requestItem, contentItem, logSpinner);
251
-
252
- // get shared attributes from any version
253
- const anyVersion = versions[0];
254
- if (!anyVersion) {
255
- throwErrorForItemRequest(requestItem, `Expected at least 1 version of the content item`);
256
- }
236
+ return (
237
+ await processItemsAsync<SourceExportItem, ExportItem>({
238
+ logger: config.logger,
239
+ action: 'Preparing content items & language variants',
240
+ parallelLimit: 1,
241
+ itemInfo: (input) => {
242
+ return {
243
+ title: `${input.itemCodename} (${input.languageCodename})`,
244
+ itemType: 'exportItem'
245
+ };
246
+ },
247
+ items: exportItems,
248
+ processAsync: async (requestItem, logSpinner) => {
249
+ const contentItem = await getContentItemAsync(requestItem, logSpinner);
250
+ const versions = await getExportItemVersionsAsync(requestItem, contentItem, logSpinner);
251
+
252
+ // get shared attributes from any version
253
+ const anyVersion = versions[0];
254
+ if (!anyVersion) {
255
+ throwErrorForItemRequest(requestItem, `Expected at least 1 version of the content item`);
256
+ }
257
257
 
258
- const { collection, contentType, language, workflow } = validateExportItem({
259
- sourceItem: requestItem,
260
- contentItem: contentItem,
261
- languageVariant: anyVersion.languageVariant
262
- });
258
+ const { collection, contentType, language, workflow } = validateExportItem({
259
+ sourceItem: requestItem,
260
+ contentItem: contentItem,
261
+ languageVariant: anyVersion.languageVariant
262
+ });
263
263
 
264
- return {
265
- contentItem,
266
- versions,
267
- contentType,
268
- requestItem,
269
- workflow,
270
- collection,
271
- language
272
- };
273
- }
274
- });
264
+ return {
265
+ contentItem,
266
+ versions,
267
+ contentType,
268
+ requestItem,
269
+ workflow,
270
+ collection,
271
+ language
272
+ };
273
+ }
274
+ })
275
+ )
276
+ .filter((m) => m.state === 'valid')
277
+ .map((m) => m.outputItem);
275
278
  };
276
279
 
277
280
  const getContentItemsByIdsAsync = async (itemIds: ReadonlySet<string>): Promise<readonly Readonly<ContentItemModels.ContentItem>[]> => {
278
281
  return (
279
- await processItemsAsync<string, Readonly<ContentItemModels.ContentItem> | undefined>({
282
+ await processItemsAsync<string, Readonly<ContentItemModels.ContentItem>>({
280
283
  logger: config.logger,
281
284
  action: 'Fetching content items',
282
285
  parallelLimit: 1,
@@ -302,16 +305,18 @@ export async function exportContextFetcherAsync(config: DefaultExportContextConf
302
305
  throw error;
303
306
  }
304
307
 
305
- return undefined;
308
+ return '404';
306
309
  }
307
310
  }
308
311
  })
309
- ).filter(isNotUndefined);
312
+ )
313
+ .filter((m) => m.state === 'valid')
314
+ .map((m) => m.outputItem);
310
315
  };
311
316
 
312
317
  const getAssetsByIdsAsync = async (itemIds: ReadonlySet<string>): Promise<readonly Readonly<AssetModels.Asset>[]> => {
313
318
  return (
314
- await processItemsAsync<string, Readonly<AssetModels.Asset> | undefined>({
319
+ await processItemsAsync<string, Readonly<AssetModels.Asset>>({
315
320
  logger: config.logger,
316
321
  action: 'Fetching assets',
317
322
  parallelLimit: 1,
@@ -337,11 +342,13 @@ export async function exportContextFetcherAsync(config: DefaultExportContextConf
337
342
  throw error;
338
343
  }
339
344
 
340
- return undefined;
345
+ return '404';
341
346
  }
342
347
  }
343
348
  })
344
- ).filter(isNotUndefined);
349
+ )
350
+ .filter((m) => m.state === 'valid')
351
+ .map((m) => m.outputItem);
345
352
  };
346
353
 
347
354
  const getItemStatesAsync = async (itemIds: ReadonlySet<string>): Promise<readonly ItemStateInSourceEnvironmentById[]> => {
@@ -45,8 +45,8 @@ export function exportManager(config: ExportConfig) {
45
45
  codename: exportItem.workflow.codename
46
46
  }
47
47
  },
48
- versions: exportItem.versions.map((version) => {
49
- return <MigrationItemVersion>{
48
+ versions: exportItem.versions.map<MigrationItemVersion>((version) => {
49
+ return {
50
50
  elements: getMigrationElements(context, exportItem.contentType, version.languageVariant.elements),
51
51
  schedule: {
52
52
  publish_time: version.languageVariant.schedule.publishTime ?? undefined,
@@ -124,10 +124,10 @@ export function exportManager(config: ExportConfig) {
124
124
  };
125
125
 
126
126
  const getMigrationElementToStore = (data: {
127
- context: ExportContext;
128
- contentType: FlattenedContentType;
129
- typeElement: FlattenedContentTypeElement;
130
- exportElement: ElementModels.ContentItemElement;
127
+ readonly context: ExportContext;
128
+ readonly contentType: FlattenedContentType;
129
+ readonly typeElement: FlattenedContentTypeElement;
130
+ readonly exportElement: ElementModels.ContentItemElement;
131
131
  }): MigrationElementTransformData => {
132
132
  try {
133
133
  return exportTransforms[data.typeElement.type]({
@@ -175,60 +175,62 @@ export function exportManager(config: ExportConfig) {
175
175
  message: `Preparing to download '${chalk.yellow(assets.length.toString())}' assets`
176
176
  });
177
177
 
178
- return await processItemsAsync<Readonly<AssetModels.Asset>, MigrationAsset>({
179
- action: 'Downloading assets',
180
- logger: logger,
181
- parallelLimit: 5,
182
- itemInfo: (input) => {
183
- return {
184
- title: input.codename,
185
- itemType: 'asset'
186
- };
187
- },
188
- items: assets,
189
- processAsync: async (asset, logSpinner) => {
190
- const assetCollection: Readonly<CollectionModels.Collection> | undefined = context.environmentData.collections.find(
191
- (m) => m.id === asset.collection?.reference?.id
192
- );
193
- const assetFolder: Readonly<AssetFolderModels.AssetFolder> | undefined = context.environmentData.assetFolders.find(
194
- (m) => m.id === asset.folder?.id
195
- );
178
+ return (
179
+ await processItemsAsync<Readonly<AssetModels.Asset>, MigrationAsset>({
180
+ action: 'Downloading assets',
181
+ logger: logger,
182
+ parallelLimit: 5,
183
+ itemInfo: (input) => {
184
+ return {
185
+ title: input.codename,
186
+ itemType: 'asset'
187
+ };
188
+ },
189
+ items: assets,
190
+ processAsync: async (asset, logSpinner) => {
191
+ const assetCollection: Readonly<CollectionModels.Collection> | undefined = context.environmentData.collections.find(
192
+ (m) => m.id === asset.collection?.reference?.id
193
+ );
194
+ const assetFolder: Readonly<AssetFolderModels.AssetFolder> | undefined = context.environmentData.assetFolders.find(
195
+ (m) => m.id === asset.folder?.id
196
+ );
196
197
 
197
- logSpinner({
198
- type: 'download',
199
- message: `${asset.url}`
200
- });
198
+ logSpinner({
199
+ type: 'download',
200
+ message: `${asset.url}`
201
+ });
201
202
 
202
- const binaryData = await getBinaryDataFromUrlAsync(asset.url);
203
+ const migrationAsset: MigrationAsset = {
204
+ filename: asset.fileName,
205
+ title: asset.title ?? '',
206
+ codename: asset.codename,
207
+ binary_data: (await getBinaryDataFromUrlAsync(asset.url)).data,
208
+ collection: assetCollection ? { codename: assetCollection.codename } : undefined,
209
+ folder: assetFolder ? { codename: assetFolder.codename } : undefined,
210
+ descriptions: asset.descriptions.map((description) => {
211
+ const language = findRequired(
212
+ context.environmentData.languages,
213
+ (language) => language.id === description.language.id,
214
+ `Could not find language with id '${chalk.red(description.language.id)}' requested by asset '${chalk.red(
215
+ asset.codename
216
+ )}'`
217
+ );
203
218
 
204
- const migrationAsset: MigrationAsset = {
205
- filename: asset.fileName,
206
- title: asset.title ?? '',
207
- codename: asset.codename,
208
- binary_data: binaryData.data,
209
- collection: assetCollection ? { codename: assetCollection.codename } : undefined,
210
- folder: assetFolder ? { codename: assetFolder.codename } : undefined,
211
- descriptions: asset.descriptions.map((description) => {
212
- const language = findRequired(
213
- context.environmentData.languages,
214
- (language) => language.id === description.language.id,
215
- `Could not find language with id '${chalk.red(description.language.id)}' requested by asset '${chalk.red(
216
- asset.codename
217
- )}'`
218
- );
219
+ return {
220
+ description: description.description ?? undefined,
221
+ language: {
222
+ codename: language.codename
223
+ }
224
+ };
225
+ })
226
+ };
219
227
 
220
- return {
221
- description: description.description ?? undefined,
222
- language: {
223
- codename: language.codename
224
- }
225
- };
226
- })
227
- };
228
-
229
- return migrationAsset;
230
- }
231
- });
228
+ return migrationAsset;
229
+ }
230
+ })
231
+ )
232
+ .filter((m) => m.state === 'valid')
233
+ .map((m) => m.outputItem);
232
234
  };
233
235
 
234
236
  return {
@@ -1,13 +1,14 @@
1
+ import { Buffer as BufferProxy } from 'buffer';
1
2
  import chalk from 'chalk';
2
- import { Logger } from '../core/index.js';
3
3
  import { promises } from 'fs';
4
+ import { Logger } from '../core/index.js';
4
5
 
5
6
  export function fileManager(logger: Logger) {
6
7
  const getFilePath = (filename: string): string => {
7
8
  return `./${filename}`;
8
9
  };
9
10
 
10
- const loadFileAsync = async (filename: string): Promise<Buffer> => {
11
+ const loadFileAsync = async (filename: string): Promise<BufferProxy> => {
11
12
  const filePath = getFilePath(filename);
12
13
 
13
14
  logger.log({
@@ -20,7 +21,7 @@ export function fileManager(logger: Logger) {
20
21
  return file;
21
22
  };
22
23
 
23
- const writeFileAsync = async (fileNameWithoutExtension: string, content: string | Buffer): Promise<void> => {
24
+ const writeFileAsync = async (fileNameWithoutExtension: string, content: string | BufferProxy): Promise<void> => {
24
25
  const filePath = getFilePath(fileNameWithoutExtension);
25
26
 
26
27
  logger.log({
@@ -1,4 +1,6 @@
1
1
  import { AssetModels, ContentItemModels, LanguageVariantModels } from '@kontent-ai/management-sdk';
2
+ import chalk from 'chalk';
3
+ import { match } from 'ts-pattern';
2
4
  import {
3
5
  AssetStateInTargetEnvironmentByCodename,
4
6
  ItemStateInTargetEnvironmentByCodename,
@@ -12,16 +14,13 @@ import {
12
14
  WorkflowStep,
13
15
  findRequired,
14
16
  is404Error,
15
- isNotUndefined,
16
17
  managementClientUtils,
17
18
  processItemsAsync,
18
19
  runMapiRequestAsync,
19
20
  workflowHelper as workflowHelperInit
20
21
  } from '../../core/index.js';
21
- import { GetFlattenedElementByCodenames, ImportContext, ImportContextConfig, ImportContextEnvironmentData } from '../import.models.js';
22
22
  import { ExtractItemByCodename, itemsExtractionProcessor } from '../../translation/index.js';
23
- import chalk from 'chalk';
24
- import { match } from 'ts-pattern';
23
+ import { GetFlattenedElementByCodenames, ImportContext, ImportContextConfig, ImportContextEnvironmentData } from '../import.models.js';
25
24
 
26
25
  interface LanguageVariantWrapper {
27
26
  readonly draftLanguageVariant: Readonly<LanguageVariantModels.ContentItemLanguageVariant> | undefined;
@@ -150,7 +149,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
150
149
  migrationItems: readonly MigrationItem[]
151
150
  ): Promise<readonly LanguageVariantWrapper[]> => {
152
151
  return (
153
- await processItemsAsync<MigrationItem, LanguageVariantWrapper | undefined>({
152
+ await processItemsAsync<MigrationItem, LanguageVariantWrapper>({
154
153
  action: 'Fetching language variants',
155
154
  logger: config.logger,
156
155
  parallelLimit: 1,
@@ -166,7 +165,7 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
166
165
 
167
166
  if (!latestLanguageVariant) {
168
167
  // there is neither published or draft version as latest version does not exist at all
169
- return undefined;
168
+ return '404';
170
169
  }
171
170
 
172
171
  if (workflowHelper.isPublishedStepById(latestLanguageVariant.workflow.stepIdentifier.id ?? '')) {
@@ -186,14 +185,16 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
186
185
  };
187
186
  }
188
187
  })
189
- ).filter(isNotUndefined);
188
+ )
189
+ .filter((m) => m.state === 'valid')
190
+ .map((m) => m.outputItem);
190
191
  };
191
192
 
192
193
  const getContentItemsByCodenamesAsync = async (
193
194
  itemCodenames: ReadonlySet<string>
194
195
  ): Promise<readonly ContentItemModels.ContentItem[]> => {
195
196
  return (
196
- await processItemsAsync<string, Readonly<ContentItemModels.ContentItem> | undefined>({
197
+ await processItemsAsync<string, Readonly<ContentItemModels.ContentItem>>({
197
198
  action: 'Fetching content items',
198
199
  logger: config.logger,
199
200
  parallelLimit: 1,
@@ -219,16 +220,18 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
219
220
  throw error;
220
221
  }
221
222
 
222
- return undefined;
223
+ return '404';
223
224
  }
224
225
  }
225
226
  })
226
- ).filter(isNotUndefined);
227
+ )
228
+ .filter((m) => m.state === 'valid')
229
+ .map((m) => m.outputItem);
227
230
  };
228
231
 
229
232
  const getAssetsByCodenamesAsync = async (assetCodenames: ReadonlySet<string>): Promise<readonly AssetModels.Asset[]> => {
230
233
  return (
231
- await processItemsAsync<string, Readonly<AssetModels.Asset> | undefined>({
234
+ await processItemsAsync<string, Readonly<AssetModels.Asset>>({
232
235
  action: 'Fetching assets',
233
236
  logger: config.logger,
234
237
  parallelLimit: 1,
@@ -254,11 +257,13 @@ export async function importContextFetcherAsync(config: ImportContextConfig) {
254
257
  throw error;
255
258
  }
256
259
 
257
- return undefined;
260
+ return '404';
258
261
  }
259
262
  }
260
263
  })
261
- ).filter(isNotUndefined);
264
+ )
265
+ .filter((m) => m.state === 'valid')
266
+ .map((m) => m.outputItem);
262
267
  };
263
268
 
264
269
  const getVariantState = (languageVariant: Readonly<LanguageVariantModels.ContentItemLanguageVariant>): LanguageVariantStateData => {