-
Notifications
You must be signed in to change notification settings - Fork 926
feat(registry-cli): emdash-plugin.jsonc manifest, validate command, publisher pinning #1040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
dc8ab73
feat(registry-cli): emdash-plugin.jsonc manifest, validate command, p…
ascorbic 94171ce
fix(registry-cli): manifest loader hardening + drop in-development wa…
ascorbic 91db987
fix(registry-cli): bounded manifest read + tmpfile cleanup on verify …
ascorbic f565fa3
style: format
emdashbot[bot] c9b2e80
fix(registry-cli): address copilot review feedback
ascorbic a763150
fix(registry-cli): address ask-bonk review feedback
ascorbic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| "@emdash-cms/registry-cli": minor | ||
| --- | ||
|
|
||
| Adds `emdash-plugin.jsonc` manifest support. Plugin authors can now declare profile fields (license, author, security contact, name, description, keywords, repo) once in a hand-edited JSONC file instead of passing them as flags on every publish. The CLI loads `./emdash-plugin.jsonc` automatically; explicit flags still win for CI use. | ||
|
|
||
| New `emdash-registry validate` command checks a manifest against the schema offline with `tsc`-style file:line:column diagnostics. | ||
|
|
||
| The manifest's optional `publisher` field pins the publishing identity. On first successful publish, the CLI writes the active session's DID back to the manifest. Subsequent publishes verify the active session matches the pinned publisher and refuse on mismatch to prevent accidental cross-account publishes. | ||
|
|
||
| JSON Schema for IDE completion ships in the package at `schemas/emdash-plugin.schema.json`; reference it via `"$schema": "./node_modules/@emdash-cms/registry-cli/schemas/emdash-plugin.schema.json"`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
204 changes: 204 additions & 0 deletions
204
packages/registry-cli/schemas/emdash-plugin.schema.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://emdashcms.com/schemas/emdash-plugin.schema.json", | ||
| "title": "EmDash plugin manifest", | ||
| "description": "Hand-authored manifest for publishing a plugin to the EmDash plugin registry. Lives next to the plugin's `package.json` as `emdash-plugin.jsonc`.", | ||
| "type": "object", | ||
| "properties": { | ||
| "$schema": { | ||
| "$ref": "#/$defs/__schema0" | ||
| }, | ||
| "license": { | ||
| "$ref": "#/$defs/__schema1" | ||
| }, | ||
| "publisher": { | ||
| "$ref": "#/$defs/__schema2" | ||
| }, | ||
| "author": { | ||
| "$ref": "#/$defs/__schema3" | ||
| }, | ||
| "authors": { | ||
| "$ref": "#/$defs/__schema8" | ||
| }, | ||
| "security": { | ||
| "$ref": "#/$defs/__schema9" | ||
| }, | ||
| "securityContacts": { | ||
| "$ref": "#/$defs/__schema13" | ||
| }, | ||
| "name": { | ||
| "$ref": "#/$defs/__schema14" | ||
| }, | ||
| "description": { | ||
| "$ref": "#/$defs/__schema15" | ||
| }, | ||
| "keywords": { | ||
| "$ref": "#/$defs/__schema16" | ||
| }, | ||
| "repo": { | ||
| "$ref": "#/$defs/__schema18" | ||
| } | ||
| }, | ||
| "required": [ | ||
| "license" | ||
| ], | ||
| "additionalProperties": false, | ||
| "$defs": { | ||
| "__schema0": { | ||
| "type": "string", | ||
| "description": "Path or URL to the JSON Schema describing this file. Editors use this for completion and validation." | ||
| }, | ||
| "__schema1": { | ||
| "type": "string", | ||
| "minLength": 1, | ||
| "maxLength": 256, | ||
| "title": "License", | ||
| "description": "SPDX license expression (e.g. \"MIT\", \"Apache-2.0\", \"MIT OR Apache-2.0\"). Required on first publish; ignored on subsequent publishes (the existing profile wins).", | ||
| "examples": [ | ||
| "MIT", | ||
| "Apache-2.0", | ||
| "MIT OR Apache-2.0" | ||
| ] | ||
| }, | ||
| "__schema2": { | ||
| "type": "string", | ||
| "title": "Publisher", | ||
| "description": "Atproto DID or handle of the publishing identity. Pinned on first publish to prevent accidental publishes from a different account. DIDs are recommended (durable); handles work but are mutable.", | ||
| "examples": [ | ||
| "did:plc:abc123def456", | ||
| "example.com" | ||
| ] | ||
| }, | ||
| "__schema3": { | ||
| "$ref": "#/$defs/__schema4" | ||
| }, | ||
| "__schema4": { | ||
| "type": "object", | ||
| "properties": { | ||
| "name": { | ||
| "$ref": "#/$defs/__schema5" | ||
| }, | ||
| "url": { | ||
| "$ref": "#/$defs/__schema6" | ||
| }, | ||
| "email": { | ||
| "$ref": "#/$defs/__schema7" | ||
| } | ||
| }, | ||
| "required": [ | ||
| "name" | ||
| ], | ||
| "additionalProperties": false, | ||
| "title": "Author", | ||
| "description": "A single author entry. Mirrors the lexicon's author shape." | ||
| }, | ||
| "__schema5": { | ||
| "type": "string", | ||
| "minLength": 1, | ||
| "maxLength": 256, | ||
| "description": "Display name." | ||
| }, | ||
| "__schema6": { | ||
| "type": "string", | ||
| "maxLength": 1024, | ||
| "format": "uri", | ||
| "description": "Author's homepage or profile URL. Either this or `email` is recommended." | ||
| }, | ||
| "__schema7": { | ||
| "type": "string", | ||
| "maxLength": 256, | ||
| "format": "email", | ||
| "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$", | ||
| "description": "Author's contact email. Either this or `url` is recommended." | ||
| }, | ||
| "__schema8": { | ||
| "minItems": 1, | ||
| "maxItems": 32, | ||
| "type": "array", | ||
| "items": { | ||
| "$ref": "#/$defs/__schema4" | ||
| }, | ||
| "title": "Authors (multiple)", | ||
| "description": "Multi-author form. Mutually exclusive with `author`. Use the singular `author` if there is only one." | ||
| }, | ||
| "__schema9": { | ||
| "$ref": "#/$defs/__schema10" | ||
| }, | ||
| "__schema10": { | ||
| "type": "object", | ||
| "properties": { | ||
| "url": { | ||
| "$ref": "#/$defs/__schema11" | ||
| }, | ||
| "email": { | ||
| "$ref": "#/$defs/__schema12" | ||
| } | ||
| }, | ||
| "additionalProperties": false, | ||
| "title": "Security contact", | ||
| "description": "A single security contact. At least one of `url` or `email` must be present." | ||
| }, | ||
| "__schema11": { | ||
| "type": "string", | ||
| "maxLength": 1024, | ||
| "format": "uri", | ||
| "description": "Security disclosure URL (e.g. a security.txt or vulnerability-reporting page). Either this or `email` is required." | ||
| }, | ||
| "__schema12": { | ||
| "type": "string", | ||
| "maxLength": 256, | ||
| "format": "email", | ||
| "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$", | ||
| "description": "Security contact email. Either this or `url` is required." | ||
| }, | ||
| "__schema13": { | ||
| "minItems": 1, | ||
| "maxItems": 8, | ||
| "type": "array", | ||
| "items": { | ||
| "$ref": "#/$defs/__schema10" | ||
| }, | ||
| "title": "Security contacts (multiple)", | ||
| "description": "Multi-contact form. Mutually exclusive with `security`. Use the singular `security` if there is only one." | ||
| }, | ||
| "__schema14": { | ||
| "type": "string", | ||
| "minLength": 1, | ||
| "maxLength": 1024, | ||
| "title": "Display name", | ||
| "description": "Human-readable name shown in directory listings. Defaults to the plugin's `id` when omitted." | ||
| }, | ||
| "__schema15": { | ||
| "type": "string", | ||
| "minLength": 1, | ||
| "maxLength": 1024, | ||
| "title": "Description", | ||
| "description": "Short description (<= 140 graphemes by FAIR convention). Aggregators may truncate longer values when displaying in compact lists." | ||
| }, | ||
| "__schema16": { | ||
| "maxItems": 5, | ||
| "type": "array", | ||
| "items": { | ||
| "$ref": "#/$defs/__schema17" | ||
| }, | ||
| "title": "Keywords", | ||
| "description": "Search keywords (<= 5 entries, FAIR convention)." | ||
| }, | ||
| "__schema17": { | ||
| "type": "string", | ||
| "minLength": 1, | ||
| "maxLength": 128 | ||
| }, | ||
| "__schema18": { | ||
| "type": "string", | ||
| "maxLength": 1024, | ||
| "format": "uri", | ||
| "pattern": "^https:\\/\\/", | ||
| "title": "Source repository", | ||
| "description": "HTTPS URL of the plugin's source repository. Surfaced in registry listings.", | ||
| "examples": [ | ||
| "https://github.com/emdash-cms/plugin-gallery" | ||
| ] | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /** | ||
| * Generate the JSON Schema for `emdash-plugin.jsonc` from the Zod source | ||
| * of truth in `src/manifest/schema.ts`. | ||
| * | ||
| * Run via `pnpm gen-schema` (wired into `build`). The output is committed | ||
| * to `schemas/emdash-plugin.schema.json` and shipped in the package's | ||
| * `files` array so users can reference it via: | ||
| * | ||
| * "$schema": "./node_modules/@emdash-cms/registry-cli/schemas/emdash-plugin.schema.json" | ||
| * | ||
| * Drift between the Zod schema and the committed JSON Schema is caught | ||
| * by the snapshot test in `tests/schema.test.ts`. | ||
| * | ||
| * Why a separate script rather than emitting on build: | ||
| * | ||
| * - The schema is part of the package's user-facing surface; checking | ||
| * it into git makes diffs visible in PR review (a field rename in | ||
| * Zod produces a tracked diff in the JSON Schema too). | ||
| * - Tests can run without first building. The schema file exists | ||
| * at-rest; the test compares Zod's current output to it. | ||
| * | ||
| * Runs under Node's native TypeScript stripping (Node 22+). No `tsx` or | ||
| * `ts-node` dependency. | ||
| */ | ||
|
|
||
| import { mkdir, writeFile } from "node:fs/promises"; | ||
| import { dirname, resolve } from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| import { z } from "zod"; | ||
|
|
||
| import { ManifestSchema } from "../src/manifest/schema.ts"; | ||
|
|
||
| const HERE = dirname(fileURLToPath(import.meta.url)); | ||
| const OUT_PATH = resolve(HERE, "..", "schemas", "emdash-plugin.schema.json"); | ||
|
|
||
| // zod 4's native JSON Schema emitter. `target: "draft-2020-12"` is what | ||
| // every modern JSON Schema editor (VS Code's built-in schema store, | ||
| // IntelliJ's JSON LSP) supports out of the box. | ||
| const jsonSchema = z.toJSONSchema(ManifestSchema, { | ||
| target: "draft-2020-12", | ||
| // Use full reuse rather than inline-everything: smaller file, easier | ||
| // diffs when a single subschema changes. | ||
| reused: "ref", | ||
| }); | ||
|
|
||
| const document = { | ||
| $schema: "https://json-schema.org/draft/2020-12/schema", | ||
| $id: "https://emdashcms.com/schemas/emdash-plugin.schema.json", | ||
| title: "EmDash plugin manifest (emdash-plugin.jsonc)", | ||
| description: | ||
| "Authoring format for publishing plugins to the EmDash plugin registry. Translated to the on-wire atproto record format at publish time. See https://github.com/emdash-cms/emdash/issues/1028.", | ||
| ...jsonSchema, | ||
| }; | ||
|
|
||
| const serialised = `${JSON.stringify(document, null, "\t")}\n`; | ||
|
|
||
| await mkdir(dirname(OUT_PATH), { recursive: true }); | ||
| await writeFile(OUT_PATH, serialised, "utf8"); | ||
| process.stdout.write(`Wrote ${OUT_PATH}\n`); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.