Skip to content

ci: release#1123

Open
emdashbot[bot] wants to merge 1 commit into
mainfrom
changeset-release/main
Open

ci: release#1123
emdashbot[bot] wants to merge 1 commit into
mainfrom
changeset-release/main

Conversation

@emdashbot
Copy link
Copy Markdown
Contributor

@emdashbot emdashbot Bot commented May 20, 2026

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@emdash-cms/admin@0.15.0

Minor Changes

  • #1146 11b3001 Thanks @MohamedH1998! - Adds first-class i18n support for bylines, mirroring the row-per-locale model already used by menus and taxonomies (PR feat: i18n menus and taxonomies #916, migrations 036).

    Schema (migration 040)

    _emdash_bylines gains two columns:

    • localeTEXT NOT NULL DEFAULT 'en'. Every row now belongs to exactly one locale.
    • translation_groupTEXT NOT NULL. Shared across every locale variant of a single byline identity. The anchor row's translation_group equals its id; siblings inherit it.

    A partial unique index idx_bylines_group_locale_unique enforces one row per (translation_group, locale). The pre-existing (slug) unique index becomes (slug, locale) to allow the same slug across locales.

    Existing rows are backfilled to the configured defaultLocale (or 'en' if i18n isn't configured) with translation_group = id. Monolingual sites see no functional change; multilingual sites continue rendering the same byline data at the default locale until editors create translations.

    Credit hydration: strict per-locale

    _emdash_content_bylines.byline_id now stores the byline's translation_group, not its row id. When an entry is rendered, credits are filtered by joining the junction against the byline sibling whose locale matches the entry's locale. If no sibling exists at the entry's locale, the credit hydrates as empty — there is no fallback to other locales' bios.

    Author-inferred bylines (where an entry has no explicit credits but its author is linked to a byline) still fall back per-locale and respect the strictness gate: an entry with explicit credits at any locale will not infer from the author even if the explicit credits don't resolve at the rendering locale.

    This is a deliberate behavior change for multilingual sites. The motivation is correctness: chain-walking credits across locales renders the wrong-language bio on translated entries.

    The "explicit credit suppresses author fallback" check reads primary_byline_id directly from the content row — set by setContentBylines iff junction rows exist, backfilled by migration 040 for pre-existing rows. No separate probe against _emdash_content_bylines is needed at hydration time; the column is folded into the single per-entry context fetch (author_id + primary_byline_id in one query). Both monolingual and multilingual sites get the same query count.

    Identity lookups: chain-walk

    getBylineBySlug(slug, { locale }) walks the configured fallback chain (resolveLocaleChain), like getMenu and getTerm. Author pages for un-translated bylines still render an identity rather than 404'ing. This is conceptually distinct from credit hydration and runs through requestCached for per-render dedupe.

    Admin

    • TranslationsPanel in the bylines editor lists every configured locale with Edit / Translate buttons. The Translate action POSTs to the new POST /_emdash/api/admin/bylines/:id/translations endpoint.
    • LocaleSwitcher on /bylines filters the list strictly to one locale. Cross-locale navigation via TranslationsPanel routes through /bylines?locale=….
    • The byline picker on the content editor is locale-pinned to the entry's locale. Editors only see bylines that will actually hydrate at the entry's locale.
    • The byline credit empty state on a locale with no bylines yet shows a CTA linking to /bylines?locale=… for inline creation.
    • Translating an entry (POST /content/:collection with translationOf) calls copyContentBylines to inherit the source's credits — these resolve at the new entry's locale via the strict-hydration model, so credits "follow" the content across translations once sibling bylines exist.

    API additions

    • GET /_emdash/api/admin/bylines/:id/translations — list every sibling row sharing a translation_group.
    • POST /_emdash/api/admin/bylines/:id/translations — create a sibling at a target locale. Body defaults (slug, displayName, websiteUrl, avatar) inherit from the source.
    • POST /_emdash/api/admin/bylines accepts translationOf + locale to create a sibling in one call.
    • GET /_emdash/api/admin/bylines?locale=… filters strictly.
    • BylineSummary gains locale: string and translationGroup: string | null (additive — existing consumers ignore the new fields).

    Permissions

    Two new entries on @emdash-cms/auth:

    • bylines:read — minimum SUBSCRIBER.
    • bylines:manage — minimum EDITOR.

    All byline routes (list, get, update, delete, translations) now check these instead of content:read / Role.EDITOR. Role thresholds are unchanged, so existing users see no permission differences. Custom RBAC configurations that bind to the old strings should add the new permission names.

    Repository

    • BylineRepository is strict per-locale: findMany, findBySlug, findById accept an optional locale and return rows matching that locale (or all locales when omitted, for the manager view).
    • New methods: listTranslations(id), findByTranslationGroup(group), copyContentBylines(collection, fromId, toId).
    • setContentBylines deduplicates by translation_group after resolving wire row ids, so passing two sibling row ids of the same identity collapses to one credit row.
    • delete is sibling-aware: removing one locale variant leaves siblings standing.

    Notable trade-offs

    • Strict hydration over chain-walking for credits. Chain-walking would render mismatched-language bios on translated content. The honest answer is to show nothing rather than the wrong thing; the picker tells editors which bylines will resolve at the entry's locale, and the empty-state CTA makes creating a sibling a one-click flow.
    • Schema is row-per-locale, not a separate byline_translations side-table. Matches the existing content / menu / taxonomy convention so query patterns and indexes are consistent across the codebase.
  • #1176 fae97ee Thanks @ascorbic! - Code blocks in the rich text editor now have an inline language picker. Hover over any code block to reveal a chip in the corner; click it to enter a language (free-form input with curated suggestions for ~30 common languages including TypeScript, Python, Bash, Rust, Astro, SQL, and more). Aliases resolve automatically -- typing ts stores typescript, c++ stores cpp, etc. The existing markdown shortcut (typing ```html followed by a space or Enter) continues to pre-populate the language. The chosen language persists on the Portable Text language field and is emitted as a language-{id} class on the rendered <pre> so frontend syntax highlighters can pick it up. The visual (in-place) editor gets the same picker UI.

  • #1114 9a30607 Thanks @ascorbic! - Plugins installed from the experimental registry can now be uninstalled and updated from the admin, the same way marketplace plugins always could. The "uninstall is not yet available for registry plugins" placeholder is gone — registry plugin rows now show the same Uninstall and Update buttons.

    The Plugins page's "updates available" indicator now covers registry plugins too. If the registry aggregator is unreachable, marketplace updates still load (and vice versa).

    Updates that need newly-declared permissions, or that newly expose a public (unauthenticated) route, prompt for re-consent before installing the new version — matching the gate that marketplace updates already have.

  • #1125 d0ff94b Thanks @ascorbic! - Adds a version picker to the registry plugin detail page. Older releases of a registry-hosted plugin are now selectable from a dropdown next to the Install button, and the displayed version, indexed date, permissions, and source link swap to match the selected release. Pre-release versions (e.g. 1.0.0-alpha.1) are flagged with a "Pre-release" badge so admins can spot them before installing. Versions still inside the configured minimum-release-age holdback remain visible in the dropdown but stay non-installable until they age into the window.

Patch Changes

  • #1177 b9cc08e Thanks @ascorbic! - Bumps @cloudflare/kumo from 1.16 to 2.3. Two internal call sites picked up breaking API changes from Kumo 2.0: Collapsible is now a compound component (Collapsible.Root / .DefaultTrigger / .DefaultPanel instead of <Collapsible label=...>), used by the accordion block; and ChartPalette.color() was renamed to ChartPalette.categorical() in the chart block. No public API changes -- consumers see identical behaviour. Tests in @emdash-cms/admin that asserted on Button's native title attribute now read aria-label instead, because Kumo 2 wraps <Button title> in a Tooltip popup rather than setting the DOM attribute.

  • #1119 393dd26 Thanks @adentdk! - Fixes auto-save not detecting plugin block field changes. When editing an existing block's attributes via the Block Kit modal, the change now correctly triggers TipTap's onUpdate callback, propagating through to the auto-save dirty detection.

  • Updated dependencies [cf3c706, b9cc08e]:

    • @emdash-cms/registry-client@0.2.0
    • @emdash-cms/blocks@0.15.0

@emdash-cms/auth@0.15.0

Minor Changes

  • #1146 11b3001 Thanks @MohamedH1998! - Adds first-class i18n support for bylines, mirroring the row-per-locale model already used by menus and taxonomies (PR feat: i18n menus and taxonomies #916, migrations 036).

    Schema (migration 040)

    _emdash_bylines gains two columns:

    • localeTEXT NOT NULL DEFAULT 'en'. Every row now belongs to exactly one locale.
    • translation_groupTEXT NOT NULL. Shared across every locale variant of a single byline identity. The anchor row's translation_group equals its id; siblings inherit it.

    A partial unique index idx_bylines_group_locale_unique enforces one row per (translation_group, locale). The pre-existing (slug) unique index becomes (slug, locale) to allow the same slug across locales.

    Existing rows are backfilled to the configured defaultLocale (or 'en' if i18n isn't configured) with translation_group = id. Monolingual sites see no functional change; multilingual sites continue rendering the same byline data at the default locale until editors create translations.

    Credit hydration: strict per-locale

    _emdash_content_bylines.byline_id now stores the byline's translation_group, not its row id. When an entry is rendered, credits are filtered by joining the junction against the byline sibling whose locale matches the entry's locale. If no sibling exists at the entry's locale, the credit hydrates as empty — there is no fallback to other locales' bios.

    Author-inferred bylines (where an entry has no explicit credits but its author is linked to a byline) still fall back per-locale and respect the strictness gate: an entry with explicit credits at any locale will not infer from the author even if the explicit credits don't resolve at the rendering locale.

    This is a deliberate behavior change for multilingual sites. The motivation is correctness: chain-walking credits across locales renders the wrong-language bio on translated entries.

    The "explicit credit suppresses author fallback" check reads primary_byline_id directly from the content row — set by setContentBylines iff junction rows exist, backfilled by migration 040 for pre-existing rows. No separate probe against _emdash_content_bylines is needed at hydration time; the column is folded into the single per-entry context fetch (author_id + primary_byline_id in one query). Both monolingual and multilingual sites get the same query count.

    Identity lookups: chain-walk

    getBylineBySlug(slug, { locale }) walks the configured fallback chain (resolveLocaleChain), like getMenu and getTerm. Author pages for un-translated bylines still render an identity rather than 404'ing. This is conceptually distinct from credit hydration and runs through requestCached for per-render dedupe.

    Admin

    • TranslationsPanel in the bylines editor lists every configured locale with Edit / Translate buttons. The Translate action POSTs to the new POST /_emdash/api/admin/bylines/:id/translations endpoint.
    • LocaleSwitcher on /bylines filters the list strictly to one locale. Cross-locale navigation via TranslationsPanel routes through /bylines?locale=….
    • The byline picker on the content editor is locale-pinned to the entry's locale. Editors only see bylines that will actually hydrate at the entry's locale.
    • The byline credit empty state on a locale with no bylines yet shows a CTA linking to /bylines?locale=… for inline creation.
    • Translating an entry (POST /content/:collection with translationOf) calls copyContentBylines to inherit the source's credits — these resolve at the new entry's locale via the strict-hydration model, so credits "follow" the content across translations once sibling bylines exist.

    API additions

    • GET /_emdash/api/admin/bylines/:id/translations — list every sibling row sharing a translation_group.
    • POST /_emdash/api/admin/bylines/:id/translations — create a sibling at a target locale. Body defaults (slug, displayName, websiteUrl, avatar) inherit from the source.
    • POST /_emdash/api/admin/bylines accepts translationOf + locale to create a sibling in one call.
    • GET /_emdash/api/admin/bylines?locale=… filters strictly.
    • BylineSummary gains locale: string and translationGroup: string | null (additive — existing consumers ignore the new fields).

    Permissions

    Two new entries on @emdash-cms/auth:

    • bylines:read — minimum SUBSCRIBER.
    • bylines:manage — minimum EDITOR.

    All byline routes (list, get, update, delete, translations) now check these instead of content:read / Role.EDITOR. Role thresholds are unchanged, so existing users see no permission differences. Custom RBAC configurations that bind to the old strings should add the new permission names.

    Repository

    • BylineRepository is strict per-locale: findMany, findBySlug, findById accept an optional locale and return rows matching that locale (or all locales when omitted, for the manager view).
    • New methods: listTranslations(id), findByTranslationGroup(group), copyContentBylines(collection, fromId, toId).
    • setContentBylines deduplicates by translation_group after resolving wire row ids, so passing two sibling row ids of the same identity collapses to one credit row.
    • delete is sibling-aware: removing one locale variant leaves siblings standing.

    Notable trade-offs

    • Strict hydration over chain-walking for credits. Chain-walking would render mismatched-language bios on translated content. The honest answer is to show nothing rather than the wrong thing; the picker tells editors which bylines will resolve at the entry's locale, and the empty-state CTA makes creating a sibling a one-click flow.
    • Schema is row-per-locale, not a separate byline_translations side-table. Matches the existing content / menu / taxonomy convention so query patterns and indexes are consistent across the codebase.

Patch Changes

  • #1139 88f544d Thanks @ask-bonk! - Upgrades kysely to ^0.29.0 (was ^0.27.0) to resolve three high-severity advisories fixed in >=0.28.17:

    Also updates import paths for Migrator and Migration types to kysely/migration to comply with kysely 0.29 export changes.

emdash@0.15.0

Minor Changes

  • #426 02ed8ba Thanks @BenjaminPrice! - Adds workerd-based plugin sandboxing for Node.js deployments.

    • emdash: Adds isHealthy() to SandboxRunner interface, SandboxUnavailableError class, sandbox: false config option, mediaStorage field on SandboxOptions, and exports createHttpAccess/createUnrestrictedHttpAccess/PluginStorageRepository/UserRepository/OptionsRepository for platform adapters.
    • @emdash-cms/cloudflare: Implements isHealthy() on CloudflareSandboxRunner. Fixes storageQuery() and storageCount() to honor where, orderBy, and cursor options (previously ignored, causing infinite pagination loops and incorrect filtered counts). Adds storageConfig to PluginBridgeProps so PluginStorageRepository can use declared indexes.
    • @emdash-cms/sandbox-workerd: New package. WorkerdSandboxRunner for production (workerd child process + capnp config + authenticated HTTP backing service) and MiniflareDevRunner for development.
  • #1146 11b3001 Thanks @MohamedH1998! - Adds first-class i18n support for bylines, mirroring the row-per-locale model already used by menus and taxonomies (PR feat: i18n menus and taxonomies #916, migrations 036).

    Schema (migration 040)

    _emdash_bylines gains two columns:

    • localeTEXT NOT NULL DEFAULT 'en'. Every row now belongs to exactly one locale.
    • translation_groupTEXT NOT NULL. Shared across every locale variant of a single byline identity. The anchor row's translation_group equals its id; siblings inherit it.

    A partial unique index idx_bylines_group_locale_unique enforces one row per (translation_group, locale). The pre-existing (slug) unique index becomes (slug, locale) to allow the same slug across locales.

    Existing rows are backfilled to the configured defaultLocale (or 'en' if i18n isn't configured) with translation_group = id. Monolingual sites see no functional change; multilingual sites continue rendering the same byline data at the default locale until editors create translations.

    Credit hydration: strict per-locale

    _emdash_content_bylines.byline_id now stores the byline's translation_group, not its row id. When an entry is rendered, credits are filtered by joining the junction against the byline sibling whose locale matches the entry's locale. If no sibling exists at the entry's locale, the credit hydrates as empty — there is no fallback to other locales' bios.

    Author-inferred bylines (where an entry has no explicit credits but its author is linked to a byline) still fall back per-locale and respect the strictness gate: an entry with explicit credits at any locale will not infer from the author even if the explicit credits don't resolve at the rendering locale.

    This is a deliberate behavior change for multilingual sites. The motivation is correctness: chain-walking credits across locales renders the wrong-language bio on translated entries.

    The "explicit credit suppresses author fallback" check reads primary_byline_id directly from the content row — set by setContentBylines iff junction rows exist, backfilled by migration 040 for pre-existing rows. No separate probe against _emdash_content_bylines is needed at hydration time; the column is folded into the single per-entry context fetch (author_id + primary_byline_id in one query). Both monolingual and multilingual sites get the same query count.

    Identity lookups: chain-walk

    getBylineBySlug(slug, { locale }) walks the configured fallback chain (resolveLocaleChain), like getMenu and getTerm. Author pages for un-translated bylines still render an identity rather than 404'ing. This is conceptually distinct from credit hydration and runs through requestCached for per-render dedupe.

    Admin

    • TranslationsPanel in the bylines editor lists every configured locale with Edit / Translate buttons. The Translate action POSTs to the new POST /_emdash/api/admin/bylines/:id/translations endpoint.
    • LocaleSwitcher on /bylines filters the list strictly to one locale. Cross-locale navigation via TranslationsPanel routes through /bylines?locale=….
    • The byline picker on the content editor is locale-pinned to the entry's locale. Editors only see bylines that will actually hydrate at the entry's locale.
    • The byline credit empty state on a locale with no bylines yet shows a CTA linking to /bylines?locale=… for inline creation.
    • Translating an entry (POST /content/:collection with translationOf) calls copyContentBylines to inherit the source's credits — these resolve at the new entry's locale via the strict-hydration model, so credits "follow" the content across translations once sibling bylines exist.

    API additions

    • GET /_emdash/api/admin/bylines/:id/translations — list every sibling row sharing a translation_group.
    • POST /_emdash/api/admin/bylines/:id/translations — create a sibling at a target locale. Body defaults (slug, displayName, websiteUrl, avatar) inherit from the source.
    • POST /_emdash/api/admin/bylines accepts translationOf + locale to create a sibling in one call.
    • GET /_emdash/api/admin/bylines?locale=… filters strictly.
    • BylineSummary gains locale: string and translationGroup: string | null (additive — existing consumers ignore the new fields).

    Permissions

    Two new entries on @emdash-cms/auth:

    • bylines:read — minimum SUBSCRIBER.
    • bylines:manage — minimum EDITOR.

    All byline routes (list, get, update, delete, translations) now check these instead of content:read / Role.EDITOR. Role thresholds are unchanged, so existing users see no permission differences. Custom RBAC configurations that bind to the old strings should add the new permission names.

    Repository

    • BylineRepository is strict per-locale: findMany, findBySlug, findById accept an optional locale and return rows matching that locale (or all locales when omitted, for the manager view).
    • New methods: listTranslations(id), findByTranslationGroup(group), copyContentBylines(collection, fromId, toId).
    • setContentBylines deduplicates by translation_group after resolving wire row ids, so passing two sibling row ids of the same identity collapses to one credit row.
    • delete is sibling-aware: removing one locale variant leaves siblings standing.

    Notable trade-offs

    • Strict hydration over chain-walking for credits. Chain-walking would render mismatched-language bios on translated content. The honest answer is to show nothing rather than the wrong thing; the picker tells editors which bylines will resolve at the entry's locale, and the empty-state CTA makes creating a sibling a one-click flow.
    • Schema is row-per-locale, not a separate byline_translations side-table. Matches the existing content / menu / taxonomy convention so query patterns and indexes are consistent across the codebase.
  • #1176 fae97ee Thanks @ascorbic! - Code blocks in the rich text editor now have an inline language picker. Hover over any code block to reveal a chip in the corner; click it to enter a language (free-form input with curated suggestions for ~30 common languages including TypeScript, Python, Bash, Rust, Astro, SQL, and more). Aliases resolve automatically -- typing ts stores typescript, c++ stores cpp, etc. The existing markdown shortcut (typing ```html followed by a space or Enter) continues to pre-populate the language. The chosen language persists on the Portable Text language field and is emitted as a language-{id} class on the rendered <pre> so frontend syntax highlighters can pick it up. The visual (in-place) editor gets the same picker UI.

  • #1114 9a30607 Thanks @ascorbic! - Plugins installed from the experimental registry can now be uninstalled and updated from the admin, the same way marketplace plugins always could. The "uninstall is not yet available for registry plugins" placeholder is gone — registry plugin rows now show the same Uninstall and Update buttons.

    The Plugins page's "updates available" indicator now covers registry plugins too. If the registry aggregator is unreachable, marketplace updates still load (and vice versa).

    Updates that need newly-declared permissions, or that newly expose a public (unauthenticated) route, prompt for re-consent before installing the new version — matching the gate that marketplace updates already have.

  • #1125 d0ff94b Thanks @ascorbic! - Adds a version picker to the registry plugin detail page. Older releases of a registry-hosted plugin are now selectable from a dropdown next to the Install button, and the displayed version, indexed date, permissions, and source link swap to match the selected release. Pre-release versions (e.g. 1.0.0-alpha.1) are flagged with a "Pre-release" badge so admins can spot them before installing. Versions still inside the configured minimum-release-age holdback remain visible in the dropdown but stay non-installable until they age into the window.

Patch Changes

  • #1139 88f544d Thanks @ask-bonk! - Upgrades kysely to ^0.29.0 (was ^0.27.0) to resolve three high-severity advisories fixed in >=0.28.17:

    Also updates import paths for Migrator and Migration types to kysely/migration to comply with kysely 0.29 export changes.

  • Updated dependencies [cf3c706, b9cc08e, 11b3001, fae97ee, 88f544d, 393dd26, 9a30607, d0ff94b]:

    • @emdash-cms/registry-client@0.2.0
    • @emdash-cms/admin@0.15.0
    • @emdash-cms/auth-atproto@0.2.8
    • @emdash-cms/auth@0.15.0
    • @emdash-cms/gutenberg-to-portable-text@0.15.0

@emdash-cms/plugin-cli@0.4.0

Minor Changes

  • #1126 cf3c706 Thanks @ascorbic! - Adds emdash-plugin update-package, a CLI command for editing an already-published plugin's registry record (license, authors, security contacts, name, description, keywords) without cutting a new release. Without --yes it prints a diff and exits without writing; with --yes it writes the updated record to the publisher's PDS using atproto's swapRecord precondition (concurrent writes surface as STALE_RECORD instead of silently overwriting each other) and bumps lastUpdated. Optional fields use a "manifest absent = no change" policy: removing a key from the manifest doesn't wipe the published value, matching publish semantics. Renaming a plugin via the manifest now surfaces a "looks like a rename" message listing the publisher's existing packages instead of a generic not-found, so publishers don't accidentally orphan releases under the old slug.

    The publishing client (@emdash-cms/registry-client) gains a swapRecord parameter on putRecord and unsafePutRecord for callers needing optimistic-concurrency writes.

Patch Changes

  • #1145 463c7a2 Thanks @ascorbic! - 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
    error messages instead of crashing with a TypeError.
  • Updated dependencies [cf3c706]:
    • @emdash-cms/registry-client@0.2.0

@emdash-cms/registry-client@0.2.0

Minor Changes

  • #1126 cf3c706 Thanks @ascorbic! - Adds emdash-plugin update-package, a CLI command for editing an already-published plugin's registry record (license, authors, security contacts, name, description, keywords) without cutting a new release. Without --yes it prints a diff and exits without writing; with --yes it writes the updated record to the publisher's PDS using atproto's swapRecord precondition (concurrent writes surface as STALE_RECORD instead of silently overwriting each other) and bumps lastUpdated. Optional fields use a "manifest absent = no change" policy: removing a key from the manifest doesn't wipe the published value, matching publish semantics. Renaming a plugin via the manifest now surfaces a "looks like a rename" message listing the publisher's existing packages instead of a generic not-found, so publishers don't accidentally orphan releases under the old slug.

    The publishing client (@emdash-cms/registry-client) gains a swapRecord parameter on putRecord and unsafePutRecord for callers needing optimistic-concurrency writes.

@emdash-cms/sandbox-workerd@0.1.0

Minor Changes

  • #426 02ed8ba Thanks @BenjaminPrice! - Adds workerd-based plugin sandboxing for Node.js deployments.
    • emdash: Adds isHealthy() to SandboxRunner interface, SandboxUnavailableError class, sandbox: false config option, mediaStorage field on SandboxOptions, and exports createHttpAccess/createUnrestrictedHttpAccess/PluginStorageRepository/UserRepository/OptionsRepository for platform adapters.
    • @emdash-cms/cloudflare: Implements isHealthy() on CloudflareSandboxRunner. Fixes storageQuery() and storageCount() to honor where, orderBy, and cursor options (previously ignored, causing infinite pagination loops and incorrect filtered counts). Adds storageConfig to PluginBridgeProps so PluginStorageRepository can use declared indexes.
    • @emdash-cms/sandbox-workerd: New package. WorkerdSandboxRunner for production (workerd child process + capnp config + authenticated HTTP backing service) and MiniflareDevRunner for development.

Patch Changes

  • #1144 c50c3b2 Thanks @ascorbic! - Aligns the kysely peer dependency with the rest of the monorepo (>=0.29.0) and switches the dev/peer references to the workspace catalog so all packages bump in lockstep going forward.

  • #1147 20c87fe Thanks @ascorbic! - Tightens the workerd sandbox internals so the package now lints and type-checks cleanly.

    • Bridge call bodies are validated with predicate-backed require* / optional* helpers instead of unchecked as casts. A misbehaving plugin that sends a malformed JSON-RPC body now gets a clear "Parameter X must be Y" error rather than triggering a downstream type confusion.
    • Content table access (ec_* collections) is centralised behind a typed asContentDb() helper. Known tables (users, media, _plugin_storage) drop their as keyof Database casts entirely.
    • HTTP init marshalling validates each field at the bridge boundary, including form-data parts.
    • The backing service uses a typed HttpError class for status-bearing errors and validates incoming chunks/body shape defensively.
    • getPluginStorageConfig() returns the real PluginStorageConfig shape from the manifest instead of Record<string, unknown>.
    • WorkerdSandboxedPlugin now implements the correct SandboxedPluginInstance interface (the old SandboxedPlugin symbol did not exist).
    • Adds a typecheck script (tsgo --noEmit) so the package participates in pnpm typecheck going forward.

    No runtime behaviour changes.

  • Updated dependencies [02ed8ba, 11b3001, fae97ee, 88f544d, 9a30607, d0ff94b]:

    • emdash@0.15.0

@emdash-cms/auth-atproto@0.2.8

Patch Changes

  • #1177 b9cc08e Thanks @ascorbic! - Bumps @cloudflare/kumo from 1.16 to 2.3. Two internal call sites picked up breaking API changes from Kumo 2.0: Collapsible is now a compound component (Collapsible.Root / .DefaultTrigger / .DefaultPanel instead of <Collapsible label=...>), used by the accordion block; and ChartPalette.color() was renamed to ChartPalette.categorical() in the chart block. No public API changes -- consumers see identical behaviour. Tests in @emdash-cms/admin that asserted on Button's native title attribute now read aria-label instead, because Kumo 2 wraps <Button title> in a Tooltip popup rather than setting the DOM attribute.

  • #1139 88f544d Thanks @ask-bonk! - Upgrades kysely to ^0.29.0 (was ^0.27.0) to resolve three high-severity advisories fixed in >=0.28.17:

    Also updates import paths for Migrator and Migration types to kysely/migration to comply with kysely 0.29 export changes.

  • Updated dependencies [02ed8ba, 11b3001, fae97ee, 88f544d, 9a30607, d0ff94b]:

    • emdash@0.15.0
    • @emdash-cms/auth@0.15.0

@emdash-cms/blocks@0.15.0

Patch Changes

  • #1177 b9cc08e Thanks @ascorbic! - Bumps @cloudflare/kumo from 1.16 to 2.3. Two internal call sites picked up breaking API changes from Kumo 2.0: Collapsible is now a compound component (Collapsible.Root / .DefaultTrigger / .DefaultPanel instead of <Collapsible label=...>), used by the accordion block; and ChartPalette.color() was renamed to ChartPalette.categorical() in the chart block. No public API changes -- consumers see identical behaviour. Tests in @emdash-cms/admin that asserted on Button's native title attribute now read aria-label instead, because Kumo 2 wraps <Button title> in a Tooltip popup rather than setting the DOM attribute.

@emdash-cms/cloudflare@0.15.0

Patch Changes

  • #426 02ed8ba Thanks @BenjaminPrice! - Adds workerd-based plugin sandboxing for Node.js deployments.

    • emdash: Adds isHealthy() to SandboxRunner interface, SandboxUnavailableError class, sandbox: false config option, mediaStorage field on SandboxOptions, and exports createHttpAccess/createUnrestrictedHttpAccess/PluginStorageRepository/UserRepository/OptionsRepository for platform adapters.
    • @emdash-cms/cloudflare: Implements isHealthy() on CloudflareSandboxRunner. Fixes storageQuery() and storageCount() to honor where, orderBy, and cursor options (previously ignored, causing infinite pagination loops and incorrect filtered counts). Adds storageConfig to PluginBridgeProps so PluginStorageRepository can use declared indexes.
    • @emdash-cms/sandbox-workerd: New package. WorkerdSandboxRunner for production (workerd child process + capnp config + authenticated HTTP backing service) and MiniflareDevRunner for development.
  • #1139 88f544d Thanks @ask-bonk! - Upgrades kysely to ^0.29.0 (was ^0.27.0) to resolve three high-severity advisories fixed in >=0.28.17:

    Also updates import paths for Migrator and Migration types to kysely/migration to comply with kysely 0.29 export changes.

  • Updated dependencies [02ed8ba, 11b3001, fae97ee, 88f544d, 9a30607, d0ff94b]:

    • emdash@0.15.0

@emdash-cms/plugin-embeds@0.1.16

Patch Changes

create-emdash@0.15.0

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

@emdash-cms/x402@0.15.0

@emdash-cms/fixture-perf-site@0.0.11

Patch Changes

@emdash-cms/perf-demo-site@0.0.11

Patch Changes

@emdash-cms/cache-demo-site@0.0.11

Patch Changes


Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks changeset-release/main. Updated automatically when the playground redeploys.

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

cloudflare-workers-and-pages Bot commented May 20, 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 aea18ae May 23 2026, 06:20 AM

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

cloudflare-workers-and-pages Bot commented May 20, 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 aea18ae May 23 2026, 06:20 AM

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

cloudflare-workers-and-pages Bot commented May 20, 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 7c5e36c May 26 2026, 04:43 PM

@github-actions
Copy link
Copy Markdown
Contributor

Scope check

This PR touches 29 files. PRs with a broad scope are harder to review. Please confirm the scope hasn't drifted beyond the intended change.
This PR spans 5 different areas (area/core, area/admin, area/plugins, area/auth, area/cloudflare). Consider breaking it into smaller, focused PRs.

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.

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

cloudflare-workers-and-pages Bot commented May 20, 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 7c5e36c May 26 2026, 04:44 PM

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

cloudflare-workers-and-pages Bot commented May 20, 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 7c5e36c May 26 2026, 04:43 PM

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 20, 2026

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

emdash

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

create-emdash

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

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

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 7c5e36c

@emdashbot emdashbot Bot force-pushed the changeset-release/main branch from d244620 to 0bd6ba4 Compare May 20, 2026 21:26
@emdashbot emdashbot Bot force-pushed the changeset-release/main branch 3 times, most recently from 67d6a96 to 8aacac7 Compare May 22, 2026 13:18
@github-actions github-actions Bot added size/L and removed size/M labels May 22, 2026
@emdashbot emdashbot Bot force-pushed the changeset-release/main branch 6 times, most recently from d4919bc to 4fae997 Compare May 23, 2026 06:07
@github-actions github-actions Bot added size/XL and removed size/L labels May 23, 2026
@emdashbot emdashbot Bot force-pushed the changeset-release/main branch from 4fae997 to aea18ae Compare May 23, 2026 06:18
@emdashbot emdashbot Bot force-pushed the changeset-release/main branch 3 times, most recently from 5e1c6b5 to 678ae04 Compare May 23, 2026 16:02
@github-actions
Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@emdashbot emdashbot Bot force-pushed the changeset-release/main branch from 678ae04 to 004fa48 Compare May 26, 2026 15:22
@emdashbot emdashbot Bot force-pushed the changeset-release/main branch from 004fa48 to 7c5e36c Compare May 26, 2026 16:41
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.

0 participants