refactor(plugin-cli): validate probed plugin shape with Zod#1145
Conversation
Replaces the hand-rolled `if`/`throw` chain in `probeAndAssemble`
with a single Zod schema (`ProbedDefaultSchema` in
`probe-schema.ts`) and translates Zod issues back into the same
`BuildPipelineError` format plugin authors already see. Removes the
last 9 `no-unsafe-type-assertion` warnings in `pipeline.ts` and
clears the file's lint debt.
Key design choices:
- `normaliseEntry` (preprocess) maps bare-function hooks/routes to
`{ handler }` so the schema only validates one shape per entry.
- Only plain objects (prototype is `Object.prototype` or null) reach
per-field validation; exotic objects (Date, RegExp, Promise, class
instances) are reduced to `null` so the entry-shape error fires
instead of a misleading 'missing handler' message.
- Non-record outer collections (`hooks: []`, `hooks: null`, etc.)
coerce to an empty record so the build doesn't fail on the kind of
shapes the original `Object.keys` iteration silently no-op'd on.
- A `safeStringify` helper survives BigInts, cycles, functions, and
symbols when embedding the offending value in the error message.
New tests in `probe-schema.test.ts` (32 cases) lock in the error-
message contract: code, exact message strings for the documented
field violations, and behavioural choices around exotic values.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | ad7897e | May 22 2026, 01:26 PM |
🦋 Changeset detectedLatest commit: ad7897e The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-perf-coordinator | ad7897e | May 22 2026, 01:24 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | ad7897e | May 22 2026, 01:26 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-i18n | ad7897e | May 22 2026, 01:24 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | ad7897e | May 22 2026, 01:25 PM |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Scope checkThis PR changes 729 lines across 5 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
There was a problem hiding this comment.
Pull request overview
Refactors @emdash-cms/plugin-cli’s probe-time validation of a sandboxed plugin’s default export by replacing manual runtime checks in probeAndAssemble with Zod-based parsing/normalization, while preserving (and testing) the existing user-facing error message contract.
Changes:
- Added
probe-schema.tswith Zod schemas + preprocessors to normalize/validate probed hook/route entries. - Reworked
probeAndAssembleto useparseProbedDefault()and added safer error-value rendering (safeStringify) for previously-crashy values (e.g., BigInt/cycles). - Added a focused test suite (
probe-schema.test.ts) to lock in parsing behavior and error message strings; added a changeset.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Updates lockfile (adds kysely@0.27.6 snapshot/entry). |
| packages/plugin-cli/tests/probe-schema.test.ts | Adds coverage for default-export parsing, normalization, and error message contracts (incl. safeStringify cases). |
| packages/plugin-cli/src/build/probe-schema.ts | Introduces Zod schemas and preprocessors for probed plugin default export validation. |
| packages/plugin-cli/src/build/pipeline.ts | Replaces hand-rolled validation with Zod parse + error translation, and adds safeStringify + assembly helpers. |
| .changeset/slick-peaches-wear.md | Documents the patch-level change for the plugin CLI release notes. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * - Anything else (built-ins like `Date`, `RegExp`, `Promise`, `Map`, | ||
| * author-defined class instances, primitives) is reduced to `null` | ||
| * so the schema produces a single "expected object" issue at the | ||
| * entry root. Without this, the schema would reach into the wrong- | ||
| * shaped object for `handler` and report a misleading "missing | ||
| * handler" issue. |
| Refactors the build pipeline's runtime validation of the probed plugin's | ||
| default export to use a Zod schema. Error messages keep the same format | ||
| (`hook "X" must be a function or { handler, ... }`, `hook "X" has | ||
| invalid FIELD VALUE (...)`). Exotic-object entries (Date, RegExp, | ||
| Promise, class instances) now produce the wrong-shape error instead of | ||
| falling through to a misleading "missing handler" error. BigInt / | ||
| cyclic-object / function / symbol field values are rendered safely in |
What does this PR do?
Refactors
probeAndAssembleinpackages/plugin-cli/src/build/pipeline.tsto validate the imported default export of a probed plugin with a Zod schema instead of a hand-rolledif/throwchain. Removes the last 9no-unsafe-type-assertionwarnings flagged by oxlint in this file (the follow-up to #1140).Design
packages/plugin-cli/src/build/probe-schema.ts. DefinesProbedDefaultSchemaplusHookEntrySchema/RouteEntrySchema. AnormaliseEntrypreprocess maps the bare-function form ("content:beforeSave": async (e, ctx) => {...}) to the config form ({ handler: fn }) so the schema only has to validate one shape per entry.parseProbedDefault(pluginEntry, definition)inpipeline.tsruns the schema and translates the first Zod issue back to aBuildPipelineErrormatching the existing message format. Exported for test access.safeStringifysurvives valuesJSON.stringifycan't serialise (BigInt, cycles, functions, symbols) by falling back todescribeShape.Behaviour preserved
BuildPipelineErrorcode (INVALID_PLUGIN_FORMAT) and message templates unchanged for documented field violations ("continue" or "abort","finite number","non-negative finite number").definition.hooks/definition.routes).hooks: [],hooks: null, etc.) still silently produce no harvested entries instead of failing the build.Behaviour changed (deliberate)
Date,RegExp,Promise, class instances,Map, ...) now produce the standard "must be a function or { handler, ... }. Got object." message rather than a misleading "missing handler" message reached by the schema descending into the wrong-shaped object.JSON.stringifywith a rawTypeError; they now render throughsafeStringify(10nfor BigInts,object/function/symbolfor unrepresentable values).Tests
Adds
packages/plugin-cli/tests/probe-schema.test.tswith 32 cases covering valid shapes, hook validation errors, route validation errors, non-record collection coercion, and the safeStringify edge cases. The full plugin-cli suite (296 tests) passes.Adversarial review
Two passes through the adversarial-reviewer sub-agent. First pass surfaced the BigInt crash and the misleading exotic-object error; both fixed. Second pass surfaced the Promise-as-entry Zod TypeError; fixed in the same pass by tightening
normaliseEntryto also reject objects with non-Object.prototypeprototypes. Defensivetry/catcharoundsafeParseleft in place as belt-and-suspenders.Closes #
Type of change
(Mostly refactor; the exotic-object and BigInt error-message behaviour changes are deliberate improvements covered by new tests.)
Checklist
pnpm typecheckpassespnpm lintpasses (no warnings introduced in plugin-cli; pre-existing warnings inpackages/workerd/from feat: Node.js plugin isolation via workerd sandbox #426 are out of scope)pnpm testpasses (@emdash-cms/plugin-cli: 296 tests)pnpm formathas been runprobe-schema.test.ts, 32 new cases)@emdash-cms/plugin-clipatchAI-generated code disclosure
Screenshots / test output
n/a