v7.0.0 release

This commit is contained in:
HeuJung
2026-07-01 10:30:32 +09:00
parent 0e7fa04b03
commit c4ea9a6cd1
3582 changed files with 626189 additions and 160151 deletions
+90 -10
View File
@@ -2,16 +2,19 @@
namespace App\Services;
use App\Exceptions\ModuleOperationException;
use App\Contracts\Extension\ModuleManagerInterface;
use App\Contracts\Repositories\ModuleRepositoryInterface;
use App\Enums\ExtensionStatus;
use App\Exceptions\ModuleOperationException;
use App\Extension\Helpers\ChangelogParser;
use App\Extension\Helpers\EditorSpecAssembler;
use App\Extension\Helpers\GithubHelper;
use App\Extension\Helpers\ZipInstallHelper;
use App\Extension\HookManager;
use App\Extension\Vendor\VendorMode;
use App\Helpers\PermissionHelper;
use App\Models\Module;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Validation\ValidationException;
@@ -102,7 +105,7 @@ class ModuleService
/**
* 설치된 모듈 목록을 조회합니다.
*
* @return \Illuminate\Database\Eloquent\Collection 설치된 모듈 목록
* @return Collection 설치된 모듈 목록
*/
public function getInstalledModules()
{
@@ -112,7 +115,7 @@ class ModuleService
/**
* 마켓플레이스에서 이용 가능한 모듈 목록을 조회합니다.
*
* @return \Illuminate\Database\Eloquent\Collection 마켓플레이스 모듈 목록
* @return Collection 마켓플레이스 모듈 목록
*/
public function getMarketplaceModules()
{
@@ -122,7 +125,7 @@ class ModuleService
/**
* 의존성 정보가 포함된 모든 모듈을 조회합니다.
*
* @return \Illuminate\Database\Eloquent\Collection 의존성 정보가 포함된 모듈 목록
* @return Collection 의존성 정보가 포함된 모듈 목록
*/
public function getAllModulesWithDependencies()
{
@@ -169,17 +172,16 @@ class ModuleService
* 지정된 모듈을 업데이트합니다.
*
* @param string $moduleName 업데이트할 모듈 identifier
* @param \App\Extension\Vendor\VendorMode $vendorMode Vendor 설치 모드
* @param VendorMode $vendorMode Vendor 설치 모드
* @param string $layoutStrategy 레이아웃 전략 (overwrite|keep)
* @return array 업데이트 결과 (identifier, from_version, to_version 등)
*
* @param bool $force 코어 버전 비호환 강제 우회 (위험 — 사용자 명시 필요)
* @return array 업데이트 결과 (identifier, from_version, to_version 등)
*
* @throws ValidationException 업데이트 실패 시
*/
public function updateModule(
string $moduleName,
\App\Extension\Vendor\VendorMode $vendorMode = \App\Extension\Vendor\VendorMode::Auto,
VendorMode $vendorMode = VendorMode::Auto,
string $layoutStrategy = 'overwrite',
bool $force = false,
): array {
@@ -238,7 +240,7 @@ class ModuleService
* 모듈을 시스템에 설치합니다.
*
* @param string $moduleName 설치할 모듈명
* @param \App\Extension\Vendor\VendorMode $vendorMode Vendor 설치 모드
* @param VendorMode $vendorMode Vendor 설치 모드
* @param bool $force Updating/Failed 등 진행 중 상태도 무시하고 강제 설치 여부
* @return array|null 설치된 모듈 정보 또는 null
*
@@ -246,7 +248,7 @@ class ModuleService
*/
public function installModule(
string $moduleName,
\App\Extension\Vendor\VendorMode $vendorMode = \App\Extension\Vendor\VendorMode::Auto,
VendorMode $vendorMode = VendorMode::Auto,
bool $force = false,
): ?array {
HookManager::doAction('core.modules.before_install', $moduleName);
@@ -736,6 +738,7 @@ class ModuleService
],
];
}
/**
* 업로드된 ZIP 파일로부터 모듈을 추출/검증하고 _pending 으로 이동 후 설치합니다.
*
@@ -950,6 +953,83 @@ class ModuleService
];
}
/**
* 모듈 편집기 스펙(editor-spec.json) 디코드 결과를 반환합니다.
*
* 활성 모듈만 대상으로 하며, 활성 디렉토리 → _bundled 폴백 순으로 읽습니다.
* editor-spec.json 은 수작업 작성 파일로 모듈 루트(module.json 옆)에 둡니다.
* 파일 미존재/미작성 시 spec=null 로 폴백합니다(편집 컨트롤 부재).
*
* @param string $identifier 모듈 식별자 (vendor-module 형식)
* @return array{success: bool, spec: array<string, mixed>|null, error: string|null}
*/
public function getEditorSpec(string $identifier): array
{
$module = $this->moduleRepository->findByIdentifier($identifier);
if (! $module || $module->status !== ExtensionStatus::Active->value) {
return ['success' => false, 'spec' => null, 'error' => 'module_not_found'];
}
// 분할 editor-spec.json 은 manifest + `$include` 블록으로 구성되므로
// 단순 디코드가 아닌 합본이 필요하다. 활성 디렉토리만 기준으로 합본한다
// (_bundled 폴백 없음). _bundled 작업분은 module:update 로 활성에
// 반영된 뒤에만 런타임에 보인다. 미분할 파일은 원본 그대로(하위 호환).
$spec = EditorSpecAssembler::assemble(
base_path("modules/{$identifier}/editor-spec.json")
);
return ['success' => true, 'spec' => $spec, 'error' => null];
}
/**
* 모듈 컴포넌트 매니페스트(components.json) 디코드 결과를 반환합니다.
*
* components.json 은 module:build 산출물로 모듈 루트에 둡니다.
* 활성 디렉토리 → _bundled 폴백 순으로 읽으며, 미생성(구버전 모듈) 시
* components=null 로 폴백합니다(무손실 보존 디그레이드 — 원칙 4.6).
*
* @param string $identifier 모듈 식별자
* @return array{success: bool, components: array<string, mixed>|null, error: string|null}
*/
public function getComponents(string $identifier): array
{
$module = $this->moduleRepository->findByIdentifier($identifier);
if (! $module || $module->status !== ExtensionStatus::Active->value) {
return ['success' => false, 'components' => null, 'error' => 'module_not_found'];
}
$components = $this->readJsonWithBundledFallback("modules/{$identifier}/components.json");
return ['success' => true, 'components' => $components, 'error' => null];
}
/**
* 활성 디렉토리 → _bundled 폴백 순으로 JSON 파일을 읽어 디코드합니다.
*
* @param string $relativePath base_path 기준 상대 경로 (modules/{id}/file.json)
* @return array<string, mixed>|null 디코드된 배열, 미존재/파싱 실패 시 null
*/
private function readJsonWithBundledFallback(string $relativePath): ?array
{
// modules/{id}/file.json → modules/_bundled/{id}/file.json 폴백
$bundledPath = preg_replace('#^modules/#', 'modules/_bundled/', $relativePath, 1);
$candidates = [base_path($relativePath), base_path((string) $bundledPath)];
foreach ($candidates as $path) {
if (! file_exists($path) || ! is_file($path)) {
continue;
}
$decoded = json_decode((string) file_get_contents($path), true);
if (is_array($decoded)) {
return $decoded;
}
}
return null;
}
/**
* MIME 타입 감지
*