option('from'); $to = (string) $this->option('to'); $force = (bool) $this->option('force'); if ($from === '' || $to === '') { $this->error('--from 과 --to 는 필수 옵션입니다.'); return self::INVALID; } try { $service->runUpgradeSteps( $from, $to, fn (string $version) => $this->info("upgrade step 실행: {$version}"), $force, ); } catch (UpgradeHandoffException $e) { // 업그레이드 스텝이 새 PHP 프로세스 재진입을 요청했다. // 부모 프로세스(spawnUpgradeStepsProcess)가 [HANDOFF] 라인을 stdout 에서 // 읽어 페이로드를 복원하고 UpgradeHandoffException 재구성 후 상위로 던지도록, // JSON 페이로드를 표식과 함께 출력한다. 표식 문자열은 구분자 역할이므로 일반 // step 출력과 충돌하지 않게 고정된 접두사를 사용한다. // // resumeCommand 는 step 작성자가 null 로 두는 것을 권장(CoreUpdateCommand 가 // from/to 버전을 사용해 자동 생성). null 도 JSON 으로 그대로 전달. $payload = json_encode([ 'afterVersion' => $e->afterVersion, 'reason' => $e->reason, 'resumeCommand' => $e->resumeCommand, ], JSON_UNESCAPED_UNICODE); $this->line('[HANDOFF] '.$payload); Log::info('core:execute-upgrade-steps 핸드오프', [ 'from' => $from, 'to' => $to, 'after' => $e->afterVersion, 'reason' => $e->reason, ]); return UpgradeHandoffException::EXIT_CODE; } catch (\Throwable $e) { Log::error('core:execute-upgrade-steps 실패', [ 'from' => $from, 'to' => $to, 'error' => $e->getMessage(), ]); $this->error($e->getMessage()); return self::FAILURE; } return self::SUCCESS; } }