From 9085d8dce96efe4dd8908dbfcd0a44a4b5b95827 Mon Sep 17 00:00:00 2001 From: Richard Sustek Date: Wed, 14 Sep 2022 10:59:59 +0200 Subject: [PATCH] feat: extends console logging with more data & colors --- lib/clean/clean.service.ts | 4 ++- lib/core/global-helper.ts | 9 +++++- lib/export/export.service.ts | 52 +++++++++++++++++++++++++++-------- lib/import/import.service.ts | 28 +++++++++++++++---- lib/node/cli/app.ts | 34 ++++++++--------------- lib/node/file/file.service.ts | 13 ++++----- lib/zip/zip.service.ts | 11 ++++---- package-lock.json | 14 ++++++++++ package.json | 3 +- 9 files changed, 111 insertions(+), 57 deletions(-) diff --git a/lib/clean/clean.service.ts b/lib/clean/clean.service.ts index f6e86dc..82a9908 100644 --- a/lib/clean/clean.service.ts +++ b/lib/clean/clean.service.ts @@ -1,7 +1,7 @@ import { AssetFolderModels, ManagementClient } from '@kontent-ai/management-sdk'; import { HttpService } from '@kontent-ai/core-sdk'; -import { defaultRetryStrategy, defaultWorkflowCodename, handleError, ItemType } from '../core'; +import { defaultRetryStrategy, defaultWorkflowCodename, handleError, ItemType, printProjectInfoToConsoleAsync } from '../core'; import { ICleanConfig, ICleanResult } from './clean.models'; export class CleanService { @@ -21,6 +21,8 @@ export class CleanService { public async cleanAllAsync(): Promise { try { + await printProjectInfoToConsoleAsync(this.client); + await this.cleanContentItemsAsync(); await this.cleanContentTypesAsync(); await this.cleanContentTypeSnippetsAsync(); diff --git a/lib/core/global-helper.ts b/lib/core/global-helper.ts index 3d7a3ab..273c76b 100644 --- a/lib/core/global-helper.ts +++ b/lib/core/global-helper.ts @@ -1,5 +1,6 @@ -import { SharedModels } from '@kontent-ai/management-sdk'; +import { IManagementClient, SharedModels } from '@kontent-ai/management-sdk'; import { IRetryStrategyOptions } from '@kontent-ai/core-sdk'; +import { yellow } from 'colors'; export const defaultRetryStrategy: IRetryStrategyOptions = { addJitter: true, @@ -8,6 +9,12 @@ export const defaultRetryStrategy: IRetryStrategyOptions = { deltaBackoffMs: 1000 }; +export async function printProjectInfoToConsoleAsync(client: IManagementClient): Promise { + const projectInformation = (await client.projectInformation().toPromise()).data; + console.log(`Project '${yellow(projectInformation.project.name)}'`); + console.log(`Environment '${yellow(projectInformation.project.environment)}'\n`); +} + export function getFilenameWithoutExtension(filename: string): string { if (!filename) { throw Error(`Invalid filename`); diff --git a/lib/export/export.service.ts b/lib/export/export.service.ts index 61a78e2..94b5649 100644 --- a/lib/export/export.service.ts +++ b/lib/export/export.service.ts @@ -13,11 +13,12 @@ import { WebhookContracts, CollectionContracts } from '@kontent-ai/management-sdk'; -import {HttpService } from '@kontent-ai/core-sdk'; +import { HttpService } from '@kontent-ai/core-sdk'; import { IExportAllResult, IExportConfig, IExportData } from './export.models'; -import { defaultRetryStrategy, ItemType } from '../core'; +import { defaultRetryStrategy, ItemType, printProjectInfoToConsoleAsync } from '../core'; import { version } from '../../package.json'; +import { green, red, yellow } from 'colors'; export class ExportService { private readonly client: ManagementClient; @@ -53,17 +54,34 @@ export class ExportService { let projectValidation: string | ProjectContracts.IProjectReportResponseContract; let isInconsistentExport: boolean = false; + await printProjectInfoToConsoleAsync(this.client); + if (!this.config.skipValidation) { + console.log(green('Running project validation')); projectValidation = await this.exportProjectValidationAsync(); - isInconsistentExport = projectValidation.type_issues.length > 0 || projectValidation.variant_issues.length > 0; - console.log(`Project validation - ${projectValidation.type_issues.length} type issues, ${projectValidation.variant_issues.length} variant issues`); + isInconsistentExport = + projectValidation.type_issues.length > 0 || projectValidation.variant_issues.length > 0; + console.log( + `Project validation results: ${ + projectValidation.type_issues.length + ? red(projectValidation.type_issues.length.toString()) + : green('0') + } type issues, ${ + projectValidation.variant_issues.length + ? red(projectValidation.variant_issues.length.toString()) + : green('0') + } variant issues` + ); + console.log('Projects with type or variant issues might not get imported back successfully'); } else { + console.log(red('Skipping project validation')); projectValidation = '{}'; - console.log('Skipping project validation endpoint'); } - const contentTypes = await this.exportContentTypesAsync({ processItem: exportItems.contentType }); + console.log(); + const contentTypes = await this.exportContentTypesAsync({ processItem: exportItems.contentType }); + const languages = await this.exportLanguagesAsync(); const contentItems = exportItems.contentItem || exportItems.languageVariant ? await this.exportContentItemsAsync() : []; @@ -76,10 +94,10 @@ export class ExportService { contentItems: exportItems.contentItem ? await this.exportContentItemsAsync() : [], collections: exportItems.collections ? await this.exportCollectionsAsync() : [], languageVariants: exportItems.languageVariant - ? await this.exportLanguageVariantsAsync(contentItems.map((m) => m.id)) + ? await this.exportLanguageVariantsAsync(contentItems, languages) : [], assets: exportItems.asset ? await this.exportAssetsAsync() : [], - languages: exportItems.language ? await this.exportLanguagesAsync() : [], + languages: exportItems.language ? languages : [], assetFolders: exportItems.assetFolder ? await this.exportAssetFoldersAsync() : [] }; @@ -208,15 +226,25 @@ export class ExportService { } public async exportLanguageVariantsAsync( - contentItemIds: string[] + contentItems: ContentItemContracts.IContentItemModelContract[], + languages: LanguageContracts.ILanguageModelContract[] ): Promise { const languageVariants: LanguageVariantContracts.ILanguageVariantModelWithComponentsContract[] = []; - for (const contentItemId of contentItemIds) { - const response = await this.client.listLanguageVariantsOfItem().byItemId(contentItemId).toPromise(); + for (const contentItem of contentItems) { + const response = await this.client.listLanguageVariantsOfItem().byItemId(contentItem.id).toPromise(); languageVariants.push(...response.data.items.map((m) => m._raw)); - response.data.items.forEach((m) => this.processItem(m.item.id?.toString() ?? '-', 'languageVariant', m)); + + for (const languageVariant of response.data.items) { + const language = languages.find((m) => m.id === languageVariant.language.id); + + this.processItem( + `${contentItem.name} (${yellow(language?.name ?? '')})`, + 'languageVariant', + languageVariant + ); + } } return languageVariants; diff --git a/lib/import/import.service.ts b/lib/import/import.service.ts index 23b2385..3bae34c 100644 --- a/lib/import/import.service.ts +++ b/lib/import/import.service.ts @@ -31,10 +31,12 @@ import { handleError, defaultWorkflowCodename, defaultObjectId, - defaultRetryStrategy + defaultRetryStrategy, + printProjectInfoToConsoleAsync } from '../core'; import { IBinaryFile, IImportConfig, IImportSource } from './import.models'; import { HttpService } from '@kontent-ai/core-sdk'; +import { yellow } from 'colors'; export class ImportService { private readonly defaultLanguageId: string = defaultObjectId; @@ -68,6 +70,8 @@ export class ImportService { sourceData: IImportSource ): Promise[]> { const importedItems: IImportItemResult[] = []; + await printProjectInfoToConsoleAsync(this.client); + // log information regarding version mismatch if (version !== sourceData.metadata.version) { console.warn( @@ -344,7 +348,9 @@ export class ImportService { // activate inactive languages if (!existingLanguage.isActive) { console.log( - `Language '${existingLanguage.name}' with codename '${existingLanguage.codename}' is not active in target project. Activating language.` + `Language '${yellow(existingLanguage.name)}' with codename '${yellow( + existingLanguage.codename + )}' is not active in target project. Activating language.` ); await this.client @@ -367,13 +373,17 @@ export class ImportService { if (!defaultExistingLanguage) { throw Error( - `Invalid default existing language. Language with id '${importLanguage.id}' was not found.` + `Invalid default existing language. Language with id '${yellow(importLanguage.id)}' was not found.` ); } if (importLanguage.codename !== defaultExistingLanguage.codename) { // languages do not match, change it console.log( - `Default language '${importLanguage.name}' with codename '${importLanguage.codename}' does not match default language in target project. Changing language codename in target project from '${defaultExistingLanguage.codename}' codename to '${importLanguage.codename}'` + `Default language '${yellow(importLanguage.name)}' with codename '${yellow( + importLanguage.codename + )}' does not match default language in target project. Changing language codename in target project from '${ + defaultExistingLanguage.codename + }' codename to '${importLanguage.codename}'` ); // check if language with imported codename exists @@ -392,7 +402,9 @@ export class ImportService { .toPromise(); } else { console.log( - `Language with codename '${importLanguage.codename}' already exists in target project, skipping update operation` + `Language with codename '${yellow( + importLanguage.codename + )}' already exists in target project, skipping update operation` ); } } @@ -408,7 +420,11 @@ export class ImportService { if (existingLanguage) { // no need to import it - console.log(`Skipping language '${existingLanguage.name}' with codename '${existingLanguage.codename}'`); + console.log( + `Skipping language '${yellow(existingLanguage.name)}' with codename '${yellow( + existingLanguage.codename + )}'` + ); return 'noImport'; } diff --git a/lib/node/cli/app.ts b/lib/node/cli/app.ts index b783826..cea52c4 100644 --- a/lib/node/cli/app.ts +++ b/lib/node/cli/app.ts @@ -10,6 +10,7 @@ import { ZipService } from '../../zip'; import { SharedModels } from '@kontent-ai/management-sdk'; import { FileService } from '../file/file.service'; import { fileHelper } from '../file/file-helper'; +import { green, red, yellow } from 'colors'; const argv = yargs(process.argv.slice(2)) .example('kbm --action=backup --apiKey=xxx --projectId=xxx', 'Creates zip backup of Kontent.ai project') @@ -41,10 +42,7 @@ const argv = yargs(process.argv.slice(2)) .alias('b', 'baseUrl') .describe('b', 'Custom base URL for Management API calls.') .alias('s', 'preserveWorkflow') - .describe( - 's', - 'Indicates if workflow information of language variants is preserved' - ) + .describe('s', 'Indicates if workflow information of language variants is preserved') .alias('e', 'exportFilter') .describe( 'e', @@ -62,7 +60,7 @@ const backupAsync = async (config: ICliFileConfig) => { skipValidation: config.skipValidation ?? false, onExport: (item) => { if (config.enableLog) { - console.log(`Exported: ${item.title} | ${item.type}`); + console.log(`Exported ${yellow(item.title)} (${green(item.type)})`); } } }); @@ -77,21 +75,11 @@ const backupAsync = async (config: ICliFileConfig) => { }); const response = await exportService.exportAllAsync(); - - if (response.metadata.isInconsistentExport) { - const logFilename: string = getLogFilename(config.zipFilename); - - console.log(`Project contains inconsistencies which may cause errors during project import.`); - console.log(`See '${logFilename}' for more details.`); - } else { - console.log(`Project does not contain any inconsistencies`); - } - const zipFileData = await zipService.createZipAsync(response); await fileService.writeFileAsync(config.zipFilename, zipFileData); - console.log('Completed'); + console.log(green('Completed')); }; const getLogFilename = (filename: string) => { @@ -102,7 +90,7 @@ const cleanAsync = async (config: ICliFileConfig) => { const cleanService = new CleanService({ onDelete: (item) => { if (config.enableLog) { - console.log(`Deleted: ${item.title} | ${item.type}`); + console.log(`Deleted: ${yellow(item.title)}`); } }, baseUrl: config.baseUrl, @@ -112,7 +100,7 @@ const cleanAsync = async (config: ICliFileConfig) => { await cleanService.cleanAllAsync(); - console.log('Completed'); + console.log(green('Completed')); }; const restoreAsync = async (config: ICliFileConfig) => { @@ -128,7 +116,7 @@ const restoreAsync = async (config: ICliFileConfig) => { const importService = new ImportService({ onImport: (item) => { if (config.enableLog) { - console.log(`Imported: ${item.title} | ${item.type}`); + console.log(`Imported: ${yellow(item.title)} (${green(item.type)})`); } }, preserveWorkflow: config.preserveWorkflow, @@ -152,14 +140,14 @@ const restoreAsync = async (config: ICliFileConfig) => { if (canImport(data, config)) { await importService.importFromSourceAsync(data); - console.log('Completed'); + console.log(green('Completed')); } else { const logFilename: string = getLogFilename(config.zipFilename); await fileHelper.createFileInCurrentFolderAsync(logFilename, JSON.stringify(data.validation)); console.log(`Project could not be imported due to data inconsistencies.`); - console.log(`A log file '${logFilename}' with issues was created in current folder.`); + console.log(`A log file '${yellow(logFilename)}' with issues was created in current folder.`); console.log(`To import data regardless of issues, set 'force' config parameter to true`); } }; @@ -286,11 +274,11 @@ run() .then((m) => {}) .catch((err) => { if (err instanceof SharedModels.ContentManagementBaseKontentError) { - console.log(`Management API error occured:`, err.message); + console.log(`Management API error occured:`, red(err.message)); for (const validationError of err.validationErrors) { console.log(validationError.message); } } else { - console.log(`There was an error processing your request: `, err); + console.log(`There was an error processing your request: `, red(err)); } }); diff --git a/lib/node/file/file.service.ts b/lib/node/file/file.service.ts index 5cd6f25..2b2ced1 100644 --- a/lib/node/file/file.service.ts +++ b/lib/node/file/file.service.ts @@ -1,19 +1,17 @@ +import { yellow } from 'colors'; import { promises } from 'fs'; import { IFileServiceConfig } from './file.models'; export class FileService { - - constructor(private config: IFileServiceConfig) { - } + constructor(private config: IFileServiceConfig) {} private readonly zipExtension: string = '.zip'; - async loadFileAsync(fileNameWithoutExtension: string): Promise { const filePath = this.getFilePath(fileNameWithoutExtension); if (this.config.enableLog) { - console.log(`Reading file '${filePath}'`); + console.log(`Reading file '${yellow(filePath)}'`); } const file = await promises.readFile(filePath); if (this.config.enableLog) { @@ -27,7 +25,7 @@ export class FileService { const filePath = this.getFilePath(fileNameWithoutExtension); if (this.config.enableLog) { - console.log(`Writing file '${filePath}'`); + console.log(`Writing file '${yellow(filePath)}'`); } await promises.writeFile(filePath, content); if (this.config.enableLog) { @@ -37,7 +35,6 @@ export class FileService { private getFilePath(fileNameWithoutExtension: string) { const filenameWithExtension = fileNameWithoutExtension + this.zipExtension; - return`./${filenameWithExtension}`; + return `./${filenameWithExtension}`; } - } diff --git a/lib/zip/zip.service.ts b/lib/zip/zip.service.ts index b2cfa3a..63e14cf 100644 --- a/lib/zip/zip.service.ts +++ b/lib/zip/zip.service.ts @@ -5,6 +5,7 @@ import * as JSZip from 'jszip'; import { IExportAllResult } from '../export'; import { IBinaryFile, IImportSource } from '../import'; import { IZipServiceConfig } from './zip.models'; +import { yellow } from 'colors'; export class ZipService { private readonly delayBetweenAssetRequestsMs: number; @@ -89,7 +90,7 @@ export class ZipService { const assetsFolder = zip.folder(this.filesName); if (!assetsFolder) { - throw Error(`Could not create folder '${this.filesName}'`); + throw Error(`Could not create folder '${yellow(this.filesName)}'`); } if (this.config.enableLog) { @@ -101,14 +102,14 @@ export class ZipService { const assetIdShortFolder = assetsFolder.folder(assetIdShortFolderName); if (!assetIdShortFolder) { - throw Error(`Could not create folder '${this.filesName}'`); + throw Error(`Could not create folder '${yellow(this.filesName)}'`); } const assetIdFolderName = asset.id; const assetIdFolder = assetIdShortFolder.folder(assetIdFolderName); if (!assetIdFolder) { - throw Error(`Could not create folder '${this.filesName}'`); + throw Error(`Could not create folder '${yellow(this.filesName)}'`); } const assetFilename = asset.file_name; @@ -183,7 +184,7 @@ export class ZipService { const file = files[filename]; if (!file) { - throw Error(`Invalid file '${filename}'`); + throw Error(`Invalid file '${yellow(filename)}'`); } const text = await file.async('text'); @@ -196,7 +197,7 @@ export class ZipService { url = url.replace('#', '%23'); if (enableLog) { - console.log(`Asset download: ${url}`); + console.log(`Asset download: ${yellow(url)}`); } return ( diff --git a/package-lock.json b/package-lock.json index b3b8319..5e5594b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@kontent-ai/management-sdk": "3.3.0", + "colors": "1.4.0", "jszip": "3.10.1", "yargs": "17.5.1" }, @@ -385,6 +386,14 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2991,6 +3000,11 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", diff --git a/package.json b/package.json index 7264b0d..8f670a2 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "dependencies": { "@kontent-ai/management-sdk": "3.3.0", "jszip": "3.10.1", - "yargs": "17.5.1" + "yargs": "17.5.1", + "colors": "1.4.0" }, "devDependencies": { "tslib": "2.4.0",