diff --git a/.talismanrc b/.talismanrc index 6ee1a9b..b1f7b3e 100644 --- a/.talismanrc +++ b/.talismanrc @@ -3,4 +3,8 @@ fileignoreconfig: checksum: 3d0b5cf07f5a87256f132f85a5556d193ce5a1fa6d92df2c7c50514071d592b7 - filename: package-lock.json checksum: 37a33f085b6df7ee03b326885bc38957b84fdb17984a3b03de04fd6921b42fee + - filename: src/commands/cm/stacks/export-query.ts + checksum: 62e15b1a2705c49ec7abfafa65e04654fdf5025361dd3485b2b9a78be70af1f6 + - filename: src/utils/logger.ts + checksum: 01a252f8f650b171f93a63ae241edd50352fde5e1e6ad5fca07c2390b38975f8 version: '1.0' diff --git a/package-lock.json b/package-lock.json index 518f2af..693859a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/cli-cm-export-query", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/cli-cm-export-query", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "license": "MIT", "dependencies": { "@contentstack/cli-cm-export": "~1.20.2", @@ -22,6 +22,7 @@ "mkdirp": "^1.0.4", "progress-stream": "^2.0.0", "promise-limit": "^2.7.0", + "tslib": "^2.8.1", "winston": "^3.17.0" }, "devDependencies": { diff --git a/package.json b/package.json index a2d9fac..27fdbf7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-export-query", "description": "Contentstack CLI plugin to export content from stack", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { @@ -18,6 +18,7 @@ "mkdirp": "^1.0.4", "progress-stream": "^2.0.0", "promise-limit": "^2.7.0", + "tslib": "^2.8.1", "winston": "^3.17.0" }, "devDependencies": { @@ -45,7 +46,7 @@ "typescript": "^4.9.5" }, "scripts": { - "build": "npm run clean && npm run compile && cp -r src/config lib/", + "build": "npm run clean && npm install && npm run compile && cp -r src/config lib/", "clean": "rm -rf ./lib ./node_modules tsconfig.build.tsbuildinfo", "compile": "tsc -b tsconfig.json", "postpack": "rm -f oclif.manifest.json", diff --git a/src/commands/cm/stacks/export-query.ts b/src/commands/cm/stacks/export-query.ts index cdd4172..06f9ba0 100644 --- a/src/commands/cm/stacks/export-query.ts +++ b/src/commands/cm/stacks/export-query.ts @@ -3,13 +3,14 @@ import { flags, FlagInput, sanitizePath, - formatError, managementSDKClient, ContentstackClient, + log, + handleAndLogError, } from '@contentstack/cli-utilities'; import { QueryExporter } from '../../../core/query-executor'; import { QueryExportConfig } from '../../../types'; -import { log, setupQueryExportConfig, setupBranches } from '../../../utils'; +import { setupQueryExportConfig, setupBranches, createLogContext } from '../../../utils'; export default class ExportQueryCommand extends Command { static description = 'Export content from a stack using query-based filtering'; @@ -82,6 +83,9 @@ export default class ExportQueryCommand extends Command { } this.exportDir = sanitizePath(exportQueryConfig.exportDir); + // Create base context without module name - module field is set dynamically during each module export + exportQueryConfig.context = createLogContext(exportQueryConfig); + log.debug('Export configuration setup completed', exportQueryConfig.context); // Initialize management API client const managementAPIClient: ContentstackClient = await managementSDKClient(exportQueryConfig); @@ -94,16 +98,18 @@ export default class ExportQueryCommand extends Command { // Setup branches (validate branch or set default to 'main') await setupBranches(exportQueryConfig, stackAPIClient); + log.debug('Branch configuration setup completed', exportQueryConfig.context); // Initialize and run query export + log.debug('Starting query exporter', exportQueryConfig.context); const queryExporter = new QueryExporter(managementAPIClient, exportQueryConfig); await queryExporter.execute(); + log.debug('Query exporter completed successfully', exportQueryConfig.context); - log(exportQueryConfig, 'Query-based export completed successfully!', 'success'); - log(exportQueryConfig, `Export files saved to: ${this.exportDir}`, 'info'); + log.success('Query-based export completed successfully!', exportQueryConfig.context); + log.info(`Export files saved to: ${this.exportDir}`, exportQueryConfig.context); } catch (error) { - log({ exportDir: this.exportDir } as QueryExportConfig, `Export failed: ${formatError(error)}`, 'error'); - throw error; + handleAndLogError(error); } } } diff --git a/src/core/module-exporter.ts b/src/core/module-exporter.ts index c115844..982c44b 100644 --- a/src/core/module-exporter.ts +++ b/src/core/module-exporter.ts @@ -1,7 +1,6 @@ -import { formatError } from '@contentstack/cli-utilities'; +import { log, handleAndLogError } from '@contentstack/cli-utilities'; import ExportCommand from '@contentstack/cli-cm-export'; import { QueryExportConfig, Modules, ExportOptions } from '../types'; -import { log } from '../utils/logger'; export class ModuleExporter { @@ -14,7 +13,9 @@ export class ModuleExporter { async exportModule(moduleName: Modules, options: ExportOptions = {}): Promise { try { - log(this.exportQueryConfig, `Exporting module: ${moduleName}`, 'info'); + const moduleLogContext = { ...this.exportQueryConfig.context, module: moduleName }; + log.info(`Exporting module: ${moduleName}`, moduleLogContext); + log.debug(`Building export command for module: ${moduleName}`, moduleLogContext); // Build command arguments const cmd = this.buildExportCommand(moduleName, options); @@ -25,6 +26,7 @@ export class ModuleExporter { // Create export command instance await ExportCommand.run(cmd); + log.debug(`Export command completed for module: ${moduleName}`, moduleLogContext); // Read the exported data // const data = await this.readExportedData(moduleName, options); @@ -34,9 +36,10 @@ export class ModuleExporter { } // success message - log(this.exportQueryConfig, `Successfully exported ${moduleName}`, 'success'); + log.success(`Successfully exported ${moduleName}`, moduleLogContext); } catch (error) { - log(this.exportQueryConfig, `Failed to export ${moduleName}: ${formatError(error)}`, 'error'); + const moduleLogContext = { ...this.exportQueryConfig.context, module: moduleName }; + handleAndLogError(error, moduleLogContext, `Failed to export ${moduleName}`); throw error; } } diff --git a/src/core/query-executor.ts b/src/core/query-executor.ts index c14036e..345a8c0 100644 --- a/src/core/query-executor.ts +++ b/src/core/query-executor.ts @@ -1,9 +1,8 @@ -import { ContentstackClient, sanitizePath } from '@contentstack/cli-utilities'; +import { ContentstackClient, sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; import * as path from 'path'; import { QueryExportConfig, Modules } from '../types'; import { QueryParser } from '../utils/query-parser'; import { ModuleExporter } from './module-exporter'; -import { log } from '../utils/logger'; import { ReferencedContentTypesHandler } from '../utils'; import { fsUtil } from '../utils'; import { ContentTypeDependenciesHandler } from '../utils'; @@ -28,11 +27,12 @@ export class QueryExporter { } async execute(): Promise { - log(this.exportQueryConfig, 'Starting query-based export...', 'info'); + log.info('Starting query-based export...', this.exportQueryConfig.context); // Step 1: Parse and validate query + log.debug('Parsing and validating query', this.exportQueryConfig.context); const parsedQuery = await this.queryParser.parse(this.exportQueryConfig.query); - log(this.exportQueryConfig, 'Query parsed and validated successfully', 'success'); + log.success('Query parsed and validated successfully', this.exportQueryConfig.context); // Step 2: Always export general modules await this.exportGeneralModules(); @@ -49,24 +49,27 @@ export class QueryExporter { ); const contentTypes: any = fsUtil.readFile(sanitizePath(contentTypesFilePath)) || []; if (contentTypes.length === 0) { - log(this.exportQueryConfig, 'No content types found, skipping export', 'info'); + log.info('No content types found, skipping export', this.exportQueryConfig.context); process.exit(0); } // Step 5: export other content types which are referenced in previous step + log.debug('Starting referenced content types export', this.exportQueryConfig.context); await this.exportReferencedContentTypes(); // Step 6: export dependent modules global fields, extensions, taxonomies + log.debug('Starting dependent modules export', this.exportQueryConfig.context); await this.exportDependentModules(); // Step 7: export content modules entries, assets + log.debug('Starting content modules export', this.exportQueryConfig.context); await this.exportContentModules(); // Step 9: export all other modules - log(this.exportQueryConfig, 'Query-based export completed successfully!', 'success'); + log.success('Query-based export completed successfully!', this.exportQueryConfig.context); } // export general modules private async exportGeneralModules(): Promise { - log(this.exportQueryConfig, 'Exporting general modules...', 'info'); + log.info('Exporting general modules...', this.exportQueryConfig.context); for (const module of this.exportQueryConfig.modules.general) { await this.moduleExporter.exportModule(module); @@ -74,22 +77,24 @@ export class QueryExporter { } private async exportQueriedModule(parsedQuery: any): Promise { + log.debug('Starting queried module export', this.exportQueryConfig.context); for (const [moduleName] of Object.entries(parsedQuery.modules)) { const module = moduleName as Modules; if (!this.exportQueryConfig.modules.queryable.includes(module)) { - log(this.exportQueryConfig, `Module "${module}" is not queryable`, 'error'); + log.error(`Module "${module}" is not queryable`, this.exportQueryConfig.context); continue; } - log(this.exportQueryConfig, `Exporting ${moduleName} with query...`, 'info'); + log.info(`Exporting ${moduleName} with query...`, this.exportQueryConfig.context); // Export the queried module await this.moduleExporter.exportModule(module, { query: parsedQuery }); } + log.debug('Queried module export completed', this.exportQueryConfig.context); } private async exportReferencedContentTypes(): Promise { - log(this.exportQueryConfig, 'Starting export of referenced content types...', 'info'); + log.info('Starting export of referenced content types...', this.exportQueryConfig.context); try { const referencedHandler = new ReferencedContentTypesHandler(this.exportQueryConfig); @@ -104,20 +109,21 @@ export class QueryExporter { ); const contentTypes: any = fsUtil.readFile(sanitizePath(contentTypesFilePath)) || []; if (contentTypes.length === 0) { - log(this.exportQueryConfig, 'No content types found, skipping referenced content types export', 'info'); + log.info('No content types found, skipping referenced content types export', this.exportQueryConfig.context); return; } // Step 2: Start with initial batch (all currently exported content types) let currentBatch = [...contentTypes]; - log(this.exportQueryConfig, `Starting with ${currentBatch.length} initial content types`, 'info'); + log.info(`Starting with ${currentBatch.length} initial content types`, this.exportQueryConfig.context); // track reference depth let iterationCount = 0; // Step 3: Process batches until no new references are found while (currentBatch.length > 0 && iterationCount < this.exportQueryConfig.maxCTReferenceDepth) { iterationCount++; + log.debug(`Processing referenced content types iteration ${iterationCount}`, this.exportQueryConfig.context); currentBatch.forEach((ct: any) => exportedContentTypeUIDs.add(ct.uid)); // Extract referenced content types from current batch const referencedUIDs = await referencedHandler.extractReferencedContentTypes(currentBatch); @@ -126,10 +132,9 @@ export class QueryExporter { const newReferencedUIDs = referencedUIDs.filter((uid: string) => !exportedContentTypeUIDs.has(uid)); if (newReferencedUIDs.length > 0) { - log( - this.exportQueryConfig, + log.info( `Found ${newReferencedUIDs.length} new referenced content types to fetch`, - 'info', + this.exportQueryConfig.context, ); // // Add to exported set to avoid duplicates in future iterations @@ -154,34 +159,35 @@ export class QueryExporter { // Push new content types to main array contentTypes.push(...newContentTypes); - log(this.exportQueryConfig, `Fetched ${currentBatch.length} new content types for next iteration`, 'info'); + log.info(`Fetched ${currentBatch.length} new content types for next iteration`, this.exportQueryConfig.context); } else { - log(this.exportQueryConfig, 'No new referenced content types found, stopping recursion', 'info'); + log.info('No new referenced content types found, stopping recursion', this.exportQueryConfig.context); break; } } fsUtil.writeFile(sanitizePath(contentTypesFilePath), contentTypes); - log(this.exportQueryConfig, 'Referenced content types export completed successfully', 'success'); + log.success('Referenced content types export completed successfully', this.exportQueryConfig.context); } catch (error) { - log(this.exportQueryConfig, `Error exporting referenced content types: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Error exporting referenced content types'); throw error; } } private async exportDependentModules(): Promise { - log(this.exportQueryConfig, 'Starting export of dependent modules...', 'info'); + log.info('Starting export of dependent modules...', this.exportQueryConfig.context); try { const dependenciesHandler = new ContentTypeDependenciesHandler(this.stackAPIClient, this.exportQueryConfig); // Extract dependencies from all exported content types const dependencies = await dependenciesHandler.extractDependencies(); + log.debug('Dependencies extracted successfully', this.exportQueryConfig.context); // Export Global Fields if (dependencies.globalFields.size > 0) { const globalFieldUIDs = Array.from(dependencies.globalFields); - log(this.exportQueryConfig, `Exporting ${globalFieldUIDs.length} global fields...`, 'info'); + log.info(`Exporting ${globalFieldUIDs.length} global fields...`, this.exportQueryConfig.context); const query = { modules: { @@ -196,7 +202,7 @@ export class QueryExporter { // Export Extensions if (dependencies.extensions.size > 0) { const extensionUIDs = Array.from(dependencies.extensions); - log(this.exportQueryConfig, `Exporting ${extensionUIDs.length} extensions...`, 'info'); + log.info(`Exporting ${extensionUIDs.length} extensions...`, this.exportQueryConfig.context); const query = { modules: { @@ -211,7 +217,7 @@ export class QueryExporter { // export marketplace apps if (dependencies.marketplaceApps.size > 0) { const marketplaceAppInstallationUIDs = Array.from(dependencies.marketplaceApps); - log(this.exportQueryConfig, `Exporting ${marketplaceAppInstallationUIDs.length} marketplace apps...`, 'info'); + log.info(`Exporting ${marketplaceAppInstallationUIDs.length} marketplace apps...`, this.exportQueryConfig.context); const query = { modules: { 'marketplace-apps': { @@ -225,7 +231,7 @@ export class QueryExporter { // Export Taxonomies if (dependencies.taxonomies.size > 0) { const taxonomyUIDs = Array.from(dependencies.taxonomies); - log(this.exportQueryConfig, `Exporting ${taxonomyUIDs.length} taxonomies...`, 'info'); + log.info(`Exporting ${taxonomyUIDs.length} taxonomies...`, this.exportQueryConfig.context); const query = { modules: { @@ -240,15 +246,15 @@ export class QueryExporter { // export personalize await this.moduleExporter.exportModule('personalize'); - log(this.exportQueryConfig, 'Dependent modules export completed successfully', 'success'); + log.success('Dependent modules export completed successfully', this.exportQueryConfig.context); } catch (error) { - log(this.exportQueryConfig, `Error exporting dependent modules: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Error exporting dependent modules'); throw error; } } private async exportContentModules(): Promise { - log(this.exportQueryConfig, 'Starting export of content modules...', 'info'); + log.info('Starting export of content modules...', this.exportQueryConfig.context); try { // Step 1: Export entries for all exported content types @@ -260,30 +266,30 @@ export class QueryExporter { await new Promise((resolve) => setTimeout(resolve, delay)); await this.exportReferencedAssets(); - log(this.exportQueryConfig, 'Content modules export completed successfully', 'success'); + log.success('Content modules export completed successfully', this.exportQueryConfig.context); } catch (error) { - log(this.exportQueryConfig, `Error exporting content modules: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Error exporting content modules'); throw error; } } private async exportEntries(): Promise { - log(this.exportQueryConfig, 'Exporting entries...', 'info'); + log.info('Exporting entries...', this.exportQueryConfig.context); try { // Export entries - module exporter will automatically read exported content types // and export entries for all of them await this.moduleExporter.exportModule('entries'); - log(this.exportQueryConfig, 'Entries export completed successfully', 'success'); + log.success('Entries export completed successfully', this.exportQueryConfig.context); } catch (error) { - log(this.exportQueryConfig, `Error exporting entries: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Error exporting entries'); throw error; } } private async exportReferencedAssets(): Promise { - log(this.exportQueryConfig, 'Starting export of referenced assets...', 'info'); + log.info('Starting export of referenced assets...', this.exportQueryConfig.context); try { const assetsDir = path.join( @@ -302,10 +308,11 @@ export class QueryExporter { const assetHandler = new AssetReferenceHandler(this.exportQueryConfig); // Extract referenced asset UIDs from all entries + log.debug('Extracting referenced assets from entries', this.exportQueryConfig.context); const assetUIDs = assetHandler.extractReferencedAssets(); if (assetUIDs.length > 0) { - log(this.exportQueryConfig, `Found ${assetUIDs.length} referenced assets to export`, 'info'); + log.info(`Found ${assetUIDs.length} referenced assets to export`, this.exportQueryConfig.context); // Define batch size - can be configurable through exportQueryConfig const batchSize = this.exportQueryConfig.assetBatchSize || 100; @@ -325,7 +332,7 @@ export class QueryExporter { // if asset size is bigger than batch size, then we need to export in batches // Calculate number of batches const totalBatches = Math.ceil(assetUIDs.length / batchSize); - log(this.exportQueryConfig, `Processing assets in ${totalBatches} batches of ${batchSize}`, 'info'); + log.info(`Processing assets in ${totalBatches} batches of ${batchSize}`, this.exportQueryConfig.context); // Process assets in batches for (let i = 0; i < totalBatches; i++) { @@ -333,10 +340,9 @@ export class QueryExporter { const end = Math.min(start + batchSize, assetUIDs.length); const batchAssetUIDs = assetUIDs.slice(start, end); - log( - this.exportQueryConfig, + log.info( `Exporting batch ${i + 1}/${totalBatches} (${batchAssetUIDs.length} assets)...`, - 'info', + this.exportQueryConfig.context, ); const query = { @@ -358,7 +364,7 @@ export class QueryExporter { // For first batch, initialize temp files with current content fsUtil.writeFile(sanitizePath(tempMetadataFilePath), currentMetadata); fsUtil.writeFile(sanitizePath(tempAssetFilePath), currentAssets); - log(this.exportQueryConfig, `Initialized temporary files with first batch data`, 'info'); + log.info(`Initialized temporary files with first batch data`, this.exportQueryConfig.context); } else { // For subsequent batches, append to temp files with incremented keys @@ -389,7 +395,7 @@ export class QueryExporter { fsUtil.writeFile(sanitizePath(tempAssetFilePath), tempAssets); - log(this.exportQueryConfig, `Updated temporary files with batch ${i + 1} data`, 'info'); + log.info(`Updated temporary files with batch ${i + 1} data`, this.exportQueryConfig.context); } // Optional: Add delay between batches to avoid rate limiting @@ -405,19 +411,19 @@ export class QueryExporter { fsUtil.writeFile(sanitizePath(metadataFilePath), finalMetadata); fsUtil.writeFile(sanitizePath(assetFilePath), finalAssets); - log(this.exportQueryConfig, `Final data written back to original files`, 'info'); + log.info(`Final data written back to original files`, this.exportQueryConfig.context); // Clean up temp files fsUtil.removeFile(sanitizePath(tempMetadataFilePath)); fsUtil.removeFile(sanitizePath(tempAssetFilePath)); - log(this.exportQueryConfig, `Temporary files cleaned up`, 'info'); - log(this.exportQueryConfig, 'Referenced assets exported successfully', 'success'); + log.info(`Temporary files cleaned up`, this.exportQueryConfig.context); + log.success('Referenced assets exported successfully', this.exportQueryConfig.context); } else { - log(this.exportQueryConfig, 'No referenced assets found in entries', 'info'); + log.info('No referenced assets found in entries', this.exportQueryConfig.context); } } catch (error) { - log(this.exportQueryConfig, `Error exporting referenced assets: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Error exporting referenced assets'); throw error; } } diff --git a/src/types/index.ts b/src/types/index.ts index d91481a..1a036ea 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -174,6 +174,20 @@ export interface DefaultConfig { maxCTReferenceDepth: number; } +/** + * Log context interface for centralized logging + */ +export interface LogContext { + command: string; + module: string; + email: string; + sessionId: string; + apiKey: string; + orgId: string; + authenticationMethod: string; + [key: string]: unknown; +} + export interface QueryExportConfig extends DefaultConfig { query: string; skipReferences: boolean; @@ -189,6 +203,7 @@ export interface QueryExportConfig extends DefaultConfig { batchDelayMs?: number; assetBatchSize?: number; assetBatchDelayMs?: number; + context?: LogContext; // Log context for centralized logging } export interface QueryMetadata { diff --git a/src/utils/branch-helper.ts b/src/utils/branch-helper.ts index 9afc118..43b0078 100644 --- a/src/utils/branch-helper.ts +++ b/src/utils/branch-helper.ts @@ -1,7 +1,6 @@ -import { getBranchFromAlias} from '@contentstack/cli-utilities'; +import { getBranchFromAlias, log, handleAndLogError } from '@contentstack/cli-utilities'; import { QueryExportConfig } from '../types'; -import { log } from './logger'; /** * Validates and sets up branch configuration for the stack @@ -15,6 +14,8 @@ export const setupBranches = async (config: QueryExportConfig, stackAPIClient: a throw new Error('The branch configuration is invalid.'); } + const context = config.context; + try { if (config.branchAlias) { config.branchName = await getBranchFromAlias(stackAPIClient, config.branchAlias); @@ -22,31 +23,31 @@ export const setupBranches = async (config: QueryExportConfig, stackAPIClient: a } if (config.branchName) { // Check if the specified branch exists - log(config, `Validating branch: ${config.branchName}`, 'info'); + log.info(`Validating branch: ${config.branchName}`, context); const result = await stackAPIClient .branch(config.branchName) .fetch() - .catch((err: Error): any => { - log(config, `Error fetching branch: ${err.message}`, 'error'); + .catch((err: unknown): any => { + handleAndLogError(err, context, 'Error fetching branch'); return null; }); if (result && typeof result === 'object') { - log(config, `Branch '${config.branchName}' found`, 'success'); + log.success(`Branch '${config.branchName}' found`, context); } else { throw new Error(`No branch found named ${config.branchName}.`); } } else { // If no branch name provided, check if the stack has branches - log(config, 'No branch specified, checking if stack has branches', 'info'); + log.info('No branch specified, checking if stack has branches', context); const result = await stackAPIClient .branch() .query() .find() .catch((): any => { - log(config, 'Stack does not have branches', 'info'); + log.info('Stack does not have branches', context); return null; }); @@ -55,13 +56,13 @@ export const setupBranches = async (config: QueryExportConfig, stackAPIClient: a config.branchName = 'main'; } else { // Stack doesn't have branches - log(config, 'Stack does not have branches', 'info'); + log.info('Stack does not have branches', context); return; } } config.branchEnabled = true; } catch (error) { - log(config, `Error setting up branches: ${error.message}`, 'error'); + handleAndLogError(error, context, 'Error setting up branches'); throw error; } }; diff --git a/src/utils/content-type-helper.ts b/src/utils/content-type-helper.ts index a54b8fe..ebb326f 100644 --- a/src/utils/content-type-helper.ts +++ b/src/utils/content-type-helper.ts @@ -1,6 +1,6 @@ import * as path from 'path'; +import { log } from '@contentstack/cli-utilities'; import { QueryExportConfig } from '../types'; -import { log } from './logger'; export class ReferencedContentTypesHandler { private exportQueryConfig: QueryExportConfig; @@ -16,7 +16,7 @@ export class ReferencedContentTypesHandler { async extractReferencedContentTypes(contentTypeBatch: any[]): Promise { const allReferencedTypes: Set = new Set(); - log(this.exportQueryConfig, `Extracting references from ${contentTypeBatch.length} content types`, 'info'); + log.info(`Extracting references from ${contentTypeBatch.length} content types`, this.exportQueryConfig.context); for (const contentType of contentTypeBatch) { if (contentType.schema) { @@ -26,7 +26,7 @@ export class ReferencedContentTypesHandler { } const result = Array.from(allReferencedTypes); - log(this.exportQueryConfig, `Found ${result.length} referenced content types`, 'info'); + log.info(`Found ${result.length} referenced content types`, this.exportQueryConfig.context); return result; } diff --git a/src/utils/dependency-resolver.ts b/src/utils/dependency-resolver.ts index 55eda28..47c9572 100644 --- a/src/utils/dependency-resolver.ts +++ b/src/utils/dependency-resolver.ts @@ -1,8 +1,7 @@ import * as path from 'path'; import { QueryExportConfig } from '../types'; import { fsUtil } from './index'; -import { ContentstackClient, sanitizePath } from '@contentstack/cli-utilities'; -import { log } from './logger'; +import { ContentstackClient, sanitizePath, log, formatError, handleAndLogError } from '@contentstack/cli-utilities'; export class ContentTypeDependenciesHandler { private exportQueryConfig: QueryExportConfig; @@ -27,7 +26,7 @@ export class ContentTypeDependenciesHandler { ); const allContentTypes = (fsUtil.readFile(sanitizePath(contentTypesFilePath)) as any[]) || []; if (allContentTypes.length === 0) { - log(this.exportQueryConfig, 'No content types found, skipping dependency extraction', 'info'); + log.info('No content types found, skipping dependency extraction', this.exportQueryConfig.context); return { globalFields: new Set(), extensions: new Set(), @@ -36,7 +35,7 @@ export class ContentTypeDependenciesHandler { }; } - log(this.exportQueryConfig, `Extracting dependencies from ${allContentTypes.length} content types`, 'info'); + log.info(`Extracting dependencies from ${allContentTypes.length} content types`, this.exportQueryConfig.context); const dependencies = { globalFields: new Set(), @@ -54,30 +53,27 @@ export class ContentTypeDependenciesHandler { // Separate extensions from marketplace apps using the extracted extension UIDs if (dependencies.extensions.size > 0) { const extensionUIDs = Array.from(dependencies.extensions); - log( - this.exportQueryConfig, + log.info( `Processing ${extensionUIDs.length} extensions to identify marketplace apps...`, - 'info', + this.exportQueryConfig.context, ); try { const { extensions, marketplaceApps } = await this.fetchExtensionsAndMarketplaceApps(extensionUIDs); dependencies.extensions = new Set(extensions); dependencies.marketplaceApps = new Set(marketplaceApps); - log( - this.exportQueryConfig, + log.info( `Dependencies separated - Global Fields: ${dependencies.globalFields.size}, Extensions: ${dependencies.extensions.size}, Taxonomies: ${dependencies.taxonomies.size}, Marketplace Apps: ${dependencies.marketplaceApps.size}`, - 'info', + this.exportQueryConfig.context, ); } catch (error) { - log(this.exportQueryConfig, `Failed to separate extensions and Marketplace apps: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Failed to separate extensions and Marketplace apps'); // Keep original extensions if separation fails } } else { - log( - this.exportQueryConfig, + log.info( `Found dependencies - Global Fields: ${dependencies.globalFields.size}, Extensions: ${dependencies.extensions.size}, Taxonomies: ${dependencies.taxonomies.size}, Marketplace Apps: ${dependencies.marketplaceApps.size}`, - 'info', + this.exportQueryConfig.context, ); } @@ -88,10 +84,9 @@ export class ContentTypeDependenciesHandler { async fetchExtensionsAndMarketplaceApps( extensionUIDs: string[], ): Promise<{ extensions: string[]; marketplaceApps: string[] }> { - log( - this.exportQueryConfig, + log.info( `Fetching details for ${extensionUIDs.length} extensions to identify marketplace apps...`, - 'info', + this.exportQueryConfig.context, ); try { @@ -108,7 +103,7 @@ export class ContentTypeDependenciesHandler { const response = await this.stackAPIClient.extension().query(queryParams).find(); if (!response || !response.items) { - log(this.exportQueryConfig, `No extensions found`, 'warn'); + log.warn(`No extensions found`, this.exportQueryConfig.context); return { extensions: extensionUIDs, marketplaceApps: [] }; } @@ -123,15 +118,14 @@ export class ContentTypeDependenciesHandler { } }); - log( - this.exportQueryConfig, + log.info( `Identified ${marketplaceApps.length} marketplace apps and ${regularExtensions.length} regular extensions from ${extensionUIDs.length} total extensions`, - 'info', + this.exportQueryConfig.context, ); return { extensions: regularExtensions, marketplaceApps }; } catch (error) { - log(this.exportQueryConfig, `Failed to fetch extensions and Marketplace apps: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Failed to fetch extensions and Marketplace apps'); return { extensions: extensionUIDs, marketplaceApps: [] }; } } diff --git a/src/utils/index.ts b/src/utils/index.ts index 693a8d5..0687cbd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ export * as fileHelper from './file-helper'; export { fsUtil } from './file-helper'; -export { log, unlinkFileLogger } from './logger'; +export { log, unlinkFileLogger, createLogContext } from './logger'; +export { LogContext } from '../types'; export * from './common-helper'; export * from './config-handler'; export * from './content-type-helper'; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index ec2ca4b..9be2a2f 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -7,8 +7,8 @@ import * as winston from 'winston'; import * as path from 'path'; import mkdirp from 'mkdirp'; -import { QueryExportConfig } from '../types'; -import { sanitizePath, redactObject } from '@contentstack/cli-utilities'; +import { QueryExportConfig, LogContext } from '../types'; +import { sanitizePath, redactObject, configHandler } from '@contentstack/cli-utilities'; const slice = Array.prototype.slice; const ansiRegexPattern = [ @@ -166,3 +166,19 @@ export const unlinkFileLogger = () => { }); } }; + +/** + * Creates a context object for logging from QueryExportConfig + */ +export function createLogContext(config: QueryExportConfig, moduleName?: string): LogContext { + return { + command: 'cm:stacks:export-query', + module: moduleName || '', + email: configHandler.get('email') || '', + sessionId: configHandler.get('sessionId') || '', + apiKey: config.stackApiKey || '', + orgId: configHandler.get('oauthOrgUid') || '', + authenticationMethod: config.managementToken ? 'Management Token' : 'Basic Auth', + }; +} + diff --git a/src/utils/query-parser.ts b/src/utils/query-parser.ts index fd87de7..bdd1c5b 100644 --- a/src/utils/query-parser.ts +++ b/src/utils/query-parser.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import { CLIError } from '@contentstack/cli-utilities'; +import { CLIError, handleAndLogError } from '@contentstack/cli-utilities'; import { QueryExportConfig } from '../types'; export class QueryParser { @@ -28,7 +28,7 @@ export class QueryParser { const content = fs.readFileSync(filePath, 'utf-8'); return JSON.parse(content); } catch (error) { - throw new CLIError(`Failed to parse the query file: ${error.message}`); + handleAndLogError(error, this.config.context, 'Failed to parse the query file'); } } @@ -36,7 +36,7 @@ export class QueryParser { try { return JSON.parse(queryString); } catch (error) { - throw new CLIError(`Invalid JSON query: ${error.message}`); + handleAndLogError(error, this.config.context, 'Invalid JSON query'); } } diff --git a/src/utils/referenced-asset-handler.ts b/src/utils/referenced-asset-handler.ts index 782abf3..247b150 100644 --- a/src/utils/referenced-asset-handler.ts +++ b/src/utils/referenced-asset-handler.ts @@ -2,8 +2,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { QueryExportConfig } from '../types'; import { fsUtil } from './index'; -import { sanitizePath } from '@contentstack/cli-utilities'; -import { log } from './logger'; +import { sanitizePath, log, formatError, handleAndLogError } from '@contentstack/cli-utilities'; export class AssetReferenceHandler { private exportQueryConfig: QueryExportConfig; @@ -22,11 +21,11 @@ export class AssetReferenceHandler { * Extract all asset UIDs by processing entries file by file (memory efficient) */ extractReferencedAssets(): string[] { - log(this.exportQueryConfig, 'Extracting referenced assets from entries...', 'info'); + log.info('Extracting referenced assets from entries...', this.exportQueryConfig.context); try { if (!fs.existsSync(this.entriesDir)) { - log(this.exportQueryConfig, 'Entries directory does not exist', 'warn'); + log.warn('Entries directory does not exist', this.exportQueryConfig.context); return []; } @@ -44,15 +43,14 @@ export class AssetReferenceHandler { } const result = Array.from(globalAssetUIDs); - log( - this.exportQueryConfig, + log.info( `Found ${result.length} unique asset UIDs from ${totalEntriesProcessed} entries across ${jsonFiles.length} files`, - 'info', + this.exportQueryConfig.context, ); return result; } catch (error) { - log(this.exportQueryConfig, `Failed to extract assets: ${error.message}`, 'error'); + handleAndLogError(error, this.exportQueryConfig.context, 'Failed to extract assets'); return []; } } @@ -84,12 +82,11 @@ export class AssetReferenceHandler { // Count entries for logging const entriesCount = Object.keys(fileContent).length; - - log(this.exportQueryConfig, `Processed ${entriesCount} entries from ${path.basename(filePath)}`, 'debug'); + log.debug(`Processed ${entriesCount} entries from ${path.basename(filePath)}`, this.exportQueryConfig.context); return entriesCount; } catch (error) { - log(this.exportQueryConfig, `Failed to process file ${filePath}: ${error.message}`, 'warn'); + log.warn(`Failed to process file ${filePath}: ${formatError(error)}`, this.exportQueryConfig.context); return 0; } } @@ -152,7 +149,7 @@ export class AssetReferenceHandler { } } } catch (error) { - log(this.exportQueryConfig, `Failed to read directory ${dir}: ${error.message}`, 'warn'); + log.warn(`Failed to read directory ${dir}: ${formatError(error)}`, this.exportQueryConfig.context); } return jsonFiles;