App Manifest 계약
App 탐색에 사용하는 Manifest 메타데이터와 확장 메서드의 계약입니다.
App Manifest 계약
App 메타데이터, Contribution 탐색 위치, 사용자 정의 경로를 등록하는 계약입니다. App 패키지 만들기에서 생성한 Manifest를 수정하세요. 메타데이터의 key와 Manifest 클래스가 일치해야 호스트가 App과 확장을 탐색할 수 있습니다.
시그니처
App Package Manifest는 SDK 기반 클래스를 확장하고 Nexia\AppRuntime\Contracts\TenantInstallableAppManifest를 만족합니다.
AbstractPackageAppManifest가 메서드 셋을 final로 공급합니다. 패키지 메타데이터에서 파생되므로 오버라이드할 수 없습니다.
final public function definition(): AppDefinition;
final public function id(): string;
final public function name(): string;
나머지 훅은 여러분이 오버라이드합니다.
public function contributionLocations(): array;
public function pageElements(): array;
public function pageElementsExtras(): array;
public function agentComponents(): array;
public function tenantFilamentResources(): array;
public function tenantMigrationPaths(): array;
클래스 이름은 AppManifest로 끝나야 하고, extra.nexia.app.manifest가 그것을 FQCN으로 지목해야 합니다.
최소 예시
App 패키지 만들기 이후 사용할 수 있는 완전한 최소 src/WorkshopAppManifest.php입니다. 생성된 Manifest에 개요나 내비게이션도 있다면 해당 선언은 유지하세요.
동작하는 Manifest는 자기 contribution이 어디 있고 마이그레이션이 어디 있는지 선언합니다.
<?php
declare(strict_types=1);
namespace Amuzcorp\Nexia\Workshop;
use Nexia\AppRuntime\AbstractPackageAppManifest;
final class WorkshopAppManifest extends AbstractPackageAppManifest
{
public function contributionLocations(): array
{
return [
['namespace' => 'Amuzcorp\Nexia\Workshop\Contribution', 'directory' => __DIR__.'/Contribution'],
['namespace' => 'Amuzcorp\Nexia\Workshop\Descriptors', 'directory' => __DIR__.'/Descriptors'],
];
}
public function tenantMigrationPaths(): array
{
return [__DIR__.'/../database/migrations/tenant'];
}
}나머지는 전부 — 권한, 내비게이션, 대시보드, descriptor — 그 디렉터리 안에서 찾아지는 별도 contributor 클래스에서 옵니다.
인자: 메타데이터 블록
definition()은 PHP가 아니라 패키지 composer.json의 extra.nexia.app에서 만들어집니다.
{
"manifest": "Amuzcorp\\Nexia\\Workshop\\WorkshopAppManifest",
"app_family": "other",
"app_icon": "box",
"launcher_order": 500,
"overview_navigation_id": "workshop",
"app_name": "Workshop",
"app_key": "workshop",
"app_table_prefix": "wsp",
"prerequisite_apps": [],
"descriptions": {"en": "Workshop application."},
"readiness": "available"
}descriptions는 [], readiness는 available이 기본값이고, 나머지 필드는 필수이며 알 수 없는 필드는 검증에 실패합니다. 전체 필드 표는 App 정체성에 있습니다.
AppDefinition이 생성 시점에 검증합니다.
| 조건 | 메시지 |
|---|---|
manifest가 FQCN이 아님 | App metadata [manifest] must be a fully qualified PHP class name. |
app_key가 예약됨 | App metadata [app_key] cannot use reserved Core key [{key}]. |
launcher_order가 음수 | App metadata [launcher_order] must be zero or greater. |
app_family가 kebab-case가 아님 | app_family에 대한 kebab-case 단정 실패 |
app_name이나 app_icon이 빔 | 그 필드에 대한 non-blank 단정 실패 |
예약 키는 core, host, platform, shell, tenant, process, apps입니다. readiness 상태는 available, beta, preview입니다. available과 beta는 나머지 catalog·prerequisite gate를 통과하면 테넌트 설치 대상이지만, preview는 catalog에 보이는 metadata일 뿐 설치 대상이 아닙니다. publication은 별도 Central 결정(draft, published, suspended)이므로 manifest metadata만으로 App을 선택할 수는 없습니다.
결과 또는 반환: 훅
| 메서드 | 돌려주는 것 | 목적 |
|---|---|---|
contributionLocations() | {namespace, directory} 목록 | contribution manifest builder가 스캔하고 색인하는 루트 |
pageElements() | array<string, string> | App 수준 경로 → 컴포넌트 이름. 기본 구현은 pageElementsExtras()를 반환 |
pageElementsExtras() | array<string, string> | Resource가 아닌 페이지의 Route → 컴포넌트 이름 |
agentComponents() | array | 에이전트 렌더러가 쓸 수 있는 컴포넌트 |
tenantFilamentResources() | class-string 목록 | 등록할 Filament 테넌트 Resource |
tenantMigrationPaths() | 경로 목록 | 테넌트 마이그레이션 디렉터리 |
contributionLocations()
namespace가 directory 안 클래스들의 PSR-4 네임스페이스와 정확히 일치해야 합니다. 하위 디렉터리는 덮입니다. Contribution/Resources/NoteModule.php는 Contribution 항목으로 찾아집니다.
선언된 모든 root 밖에 있는 class는 어떤 interface를 구현하든 색인되지 않습니다. 일반 요청은 생성된 manifest를 신뢰하고 이 root를 다시 스캔하지 않습니다. contribution source를 바꾼 뒤 nexia-runtime:refresh-runtime-caches를 실행하세요.
pageElements()와 pageElementsExtras()
기본 pageElements()는 pageElementsExtras()의 반환값을 그대로 사용합니다. App 수준 경로는 여기에 추가하세요. 생성된 Resource 경로는 각 Module의 ShellResourceContribution에서 수집하며, 생성기가 Manifest의 pageElements()를 수정하는 방식이 아닙니다.
생성된 Manifest 내부 메서드 발췌입니다.
public function pageElementsExtras(): array
{
return [
'/apps/workshop' => 'WorkshopOverviewSurface',
];
}게시된 이름마다 프런트엔드 등록이 필요합니다. 없으면 doctor의 shell component pairing 검사가 실패하고 Route가 빈 화면을 렌더링합니다. frontend component registration은 더 거칠어서 index.ts가 등록 함수를 호출하는지만 확인합니다.
tenantMigrationPaths()
__DIR__로 만든 절대 디렉터리 경로를 반환합니다. 테넌트 설치 시 선택한 App과 선행 App의 마이그레이션을 초기화 전에 실행합니다. 이후 tenants:migrate는 비활성 App을 포함해 설치 이력이 있는 App만 업데이트합니다. 미설치 App의 스키마는 신규 테넌트에 생성하지 않습니다. 로컬 Composer 심볼릭 링크와 vendor 설치본에서 모두 올바르게 해석되도록 __DIR__을 사용하세요.
선택적 contribution 인터페이스
Contribution이 Resource 하나가 아니라 App에 속할 때는 Manifest 자신이 contribution 인터페이스를 구현할 수 있습니다. 실제로는 어떤 Resource도 소유하지 않는 목적지를 위한 NavigationContribution입니다.
생성된 Manifest의 NavigationContribution 구현, NavigationItem import, navigationItems() 메서드를 유지하세요. App 단위 목적지는 해당 메서드에 추가합니다. 생성된 개요 항목에서 완전한 호출 예시를 확인할 수 있습니다.
Resource별 권한이나 내비게이션, descriptor를 Manifest로 옮기지 마세요. 그것들은 Resource 자신의 카탈로그 contributor에 속하고, 발견이 선언된 위치를 통해 찾아냅니다.
오류
| 메시지 | 원인 | 해결 |
|---|---|---|
App manifest [{class}] must end with AppManifest. | 클래스 이름 관례 위반 | 클래스 rename 후 extra.nexia.app.manifest 갱신 |
WARN runtime app registration: AppRegistry cannot see {key} | 패키지가 호스트 Composer 요구사항에 없거나 오토로더가 낡음 | nexia-apps:activate-package-app 후 프로세스 재시작 |
FAIL runtime app metadata: registered manifest metadata differs from composer.json. | 로드된 정의가 파일과 갈라짐 | nexia-access:sync-manifest와 활성화 재실행 |
FAIL runtime app metadata: registered manifest is not tenant-installable. | Manifest가 TenantInstallableAppManifest를 만족하지 않음 | AbstractPackageAppManifest 확장 |
WARN contribution locations: no package contribution location is registered | 디렉터리 미선언 또는 네임스페이스 불일치 | contributionLocations() 수정 |
App에 적용
현재 Quality Manifest에는 Contribution 탐색 위치 두 곳, 테넌트 migration 경로 하나, 개요 경로와 내비게이션 항목 하나가 있습니다. 테넌트 Filament Resource 목록은 비어 있습니다. 메타데이터는 composer.json에서 읽습니다. 새 App은 생성된 Manifest를 출발점으로 삼으세요.
관련 문서
- App 정체성 — 모든 메타데이터 필드 설명
- 확장 모델 — 발견이 선언된 위치로 하는 일
- React 컴포넌트와 훅 —
pageElements()가 지목하는 컴포넌트 등록 - Contribution 발견 문제 해결 — 위 실패 각각 진단