@@ -626,6 +626,10 @@ public function adopt_repo( array $args, array $assoc_args ): void {
626626 * [--verbose]
627627 * : Include full diagnostic child job ID lists in task-backed cleanup status output.
628628 *
629+ * [--drain]
630+ * : For `cleanup run`, drain the queued parent job, drain active child cleanup
631+ * jobs discovered from cleanup status, then print verified bytes reclaimed.
632+ *
629633 * [--format=<format>]
630634 * : Output format.
631635 * ---
@@ -750,9 +754,144 @@ private function run_cleanup_task( array $assoc_args ): void {
750754 return ;
751755 }
752756
757+ $ result = $ this ->attach_cleanup_run_commands ($ result , $ mode );
758+ if ( ! empty ($ assoc_args ['drain ' ]) ) {
759+ $ result = $ this ->drain_cleanup_run_to_status ($ result , $ assoc_args );
760+ }
761+
753762 $ this ->render_cleanup_control_result ($ result , $ assoc_args );
754763 }
755764
765+ /**
766+ * Attach operator commands to a queued cleanup run response.
767+ *
768+ * @param array<string,mixed> $result Cleanup run result.
769+ * @param string $mode Cleanup mode.
770+ * @return array<string,mixed>
771+ */
772+ private function attach_cleanup_run_commands ( array $ result , string $ mode ): array {
773+ $ job_id = (int ) ( $ result ['job_id ' ] ?? 0 );
774+ $ run_id = (string ) ( $ result ['run_id ' ] ?? ( $ job_id > 0 ? $ this ->cleanup_run_id ($ job_id ) : '' ) );
775+ if ( $ job_id <= 0 || '' === $ run_id ) {
776+ return $ result ;
777+ }
778+
779+ $ result ['commands ' ] = array (
780+ 'drain_parent ' => sprintf ('studio wp datamachine drain --job-id=%d ' , $ job_id ),
781+ 'status ' => sprintf ('studio wp datamachine-code workspace cleanup status %s --format=json ' , $ run_id ),
782+ 'status_verbose ' => sprintf ('studio wp datamachine-code workspace cleanup status %s --verbose --format=json ' , $ run_id ),
783+ 'one_command_drain ' => sprintf ('studio wp datamachine-code workspace cleanup run --mode=%s --drain --format=json ' , $ mode ),
784+ 'bytes_verification ' => sprintf ('studio wp datamachine-code workspace cleanup status %s --format=json ' , $ run_id ),
785+ );
786+
787+ return $ result ;
788+ }
789+
790+ /**
791+ * Drain a queued job-backed cleanup run through Data Machine, then return status evidence.
792+ *
793+ * @param array<string,mixed> $result Initial cleanup run result.
794+ * @param array<string,mixed> $assoc_args CLI associative args.
795+ * @return array<string,mixed>
796+ */
797+ private function drain_cleanup_run_to_status ( array $ result , array $ assoc_args ): array {
798+ $ job_id = (int ) ( $ result ['job_id ' ] ?? 0 );
799+ $ run_id = (string ) ( $ result ['run_id ' ] ?? ( $ job_id > 0 ? $ this ->cleanup_run_id ($ job_id ) : '' ) );
800+ if ( $ job_id <= 0 || '' === $ run_id ) {
801+ $ result ['drain ' ] = array (
802+ 'success ' => false ,
803+ 'error ' => 'Cleanup run did not return a job id to drain. ' ,
804+ );
805+ return $ result ;
806+ }
807+
808+ $ commands = array ();
809+ $ errors = array ();
810+ $ max_passes = 10 ;
811+
812+ $ parent_command = sprintf ('datamachine drain --job-id=%d ' , $ job_id );
813+ $ commands [] = 'studio wp ' . $ parent_command ;
814+ $ error = $ this ->run_wp_cli_command ($ parent_command );
815+ if ( '' !== $ error ) {
816+ $ errors [] = $ error ;
817+ }
818+
819+ for ( $ pass = 0 ; $ pass < $ max_passes ; ++$ pass ) {
820+ $ status = $ this ->cleanup_run_evidence_store ()->read ($ run_id , true , true );
821+ if ( $ status instanceof \WP_Error ) {
822+ $ errors [] = $ status ->get_error_message ();
823+ break ;
824+ }
825+
826+ $ children = (array ) ( $ status ['evidence ' ]['children ' ] ?? array () );
827+ $ active_child_ids = array_values (
828+ array_unique (
829+ array_filter (
830+ array_map (
831+ 'intval ' ,
832+ array_merge (
833+ (array ) ( $ children ['pending_job_ids ' ] ?? array () ),
834+ (array ) ( $ children ['processing_job_ids ' ] ?? array () )
835+ )
836+ )
837+ )
838+ )
839+ );
840+ if ( array () === $ active_child_ids ) {
841+ break ;
842+ }
843+
844+ $ child_command = sprintf ('datamachine drain --job-id=%s ' , implode (', ' , $ active_child_ids ));
845+ $ commands [] = 'studio wp ' . $ child_command ;
846+ $ error = $ this ->run_wp_cli_command ($ child_command );
847+ if ( '' !== $ error ) {
848+ $ errors [] = $ error ;
849+ break ;
850+ }
851+ }
852+
853+ $ final = $ this ->cleanup_run_evidence_store ()->read ($ run_id , false , ! empty ($ assoc_args ['verbose ' ]));
854+ $ output = $ final instanceof \WP_Error ? $ result : $ final ;
855+ $ output ['initial_run ' ] = $ result ;
856+ $ output ['drain ' ] = array (
857+ 'success ' => array () === $ errors ,
858+ 'commands ' => $ commands ,
859+ 'errors ' => $ errors ,
860+ 'verify_command ' => sprintf ('studio wp datamachine-code workspace cleanup status %s --format=json ' , $ run_id ),
861+ 'bytes_reclaimed ' => (int ) ( $ output ['cleanup_items ' ]['bytes_reclaimed ' ] ?? 0 ),
862+ 'freed_human ' => (string ) ( $ output ['cleanup_items ' ]['freed_human ' ] ?? $ this ->format_bytes (0 ) ),
863+ 'completion_state ' => (string ) ( $ output ['state ' ] ?? 'unknown ' ),
864+ );
865+
866+ return $ output ;
867+ }
868+
869+ /**
870+ * Run a WP-CLI command and return an error message on failure.
871+ *
872+ * @param string $command Command without the leading `wp` binary.
873+ * @return string Empty string on success.
874+ */
875+ private function run_wp_cli_command ( string $ command ): string {
876+ if ( ! method_exists ('WP_CLI ' , 'runcommand ' ) ) {
877+ return 'WP_CLI::runcommand is unavailable; run the reported drain commands manually. ' ;
878+ }
879+
880+ try {
881+ WP_CLI ::runcommand (
882+ $ command ,
883+ array (
884+ 'return ' => true ,
885+ 'exit_error ' => false ,
886+ )
887+ );
888+ } catch ( \Throwable $ e ) {
889+ return $ e ->getMessage ();
890+ }
891+
892+ return '' ;
893+ }
894+
756895 private function cleanup_run_input ( string $ mode , array $ assoc_args ): array {
757896 $ input = array (
758897 'mode ' => $ mode ,
@@ -1055,6 +1194,12 @@ private function render_cleanup_control_result( array $result, array $assoc_args
10551194 if ( ! empty ($ result ['progress ' ]) && is_array ($ result ['progress ' ]) ) {
10561195 $ this ->render_cleanup_progress_summary ( (array ) $ result ['progress ' ]);
10571196 }
1197+ if ( ! empty ($ result ['commands ' ]) && is_array ($ result ['commands ' ]) ) {
1198+ $ this ->render_cleanup_command_hints ( (array ) $ result ['commands ' ]);
1199+ }
1200+ if ( ! empty ($ result ['drain ' ]) && is_array ($ result ['drain ' ]) ) {
1201+ $ this ->render_cleanup_drain_summary ( (array ) $ result ['drain ' ]);
1202+ }
10581203 if ( ! empty ($ result ['remaining_work_summary ' ]) && is_array ($ result ['remaining_work_summary ' ]) ) {
10591204 $ this ->render_cleanup_remaining_work_summary ( (array ) $ result ['remaining_work_summary ' ]);
10601205 }
@@ -1135,6 +1280,47 @@ private function render_cleanup_remaining_work_summary( array $summary ): void {
11351280 }
11361281 }
11371282
1283+ /**
1284+ * Render cleanup command hints.
1285+ *
1286+ * @param array<string,string> $commands Commands keyed by purpose.
1287+ * @return void
1288+ */
1289+ private function render_cleanup_command_hints ( array $ commands ): void {
1290+ WP_CLI ::log ('' );
1291+ WP_CLI ::log ('Cleanup commands: ' );
1292+ $ rows = array ();
1293+ foreach ( $ commands as $ purpose => $ command ) {
1294+ $ rows [] = array (
1295+ 'purpose ' => (string ) $ purpose ,
1296+ 'command ' => (string ) $ command ,
1297+ );
1298+ }
1299+ $ this ->format_items ($ rows , array ( 'purpose ' , 'command ' ), array ( 'format ' => 'table ' ), 'purpose ' );
1300+ }
1301+
1302+ /**
1303+ * Render cleanup drain summary.
1304+ *
1305+ * @param array<string,mixed> $drain Drain summary.
1306+ * @return void
1307+ */
1308+ private function render_cleanup_drain_summary ( array $ drain ): void {
1309+ WP_CLI ::log ('' );
1310+ WP_CLI ::log ('Drain summary: ' );
1311+ $ this ->format_items (
1312+ array (
1313+ array ( 'metric ' => 'success ' , 'value ' => ! empty ($ drain ['success ' ]) ? 'yes ' : 'no ' ),
1314+ array ( 'metric ' => 'completion_state ' , 'value ' => (string ) ( $ drain ['completion_state ' ] ?? 'unknown ' ) ),
1315+ array ( 'metric ' => 'bytes_reclaimed ' , 'value ' => $ this ->format_bytes ($ drain ['bytes_reclaimed ' ] ?? 0 ) ),
1316+ array ( 'metric ' => 'verify_command ' , 'value ' => (string ) ( $ drain ['verify_command ' ] ?? '' ) ),
1317+ ),
1318+ array ( 'metric ' , 'value ' ),
1319+ array ( 'format ' => 'table ' ),
1320+ 'metric '
1321+ );
1322+ }
1323+
11381324 private function render_cleanup_progress_summary ( array $ progress ): void {
11391325 WP_CLI ::log ('' );
11401326 WP_CLI ::log ('Progress: ' );
0 commit comments