Skip to content

cli: prompt for plugin format and align scaffolds with docs#902

Merged
ascorbic merged 2 commits intomainfrom
cli/plugin-init-format-prompt
May 3, 2026
Merged

cli: prompt for plugin format and align scaffolds with docs#902
ascorbic merged 2 commits intomainfrom
cli/plugin-init-format-prompt

Conversation

@ascorbic
Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic commented May 3, 2026

What does this PR do?

emdash plugin init previously always scaffolded the standard (sandboxed) format, with --native as the only escape hatch. Users who didn't already know the two formats existed had no signal that they had a choice.

This PR:

  • Adds an interactive prompt when stdout is a TTY and neither --format nor --native is set. Options are Sandboxed (default; "runs in an isolated sandbox; safe to install from the marketplace") and Native ("full runtime access; install from npm").
  • Adds --format=sandboxed / --format=native to skip the prompt explicitly. --native is kept as a shortcut for --format=native. Non-TTY runs continue to default to sandboxed, so scripted usage is unchanged.
  • Rewrites both scaffolds to match the canonical patterns from docs/src/content/docs/plugins/creating-plugins/your-first-plugin.mdx and creating-native-plugins/your-first-native-plugin.mdx:
    • package.json ships a tsdown build (build/dev/typecheck scripts), main + conditional exports pointing at dist/, files: ["dist"], keywords, license: "MIT", and devDependencies for emdash/tsdown/typescript.
    • The sandboxed src/sandbox-entry.ts drops event: any, defines a typed ContentSaveEvent, writes to ctx.storage.events, and demonstrates a recent route using the standard two-arg (routeCtx, ctx) signature.
    • The native src/index.ts adds an Options interface, forwards the options through createPlugin, declares storage, demonstrates an options-aware hook, and exposes a recent route using the native single-arg (ctx) signature.
  • Fixes a latent bug in both scaffolds: descriptor id was being set to the raw pluginName, which fails the runtime regex /^[a-z][a-z0-9_-]*$/ when given a scoped name like @org/foo. It now uses the stripped slug.
  • Updates the next-steps printout to add pnpm build and skip emdash plugin validate for native plugins (validate is part of the marketplace bundle flow, which only applies to sandboxed plugins).

Closes #

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.7 (Claude Code)

Screenshots / test output

Smoke-tested locally by scaffolding both formats with a scoped name (@my-org/test-sandboxed and @my-org/test-native) and verifying the generated package.json, tsconfig.json, and src/*.ts files match the canonical examples from the docs and produce a runtime-valid descriptor id.

$ emdash plugin init --dir ./test-sandboxed --name @my-org/test-sandboxed --format sandboxed
◐ Scaffolding sandboxed plugin: @my-org/test-sandboxed
✔ Plugin scaffolded in ./test-sandboxed
ℹ Next steps:
ℹ   1. cd ./test-sandboxed
ℹ   2. pnpm install
ℹ   3. Edit src/sandbox-entry.ts to add hooks and routes
ℹ   4. pnpm build
ℹ   5. emdash plugin validate --dir .

`emdash plugin init` now asks whether to scaffold a sandboxed or native
plugin when run interactively. `--format=sandboxed`, `--format=native`,
and the existing `--native` flag all skip the prompt; non-TTY runs
default to sandboxed.

Both scaffolds now match the canonical patterns from the plugin docs:
they build to `dist/` via tsdown, declare a sample storage collection,
and demonstrate a hook plus an API route. The sandboxed entry uses an
explicitly typed `ContentSaveEvent`; the native entry forwards options
through `createPlugin`. The descriptor `id` is derived from the slug
instead of the full scoped package name, so scoped names like
`@org/my-plugin` produce a runtime-valid id.
Copilot AI review requested due to automatic review settings May 3, 2026 18:29
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 3, 2026

🦋 Changeset detected

Latest commit: 448c232

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/admin Minor
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 3, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-perf-coordinator 448c232 May 03 2026, 07:04 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 3, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-i18n 448c232 May 03 2026, 07:04 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 3, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 448c232 May 03 2026, 07:05 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 3, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 448c232 May 03 2026, 07:06 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 3, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 448c232 May 03 2026, 07:06 PM

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 3, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@902

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@902

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@902

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@902

emdash

npm i https://pkg.pr.new/emdash@902

create-emdash

npm i https://pkg.pr.new/create-emdash@902

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@902

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@902

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@902

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@902

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@902

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@902

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@902

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@902

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@902

commit: 448c232

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates emdash plugin init so new plugin authors can choose between sandboxed and native plugin formats up front, and refreshes the generated scaffolds/build metadata to better match current plugin packaging patterns in the codebase.

Changes:

  • Adds --format support plus an interactive format prompt for TTY runs, while keeping non-interactive defaults unchanged.
  • Reworks both generated plugin templates to emit dist/-based packages with tsdown/typecheck scripts and richer sample hook/route/storage code.
  • Fixes scaffolded descriptor IDs for scoped package names by deriving plugin IDs from the unscoped slug.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
packages/core/src/cli/commands/plugin-init.ts Adds format selection logic and rewrites the generated sandboxed/native plugin scaffolds.
.changeset/plugin-init-format-prompt.md Documents the new prompt behavior, scaffold updates, and scoped-name ID fix for release notes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

\tenabled?: boolean;
}

export function ${fnName}Plugin(options: ${typeName}Options = {}): PluginDescriptor {
\t\tformat: "standard",
\t\tentrypoint: "${pluginName}/sandbox",

\t\tcapabilities: [],
\troutes: {
\t\trecent: {
\t\t\thandler: async (_routeCtx, ctx: PluginContext) => {
\t\t\t\tconst result = await ctx.storage.events.query({ limit: 10 });
\treturn definePlugin({
\t\tid: "${pluginName}",
\t\tid: "${slug}",
\t\tversion: "0.1.0",
\t\troutes: {
\t\t\trecent: {
\t\t\t\thandler: async (ctx) => {
\t\t\t\t\tconst result = await ctx.storage.events.query({ limit: 10 });
Comment on lines +120 to +127
if (formatArg) {
const normalized = formatArg.toLowerCase();
if (normalized === "native") return "native";
if (normalized === "sandboxed" || normalized === "standard") return "standard";
consola.error(`Invalid --format "${formatArg}". Use "sandboxed" or "native".`);
process.exit(1);
}
if (nativeFlag) return "native";
- Both scaffolded hooks declare `content:read` so HookPipeline does not
  silently skip them (afterSave requires `content:read`).
- The native descriptor factory now returns
  `PluginDescriptor<${Type}Options>` so the options type survives,
  matching first-party plugins like ai-moderation and embeds.
- The `recent` routes pass `orderBy: { ...: "desc" }`; storage queries
  default to `created_at ASC`, which returned the oldest events.
- `--native --format=sandboxed` (or any conflicting pair) now errors
  with exit 1 instead of silently picking the format flag.
@ascorbic ascorbic merged commit 7e32092 into main May 3, 2026
36 checks passed
@ascorbic ascorbic deleted the cli/plugin-init-format-prompt branch May 3, 2026 20:11
@emdashbot emdashbot Bot mentioned this pull request May 3, 2026
aklusa022 pushed a commit to Zenfora/emdash that referenced this pull request May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants