From ec822a5bfbac21273ae05d6289078db48c48ec8d Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Tue, 14 Jul 2026 15:23:47 +0530 Subject: [PATCH 1/5] feat(bulk-operations): integrate progress manager UI Wire CLIProgressManager/SummaryManager into the bulk operations commands, matching the export/import/audit pattern: - replace the direct `chalk` import with loadChalk()/getChalk() (ESM-safe) - set/clear `log.progressSupportedModule` in init()/finally(); null-reset first so pre-flight/auth/setup errors still reach the console - initialize the run-level global summary + header in executeBulkOperation (empty-branch fallback like import; label suffixed with branch when set) - show a loading spinner while the async batch/job queue drains, then record accurate success/failed counts from the final result - print the global summary and clear the progress-module flag in finally() --- .../src/base-bulk-command.ts | 81 +++++++++++++++++-- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/packages/contentstack-bulk-operations/src/base-bulk-command.ts b/packages/contentstack-bulk-operations/src/base-bulk-command.ts index 55436fc33..d932eec2d 100644 --- a/packages/contentstack-bulk-operations/src/base-bulk-command.ts +++ b/packages/contentstack-bulk-operations/src/base-bulk-command.ts @@ -1,6 +1,17 @@ -import chalk from 'chalk'; import { Command } from '@contentstack/cli-command'; -import { flags, log, createLogContext, getLogPath, handleAndLogError, FlagInput } from '@contentstack/cli-utilities'; +import { + flags, + log, + createLogContext, + getLogPath, + handleAndLogError, + FlagInput, + loadChalk, + getChalk, + configHandler, + CLIProgressManager, + clearProgressModuleSetting, +} from '@contentstack/cli-utilities'; import config from './config'; import messages, { $t } from './messages'; @@ -141,6 +152,11 @@ export abstract class BaseBulkCommand extends Command { protected async init(): Promise { await super.init(); + // Progress UI: load chalk (ESM) up-front and clear any stale progress-module flag so + // pre-flight/auth/setup errors always reach the console regardless of showConsoleLogs. + await loadChalk(); + configHandler.set('log.progressSupportedModule', null); + let { flags } = await this.parse(this.constructor as typeof BaseBulkCommand); this.parsedFlags = flags; @@ -176,6 +192,9 @@ export abstract class BaseBulkCommand extends Command { await this.setupStack(); await this.initializeComponents(); + // Setup succeeded — enable progress-bar UI (console logs suppressed by default) for the operation. + configHandler.set('log.progressSupportedModule', 'bulk-operations'); + this.logger.debug($t(messages.INITIALIZING, { resourceType: this.resourceType }), this.loggerContext); } @@ -219,6 +238,9 @@ export abstract class BaseBulkCommand extends Command { await this.setupStack(); await this.initializeComponents(); + // Setup succeeded — enable progress-bar UI for the revert/retry operation. + configHandler.set('log.progressSupportedModule', 'bulk-operations'); + await this.handleRevertOrRetry(mergedFlags); process.exit(0); } @@ -365,20 +387,58 @@ export abstract class BaseBulkCommand extends Command { this.logger.debug($t(messages.EXECUTING_OPERATION, { count: items.length }), this.loggerContext); const startTime = Date.now(); + const showConsoleLogs = Boolean(configHandler.get('log')?.showConsoleLogs); + const operationLabel = (this.bulkOperationConfig.operation || 'operation').toString().toUpperCase(); + + // Initialize the run-level summary + header once (progress-manager UX, same as import). + // Fall back to an empty branch name; displayOperationHeader applies its own `|| 'main'` + // for display, and the label is suffixed with the branch only when one is set. + const branchName = this.bulkOperationConfig.branch || ''; + CLIProgressManager.initializeGlobalSummary( + branchName ? `BULK ${operationLabel}-${branchName}` : `BULK ${operationLabel}`, + branchName, + $t(messages.EXECUTING_OPERATION, { count: items.length }), + ); + + let result: BulkOperationResult; try { logOperationInfo(items, this.logger); const publishMode = this.bulkOperationConfig.publishMode || PublishMode.BULK; this.logger.debug(`Using ${publishMode.toUpperCase()} mode for operation`, this.loggerContext); - if (publishMode === PublishMode.SINGLE) { - return await this.executeSingleMode(items, startTime); - } - - return await this.executeBulkMode(items, startTime); + // The Bulk API submits async batch/jobs, so per-item completion isn't observable + // mid-flight. Show a spinner while the queue drains, then record accurate pass/fail + // counts from the final result into the progress-manager summary. + result = await CLIProgressManager.withLoadingSpinner( + $t(messages.EXECUTING_OPERATION, { count: items.length }), + async () => + publishMode === PublishMode.SINGLE + ? this.executeSingleMode(items, startTime) + : this.executeBulkMode(items, startTime), + ); } catch (error: any) { - return handleOperationError(error, items, startTime); + result = handleOperationError(error, items, startTime); } + + this.recordProgressSummary(result, showConsoleLogs); + return result; + } + + /** + * Record accurate success/failure counts from the final result into the + * CLIProgressManager summary. Bulk operations submit async jobs, so counts come from + * the aggregated result rather than live per-item ticks. + */ + private recordProgressSummary(result: BulkOperationResult, showConsoleLogs: boolean): void { + const total = result?.total || 0; + const failed = result?.failed || 0; + const success = typeof result?.success === 'number' ? result.success : Math.max(total - failed, 0); + + const progress = CLIProgressManager.createSimple(this.resourceType, total, showConsoleLogs); + for (let i = 0; i < success; i++) progress.tick(true); + for (let i = 0; i < failed; i++) progress.tick(false); + progress.complete(failed === 0); } /** @@ -444,6 +504,7 @@ export abstract class BaseBulkCommand extends Command { * Called at the end of run() method in subclasses */ protected printOperationSummary(result: BulkOperationResult): void { + const chalk = getChalk(); const publishMode = this.bulkOperationConfig.publishMode || PublishMode.BULK; console.log(''); @@ -555,6 +616,10 @@ export abstract class BaseBulkCommand extends Command { abstract run(): Promise; protected async finally(_error: Error | undefined): Promise { + // Print the run-level progress summary, then clear the progress-module flag so it + // never leaks into a later command in the same process (mirrors export/import/clone). + CLIProgressManager.printGlobalSummary(); + clearProgressModuleSetting(); await this.cleanup(); } } From 2424bbaa59c23de1975e5ff0cccb6a8358fbdd56 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Tue, 14 Jul 2026 17:18:36 +0530 Subject: [PATCH 2/5] refactor(bulk-operations): align progress-manager wiring --- .../src/base-am-command.ts | 22 ++++- .../src/base-bulk-command.ts | 87 ++++++++++--------- .../src/commands/cm/stacks/bulk-taxonomies.ts | 16 +++- .../src/utils/config-builder.ts | 5 ++ 4 files changed, 85 insertions(+), 45 deletions(-) diff --git a/packages/contentstack-bulk-operations/src/base-am-command.ts b/packages/contentstack-bulk-operations/src/base-am-command.ts index 0d23b9c93..769a1374b 100644 --- a/packages/contentstack-bulk-operations/src/base-am-command.ts +++ b/packages/contentstack-bulk-operations/src/base-am-command.ts @@ -1,5 +1,11 @@ import { Command } from '@contentstack/cli-command'; -import { handleAndLogError } from '@contentstack/cli-utilities'; +import { + handleAndLogError, + configHandler, + loadChalk, + CLIProgressManager, + clearProgressModuleSetting, +} from '@contentstack/cli-utilities'; import { fillMissingCsAssetsFlags } from './utils'; import type { CsAssetsFlags } from './interfaces'; @@ -16,6 +22,12 @@ export abstract class BaseCsAssetsCommand extends Command { protected async init(): Promise { await super.init(); + + // Suppress timestamped console logs + load chalk (same UX as the other bulk commands). + // Must run before the first log call. CS Assets keeps its own printCsAssetsSummary output. + configHandler.set('log.progressSupportedModule', 'bulk-operations'); + await loadChalk(); + const { flags } = await this.parse(this.constructor as typeof BaseCsAssetsCommand); this.loggerContext = { module: this.id ?? 'cm:stacks:bulk-am-assets' }; this.parsedFlags = (await fillMissingCsAssetsFlags(flags)) as CsAssetsFlags; @@ -25,5 +37,13 @@ export abstract class BaseCsAssetsCommand extends Command { handleAndLogError(error); } + protected async finally(_error?: Error): Promise { + // Clear progress state so the module flag never leaks into a later command in the process. + // (CS Assets doesn't initialize a global summary, so printGlobalSummary is a no-op here.) + CLIProgressManager.printGlobalSummary(); + CLIProgressManager.clearGlobalSummary(); + clearProgressModuleSetting(); + } + abstract run(): Promise; } diff --git a/packages/contentstack-bulk-operations/src/base-bulk-command.ts b/packages/contentstack-bulk-operations/src/base-bulk-command.ts index d932eec2d..d04d12836 100644 --- a/packages/contentstack-bulk-operations/src/base-bulk-command.ts +++ b/packages/contentstack-bulk-operations/src/base-bulk-command.ts @@ -6,8 +6,8 @@ import { getLogPath, handleAndLogError, FlagInput, - loadChalk, getChalk, + loadChalk, configHandler, CLIProgressManager, clearProgressModuleSetting, @@ -152,10 +152,9 @@ export abstract class BaseBulkCommand extends Command { protected async init(): Promise { await super.init(); - // Progress UI: load chalk (ESM) up-front and clear any stale progress-module flag so - // pre-flight/auth/setup errors always reach the console regardless of showConsoleLogs. + // Load chalk (ESM) up-front. The progress-module flag is set in buildConfig() + // (config layer, mirrors export-config-handler) before the first log call. await loadChalk(); - configHandler.set('log.progressSupportedModule', null); let { flags } = await this.parse(this.constructor as typeof BaseBulkCommand); @@ -192,9 +191,6 @@ export abstract class BaseBulkCommand extends Command { await this.setupStack(); await this.initializeComponents(); - // Setup succeeded — enable progress-bar UI (console logs suppressed by default) for the operation. - configHandler.set('log.progressSupportedModule', 'bulk-operations'); - this.logger.debug($t(messages.INITIALIZING, { resourceType: this.resourceType }), this.loggerContext); } @@ -238,9 +234,6 @@ export abstract class BaseBulkCommand extends Command { await this.setupStack(); await this.initializeComponents(); - // Setup succeeded — enable progress-bar UI for the revert/retry operation. - configHandler.set('log.progressSupportedModule', 'bulk-operations'); - await this.handleRevertOrRetry(mergedFlags); process.exit(0); } @@ -387,18 +380,8 @@ export abstract class BaseBulkCommand extends Command { this.logger.debug($t(messages.EXECUTING_OPERATION, { count: items.length }), this.loggerContext); const startTime = Date.now(); - const showConsoleLogs = Boolean(configHandler.get('log')?.showConsoleLogs); - const operationLabel = (this.bulkOperationConfig.operation || 'operation').toString().toUpperCase(); - // Initialize the run-level summary + header once (progress-manager UX, same as import). - // Fall back to an empty branch name; displayOperationHeader applies its own `|| 'main'` - // for display, and the label is suffixed with the branch only when one is set. - const branchName = this.bulkOperationConfig.branch || ''; - CLIProgressManager.initializeGlobalSummary( - branchName ? `BULK ${operationLabel}-${branchName}` : `BULK ${operationLabel}`, - branchName, - $t(messages.EXECUTING_OPERATION, { count: items.length }), - ); + this.beginOperationSummary(items.length); let result: BulkOperationResult; try { @@ -407,33 +390,44 @@ export abstract class BaseBulkCommand extends Command { const publishMode = this.bulkOperationConfig.publishMode || PublishMode.BULK; this.logger.debug(`Using ${publishMode.toUpperCase()} mode for operation`, this.loggerContext); - // The Bulk API submits async batch/jobs, so per-item completion isn't observable - // mid-flight. Show a spinner while the queue drains, then record accurate pass/fail - // counts from the final result into the progress-manager summary. - result = await CLIProgressManager.withLoadingSpinner( - $t(messages.EXECUTING_OPERATION, { count: items.length }), - async () => - publishMode === PublishMode.SINGLE - ? this.executeSingleMode(items, startTime) - : this.executeBulkMode(items, startTime), - ); + result = + publishMode === PublishMode.SINGLE + ? await this.executeSingleMode(items, startTime) + : await this.executeBulkMode(items, startTime); } catch (error: any) { result = handleOperationError(error, items, startTime); } - this.recordProgressSummary(result, showConsoleLogs); + this.recordModuleSummary(result, items.length); return result; } /** - * Record accurate success/failure counts from the final result into the - * CLIProgressManager summary. Bulk operations submit async jobs, so counts come from - * the aggregated result rather than live per-item ticks. + * Initialize the run-level summary + header once (mirrors import). Falls back to an empty + * branch name — displayOperationHeader applies its own `|| 'main'` for display — and suffixes + * the label with the branch only when one is set. Shared with bulk-taxonomies via inheritance. + */ + protected beginOperationSummary(itemCount: number): void { + const operationLabel = (this.bulkOperationConfig.operation || 'operation').toString().toUpperCase(); + const branchName = this.bulkOperationConfig.branch || ''; + CLIProgressManager.initializeGlobalSummary( + branchName ? `BULK ${operationLabel}-${branchName}` : `BULK ${operationLabel}`, + branchName, + $t(messages.EXECUTING_OPERATION, { count: itemCount }), + ); + } + + /** + * Record a per-module row in the summary so the final summary shows Module Details for this + * command (entry/asset/taxonomy). SINGLE mode returns real success/failed counts; async + * submissions (BULK mode, taxonomy) report those as 0 (publish happens server-side), so the + * items successfully submitted are counted as success — the status URL tracks the real outcome. */ - private recordProgressSummary(result: BulkOperationResult, showConsoleLogs: boolean): void { - const total = result?.total || 0; + protected recordModuleSummary(result: BulkOperationResult, submittedCount: number): void { + const showConsoleLogs = Boolean(configHandler.get('log')?.showConsoleLogs); + const total = result?.total || submittedCount || 0; const failed = result?.failed || 0; - const success = typeof result?.success === 'number' ? result.success : Math.max(total - failed, 0); + const success = result?.success ? result.success : Math.max(total - failed, 0); const progress = CLIProgressManager.createSimple(this.resourceType, total, showConsoleLogs); for (let i = 0; i < success; i++) progress.tick(true); @@ -441,6 +435,18 @@ export abstract class BaseBulkCommand extends Command { progress.complete(failed === 0); } + /** + * Print the run-level summary once and clear progress state. Idempotent: subclasses call + * finally() explicitly AND oclif calls it again, so clearing the summary after printing makes + * the second invocation a no-op. Also clears the progress-module flag so it never leaks into + * a later command in the same process (mirrors export/import/clone). + */ + protected finalizeProgressSummary(): void { + CLIProgressManager.printGlobalSummary(); + CLIProgressManager.clearGlobalSummary(); + clearProgressModuleSetting(); + } + /** * Execute operation in SINGLE mode - processes items individually */ @@ -616,10 +622,7 @@ export abstract class BaseBulkCommand extends Command { abstract run(): Promise; protected async finally(_error: Error | undefined): Promise { - // Print the run-level progress summary, then clear the progress-module flag so it - // never leaks into a later command in the same process (mirrors export/import/clone). - CLIProgressManager.printGlobalSummary(); - clearProgressModuleSetting(); + this.finalizeProgressSummary(); await this.cleanup(); } } diff --git a/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-taxonomies.ts b/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-taxonomies.ts index 5d0cf4bb9..cfee2fda7 100644 --- a/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-taxonomies.ts +++ b/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-taxonomies.ts @@ -1,5 +1,5 @@ import { Command } from '@contentstack/cli-command'; -import { flags, log, createLogContext, handleAndLogError } from '@contentstack/cli-utilities'; +import { flags, log, createLogContext, handleAndLogError, loadChalk } from '@contentstack/cli-utilities'; import messages, { $t } from '../../../messages'; import { BaseBulkCommand } from '../../../base-bulk-command'; @@ -58,6 +58,10 @@ export default class BulkTaxonomies extends BaseBulkCommand { // Call oclif Command init without running BaseBulkCommand.init (taxonomy uses its own prompts). await (Command.prototype as unknown as { init(this: Command): Promise }).init.call(this); + // Load chalk (ESM) up-front. The progress-module flag is set in buildConfig() (invoked by + // buildConfiguration below), before the first log call. + await loadChalk(); + let { flags: parsed } = await this.parse(BulkTaxonomies); if (parsed.revert || parsed['retry-failed']) { @@ -161,6 +165,9 @@ export default class BulkTaxonomies extends BaseBulkCommand { this.logger.debug($t(messages.EXECUTING_OPERATION, { count: items.length }), this.loggerContext); const startTime = Date.now(); + // Initialize the run-level summary + header once (inherited from BaseBulkCommand). + this.beginOperationSummary(items.length); + const operation = this.bulkOperationConfig.operation; if (operation !== OperationType.PUBLISH && operation !== OperationType.UNPUBLISH) { throw new Error($t(messages.UNSUPPORTED_OPERATION, { operation: operation ?? 'unknown' })); @@ -190,12 +197,17 @@ export default class BulkTaxonomies extends BaseBulkCommand { this.logger.info(String(response.notice)); } - return { + const result: BulkOperationResult = { success: 0, failed: 0, total: items.length, duration, jobIds: jobId ? [jobId] : [], }; + + // Record the taxonomy module row in the summary (inherited from BaseBulkCommand). + this.recordModuleSummary(result, items.length); + + return result; } } diff --git a/packages/contentstack-bulk-operations/src/utils/config-builder.ts b/packages/contentstack-bulk-operations/src/utils/config-builder.ts index c53deb98c..30c6c2130 100644 --- a/packages/contentstack-bulk-operations/src/utils/config-builder.ts +++ b/packages/contentstack-bulk-operations/src/utils/config-builder.ts @@ -234,6 +234,11 @@ export function validateFlags(flagsOrConfig: CommandFlags | BulkOperationConfig) * Supports both CLI flags and config file format */ export function buildConfig(flags: CommandFlags): BulkOperationConfig { + // Enable the progress-bar UI + suppress the timestamped console logs for the whole command + // (mirrors export-config-handler). Set here — during config build, before the first log call — + // because the logger builds its transports once, lazily, so setting it later has no effect. + configHandler.set('log.progressSupportedModule', 'bulk-operations'); + const config: BulkOperationConfig = { alias: flags.alias, stackApiKey: flags['stack-api-key'], From ed26bf85a9aff221a50c87edf74224349a8b16b3 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Tue, 14 Jul 2026 17:25:27 +0530 Subject: [PATCH 3/5] fix: null-safe bulkOperationConfig access in beginOperationSummary --- .../contentstack-bulk-operations/src/base-bulk-command.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contentstack-bulk-operations/src/base-bulk-command.ts b/packages/contentstack-bulk-operations/src/base-bulk-command.ts index d04d12836..8d1311ca5 100644 --- a/packages/contentstack-bulk-operations/src/base-bulk-command.ts +++ b/packages/contentstack-bulk-operations/src/base-bulk-command.ts @@ -408,12 +408,12 @@ export abstract class BaseBulkCommand extends Command { * the label with the branch only when one is set. Shared with bulk-taxonomies via inheritance. */ protected beginOperationSummary(itemCount: number): void { - const operationLabel = (this.bulkOperationConfig.operation || 'operation').toString().toUpperCase(); - const branchName = this.bulkOperationConfig.branch || ''; + const operationLabel = (this.bulkOperationConfig?.operation || 'operation').toString().toUpperCase(); + const branchName = this.bulkOperationConfig?.branch || ''; CLIProgressManager.initializeGlobalSummary( branchName ? `BULK ${operationLabel}-${branchName}` : `BULK ${operationLabel}`, branchName, - $t(messages.EXECUTING_OPERATION, { count: itemCount }), + $t(messages.EXECUTING_OPERATION, { count: itemCount }) ); } From 25c61ad3cb36973c33a97a8591e1e3fae2d85136 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Tue, 14 Jul 2026 17:59:28 +0530 Subject: [PATCH 4/5] fix: address progress-manager review feedback --- .../src/base-bulk-command.ts | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/contentstack-bulk-operations/src/base-bulk-command.ts b/packages/contentstack-bulk-operations/src/base-bulk-command.ts index 8d1311ca5..78e4dd162 100644 --- a/packages/contentstack-bulk-operations/src/base-bulk-command.ts +++ b/packages/contentstack-bulk-operations/src/base-bulk-command.ts @@ -44,6 +44,7 @@ import { generateBulkPublishStatusUrl, validateBranch, validateEnvironments, + aggregateBatchResults, } from './utils'; import { OperationType, @@ -235,6 +236,9 @@ export abstract class BaseBulkCommand extends Command { await this.initializeComponents(); await this.handleRevertOrRetry(mergedFlags); + // This early exit bypasses finally(); print the run summary and clear the progress-module + // flag here so it doesn't leak into the persisted config / later commands. + this.finalizeProgressSummary(); process.exit(0); } @@ -295,6 +299,9 @@ export abstract class BaseBulkCommand extends Command { const validation = validateFlags(this.bulkOperationConfig); if (!validation.valid) { + // buildConfig() set the progress-module flag; this early exit bypasses finally(), so + // clear it here to avoid leaking the setting into the persisted config / later commands. + this.finalizeProgressSummary(); process.exit(1); } @@ -403,9 +410,11 @@ export abstract class BaseBulkCommand extends Command { } /** - * Initialize the run-level summary + header once (mirrors import). Falls back to an empty - * branch name — displayOperationHeader applies its own `|| 'main'` for display — and suffixes - * the label with the branch only when one is set. Shared with bulk-taxonomies via inheritance. + * Initialize the run-level summary + header once. The label includes the branch, which + * defaults to 'main' from the --branch flag, so it normally reads e.g. "BULK PUBLISH-main" — + * the same "-" title shape export/import produce (SummaryManager uses the passed + * operationName verbatim). The branch is dropped from the label only in the edge case where + * it is unset. Shared with bulk-taxonomies via inheritance. */ protected beginOperationSummary(itemCount: number): void { const operationLabel = (this.bulkOperationConfig?.operation || 'operation').toString().toUpperCase(); @@ -419,15 +428,33 @@ export abstract class BaseBulkCommand extends Command { /** * Record a per-module row in the summary so the final summary shows Module Details for this - * command (entry/asset/taxonomy). SINGLE mode returns real success/failed counts; async - * submissions (BULK mode, taxonomy) report those as 0 (publish happens server-side), so the - * items successfully submitted are counted as success — the status URL tracks the real outcome. + * command (entry/asset/taxonomy). + * + * SINGLE mode processes items individually and returns real success/failed counts, so those + * are used directly. BULK mode submits async jobs — buildBulkModeResult reports success/failed + * as 0 because the publish runs server-side — so we count submission-level failures from + * batchResults (recorded as status:'failed') and treat the remaining submitted items as + * success. The printed status URL remains the source of truth for the real publish outcome. */ protected recordModuleSummary(result: BulkOperationResult, submittedCount: number): void { const showConsoleLogs = Boolean(configHandler.get('log')?.showConsoleLogs); + const publishMode = this.bulkOperationConfig?.publishMode || PublishMode.BULK; const total = result?.total || submittedCount || 0; - const failed = result?.failed || 0; - const success = result?.success ? result.success : Math.max(total - failed, 0); + + let success: number; + let failed: number; + if (publishMode === PublishMode.SINGLE) { + failed = result?.failed || 0; + success = typeof result?.success === 'number' ? result.success : Math.max(total - failed, 0); + } else { + // BULK: derive failures from batch submissions (result.failed is always 0 here). + failed = aggregateBatchResults(this.batchResults).totalFailed; + success = Math.max(total - failed, 0); + } + + // Clamp so counts never over/under-report relative to total. + failed = Math.min(Math.max(failed, 0), total); + success = Math.min(Math.max(success, 0), total - failed); const progress = CLIProgressManager.createSimple(this.resourceType, total, showConsoleLogs); for (let i = 0; i < success; i++) progress.tick(true); From 1983f233150572e7710869eab1bd414482453926 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Tue, 14 Jul 2026 19:45:44 +0530 Subject: [PATCH 5/5] refactor(bulk-operations): move progress-module flag out of pure buildConfig --- .../src/base-bulk-command.ts | 11 +++++++++-- .../src/utils/config-builder.ts | 5 ----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/contentstack-bulk-operations/src/base-bulk-command.ts b/packages/contentstack-bulk-operations/src/base-bulk-command.ts index 78e4dd162..a3a70cbaa 100644 --- a/packages/contentstack-bulk-operations/src/base-bulk-command.ts +++ b/packages/contentstack-bulk-operations/src/base-bulk-command.ts @@ -287,6 +287,13 @@ export abstract class BaseBulkCommand extends Command { * Build operation configuration */ protected async buildConfiguration(flags: any): Promise { + // Enable the progress-bar UI + suppress the timestamped console logs for the whole command. + // Set here (command lifecycle, runs before the first log call) rather than in the pure + // buildConfig() util so it isn't triggered by direct/unit-test callers of buildConfig. + // Cleared on every exit path: finally() on normal runs, and finalizeProgressSummary() before + // the validation exit(1) below and the revert/retry exit(0). + configHandler.set('log.progressSupportedModule', 'bulk-operations'); + this.bulkOperationConfig = buildConfig(flags); // buildConfig splits comma-separated oclif `multiple` values; mirror onto flags so @@ -299,8 +306,8 @@ export abstract class BaseBulkCommand extends Command { const validation = validateFlags(this.bulkOperationConfig); if (!validation.valid) { - // buildConfig() set the progress-module flag; this early exit bypasses finally(), so - // clear it here to avoid leaking the setting into the persisted config / later commands. + // The progress-module flag was set above; this early exit bypasses finally(), so clear it + // here to avoid leaking the setting into the persisted config / later commands. this.finalizeProgressSummary(); process.exit(1); } diff --git a/packages/contentstack-bulk-operations/src/utils/config-builder.ts b/packages/contentstack-bulk-operations/src/utils/config-builder.ts index 30c6c2130..c53deb98c 100644 --- a/packages/contentstack-bulk-operations/src/utils/config-builder.ts +++ b/packages/contentstack-bulk-operations/src/utils/config-builder.ts @@ -234,11 +234,6 @@ export function validateFlags(flagsOrConfig: CommandFlags | BulkOperationConfig) * Supports both CLI flags and config file format */ export function buildConfig(flags: CommandFlags): BulkOperationConfig { - // Enable the progress-bar UI + suppress the timestamped console logs for the whole command - // (mirrors export-config-handler). Set here — during config build, before the first log call — - // because the logger builds its transports once, lazily, so setting it later has no effect. - configHandler.set('log.progressSupportedModule', 'bulk-operations'); - const config: BulkOperationConfig = { alias: flags.alias, stackApiKey: flags['stack-api-key'],