fix(core,installer): dev vendor 설치본의 코어 업데이트 중단 수정 — stale 패키지 매니페스트 3계층
Laravel PackageManifest 는 bootstrap/cache/packages.php 가 있으면 stale 여부를 검사하지 않고 그대로 읽어 등재된 provider 를 new 한다. 코어 업데이트는 Step 6/8 에서 vendor 를 --no-dev 로 교체하지만 그 파일은 Step 11 까지 이전 설치본의 것이 남으므로, 옵션 없는 `composer install` 로 깔린 사이트에서는 Step 10 spawn 자식이 새 vendor 에 없는 provider 를 찾다 부팅 단계에서 죽는다. 부팅 전이라 앱 로그에 흔적이 없고 부모에게는 자식의 비정상 종료로만 보여, 운영자에게는 「Class ... not found」 와 수동 재개 안내만 남는다. (sir.kr 커뮤니티 제보, 7.0.9 → 7.0.10) 3계층으로 막는다. 1. 부모 — spawn 직전 PackageManifestCacheHelper::clear 2. 자식 — bootstrap/app.php 가 G7_UPDATE_IN_PROGRESS 를 보고 스스로 정리한다. 이미 배포된 7.0.9·7.0.10 부모는 고칠 수 없으므로 그 아래에서 도는 신버전 자식의 유일한 방어다. App\ 클래스를 참조하지 않고 실패는 무시한다. 3. 범위 — CoreVersionChecker 의 env APP_VERSION 우선을 CoreUpdateContext 트리 안으로 축소한다. 업데이트 전에 뜬 artisan serve·큐 워커가 옛 값을 물고 확장을 incompatible_core 로 끄던 경로를 닫는다(관리자 템플릿이 대상이면 복구 UI 자체에 도달할 수 없다). 업데이트 트리 판정은 App\Support\CoreUpdateContext 가 단독 소유하고 CoreServiceProvider::isCoreUpdateInProgress 는 위임으로 남는다. bootstrap/app.php 의 복제본은 부팅 전이라 불가피한 예외이며, 두 조건의 동형성을 테스트가 단언한다. 실측 중 드러난 결함 2건을 함께 고쳤다. - ConfigCacheHelper::withPreservedContainer 가 파사드 애플리케이션을 되돌리지 않아 Step 11 이 `Target class [command.tinker] does not exist` 로 실패·롤백했다. - updateVersionInEnv 가 프로세스 환경을 갱신하지 않아 config 캐시에 이전 버전이 구워졌다 (Laravel env 저장소가 불변이라 재부팅으로도 덮이지 않는다). 인스톨러는 재사용 vendor 의 개발용 패키지를 installed.json 으로 감지해 설치 환경 확인 카드·설치 로그로 알리되 설치를 차단하지 않고( 결정 D1), 재사용 경로에서도 컴파일 캐시를 정리한다. 실행되는 명령만이 아니라 실패 시 안내하는 수동 명령까지 --no-dev 로 맞췄다. 코어 업데이트 완료·핸드오프·단독 재개 사후 단계에서 queue:restart 신호를 보낸다(D3). 코어 7.0.10 → 7.0.11.
This commit is contained in:
1 parent
21177c8e17
commit
ffab451b4a
42 files changed
+2660
-82
No files matched your search
@@ -39,14 +39,72 @@ class CoreUpdateServiceTest extends TestCase
|
||||
*/
|
||||
private array $tempFiles = [];
|
||||
|
||||
/**
|
||||
* 진입 시점의 업데이트 트리 플래그 (tearDown 에서 복원)
|
||||
*/
|
||||
private string|false $originalUpdateFlag = false;
|
||||
|
||||
/**
|
||||
* 진입 시점의 APP_VERSION 3채널 값 (tearDown 에서 복원)
|
||||
*
|
||||
* @var array{env: string|null, server: string|null, putenv: string|false}
|
||||
*/
|
||||
private array $originalAppVersion = ['env' => null, 'server' => null, 'putenv' => false];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// `updateVersionInEnv()` 는 `.env` 파일만이 아니라 프로세스 환경(3채널)도 갱신한다.
|
||||
// 그 부수효과가 클래스 안에 남으면 **뒤이은 테스트가 부팅하는 앱의 `config('app.version')`**
|
||||
// 이 오염된다(`config/app.php` 가 `env('APP_VERSION', …)` 이므로). 실제로 그 순서 의존으로
|
||||
// `test_check_updates_command_shows_success` 가 "현재 최신 버전입니다" 대신 업데이트 있음으로
|
||||
// 갈렸다. 진입 시 스냅샷을 떠 두고 종료 시 되돌린다.
|
||||
$this->originalAppVersion = [
|
||||
'env' => $_ENV['APP_VERSION'] ?? null,
|
||||
'server' => $_SERVER['APP_VERSION'] ?? null,
|
||||
'putenv' => getenv('APP_VERSION'),
|
||||
];
|
||||
|
||||
// 이 클래스의 한 케이스가 `core:update` 를 실제로 호출하는데, 부모 커맨드는 "프로세스
|
||||
// 종료와 함께 소멸" 을 전제로 `G7_UPDATE_IN_PROGRESS` 를 정리하지 않는다. PHPUnit 은
|
||||
// 프로세스가 이어지므로 그 플래그가 이후 모든 테스트로 새어 나가, 부팅마다
|
||||
// `bootstrap/app.php` 의 매니페스트 자가 치유가 발동한다(내용은 같아도 불필요한 churn).
|
||||
// 진입 시 비우고 종료 시 되돌린다 — ExecuteUpgradeStepsStandaloneTest 와 같은 규율.
|
||||
$this->originalUpdateFlag = getenv('G7_UPDATE_IN_PROGRESS');
|
||||
putenv('G7_UPDATE_IN_PROGRESS');
|
||||
unset($_ENV['G7_UPDATE_IN_PROGRESS'], $_SERVER['G7_UPDATE_IN_PROGRESS']);
|
||||
|
||||
$this->service = new CoreUpdateService;
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// APP_VERSION 3채널 복원 (updateVersionInEnv 부수효과 격리)
|
||||
foreach (['env' => &$_ENV, 'server' => &$_SERVER] as $slot => &$bag) {
|
||||
if ($this->originalAppVersion[$slot] === null) {
|
||||
unset($bag['APP_VERSION']);
|
||||
} else {
|
||||
$bag['APP_VERSION'] = $this->originalAppVersion[$slot];
|
||||
}
|
||||
}
|
||||
unset($bag);
|
||||
|
||||
if ($this->originalAppVersion['putenv'] === false) {
|
||||
putenv('APP_VERSION');
|
||||
} else {
|
||||
putenv('APP_VERSION='.$this->originalAppVersion['putenv']);
|
||||
}
|
||||
|
||||
if ($this->originalUpdateFlag === false) {
|
||||
putenv('G7_UPDATE_IN_PROGRESS');
|
||||
unset($_ENV['G7_UPDATE_IN_PROGRESS'], $_SERVER['G7_UPDATE_IN_PROGRESS']);
|
||||
} else {
|
||||
putenv('G7_UPDATE_IN_PROGRESS='.$this->originalUpdateFlag);
|
||||
$_ENV['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag;
|
||||
$_SERVER['G7_UPDATE_IN_PROGRESS'] = $this->originalUpdateFlag;
|
||||
}
|
||||
|
||||
// 임시 파일 정리
|
||||
foreach ($this->tempFiles as $file) {
|
||||
if (File::exists($file)) {
|
||||
@@ -425,6 +483,55 @@ MD;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* `.env` 파일만이 아니라 프로세스 환경(3채널)도 함께 갱신하는지 검증합니다.
|
||||
*
|
||||
* 부모가 config 캐시 없이 부팅했다면 Dotenv 가 `$_ENV['APP_VERSION']` 에 이전 버전을 채워 두고,
|
||||
* env 저장소가 불변이라 뒤이어 부팅하는 `config:cache` 프로세스가 `.env` 를 다시 읽어도 그 값이
|
||||
* 바뀌지 않는다. 그러면 `bootstrap/cache/config.php` 에 **이전 버전**이 박제되어 이후 모든 웹
|
||||
* 요청이 옛 버전으로 판정한다 — 새 코어를 요구하는 확장이 `incompatible_core` 로 꺼지는 경로다.
|
||||
* (2026-09-07 실측: `config:clear` 후 7.0.10 → 7.0.11 업데이트가 config 캐시에 7.0.10 을 구웠다.)
|
||||
*/
|
||||
public function test_update_version_in_env_also_updates_process_env(): void
|
||||
{
|
||||
$originalEnv = $_ENV['APP_VERSION'] ?? null;
|
||||
$originalServer = $_SERVER['APP_VERSION'] ?? null;
|
||||
$originalPutenv = getenv('APP_VERSION');
|
||||
|
||||
try {
|
||||
// 업데이트 전 프로세스 환경 = 이전 버전 (캐시 없이 부팅한 부모의 상태)
|
||||
$_ENV['APP_VERSION'] = '1.0.0';
|
||||
$_SERVER['APP_VERSION'] = '1.0.0';
|
||||
putenv('APP_VERSION=1.0.0');
|
||||
|
||||
$this->withIsolatedBasePath(function (string $tempBase): void {
|
||||
File::put($tempBase.DIRECTORY_SEPARATOR.'.env', "APP_NAME=G7\nAPP_VERSION=1.0.0\n");
|
||||
|
||||
$this->service->updateVersionInEnv('2.5.0');
|
||||
});
|
||||
|
||||
$this->assertSame('2.5.0', $_ENV['APP_VERSION'], '$_ENV 채널이 갱신되어야 한다');
|
||||
$this->assertSame('2.5.0', $_SERVER['APP_VERSION'], '$_SERVER 채널이 갱신되어야 한다');
|
||||
$this->assertSame('2.5.0', getenv('APP_VERSION'), 'putenv 채널이 갱신되어야 한다');
|
||||
} finally {
|
||||
if ($originalEnv === null) {
|
||||
unset($_ENV['APP_VERSION']);
|
||||
} else {
|
||||
$_ENV['APP_VERSION'] = $originalEnv;
|
||||
}
|
||||
if ($originalServer === null) {
|
||||
unset($_SERVER['APP_VERSION']);
|
||||
} else {
|
||||
$_SERVER['APP_VERSION'] = $originalServer;
|
||||
}
|
||||
if ($originalPutenv === false) {
|
||||
putenv('APP_VERSION');
|
||||
} else {
|
||||
putenv('APP_VERSION='.$originalPutenv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* .env 파일에 APP_VERSION 이 없을 때 새로 추가하는지 검증합니다.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user