fix(core): core:update 증분 모드가 _bundled 확장 파일을 반영하도록 protected 오버라이드
targets 에 명시된 {modules,plugins,templates,lang-packs}/_bundled 가
상위 protected(modules 등)에 걸려 3-way apply 목록에서 제외되던 결함 수정.
targets 가 protected 보다 더 구체적이면 오버라이드해 통과시켜, 코어 업데이트로
번들 확장 갱신 파일(composer.json·vendor-bundle.json 등)이 정상 반영되도록 한다.
자동 발견 폴백의 방어 및 prune 롤백 경로는 불변.
아울러 이슈 템플릿의 사용법 문의 링크를 Discussions 에서 sir.kr Q&A 로 전환.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 질문·사용법 문의 (Discussions)
|
||||
url: https://github.com/gnuboard/g7/discussions
|
||||
about: 버그가 아닌 사용법 질문·아이디어 논의는 Discussions 를 이용해 주세요.
|
||||
- name: 질문·사용법 문의 (sir.kr Q&A)
|
||||
url: https://sir.kr/questions/create?tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C7
|
||||
about: 버그가 아닌 사용법 질문·사용법 문의는 sir.kr Q&A 를 이용해 주세요.
|
||||
- name: 보안 취약점 신고
|
||||
url: https://github.com/gnuboard/g7/security/advisories/new
|
||||
about: 보안 취약점은 공개 이슈가 아닌 비공개 Security Advisory 로 신고해 주세요.
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- 코어 업데이트가 파일을 반영하는 기본 방식을 "새 버전이 실제로 추가·변경한 파일만 반영"으로 바꿔, 업데이트로 인한 사고 가능성을 크게 줄였습니다. 이제 코어가 건드리지 않은 파일은 그대로 두므로, `public/.htaccess` 에 직접 넣은 설정이나 사용자가 추가해 둔 파일이 업데이트 과정에서 사라지지 않습니다. 기존처럼 새 버전에서 제거된 파일 정리까지 포함한 전체 덮어쓰기를 원하면 `--prune` 옵션으로 업데이트하세요. (#64 @bigmsg 님께서 제보해주셨습니다.)
|
||||
- 코어 업데이트가 파일을 반영하는 기본 방식을 "새 버전이 실제로 추가·변경한 파일만 반영"으로 바꿔, 업데이트로 인한 사고 가능성을 크게 줄였습니다. 이제 코어가 건드리지 않은 파일은 그대로 두므로, `public/.htaccess` 에 직접 넣은 설정이나 사용자가 추가해 둔 파일이 업데이트 과정에서 사라지지 않습니다. 새 버전에 함께 담긴 기본 제공 확장(모듈·플러그인·템플릿·언어팩)의 갱신 파일은 이 과정에서도 정상적으로 최신 내용으로 반영되며, 사용자가 확장 폴더에 직접 추가해 둔 항목은 그대로 보존됩니다. 기존처럼 새 버전에서 제거된 파일 정리까지 포함한 전체 덮어쓰기를 원하면 `--prune` 옵션으로 업데이트하세요. (#64 @bigmsg 님께서 제보해주셨습니다.)
|
||||
- 화면·데이터를 내려받을 때의 전송 용량을 줄여 초기 로딩과 페이지 이동 속도를 개선했습니다.
|
||||
- 모듈·플러그인의 묶음 스크립트·스타일이 지금까지 압축 없이 전송되던 것을 압축 전송하도록 바꿔, 확장이 많은 사이트에서 첫 화면에 받는 용량을 크게 줄였습니다.
|
||||
- 한글 등 다국어 텍스트가 응답에 불필요하게 부풀려 담기던 것을 원래 형태 그대로 담도록 바꿔, 특히 다국어 번역 데이터의 전송 용량을 줄였습니다.
|
||||
|
||||
@@ -238,6 +238,7 @@ class CoreBackupHelper
|
||||
$newDirs = [];
|
||||
|
||||
$protectedSet = self::normalizeProtectedSet($protectedPaths);
|
||||
$allowedTargetSet = self::normalizeProtectedSet($targets);
|
||||
$excludeSet = array_values(array_filter(array_map('trim', $excludes)));
|
||||
|
||||
foreach ($targets as $target) {
|
||||
@@ -246,8 +247,9 @@ class CoreBackupHelper
|
||||
continue;
|
||||
}
|
||||
|
||||
// target 자체가 보호 경로면 스킵
|
||||
if (self::isWithinProtectedPath($target, $protectedSet)) {
|
||||
// target 자체가 보호 경로면 스킵 (단, targets 에 더 구체적으로 명시된
|
||||
// 경로는 상위 protected 를 오버라이드 — _bundled 갱신 허용)
|
||||
if (self::isWithinProtectedPath($target, $protectedSet, $allowedTargetSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -284,7 +286,7 @@ class CoreBackupHelper
|
||||
if (self::matchesExcludes($relative, $excludeSet)) {
|
||||
continue;
|
||||
}
|
||||
if (self::isWithinProtectedPath($relative, $protectedSet)) {
|
||||
if (self::isWithinProtectedPath($relative, $protectedSet, $allowedTargetSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -369,7 +371,7 @@ class CoreBackupHelper
|
||||
* @param array $protectedPaths 보호 경로 목록 (목록에서 제외)
|
||||
* @param array $excludes 제외 패턴 목록 (예: ['node_modules', '.git'])
|
||||
* @return array{apply:array<int,string>, added_count:int, changed_count:int, has_symlink:bool}
|
||||
* apply = 적용 대상 상대경로 목록(정렬됨, 슬래시 정규화)
|
||||
* apply = 적용 대상 상대경로 목록(정렬됨, 슬래시 정규화)
|
||||
*/
|
||||
public static function computeApplyList(
|
||||
string $backupPath,
|
||||
@@ -383,6 +385,7 @@ class CoreBackupHelper
|
||||
$hasSymlink = false;
|
||||
|
||||
$protectedSet = self::normalizeProtectedSet($protectedPaths);
|
||||
$allowedTargetSet = self::normalizeProtectedSet($targets);
|
||||
$excludeSet = array_values(array_filter(array_map('trim', $excludes)));
|
||||
|
||||
foreach ($targets as $target) {
|
||||
@@ -391,7 +394,7 @@ class CoreBackupHelper
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::isWithinProtectedPath($target, $protectedSet)) {
|
||||
if (self::isWithinProtectedPath($target, $protectedSet, $allowedTargetSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -433,7 +436,7 @@ class CoreBackupHelper
|
||||
if (self::matchesExcludes($relative, $excludeSet)) {
|
||||
continue;
|
||||
}
|
||||
if (self::isWithinProtectedPath($relative, $protectedSet)) {
|
||||
if (self::isWithinProtectedPath($relative, $protectedSet, $allowedTargetSet)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -661,23 +664,61 @@ class CoreBackupHelper
|
||||
|
||||
/**
|
||||
* 상대 경로가 protected_paths 목록의 어떤 항목 하위에 위치하는지 검사합니다.
|
||||
*
|
||||
* `$allowedTargetSet` 이 주어지면 "targets 에 명시된 더 구체적인 경로" 는 상위
|
||||
* protected 를 오버라이드한다. 예: `protected_paths` 에 `modules` 가 있고
|
||||
* `targets` 에 `modules/_bundled` 가 명시되어 있으면, `modules/_bundled/...` 파일은
|
||||
* protected 로 차단되지 않고 apply 대상에 포함된다.
|
||||
*
|
||||
* 배경: `protected_paths` 의 확장 부모(`modules`/`plugins`/`templates`/`lang-packs`)는
|
||||
* "자동 발견 폴백" 이 활성 서브디렉토리(`modules/sirsoft-*`)를 통째로 삭제하는
|
||||
* 회귀(#347) 를 막기 위한 방어인데, 정상 증분 흐름에서는 `_bundled` 갱신까지
|
||||
* 함께 막아 코어 업데이트로 번들 확장 파일이 반영되지 않는 결함(#452 / 공개 #64)을
|
||||
* 유발했다. targets 에 명시된 경로가 protected 보다 더 구체적(하위)이면 예외 처리한다.
|
||||
*
|
||||
* @param string $relative 검사할 상대 경로
|
||||
* @param array<int, string> $protectedSet 정규화된 보호 경로 집합
|
||||
* @param array<int, string> $allowedTargetSet 정규화된 targets 집합 (protected 오버라이드 허용)
|
||||
*/
|
||||
private static function isWithinProtectedPath(string $relative, array $protectedSet): bool
|
||||
private static function isWithinProtectedPath(string $relative, array $protectedSet, array $allowedTargetSet = []): bool
|
||||
{
|
||||
$relative = self::normalizeRelative($relative);
|
||||
|
||||
// 가장 구체적으로 매칭되는 protected 경로 길이(세그먼트 수)를 찾는다.
|
||||
$matchedProtectedLen = -1;
|
||||
foreach ($protectedSet as $p) {
|
||||
if ($p === '') {
|
||||
continue;
|
||||
}
|
||||
if ($relative === $p) {
|
||||
return true;
|
||||
}
|
||||
if (str_starts_with($relative, $p.'/')) {
|
||||
return true;
|
||||
if ($relative === $p || str_starts_with($relative, $p.'/')) {
|
||||
$len = substr_count($p, '/') + 1;
|
||||
if ($len > $matchedProtectedLen) {
|
||||
$matchedProtectedLen = $len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
if ($matchedProtectedLen === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// protected 하위이지만, targets 에 더 구체적으로(= 더 깊게) 명시된 경로의
|
||||
// 하위이면 오버라이드하여 통과(= 보호 해제). target 이 protected 와 같거나
|
||||
// 더 얕으면 오버라이드하지 않는다(예: target `storage` == protected `storage`).
|
||||
foreach ($allowedTargetSet as $t) {
|
||||
if ($t === '') {
|
||||
continue;
|
||||
}
|
||||
$targetLen = substr_count($t, '/') + 1;
|
||||
if ($targetLen <= $matchedProtectedLen) {
|
||||
continue;
|
||||
}
|
||||
if ($relative === $t || str_starts_with($relative, $t.'/')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -193,8 +193,11 @@ v접두사 자동 감지 (resolveGithubArchiveUrl):
|
||||
· base 없음 → added / size·md5 다름 → changed / 동일 → 제외(스킵)
|
||||
· size 선필터 후 size 동일할 때만 md5 (mtime 비교 안 함 — _pending 은 추출 시각)
|
||||
· symlink / excludes / protected_paths 하위 → 목록 제외
|
||||
· 단, targets 에 더 구체적으로 명시된 경로는 상위 protected 를 오버라이드(아래 주석)
|
||||
```
|
||||
|
||||
> **protected_paths 오버라이드 (공개 #64 / 내부 #452)**: `protected_paths` 에는 확장 부모(`modules`·`plugins`·`templates`·`lang-packs`)가 포함되지만, `targets` 에는 `{domain}/_bundled` 가 명시된다. 3-way 산출(`computeApplyList`)과 신규 파일 manifest(`writeNewFilesManifest`)는 "targets 에 더 구체적(하위)으로 명시된 경로가 상위 protected 를 오버라이드"하도록 판정한다. 이로써 코어 배포본에 포함된 번들 확장의 갱신 파일(`_bundled/{id}/composer.json`·`vendor-bundle.json` 등)이 코어 업데이트로 정상 반영된다. 오버라이드는 target 이 protected 보다 **더 깊을 때만** 적용되므로, `storage`(target) == `storage`(protected) 같은 동일 경로는 여전히 제외된다. 확장 부모를 protected 에 둔 원래 의도(자동 발견 폴백이 활성 서브디렉토리 `modules/sirsoft-*` 를 삭제하는 #347 방어)는 그대로 유지된다 — 자동 발견 폴백은 targets 순회가 아니므로 오버라이드 영향을 받지 않는다.
|
||||
|
||||
### Step 7: 파일 적용
|
||||
|
||||
```text
|
||||
@@ -662,7 +665,7 @@ config('app.version') = env('APP_VERSION', 'config/app.php 기본값')
|
||||
|--------|---------|------|
|
||||
| `createBackup()` | `(?Closure $onProgress): string` | CoreBackupHelper로 백업 생성 |
|
||||
| `restoreFromBackup()` | `(string $backupPath, ?Closure $onProgress): void` | 백업에서 파일 복원 |
|
||||
| `CoreBackupHelper::computeApplyList()` | `(string $backupPath, string $sourcePath, array $targets, array $protectedPaths, array $excludes): array` | 3-way 판정으로 증분 적용 대상(added/changed) 산출 (`apply`, `added_count`, `changed_count`, `has_symlink`) |
|
||||
| `CoreBackupHelper::computeApplyList()` | `(string $backupPath, string $sourcePath, array $targets, array $protectedPaths, array $excludes): array` | 3-way 판정으로 증분 적용 대상(added/changed) 산출 (`apply`, `added_count`, `changed_count`, `has_symlink`). targets 에 명시된 `{domain}/_bundled` 는 상위 protected(`modules` 등)를 오버라이드해 목록에 포함 (공개 #64 / 내부 #452) |
|
||||
|
||||
### 적용 및 설치
|
||||
|
||||
|
||||
@@ -163,6 +163,102 @@ class CoreBackupHelperApplyListTest extends TestCase
|
||||
$this->assertSame([], $result['apply']);
|
||||
}
|
||||
|
||||
/**
|
||||
* targets 에 명시된 `{domain}/_bundled` 경로는 상위 protected(`{domain}`)보다
|
||||
* 우선하여 apply 목록에 포함됩니다 (공개 #64 / 내부 #452 회귀).
|
||||
*
|
||||
* 배경: `config/app.php` 의 update.targets 에는 `modules/_bundled`,
|
||||
* `plugins/_bundled`, `templates/_bundled`, `lang-packs/_bundled` 가 있고,
|
||||
* protected_paths 에는 부모 `modules`, `plugins`, `templates`, `lang-packs` 가
|
||||
* 있다. protected 는 자동 발견 폴백이 활성 서브디렉토리(`modules/sirsoft-*`)를
|
||||
* 통째로 삭제하는 회귀(#347)를 막기 위한 것인데, 정상 증분 흐름의 apply 산출에서
|
||||
* `{domain}/_bundled/...` 파일이 `{domain}/` 접두사에 걸려 통째로 제외되면
|
||||
* 코어 업데이트 시 번들 확장의 새 파일(composer.json·vendor-bundle.*)이
|
||||
* 반영되지 않는다. targets 에 더 구체적으로 명시된 경로는 상위 protected 를
|
||||
* 오버라이드해야 한다. 4종 확장(모듈/플러그인/템플릿/언어팩) 전수 검증.
|
||||
*
|
||||
* @dataProvider bundledDomainProvider
|
||||
*/
|
||||
public function test_bundled_target_overrides_parent_protected_path(string $domain): void
|
||||
{
|
||||
$bundledTarget = "{$domain}/_bundled";
|
||||
|
||||
// theirs 에만 존재하는 신규 번들 파일 (added)
|
||||
$newRel = "{$bundledTarget}/vendor-ext/composer.json";
|
||||
$newAbs = $this->theirsPath.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $newRel);
|
||||
File::ensureDirectoryExists(dirname($newAbs));
|
||||
File::put($newAbs, '{"version":"1.0.1"}');
|
||||
|
||||
// base·theirs 양쪽에 있지만 내용이 다른 번들 파일 (changed)
|
||||
$this->writeBoth(
|
||||
"{$bundledTarget}/vendor-ext/vendor-bundle.json",
|
||||
'{"composer_json_sha256":"old"}',
|
||||
'{"composer_json_sha256":"new-value-differs"}',
|
||||
);
|
||||
|
||||
// 활성 서브디렉토리(=targets 밖, protected 로 보존되어야 함)
|
||||
$activeRel = "{$domain}/vendor-ext-active";
|
||||
File::ensureDirectoryExists($this->theirsPath.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $activeRel));
|
||||
File::put(
|
||||
$this->theirsPath.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, "{$activeRel}/active.php"),
|
||||
'active',
|
||||
);
|
||||
|
||||
$result = CoreBackupHelper::computeApplyList(
|
||||
$this->basePath,
|
||||
$this->theirsPath,
|
||||
[$bundledTarget], // targets: _bundled 만 명시
|
||||
[$domain, 'storage', 'vendor'], // protected: 부모 도메인 포함 (실제 config 반영)
|
||||
['node_modules', '.git'],
|
||||
);
|
||||
|
||||
// _bundled 하위 신규/변경 파일은 apply 목록에 포함되어야 한다
|
||||
$this->assertContains("{$bundledTarget}/vendor-ext/composer.json", $result['apply']);
|
||||
$this->assertContains("{$bundledTarget}/vendor-ext/vendor-bundle.json", $result['apply']);
|
||||
$this->assertSame(1, $result['added_count']);
|
||||
$this->assertSame(1, $result['changed_count']);
|
||||
|
||||
// 활성 서브디렉토리(targets 밖)는 순회 대상이 아니므로 목록에 없어야 한다
|
||||
$this->assertNotContains("{$activeRel}/active.php", $result['apply']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4종 번들 확장 도메인 (config/app.php 의 targets/protected_paths 실제 조합).
|
||||
*
|
||||
* @return array<string, array{string}>
|
||||
*/
|
||||
public static function bundledDomainProvider(): array
|
||||
{
|
||||
return [
|
||||
'modules' => ['modules'],
|
||||
'plugins' => ['plugins'],
|
||||
'templates' => ['templates'],
|
||||
'lang-packs' => ['lang-packs'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* target 과 protected 가 정확히 동일한 경로면 여전히 제외됩니다 (기존 방어 유지).
|
||||
*
|
||||
* `_bundled` 오버라이드는 "targets 가 protected 보다 더 구체적(하위)일 때"만
|
||||
* 적용된다. target == protected (예: storage) 는 제외가 정상.
|
||||
*/
|
||||
public function test_target_equal_to_protected_is_still_excluded(): void
|
||||
{
|
||||
File::ensureDirectoryExists($this->theirsPath.DIRECTORY_SEPARATOR.'storage'.DIRECTORY_SEPARATOR.'app');
|
||||
File::put($this->theirsPath.DIRECTORY_SEPARATOR.'storage'.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'runtime.txt', 'data');
|
||||
|
||||
$result = CoreBackupHelper::computeApplyList(
|
||||
$this->basePath,
|
||||
$this->theirsPath,
|
||||
['storage'],
|
||||
['storage'],
|
||||
[],
|
||||
);
|
||||
|
||||
$this->assertSame([], $result['apply']);
|
||||
}
|
||||
|
||||
/**
|
||||
* added 와 changed 가 섞여 있을 때 apply 목록이 정렬되어 둘 다 포함합니다.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Extension\Helpers\CoreBackupHelper;
|
||||
use App\Extension\Helpers\FilePermissionHelper;
|
||||
use App\Services\CoreUpdateService;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
@@ -1185,6 +1186,57 @@ MD;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* end-to-end 회귀 (공개 #64 / 내부 #452): 코어 업데이트가 번들 확장의 변경된
|
||||
* `_bundled` 파일을 실제로 반영합니다.
|
||||
*
|
||||
* computeApplyList(3-way 산출) → applyUpdate(적용) 전 체인을 실제 config 조합
|
||||
* (protected 에 `modules`, targets 에 `modules/_bundled`)으로 검증한다.
|
||||
* protected 필터가 `_bundled` 갱신을 삼키던 결함으로 인해, 코어 배포본에
|
||||
* 새 번들(vendor-bundle.json/composer.json 갱신)이 포함돼도 활성 서버의
|
||||
* `_bundled` 가 갱신되지 않아 이후 `module:update` 무결성 검증이 실패하던
|
||||
* 회귀를 차단한다.
|
||||
*/
|
||||
public function test_incremental_apply_reflects_changed_bundled_extension_file(): void
|
||||
{
|
||||
[$source, $fakeBase, $restore] = $this->prepareApplyUpdateEnv(['modules/_bundled']);
|
||||
|
||||
try {
|
||||
// 실제 config 조합 재현: protected 에 부모 도메인 포함
|
||||
config(['app.update.protected_paths' => ['.env', 'storage', 'vendor', 'modules', 'plugins', 'templates', 'lang-packs']]);
|
||||
|
||||
$rel = 'modules/_bundled/sirsoft-ecommerce/vendor-bundle.json';
|
||||
$relPlatform = str_replace('/', DIRECTORY_SEPARATOR, $rel);
|
||||
|
||||
// base(구버전) = 옛 해시, source(신버전) = 새 해시 (코어가 변경한 번들 파일)
|
||||
File::ensureDirectoryExists(dirname($fakeBase.DIRECTORY_SEPARATOR.$relPlatform));
|
||||
File::put($fakeBase.DIRECTORY_SEPARATOR.$relPlatform, '{"composer_json_sha256":"OLD"}');
|
||||
File::ensureDirectoryExists(dirname($source.DIRECTORY_SEPARATOR.$relPlatform));
|
||||
File::put($source.DIRECTORY_SEPARATOR.$relPlatform, '{"composer_json_sha256":"NEW-differs-in-length"}');
|
||||
|
||||
// 3-way 산출: base != theirs → changed 로 목록 포함되어야 한다
|
||||
$applyResult = CoreBackupHelper::computeApplyList(
|
||||
$fakeBase,
|
||||
$source,
|
||||
config('app.update.targets'),
|
||||
config('app.update.protected_paths'),
|
||||
config('app.update.excludes'),
|
||||
);
|
||||
$this->assertContains($rel, $applyResult['apply'], 'changed 된 _bundled 파일이 apply 목록에 포함되어야 한다');
|
||||
|
||||
// 적용: 활성(base) 파일이 신버전 내용으로 갱신되어야 한다
|
||||
$this->service->applyUpdate($source, null, prune: false, applyList: $applyResult['apply']);
|
||||
|
||||
$this->assertSame(
|
||||
'{"composer_json_sha256":"NEW-differs-in-length"}',
|
||||
File::get($fakeBase.DIRECTORY_SEPARATOR.$relPlatform),
|
||||
'코어 업데이트가 변경된 _bundled 번들 파일을 실제로 반영해야 한다',
|
||||
);
|
||||
} finally {
|
||||
$restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 증분 모드는 orphan(소스에 없는 대상 파일)을 삭제하지 않습니다
|
||||
* (사용자가 추가한 신규 파일 보존).
|
||||
|
||||
@@ -45,6 +45,9 @@ axes:
|
||||
apply_mode: [incremental, prune, fallback_no_backup] # 파일 적용 방식
|
||||
three_way_verdict: [unchanged_same, added_new, changed_size, changed_md5] # base↔theirs 판정
|
||||
user_local_modification: [modified, untouched] # mine 이 base 와 다른지 (사용자 수정)
|
||||
# targets 의 {domain}/_bundled 가 상위 protected({domain}) 를 오버라이드해 apply 되는지 (공개 #64 / 내부 #452)
|
||||
bundled_apply_override: [override_applies, sibling_active_preserved, target_equals_protected_still_blocked]
|
||||
protected_override_domain: [modules, plugins, templates, lang-packs] # 오버라이드 대상 4종 확장
|
||||
|
||||
exclusions:
|
||||
- { source_structure: empty_top_level, active_subdir_state: present, reason: "source 가 비었으면 자동 발견 폴백 자체가 트리거 안 됨 — 활성 서브디렉토리 보존 무조건 보장" }
|
||||
@@ -53,6 +56,8 @@ exclusions:
|
||||
- { apply_mode: prune, three_way_verdict: unchanged_same, reason: "prune 은 3-way 판정을 건너뛰고 전체 덮어쓰기 — three_way_verdict 무의미" }
|
||||
- { apply_mode: fallback_no_backup, three_way_verdict: unchanged_same, reason: "백업 부재 시 base 자체가 없어 3-way 불가 — 전체 덮어쓰기로 회귀" }
|
||||
- { apply_mode: incremental, user_local_modification: modified, three_way_verdict: changed_size, reason: "코어+사용자 동시 변경 — 코어 버전으로 갱신(§6 한계), 백업에서 복구 가능" }
|
||||
- { apply_mode: prune, bundled_apply_override: override_applies, reason: "prune 은 3-way/protected 필터를 우회하고 전체 덮어쓰기 — 오버라이드 판정 자체가 무의미" }
|
||||
- { bundled_apply_override: target_equals_protected_still_blocked, protected_override_domain: modules, reason: "target==protected 방어는 storage 같은 동일 경로 케이스로 검증 — 도메인 무관 (protected_override_domain 축과 직교 아님)" }
|
||||
|
||||
effects:
|
||||
- applyDiscoveredTopLevelPaths_does_not_remove_active_subdirs_when_source_only_has_bundled
|
||||
@@ -91,6 +96,11 @@ effects:
|
||||
- test_apply_update_incremental_does_not_remove_orphans # 증분=삭제 안 함
|
||||
- test_apply_update_prune_removes_orphans_and_overwrites_all # prune=전체 덮어쓰기+삭제
|
||||
- test_apply_update_null_apply_list_falls_back_to_full_overwrite # 백업 부재 fallback
|
||||
# targets 의 {domain}/_bundled 가 상위 protected 를 오버라이드해 코어 업데이트로 번들 확장 파일이 갱신됨 (공개 #64 / 내부 #452).
|
||||
# effect 명 = 실제 테스트 메서드명 (CoreBackupHelperApplyListTest / CoreUpdateServiceTest).
|
||||
- test_bundled_target_overrides_parent_protected_path # 4종 확장 전수 (dataProvider)
|
||||
- test_target_equal_to_protected_is_still_excluded # target==protected 는 여전히 제외 (방어 유지)
|
||||
- test_incremental_apply_reflects_changed_bundled_extension_file # computeApplyList→applyUpdate end-to-end 반영
|
||||
|
||||
test_files:
|
||||
- tests/Unit/Services/CoreUpdateServiceTest.php
|
||||
|
||||
Reference in New Issue
Block a user