resolveTargetLocales(); if (empty($targetLocales)) { $this->info('프로비저닝 대상 로케일이 없습니다 (base locale 외 supported_locales 미선언).'); return self::SUCCESS; } $scopeFilter = array_values(array_filter(array_map('strval', (array) $this->option('scope')))); $autoActivate = ! (bool) $this->option('no-activate'); $inScope = fn (LanguagePack $pack) => in_array($pack->locale, $targetLocales, true) && (empty($scopeFilter) || in_array($pack->scope, $scopeFilter, true)); // 미설치 번들(신규 프로비저닝) + 드리프트 설치 행(수동 복구) 이 모두 대상이다. // 드리프트 행은 슬롯이 점유돼 있어 getUninstalledBundledPacks() 가 잡지 못하므로 따로 합친다. $candidates = $this->service->getUninstalledBundledPacks()->filter($inScope) ->concat($this->service->getDriftedInstalledPacks()->filter($inScope)) ->values(); if ($candidates->isEmpty()) { $this->info(sprintf( '설치하거나 복구할 번들 언어팩이 없습니다 (로케일: %s). 이미 프로비저닝된 상태입니다.', implode(', ', $targetLocales), )); return self::SUCCESS; } $installed = 0; $repaired = 0; $skipped = 0; $failed = 0; foreach ($candidates as $pack) { $identifier = (string) ($pack->getAttribute('bundled_identifier') ?? $pack->identifier); $blockedReason = $pack->getAttribute('install_blocked_reason'); // 대상 확장 미설치/미활성 등으로 설치가 불가한 팩은 best-effort 로 건너뛴다. if ($blockedReason !== null && $blockedReason !== '') { $this->warn(sprintf('건너뜀 (설치 불가: %s): %s', $blockedReason, $identifier)); $skipped++; continue; } try { $isRepair = (bool) $pack->getAttribute('files_missing'); $result = $this->service->installFromBundled($identifier, $autoActivate); $this->info(sprintf( '%s: %s v%s (status=%s)', $isRepair ? '복구' : '설치', $result->identifier, $result->version, $result->status, )); $isRepair ? $repaired++ : $installed++; } catch (Throwable $e) { $this->warn(sprintf('실패 (건너뜀): %s — %s', $identifier, $e->getMessage())); $failed++; } } $this->info(sprintf( '프로비저닝 완료 — 설치 %d, 복구 %d, 건너뜀 %d, 실패 %d', $installed, $repaired, $skipped, $failed, )); // 성과가 하나도 없고 실패만 있었다면 실패로 보고 (멱등 재실행/전량 스킵은 성공). return $installed === 0 && $repaired === 0 && $failed > 0 ? self::FAILURE : self::SUCCESS; } /** * 프로비저닝 대상 로케일을 해석합니다. * * `--locale` 지정 시 그 값, 미지정 시 `config('app.supported_locales')` 에서 base locale(ko/en)을 제외한 집합. * * @return array 대상 로케일 목록 (base locale 제외) */ private function resolveTargetLocales(): array { $explicit = array_values(array_filter(array_map('strval', (array) $this->option('locale')))); $source = ! empty($explicit) ? $explicit : array_map('strval', (array) config('app.supported_locales', [])); return array_values(array_unique(array_filter( $source, fn (string $locale) => $locale !== '' && ! LanguagePackBaseLocales::isBaseLocale($locale), ))); } }