모든 샌드박스 플러그인은 package.json 옆에 emdash-plugin.jsonc를 가지고 있습니다. 수동으로 편집되며 플러그인의 아이덴티티, 신뢰 계약(기능, 호스트, 스토리지), 레지스트리에 표시되는 프로필 필드를 포함합니다. emdash-plugin init이 스캐폴드를 생성합니다. CLI는 build, dev, validate, bundle, publish에서 ./emdash-plugin.jsonc를 자동으로 읽습니다.
파일은 JSONC입니다: 주석과 후행 쉼표가 허용됩니다.
다음 예제는 이미지 갤러리 플러그인의 완전한 매니페스트를 보여줍니다:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "gallery",
"publisher": "did:plc:abc123def456",
"license": "MIT",
"author": { "name": "Jane Doe", "url": "https://example.com" },
"security": { "email": "[email protected]" },
// 선택적 프로필
"name": "Gallery",
"description": "EmDash용 이미지 갤러리 블록.",
"keywords": ["gallery", "images"],
"repo": "https://github.com/example/plugin-gallery",
// 신뢰 계약
"capabilities": ["content:read"],
"allowedHosts": [],
"storage": {}
}
아이덴티티
| 필드 | 필수 | 비고 |
|---|---|---|
slug | 예 | 퍼블리셔 네임스페이스 내의 URL 안전 ID. /^[a-z][a-z0-9_-]*$/, 최대 64자. |
publisher | 예 | Atmosphere 계정의 DID 또는 핸들. 퍼블리셔 피닝 참조. |
version | 아니오 | 빌드 메타데이터 없는 Semver 2.0. 보통 생략 — 아래 참조. |
slug와 publisher가 합쳐져 패키지의 아이덴티티가 됩니다. EmDash는 이들로부터 패키지의 전체 식별자를 자동으로 도출합니다.
version은 package.json에 둡니다
빌드는 매니페스트의 version을 package.json#version과 대조합니다:
- 둘 다 설정되어 있고 같음 → 정상.
- 둘 다 설정되어 있고 다름 → 하드 에러.
- 하나만 설정 → 해당 값이 우선.
- 둘 다 미설정 → 하드 에러.
npm 배포 플러그인의 권장 패턴은 매니페스트에서 version을 생략하고 package.json을 유일한 진실의 원천으로 삼는 것입니다(릴리스 도구가 이미 거기서 버전을 올립니다). package.json이 없는 레지스트리 전용 플러그인은 매니페스트에 version을 설정해야 합니다 — 다른 곳에 둘 수 없습니다.
프로필
이들은 레지스트리 목록에 반영됩니다. license, 저자(author 또는 authors), 보안 연락처(security 또는 securityContacts)는 필수입니다. 나머지는 선택 사항입니다.
| 필드 | 필수 | 비고 |
|---|---|---|
license | 예 | SPDX 표현식("MIT", "Apache-2.0", "MIT OR Apache-2.0"). 첫 번째 게시 시 사용; 이후 게시에서는 기존 프로필이 우선. |
author / authors | 예 | 둘 중 하나. author: { name, url?, email? }는 단일 저자용; authors: [...] (≤ 32)은 여러 저자용. 둘 다 설정하면 에러. |
security / securityContacts | 예 | 둘 중 하나. 각 연락처는 최소 email 또는 url이 필요. securityContacts: [...] (≤ 8)은 여러 개용. 둘 다 설정하면 에러. |
name | 아니오 | 표시 이름. 기본값은 slug. |
description | 아니오 | 짧게 유지(약 140자). 긴 값은 목록에서 잘릴 수 있음. |
keywords | 아니오 | ≤ 5개 항목. |
repo | 아니오 | 소스 저장소의 https:// URL. |
실제로 여러 명이 아닌 한 단수형 author / security를 사용하세요 — 이것이 일반적인 경우이며 스캐폴드도 이렇게 출력합니다.
신뢰 계약
신뢰 계약은 capabilities, allowedHosts, storage입니다. 세 가지 모두 기본값은 비어 있으므로, 추가 권한이 필요 없는 플러그인은 완전히 생략할 수 있습니다.
{
"capabilities": ["network:request", "content:read"],
"allowedHosts": ["api.example.com", "*.cdn.example.com"],
"storage": {
"events": { "indexes": ["timestamp"] },
"submissions": { "indexes": ["email"], "uniqueIndexes": ["token"] }
}
}
기능
인식되는 이름:
| 기능 | 허용하는 것 |
|---|---|
content:read / content:write | ctx를 통한 사이트 콘텐츠 읽기 / 변경. |
taxonomies:read | 택소노미 정의 및 용어 읽기(읽기 전용). |
media:read / media:write | 미디어 읽기 / 쓰기. |
users:read | 사용자 레코드 읽기. |
email:send | ctx를 통한 이메일 전송. |
network:request | ctx.http를 통한 아웃바운드 HTTP, allowedHosts로 제한. |
network:request:unrestricted | 모든 호스트로의 아웃바운드 HTTP. network:request 대신 사용. |
hooks.email-transport:register | 이메일 전송 훅 등록. |
hooks.email-events:register | 이메일 라이프사이클 훅 등록. |
hooks.page-fragments:register | page:fragments 훅 등록(네이티브만). |
CLI가 강제하는 두 가지 교차 필드 규칙(에디터의 JSON-Schema 검사에서는 강제되지 않음 — emdash-plugin validate를 실행하세요):
network:request는 비어 있지 않은allowedHosts를 필요로 합니다. 플러그인이 정말로 모든 호스트에 도달해야 하면, 대신network:request:unrestricted를 사용하세요.network:request:unrestricted는allowedHosts가 비어 있을 것을 필요로 합니다 — 무제한 기능이 이미 모든 호스트를 허용하므로, 목록은 모순됩니다.
호스트 패턴은 베어 호스트명입니다(스킴, 경로, 공백 없음). 앞에 *.을 붙이면 하위 도메인을 허용합니다: *.cdn.example.com.
스토리지
컬렉션 이름 → 인덱스 설정의 맵. 컬렉션 이름은 같은 /^[a-z][a-z0-9_]*$/ 규칙을 따릅니다(런타임은 이름을 SQL 테이블 접미사로 사용). 인덱스는 필드 이름 또는 복합 배열입니다. uniqueIndexes도 쿼리 가능합니다 — indexes에 추가로 나열하지 마세요.
"storage": {
"events": { "indexes": ["timestamp", ["collection", "timestamp"]] }
}
관리 화면
선택 사항. 샌드박스 플러그인은 Block Kit을 통해 관리 페이지와 대시보드 위젯을 렌더링합니다. 매니페스트는 어디에 나타나는지만 선언합니다. 플러그인에 관리 UI가 없으면 admin 키를 완전히 생략하세요.
"admin": {
"pages": [{ "path": "/gallery", "label": "갤러리", "icon": "image" }],
"widgets": [{ "id": "recent-uploads", "title": "최근 업로드", "size": "half" }]
}
admin.pages 또는 admin.widgets를 선언하는 플러그인은 src/plugin.ts에서 Block Kit 콘텐츠를 렌더링하는 admin 라우트도 제공해야 합니다 — 스키마가 이를 강제할 수 없지만(라우트 이름은 매니페스트가 아닌 소스에서 탐색됨), 런타임이 확인합니다.
퍼블리셔 피닝
publisher는 게시 아이덴티티를 고정하여 실수로 잘못된 계정으로 플러그인을 게시하는 것을 방지합니다.
첫 번째 성공적인 게시 시, 매니페스트의 publisher가 활성 세션과 일치하면 그대로 유지됩니다. emdash-plugin init으로 스캐폴드를 생성하고 비워 둔 경우, CLI가 활성 세션의 DID를 매니페스트에 다시 씁니다.
다음 예제는 CLI가 쓰는 줄을 보여주며, 가독성을 위해 해결된 핸들이 주석으로 추가되었습니다:
"publisher": "did:plc:abc123def456", // jane.example.com
이후 각 게시 시, CLI는 활성 세션과 고정된 publisher를 DID로 해결하여 비교합니다. 불일치는 MANIFEST_PUBLISHER_MISMATCH로 즉시 실패합니다 — 오버라이드 플래그가 없습니다. 의도적으로 해결하세요:
- 잘못된 세션:
emdash-plugin switch <did>후 다시 게시. - 플러그인을 새 퍼블리셔에게 정식 이전: 매니페스트의
publisher를 편집.
게시 없이 유효성 검사
emdash-plugin validate # ./emdash-plugin.jsonc
emdash-plugin validate path/ # 특정 디렉토리
교차 필드 규칙을 포함한 tsc 스타일 파일:줄:열 진단이 있는 오프라인 스키마 검사. 프리커밋 훅이나 CI 단계에 적합합니다. 중복 키와 알 수 없는 키는 에러입니다(엄격 모드는 "licens" 같은 오타를 잡습니다).
CLI 플래그가 항상 우선
명시적 플래그(--license, --author-name, …)는 둘 다 설정된 경우 매니페스트 값을 덮어씁니다 — CI 오버라이드에 유용합니다. --no-manifest는 매니페스트를 완전히 건너뜁니다(기본 경로에 존재하면 경고하여 퍼블리셔 핀 보안 스토리가 보이도록 합니다).