1- import chalk from 'chalk' ;
21import { Command } from '@contentstack/cli-command' ;
3- import { flags , log , createLogContext , getLogPath , handleAndLogError , FlagInput } from '@contentstack/cli-utilities' ;
2+ import {
3+ flags ,
4+ log ,
5+ createLogContext ,
6+ getLogPath ,
7+ handleAndLogError ,
8+ FlagInput ,
9+ getChalk ,
10+ loadChalk ,
11+ configHandler ,
12+ CLIProgressManager ,
13+ clearProgressModuleSetting ,
14+ } from '@contentstack/cli-utilities' ;
415
516import config from './config' ;
617import messages , { $t } from './messages' ;
@@ -33,6 +44,7 @@ import {
3344 generateBulkPublishStatusUrl ,
3445 validateBranch ,
3546 validateEnvironments ,
47+ aggregateBatchResults ,
3648} from './utils' ;
3749import {
3850 OperationType ,
@@ -150,6 +162,9 @@ export abstract class BaseBulkCommand extends Command {
150162 protected async init ( ) : Promise < void > {
151163 await super . init ( ) ;
152164
165+ // Load chalk (ESM) up-front. The progress-module flag is set in buildConfig()
166+ // (config layer, mirrors export-config-handler) before the first log call.
167+ await loadChalk ( ) ;
153168 if ( this . shouldSkipBulkPipeline ( ) ) {
154169 return ;
155170 }
@@ -233,6 +248,9 @@ export abstract class BaseBulkCommand extends Command {
233248 await this . initializeComponents ( ) ;
234249
235250 await this . handleRevertOrRetry ( mergedFlags ) ;
251+ // This early exit bypasses finally(); print the run summary and clear the progress-module
252+ // flag here so it doesn't leak into the persisted config / later commands.
253+ this . finalizeProgressSummary ( ) ;
236254 process . exit ( 0 ) ;
237255 }
238256
@@ -281,6 +299,13 @@ export abstract class BaseBulkCommand extends Command {
281299 * Build operation configuration
282300 */
283301 protected async buildConfiguration ( flags : any ) : Promise < void > {
302+ // Enable the progress-bar UI + suppress the timestamped console logs for the whole command.
303+ // Set here (command lifecycle, runs before the first log call) rather than in the pure
304+ // buildConfig() util so it isn't triggered by direct/unit-test callers of buildConfig.
305+ // Cleared on every exit path: finally() on normal runs, and finalizeProgressSummary() before
306+ // the validation exit(1) below and the revert/retry exit(0).
307+ configHandler . set ( 'log.progressSupportedModule' , 'bulk-operations' ) ;
308+
284309 this . bulkOperationConfig = buildConfig ( flags ) ;
285310
286311 // buildConfig splits comma-separated oclif `multiple` values; mirror onto flags so
@@ -293,6 +318,9 @@ export abstract class BaseBulkCommand extends Command {
293318
294319 const validation = validateFlags ( this . bulkOperationConfig ) ;
295320 if ( ! validation . valid ) {
321+ // The progress-module flag was set above; this early exit bypasses finally(), so clear it
322+ // here to avoid leaking the setting into the persisted config / later commands.
323+ this . finalizeProgressSummary ( ) ;
296324 process . exit ( 1 ) ;
297325 }
298326
@@ -378,20 +406,91 @@ export abstract class BaseBulkCommand extends Command {
378406 this . logger . debug ( $t ( messages . EXECUTING_OPERATION , { count : items . length } ) , this . loggerContext ) ;
379407 const startTime = Date . now ( ) ;
380408
409+ // Initialize the run-level summary + header once (progress-manager UX, same as import).
410+ this . beginOperationSummary ( items . length ) ;
411+
412+ let result : BulkOperationResult ;
381413 try {
382414 logOperationInfo ( items , this . logger ) ;
383415
384416 const publishMode = this . bulkOperationConfig . publishMode || PublishMode . BULK ;
385417 this . logger . debug ( `Using ${ publishMode . toUpperCase ( ) } mode for operation` , this . loggerContext ) ;
386418
387- if ( publishMode === PublishMode . SINGLE ) {
388- return await this . executeSingleMode ( items , startTime ) ;
389- }
390-
391- return await this . executeBulkMode ( items , startTime ) ;
419+ result =
420+ publishMode === PublishMode . SINGLE
421+ ? await this . executeSingleMode ( items , startTime )
422+ : await this . executeBulkMode ( items , startTime ) ;
392423 } catch ( error : any ) {
393- return handleOperationError ( error , items , startTime ) ;
424+ result = handleOperationError ( error , items , startTime ) ;
425+ }
426+
427+ this . recordModuleSummary ( result , items . length ) ;
428+ return result ;
429+ }
430+
431+ /**
432+ * Initialize the run-level summary + header once. The label includes the branch, which
433+ * defaults to 'main' from the --branch flag, so it normally reads e.g. "BULK PUBLISH-main" —
434+ * the same "<OP>-<branch>" title shape export/import produce (SummaryManager uses the passed
435+ * operationName verbatim). The branch is dropped from the label only in the edge case where
436+ * it is unset. Shared with bulk-taxonomies via inheritance.
437+ */
438+ protected beginOperationSummary ( itemCount : number ) : void {
439+ const operationLabel = ( this . bulkOperationConfig ?. operation || 'operation' ) . toString ( ) . toUpperCase ( ) ;
440+ const branchName = this . bulkOperationConfig ?. branch || '' ;
441+ CLIProgressManager . initializeGlobalSummary (
442+ branchName ? `BULK ${ operationLabel } -${ branchName } ` : `BULK ${ operationLabel } ` ,
443+ branchName ,
444+ $t ( messages . EXECUTING_OPERATION , { count : itemCount } )
445+ ) ;
446+ }
447+
448+ /**
449+ * Record a per-module row in the summary so the final summary shows Module Details for this
450+ * command (entry/asset/taxonomy).
451+ *
452+ * SINGLE mode processes items individually and returns real success/failed counts, so those
453+ * are used directly. BULK mode submits async jobs — buildBulkModeResult reports success/failed
454+ * as 0 because the publish runs server-side — so we count submission-level failures from
455+ * batchResults (recorded as status:'failed') and treat the remaining submitted items as
456+ * success. The printed status URL remains the source of truth for the real publish outcome.
457+ */
458+ protected recordModuleSummary ( result : BulkOperationResult , submittedCount : number ) : void {
459+ const showConsoleLogs = Boolean ( configHandler . get ( 'log' ) ?. showConsoleLogs ) ;
460+ const publishMode = this . bulkOperationConfig ?. publishMode || PublishMode . BULK ;
461+ const total = result ?. total || submittedCount || 0 ;
462+
463+ let success : number ;
464+ let failed : number ;
465+ if ( publishMode === PublishMode . SINGLE ) {
466+ failed = result ?. failed || 0 ;
467+ success = typeof result ?. success === 'number' ? result . success : Math . max ( total - failed , 0 ) ;
468+ } else {
469+ // BULK: derive failures from batch submissions (result.failed is always 0 here).
470+ failed = aggregateBatchResults ( this . batchResults ) . totalFailed ;
471+ success = Math . max ( total - failed , 0 ) ;
394472 }
473+
474+ // Clamp so counts never over/under-report relative to total.
475+ failed = Math . min ( Math . max ( failed , 0 ) , total ) ;
476+ success = Math . min ( Math . max ( success , 0 ) , total - failed ) ;
477+
478+ const progress = CLIProgressManager . createSimple ( this . resourceType , total , showConsoleLogs ) ;
479+ for ( let i = 0 ; i < success ; i ++ ) progress . tick ( true ) ;
480+ for ( let i = 0 ; i < failed ; i ++ ) progress . tick ( false ) ;
481+ progress . complete ( failed === 0 ) ;
482+ }
483+
484+ /**
485+ * Print the run-level summary once and clear progress state. Idempotent: subclasses call
486+ * finally() explicitly AND oclif calls it again, so clearing the summary after printing makes
487+ * the second invocation a no-op. Also clears the progress-module flag so it never leaks into
488+ * a later command in the same process (mirrors export/import/clone).
489+ */
490+ protected finalizeProgressSummary ( ) : void {
491+ CLIProgressManager . printGlobalSummary ( ) ;
492+ CLIProgressManager . clearGlobalSummary ( ) ;
493+ clearProgressModuleSetting ( ) ;
395494 }
396495
397496 /**
@@ -457,6 +556,7 @@ export abstract class BaseBulkCommand extends Command {
457556 * Called at the end of run() method in subclasses
458557 */
459558 protected printOperationSummary ( result : BulkOperationResult ) : void {
559+ const chalk = getChalk ( ) ;
460560 const publishMode = this . bulkOperationConfig . publishMode || PublishMode . BULK ;
461561
462562 console . log ( '' ) ;
@@ -568,6 +668,7 @@ export abstract class BaseBulkCommand extends Command {
568668 abstract run ( ) : Promise < void > ;
569669
570670 protected async finally ( _error : Error | undefined ) : Promise < void > {
671+ this . finalizeProgressSummary ( ) ;
571672 await this . cleanup ( ) ;
572673 }
573674}
0 commit comments