Socket
Socket
Sign inDemoInstall

xeno-test

Package Overview
Dependencies
56
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.18 to 0.0.19

dist/es2022/core/migration-models.d.ts

36

dist/es2022/core/core.models.d.ts

@@ -1,5 +0,5 @@

import { AssetModels, ContentItemModels, ElementContracts, ElementModels, LanguageVariantModels } from '@kontent-ai/management-sdk';
import { AssetModels, ContentItemModels, ElementContracts, LanguageVariantModels } from '@kontent-ai/management-sdk';
import { ProcessingFormat } from '../file-processor/index.js';
import { IParsedAsset, IParsedContentItem } from '../import/index.js';
import { ContentItemElementsIndexer, IContentItem, IContentType } from '@kontent-ai/delivery-sdk';
import { IMigrationItem, IMigrationAsset } from './migration-models.js';
export interface ICliFileConfig {

@@ -15,2 +15,3 @@ adapter?: ExportAdapter;

skipFailedItems: boolean;
contentItemsFetchMode?: ContentItemsFetchMode;
replaceInvalidLinks: boolean;

@@ -22,9 +23,17 @@ action: CliAction;

exportTypes?: string[];
exportLanguages?: string[];
exportAssets: boolean;
logLevel: LogLevel;
}
export type LogLevel = 'verbose' | 'default';
export type CliAction = 'export' | 'import';
export type ExportAdapter = 'kontentAi';
export type ItemType = 'component' | 'contentItem' | 'languageVariant' | 'asset' | 'binaryFile' | 'zipFile';
export type ActionType = 'skip' | 'save' | 'readFs' | 'writeFs' | 'download' | 'zip' | 'read' | 'archive' | 'upsert' | 'upload' | 'publish' | 'changeWorkflowStep' | 'createNewVersion' | 'fetch' | 'create' | 'publish' | 'unArchive' | 'extractBinaryData' | 'update';
export type ContentElementType = ElementModels.ElementType;
export type ItemType = 'component' | 'contentItem' | 'listContentItems' | 'languageVariant' | 'asset' | 'binaryFile' | 'zipFile' | 'count';
export type ContentItemsFetchMode = 'oneByOne' | 'listAll';
export type ActionType = 'skip' | 'save' | 'unpublish' | 'readFs' | 'writeFs' | 'download' | 'zip' | 'read' | 'archive' | 'upsert' | 'upload' | 'publish' | 'changeWorkflowStep' | 'createNewVersion' | 'fetch' | 'create' | 'publish' | 'unArchive' | 'extractBinaryData' | 'update';
export interface IErrorData {
message: string;
requestData?: string;
requestUrl?: string;
}
export interface IProcessedItem {

@@ -38,11 +47,11 @@ title: string;

assets: {
original: IParsedAsset;
original: IMigrationAsset;
imported: AssetModels.Asset;
}[];
contentItems: {
original: IParsedContentItem;
original: IMigrationItem;
imported: ContentItemModels.ContentItem;
}[];
languageVariants: {
original: IParsedContentItem;
original: IMigrationItem;
imported: LanguageVariantModels.ContentItemLanguageVariant;

@@ -74,3 +83,3 @@ }[];

importedData: IImportedData;
sourceItems: IParsedContentItem[];
sourceItems: IMigrationItem[];
}) => ElementContracts.IContentItemElementContract;

@@ -83,1 +92,10 @@ export interface IExportTransformConfig {

}
export interface IChunk<T> {
items: T[];
index: number;
}
export interface IProcessInChunksItemInfo {
title: string;
itemType: ItemType;
partA?: string;
}

@@ -1,4 +0,4 @@

import { IManagementClient, EnvironmentModels, SharedModels } from '@kontent-ai/management-sdk';
import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
import { HttpService } from '@kontent-ai/core-sdk';
import { IErrorData, IProcessInChunksItemInfo } from './core.models.js';
export declare const defaultHttpService: HttpService;

@@ -9,7 +9,12 @@ export declare function formatBytes(bytes: number): string;

export declare function getExtension(url: string): string | undefined;
export declare function printProjectAndEnvironmentInfoToConsoleAsync(client: IManagementClient<any>): Promise<EnvironmentModels.EnvironmentInformationModel>;
export declare function extractErrorMessage(error: any): string;
export declare function extractErrorData(error: any): IErrorData;
export declare function is404Error(error: any): boolean;
export declare function handleError(error: any | SharedModels.ContentManagementBaseKontentError): void;
export declare function handleError(error: any): void;
export declare function extractAssetIdFromUrl(assetUrl: string): string;
export declare function extractFilenameFromUrl(assetUrl: string): string;
export declare function processInChunksAsync<TInputItem, TOutputItem>(data: {
items: TInputItem[];
chunkSize: number;
processFunc: (item: TInputItem) => Promise<TOutputItem>;
itemInfo?: (item: TInputItem) => IProcessInChunksItemInfo;
}): Promise<TOutputItem[]>;
import { SharedModels } from '@kontent-ai/management-sdk';
import { format } from 'bytes';
import { logDebug, logErrorAndExit } from './log-helper.js';
import { logDebug, logErrorAndExit, logProcessingDebug } from './log-helper.js';
import { HttpService } from '@kontent-ai/core-sdk';

@@ -31,28 +31,22 @@ const rateExceededErrorCode = 10000;

}
export async function printProjectAndEnvironmentInfoToConsoleAsync(client) {
const environmentInformation = (await client.environmentInformation().toPromise()).data;
logDebug({
type: 'info',
message: 'Project information',
partA: environmentInformation.project.name
});
logDebug({
type: 'info',
message: 'Environment information',
partA: environmentInformation.project.environment
});
return environmentInformation.project;
}
export function extractErrorMessage(error) {
export function extractErrorData(error) {
let message = `Unknown error`;
let requestUrl = undefined;
let requestData = undefined;
if (error instanceof SharedModels.ContentManagementBaseKontentError) {
let message = `${error.message}`;
message = `${error.message}`;
requestUrl = error.originalError?.response?.config.url;
requestData = error.originalError?.response?.config.data;
for (const validationError of error.validationErrors) {
message += ` ${validationError.message}`;
}
return message;
}
if (error instanceof Error) {
return error.message;
else if (error instanceof Error) {
message = error.message;
}
return `Unknown error`;
return {
message: message,
requestData: requestData,
requestUrl: requestUrl
};
}

@@ -67,14 +61,20 @@ export function is404Error(error) {

export function handleError(error) {
if (error instanceof SharedModels.ContentManagementBaseKontentError) {
logErrorAndExit({
message: `${error.message}. Error code '${error.errorCode}'. Request Id '${error.requestId}'.${error.validationErrors.length ? ` ${error.validationErrors.map((m) => m.message).join(', ')}` : ''}`
const errorData = extractErrorData(error);
if (errorData.requestUrl) {
logDebug({
type: 'errorData',
message: errorData.requestUrl,
partA: 'Request Url'
});
}
if (error instanceof Error) {
logErrorAndExit({
message: error.message
if (errorData.requestData) {
logDebug({
type: 'errorData',
message: errorData.requestData,
partA: 'Request Data'
});
}
// unhandled error
throw error;
logErrorAndExit({
message: errorData.message
});
}

@@ -96,2 +96,40 @@ export function extractAssetIdFromUrl(assetUrl) {

}
export async function processInChunksAsync(data) {
const chunks = splitArrayIntoChunks(data.items, data.chunkSize);
const outputItems = [];
let processingIndex = 0;
for (const chunk of chunks) {
await Promise.all(chunk.items.map((item) => {
processingIndex++;
if (data.itemInfo) {
const itemInfo = data.itemInfo(item);
logProcessingDebug({
index: processingIndex,
totalCount: data.items.length,
itemType: itemInfo.itemType,
title: itemInfo.title,
partA: itemInfo.partA
});
}
return data.processFunc(item).then((output) => {
outputItems.push(output);
});
}));
}
return outputItems;
}
function splitArrayIntoChunks(items, chunkSize) {
if (!items.length) {
return [];
}
const chunks = [];
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
chunks.push({
index: i,
items: chunk
});
}
return chunks;
}
//# sourceMappingURL=global-helper.js.map
export * from './core.models.js';
export * from './global-helper.js';
export * from './log-helper.js';
export * from './migration-models.js';
export * from './core.models.js';
export * from './global-helper.js';
export * from './log-helper.js';
export * from './migration-models.js';
//# sourceMappingURL=index.js.map

@@ -1,3 +0,3 @@

import { ActionType, ItemType } from './core.models.js';
export type DebugType = 'error' | 'warning' | 'info' | ActionType;
import { ActionType, ItemType, LogLevel } from './core.models.js';
export type DebugType = 'error' | 'warning' | 'info' | 'errorData' | ActionType;
export declare function logErrorAndExit(data: {

@@ -11,2 +11,3 @@ message: string;

title: string;
partA?: string;
}): void;

@@ -22,7 +23,8 @@ export declare function logDebug(data: {

}): void;
export declare function logItemAction(actionType: ActionType, itemType: ItemType, data: {
export declare function logItemAction(logLevel: LogLevel, actionType: ActionType, itemType: ItemType, data: {
language?: string;
workflowStep?: string;
workflow?: string;
title: string;
codename?: string;
}): void;

@@ -5,4 +5,3 @@ import colors from 'colors';

type: 'error',
message: data.message,
partA: 'Stopping process'
message: data.message
});

@@ -12,11 +11,11 @@ process.exit(1);

export function logProcessingDebug(data) {
console.log(`[${colors.bgYellow(colors.black(`${data.index}/${data.totalCount}`))}][${colors.yellow(data.itemType)}]: Starts processing ${data.title}`);
console.log(`[${colors.cyan(`${data.index}/${data.totalCount}`)}][${colors.yellow(data.itemType)}]${data.partA ? `[${colors.green(data.partA)}]` : ''}: ${data.title}`);
}
export function logDebug(data) {
let typeColor = colors.green;
if (data.type === 'error') {
if (data.type === 'error' || data.type === 'errorData') {
typeColor = colors.red;
}
else if (data.type === 'info') {
typeColor = colors.blue;
typeColor = colors.cyan;
}

@@ -26,14 +25,16 @@ else if (data.type === 'warning') {

}
console.log(`[${typeColor(data.type)}]${data.partA ? `[${colors.yellow(data.partA)}]` : ''}${data.partB ? `[${colors.cyan(data.partB)}]` : ''}${data.partC ? `[${colors.red(data.partC)}]` : ''}${data.partD ? `[${colors.magenta(data.partD)}]` : ''}${data.performance ? `[${colors.bgYellow(colors.black(data.performance))}]` : ''}: ${data.message}`);
console.log(`[${typeColor(data.type)}]${data.partA ? `[${colors.yellow(data.partA)}]` : ''}${data.partB ? `[${colors.cyan(data.partB)}]` : ''}${data.partC ? `[${colors.red(data.partC)}]` : ''}${data.partD ? `[${colors.yellow(data.partD)}]` : ''}${data.performance ? `[${colors.bgYellow(colors.black(data.performance))}]` : ''}: ${data.message}`);
}
export function logItemAction(actionType, itemType, data) {
logDebug({
type: actionType,
message: data.title,
partA: itemType,
partB: data.codename,
partC: data.language,
partD: data.workflowStep
});
export function logItemAction(logLevel, actionType, itemType, data) {
if (logLevel === 'verbose') {
logDebug({
type: actionType,
message: data.title,
partA: itemType,
partB: data.codename,
partC: data.language,
partD: data.workflow && data.workflowStep ? `${data.workflow}>${data.workflowStep}` : undefined
});
}
}
//# sourceMappingURL=log-helper.js.map
import { IContentType, ILanguage, IContentItem, IDeliveryClient } from '@kontent-ai/delivery-sdk';
import { IExportConfig, IExportContentItem } from '../../../export.models.js';
import { IMigrationItem } from '../../../../core/index.js';
import { IKontentAiExportAdapterConfig } from '../../../export.models.js';
export declare class ExportContentItemHelper {
exportContentItemsAsync(deliveryClient: IDeliveryClient, config: IExportConfig, types: IContentType[], languages: ILanguage[]): Promise<{
exportContentItems: IExportContentItem[];
private readonly fetchCountForTypesChunkSize;
private readonly exportContentItemsChunkSize;
exportContentItemsAsync(deliveryClient: IDeliveryClient, config: IKontentAiExportAdapterConfig, types: IContentType[], languages: ILanguage[]): Promise<{
exportContentItems: IMigrationItem[];
deliveryContentItems: IContentItem[];
}>;
private getTotalNumberOfItemsToExportAsync;
private maptoExportContentItem;
private logItem;
private getTypesToExport;
private getLanguagesToExport;
private getTypeLanguageMaps;
}
export declare const exportContentItemHelper: ExportContentItemHelper;

@@ -1,6 +0,10 @@

import { logDebug } from '../../../../core/index.js';
import { logDebug, logProcessingDebug, processInChunksAsync } from '../../../../core/index.js';
import { translationHelper } from '../../../../translation/index.js';
import colors from 'colors';
export class ExportContentItemHelper {
fetchCountForTypesChunkSize = 100;
exportContentItemsChunkSize = 100;
async exportContentItemsAsync(deliveryClient, config, types, languages) {
const typesToExport = this.getTypesToExport(config, types);
const languagesToExport = this.getLanguagesToExport(config, languages);
const contentItems = [];

@@ -14,4 +18,6 @@ if (config.customItemsExport) {

for (const contentItem of customItems) {
this.logItem(`${contentItem.system.name} | ${contentItem.system.type}`, 'contentItem', 'fetch', {
language: contentItem.system.language
logDebug({
type: 'fetch',
message: `${contentItem.system.name} | ${contentItem.system.type}`,
partA: contentItem.system.language
});

@@ -24,11 +30,31 @@ contentItems.push(contentItem);

type: 'info',
message: `Exporting content items of types`,
partA: typesToExport.map((m) => m.system.codename).join(', ')
message: `Exporting content items of '${colors.yellow(languagesToExport.length.toString())}' content types and '${colors.yellow(languagesToExport.length.toString())}' languages`
});
for (const type of typesToExport) {
for (const language of languages) {
logDebug({
type: 'info',
message: `Calculating total items to export`
});
const totalItemsToExport = await this.getTotalNumberOfItemsToExportAsync({
typesToExport: typesToExport,
deliveryClient: deliveryClient,
languagesToExport: languagesToExport
});
let exportedItemsCount = 0;
let extractedComponentsCount = 0;
logDebug({
type: 'info',
message: `Found '${colors.yellow(totalItemsToExport.toString())}' items in total to export`
});
const typeLanguageMaps = this.getTypeLanguageMaps({
languagesToExport: languagesToExport,
typesToExport: typesToExport
});
await processInChunksAsync({
chunkSize: this.exportContentItemsChunkSize,
items: typeLanguageMaps,
processFunc: async (typeLanguageMap) => {
await deliveryClient
.itemsFeed()
.type(type.system.codename)
.equalsFilter('system.language', language.system.codename)
.type(typeLanguageMap.type.system.codename)
.equalsFilter('system.language', typeLanguageMap.language.system.codename)
.toAllPromise({

@@ -38,6 +64,11 @@ responseFetched: (response) => {

for (const contentItem of response.data.items) {
this.logItem(`${contentItem.system.name}`, 'contentItem', 'fetch', {
language: contentItem.system.language
this.logItem({
index: exportedItemsCount + 1,
totalCount: totalItemsToExport,
title: contentItem.system.name,
language: contentItem.system.language,
itemType: 'contentItem'
});
contentItems.push(contentItem);
exportedItemsCount++;
}

@@ -47,6 +78,4 @@ // add components to result

if (!contentItems.find((m) => m.system.codename === codename)) {
this.logItem(`${contentItem.system.name}`, 'component', 'fetch', {
language: contentItem.system.language
});
contentItems.push(contentItem);
extractedComponentsCount++;
}

@@ -57,3 +86,7 @@ }

}
}
});
logDebug({
type: 'info',
message: `Adding '${colors.yellow(extractedComponentsCount.toString())}' components to export result`
});
}

@@ -65,4 +98,32 @@ return {

}
async getTotalNumberOfItemsToExportAsync(data) {
let totalItemsCount = 0;
await processInChunksAsync({
chunkSize: this.fetchCountForTypesChunkSize,
itemInfo: (type) => {
return {
itemType: 'count',
title: type.system.name,
partA: type.system.codename
};
},
items: data.typesToExport,
processFunc: async (type) => {
for (const language of data.languagesToExport) {
const response = await data.deliveryClient
.items()
.type(type.system.codename)
.equalsFilter('system.language', language.system.codename)
.limitParameter(1)
.depthParameter(0)
.includeTotalCountParameter()
.toPromise();
totalItemsCount += response.data.pagination.totalCount ?? 0;
}
}
});
return totalItemsCount;
}
maptoExportContentItem(item, items, types, config) {
return {
const migrationItem = {
system: {

@@ -74,5 +135,4 @@ codename: item.system.codename,

collection: item.system.collection,
id: item.system.id,
last_modified: item.system.lastModified,
workflow_step: item.system.workflowStep ?? undefined
workflow_step: item.system.workflowStep ?? undefined,
workflow: item.system.workflow ?? undefined
},

@@ -98,9 +158,11 @@ elements: Object.entries(item.elements).map(([key, element]) => {

};
return migrationItem;
}
logItem(title, itemType, actionType, data) {
logDebug({
type: actionType,
message: title,
partA: itemType,
partB: data.language
logItem(data) {
logProcessingDebug({
itemType: data.itemType,
title: `${data.title}`,
index: data.index,
totalCount: data.totalCount,
partA: data.language
});

@@ -116,3 +178,2 @@ }

if (config.exportTypes.find((m) => m.toLowerCase() === type.system.codename.toLowerCase())) {
// content type can be exported
filteredTypes.push(type);

@@ -123,4 +184,29 @@ }

}
getLanguagesToExport(config, languages) {
const filteredLanguages = [];
if (!config?.exportLanguages?.length) {
// export all languages
return languages;
}
for (const language of languages) {
if (config.exportLanguages.find((m) => m.toLowerCase() === language.system.codename.toLowerCase())) {
filteredLanguages.push(language);
}
}
return filteredLanguages;
}
getTypeLanguageMaps(data) {
const maps = [];
for (const type of data.typesToExport) {
for (const language of data.languagesToExport) {
maps.push({
type: type,
language: language
});
}
}
return maps;
}
}
export const exportContentItemHelper = new ExportContentItemHelper();
//# sourceMappingURL=export-content-item.helper.js.map

@@ -1,9 +0,9 @@

import { IExportAdapter, IExportAdapterResult, IExportConfig } from '../../export.models.js';
import { IExportAdapter, IExportAdapterResult, IKontentAiExportAdapterConfig } from '../../export.models.js';
export declare class KontentAiExportAdapter implements IExportAdapter {
private config;
constructor(config: IExportConfig);
constructor(config: IKontentAiExportAdapterConfig);
exportAsync(): Promise<IExportAdapterResult>;
private getLanguagesAsync;
private getContentTypesAsync;
private getAllLanguagesAsync;
private getAllContentTypesAsync;
private getDeliveryClient;
}
import { createDeliveryClient } from '@kontent-ai/delivery-sdk';
import colors from 'colors';
import { exportContentItemHelper } from './helpers/export-content-item.helper.js';
import { defaultHttpService, defaultRetryStrategy } from '../../../core/global-helper.js';
import { exportAssetsHelper } from './helpers/export-assets-item.helper.js';
import { exportAssetsHelper } from './helpers/export-assets.helper.js';
import { logDebug } from '../../../core/index.js';

@@ -12,13 +13,19 @@ export class KontentAiExportAdapter {

async exportAsync() {
logDebug({ type: 'info', message: 'Environment id', partA: this.config.environmentId });
logDebug({ type: 'info', message: 'Using Secure API', partA: this.config.isSecure ? 'true' : 'false' });
logDebug({ type: 'info', message: 'Using Preview API', partA: this.config.isPreview ? 'true' : 'false' });
logDebug({
type: 'info',
message: `Preparing export from environment ${colors.yellow(this.config.environmentId)}`
});
logDebug({ type: 'info', message: this.config.isSecure ? `Using Secure API` : `Not using Secure API` });
logDebug({
type: 'info',
message: this.config.isPreview ? `Using Preview API` : `Using Delivery API`
});
const deliveryClient = this.getDeliveryClient();
const types = await this.getContentTypesAsync(deliveryClient);
const languages = await this.getLanguagesAsync(deliveryClient);
const contentItemsResult = await exportContentItemHelper.exportContentItemsAsync(deliveryClient, this.config, types, languages);
const allTypes = await this.getAllContentTypesAsync(deliveryClient);
const allLanguages = await this.getAllLanguagesAsync(deliveryClient);
const contentItemsResult = await exportContentItemHelper.exportContentItemsAsync(deliveryClient, this.config, allTypes, allLanguages);
const assets = [];
if (this.config.exportAssets) {
logDebug({ type: 'info', message: `Extracting assets referenced by content items` });
assets.push(...(await exportAssetsHelper.extractAssetsAsync(contentItemsResult.deliveryContentItems, types)));
assets.push(...(await exportAssetsHelper.extractAssetsAsync(contentItemsResult.deliveryContentItems, allTypes)));
}

@@ -33,7 +40,7 @@ else {

}
async getLanguagesAsync(deliveryClient) {
async getAllLanguagesAsync(deliveryClient) {
const response = await deliveryClient.languages().toAllPromise();
return response.data.items;
}
async getContentTypesAsync(deliveryClient) {
async getAllContentTypesAsync(deliveryClient) {
const response = await deliveryClient.types().toAllPromise();

@@ -40,0 +47,0 @@ return response.data.items;

@@ -1,4 +0,3 @@

/// <reference types="node" resolution-mode="require"/>
import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
import { ContentElementType, IExportTransformConfig } from '../core/index.js';
import { IExportTransformConfig, IMigrationItem, IMigrationAsset } from '../core/index.js';
import { IContentItem, IDeliveryClient } from '@kontent-ai/delivery-sdk';

@@ -9,4 +8,4 @@ export interface IExportAdapter {

export interface IExportAdapterResult {
items: IExportContentItem[];
assets: IExportAsset[];
items: IMigrationItem[];
assets: IMigrationAsset[];
}

@@ -19,3 +18,3 @@ export interface IExportFilter {

}
export interface IExportConfig {
export interface IKontentAiExportAdapterConfig {
environmentId: string;

@@ -29,2 +28,3 @@ managementApiKey?: string;

exportTypes?: string[];
exportLanguages?: string[];
exportAssets: boolean;

@@ -35,26 +35,1 @@ retryStrategy?: IRetryStrategyOptions;

}
export interface IExportAsset {
url: string;
extension: string;
assetId: string;
filename: string;
binaryData: Buffer | Blob;
}
export interface IExportElement {
value: string | undefined | string[];
type: ContentElementType;
codename: string;
}
export interface IExportContentItem {
system: {
codename: string;
id: string;
name: string;
language: string;
type: string;
collection: string;
last_modified?: string;
workflow_step?: string;
};
elements: IExportElement[];
}

@@ -1,4 +0,4 @@

import { IParsedAsset } from '../../import/index.js';
import { AssetsParseData, AssetsTransformData, FileBinaryData } from '../file-processor.models.js';
import { BaseAssetProcessorService } from '../base-asset-processor.service.js';
import { IMigrationAsset } from '../../core/index.js';
export declare class AssetCsvProcessorService extends BaseAssetProcessorService {

@@ -9,3 +9,3 @@ private readonly csvDelimiter;

transformAssetsAsync(data: AssetsTransformData): Promise<FileBinaryData>;
parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]>;
parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]>;
private getAssetZipFilename;

@@ -12,0 +12,0 @@ private geCsvParser;

@@ -1,2 +0,2 @@

import { IParsedAsset } from '../../import/index.js';
import { IMigrationAsset } from '../../core/index.js';
import { AssetsParseData, AssetsTransformData, FileBinaryData } from '../file-processor.models.js';

@@ -8,4 +8,4 @@ import { BaseAssetProcessorService } from '../base-asset-processor.service.js';

transformAssetsAsync(data: AssetsTransformData): Promise<FileBinaryData>;
parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]>;
parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]>;
private getAssetZipFilename;
}

@@ -1,2 +0,2 @@

import { IParsedAsset } from '../import/index.js';
import { IMigrationAsset } from '../core/index.js';
import { IAssetFormatService, AssetsTransformData, FileBinaryData, AssetsParseData } from './file-processor.models.js';

@@ -6,4 +6,4 @@ export declare abstract class BaseAssetProcessorService implements IAssetFormatService {

abstract transformAssetsAsync(data: AssetsTransformData): Promise<FileBinaryData>;
abstract parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]>;
abstract parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]>;
protected getSystemAssetFields(): string[];
}

@@ -1,9 +0,10 @@

import { IImportContentType, IImportContentTypeElement, IParsedContentItem } from '../import/index.js';
import { IImportContentType, IImportContentTypeElement } from '../import/index.js';
import { IItemFormatService, ItemsTransformData, ItemsParseData, FileBinaryData } from './file-processor.models.js';
import { IMigrationItem } from '../core/index.js';
export declare abstract class BaseItemProcessorService implements IItemFormatService {
abstract name: string;
abstract transformContentItemsAsync(data: ItemsTransformData): Promise<FileBinaryData>;
abstract parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]>;
abstract parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]>;
protected getSystemContentItemFields(): string[];
protected getElement(types: IImportContentType[], contentItemType: string, elementCodename: string): IImportContentTypeElement;
}
import { logErrorAndExit } from '../core/index.js';
import colors from 'colors';
export class BaseItemProcessorService {

@@ -10,3 +11,3 @@ getSystemContentItemFields() {

logErrorAndExit({
message: `Could not find content type '${contentItemType}'`
message: `Could not find content type '${colors.red(contentItemType)}'`
});

@@ -17,3 +18,3 @@ }

logErrorAndExit({
message: `Could not find element with codename '${elementCodename}' for type '${type.contentTypeCodename}'`
message: `Could not find element with codename '${colors.red(elementCodename)}' for type '${colors.yellow(type.contentTypeCodename)}'`
});

@@ -20,0 +21,0 @@ }

/// <reference types="node" resolution-mode="require"/>
import { IExportContentItem, IExportAsset } from '../export/index.js';
import { IImportContentType, IParsedAsset, IParsedContentItem } from '../import/index.js';
import { IMigrationAsset, IMigrationItem } from '../core/index.js';
import { IImportContentType } from '../import/index.js';
import { ZipPackage } from './zip-package.class.js';

@@ -14,3 +14,3 @@ /**

readonly zip: ZipPackage;
readonly items: IExportContentItem[];
readonly items: IMigrationItem[];
};

@@ -24,7 +24,7 @@ export type ItemsParseData = {

transformContentItemsAsync(data: ItemsTransformData): Promise<FileBinaryData>;
parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]>;
parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]>;
}
export type AssetsTransformData = {
readonly zip: ZipPackage;
readonly assets: IExportAsset[];
readonly assets: IMigrationAsset[];
};

@@ -37,3 +37,3 @@ export type AssetsParseData = {

transformAssetsAsync(data: AssetsTransformData): Promise<FileBinaryData>;
parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]>;
parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]>;
}

@@ -40,0 +40,0 @@ export interface IExtractedBinaryFileData {

@@ -20,2 +20,6 @@ import colors from 'colors';

const itemsZipFile = await JSZip.loadAsync(data.items.file, {});
logDebug({
type: 'info',
message: 'Parsing items zip data'
});
result.importData.items.push(...(await data.items.formatService.parseContentItemsAsync({

@@ -25,6 +29,2 @@ zip: new ZipPackage(itemsZipFile),

})));
logDebug({
type: 'info',
message: 'Parsing items zip data'
});
}

@@ -37,5 +37,2 @@ if (data.assets) {

const assetsZipFile = await JSZip.loadAsync(data.assets.file, {});
result.importData.assets.push(...(await data.assets.formatService.parseAssetsAsync({
zip: new ZipPackage(assetsZipFile)
})));
logDebug({

@@ -45,6 +42,9 @@ type: 'info',

});
result.importData.assets.push(...(await data.assets.formatService.parseAssetsAsync({
zip: new ZipPackage(assetsZipFile)
})));
}
logDebug({
type: 'info',
message: 'Parsing completed'
message: `Parsing completed. Parsed '${colors.yellow(result.importData.items.length.toString())}' items and '${colors.yellow(result.importData.assets.length.toString())}' assets`
});

@@ -51,0 +51,0 @@ return result;

@@ -1,3 +0,3 @@

import { IImportContentTypeElement, IParsedContentItem } from '../../../import/index.js';
import { IExportContentItem } from '../../../export/index.js';
import { IImportContentTypeElement } from '../../../import/index.js';
import { IMigrationItem } from '../../../core/index.js';
export interface IJsonElements {

@@ -13,4 +13,4 @@ [elementCodename: string]: string | string[] | undefined;

collection: string;
last_modified?: string;
workflow_step?: string;
workflow?: string;
};

@@ -21,5 +21,5 @@ elements: IJsonElements;

typeCodename: string;
items: IExportContentItem[];
items: IMigrationItem[];
}
export declare function mapToJsonItem(item: IExportContentItem): IJsonItem;
export declare function parseJsonItem(item: IJsonItem, getElement: (typeCodename: string, elementCodename: string) => IImportContentTypeElement): IParsedContentItem;
export declare function mapToJsonItem(item: IMigrationItem): IJsonItem;
export declare function parseJsonItem(item: IJsonItem, getElement: (typeCodename: string, elementCodename: string) => IImportContentTypeElement): IMigrationItem;

@@ -11,6 +11,6 @@ export function mapToJsonItem(item) {

language: item.system.language,
last_modified: item.system.last_modified,
name: item.system.name,
type: item.system.type,
workflow_step: item.system.workflow_step
workflow_step: item.system.workflow_step,
workflow: item.system.workflow
},

@@ -36,6 +36,6 @@ elements: jsonElements

language: item.system.language,
last_modified: item.system.last_modified,
name: item.system.name,
type: item.system.type,
workflow_step: item.system.workflow_step
workflow_step: item.system.workflow_step,
workflow: item.system.workflow
},

@@ -42,0 +42,0 @@ elements: elements

@@ -1,4 +0,4 @@

import { IParsedContentItem } from '../../import/index.js';
import { FileBinaryData, ItemsParseData, ItemsTransformData } from '../file-processor.models.js';
import { BaseItemProcessorService } from '../base-item-processor.service.js';
import { IMigrationItem } from '../../core/index.js';
export declare class ItemCsvProcessorService extends BaseItemProcessorService {

@@ -8,3 +8,3 @@ private readonly csvDelimiter;

transformContentItemsAsync(data: ItemsTransformData): Promise<FileBinaryData>;
parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]>;
parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]>;
private getTypeWrappers;

@@ -11,0 +11,0 @@ private mapToCsvItem;

@@ -50,5 +50,5 @@ import { parse } from 'csv-parse';

language: '',
last_modified: '',
name: '',
workflow_step: ''
workflow_step: '',
workflow: ''
},

@@ -107,5 +107,5 @@ elements: []

language: item.system.language,
last_modified: item.system.last_modified,
name: item.system.name,
workflow_step: item.system.workflow_step
workflow_step: item.system.workflow_step,
workflow: item.system.workflow
};

@@ -112,0 +112,0 @@ for (const elementCodename of typeWrapper.elementCodenames) {

@@ -1,4 +0,4 @@

import { IParsedContentItem } from '../../import/index.js';
import { FileBinaryData, ItemsParseData, ItemsTransformData } from '../file-processor.models.js';
import { BaseItemProcessorService } from '../base-item-processor.service.js';
import { IMigrationItem } from '../../core/index.js';
export declare class ItemJsonJoinedProcessorService extends BaseItemProcessorService {

@@ -8,3 +8,3 @@ private readonly itemsFileName;

transformContentItemsAsync(data: ItemsTransformData): Promise<FileBinaryData>;
parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]>;
parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]>;
}

@@ -1,9 +0,9 @@

import { IParsedContentItem } from '../../import/index.js';
import { FileBinaryData, ItemsParseData, ItemsTransformData } from '../file-processor.models.js';
import { BaseItemProcessorService } from '../base-item-processor.service.js';
import { IMigrationItem } from '../../core/index.js';
export declare class ItemJsonProcessorService extends BaseItemProcessorService {
readonly name: string;
transformContentItemsAsync(data: ItemsTransformData): Promise<FileBinaryData>;
parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]>;
parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]>;
private getTypeWrappers;
}
import { ManagementClient } from '@kontent-ai/management-sdk';
import { IImportedData } from '../../core/index.js';
import { IParsedAsset } from '../import.models.js';
import { IImportedData, IMigrationAsset, LogLevel } from '../../core/index.js';
export declare function getImportAssetsHelper(config: {
logLevel: LogLevel;
}): ImportAssetsHelper;
export declare class ImportAssetsHelper {
private readonly logLevel;
private readonly importAssetsChunkSize;
constructor(logLevel: LogLevel);
importAssetsAsync(data: {
managementClient: ManagementClient;
assets: IParsedAsset[];
assets: IMigrationAsset[];
importedData: IImportedData;
}): Promise<void>;
}
export declare const importAssetsHelper: ImportAssetsHelper;

@@ -1,92 +0,102 @@

import { is404Error, logItemAction, logProcessingDebug } from '../../core/index.js';
import { is404Error, logItemAction, processInChunksAsync } from '../../core/index.js';
import mime from 'mime';
export function getImportAssetsHelper(config) {
return new ImportAssetsHelper(config.logLevel);
}
export class ImportAssetsHelper {
logLevel;
importAssetsChunkSize = 3;
constructor(logLevel) {
this.logLevel = logLevel;
}
async importAssetsAsync(data) {
let assetIndex = 1;
for (const asset of data.assets) {
logProcessingDebug({
index: assetIndex,
totalCount: data.assets.length,
itemType: 'asset',
title: `${asset.filename}`
});
// use asset id as external id
const assetExternalId = asset.assetId;
// check if asset with given external id already exists
let existingAsset;
try {
// when target project is the same as source project, the id of asset would be the same
// and such asset should not be imported again
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(asset.assetId)
.toPromise()
.then((m) => m.data);
}
catch (error) {
if (!is404Error(error)) {
throw error;
await processInChunksAsync({
chunkSize: this.importAssetsChunkSize,
items: data.assets,
itemInfo: (input) => {
return {
itemType: 'asset',
title: input.filename,
partA: input.extension
};
},
processFunc: async (asset) => {
// use asset id as external id
const assetExternalId = asset.assetId;
// check if asset with given external id already exists
let existingAsset;
try {
// when target project is the same as source project, the id of asset would be the same
// and such assets should not be imported again
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(asset.assetId)
.toPromise()
.then((m) => m.data);
}
}
try {
// check if asset with given external id was already created
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(assetExternalId)
.toPromise()
.then((m) => m.data);
}
catch (error) {
if (!is404Error(error)) {
throw error;
catch (error) {
if (!is404Error(error)) {
throw error;
}
}
try {
// check if asset with given external id was already created
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(assetExternalId)
.toPromise()
.then((m) => m.data);
}
catch (error) {
if (!is404Error(error)) {
throw error;
}
}
if (!existingAsset) {
// only import asset if it didn't exist
logItemAction(this.logLevel, 'upload', 'binaryFile', {
title: asset.filename
});
const uploadedBinaryFile = await data.managementClient
.uploadBinaryFile()
.withData({
binaryData: asset.binaryData,
contentType: mime.getType(asset.filename) ?? '',
filename: asset.filename
})
.toPromise();
logItemAction(this.logLevel, 'create', 'asset', {
title: asset.filename
});
const createdAsset = await data.managementClient
.addAsset()
.withData((builder) => {
return {
file_reference: {
id: uploadedBinaryFile.data.id,
type: 'internal'
},
external_id: assetExternalId
};
})
.toPromise()
.then((m) => m.data);
data.importedData.assets.push({
imported: createdAsset,
original: asset
});
}
else {
data.importedData.assets.push({
imported: existingAsset,
original: asset
});
logItemAction(this.logLevel, 'skip', 'asset', {
title: asset.filename
});
}
}
if (!existingAsset) {
// only import asset if it wasn't already there
const uploadedBinaryFile = await data.managementClient
.uploadBinaryFile()
.withData({
binaryData: asset.binaryData,
contentType: mime.getType(asset.filename) ?? '',
filename: asset.filename
})
.toPromise();
logItemAction('upload', 'binaryFile', {
title: asset.filename
});
const createdAsset = await data.managementClient
.addAsset()
.withData((builder) => {
return {
file_reference: {
id: uploadedBinaryFile.data.id,
type: 'internal'
},
external_id: assetExternalId
};
})
.toPromise()
.then((m) => m.data);
data.importedData.assets.push({
imported: createdAsset,
original: asset
});
logItemAction('create', 'asset', {
title: asset.filename
});
}
else {
data.importedData.assets.push({
imported: existingAsset,
original: asset
});
logItemAction('skip', 'asset', {
title: asset.filename
});
}
assetIndex++;
}
});
}
}
export const importAssetsHelper = new ImportAssetsHelper();
//# sourceMappingURL=import-assets.helper.js.map
import { CollectionModels, ContentItemModels, ManagementClient } from '@kontent-ai/management-sdk';
import { IImportedData } from '../../core/index.js';
import { IParsedContentItem } from '../import.models.js';
import { IImportedData, LogLevel, ContentItemsFetchMode, IMigrationItem } from '../../core/index.js';
export declare function getImportContentItemHelper(config: {
logLevel: LogLevel;
skipFailedItems: boolean;
fetchMode: ContentItemsFetchMode;
}): ImportContentItemHelper;
export declare class ImportContentItemHelper {
private readonly logLevel;
private readonly skipFailedItems;
private readonly fetchMode;
private readonly importContentItemChunkSize;
constructor(logLevel: LogLevel, skipFailedItems: boolean, fetchMode: ContentItemsFetchMode);
importContentItemsAsync(data: {
managementClient: ManagementClient;
parsedContentItems: IParsedContentItem[];
migrationContentItems: IMigrationItem[];
collections: CollectionModels.Collection[];
importedData: IImportedData;
config: {
skipFailedItems: boolean;
};
}): Promise<ContentItemModels.ContentItem[]>;
private fetchContentItemsOneByOneAsync;
private fetchAllContentItemsAsync;
private importContentItemAsync;

@@ -18,2 +26,1 @@ private shouldUpdateContentItem;

}
export declare const importContentItemHelper: ImportContentItemHelper;

@@ -1,36 +0,56 @@

import { extractErrorMessage, is404Error, logItemAction, logDebug, logErrorAndExit, logProcessingDebug } from '../../core/index.js';
import { extractErrorData, is404Error, logItemAction, logDebug, logErrorAndExit, processInChunksAsync } from '../../core/index.js';
import { parsedItemsHelper } from './parsed-items-helper.js';
import colors from 'colors';
export function getImportContentItemHelper(config) {
return new ImportContentItemHelper(config.logLevel, config.skipFailedItems, config.fetchMode);
}
export class ImportContentItemHelper {
logLevel;
skipFailedItems;
fetchMode;
importContentItemChunkSize = 3;
constructor(logLevel, skipFailedItems, fetchMode) {
this.logLevel = logLevel;
this.skipFailedItems = skipFailedItems;
this.fetchMode = fetchMode;
}
async importContentItemsAsync(data) {
const preparedItems = [];
let itemIndex = 0;
const categorizedParsedItems = parsedItemsHelper.categorizeParsedItems(data.parsedContentItems);
logItemAction('skip', 'contentItem', {
title: `Skipping '${categorizedParsedItems.componentItems.length}' because they represent component items`
const categorizedParsedItems = parsedItemsHelper.categorizeParsedItems(data.migrationContentItems);
logItemAction(this.logLevel, 'skip', 'contentItem', {
title: `Skipping '${colors.yellow(categorizedParsedItems.componentItems.length.toString())}' because they represent component items`
});
for (const importContentItem of categorizedParsedItems.regularItems) {
itemIndex++;
logProcessingDebug({
index: itemIndex,
totalCount: categorizedParsedItems.regularItems.length,
itemType: 'contentItem',
title: `'${importContentItem.system.name}' of type '${importContentItem.system.type}'`
let fetchedContentItems = [];
logDebug({
message: `Fetching items via '${colors.yellow(this.fetchMode)}' mode`,
type: 'info'
});
if (this.fetchMode === 'oneByOne') {
fetchedContentItems = await this.fetchContentItemsOneByOneAsync({
categorizedParsedItems: categorizedParsedItems,
managementClient: data.managementClient
});
}
else {
fetchedContentItems = await this.fetchAllContentItemsAsync({ managementClient: data.managementClient });
}
const preparedItems = [];
for (const parsedItem of data.migrationContentItems) {
try {
await this.importContentItemAsync({
const contentItem = await this.importContentItemAsync({
managementClient: data.managementClient,
collections: data.collections,
importContentItem: importContentItem,
importContentItem: parsedItem,
importedData: data.importedData,
parsedContentItems: data.parsedContentItems,
preparedItems: preparedItems
migrationContentItems: data.migrationContentItems,
fetchedContentItems: fetchedContentItems
});
preparedItems.push(contentItem);
}
catch (error) {
if (data.config.skipFailedItems) {
if (this.skipFailedItems) {
logDebug({
type: 'error',
message: `Failed to import content item`,
partA: importContentItem.system.codename,
partB: extractErrorMessage(error)
partA: parsedItem.system.codename,
partB: extractErrorData(error).message
});

@@ -45,9 +65,62 @@ }

}
async fetchContentItemsOneByOneAsync(data) {
const contentItems = [];
await processInChunksAsync({
chunkSize: this.importContentItemChunkSize,
items: data.categorizedParsedItems.regularItems,
itemInfo: (input) => {
return {
itemType: 'contentItem',
title: input.system.name,
partA: input.system.type
};
},
processFunc: async (migrationContentItem) => {
try {
logItemAction(this.logLevel, 'fetch', 'contentItem', {
title: `${migrationContentItem.system.name}`,
codename: migrationContentItem.system.codename
});
const contentItem = await data.managementClient
.viewContentItem()
.byItemCodename(migrationContentItem.system.codename)
.toPromise()
.then((m) => m.data);
contentItems.push(contentItem);
}
catch (error) {
if (!is404Error(error)) {
throw error;
}
}
}
});
return contentItems;
}
async fetchAllContentItemsAsync(data) {
return (await data.managementClient
.listContentItems()
.withListQueryConfig({
responseFetched: (response, token) => {
logItemAction(this.logLevel, 'fetch', 'listContentItems', {
title: `Fetched '${colors.yellow(response.data.items.length.toString())}' items`
});
}
})
.toAllPromise()).data.items;
}
async importContentItemAsync(data) {
const preparedContentItemResult = await this.prepareContentItemAsync(data.managementClient, data.importContentItem, data.importedData);
data.preparedItems.push(preparedContentItemResult.contentItem);
const preparedContentItemResult = await this.prepareContentItemAsync(data.managementClient, data.importContentItem, data.fetchedContentItems);
data.importedData.contentItems.push({
original: data.importContentItem,
imported: preparedContentItemResult.contentItem
});
// check if name should be updated, no other changes are supported
if (preparedContentItemResult.status === 'itemAlreadyExists') {
if (this.shouldUpdateContentItem(data.importContentItem, preparedContentItemResult.contentItem, data.collections)) {
const upsertedContentItem = await data.managementClient
logItemAction(this.logLevel, 'upsert', 'contentItem', {
title: `${data.importContentItem.system.name}`,
codename: data.importContentItem.system.codename
});
await data.managementClient
.upsertContentItem()

@@ -63,40 +136,19 @@ .byItemCodename(data.importContentItem.system.codename)

.then((m) => m.data);
logItemAction('upsert', 'contentItem', {
title: `Upserting item '${upsertedContentItem.name}'`,
codename: data.importContentItem.system.codename
});
}
else {
logItemAction('skip', 'contentItem', {
title: `Item '${data.importContentItem.system.name}' already exists`,
codename: data.importContentItem.system.codename
});
}
}
return preparedContentItemResult.contentItem;
}
shouldUpdateContentItem(parsedContentItem, contentItem, collections) {
const collection = collections.find((m) => m.codename === parsedContentItem.system.collection);
shouldUpdateContentItem(migrationContentItem, contentItem, collections) {
const collection = collections.find((m) => m.codename === migrationContentItem.system.collection);
if (!collection) {
logErrorAndExit({
message: `Invalid collection '${parsedContentItem.system.collection}'`
message: `Invalid collection '${migrationContentItem.system.collection}'`
});
}
return (parsedContentItem.system.name !== contentItem.name ||
parsedContentItem.system.collection !== collection.codename);
return (migrationContentItem.system.name !== contentItem.name ||
migrationContentItem.system.collection !== collection.codename);
}
async prepareContentItemAsync(managementClient, parsedContentItem, importedData) {
try {
const contentItem = await managementClient
.viewContentItem()
.byItemCodename(parsedContentItem.system.codename)
.toPromise()
.then((m) => m.data);
logItemAction('fetch', 'contentItem', {
title: `Loading item '${contentItem.name}'`,
codename: contentItem.codename
});
importedData.contentItems.push({
original: parsedContentItem,
imported: contentItem
});
async prepareContentItemAsync(managementClient, migrationContentItem, fetchedContentItems) {
const contentItem = fetchedContentItems.find((m) => m.codename === migrationContentItem.system.codename);
if (contentItem) {
return {

@@ -107,36 +159,22 @@ contentItem: contentItem,

}
catch (error) {
if (is404Error(error)) {
const contentItem = await managementClient
.addContentItem()
.withData({
name: parsedContentItem.system.name,
type: {
codename: parsedContentItem.system.type
},
codename: parsedContentItem.system.codename,
collection: {
codename: parsedContentItem.system.collection
}
})
.toPromise()
.then((m) => m.data);
importedData.contentItems.push({
original: parsedContentItem,
imported: contentItem
});
logItemAction('create', 'contentItem', {
title: `Creating item '${contentItem.name}'`,
codename: contentItem.codename
});
return {
contentItem: contentItem,
status: 'created'
};
const createdContentItem = await managementClient
.addContentItem()
.withData({
name: migrationContentItem.system.name,
type: {
codename: migrationContentItem.system.type
},
codename: migrationContentItem.system.codename,
collection: {
codename: migrationContentItem.system.collection
}
throw error;
}
})
.toPromise()
.then((m) => m.data);
return {
contentItem: createdContentItem,
status: 'created'
};
}
}
export const importContentItemHelper = new ImportContentItemHelper();
//# sourceMappingURL=import-content-item.helper.js.map
import { WorkflowModels, ContentItemModels, ManagementClient } from '@kontent-ai/management-sdk';
import { IImportedData } from '../../core/index.js';
import { IParsedContentItem } from '../import.models.js';
import { IImportedData, LogLevel, IMigrationItem } from '../../core/index.js';
export declare function getImportLanguageVariantstemHelper(config: {
logLevel: LogLevel;
skipFailedItems: boolean;
}): ImportLanguageVariantHelper;
export declare class ImportLanguageVariantHelper {
private readonly logLevel;
private readonly skipFailedItems;
private readonly importContentItemChunkSize;
private readonly importWorkflowHelper;
constructor(logLevel: LogLevel, skipFailedItems: boolean);
importLanguageVariantsAsync(data: {
managementClient: ManagementClient;
importContentItems: IParsedContentItem[];
importContentItems: IMigrationItem[];
workflows: WorkflowModels.Workflow[];
preparedContentItems: ContentItemModels.ContentItem[];
importedData: IImportedData;
config: {
skipFailedItems: boolean;
};
}): Promise<void>;

@@ -21,2 +26,1 @@ private importLanguageVariantAsync;

}
export declare const importLanguageVariantHelper: ImportLanguageVariantHelper;

@@ -1,50 +0,66 @@

import { extractErrorMessage, is404Error, logItemAction, logDebug, logErrorAndExit, logProcessingDebug } from '../../core/index.js';
import { importWorkflowHelper } from './import-workflow.helper.js';
import { extractErrorData, is404Error, logItemAction, logDebug, logErrorAndExit, processInChunksAsync } from '../../core/index.js';
import { getImportWorkflowHelper } from './import-workflow.helper.js';
import { parsedItemsHelper } from './parsed-items-helper.js';
import { translationHelper } from '../../translation/index.js';
import colors from 'colors';
export function getImportLanguageVariantstemHelper(config) {
return new ImportLanguageVariantHelper(config.logLevel, config.skipFailedItems);
}
export class ImportLanguageVariantHelper {
logLevel;
skipFailedItems;
importContentItemChunkSize = 3;
importWorkflowHelper;
constructor(logLevel, skipFailedItems) {
this.logLevel = logLevel;
this.skipFailedItems = skipFailedItems;
this.importWorkflowHelper = getImportWorkflowHelper({ logLevel: logLevel });
}
async importLanguageVariantsAsync(data) {
const categorizedParsedItems = parsedItemsHelper.categorizeParsedItems(data.importContentItems);
logItemAction('skip', 'languageVariant', {
title: `Skipping '${categorizedParsedItems.componentItems.length}' because they represent component items`
logItemAction(this.logLevel, 'skip', 'languageVariant', {
title: `Skipping '${colors.yellow(categorizedParsedItems.componentItems.length.toString())}' because they represent components`
});
let itemIndex = 0;
for (const importContentItem of categorizedParsedItems.regularItems) {
try {
itemIndex++;
logProcessingDebug({
index: itemIndex,
totalCount: categorizedParsedItems.regularItems.length,
await processInChunksAsync({
chunkSize: this.importContentItemChunkSize,
items: categorizedParsedItems.regularItems,
itemInfo: (input) => {
return {
itemType: 'languageVariant',
title: `'${importContentItem.system.name}' of type '${importContentItem.system.type}' in language '${importContentItem.system.language}'`
});
const preparedContentItem = data.preparedContentItems.find((m) => m.codename === importContentItem.system.codename);
if (!preparedContentItem) {
logErrorAndExit({
message: `Invalid content item for codename '${importContentItem.system.codename}'`
title: input.system.name,
partA: input.system.language
};
},
processFunc: async (importContentItem) => {
try {
const preparedContentItem = data.preparedContentItems.find((m) => m.codename === importContentItem.system.codename);
if (!preparedContentItem) {
logErrorAndExit({
message: `Invalid content item for codename '${colors.red(importContentItem.system.codename)}'`
});
}
await this.importLanguageVariantAsync({
importContentItem,
preparedContentItem,
managementClient: data.managementClient,
importContentItems: data.importContentItems,
workflows: data.workflows,
importedData: data.importedData
});
}
await this.importLanguageVariantAsync({
importContentItem,
preparedContentItem,
managementClient: data.managementClient,
importContentItems: data.importContentItems,
workflows: data.workflows,
importedData: data.importedData
});
}
catch (error) {
if (data.config.skipFailedItems) {
logDebug({
type: 'error',
message: `Failed to import language variant '${importContentItem.system.name}' in language '${importContentItem.system.language}'`,
partA: importContentItem.system.codename,
partB: extractErrorMessage(error)
});
catch (error) {
if (this.skipFailedItems) {
logDebug({
type: 'error',
message: `Failed to import language variant '${colors.red(importContentItem.system.name)}' in language '${colors.red(importContentItem.system.language)}'`,
partA: importContentItem.system.codename,
partB: extractErrorData(error).message
});
}
else {
throw error;
}
}
else {
throw error;
}
}
}
});
}

@@ -57,2 +73,8 @@ async importLanguageVariantAsync(data) {

});
logItemAction(this.logLevel, 'upsert', 'languageVariant', {
title: `${data.preparedContentItem.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
const upsertedLanguageVariant = await data.managementClient

@@ -74,11 +96,5 @@ .upsertLanguageVariant()

});
logItemAction('upsert', 'languageVariant', {
title: `${data.preparedContentItem.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
// set workflow of language variant
if (data.importContentItem.system.workflow_step) {
await importWorkflowHelper.setWorkflowOfLanguageVariantAsync(data.managementClient, data.importContentItem.system.workflow_step, data.importContentItem, data.workflows);
if (data.importContentItem.system.workflow_step && data.importContentItem.system.workflow) {
await this.importWorkflowHelper.setWorkflowOfLanguageVariantAsync(data.managementClient, data.importContentItem.system.workflow, data.importContentItem.system.workflow_step, data.importContentItem, data.workflows);
}

@@ -88,3 +104,14 @@ }

let languageVariantOfContentItem;
const workflowCodename = data.importContentItem.system.workflow;
if (!workflowCodename) {
throw Error(`Item with codename '${data.importContentItem.system.codename}' does not have workflow property assigned`);
}
const workflow = this.importWorkflowHelper.getWorkflowByCodename(workflowCodename, data.workflows);
try {
logItemAction(this.logLevel, 'fetch', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
languageVariantOfContentItem = await data.managementClient

@@ -96,11 +123,5 @@ .viewLanguageVariant()

.then((m) => m.data);
logItemAction('fetch', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
if (!languageVariantOfContentItem) {
logErrorAndExit({
message: `Invalid langauge variant for item '${data.importContentItem.system.codename}' of type '${data.importContentItem.system.type}' and language '${data.importContentItem.system.language}'`
message: `Invalid langauge variant for item '${colors.red(data.importContentItem.system.codename)}' of type '${colors.yellow(data.importContentItem.system.type)}' and language '${colors.yellow(data.importContentItem.system.language)}'`
});

@@ -118,2 +139,8 @@ }

if (this.isLanguageVariantPublished(languageVariantOfContentItem, data.workflows)) {
logItemAction(this.logLevel, 'createNewVersion', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
// create new version

@@ -125,3 +152,6 @@ await data.managementClient

.toPromise();
logItemAction('createNewVersion', 'languageVariant', {
}
else if (this.isLanguageVariantArchived(languageVariantOfContentItem, data.workflows)) {
// change workflow step to draft
logItemAction(this.logLevel, 'unArchive', 'languageVariant', {
title: `${data.importContentItem.system.name}`,

@@ -132,8 +162,4 @@ language: data.importContentItem.system.language,

});
}
else if (this.isLanguageVariantArchived(languageVariantOfContentItem, data.workflows)) {
// change workflow step to draft
if (languageVariantOfContentItem.workflow.stepIdentifier.id) {
const workflow = importWorkflowHelper.getWorkflowForGivenStepById(languageVariantOfContentItem.workflow.stepIdentifier.id, data.workflows);
const newWorkflowStep = workflow.steps[0];
const firstWorkflowStep = workflow.steps?.[0];
if (firstWorkflowStep) {
await data.managementClient

@@ -143,10 +169,4 @@ .changeWorkflowStepOfLanguageVariant()

.byLanguageCodename(data.importContentItem.system.language)
.byWorkflowStepCodename(newWorkflowStep.codename)
.byWorkflowStepCodename(firstWorkflowStep.codename)
.toPromise();
logItemAction('unArchive', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
}

@@ -176,3 +196,3 @@ }

logErrorAndExit({
message: `Missing import contract for element '${element.codename}' `
message: `Missing import contract for element '${colors.red(element.codename)}' `
});

@@ -183,3 +203,2 @@ }

}
export const importLanguageVariantHelper = new ImportLanguageVariantHelper();
//# sourceMappingURL=import-language-variant.helper.js.map
import { ManagementClient, WorkflowModels } from '@kontent-ai/management-sdk';
import { IParsedContentItem } from '../import.models.js';
import { LogLevel, IMigrationItem } from '../../core/index.js';
export declare function getImportWorkflowHelper(config: {
logLevel: LogLevel;
}): ImportWorkflowHelper;
export declare class ImportWorkflowHelper {
private readonly defaultWorkflowCodename;
getWorkflowForGivenStepById(workflowId: string, workflows: WorkflowModels.Workflow[]): WorkflowModels.Workflow;
setWorkflowOfLanguageVariantAsync(managementClient: ManagementClient, workflowStepCodename: string, importContentItem: IParsedContentItem, workflows: WorkflowModels.Workflow[]): Promise<void>;
private readonly logLevel;
constructor(logLevel: LogLevel);
getWorkflowByCodename(workflowCodename: string, workflows: WorkflowModels.Workflow[]): WorkflowModels.Workflow;
setWorkflowOfLanguageVariantAsync(managementClient: ManagementClient, workflowCodename: string, workflowStepCodename: string, importContentItem: IMigrationItem, workflows: WorkflowModels.Workflow[]): Promise<void>;
private doesWorkflowStepCodenameRepresentPublishedStep;
private doesWorkflowStepCodenameRepresentArchivedStep;
private doesWorkflowStepCodenameRepresentScheduledStep;
private getWorkflowForGivenStep;
private getWorkflowForGivenStepByCodename;
private doesWorkflowStepExist;
}
export declare const importWorkflowHelper: ImportWorkflowHelper;

@@ -1,30 +0,38 @@

import { logItemAction, logErrorAndExit } from '../../core/index.js';
import { SharedModels } from '@kontent-ai/management-sdk';
import { logItemAction, logErrorAndExit, logDebug } from '../../core/index.js';
import colors from 'colors';
export function getImportWorkflowHelper(config) {
return new ImportWorkflowHelper(config.logLevel);
}
export class ImportWorkflowHelper {
defaultWorkflowCodename = 'Default';
getWorkflowForGivenStepById(workflowId, workflows) {
return this.getWorkflowForGivenStep(workflows, (workflow) => {
if (workflow.archivedStep.id === workflowId) {
return true;
}
if (workflow.publishedStep.id === workflowId) {
return true;
}
if (workflow.scheduledStep.id === workflowId) {
return true;
}
const step = workflow.steps.find((m) => m.id === workflowId);
if (step) {
return true;
}
return false;
});
logLevel;
constructor(logLevel) {
this.logLevel = logLevel;
}
async setWorkflowOfLanguageVariantAsync(managementClient, workflowStepCodename, importContentItem, workflows) {
getWorkflowByCodename(workflowCodename, workflows) {
const workflow = workflows.find((m) => m.codename?.toLowerCase() === workflowCodename.toLowerCase());
if (!workflow) {
const errorMessages = [
`Workflow with codename '${workflowCodename}' does not exist in target project`,
`Available workflows are: ${workflows.map((m) => m.codename).join(', ')}`
];
throw Error(errorMessages.join('. '));
}
return workflow;
}
async setWorkflowOfLanguageVariantAsync(managementClient, workflowCodename, workflowStepCodename, importContentItem, workflows) {
const workflow = this.getWorkflowByCodename(workflowCodename, workflows);
// check if workflow step exists in target project
if (!this.doesWorkflowStepExist(workflowStepCodename, workflows)) {
if (!this.doesWorkflowStepExist(workflowCodename, workflowStepCodename, workflows)) {
logErrorAndExit({
message: `Could not change workflow step for item '${importContentItem.system.codename}' (${importContentItem.system.name}) because step with codename '${workflowStepCodename}' does not exist in target project.`
message: `Could not change workflow step for item '${colors.yellow(importContentItem.system.codename)}' because step with codename '${colors.red(workflowStepCodename)}' does not exist`
});
}
if (this.doesWorkflowStepCodenameRepresentPublishedStep(workflowStepCodename, workflows)) {
logItemAction(this.logLevel, 'publish', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
await managementClient

@@ -36,4 +44,6 @@ .publishLanguageVariant()

.toPromise();
logItemAction('publish', 'languageVariant', {
title: `${importContentItem.system.name}`,
}
else if (this.doesWorkflowStepCodenameRepresentScheduledStep(workflowStepCodename, workflows)) {
logItemAction(this.logLevel, 'skip', 'languageVariant', {
title: `Skipping scheduled workflow step for item '${colors.yellow(importContentItem.system.name)}'`,
language: importContentItem.system.language,

@@ -44,5 +54,35 @@ codename: importContentItem.system.codename,

}
else if (this.doesWorkflowStepCodenameRepresentScheduledStep(workflowStepCodename, workflows)) {
logItemAction('skip', 'languageVariant', {
title: `Skipping scheduled workflow step for item '${importContentItem.system.name}'`,
else if (this.doesWorkflowStepCodenameRepresentArchivedStep(workflowStepCodename, workflows)) {
// unpublish the language variant first if published
// there is no way to determine if language variant is published via MAPI
// so we have to always try unpublishing first and catching possible errors
try {
logItemAction(this.logLevel, 'unpublish', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
await managementClient
.unpublishLanguageVariant()
.byItemCodename(importContentItem.system.codename)
.byLanguageCodename(importContentItem.system.language)
.withoutData()
.toPromise();
}
catch (error) {
if (error instanceof SharedModels.ContentManagementBaseKontentError) {
if (this.logLevel === 'verbose') {
logDebug({
type: 'info',
message: `Unpublish failed, but this may be expected behavior as we cannot determine the published state of language variant. Error received: ${error.message}`
});
}
}
else {
throw error;
}
}
logItemAction(this.logLevel, 'archive', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,

@@ -52,5 +92,2 @@ codename: importContentItem.system.codename,

});
}
else if (this.doesWorkflowStepCodenameRepresentArchivedStep(workflowStepCodename, workflows)) {
const workflow = this.getWorkflowForGivenStepByCodename(workflowStepCodename, workflows);
await managementClient

@@ -69,11 +106,4 @@ .changeWorkflowOfLanguageVariant()

.toPromise();
logItemAction('archive', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
}
else {
const workflow = this.getWorkflowForGivenStepByCodename(workflowStepCodename, workflows);
if (workflow.codename === workflowStepCodename) {

@@ -83,2 +113,8 @@ // item is already in the target workflow step

else {
logItemAction(this.logLevel, 'changeWorkflowStep', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
await managementClient

@@ -97,8 +133,2 @@ .changeWorkflowOfLanguageVariant()

.toPromise();
logItemAction('changeWorkflowStep', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
}

@@ -131,53 +161,20 @@ }

}
getWorkflowForGivenStep(workflows, workflowMatcher) {
const matchedWorkflow = workflows.find((workflow) => workflowMatcher(workflow));
if (matchedWorkflow) {
return matchedWorkflow;
doesWorkflowStepExist(workflowCodename, stepCodename, workflows) {
const workflow = this.getWorkflowByCodename(workflowCodename, workflows);
if (workflow.archivedStep.codename === stepCodename) {
return true;
}
const defaultWorkflow = workflows.find((m) => m.codename.toLowerCase() === this.defaultWorkflowCodename.toLowerCase());
if (!defaultWorkflow) {
logErrorAndExit({
message: `Missing default workflow`
});
if (workflow.publishedStep.codename === stepCodename) {
return true;
}
return defaultWorkflow;
}
getWorkflowForGivenStepByCodename(stepCodename, workflows) {
return this.getWorkflowForGivenStep(workflows, (workflow) => {
if (workflow.archivedStep.codename === stepCodename) {
return true;
}
if (workflow.publishedStep.codename === stepCodename) {
return true;
}
if (workflow.scheduledStep.codename === stepCodename) {
return true;
}
const step = workflow.steps.find((m) => m.codename === stepCodename);
if (step) {
return true;
}
return false;
});
}
doesWorkflowStepExist(stepCodename, workflows) {
for (const workflow of workflows) {
if (workflow.archivedStep.codename === stepCodename) {
return true;
}
if (workflow.publishedStep.codename === stepCodename) {
return true;
}
if (workflow.scheduledStep.codename === stepCodename) {
return true;
}
const step = workflow.steps.find((m) => m.codename === stepCodename);
if (step) {
return true;
}
if (workflow.scheduledStep.codename === stepCodename) {
return true;
}
const step = workflow.steps.find((m) => m.codename === stepCodename);
if (step) {
return true;
}
return false;
}
}
export const importWorkflowHelper = new ImportWorkflowHelper();
//# sourceMappingURL=import-workflow.helper.js.map

@@ -1,9 +0,9 @@

import { IParsedContentItem } from "../import.models.js";
import { IMigrationItem } from '../../core/index.js';
export interface ICategorizedParsedItems {
componentItems: IParsedContentItem[];
regularItems: IParsedContentItem[];
componentItems: IMigrationItem[];
regularItems: IMigrationItem[];
}
export declare class ParsedItemsHelper {
categorizeParsedItems(items: IParsedContentItem[]): ICategorizedParsedItems;
categorizeParsedItems(items: IMigrationItem[]): ICategorizedParsedItems;
}
export declare const parsedItemsHelper: ParsedItemsHelper;

@@ -1,5 +0,5 @@

/// <reference types="node" resolution-mode="require"/>
import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
import { ContentElementType } from '../core/index.js';
import { MigrationElementType, ContentItemsFetchMode, IMigrationItem, LogLevel, IMigrationAsset } from '../core/index.js';
export interface IImportConfig {
logLevel?: LogLevel;
managementApiKey: string;

@@ -10,5 +10,6 @@ skipFailedItems: boolean;

environmentId: string;
contentItemsFetchMode?: ContentItemsFetchMode;
canImport?: {
contentItem?: (item: IParsedContentItem) => boolean | Promise<boolean>;
asset?: (item: IParsedAsset) => boolean | Promise<boolean>;
contentItem?: (item: IMigrationItem) => boolean | Promise<boolean>;
asset?: (item: IMigrationAsset) => boolean | Promise<boolean>;
};

@@ -22,15 +23,6 @@ }

}
export interface IParsedAssetRecord {
assetId: string;
filename: string;
extension: string;
url: string;
}
export interface IParsedAsset extends IParsedAssetRecord {
binaryData: Buffer | Blob | undefined;
}
export interface IImportSource {
importData: {
items: IParsedContentItem[];
assets: IParsedAsset[];
items: IMigrationItem[];
assets: IMigrationAsset[];
};

@@ -43,22 +35,5 @@ }

}
export interface IParsedElement {
value: string | undefined | string[];
type: ContentElementType;
codename: string;
}
export interface IParsedContentItem {
system: {
codename: string;
name: string;
language: string;
type: string;
collection: string;
last_modified?: string;
workflow_step?: string;
};
elements: IParsedElement[];
}
export interface IImportContentTypeElement {
codename: string;
type: ContentElementType;
type: MigrationElementType;
}

@@ -65,0 +40,0 @@ export interface IImportContentType {

@@ -6,3 +6,7 @@ import { IImportedData } from '../core/index.js';

private readonly managementClient;
private readonly importAssetsHelper;
private readonly importContentItemHelper;
private readonly importLanguageVariantHelper;
constructor(config: IImportConfig);
printInfoAsync(): Promise<void>;
getImportContentTypesAsync(): Promise<IImportContentType[]>;

@@ -12,5 +16,5 @@ importAsync(sourceData: IImportSource): Promise<IImportedData>;

private getDataToImport;
private importParsedContentItemsAsync;
private importmigrationContentItemAsync;
private getWorkflowsAsync;
private getCollectionsAsync;
}

@@ -1,12 +0,16 @@

import { ManagementClient } from '@kontent-ai/management-sdk';
import { defaultRetryStrategy, printProjectAndEnvironmentInfoToConsoleAsync, defaultHttpService, logDebug, logErrorAndExit } from '../core/index.js';
import { importAssetsHelper } from './helpers/import-assets.helper.js';
import { importContentItemHelper } from './helpers/import-content-item.helper.js';
import { importLanguageVariantHelper } from './helpers/import-language-variant.helper.js';
import { createManagementClient } from '@kontent-ai/management-sdk';
import { defaultRetryStrategy, defaultHttpService, logDebug, logErrorAndExit } from '../core/index.js';
import { getImportAssetsHelper } from './helpers/import-assets.helper.js';
import { getImportContentItemHelper } from './helpers/import-content-item.helper.js';
import { getImportLanguageVariantstemHelper } from './helpers/import-language-variant.helper.js';
import colors from 'colors';
export class ImportService {
config;
managementClient;
importAssetsHelper;
importContentItemHelper;
importLanguageVariantHelper;
constructor(config) {
this.config = config;
this.managementClient = new ManagementClient({
this.managementClient = createManagementClient({
apiKey: config.managementApiKey,

@@ -18,8 +22,21 @@ baseUrl: config.baseUrl,

});
this.importAssetsHelper = getImportAssetsHelper({ logLevel: config.logLevel ?? 'default' });
this.importContentItemHelper = getImportContentItemHelper({
logLevel: config.logLevel ?? 'default',
skipFailedItems: config.skipFailedItems,
fetchMode: config?.contentItemsFetchMode ?? 'oneByOne'
});
this.importLanguageVariantHelper = getImportLanguageVariantstemHelper({
logLevel: config.logLevel ?? 'default',
skipFailedItems: config.skipFailedItems
});
}
async getImportContentTypesAsync() {
async printInfoAsync() {
const environmentInformation = (await this.managementClient.environmentInformation().toPromise()).data;
logDebug({
type: 'info',
message: `Fetching content types from environment`
message: `Importing into '${colors.yellow(environmentInformation.project.environment)}' environment of project '${colors.yellow(environmentInformation.project.name)}'`
});
}
async getImportContentTypesAsync() {
const contentTypes = (await this.managementClient.listContentTypes().toAllPromise()).data.items;

@@ -29,7 +46,7 @@ const contentTypeSnippets = (await this.managementClient.listContentTypeSnippets().toAllPromise()).data.items;

type: 'info',
message: `Fetched '${contentTypes.length}' content types`
message: `Fetched '${colors.yellow(contentTypes.length.toString())}' content types`
});
logDebug({
type: 'info',
message: `Fetched '${contentTypeSnippets.length}' content type snippets`
message: `Fetched '${colors.yellow(contentTypeSnippets.length.toString())}' content type snippets`
});

@@ -52,3 +69,2 @@ return [

};
await printProjectAndEnvironmentInfoToConsoleAsync(this.managementClient);
// this is an optional step where users can exclude certain objects from being imported

@@ -63,3 +79,3 @@ const dataToImport = this.getDataToImport(sourceData);

});
await importAssetsHelper.importAssetsAsync({
await this.importAssetsHelper.importAssetsAsync({
managementClient: this.managementClient,

@@ -82,3 +98,3 @@ assets: dataToImport.importData.assets,

});
await this.importParsedContentItemsAsync(dataToImport.importData.items, importedData);
await this.importmigrationContentItemAsync(dataToImport.importData.items, importedData);
}

@@ -113,3 +129,3 @@ else {

logErrorAndExit({
message: `Could not find content type snippet for element. This snippet is referenced in type '${contentType.codename}'`
message: `Could not find content type snippet for element. This snippet is referenced in type '${colors.red(contentType.codename)}'`
});

@@ -173,3 +189,3 @@ }

type: 'info',
message: `Removed '${removedAssets.toString()}' assets from import`
message: `Removed '${colors.yellow(removedAssets.toString())}' assets from import`
});

@@ -180,3 +196,3 @@ }

type: 'info',
message: `Removed '${removedContentItems.toString()}' content items from import`
message: `Removed '${colors.yellow(removedContentItems.toString())}' content items from import`
});

@@ -186,25 +202,19 @@ }

}
async importParsedContentItemsAsync(parsedContentItems, importedData) {
async importmigrationContentItemAsync(migrationContentItem, importedData) {
const workflows = await this.getWorkflowsAsync();
const collections = await this.getCollectionsAsync();
// first prepare content items
const preparedContentItems = await importContentItemHelper.importContentItemsAsync({
const preparedContentItems = await this.importContentItemHelper.importContentItemsAsync({
managementClient: this.managementClient,
collections: collections,
importedData: importedData,
parsedContentItems: parsedContentItems,
config: {
skipFailedItems: this.config.skipFailedItems
}
migrationContentItems: migrationContentItem
});
// then process language variants
await importLanguageVariantHelper.importLanguageVariantsAsync({
await this.importLanguageVariantHelper.importLanguageVariantsAsync({
managementClient: this.managementClient,
importContentItems: parsedContentItems,
importContentItems: migrationContentItem,
importedData: importedData,
preparedContentItems: preparedContentItems,
workflows: workflows,
config: {
skipFailedItems: this.config.skipFailedItems
}
workflows: workflows
});

@@ -211,0 +221,0 @@ }

@@ -17,2 +17,4 @@ #!/usr/bin/env node

.describe('e', 'environmentId')
.alias('fm', 'fetchMode')
.describe('fm', 'Fetch mode. One of: "oneByOne" | "listAll')
.alias('mapi', 'apiKey')

@@ -45,3 +47,5 @@ .describe('mapi', 'Management API Key')

.alias('et', 'exportTypes')
.describe('et', 'Can be used to export only selected content types. Expects CSV of type codenames. If not provided, all content items of all types are exported')
.describe('et', 'CSV of content type codenames of which content items will be exported. If not provided, all content items of all types are exported')
.alias('el', 'exportLanguages')
.describe('el', 'CSV of language codenames of which content items will be exported. If not provided, all content items of all types are exported')
.help('h')

@@ -71,2 +75,3 @@ .alias('h', 'help').argv;

exportTypes: config.exportTypes,
exportLanguages: config.exportLanguages,
exportAssets: config.exportAssets

@@ -112,4 +117,6 @@ });

const importToolkit = new ImportToolkit({
logLevel: config.logLevel,
skipFailedItems: config.skipFailedItems,
baseUrl: config.baseUrl,
contentItemsFetchMode: config.contentItemsFetchMode,
environmentId: config.environmentId,

@@ -180,4 +187,6 @@ managementApiKey: config.managementApiKey,

const adapter = getOptionalArgumentValue(resolvedArgs, 'adapter');
const fetchMode = getOptionalArgumentValue(resolvedArgs, 'fetchMode');
let mappedFormat = 'csv';
let mappedAdapter = 'kontentAi';
let mappedFetchMode = 'oneByOne';
if (format?.toLowerCase() === 'csv'.toLowerCase()) {

@@ -209,2 +218,8 @@ mappedFormat = 'csv';

}
if (fetchMode?.toLowerCase() === 'listAll'.toLowerCase()) {
mappedFetchMode = 'listAll';
}
else {
mappedFetchMode = 'oneByOne';
}
const config = {

@@ -228,3 +243,5 @@ action: action,

adapter: mappedAdapter,
format: mappedFormat
format: mappedFormat,
contentItemsFetchMode: mappedFetchMode,
logLevel: getOptionalArgumentValue(resolvedArgs, 'logLevel')?.toLowerCase() === 'verbose' ? 'verbose' : 'default'
};

@@ -236,5 +253,3 @@ return config;

};
run()
.then((m) => { })
.catch((err) => {
run().catch((err) => {
handleError(err);

@@ -241,0 +256,0 @@ });

@@ -9,3 +9,3 @@ import { promises } from 'fs';

type: 'readFs',
message: `Reading FS`,
message: `Reading file system`,
partA: filePath

@@ -20,3 +20,3 @@ });

type: 'writeFs',
message: `Storing on FS`,
message: `Storing on file system`,
partA: filePath

@@ -23,0 +23,0 @@ });

@@ -13,2 +13,3 @@ import { FileProcessorService } from '../file-processor/index.js';

const importService = new ImportService(this.config);
await importService.printInfoAsync();
// prepare content types

@@ -37,2 +38,3 @@ const contentTypes = await importService.getImportContentTypesAsync();

const importService = new ImportService(this.config);
await importService.printInfoAsync();
// prepare content types

@@ -39,0 +41,0 @@ const contentTypes = await importService.getImportContentTypesAsync();

export * from './import-toolkit.class.js';
export * from './export-toolkit.class.js';
export * from './external-migration.class.js';
export * from './import-toolkit.class.js';
export * from './export-toolkit.class.js';
export * from './external-migration.class.js';
//# sourceMappingURL=index.js.map
import { ContentItemElementsIndexer, IContentItem, IContentType } from '@kontent-ai/delivery-sdk';
import { ElementContracts } from '@kontent-ai/management-sdk';
import { IParsedContentItem } from '../import/index.js';
import { ContentElementType, IExportTransformConfig, IImportedData } from '../core/core.models.js';
import { IExportTransformConfig, IImportedData } from '../core/core.models.js';
import { IMigrationItem, MigrationElementType } from '../core/index.js';
export declare class ElementTranslationHelper {

@@ -24,3 +24,3 @@ private readonly linkCodenameAttributeName;

}): string | string[] | undefined;
transformToImportValue(value: string | string[] | undefined, elementCodename: string, type: ContentElementType, importedData: IImportedData, sourceItems: IParsedContentItem[]): ElementContracts.IContentItemElementContract | undefined;
transformToImportValue(value: string | string[] | undefined, elementCodename: string, type: MigrationElementType, importedData: IImportedData, sourceItems: IMigrationItem[]): ElementContracts.IContentItemElementContract | undefined;
private parseArrayValue;

@@ -27,0 +27,0 @@ private processExportRichTextHtmlValue;

@@ -7,2 +7,3 @@ import { validate } from 'uuid';

import { logDebug, logErrorAndExit } from '../core/index.js';
import colors from 'colors';
export class ElementTranslationHelper {

@@ -155,3 +156,3 @@ linkCodenameAttributeName = 'data-manager-link-codename';

logErrorAndExit({
message: `Could not find component item with codename '${componentCodename}'`
message: `Could not find component item with codename '${colors.red(componentCodename)}'`
});

@@ -260,5 +261,3 @@ }

type: 'warning',
message: `Could not find content item with id '${id}' referenced as a link in
Rich text element '${data.richTextElement.name}' in item '${data.item.system.name}'
and language '${data.item.system.language}'. Replacing link with plain text.`
message: `Could not find content item with id '${colors.red(id)}' referenced as a link in Rich text element '${colors.yellow(data.richTextElement.name)}' in item '${colors.yellow(data.item.system.name)}' and language '${colors.yellow(data.item.system.language)}'. Replacing link with plain text.`
});

@@ -269,5 +268,3 @@ }

type: 'warning',
message: `Could not find content item with id '${id}' referenced as a link
in Rich text element '${data.richTextElement.name}' in item '${data.item.system.name}'
and language '${data.item.system.language}'. This may be fixed by enabling 'replaceInvalidLinks' option.`
message: `Could not find content item with id '${colors.red(id)}' referenced as a link in Rich text element '${colors.yellow(data.richTextElement.name)}' in item '${colors.yellow(data.item.system.name)}' and language '${colors.yellow(data.item.system.language)}'. This may be fixed by enabling 'replaceInvalidLinks' option.`
});

@@ -352,3 +349,3 @@ }

type: 'warning',
message: `Could not find content item with codename '${codename}'. This item was referenced as a link in Rich text element.`
message: `Could not find content item with codename '${colors.red(codename)}'. This item was referenced as a link in Rich text element.`
});

@@ -355,0 +352,0 @@ }

@@ -1,11 +0,5 @@

import {
AssetModels,
ContentItemModels,
ElementContracts,
ElementModels,
LanguageVariantModels
} from '@kontent-ai/management-sdk';
import { AssetModels, ContentItemModels, ElementContracts, LanguageVariantModels } from '@kontent-ai/management-sdk';
import { ProcessingFormat } from '../file-processor/index.js';
import { IParsedAsset, IParsedContentItem } from '../import/index.js';
import { ContentItemElementsIndexer, IContentItem, IContentType } from '@kontent-ai/delivery-sdk';
import { IMigrationItem, IMigrationAsset } from './migration-models.js';

@@ -22,2 +16,3 @@ export interface ICliFileConfig {

skipFailedItems: boolean;
contentItemsFetchMode?: ContentItemsFetchMode;
replaceInvalidLinks: boolean;

@@ -29,8 +24,20 @@ action: CliAction;

exportTypes?: string[];
exportLanguages?: string[];
exportAssets: boolean;
logLevel: LogLevel;
}
export type LogLevel = 'verbose' | 'default';
export type CliAction = 'export' | 'import';
export type ExportAdapter = 'kontentAi';
export type ItemType = 'component' | 'contentItem' | 'languageVariant' | 'asset' | 'binaryFile' | 'zipFile';
export type ItemType =
| 'component'
| 'contentItem'
| 'listContentItems'
| 'languageVariant'
| 'asset'
| 'binaryFile'
| 'zipFile'
| 'count';
export type ContentItemsFetchMode = 'oneByOne' | 'listAll';

@@ -40,2 +47,3 @@ export type ActionType =

| 'save'
| 'unpublish'
| 'readFs'

@@ -59,3 +67,7 @@ | 'writeFs'

export type ContentElementType = ElementModels.ElementType;
export interface IErrorData {
message: string;
requestData?: string;
requestUrl?: string;
}

@@ -71,11 +83,11 @@ export interface IProcessedItem {

assets: {
original: IParsedAsset;
original: IMigrationAsset;
imported: AssetModels.Asset;
}[];
contentItems: {
original: IParsedContentItem;
original: IMigrationItem;
imported: ContentItemModels.ContentItem;
}[];
languageVariants: {
original: IParsedContentItem;
original: IMigrationItem;
imported: LanguageVariantModels.ContentItemLanguageVariant;

@@ -112,3 +124,3 @@ }[];

importedData: IImportedData;
sourceItems: IParsedContentItem[];
sourceItems: IMigrationItem[];
}) => ElementContracts.IContentItemElementContract;

@@ -123,1 +135,12 @@

}
export interface IChunk<T> {
items: T[];
index: number;
}
export interface IProcessInChunksItemInfo {
title: string;
itemType: ItemType;
partA?: string;
}

@@ -1,6 +0,7 @@

import { IManagementClient, EnvironmentModels, SharedModels } from '@kontent-ai/management-sdk';
import { SharedModels } from '@kontent-ai/management-sdk';
import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
import { format } from 'bytes';
import { logDebug, logErrorAndExit } from './log-helper.js';
import { logDebug, logErrorAndExit, logProcessingDebug } from './log-helper.js';
import { HttpService } from '@kontent-ai/core-sdk';
import { IChunk, IErrorData, IProcessInChunksItemInfo } from './core.models.js';

@@ -39,24 +40,11 @@ const rateExceededErrorCode: number = 10000;

export async function printProjectAndEnvironmentInfoToConsoleAsync(
client: IManagementClient<any>
): Promise<EnvironmentModels.EnvironmentInformationModel> {
const environmentInformation = (await client.environmentInformation().toPromise()).data;
export function extractErrorData(error: any): IErrorData {
let message: string = `Unknown error`;
let requestUrl: string | undefined = undefined;
let requestData: string | undefined = undefined;
logDebug({
type: 'info',
message: 'Project information',
partA: environmentInformation.project.name
});
logDebug({
type: 'info',
message: 'Environment information',
partA: environmentInformation.project.environment
});
return environmentInformation.project;
}
export function extractErrorMessage(error: any): string {
if (error instanceof SharedModels.ContentManagementBaseKontentError) {
let message: string = `${error.message}`;
message = `${error.message}`;
requestUrl = error.originalError?.response?.config.url;
requestData = error.originalError?.response?.config.data;

@@ -66,9 +54,11 @@ for (const validationError of error.validationErrors) {

}
return message;
} else if (error instanceof Error) {
message = error.message;
}
if (error instanceof Error) {
return error.message;
}
return `Unknown error`;
return {
message: message,
requestData: requestData,
requestUrl: requestUrl
};
}

@@ -87,19 +77,24 @@

export function handleError(error: any | SharedModels.ContentManagementBaseKontentError): void {
if (error instanceof SharedModels.ContentManagementBaseKontentError) {
logErrorAndExit({
message: `${error.message}. Error code '${error.errorCode}'. Request Id '${error.requestId}'.${
error.validationErrors.length ? ` ${error.validationErrors.map((m) => m.message).join(', ')}` : ''
}`
export function handleError(error: any): void {
const errorData = extractErrorData(error);
if (errorData.requestUrl) {
logDebug({
type: 'errorData',
message: errorData.requestUrl,
partA: 'Request Url'
});
}
if (error instanceof Error) {
logErrorAndExit({
message: error.message
if (errorData.requestData) {
logDebug({
type: 'errorData',
message: errorData.requestData,
partA: 'Request Data'
});
}
// unhandled error
throw error;
logErrorAndExit({
message: errorData.message
});
}

@@ -125,1 +120,54 @@

}
export async function processInChunksAsync<TInputItem, TOutputItem>(data: {
items: TInputItem[];
chunkSize: number;
processFunc: (item: TInputItem) => Promise<TOutputItem>;
itemInfo?: (item: TInputItem) => IProcessInChunksItemInfo;
}): Promise<TOutputItem[]> {
const chunks = splitArrayIntoChunks<TInputItem>(data.items, data.chunkSize);
const outputItems: TOutputItem[] = [];
let processingIndex: number = 0;
for (const chunk of chunks) {
await Promise.all(
chunk.items.map((item) => {
processingIndex++;
if (data.itemInfo) {
const itemInfo = data.itemInfo(item);
logProcessingDebug({
index: processingIndex,
totalCount: data.items.length,
itemType: itemInfo.itemType,
title: itemInfo.title,
partA: itemInfo.partA
});
}
return data.processFunc(item).then((output) => {
outputItems.push(output);
});
})
);
}
return outputItems;
}
function splitArrayIntoChunks<T>(items: T[], chunkSize: number): IChunk<T>[] {
if (!items.length) {
return [];
}
const chunks: IChunk<T>[] = [];
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
chunks.push({
index: i,
items: chunk
});
}
return chunks;
}
export * from './core.models.js';
export * from './global-helper.js';
export * from './log-helper.js';
export * from './migration-models.js';
import colors from 'colors';
import { ActionType, ItemType } from './core.models.js';
import { ActionType, ItemType, LogLevel } from './core.models.js';
export type DebugType = 'error' | 'warning' | 'info' | ActionType;
export type DebugType = 'error' | 'warning' | 'info' | 'errorData' | ActionType;

@@ -10,4 +10,3 @@ export function logErrorAndExit(data: { message: string }): never {

type: 'error',
message: data.message,
partA: 'Stopping process'
message: data.message
});

@@ -22,7 +21,8 @@ process.exit(1);

title: string;
partA?: string;
}): void {
console.log(
`[${colors.bgYellow(colors.black(`${data.index}/${data.totalCount}`))}][${colors.yellow(
data.itemType
)}]: Starts processing ${data.title}`
`[${colors.cyan(`${data.index}/${data.totalCount}`)}][${colors.yellow(data.itemType)}]${
data.partA ? `[${colors.green(data.partA)}]` : ''
}: ${data.title}`
);

@@ -42,6 +42,6 @@ }

if (data.type === 'error') {
if (data.type === 'error' || data.type === 'errorData') {
typeColor = colors.red;
} else if (data.type === 'info') {
typeColor = colors.blue;
typeColor = colors.cyan;
} else if (data.type === 'warning') {

@@ -54,3 +54,3 @@ typeColor = colors.red;

data.partB ? `[${colors.cyan(data.partB)}]` : ''
}${data.partC ? `[${colors.red(data.partC)}]` : ''}${data.partD ? `[${colors.magenta(data.partD)}]` : ''}${
}${data.partC ? `[${colors.red(data.partC)}]` : ''}${data.partD ? `[${colors.yellow(data.partD)}]` : ''}${
data.performance ? `[${colors.bgYellow(colors.black(data.performance))}]` : ''

@@ -62,2 +62,3 @@ }: ${data.message}`

export function logItemAction(
logLevel: LogLevel,
actionType: ActionType,

@@ -68,2 +69,3 @@ itemType: ItemType,

workflowStep?: string;
workflow?: string;
title: string;

@@ -73,10 +75,12 @@ codename?: string;

): void {
logDebug({
type: actionType,
message: data.title,
partA: itemType,
partB: data.codename,
partC: data.language,
partD: data.workflowStep
});
if (logLevel === 'verbose') {
logDebug({
type: actionType,
message: data.title,
partA: itemType,
partB: data.codename,
partC: data.language,
partD: data.workflow && data.workflowStep ? `${data.workflow}>${data.workflowStep}` : undefined
});
}
}
import { IContentType, ILanguage, IContentItem, IDeliveryClient } from '@kontent-ai/delivery-sdk';
import { ActionType, ContentElementType, ItemType, logDebug } from '../../../../core/index.js';
import { IExportConfig, IExportContentItem, IExportElement } from '../../../export.models.js';
import {
MigrationElementType,
ItemType,
logDebug,
logProcessingDebug,
processInChunksAsync,
IMigrationItem,
IMigrationElement
} from '../../../../core/index.js';
import { IKontentAiExportAdapterConfig } from '../../../export.models.js';
import { translationHelper } from '../../../../translation/index.js';
import colors from 'colors';
interface ITypeLanguageMap {
language: ILanguage;
type: IContentType;
}
export class ExportContentItemHelper {
private readonly fetchCountForTypesChunkSize: number = 100;
private readonly exportContentItemsChunkSize: number = 100;
async exportContentItemsAsync(
deliveryClient: IDeliveryClient,
config: IExportConfig,
config: IKontentAiExportAdapterConfig,
types: IContentType[],
languages: ILanguage[]
): Promise<{ exportContentItems: IExportContentItem[]; deliveryContentItems: IContentItem[] }> {
): Promise<{ exportContentItems: IMigrationItem[]; deliveryContentItems: IContentItem[] }> {
const typesToExport: IContentType[] = this.getTypesToExport(config, types);
const languagesToExport: ILanguage[] = this.getLanguagesToExport(config, languages);
const contentItems: IContentItem[] = [];

@@ -25,4 +43,6 @@

for (const contentItem of customItems) {
this.logItem(`${contentItem.system.name} | ${contentItem.system.type}`, 'contentItem', 'fetch', {
language: contentItem.system.language
logDebug({
type: 'fetch',
message: `${contentItem.system.name} | ${contentItem.system.type}`,
partA: contentItem.system.language
});

@@ -34,12 +54,38 @@ contentItems.push(contentItem);

type: 'info',
message: `Exporting content items of types`,
partA: typesToExport.map((m) => m.system.codename).join(', ')
message: `Exporting content items of '${colors.yellow(
languagesToExport.length.toString()
)}' content types and '${colors.yellow(languagesToExport.length.toString())}' languages`
});
for (const type of typesToExport) {
for (const language of languages) {
logDebug({
type: 'info',
message: `Calculating total items to export`
});
const totalItemsToExport: number = await this.getTotalNumberOfItemsToExportAsync({
typesToExport: typesToExport,
deliveryClient: deliveryClient,
languagesToExport: languagesToExport
});
let exportedItemsCount: number = 0;
let extractedComponentsCount: number = 0;
logDebug({
type: 'info',
message: `Found '${colors.yellow(totalItemsToExport.toString())}' items in total to export`
});
const typeLanguageMaps = this.getTypeLanguageMaps({
languagesToExport: languagesToExport,
typesToExport: typesToExport
});
await processInChunksAsync<ITypeLanguageMap, void>({
chunkSize: this.exportContentItemsChunkSize,
items: typeLanguageMaps,
processFunc: async (typeLanguageMap) => {
await deliveryClient
.itemsFeed()
.type(type.system.codename)
.equalsFilter('system.language', language.system.codename)
.type(typeLanguageMap.type.system.codename)
.equalsFilter('system.language', typeLanguageMap.language.system.codename)
.toAllPromise({

@@ -49,6 +95,11 @@ responseFetched: (response) => {

for (const contentItem of response.data.items) {
this.logItem(`${contentItem.system.name}`, 'contentItem', 'fetch', {
language: contentItem.system.language
this.logItem({
index: exportedItemsCount + 1,
totalCount: totalItemsToExport,
title: contentItem.system.name,
language: contentItem.system.language,
itemType: 'contentItem'
});
contentItems.push(contentItem);
exportedItemsCount++;
}

@@ -59,6 +110,4 @@

if (!contentItems.find((m) => m.system.codename === codename)) {
this.logItem(`${contentItem.system.name}`, 'component', 'fetch', {
language: contentItem.system.language
});
contentItems.push(contentItem);
extractedComponentsCount++;
}

@@ -69,3 +118,8 @@ }

}
}
});
logDebug({
type: 'info',
message: `Adding '${colors.yellow(extractedComponentsCount.toString())}' components to export result`
});
}

@@ -79,2 +133,37 @@

private async getTotalNumberOfItemsToExportAsync(data: {
deliveryClient: IDeliveryClient;
languagesToExport: ILanguage[];
typesToExport: IContentType[];
}): Promise<number> {
let totalItemsCount: number = 0;
await processInChunksAsync<IContentType, void>({
chunkSize: this.fetchCountForTypesChunkSize,
itemInfo: (type) => {
return {
itemType: 'count',
title: type.system.name,
partA: type.system.codename
};
},
items: data.typesToExport,
processFunc: async (type) => {
for (const language of data.languagesToExport) {
const response = await data.deliveryClient
.items()
.type(type.system.codename)
.equalsFilter('system.language', language.system.codename)
.limitParameter(1)
.depthParameter(0)
.includeTotalCountParameter()
.toPromise();
totalItemsCount += response.data.pagination.totalCount ?? 0;
}
}
});
return totalItemsCount;
}
private maptoExportContentItem(

@@ -84,5 +173,5 @@ item: IContentItem,

types: IContentType[],
config: IExportConfig
): IExportContentItem {
return {
config: IKontentAiExportAdapterConfig
): IMigrationItem {
const migrationItem: IMigrationItem = {
system: {

@@ -94,8 +183,7 @@ codename: item.system.codename,

collection: item.system.collection,
id: item.system.id,
last_modified: item.system.lastModified,
workflow_step: item.system.workflowStep ?? undefined
workflow_step: item.system.workflowStep ?? undefined,
workflow: item.system.workflow ?? undefined
},
elements: Object.entries(item.elements).map(([key, element]) => {
const mappedElement: IExportElement = {
const mappedElement: IMigrationElement = {
codename: key,

@@ -113,3 +201,3 @@ value: translationHelper.transformToExportElementValue({

}),
type: element.type as ContentElementType
type: element.type as MigrationElementType
};

@@ -120,21 +208,23 @@

};
return migrationItem;
}
private logItem(
title: string,
itemType: ItemType,
actionType: ActionType,
data: {
language?: string;
}
): void {
logDebug({
type: actionType,
message: title,
partA: itemType,
partB: data.language
private logItem(data: {
title: string;
index: number;
totalCount: number;
itemType: ItemType;
language?: string;
}): void {
logProcessingDebug({
itemType: data.itemType,
title: `${data.title}`,
index: data.index,
totalCount: data.totalCount,
partA: data.language
});
}
private getTypesToExport(config: IExportConfig, types: IContentType[]): IContentType[] {
private getTypesToExport(config: IKontentAiExportAdapterConfig, types: IContentType[]): IContentType[] {
const filteredTypes: IContentType[] = [];

@@ -149,3 +239,2 @@

if (config.exportTypes.find((m) => m.toLowerCase() === type.system.codename.toLowerCase())) {
// content type can be exported
filteredTypes.push(type);

@@ -157,4 +246,39 @@ }

}
private getLanguagesToExport(config: IKontentAiExportAdapterConfig, languages: ILanguage[]): ILanguage[] {
const filteredLanguages: ILanguage[] = [];
if (!config?.exportLanguages?.length) {
// export all languages
return languages;
}
for (const language of languages) {
if (config.exportLanguages.find((m) => m.toLowerCase() === language.system.codename.toLowerCase())) {
filteredLanguages.push(language);
}
}
return filteredLanguages;
}
private getTypeLanguageMaps(data: {
typesToExport: IContentType[];
languagesToExport: ILanguage[];
}): ITypeLanguageMap[] {
const maps: ITypeLanguageMap[] = [];
for (const type of data.typesToExport) {
for (const language of data.languagesToExport) {
maps.push({
type: type,
language: language
});
}
}
return maps;
}
}
export const exportContentItemHelper = new ExportContentItemHelper();
import { IContentType, IDeliveryClient, ILanguage, createDeliveryClient } from '@kontent-ai/delivery-sdk';
import { IExportAdapter, IExportAdapterResult, IExportAsset, IExportConfig } from '../../export.models.js';
import { IExportAdapter, IExportAdapterResult, IKontentAiExportAdapterConfig } from '../../export.models.js';
import colors from 'colors';
import { exportContentItemHelper } from './helpers/export-content-item.helper.js';
import { defaultHttpService, defaultRetryStrategy } from '../../../core/global-helper.js';
import { exportAssetsHelper } from './helpers/export-assets-item.helper.js';
import { logDebug } from '../../../core/index.js';
import { exportAssetsHelper } from './helpers/export-assets.helper.js';
import { IMigrationAsset, logDebug } from '../../../core/index.js';
export class KontentAiExportAdapter implements IExportAdapter {
constructor(private config: IExportConfig) {}
constructor(private config: IKontentAiExportAdapterConfig) {}
async exportAsync(): Promise<IExportAdapterResult> {
logDebug({ type: 'info', message: 'Environment id', partA: this.config.environmentId });
logDebug({ type: 'info', message: 'Using Secure API', partA: this.config.isSecure ? 'true' : 'false' });
logDebug({ type: 'info', message: 'Using Preview API', partA: this.config.isPreview ? 'true' : 'false' });
logDebug({
type: 'info',
message: `Preparing export from environment ${colors.yellow(this.config.environmentId)}`
});
logDebug({ type: 'info', message: this.config.isSecure ? `Using Secure API` : `Not using Secure API` });
logDebug({
type: 'info',
message: this.config.isPreview ? `Using Preview API` : `Using Delivery API`
});
const deliveryClient = this.getDeliveryClient();
const types = await this.getContentTypesAsync(deliveryClient);
const languages = await this.getLanguagesAsync(deliveryClient);
const allTypes = await this.getAllContentTypesAsync(deliveryClient);
const allLanguages = await this.getAllLanguagesAsync(deliveryClient);

@@ -24,7 +31,7 @@ const contentItemsResult = await exportContentItemHelper.exportContentItemsAsync(

this.config,
types,
languages
allTypes,
allLanguages
);
const assets: IExportAsset[] = [];
const assets: IMigrationAsset[] = [];

@@ -34,3 +41,3 @@ if (this.config.exportAssets) {

assets.push(
...(await exportAssetsHelper.extractAssetsAsync(contentItemsResult.deliveryContentItems, types))
...(await exportAssetsHelper.extractAssetsAsync(contentItemsResult.deliveryContentItems, allTypes))
);

@@ -47,3 +54,3 @@ } else {

private async getLanguagesAsync(deliveryClient: IDeliveryClient): Promise<ILanguage[]> {
private async getAllLanguagesAsync(deliveryClient: IDeliveryClient): Promise<ILanguage[]> {
const response = await deliveryClient.languages().toAllPromise();

@@ -53,3 +60,3 @@ return response.data.items;

private async getContentTypesAsync(deliveryClient: IDeliveryClient): Promise<IContentType[]> {
private async getAllContentTypesAsync(deliveryClient: IDeliveryClient): Promise<IContentType[]> {
const response = await deliveryClient.types().toAllPromise();

@@ -56,0 +63,0 @@ return response.data.items;

import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
import { ContentElementType, IExportTransformConfig } from '../core/index.js';
import { IExportTransformConfig, IMigrationItem, IMigrationAsset } from '../core/index.js';
import { IContentItem, IDeliveryClient } from '@kontent-ai/delivery-sdk';

@@ -11,4 +11,4 @@

export interface IExportAdapterResult {
items: IExportContentItem[];
assets: IExportAsset[];
items: IMigrationItem[];
assets: IMigrationAsset[];
}

@@ -23,3 +23,3 @@

export interface IExportConfig {
export interface IKontentAiExportAdapterConfig {
environmentId: string;

@@ -33,2 +33,3 @@ managementApiKey?: string;

exportTypes?: string[];
exportLanguages?: string[];
exportAssets: boolean;

@@ -39,29 +40,1 @@ retryStrategy?: IRetryStrategyOptions;

}
export interface IExportAsset {
url: string;
extension: string;
assetId: string;
filename: string;
binaryData: Buffer | Blob;
}
export interface IExportElement {
value: string | undefined | string[];
type: ContentElementType;
codename: string;
}
export interface IExportContentItem {
system: {
codename: string;
id: string;
name: string;
language: string;
type: string;
collection: string;
last_modified?: string;
workflow_step?: string;
};
elements: IExportElement[];
}
import { parse } from 'csv-parse';
import { AsyncParser, FieldInfo } from 'json2csv';
import { IParsedAsset } from '../../import/index.js';
import { Readable } from 'stream';
import { AssetsParseData, AssetsTransformData, FileBinaryData } from '../file-processor.models.js';
import { BaseAssetProcessorService } from '../base-asset-processor.service.js';
import { IMigrationAsset } from '../../core/index.js';

@@ -37,3 +37,3 @@ export class AssetCsvProcessorService extends BaseAssetProcessorService {

async parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]> {
async parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]> {
const text = await data.zip.getFileContentAsync(this.assetsFilename);

@@ -45,3 +45,3 @@

const parsedAssets: IParsedAsset[] = [];
const parsedAssets: IMigrationAsset[] = [];
let index = 0;

@@ -61,3 +61,3 @@ const parser = parse(text, {

// process data row
const parsedAsset: IParsedAsset = {
const parsedAsset: IMigrationAsset = {
assetId: '',

@@ -64,0 +64,0 @@ extension: '',

@@ -1,5 +0,7 @@

import { IParsedAsset, IParsedAssetRecord } from '../../import/index.js';
import { IMigrationAsset } from '../../core/index.js';
import { AssetsParseData, AssetsTransformData, FileBinaryData } from '../file-processor.models.js';
import { BaseAssetProcessorService } from '../base-asset-processor.service.js';
type AssetRecord = Omit<IMigrationAsset, 'binaryData'>;
export class AssetJsonProcessorService extends BaseAssetProcessorService {

@@ -10,3 +12,3 @@ public readonly name: string = 'json';

async transformAssetsAsync(data: AssetsTransformData): Promise<FileBinaryData> {
const assetRecords: IParsedAssetRecord[] = [];
const assetRecords: AssetRecord[] = [];

@@ -31,3 +33,3 @@ for (const exportAsset of data.assets) {

}
async parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]> {
async parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]> {
const text = await data.zip.getFileContentAsync(this.assetsFilename);

@@ -39,4 +41,4 @@

const assetRecords: IParsedAssetRecord[] = JSON.parse(text);
const parsedAssets: IParsedAsset[] = [];
const assetRecords: AssetRecord[] = JSON.parse(text);
const parsedAssets: IMigrationAsset[] = [];

@@ -43,0 +45,0 @@ for (const assetRecord of assetRecords) {

@@ -1,2 +0,2 @@

import { IParsedAsset } from '../import/index.js';
import { IMigrationAsset } from '../core/index.js';
import { IAssetFormatService, AssetsTransformData, FileBinaryData, AssetsParseData } from './file-processor.models.js';

@@ -7,3 +7,3 @@

abstract transformAssetsAsync(data: AssetsTransformData): Promise<FileBinaryData>;
abstract parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]>;
abstract parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]>;

@@ -10,0 +10,0 @@ protected getSystemAssetFields(): string[] {

@@ -1,4 +0,5 @@

import { IImportContentType, IImportContentTypeElement, IParsedContentItem } from '../import/index.js';
import { IImportContentType, IImportContentTypeElement } from '../import/index.js';
import { IItemFormatService, ItemsTransformData, ItemsParseData, FileBinaryData } from './file-processor.models.js';
import { logErrorAndExit } from '../core/index.js';
import { IMigrationItem, logErrorAndExit } from '../core/index.js';
import colors from 'colors';

@@ -8,3 +9,3 @@ export abstract class BaseItemProcessorService implements IItemFormatService {

abstract transformContentItemsAsync(data: ItemsTransformData): Promise<FileBinaryData>;
abstract parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]>;
abstract parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]>;

@@ -24,3 +25,3 @@ protected getSystemContentItemFields(): string[] {

logErrorAndExit({
message: `Could not find content type '${contentItemType}'`
message: `Could not find content type '${colors.red(contentItemType)}'`
});

@@ -33,3 +34,5 @@ }

logErrorAndExit({
message: `Could not find element with codename '${elementCodename}' for type '${type.contentTypeCodename}'`
message: `Could not find element with codename '${colors.red(
elementCodename
)}' for type '${colors.yellow(type.contentTypeCodename)}'`
});

@@ -36,0 +39,0 @@ }

@@ -1,3 +0,3 @@

import { IExportContentItem, IExportAsset } from '../export/index.js';
import { IImportContentType, IParsedAsset, IParsedContentItem } from '../import/index.js';
import { IMigrationAsset, IMigrationItem } from '../core/index.js';
import { IImportContentType } from '../import/index.js';
import { ZipPackage } from './zip-package.class.js';

@@ -18,3 +18,3 @@

readonly zip: ZipPackage;
readonly items: IExportContentItem[];
readonly items: IMigrationItem[];
};

@@ -30,3 +30,3 @@

transformContentItemsAsync(data: ItemsTransformData): Promise<FileBinaryData>;
parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]>;
parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]>;
}

@@ -36,3 +36,3 @@

readonly zip: ZipPackage;
readonly assets: IExportAsset[];
readonly assets: IMigrationAsset[];
};

@@ -47,3 +47,3 @@

transformAssetsAsync(data: AssetsTransformData): Promise<FileBinaryData>;
parseAssetsAsync(data: AssetsParseData): Promise<IParsedAsset[]>;
parseAssetsAsync(data: AssetsParseData): Promise<IMigrationAsset[]>;
}

@@ -58,2 +58,1 @@

}

@@ -5,3 +5,3 @@ import colors from 'colors';

import { IExportAdapterResult } from '../export/index.js';
import { IParsedContentItem, IImportSource, IImportContentType, IParsedAsset } from '../import/index.js';
import { IImportSource, IImportContentType } from '../import/index.js';
import {

@@ -13,3 +13,3 @@ IItemFormatService,

} from './file-processor.models.js';
import { IExportTransformConfig, logDebug } from '../core/index.js';
import { IExportTransformConfig, IMigrationItem, IMigrationAsset, logDebug } from '../core/index.js';
import { ZipPackage } from './zip-package.class.js';

@@ -45,2 +45,7 @@

logDebug({
type: 'info',
message: 'Parsing items zip data'
});
result.importData.items.push(

@@ -52,7 +57,2 @@ ...(await data.items.formatService.parseContentItemsAsync({

);
logDebug({
type: 'info',
message: 'Parsing items zip data'
});
}

@@ -67,2 +67,7 @@

logDebug({
type: 'info',
message: 'Parsing assets zip data'
});
result.importData.assets.push(

@@ -73,7 +78,2 @@ ...(await data.assets.formatService.parseAssetsAsync({

);
logDebug({
type: 'info',
message: 'Parsing assets zip data'
});
}

@@ -83,3 +83,5 @@

type: 'info',
message: 'Parsing completed'
message: `Parsing completed. Parsed '${colors.yellow(
result.importData.items.length.toString()
)}' items and '${colors.yellow(result.importData.assets.length.toString())}' assets`
});

@@ -101,4 +103,4 @@

}): Promise<IImportSource> {
let parsedItems: IParsedContentItem[] = [];
let parsedAssets: IParsedAsset[] = [];
let parsedItems: IMigrationItem[] = [];
let parsedAssets: IMigrationAsset[] = [];

@@ -105,0 +107,0 @@ if (data.items) {

@@ -1,3 +0,3 @@

import { IImportContentTypeElement, IParsedContentItem, IParsedElement } from '../../../import/index.js';
import { IExportContentItem } from '../../../export/index.js';
import { IImportContentTypeElement } from '../../../import/index.js';
import { IMigrationElement, IMigrationItem } from '../../../core/index.js';

@@ -15,4 +15,4 @@ export interface IJsonElements {

collection: string;
last_modified?: string;
workflow_step?: string;
workflow?: string;
};

@@ -24,6 +24,6 @@ elements: IJsonElements;

typeCodename: string;
items: IExportContentItem[];
items: IMigrationItem[];
}
export function mapToJsonItem(item: IExportContentItem): IJsonItem {
export function mapToJsonItem(item: IMigrationItem): IJsonItem {
const jsonElements: IJsonElements = {};

@@ -40,6 +40,6 @@

language: item.system.language,
last_modified: item.system.last_modified,
name: item.system.name,
type: item.system.type,
workflow_step: item.system.workflow_step
workflow_step: item.system.workflow_step,
workflow: item.system.workflow
},

@@ -54,4 +54,4 @@ elements: jsonElements

getElement: (typeCodename: string, elementCodename: string) => IImportContentTypeElement
): IParsedContentItem {
const elements: IParsedElement[] = [];
): IMigrationItem {
const elements: IMigrationElement[] = [];

@@ -68,3 +68,3 @@ for (const propertyName of Object.keys(item.elements)) {

const parsedItem: IParsedContentItem = {
const parsedItem: IMigrationItem = {
system: {

@@ -74,6 +74,6 @@ codename: item.system.codename,

language: item.system.language,
last_modified: item.system.last_modified,
name: item.system.name,
type: item.system.type,
workflow_step: item.system.workflow_step
workflow_step: item.system.workflow_step,
workflow: item.system.workflow
},

@@ -80,0 +80,0 @@ elements: elements

import { parse } from 'csv-parse';
import { AsyncParser, FieldInfo } from 'json2csv';
import { IParsedContentItem } from '../../import/index.js';
import { Readable } from 'stream';
import { FileBinaryData, ItemsParseData, ItemsTransformData } from '../file-processor.models.js';
import { BaseItemProcessorService } from '../base-item-processor.service.js';
import { IExportContentItem } from '../../export/index.js';
import { IMigrationItem } from '../../core/index.js';

@@ -15,4 +14,4 @@ interface ICsvItem {

collection: string;
last_modified?: string;
workflow_step?: string;
workflow?: string;
[propertyName: string]: string | undefined | string[];

@@ -23,3 +22,3 @@ }

typeCodename: string;
items: IExportContentItem[];
items: IMigrationItem[];
elementCodenames: string[];

@@ -56,5 +55,5 @@ }

async parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]> {
async parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]> {
const zipFiles = await data.zip.getAllFilesAsync<string>('string');
const parsedItems: IParsedContentItem[] = [];
const parsedItems: IMigrationItem[] = [];

@@ -77,3 +76,3 @@ for (const file of zipFiles) {

// process data row
const contentItem: IParsedContentItem = {
const contentItem: IMigrationItem = {
system: {

@@ -84,5 +83,5 @@ type: '',

language: '',
last_modified: '',
name: '',
workflow_step: ''
workflow_step: '',
workflow: ''
},

@@ -123,3 +122,3 @@ elements: []

private getTypeWrappers(items: IExportContentItem[]): ITypeWrapper[] {
private getTypeWrappers(items: IMigrationItem[]): ITypeWrapper[] {
const typeWrappers: ITypeWrapper[] = [];

@@ -145,3 +144,3 @@

private mapToCsvItem(item: IExportContentItem, typeWrapper: ITypeWrapper): ICsvItem {
private mapToCsvItem(item: IMigrationItem, typeWrapper: ITypeWrapper): ICsvItem {
const csvItem: ICsvItem = {

@@ -152,5 +151,5 @@ type: item.system.type,

language: item.system.language,
last_modified: item.system.last_modified,
name: item.system.name,
workflow_step: item.system.workflow_step
workflow_step: item.system.workflow_step,
workflow: item.system.workflow
};

@@ -157,0 +156,0 @@

@@ -1,5 +0,5 @@

import { IParsedContentItem } from '../../import/index.js';
import { FileBinaryData, ItemsParseData, ItemsTransformData } from '../file-processor.models.js';
import { BaseItemProcessorService } from '../base-item-processor.service.js';
import { IJsonItem, mapToJsonItem, parseJsonItem } from './helpers/json-item.helper.js';
import { IMigrationItem } from '../../core/index.js';

@@ -18,3 +18,3 @@ export class ItemJsonJoinedProcessorService extends BaseItemProcessorService {

async parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]> {
async parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]> {
const text = await data.zip.getFileContentAsync(this.itemsFileName);

@@ -21,0 +21,0 @@

@@ -1,6 +0,5 @@

import { IParsedContentItem } from '../../import/index.js';
import { FileBinaryData, ItemsParseData, ItemsTransformData } from '../file-processor.models.js';
import { BaseItemProcessorService } from '../base-item-processor.service.js';
import { IExportContentItem } from '../../export/index.js';
import { IJsonItem, ITypeWrapper, mapToJsonItem, parseJsonItem } from './helpers/json-item.helper.js';
import { IMigrationItem } from '../../core/index.js';

@@ -23,5 +22,5 @@ export class ItemJsonProcessorService extends BaseItemProcessorService {

async parseContentItemsAsync(data: ItemsParseData): Promise<IParsedContentItem[]> {
async parseContentItemsAsync(data: ItemsParseData): Promise<IMigrationItem[]> {
const zipFiles = await data.zip.getAllFilesAsync<string>('string');
const parsedItems: IParsedContentItem[] = [];
const parsedItems: IMigrationItem[] = [];

@@ -43,3 +42,3 @@ for (const zipFile of zipFiles) {

private getTypeWrappers(items: IExportContentItem[]): ITypeWrapper[] {
private getTypeWrappers(items: IMigrationItem[]): ITypeWrapper[] {
const typeWrappers: ITypeWrapper[] = [];

@@ -46,0 +45,0 @@

import { AssetModels, ManagementClient } from '@kontent-ai/management-sdk';
import { IImportedData, is404Error, logItemAction, logProcessingDebug } from '../../core/index.js';
import { IParsedAsset } from '../import.models.js';
import {
IImportedData,
IMigrationAsset,
LogLevel,
is404Error,
logItemAction,
processInChunksAsync
} from '../../core/index.js';
import mime from 'mime';
export function getImportAssetsHelper(config: { logLevel: LogLevel }): ImportAssetsHelper {
return new ImportAssetsHelper(config.logLevel);
}
export class ImportAssetsHelper {
private readonly importAssetsChunkSize: number = 3;
constructor(private readonly logLevel: LogLevel) {}
async importAssetsAsync(data: {
managementClient: ManagementClient;
assets: IParsedAsset[];
assets: IMigrationAsset[];
importedData: IImportedData;
}): Promise<void> {
let assetIndex: number = 1;
for (const asset of data.assets) {
logProcessingDebug({
index: assetIndex,
totalCount: data.assets.length,
itemType: 'asset',
title: `${asset.filename}`
});
await processInChunksAsync<IMigrationAsset, void>({
chunkSize: this.importAssetsChunkSize,
items: data.assets,
itemInfo: (input) => {
return {
itemType: 'asset',
title: input.filename,
partA: input.extension
};
},
processFunc: async (asset) => {
// use asset id as external id
const assetExternalId: string = asset.assetId;
// use asset id as external id
const assetExternalId: string = asset.assetId;
// check if asset with given external id already exists
let existingAsset: AssetModels.Asset | undefined;
// check if asset with given external id already exists
let existingAsset: AssetModels.Asset | undefined;
try {
// when target project is the same as source project, the id of asset would be the same
// and such asset should not be imported again
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(asset.assetId)
.toPromise()
.then((m) => m.data);
} catch (error) {
if (!is404Error(error)) {
throw error;
try {
// when target project is the same as source project, the id of asset would be the same
// and such assets should not be imported again
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(asset.assetId)
.toPromise()
.then((m) => m.data);
} catch (error) {
if (!is404Error(error)) {
throw error;
}
}
}
try {
// check if asset with given external id was already created
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(assetExternalId)
.toPromise()
.then((m) => m.data);
} catch (error) {
if (!is404Error(error)) {
throw error;
try {
// check if asset with given external id was already created
existingAsset = await data.managementClient
.viewAsset()
.byAssetExternalId(assetExternalId)
.toPromise()
.then((m) => m.data);
} catch (error) {
if (!is404Error(error)) {
throw error;
}
}
}
if (!existingAsset) {
// only import asset if it wasn't already there
const uploadedBinaryFile = await data.managementClient
.uploadBinaryFile()
.withData({
binaryData: asset.binaryData,
contentType: mime.getType(asset.filename) ?? '',
filename: asset.filename
})
.toPromise();
if (!existingAsset) {
// only import asset if it didn't exist
logItemAction('upload', 'binaryFile', {
title: asset.filename
});
logItemAction(this.logLevel, 'upload', 'binaryFile', {
title: asset.filename
});
const uploadedBinaryFile = await data.managementClient
.uploadBinaryFile()
.withData({
binaryData: asset.binaryData,
contentType: mime.getType(asset.filename) ?? '',
filename: asset.filename
})
.toPromise();
const createdAsset = await data.managementClient
.addAsset()
.withData((builder) => {
return {
file_reference: {
id: uploadedBinaryFile.data.id,
type: 'internal'
},
external_id: assetExternalId
};
})
.toPromise()
.then((m) => m.data);
logItemAction(this.logLevel, 'create', 'asset', {
title: asset.filename
});
const createdAsset = await data.managementClient
.addAsset()
.withData((builder) => {
return {
file_reference: {
id: uploadedBinaryFile.data.id,
type: 'internal'
},
external_id: assetExternalId
};
})
.toPromise()
.then((m) => m.data);
data.importedData.assets.push({
imported: createdAsset,
original: asset
});
logItemAction('create', 'asset', {
title: asset.filename
});
} else {
data.importedData.assets.push({
imported: existingAsset,
original: asset
});
logItemAction('skip', 'asset', {
title: asset.filename
});
data.importedData.assets.push({
imported: createdAsset,
original: asset
});
} else {
data.importedData.assets.push({
imported: existingAsset,
original: asset
});
logItemAction(this.logLevel, 'skip', 'asset', {
title: asset.filename
});
}
}
assetIndex++;
}
});
}
}
export const importAssetsHelper = new ImportAssetsHelper();
import { CollectionModels, ContentItemModels, ManagementClient } from '@kontent-ai/management-sdk';
import {
IImportedData,
extractErrorMessage,
extractErrorData,
is404Error,

@@ -9,54 +9,80 @@ logItemAction,

logErrorAndExit,
logProcessingDebug
processInChunksAsync,
LogLevel,
ContentItemsFetchMode,
IMigrationItem
} from '../../core/index.js';
import { IParsedContentItem } from '../import.models.js';
import { ICategorizedParsedItems, parsedItemsHelper } from './parsed-items-helper.js';
import colors from 'colors';
export function getImportContentItemHelper(config: {
logLevel: LogLevel;
skipFailedItems: boolean;
fetchMode: ContentItemsFetchMode;
}): ImportContentItemHelper {
return new ImportContentItemHelper(config.logLevel, config.skipFailedItems, config.fetchMode);
}
export class ImportContentItemHelper {
private readonly importContentItemChunkSize: number = 3;
constructor(
private readonly logLevel: LogLevel,
private readonly skipFailedItems: boolean,
private readonly fetchMode: ContentItemsFetchMode
) {}
async importContentItemsAsync(data: {
managementClient: ManagementClient;
parsedContentItems: IParsedContentItem[];
migrationContentItems: IMigrationItem[];
collections: CollectionModels.Collection[];
importedData: IImportedData;
config: {
skipFailedItems: boolean;
};
}): Promise<ContentItemModels.ContentItem[]> {
const preparedItems: ContentItemModels.ContentItem[] = [];
let itemIndex: number = 0;
const categorizedParsedItems: ICategorizedParsedItems = parsedItemsHelper.categorizeParsedItems(
data.parsedContentItems
data.migrationContentItems
);
logItemAction('skip', 'contentItem', {
title: `Skipping '${categorizedParsedItems.componentItems.length}' because they represent component items`
logItemAction(this.logLevel, 'skip', 'contentItem', {
title: `Skipping '${colors.yellow(
categorizedParsedItems.componentItems.length.toString()
)}' because they represent component items`
});
for (const importContentItem of categorizedParsedItems.regularItems) {
itemIndex++;
let fetchedContentItems: ContentItemModels.ContentItem[] = [];
logProcessingDebug({
index: itemIndex,
totalCount: categorizedParsedItems.regularItems.length,
itemType: 'contentItem',
title: `'${importContentItem.system.name}' of type '${importContentItem.system.type}'`
logDebug({
message: `Fetching items via '${colors.yellow(this.fetchMode)}' mode`,
type: 'info'
});
if (this.fetchMode === 'oneByOne') {
fetchedContentItems = await this.fetchContentItemsOneByOneAsync({
categorizedParsedItems: categorizedParsedItems,
managementClient: data.managementClient
});
} else {
fetchedContentItems = await this.fetchAllContentItemsAsync({ managementClient: data.managementClient });
}
const preparedItems: ContentItemModels.ContentItem[] = [];
for (const parsedItem of data.migrationContentItems) {
try {
await this.importContentItemAsync({
const contentItem = await this.importContentItemAsync({
managementClient: data.managementClient,
collections: data.collections,
importContentItem: importContentItem,
importContentItem: parsedItem,
importedData: data.importedData,
parsedContentItems: data.parsedContentItems,
preparedItems: preparedItems
migrationContentItems: data.migrationContentItems,
fetchedContentItems: fetchedContentItems
});
preparedItems.push(contentItem);
} catch (error) {
if (data.config.skipFailedItems) {
if (this.skipFailedItems) {
logDebug({
type: 'error',
message: `Failed to import content item`,
partA: importContentItem.system.codename,
partB: extractErrorMessage(error)
partA: parsedItem.system.codename,
partB: extractErrorData(error).message
});

@@ -72,17 +98,78 @@ } else {

private async fetchContentItemsOneByOneAsync(data: {
managementClient: ManagementClient;
categorizedParsedItems: ICategorizedParsedItems;
}): Promise<ContentItemModels.ContentItem[]> {
const contentItems: ContentItemModels.ContentItem[] = [];
await processInChunksAsync<IMigrationItem, void>({
chunkSize: this.importContentItemChunkSize,
items: data.categorizedParsedItems.regularItems,
itemInfo: (input) => {
return {
itemType: 'contentItem',
title: input.system.name,
partA: input.system.type
};
},
processFunc: async (migrationContentItem) => {
try {
logItemAction(this.logLevel, 'fetch', 'contentItem', {
title: `${migrationContentItem.system.name}`,
codename: migrationContentItem.system.codename
});
const contentItem = await data.managementClient
.viewContentItem()
.byItemCodename(migrationContentItem.system.codename)
.toPromise()
.then((m) => m.data);
contentItems.push(contentItem);
} catch (error) {
if (!is404Error(error)) {
throw error;
}
}
}
});
return contentItems;
}
private async fetchAllContentItemsAsync(data: {
managementClient: ManagementClient;
}): Promise<ContentItemModels.ContentItem[]> {
return (
await data.managementClient
.listContentItems()
.withListQueryConfig({
responseFetched: (response, token) => {
logItemAction(this.logLevel, 'fetch', 'listContentItems', {
title: `Fetched '${colors.yellow(response.data.items.length.toString())}' items`
});
}
})
.toAllPromise()
).data.items;
}
private async importContentItemAsync(data: {
importContentItem: IParsedContentItem;
importContentItem: IMigrationItem;
managementClient: ManagementClient;
parsedContentItems: IParsedContentItem[];
migrationContentItems: IMigrationItem[];
collections: CollectionModels.Collection[];
importedData: IImportedData;
preparedItems: ContentItemModels.ContentItem[];
}): Promise<void> {
fetchedContentItems: ContentItemModels.ContentItem[];
}): Promise<ContentItemModels.ContentItem> {
const preparedContentItemResult = await this.prepareContentItemAsync(
data.managementClient,
data.importContentItem,
data.importedData
data.fetchedContentItems
);
data.preparedItems.push(preparedContentItemResult.contentItem);
data.importedData.contentItems.push({
original: data.importContentItem,
imported: preparedContentItemResult.contentItem
});
// check if name should be updated, no other changes are supported

@@ -97,3 +184,8 @@ if (preparedContentItemResult.status === 'itemAlreadyExists') {

) {
const upsertedContentItem = await data.managementClient
logItemAction(this.logLevel, 'upsert', 'contentItem', {
title: `${data.importContentItem.system.name}`,
codename: data.importContentItem.system.codename
});
await data.managementClient
.upsertContentItem()

@@ -109,31 +201,23 @@ .byItemCodename(data.importContentItem.system.codename)

.then((m) => m.data);
logItemAction('upsert', 'contentItem', {
title: `Upserting item '${upsertedContentItem.name}'`,
codename: data.importContentItem.system.codename
});
} else {
logItemAction('skip', 'contentItem', {
title: `Item '${data.importContentItem.system.name}' already exists`,
codename: data.importContentItem.system.codename
});
}
}
return preparedContentItemResult.contentItem;
}
private shouldUpdateContentItem(
parsedContentItem: IParsedContentItem,
migrationContentItem: IMigrationItem,
contentItem: ContentItemModels.ContentItem,
collections: CollectionModels.Collection[]
): boolean {
const collection = collections.find((m) => m.codename === parsedContentItem.system.collection);
const collection = collections.find((m) => m.codename === migrationContentItem.system.collection);
if (!collection) {
logErrorAndExit({
message: `Invalid collection '${parsedContentItem.system.collection}'`
message: `Invalid collection '${migrationContentItem.system.collection}'`
});
}
return (
parsedContentItem.system.name !== contentItem.name ||
parsedContentItem.system.collection !== collection.codename
migrationContentItem.system.name !== contentItem.name ||
migrationContentItem.system.collection !== collection.codename
);

@@ -144,22 +228,8 @@ }

managementClient: ManagementClient,
parsedContentItem: IParsedContentItem,
importedData: IImportedData
migrationContentItem: IMigrationItem,
fetchedContentItems: ContentItemModels.ContentItem[]
): Promise<{ contentItem: ContentItemModels.ContentItem; status: 'created' | 'itemAlreadyExists' }> {
try {
const contentItem = await managementClient
.viewContentItem()
.byItemCodename(parsedContentItem.system.codename)
.toPromise()
.then((m) => m.data);
const contentItem = fetchedContentItems.find((m) => m.codename === migrationContentItem.system.codename);
logItemAction('fetch', 'contentItem', {
title: `Loading item '${contentItem.name}'`,
codename: contentItem.codename
});
importedData.contentItems.push({
original: parsedContentItem,
imported: contentItem
});
if (contentItem) {
return {

@@ -169,40 +239,23 @@ contentItem: contentItem,

};
} catch (error) {
if (is404Error(error)) {
const contentItem = await managementClient
.addContentItem()
.withData({
name: parsedContentItem.system.name,
type: {
codename: parsedContentItem.system.type
},
codename: parsedContentItem.system.codename,
collection: {
codename: parsedContentItem.system.collection
}
})
.toPromise()
.then((m) => m.data);
}
const createdContentItem = await managementClient
.addContentItem()
.withData({
name: migrationContentItem.system.name,
type: {
codename: migrationContentItem.system.type
},
codename: migrationContentItem.system.codename,
collection: {
codename: migrationContentItem.system.collection
}
})
.toPromise()
.then((m) => m.data);
importedData.contentItems.push({
original: parsedContentItem,
imported: contentItem
});
logItemAction('create', 'contentItem', {
title: `Creating item '${contentItem.name}'`,
codename: contentItem.codename
});
return {
contentItem: contentItem,
status: 'created'
};
}
throw error;
}
return {
contentItem: createdContentItem,
status: 'created'
};
}
}
export const importContentItemHelper = new ImportContentItemHelper();

@@ -11,3 +11,3 @@ import {

IImportedData,
extractErrorMessage,
extractErrorData,
is404Error,

@@ -17,19 +17,33 @@ logItemAction,

logErrorAndExit,
logProcessingDebug
processInChunksAsync,
LogLevel,
IMigrationItem,
IMigrationElement
} from '../../core/index.js';
import { IParsedContentItem, IParsedElement } from '../import.models.js';
import { importWorkflowHelper } from './import-workflow.helper.js';
import { ImportWorkflowHelper, getImportWorkflowHelper } from './import-workflow.helper.js';
import { ICategorizedParsedItems, parsedItemsHelper } from './parsed-items-helper.js';
import { translationHelper } from '../../translation/index.js';
import colors from 'colors';
export function getImportLanguageVariantstemHelper(config: {
logLevel: LogLevel;
skipFailedItems: boolean;
}): ImportLanguageVariantHelper {
return new ImportLanguageVariantHelper(config.logLevel, config.skipFailedItems);
}
export class ImportLanguageVariantHelper {
private readonly importContentItemChunkSize: number = 3;
private readonly importWorkflowHelper: ImportWorkflowHelper;
constructor(private readonly logLevel: LogLevel, private readonly skipFailedItems: boolean) {
this.importWorkflowHelper = getImportWorkflowHelper({ logLevel: logLevel });
}
async importLanguageVariantsAsync(data: {
managementClient: ManagementClient;
importContentItems: IParsedContentItem[];
importContentItems: IMigrationItem[];
workflows: WorkflowModels.Workflow[];
preparedContentItems: ContentItemModels.ContentItem[];
importedData: IImportedData;
config: {
skipFailedItems: boolean;
};
}): Promise<void> {

@@ -40,56 +54,63 @@ const categorizedParsedItems: ICategorizedParsedItems = parsedItemsHelper.categorizeParsedItems(

logItemAction('skip', 'languageVariant', {
title: `Skipping '${categorizedParsedItems.componentItems.length}' because they represent component items`
logItemAction(this.logLevel, 'skip', 'languageVariant', {
title: `Skipping '${colors.yellow(
categorizedParsedItems.componentItems.length.toString()
)}' because they represent components`
});
let itemIndex: number = 0;
for (const importContentItem of categorizedParsedItems.regularItems) {
try {
itemIndex++;
logProcessingDebug({
index: itemIndex,
totalCount: categorizedParsedItems.regularItems.length,
await processInChunksAsync<IMigrationItem, void>({
chunkSize: this.importContentItemChunkSize,
items: categorizedParsedItems.regularItems,
itemInfo: (input) => {
return {
itemType: 'languageVariant',
title: `'${importContentItem.system.name}' of type '${importContentItem.system.type}' in language '${importContentItem.system.language}'`
});
title: input.system.name,
partA: input.system.language
};
},
processFunc: async (importContentItem) => {
try {
const preparedContentItem = data.preparedContentItems.find(
(m) => m.codename === importContentItem.system.codename
);
const preparedContentItem = data.preparedContentItems.find(
(m) => m.codename === importContentItem.system.codename
);
if (!preparedContentItem) {
logErrorAndExit({
message: `Invalid content item for codename '${colors.red(
importContentItem.system.codename
)}'`
});
}
if (!preparedContentItem) {
logErrorAndExit({
message: `Invalid content item for codename '${importContentItem.system.codename}'`
await this.importLanguageVariantAsync({
importContentItem,
preparedContentItem,
managementClient: data.managementClient,
importContentItems: data.importContentItems,
workflows: data.workflows,
importedData: data.importedData
});
} catch (error) {
if (this.skipFailedItems) {
logDebug({
type: 'error',
message: `Failed to import language variant '${colors.red(
importContentItem.system.name
)}' in language '${colors.red(importContentItem.system.language)}'`,
partA: importContentItem.system.codename,
partB: extractErrorData(error).message
});
} else {
throw error;
}
}
await this.importLanguageVariantAsync({
importContentItem,
preparedContentItem,
managementClient: data.managementClient,
importContentItems: data.importContentItems,
workflows: data.workflows,
importedData: data.importedData
});
} catch (error) {
if (data.config.skipFailedItems) {
logDebug({
type: 'error',
message: `Failed to import language variant '${importContentItem.system.name}' in language '${importContentItem.system.language}'`,
partA: importContentItem.system.codename,
partB: extractErrorMessage(error)
});
} else {
throw error;
}
}
}
});
}
private async importLanguageVariantAsync(data: {
importContentItem: IParsedContentItem;
importContentItem: IMigrationItem;
preparedContentItem: ContentItemModels.ContentItem;
managementClient: ManagementClient;
importContentItems: IParsedContentItem[];
importContentItems: IMigrationItem[];
workflows: WorkflowModels.Workflow[];

@@ -104,2 +125,9 @@ importedData: IImportedData;

logItemAction(this.logLevel, 'upsert', 'languageVariant', {
title: `${data.preparedContentItem.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
const upsertedLanguageVariant = await data.managementClient

@@ -127,13 +155,7 @@ .upsertLanguageVariant()

logItemAction('upsert', 'languageVariant', {
title: `${data.preparedContentItem.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
// set workflow of language variant
if (data.importContentItem.system.workflow_step) {
await importWorkflowHelper.setWorkflowOfLanguageVariantAsync(
if (data.importContentItem.system.workflow_step && data.importContentItem.system.workflow) {
await this.importWorkflowHelper.setWorkflowOfLanguageVariantAsync(
data.managementClient,
data.importContentItem.system.workflow,
data.importContentItem.system.workflow_step,

@@ -148,8 +170,23 @@ data.importContentItem,

managementClient: ManagementClient;
importContentItem: IParsedContentItem;
importContentItem: IMigrationItem;
workflows: WorkflowModels.Workflow[];
}): Promise<void> {
let languageVariantOfContentItem: undefined | LanguageVariantModels.ContentItemLanguageVariant;
const workflowCodename = data.importContentItem.system.workflow;
if (!workflowCodename) {
throw Error(
`Item with codename '${data.importContentItem.system.codename}' does not have workflow property assigned`
);
}
const workflow = this.importWorkflowHelper.getWorkflowByCodename(workflowCodename, data.workflows);
try {
logItemAction(this.logLevel, 'fetch', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
languageVariantOfContentItem = await data.managementClient

@@ -162,12 +199,9 @@ .viewLanguageVariant()

logItemAction('fetch', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
if (!languageVariantOfContentItem) {
logErrorAndExit({
message: `Invalid langauge variant for item '${data.importContentItem.system.codename}' of type '${data.importContentItem.system.type}' and language '${data.importContentItem.system.language}'`
message: `Invalid langauge variant for item '${colors.red(
data.importContentItem.system.codename
)}' of type '${colors.yellow(data.importContentItem.system.type)}' and language '${colors.yellow(
data.importContentItem.system.language
)}'`
});

@@ -185,2 +219,9 @@ }

if (this.isLanguageVariantPublished(languageVariantOfContentItem, data.workflows)) {
logItemAction(this.logLevel, 'createNewVersion', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
// create new version

@@ -192,4 +233,5 @@ await data.managementClient

.toPromise();
logItemAction('createNewVersion', 'languageVariant', {
} else if (this.isLanguageVariantArchived(languageVariantOfContentItem, data.workflows)) {
// change workflow step to draft
logItemAction(this.logLevel, 'unArchive', 'languageVariant', {
title: `${data.importContentItem.system.name}`,

@@ -200,11 +242,6 @@ language: data.importContentItem.system.language,

});
} else if (this.isLanguageVariantArchived(languageVariantOfContentItem, data.workflows)) {
// change workflow step to draft
if (languageVariantOfContentItem.workflow.stepIdentifier.id) {
const workflow = importWorkflowHelper.getWorkflowForGivenStepById(
languageVariantOfContentItem.workflow.stepIdentifier.id,
data.workflows
);
const newWorkflowStep = workflow.steps[0];
const firstWorkflowStep = workflow.steps?.[0];
if (firstWorkflowStep) {
await data.managementClient

@@ -214,11 +251,4 @@ .changeWorkflowStepOfLanguageVariant()

.byLanguageCodename(data.importContentItem.system.language)
.byWorkflowStepCodename(newWorkflowStep.codename)
.byWorkflowStepCodename(firstWorkflowStep.codename)
.toPromise();
logItemAction('unArchive', 'languageVariant', {
title: `${data.importContentItem.system.name}`,
language: data.importContentItem.system.language,
codename: data.importContentItem.system.codename,
workflowStep: data.importContentItem.system.workflow_step
});
}

@@ -256,4 +286,4 @@ }

private getElementContract(
sourceItems: IParsedContentItem[],
element: IParsedElement,
sourceItems: IMigrationItem[],
element: IMigrationElement,
importedData: IImportedData

@@ -271,3 +301,3 @@ ): ElementContracts.IContentItemElementContract {

logErrorAndExit({
message: `Missing import contract for element '${element.codename}' `
message: `Missing import contract for element '${colors.red(element.codename)}' `
});

@@ -279,3 +309,1 @@ }

}
export const importLanguageVariantHelper = new ImportLanguageVariantHelper();

@@ -1,27 +0,25 @@

import { ManagementClient, WorkflowModels } from '@kontent-ai/management-sdk';
import { IParsedContentItem } from '../import.models.js';
import { logItemAction, logErrorAndExit } from '../../core/index.js';
import { ManagementClient, SharedModels, WorkflowModels } from '@kontent-ai/management-sdk';
import { logItemAction, logErrorAndExit, LogLevel, logDebug, IMigrationItem } from '../../core/index.js';
import colors from 'colors';
export function getImportWorkflowHelper(config: { logLevel: LogLevel }): ImportWorkflowHelper {
return new ImportWorkflowHelper(config.logLevel);
}
export class ImportWorkflowHelper {
private readonly defaultWorkflowCodename: string = 'Default';
constructor(private readonly logLevel: LogLevel) {}
getWorkflowForGivenStepById(workflowId: string, workflows: WorkflowModels.Workflow[]): WorkflowModels.Workflow {
return this.getWorkflowForGivenStep(workflows, (workflow) => {
if (workflow.archivedStep.id === workflowId) {
return true;
}
if (workflow.publishedStep.id === workflowId) {
return true;
}
if (workflow.scheduledStep.id === workflowId) {
return true;
}
const step = workflow.steps.find((m) => m.id === workflowId);
getWorkflowByCodename(workflowCodename: string, workflows: WorkflowModels.Workflow[]): WorkflowModels.Workflow {
const workflow = workflows.find((m) => m.codename?.toLowerCase() === workflowCodename.toLowerCase());
if (step) {
return true;
}
if (!workflow) {
const errorMessages: string[] = [
`Workflow with codename '${workflowCodename}' does not exist in target project`,
`Available workflows are: ${workflows.map((m) => m.codename).join(', ')}`
];
return false;
});
throw Error(errorMessages.join('. '));
}
return workflow;
}

@@ -31,10 +29,15 @@

managementClient: ManagementClient,
workflowCodename: string,
workflowStepCodename: string,
importContentItem: IParsedContentItem,
importContentItem: IMigrationItem,
workflows: WorkflowModels.Workflow[]
): Promise<void> {
const workflow = this.getWorkflowByCodename(workflowCodename, workflows);
// check if workflow step exists in target project
if (!this.doesWorkflowStepExist(workflowStepCodename, workflows)) {
if (!this.doesWorkflowStepExist(workflowCodename, workflowStepCodename, workflows)) {
logErrorAndExit({
message: `Could not change workflow step for item '${importContentItem.system.codename}' (${importContentItem.system.name}) because step with codename '${workflowStepCodename}' does not exist in target project.`
message: `Could not change workflow step for item '${colors.yellow(
importContentItem.system.codename
)}' because step with codename '${colors.red(workflowStepCodename)}' does not exist`
});

@@ -44,2 +47,9 @@ }

if (this.doesWorkflowStepCodenameRepresentPublishedStep(workflowStepCodename, workflows)) {
logItemAction(this.logLevel, 'publish', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
await managementClient

@@ -51,5 +61,5 @@ .publishLanguageVariant()

.toPromise();
logItemAction('publish', 'languageVariant', {
title: `${importContentItem.system.name}`,
} else if (this.doesWorkflowStepCodenameRepresentScheduledStep(workflowStepCodename, workflows)) {
logItemAction(this.logLevel, 'skip', 'languageVariant', {
title: `Skipping scheduled workflow step for item '${colors.yellow(importContentItem.system.name)}'`,
language: importContentItem.system.language,

@@ -59,5 +69,34 @@ codename: importContentItem.system.codename,

});
} else if (this.doesWorkflowStepCodenameRepresentScheduledStep(workflowStepCodename, workflows)) {
logItemAction('skip', 'languageVariant', {
title: `Skipping scheduled workflow step for item '${importContentItem.system.name}'`,
} else if (this.doesWorkflowStepCodenameRepresentArchivedStep(workflowStepCodename, workflows)) {
// unpublish the language variant first if published
// there is no way to determine if language variant is published via MAPI
// so we have to always try unpublishing first and catching possible errors
try {
logItemAction(this.logLevel, 'unpublish', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
await managementClient
.unpublishLanguageVariant()
.byItemCodename(importContentItem.system.codename)
.byLanguageCodename(importContentItem.system.language)
.withoutData()
.toPromise();
} catch (error) {
if (error instanceof SharedModels.ContentManagementBaseKontentError) {
if (this.logLevel === 'verbose') {
logDebug({
type: 'info',
message: `Unpublish failed, but this may be expected behavior as we cannot determine the published state of language variant. Error received: ${error.message}`
});
}
} else {
throw error;
}
}
logItemAction(this.logLevel, 'archive', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,

@@ -67,5 +106,2 @@ codename: importContentItem.system.codename,

});
} else if (this.doesWorkflowStepCodenameRepresentArchivedStep(workflowStepCodename, workflows)) {
const workflow = this.getWorkflowForGivenStepByCodename(workflowStepCodename, workflows);
await managementClient

@@ -84,15 +120,13 @@ .changeWorkflowOfLanguageVariant()

.toPromise();
logItemAction('archive', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
} else {
const workflow = this.getWorkflowForGivenStepByCodename(workflowStepCodename, workflows);
if (workflow.codename === workflowStepCodename) {
// item is already in the target workflow step
} else {
logItemAction(this.logLevel, 'changeWorkflowStep', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
await managementClient

@@ -111,9 +145,2 @@ .changeWorkflowOfLanguageVariant()

.toPromise();
logItemAction('changeWorkflowStep', 'languageVariant', {
title: `${importContentItem.system.name}`,
language: importContentItem.system.language,
codename: importContentItem.system.codename,
workflowStep: importContentItem.system.workflow_step
});
}

@@ -162,65 +189,22 @@ }

private getWorkflowForGivenStep(
workflows: WorkflowModels.Workflow[],
workflowMatcher: (workflow: WorkflowModels.Workflow) => boolean
): WorkflowModels.Workflow {
const matchedWorkflow = workflows.find((workflow) => workflowMatcher(workflow));
private doesWorkflowStepExist(
workflowCodename: string,
stepCodename: string,
workflows: WorkflowModels.Workflow[]
): boolean {
const workflow = this.getWorkflowByCodename(workflowCodename, workflows);
if (matchedWorkflow) {
return matchedWorkflow;
if (workflow.archivedStep.codename === stepCodename) {
return true;
}
const defaultWorkflow = workflows.find(
(m) => m.codename.toLowerCase() === this.defaultWorkflowCodename.toLowerCase()
);
if (!defaultWorkflow) {
logErrorAndExit({
message: `Missing default workflow`
});
if (workflow.publishedStep.codename === stepCodename) {
return true;
}
if (workflow.scheduledStep.codename === stepCodename) {
return true;
}
const step = workflow.steps.find((m) => m.codename === stepCodename);
return defaultWorkflow;
}
private getWorkflowForGivenStepByCodename(
stepCodename: string,
workflows: WorkflowModels.Workflow[]
): WorkflowModels.Workflow {
return this.getWorkflowForGivenStep(workflows, (workflow) => {
if (workflow.archivedStep.codename === stepCodename) {
return true;
}
if (workflow.publishedStep.codename === stepCodename) {
return true;
}
if (workflow.scheduledStep.codename === stepCodename) {
return true;
}
const step = workflow.steps.find((m) => m.codename === stepCodename);
if (step) {
return true;
}
return false;
});
}
private doesWorkflowStepExist(stepCodename: string, workflows: WorkflowModels.Workflow[]): boolean {
for (const workflow of workflows) {
if (workflow.archivedStep.codename === stepCodename) {
return true;
}
if (workflow.publishedStep.codename === stepCodename) {
return true;
}
if (workflow.scheduledStep.codename === stepCodename) {
return true;
}
const step = workflow.steps.find((m) => m.codename === stepCodename);
if (step) {
return true;
}
if (step) {
return true;
}

@@ -231,3 +215,1 @@

}
export const importWorkflowHelper = new ImportWorkflowHelper();

@@ -1,10 +0,10 @@

import { IParsedContentItem } from "../import.models.js";
import { IMigrationItem } from '../../core/index.js';
export interface ICategorizedParsedItems {
componentItems: IParsedContentItem[];
regularItems: IParsedContentItem[];
componentItems: IMigrationItem[];
regularItems: IMigrationItem[];
}
export class ParsedItemsHelper {
categorizeParsedItems(items: IParsedContentItem[]): ICategorizedParsedItems {
categorizeParsedItems(items: IMigrationItem[]): ICategorizedParsedItems {
return {

@@ -11,0 +11,0 @@ // if content item does not have a workflow step it means it is used as a component within Rich text element

import { IRetryStrategyOptions } from '@kontent-ai/core-sdk';
import { ContentElementType } from '../core/index.js';
import { MigrationElementType, ContentItemsFetchMode, IMigrationItem, LogLevel, IMigrationAsset } from '../core/index.js';
export interface IImportConfig {
logLevel?: LogLevel;
managementApiKey: string;

@@ -11,5 +12,6 @@ skipFailedItems: boolean;

environmentId: string;
contentItemsFetchMode?: ContentItemsFetchMode;
canImport?: {
contentItem?: (item: IParsedContentItem) => boolean | Promise<boolean>;
asset?: (item: IParsedAsset) => boolean | Promise<boolean>;
contentItem?: (item: IMigrationItem) => boolean | Promise<boolean>;
asset?: (item: IMigrationAsset) => boolean | Promise<boolean>;
};

@@ -25,17 +27,6 @@ }

export interface IParsedAssetRecord {
assetId: string;
filename: string;
extension: string;
url: string;
}
export interface IParsedAsset extends IParsedAssetRecord {
binaryData: Buffer | Blob | undefined;
}
export interface IImportSource {
importData: {
items: IParsedContentItem[];
assets: IParsedAsset[];
items: IMigrationItem[];
assets: IMigrationAsset[];
};

@@ -50,24 +41,5 @@ }

export interface IParsedElement {
value: string | undefined | string[];
type: ContentElementType;
codename: string;
}
export interface IParsedContentItem {
system: {
codename: string;
name: string;
language: string;
type: string;
collection: string;
last_modified?: string;
workflow_step?: string;
};
elements: IParsedElement[];
}
export interface IImportContentTypeElement {
codename: string;
type: ContentElementType;
type: MigrationElementType;
}

@@ -74,0 +46,0 @@

@@ -8,3 +8,4 @@ import {

ManagementClient,
WorkflowModels
WorkflowModels,
createManagementClient
} from '@kontent-ai/management-sdk';

@@ -15,23 +16,24 @@

defaultRetryStrategy,
printProjectAndEnvironmentInfoToConsoleAsync,
defaultHttpService,
logDebug,
logErrorAndExit
logErrorAndExit,
IMigrationItem
} from '../core/index.js';
import { IImportConfig, IImportSource, IImportContentType, IImportContentTypeElement } from './import.models.js';
import { ImportAssetsHelper, getImportAssetsHelper } from './helpers/import-assets.helper.js';
import { ImportContentItemHelper, getImportContentItemHelper } from './helpers/import-content-item.helper.js';
import {
IImportConfig,
IParsedContentItem,
IImportSource,
IImportContentType,
IImportContentTypeElement
} from './import.models.js';
import { importAssetsHelper } from './helpers/import-assets.helper.js';
import { importContentItemHelper } from './helpers/import-content-item.helper.js';
import { importLanguageVariantHelper } from './helpers/import-language-variant.helper.js';
ImportLanguageVariantHelper,
getImportLanguageVariantstemHelper
} from './helpers/import-language-variant.helper.js';
import colors from 'colors';
export class ImportService {
private readonly managementClient: ManagementClient;
private readonly importAssetsHelper: ImportAssetsHelper;
private readonly importContentItemHelper: ImportContentItemHelper;
private readonly importLanguageVariantHelper: ImportLanguageVariantHelper;
constructor(private config: IImportConfig) {
this.managementClient = new ManagementClient({
this.managementClient = createManagementClient({
apiKey: config.managementApiKey,

@@ -43,10 +45,27 @@ baseUrl: config.baseUrl,

});
this.importAssetsHelper = getImportAssetsHelper({ logLevel: config.logLevel ?? 'default' });
this.importContentItemHelper = getImportContentItemHelper({
logLevel: config.logLevel ?? 'default',
skipFailedItems: config.skipFailedItems,
fetchMode: config?.contentItemsFetchMode ?? 'oneByOne'
});
this.importLanguageVariantHelper = getImportLanguageVariantstemHelper({
logLevel: config.logLevel ?? 'default',
skipFailedItems: config.skipFailedItems
});
}
async getImportContentTypesAsync(): Promise<IImportContentType[]> {
async printInfoAsync(): Promise<void> {
const environmentInformation = (await this.managementClient.environmentInformation().toPromise()).data;
logDebug({
type: 'info',
message: `Fetching content types from environment`
message: `Importing into '${colors.yellow(
environmentInformation.project.environment
)}' environment of project '${colors.yellow(environmentInformation.project.name)}'`
});
}
async getImportContentTypesAsync(): Promise<IImportContentType[]> {
const contentTypes = (await this.managementClient.listContentTypes().toAllPromise()).data.items;

@@ -57,3 +76,3 @@ const contentTypeSnippets = (await this.managementClient.listContentTypeSnippets().toAllPromise()).data.items;

type: 'info',
message: `Fetched '${contentTypes.length}' content types`
message: `Fetched '${colors.yellow(contentTypes.length.toString())}' content types`
});

@@ -63,3 +82,3 @@

type: 'info',
message: `Fetched '${contentTypeSnippets.length}' content type snippets`
message: `Fetched '${colors.yellow(contentTypeSnippets.length.toString())}' content type snippets`
});

@@ -85,3 +104,2 @@

};
await printProjectAndEnvironmentInfoToConsoleAsync(this.managementClient);

@@ -98,3 +116,3 @@ // this is an optional step where users can exclude certain objects from being imported

});
await importAssetsHelper.importAssetsAsync({
await this.importAssetsHelper.importAssetsAsync({
managementClient: this.managementClient,

@@ -117,3 +135,3 @@ assets: dataToImport.importData.assets,

});
await this.importParsedContentItemsAsync(dataToImport.importData.items, importedData);
await this.importmigrationContentItemAsync(dataToImport.importData.items, importedData);
} else {

@@ -159,3 +177,5 @@ logDebug({

logErrorAndExit({
message: `Could not find content type snippet for element. This snippet is referenced in type '${contentType.codename}'`
message: `Could not find content type snippet for element. This snippet is referenced in type '${colors.red(
contentType.codename
)}'`
});

@@ -222,3 +242,3 @@ }

type: 'info',
message: `Removed '${removedAssets.toString()}' assets from import`
message: `Removed '${colors.yellow(removedAssets.toString())}' assets from import`
});

@@ -230,3 +250,3 @@ }

type: 'info',
message: `Removed '${removedContentItems.toString()}' content items from import`
message: `Removed '${colors.yellow(removedContentItems.toString())}' content items from import`
});

@@ -238,4 +258,4 @@ }

private async importParsedContentItemsAsync(
parsedContentItems: IParsedContentItem[],
private async importmigrationContentItemAsync(
migrationContentItem: IMigrationItem[],
importedData: IImportedData

@@ -248,22 +268,16 @@ ): Promise<void> {

const preparedContentItems: ContentItemModels.ContentItem[] =
await importContentItemHelper.importContentItemsAsync({
await this.importContentItemHelper.importContentItemsAsync({
managementClient: this.managementClient,
collections: collections,
importedData: importedData,
parsedContentItems: parsedContentItems,
config: {
skipFailedItems: this.config.skipFailedItems
}
migrationContentItems: migrationContentItem
});
// then process language variants
await importLanguageVariantHelper.importLanguageVariantsAsync({
await this.importLanguageVariantHelper.importLanguageVariantsAsync({
managementClient: this.managementClient,
importContentItems: parsedContentItems,
importContentItems: migrationContentItem,
importedData: importedData,
preparedContentItems: preparedContentItems,
workflows: workflows,
config: {
skipFailedItems: this.config.skipFailedItems
}
workflows: workflows
});

@@ -270,0 +284,0 @@ }

@@ -12,3 +12,4 @@ #!/usr/bin/env node

handleError,
logErrorAndExit
logErrorAndExit,
ContentItemsFetchMode
} from '../../core/index.js';

@@ -45,2 +46,4 @@ import {

.describe('e', 'environmentId')
.alias('fm', 'fetchMode')
.describe('fm', 'Fetch mode. One of: "oneByOne" | "listAll')
.alias('mapi', 'apiKey')

@@ -75,4 +78,9 @@ .describe('mapi', 'Management API Key')

'et',
'Can be used to export only selected content types. Expects CSV of type codenames. If not provided, all content items of all types are exported'
'CSV of content type codenames of which content items will be exported. If not provided, all content items of all types are exported'
)
.alias('el', 'exportLanguages')
.describe(
'el',
'CSV of language codenames of which content items will be exported. If not provided, all content items of all types are exported'
)
.help('h')

@@ -106,2 +114,3 @@ .alias('h', 'help').argv;

exportTypes: config.exportTypes,
exportLanguages: config.exportLanguages,
exportAssets: config.exportAssets

@@ -153,4 +162,6 @@ });

const importToolkit = new ImportToolkit({
logLevel: config.logLevel,
skipFailedItems: config.skipFailedItems,
baseUrl: config.baseUrl,
contentItemsFetchMode: config.contentItemsFetchMode,
environmentId: config.environmentId,

@@ -224,11 +235,13 @@ managementApiKey: config.managementApiKey,

const adapter: string | undefined = getOptionalArgumentValue(resolvedArgs, 'adapter');
const fetchMode: string | undefined = getOptionalArgumentValue(resolvedArgs, 'fetchMode');
let mappedFormat: ProcessingFormat = 'csv';
let mappedAdapter: ExportAdapter = 'kontentAi';
let mappedFetchMode: ContentItemsFetchMode = 'oneByOne';
if (format?.toLowerCase() === 'csv'.toLowerCase()) {
if (format?.toLowerCase() === <ProcessingFormat>'csv'.toLowerCase()) {
mappedFormat = 'csv';
} else if (format?.toLowerCase() === 'json'.toLowerCase()) {
} else if (format?.toLowerCase() === <ProcessingFormat>'json'.toLowerCase()) {
mappedFormat = 'json';
} else if (format?.toLowerCase() === 'jsonJoined'.toLowerCase()) {
} else if (format?.toLowerCase() === <ProcessingFormat>'jsonJoined'.toLowerCase()) {
mappedFormat = 'jsonJoined';

@@ -243,3 +256,3 @@ } else {

if (adapter?.toLowerCase() === 'kontentAi'.toLowerCase()) {
if (adapter?.toLowerCase() === <ExportAdapter>'kontentAi'.toLowerCase()) {
mappedAdapter = 'kontentAi';

@@ -254,2 +267,8 @@ } else {

if (fetchMode?.toLowerCase() === <ContentItemsFetchMode>'listAll'.toLowerCase()) {
mappedFetchMode = 'listAll';
} else {
mappedFetchMode = 'oneByOne';
}
const config: ICliFileConfig = {

@@ -274,3 +293,6 @@ action: action,

adapter: mappedAdapter,
format: mappedFormat
format: mappedFormat,
contentItemsFetchMode: mappedFetchMode,
logLevel:
getOptionalArgumentValue(resolvedArgs, 'logLevel')?.toLowerCase() === 'verbose' ? 'verbose' : 'default'
};

@@ -285,7 +307,5 @@

run()
.then((m) => {})
.catch((err) => {
handleError(err);
});
run().catch((err) => {
handleError(err);
});

@@ -292,0 +312,0 @@ function getAssetFormatService(format: ProcessingFormat | undefined): IAssetFormatService {

@@ -12,3 +12,3 @@ import { promises } from 'fs';

type: 'readFs',
message: `Reading FS`,
message: `Reading file system`,
partA: filePath

@@ -27,3 +27,3 @@ });

type: 'writeFs',
message: `Storing on FS`,
message: `Storing on file system`,
partA: filePath

@@ -30,0 +30,0 @@ });

@@ -25,2 +25,4 @@ import { FileProcessorService, IAssetFormatService, IItemFormatService } from '../file-processor/index.js';

await importService.printInfoAsync();
// prepare content types

@@ -53,2 +55,4 @@ const contentTypes = await importService.getImportContentTypesAsync();

await importService.printInfoAsync();
// prepare content types

@@ -55,0 +59,0 @@ const contentTypes = await importService.getImportContentTypesAsync();

export * from './import-toolkit.class.js';
export * from './export-toolkit.class.js';
export * from './external-migration.class.js';

@@ -17,5 +17,3 @@ import {

} from '@kontent-ai/management-sdk';
import { IParsedContentItem } from '../import/index.js';
import {
ContentElementType,
ExportTransformFunc,

@@ -29,3 +27,4 @@ IExportTransformConfig,

import { idTranslateHelper } from './id-translate-helper.js';
import { logDebug, logErrorAndExit } from '../core/index.js';
import { IMigrationItem, logDebug, logErrorAndExit, MigrationElementType } from '../core/index.js';
import colors from 'colors';

@@ -78,3 +77,3 @@ export class ElementTranslationHelper {

*/
private readonly importTransforms: Readonly<Record<ContentElementType, ImportTransformFunc>> = {
private readonly importTransforms: Readonly<Record<MigrationElementType, ImportTransformFunc>> = {
guidelines: (data) => {

@@ -183,3 +182,3 @@ logErrorAndExit({

const processedRte = this.processImportRichTextHtmlValue(data.value?.toString() ?? '', data.importedData);
const componentItems: IParsedContentItem[] = [];
const componentItems: IMigrationItem[] = [];

@@ -191,3 +190,3 @@ for (const componentCodename of processedRte.componentCodenames) {

logErrorAndExit({
message: `Could not find component item with codename '${componentCodename}'`
message: `Could not find component item with codename '${colors.red(componentCodename)}'`
});

@@ -275,5 +274,5 @@ }

elementCodename: string,
type: ContentElementType,
type: MigrationElementType,
importedData: IImportedData,
sourceItems: IParsedContentItem[]
sourceItems: IMigrationItem[]
): ElementContracts.IContentItemElementContract | undefined {

@@ -342,5 +341,9 @@ const transformFunc = this.importTransforms[type];

type: 'warning',
message: `Could not find content item with id '${id}' referenced as a link in
Rich text element '${data.richTextElement.name}' in item '${data.item.system.name}'
and language '${data.item.system.language}'. Replacing link with plain text.`
message: `Could not find content item with id '${colors.red(
id
)}' referenced as a link in Rich text element '${colors.yellow(
data.richTextElement.name
)}' in item '${colors.yellow(data.item.system.name)}' and language '${colors.yellow(
data.item.system.language
)}'. Replacing link with plain text.`
});

@@ -350,5 +353,9 @@ } else {

type: 'warning',
message: `Could not find content item with id '${id}' referenced as a link
in Rich text element '${data.richTextElement.name}' in item '${data.item.system.name}'
and language '${data.item.system.language}'. This may be fixed by enabling 'replaceInvalidLinks' option.`
message: `Could not find content item with id '${colors.red(
id
)}' referenced as a link in Rich text element '${colors.yellow(
data.richTextElement.name
)}' in item '${colors.yellow(data.item.system.name)}' and language '${colors.yellow(
data.item.system.language
)}'. This may be fixed by enabling 'replaceInvalidLinks' option.`
});

@@ -461,3 +468,5 @@ }

type: 'warning',
message: `Could not find content item with codename '${codename}'. This item was referenced as a link in Rich text element.`
message: `Could not find content item with codename '${colors.red(
codename
)}'. This item was referenced as a link in Rich text element.`
});

@@ -464,0 +473,0 @@ } else {

{
"name": "xeno-test",
"version": "0.0.18",
"version": "0.0.19",
"description": "This program can be used to import content related data into Kontent.ai from various formats. Additionally, it can also be used to export Kontent.ai data using Delivery API.",

@@ -32,5 +32,7 @@ "preferGlobal": true,

"build": "npm run clean && npm run build:es2022",
"prepareSampleExport": "npm run build && cd samples/export-data && node --max-http-header-size 150000 ../../dist/es2022/node/cli/app --max-http-header-size=80000000 --config=export-config.json ",
"test:all": "npm run build",
"test:export": "npm run build && cd output && node --max-http-header-size 150000 ../dist/es2022/node/cli/app --max-http-header-size=80000000 --config=export-config.json ",
"test:import": "npm run build && cd output && node --max-http-header-size 150000 ../dist/es2022/node/cli/app --config=import-config.json",
"test:help": "npm run build && cd output && node --max-http-header-size 150000 ../dist/es2022/node/cli/app --help",
"lint": "npx eslint lib",

@@ -49,11 +51,11 @@ "clean": "node clean.js"

"dependencies": {
"@kontent-ai/delivery-sdk": "14.5.0",
"@kontent-ai/management-sdk": "5.7.0",
"@kontent-ai/delivery-sdk": "14.6.0",
"@kontent-ai/management-sdk": "5.8.2",
"uuid-by-string": "4.0.0",
"bytes": "3.1.2",
"colors": "1.4.0",
"csv-parse": "5.5.2",
"csv-parse": "5.5.3",
"json2csv": "5.0.7",
"jszip": "3.10.1",
"mime": "4.0.0",
"mime": "4.0.1",
"uuid": "9.0.1",

@@ -67,10 +69,10 @@ "yargs": "17.7.2"

"@types/mime": "3.0.4",
"@types/node": "20.10.4",
"@types/uuid": "9.0.7",
"@types/node": "20.11.13",
"@types/uuid": "9.0.8",
"@types/yargs": "17.0.32",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"eslint": "8.55.0",
"@typescript-eslint/eslint-plugin": "6.20.0",
"@typescript-eslint/parser": "6.20.0",
"eslint": "8.56.0",
"standard-version": "9.5.0",
"ts-node": "10.9.1",
"ts-node": "10.9.2",
"tslib": "2.6.2",

@@ -77,0 +79,0 @@ "typescript": "5.3.3"

# Kontent.ai Migration Toolkit
The purpose of this project is to import content data to [Kontent.ai](https://kontent.ai) projects using various formats
and export adapters. Currently we support only `Kontent.ai` export adapter (meaning you can export data from Kontent.ai
and re-import it to the same or different project)
and export adapters. We provide `Kontent.ai` export adapter by default (meaning you can export data from Kontent.ai and
re-import it to the same or different project).
> [!TIP]
> The idea behind this tool is to help migration of data into `Kontent.ai` from a simple object structure (json, csv..).
> Developers should export data from their system into this format and use this tool to import it. This tool takes care
> of preparing content items, language variants, moving items through workflow, publishing, archiving, uploading assets,
> retry policy and some basic validation and more.
This library can only be used in `node.js`. Use in Browsers is not supported.
### Important Disclaimer
# Getting started
> We do not recommend importing data into your production environment directly (= without proper testing), unless you
> are absolutely sure you know what you are doing. Instead, we recommend that you create a new environment based on your
> production and test the import there first. If the import meets your expectations, you may swap environments or run it
> again on the production.
We recommend running data-ops with `npx`. Use `--help` anytime to get information about available commands and their
options.
## Installation
```bash
npx xeno-test --help
# or
yarn dlx xeno-test --help
Install package globally:
# help for a specific command
npx xeno-test <command> --help
`npm i xeno-test -g`
# you can also install the package globally, or locally
npm i xeno-test -g
# with the package installed, you can call the tool as follows
kontent-ai-migration-toolkit --help
```
# Import
> [!CAUTION]
> **We do not recommended importing into a production environment directly** (without proper testing). Instead you
> should first create a testing environment and run the script there to make sure everything works as you intended to.
> [!NOTE]
> When importing it is essential that `Content types`, `Taxonomies` and `Workflows` matches the input data. Any

@@ -28,8 +46,11 @@ > inconsistency in data such as referencing inexistent taxonomy term, incorrect element type and other problems will

## How are content items imported?
## How are content items & language variants imported?
The Migration Toolkit creates content items that are not present in target project. If the content item exists in target
project (based on item `codename`) the item will be updated. The workflow or published state will be set according to
the source data.
project (based on item's `codename`) the item will be updated. The workflow of imported language variant will be set
according to `workflowStep` field.
You can run `kontent-ai-migration-toolkit` many times over without being worried that identical content item will be
created multiple times.
## How are assets imported?

@@ -96,2 +117,89 @@

# Migration models
This tools uses simplified models to make migration simpler and more developer friendly. This is useful especially when
migrating from external systems because data structure is almost certainly very different to models used within
Kontent.ai APIs. These migration models act as an abstraction on the API layer.
### Model definitions
> Models are defined at https://github.com/Enngage/kontent-ai-migration-toolkit/blob/main/lib/core/migration-models.ts
```typescript
export type MigrationElementType =
| 'text'
| 'rich_text'
| 'number'
| 'multiple_choice'
| 'date_time'
| 'asset'
| 'modular_content'
| 'taxonomy'
| 'url_slug'
| 'guidelines'
| 'snippet'
| 'custom'
| 'subpages';
export interface IMigrationElement {
value: string | undefined | string[];
type: MigrationElementType;
codename: string;
}
export interface IMigrationItem {
system: {
codename: string;
name: string;
language: string;
type: string;
collection: string;
workflow_step?: string;
workflow?: string;
};
elements: IMigrationElement[];
}
export interface IMigrationAsset {
binaryData: Buffer | Blob | undefined;
assetId: string;
filename: string;
extension: string;
url: string;
}
```
### Model examples
A single record of `IMigrationItem` type in `json` format may look like this:
```json
{
"system": {
"codename": "_the_dark_knight_rises",
"collection": "default",
"language": "en",
"name": " The Dark Knight Rises",
"type": "movie",
"workflow_step": "published",
"workflow": "default"
},
"elements": {
"title": "The Dark Knight Rises",
"plot": "<p>Eight years after the Joker's reign of anarchy, the Dark Knight, with the help of the enigmatic Selina, is forced from his imposed exile to save Gotham City, now on the edge of total annihilation, from the brutal guerrilla terrorist Bane.</p>",
"released": "2012-07-20T00:00:00Z",
"length": 164,
"poster": [
"https://assets-eu-01.kc-usercontent.com:443/cdbf5823-cbec-010d-f4c3-0411eee31c0e/787c8c83-16b4-40b4-878e-b90b6d42a4ef/the_dark_knight_rises.jpg"
],
"category": ["sci_fi", "action"],
"stars": ["christian_bale", "anne_hathaway", "tom_hardy"],
"seoname": "the-dark-knight-rises",
"releasecategory": []
}
}
```
> You may find sample export (`.zip`) with both items & assets at https://github.com/Enngage/kontent-ai-migration-toolkit/tree/main/samples/export-data
# Export

@@ -112,2 +220,4 @@

| exportAssets | When set to `true`, Binary data of assets is exported. Defaults to `false` |
| exporTypes | CSV of content type codenames of which content items will be exported. If none is provided, all types are exported |
| exporLanguages | CSV of language codenames of which content items will be exported. If none is provided, all types are exported |
| replaceInvalidLinks | RTE may contain links to invalid items. You won't be able to re-import such items due to validation error. By setting this to `true` the Migration Toolkit will automatically remove these links. Defaults to `false` |

@@ -189,3 +299,4 @@ | itemsFilename | Name of the items file that will be created in folder where script is run |

"baseUrl": null,
"format": "json"
"format": "json", // or 'jsonJoined' / 'csv'
"logLevel": "verbose" // or 'default'
}

@@ -192,0 +303,0 @@ ```

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc