@prenta/core 0.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2974 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/a11y/index.d.ts +25 -0
- package/dist/a11y/index.d.ts.map +1 -0
- package/dist/a11y/index.js +91 -0
- package/dist/a11y/index.js.map +1 -0
- package/dist/actions.d.ts +130 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +1324 -0
- package/dist/actions.js.map +1 -0
- package/dist/ai/brand-voice-alignment.d.ts +17 -0
- package/dist/ai/brand-voice-alignment.d.ts.map +1 -0
- package/dist/ai/brand-voice-alignment.js +40 -0
- package/dist/ai/brand-voice-alignment.js.map +1 -0
- package/dist/ai/brand-voice-analyze.d.ts +71 -0
- package/dist/ai/brand-voice-analyze.d.ts.map +1 -0
- package/dist/ai/brand-voice-analyze.js +151 -0
- package/dist/ai/brand-voice-analyze.js.map +1 -0
- package/dist/ai/brand-voice-document.d.ts +56 -0
- package/dist/ai/brand-voice-document.d.ts.map +1 -0
- package/dist/ai/brand-voice-document.js +335 -0
- package/dist/ai/brand-voice-document.js.map +1 -0
- package/dist/ai/brand-voice-fix.d.ts +26 -0
- package/dist/ai/brand-voice-fix.d.ts.map +1 -0
- package/dist/ai/brand-voice-fix.js +97 -0
- package/dist/ai/brand-voice-fix.js.map +1 -0
- package/dist/ai/brand-voice-save.d.ts +11 -0
- package/dist/ai/brand-voice-save.d.ts.map +1 -0
- package/dist/ai/brand-voice-save.js +51 -0
- package/dist/ai/brand-voice-save.js.map +1 -0
- package/dist/ai/brand-voice-sources.d.ts +16 -0
- package/dist/ai/brand-voice-sources.d.ts.map +1 -0
- package/dist/ai/brand-voice-sources.js +106 -0
- package/dist/ai/brand-voice-sources.js.map +1 -0
- package/dist/ai/brand-voice-structured.d.ts +87 -0
- package/dist/ai/brand-voice-structured.d.ts.map +1 -0
- package/dist/ai/brand-voice-structured.js +438 -0
- package/dist/ai/brand-voice-structured.js.map +1 -0
- package/dist/ai/brand-voice.d.ts +40 -0
- package/dist/ai/brand-voice.d.ts.map +1 -0
- package/dist/ai/brand-voice.js +103 -0
- package/dist/ai/brand-voice.js.map +1 -0
- package/dist/ai/catalog.d.ts +35 -0
- package/dist/ai/catalog.d.ts.map +1 -0
- package/dist/ai/catalog.js +370 -0
- package/dist/ai/catalog.js.map +1 -0
- package/dist/ai/config-store.d.ts +55 -0
- package/dist/ai/config-store.d.ts.map +1 -0
- package/dist/ai/config-store.js +177 -0
- package/dist/ai/config-store.js.map +1 -0
- package/dist/ai/content-writing-generate.d.ts +19 -0
- package/dist/ai/content-writing-generate.d.ts.map +1 -0
- package/dist/ai/content-writing-generate.js +64 -0
- package/dist/ai/content-writing-generate.js.map +1 -0
- package/dist/ai/feature-defaults.d.ts +20 -0
- package/dist/ai/feature-defaults.d.ts.map +1 -0
- package/dist/ai/feature-defaults.js +104 -0
- package/dist/ai/feature-defaults.js.map +1 -0
- package/dist/ai/index.d.ts +24 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +24 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/models.d.ts +37 -0
- package/dist/ai/models.d.ts.map +1 -0
- package/dist/ai/models.js +126 -0
- package/dist/ai/models.js.map +1 -0
- package/dist/ai/providers/anthropic.d.ts +10 -0
- package/dist/ai/providers/anthropic.d.ts.map +1 -0
- package/dist/ai/providers/anthropic.js +63 -0
- package/dist/ai/providers/anthropic.js.map +1 -0
- package/dist/ai/providers/custom.d.ts +12 -0
- package/dist/ai/providers/custom.d.ts.map +1 -0
- package/dist/ai/providers/custom.js +76 -0
- package/dist/ai/providers/custom.js.map +1 -0
- package/dist/ai/providers/http.d.ts +66 -0
- package/dist/ai/providers/http.d.ts.map +1 -0
- package/dist/ai/providers/http.js +185 -0
- package/dist/ai/providers/http.js.map +1 -0
- package/dist/ai/providers/index.d.ts +18 -0
- package/dist/ai/providers/index.d.ts.map +1 -0
- package/dist/ai/providers/index.js +28 -0
- package/dist/ai/providers/index.js.map +1 -0
- package/dist/ai/providers/openai.d.ts +11 -0
- package/dist/ai/providers/openai.d.ts.map +1 -0
- package/dist/ai/providers/openai.js +44 -0
- package/dist/ai/providers/openai.js.map +1 -0
- package/dist/ai/providers/xai.d.ts +10 -0
- package/dist/ai/providers/xai.d.ts.map +1 -0
- package/dist/ai/providers/xai.js +38 -0
- package/dist/ai/providers/xai.js.map +1 -0
- package/dist/ai/resolve.d.ts +41 -0
- package/dist/ai/resolve.d.ts.map +1 -0
- package/dist/ai/resolve.js +143 -0
- package/dist/ai/resolve.js.map +1 -0
- package/dist/ai/runtime.d.ts +88 -0
- package/dist/ai/runtime.d.ts.map +1 -0
- package/dist/ai/runtime.js +192 -0
- package/dist/ai/runtime.js.map +1 -0
- package/dist/ai/sampling.d.ts +17 -0
- package/dist/ai/sampling.d.ts.map +1 -0
- package/dist/ai/sampling.js +51 -0
- package/dist/ai/sampling.js.map +1 -0
- package/dist/ai/types.d.ts +193 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +11 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/ai/usage.d.ts +18 -0
- package/dist/ai/usage.d.ts.map +1 -0
- package/dist/ai/usage.js +70 -0
- package/dist/ai/usage.js.map +1 -0
- package/dist/api/collection-introspection.d.ts +46 -0
- package/dist/api/collection-introspection.d.ts.map +1 -0
- package/dist/api/collection-introspection.js +82 -0
- package/dist/api/collection-introspection.js.map +1 -0
- package/dist/api/guarded-db.d.ts +14 -0
- package/dist/api/guarded-db.d.ts.map +1 -0
- package/dist/api/guarded-db.js +62 -0
- package/dist/api/guarded-db.js.map +1 -0
- package/dist/api/handler-factory.d.ts +7 -0
- package/dist/api/handler-factory.d.ts.map +1 -0
- package/dist/api/handler-factory.js +380 -0
- package/dist/api/handler-factory.js.map +1 -0
- package/dist/api/handlers.d.ts +8 -0
- package/dist/api/handlers.d.ts.map +1 -0
- package/dist/api/handlers.js +62 -0
- package/dist/api/handlers.js.map +1 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/openapi.d.ts +3 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +539 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/api/route-helpers.d.ts +369 -0
- package/dist/api/route-helpers.d.ts.map +1 -0
- package/dist/api/route-helpers.js +1574 -0
- package/dist/api/route-helpers.js.map +1 -0
- package/dist/api/router.d.ts +21 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +75 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/routes/ai-seo.d.ts +3 -0
- package/dist/api/routes/ai-seo.d.ts.map +1 -0
- package/dist/api/routes/ai-seo.js +482 -0
- package/dist/api/routes/ai-seo.js.map +1 -0
- package/dist/api/routes/ai-settings.d.ts +3 -0
- package/dist/api/routes/ai-settings.d.ts.map +1 -0
- package/dist/api/routes/ai-settings.js +1078 -0
- package/dist/api/routes/ai-settings.js.map +1 -0
- package/dist/api/routes/auth.d.ts +3 -0
- package/dist/api/routes/auth.d.ts.map +1 -0
- package/dist/api/routes/auth.js +877 -0
- package/dist/api/routes/auth.js.map +1 -0
- package/dist/api/routes/collab.d.ts +3 -0
- package/dist/api/routes/collab.d.ts.map +1 -0
- package/dist/api/routes/collab.js +426 -0
- package/dist/api/routes/collab.js.map +1 -0
- package/dist/api/routes/dashboard.d.ts +7 -0
- package/dist/api/routes/dashboard.d.ts.map +1 -0
- package/dist/api/routes/dashboard.js +540 -0
- package/dist/api/routes/dashboard.js.map +1 -0
- package/dist/api/routes/documents.d.ts +11 -0
- package/dist/api/routes/documents.d.ts.map +1 -0
- package/dist/api/routes/documents.js +340 -0
- package/dist/api/routes/documents.js.map +1 -0
- package/dist/api/routes/email-settings.d.ts +3 -0
- package/dist/api/routes/email-settings.d.ts.map +1 -0
- package/dist/api/routes/email-settings.js +248 -0
- package/dist/api/routes/email-settings.js.map +1 -0
- package/dist/api/routes/folders.d.ts +3 -0
- package/dist/api/routes/folders.d.ts.map +1 -0
- package/dist/api/routes/folders.js +195 -0
- package/dist/api/routes/folders.js.map +1 -0
- package/dist/api/routes/forms.d.ts +3 -0
- package/dist/api/routes/forms.d.ts.map +1 -0
- package/dist/api/routes/forms.js +776 -0
- package/dist/api/routes/forms.js.map +1 -0
- package/dist/api/routes/globals.d.ts +3 -0
- package/dist/api/routes/globals.d.ts.map +1 -0
- package/dist/api/routes/globals.js +157 -0
- package/dist/api/routes/globals.js.map +1 -0
- package/dist/api/routes/media.d.ts +3 -0
- package/dist/api/routes/media.d.ts.map +1 -0
- package/dist/api/routes/media.js +807 -0
- package/dist/api/routes/media.js.map +1 -0
- package/dist/api/routes/meta.d.ts +3 -0
- package/dist/api/routes/meta.d.ts.map +1 -0
- package/dist/api/routes/meta.js +97 -0
- package/dist/api/routes/meta.js.map +1 -0
- package/dist/api/routes/navigations.d.ts +7 -0
- package/dist/api/routes/navigations.d.ts.map +1 -0
- package/dist/api/routes/navigations.js +81 -0
- package/dist/api/routes/navigations.js.map +1 -0
- package/dist/api/routes/page-templates.d.ts +3 -0
- package/dist/api/routes/page-templates.d.ts.map +1 -0
- package/dist/api/routes/page-templates.js +265 -0
- package/dist/api/routes/page-templates.js.map +1 -0
- package/dist/api/routes/presence.d.ts +3 -0
- package/dist/api/routes/presence.d.ts.map +1 -0
- package/dist/api/routes/presence.js +35 -0
- package/dist/api/routes/presence.js.map +1 -0
- package/dist/api/routes/preview.d.ts +3 -0
- package/dist/api/routes/preview.d.ts.map +1 -0
- package/dist/api/routes/preview.js +111 -0
- package/dist/api/routes/preview.js.map +1 -0
- package/dist/api/routes/redirects.d.ts +3 -0
- package/dist/api/routes/redirects.d.ts.map +1 -0
- package/dist/api/routes/redirects.js +937 -0
- package/dist/api/routes/redirects.js.map +1 -0
- package/dist/api/routes/saved-sections.d.ts +3 -0
- package/dist/api/routes/saved-sections.d.ts.map +1 -0
- package/dist/api/routes/saved-sections.js +1071 -0
- package/dist/api/routes/saved-sections.js.map +1 -0
- package/dist/api/routes/scheduling.d.ts +3 -0
- package/dist/api/routes/scheduling.d.ts.map +1 -0
- package/dist/api/routes/scheduling.js +391 -0
- package/dist/api/routes/scheduling.js.map +1 -0
- package/dist/api/routes/sections.d.ts +3 -0
- package/dist/api/routes/sections.d.ts.map +1 -0
- package/dist/api/routes/sections.js +512 -0
- package/dist/api/routes/sections.js.map +1 -0
- package/dist/api/routes/security-center.d.ts +3 -0
- package/dist/api/routes/security-center.d.ts.map +1 -0
- package/dist/api/routes/security-center.js +71 -0
- package/dist/api/routes/security-center.js.map +1 -0
- package/dist/api/routes/seo-public.d.ts +14 -0
- package/dist/api/routes/seo-public.d.ts.map +1 -0
- package/dist/api/routes/seo-public.js +1366 -0
- package/dist/api/routes/seo-public.js.map +1 -0
- package/dist/api/routes/seo.d.ts +8 -0
- package/dist/api/routes/seo.d.ts.map +1 -0
- package/dist/api/routes/seo.js +2256 -0
- package/dist/api/routes/seo.js.map +1 -0
- package/dist/api/routes/system.d.ts +3 -0
- package/dist/api/routes/system.d.ts.map +1 -0
- package/dist/api/routes/system.js +353 -0
- package/dist/api/routes/system.js.map +1 -0
- package/dist/api/routes/url-resolution.d.ts +3 -0
- package/dist/api/routes/url-resolution.d.ts.map +1 -0
- package/dist/api/routes/url-resolution.js +522 -0
- package/dist/api/routes/url-resolution.js.map +1 -0
- package/dist/api/routes/users.d.ts +3 -0
- package/dist/api/routes/users.d.ts.map +1 -0
- package/dist/api/routes/users.js +1177 -0
- package/dist/api/routes/users.js.map +1 -0
- package/dist/api/routes/webhooks.d.ts +3 -0
- package/dist/api/routes/webhooks.d.ts.map +1 -0
- package/dist/api/routes/webhooks.js +422 -0
- package/dist/api/routes/webhooks.js.map +1 -0
- package/dist/appearance/color.d.ts +24 -0
- package/dist/appearance/color.d.ts.map +1 -0
- package/dist/appearance/color.js +90 -0
- package/dist/appearance/color.js.map +1 -0
- package/dist/appearance/favicon-icon.d.ts +12 -0
- package/dist/appearance/favicon-icon.d.ts.map +1 -0
- package/dist/appearance/favicon-icon.js +58 -0
- package/dist/appearance/favicon-icon.js.map +1 -0
- package/dist/appearance/favicon-paths.d.ts +6 -0
- package/dist/appearance/favicon-paths.d.ts.map +1 -0
- package/dist/appearance/favicon-paths.js +6 -0
- package/dist/appearance/favicon-paths.js.map +1 -0
- package/dist/appearance/index.d.ts +15 -0
- package/dist/appearance/index.d.ts.map +1 -0
- package/dist/appearance/index.js +7 -0
- package/dist/appearance/index.js.map +1 -0
- package/dist/appearance/logo-shape.d.ts +26 -0
- package/dist/appearance/logo-shape.d.ts.map +1 -0
- package/dist/appearance/logo-shape.js +62 -0
- package/dist/appearance/logo-shape.js.map +1 -0
- package/dist/appearance/resolve.d.ts +52 -0
- package/dist/appearance/resolve.d.ts.map +1 -0
- package/dist/appearance/resolve.js +219 -0
- package/dist/appearance/resolve.js.map +1 -0
- package/dist/appearance/theme-tokens.d.ts +49 -0
- package/dist/appearance/theme-tokens.d.ts.map +1 -0
- package/dist/appearance/theme-tokens.js +229 -0
- package/dist/appearance/theme-tokens.js.map +1 -0
- package/dist/appearance/types.d.ts +110 -0
- package/dist/appearance/types.d.ts.map +1 -0
- package/dist/appearance/types.js +16 -0
- package/dist/appearance/types.js.map +1 -0
- package/dist/astro-cache.d.ts +80 -0
- package/dist/astro-cache.d.ts.map +1 -0
- package/dist/astro-cache.js +108 -0
- package/dist/astro-cache.js.map +1 -0
- package/dist/astro-content.d.ts +81 -0
- package/dist/astro-content.d.ts.map +1 -0
- package/dist/astro-content.js +151 -0
- package/dist/astro-content.js.map +1 -0
- package/dist/astro-redirects.d.ts +38 -0
- package/dist/astro-redirects.d.ts.map +1 -0
- package/dist/astro-redirects.js +45 -0
- package/dist/astro-redirects.js.map +1 -0
- package/dist/astro-security.d.ts +34 -0
- package/dist/astro-security.d.ts.map +1 -0
- package/dist/astro-security.js +31 -0
- package/dist/astro-security.js.map +1 -0
- package/dist/astro-site.d.ts +69 -0
- package/dist/astro-site.d.ts.map +1 -0
- package/dist/astro-site.js +81 -0
- package/dist/astro-site.js.map +1 -0
- package/dist/auth/index.d.ts +17 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +12 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/invite-email.d.ts +12 -0
- package/dist/auth/invite-email.d.ts.map +1 -0
- package/dist/auth/invite-email.js +57 -0
- package/dist/auth/invite-email.js.map +1 -0
- package/dist/auth/invites.d.ts +72 -0
- package/dist/auth/invites.d.ts.map +1 -0
- package/dist/auth/invites.js +279 -0
- package/dist/auth/invites.js.map +1 -0
- package/dist/auth/mfa-pending.d.ts +24 -0
- package/dist/auth/mfa-pending.d.ts.map +1 -0
- package/dist/auth/mfa-pending.js +38 -0
- package/dist/auth/mfa-pending.js.map +1 -0
- package/dist/auth/oauth.d.ts +139 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +376 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/password.d.ts +57 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +150 -0
- package/dist/auth/password.js.map +1 -0
- package/dist/auth/providers/github.d.ts +9 -0
- package/dist/auth/providers/github.d.ts.map +1 -0
- package/dist/auth/providers/github.js +10 -0
- package/dist/auth/providers/github.js.map +1 -0
- package/dist/auth/providers/google.d.ts +9 -0
- package/dist/auth/providers/google.d.ts.map +1 -0
- package/dist/auth/providers/google.js +10 -0
- package/dist/auth/providers/google.js.map +1 -0
- package/dist/auth/providers/microsoft.d.ts +9 -0
- package/dist/auth/providers/microsoft.d.ts.map +1 -0
- package/dist/auth/providers/microsoft.js +11 -0
- package/dist/auth/providers/microsoft.js.map +1 -0
- package/dist/auth/reset-email.d.ts +10 -0
- package/dist/auth/reset-email.d.ts.map +1 -0
- package/dist/auth/reset-email.js +30 -0
- package/dist/auth/reset-email.js.map +1 -0
- package/dist/auth/reset.d.ts +42 -0
- package/dist/auth/reset.d.ts.map +1 -0
- package/dist/auth/reset.js +142 -0
- package/dist/auth/reset.js.map +1 -0
- package/dist/auth/roles.d.ts +51 -0
- package/dist/auth/roles.d.ts.map +1 -0
- package/dist/auth/roles.js +159 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/auth/session.d.ts +38 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +106 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/team.d.ts +99 -0
- package/dist/auth/team.d.ts.map +1 -0
- package/dist/auth/team.js +327 -0
- package/dist/auth/team.js.map +1 -0
- package/dist/auth/totp.d.ts +31 -0
- package/dist/auth/totp.d.ts.map +1 -0
- package/dist/auth/totp.js +134 -0
- package/dist/auth/totp.js.map +1 -0
- package/dist/auth/user-admin.d.ts +119 -0
- package/dist/auth/user-admin.d.ts.map +1 -0
- package/dist/auth/user-admin.js +292 -0
- package/dist/auth/user-admin.js.map +1 -0
- package/dist/backup/index.d.ts +19 -0
- package/dist/backup/index.d.ts.map +1 -0
- package/dist/backup/index.js +24 -0
- package/dist/backup/index.js.map +1 -0
- package/dist/billing/stripe-meter.d.ts +22 -0
- package/dist/billing/stripe-meter.d.ts.map +1 -0
- package/dist/billing/stripe-meter.js +101 -0
- package/dist/billing/stripe-meter.js.map +1 -0
- package/dist/brand-voice/index.d.ts +7 -0
- package/dist/brand-voice/index.d.ts.map +1 -0
- package/dist/brand-voice/index.js +7 -0
- package/dist/brand-voice/index.js.map +1 -0
- package/dist/cache/http.d.ts +140 -0
- package/dist/cache/http.d.ts.map +1 -0
- package/dist/cache/http.js +213 -0
- package/dist/cache/http.js.map +1 -0
- package/dist/cache/index.d.ts +15 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +32 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/client.d.ts +51 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +59 -0
- package/dist/client.js.map +1 -0
- package/dist/codegen/index.d.ts +4 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +366 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/collections/index.d.ts +19 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +30 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/collections/presets.d.ts +90 -0
- package/dist/collections/presets.d.ts.map +1 -0
- package/dist/collections/presets.js +573 -0
- package/dist/collections/presets.js.map +1 -0
- package/dist/config/define.d.ts +8 -0
- package/dist/config/define.d.ts.map +1 -0
- package/dist/config/define.js +7 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/index.d.ts +31 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +196 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/runtime.d.ts +107 -0
- package/dist/config/runtime.d.ts.map +1 -0
- package/dist/config/runtime.js +77 -0
- package/dist/config/runtime.js.map +1 -0
- package/dist/config/types.d.ts +924 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/content/ai-api.d.ts +21 -0
- package/dist/content/ai-api.d.ts.map +1 -0
- package/dist/content/ai-api.js +25 -0
- package/dist/content/ai-api.js.map +1 -0
- package/dist/content/content-graph.d.ts +25 -0
- package/dist/content/content-graph.d.ts.map +1 -0
- package/dist/content/content-graph.js +40 -0
- package/dist/content/content-graph.js.map +1 -0
- package/dist/content/extract.d.ts +7 -0
- package/dist/content/extract.d.ts.map +1 -0
- package/dist/content/extract.js +33 -0
- package/dist/content/extract.js.map +1 -0
- package/dist/content/index.d.ts +9 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +6 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/slug.d.ts +40 -0
- package/dist/content/slug.d.ts.map +1 -0
- package/dist/content/slug.js +78 -0
- package/dist/content/slug.js.map +1 -0
- package/dist/content/structured-data.d.ts +163 -0
- package/dist/content/structured-data.d.ts.map +1 -0
- package/dist/content/structured-data.js +479 -0
- package/dist/content/structured-data.js.map +1 -0
- package/dist/content-health/cron.d.ts +68 -0
- package/dist/content-health/cron.d.ts.map +1 -0
- package/dist/content-health/cron.js +167 -0
- package/dist/content-health/cron.js.map +1 -0
- package/dist/content-health/scanner.d.ts +119 -0
- package/dist/content-health/scanner.d.ts.map +1 -0
- package/dist/content-health/scanner.js +257 -0
- package/dist/content-health/scanner.js.map +1 -0
- package/dist/cron/index.d.ts +90 -0
- package/dist/cron/index.d.ts.map +1 -0
- package/dist/cron/index.js +299 -0
- package/dist/cron/index.js.map +1 -0
- package/dist/db/adapters/mysql.d.ts +5 -0
- package/dist/db/adapters/mysql.d.ts.map +1 -0
- package/dist/db/adapters/mysql.js +26 -0
- package/dist/db/adapters/mysql.js.map +1 -0
- package/dist/db/adapters/postgres.d.ts +7 -0
- package/dist/db/adapters/postgres.d.ts.map +1 -0
- package/dist/db/adapters/postgres.js +25 -0
- package/dist/db/adapters/postgres.js.map +1 -0
- package/dist/db/adapters/sqlite.d.ts +5 -0
- package/dist/db/adapters/sqlite.d.ts.map +1 -0
- package/dist/db/adapters/sqlite.js +27 -0
- package/dist/db/adapters/sqlite.js.map +1 -0
- package/dist/db/client-types.d.ts +75 -0
- package/dist/db/client-types.d.ts.map +1 -0
- package/dist/db/client-types.js +2 -0
- package/dist/db/client-types.js.map +1 -0
- package/dist/db/create-adapter.d.ts +11 -0
- package/dist/db/create-adapter.d.ts.map +1 -0
- package/dist/db/create-adapter.js +43 -0
- package/dist/db/create-adapter.js.map +1 -0
- package/dist/db/find-in-batches.d.ts +12 -0
- package/dist/db/find-in-batches.d.ts.map +1 -0
- package/dist/db/find-in-batches.js +20 -0
- package/dist/db/find-in-batches.js.map +1 -0
- package/dist/db/index.d.ts +9 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +5 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/model-types.d.ts +602 -0
- package/dist/db/model-types.d.ts.map +1 -0
- package/dist/db/model-types.js +14 -0
- package/dist/db/model-types.js.map +1 -0
- package/dist/db.d.ts +20 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +35 -0
- package/dist/db.js.map +1 -0
- package/dist/diagnostics/api-metrics.d.ts +135 -0
- package/dist/diagnostics/api-metrics.d.ts.map +1 -0
- package/dist/diagnostics/api-metrics.js +376 -0
- package/dist/diagnostics/api-metrics.js.map +1 -0
- package/dist/diagnostics/env.d.ts +44 -0
- package/dist/diagnostics/env.d.ts.map +1 -0
- package/dist/diagnostics/env.js +354 -0
- package/dist/diagnostics/env.js.map +1 -0
- package/dist/diagnostics/logger.d.ts +38 -0
- package/dist/diagnostics/logger.d.ts.map +1 -0
- package/dist/diagnostics/logger.js +89 -0
- package/dist/diagnostics/logger.js.map +1 -0
- package/dist/diagnostics/sentry.d.ts +8 -0
- package/dist/diagnostics/sentry.d.ts.map +1 -0
- package/dist/diagnostics/sentry.js +54 -0
- package/dist/diagnostics/sentry.js.map +1 -0
- package/dist/email/resolve.d.ts +27 -0
- package/dist/email/resolve.d.ts.map +1 -0
- package/dist/email/resolve.js +68 -0
- package/dist/email/resolve.js.map +1 -0
- package/dist/email/sender.d.ts +71 -0
- package/dist/email/sender.d.ts.map +1 -0
- package/dist/email/sender.js +41 -0
- package/dist/email/sender.js.map +1 -0
- package/dist/email/settings-store.d.ts +116 -0
- package/dist/email/settings-store.d.ts.map +1 -0
- package/dist/email/settings-store.js +222 -0
- package/dist/email/settings-store.js.map +1 -0
- package/dist/fields/component-block.d.ts +23 -0
- package/dist/fields/component-block.d.ts.map +1 -0
- package/dist/fields/component-block.js +137 -0
- package/dist/fields/component-block.js.map +1 -0
- package/dist/fields/index.d.ts +19 -0
- package/dist/fields/index.d.ts.map +1 -0
- package/dist/fields/index.js +95 -0
- package/dist/fields/index.js.map +1 -0
- package/dist/fields/presets.d.ts +144 -0
- package/dist/fields/presets.d.ts.map +1 -0
- package/dist/fields/presets.js +350 -0
- package/dist/fields/presets.js.map +1 -0
- package/dist/fields/relations.d.ts +87 -0
- package/dist/fields/relations.d.ts.map +1 -0
- package/dist/fields/relations.js +231 -0
- package/dist/fields/relations.js.map +1 -0
- package/dist/forms/analytics.d.ts +62 -0
- package/dist/forms/analytics.d.ts.map +1 -0
- package/dist/forms/analytics.js +95 -0
- package/dist/forms/analytics.js.map +1 -0
- package/dist/forms/attribution.d.ts +175 -0
- package/dist/forms/attribution.d.ts.map +1 -0
- package/dist/forms/attribution.js +577 -0
- package/dist/forms/attribution.js.map +1 -0
- package/dist/forms/client-validation.d.ts +50 -0
- package/dist/forms/client-validation.d.ts.map +1 -0
- package/dist/forms/client-validation.js +215 -0
- package/dist/forms/client-validation.js.map +1 -0
- package/dist/forms/delivery.d.ts +89 -0
- package/dist/forms/delivery.d.ts.map +1 -0
- package/dist/forms/delivery.js +477 -0
- package/dist/forms/delivery.js.map +1 -0
- package/dist/forms/encryption.d.ts +20 -0
- package/dist/forms/encryption.d.ts.map +1 -0
- package/dist/forms/encryption.js +80 -0
- package/dist/forms/encryption.js.map +1 -0
- package/dist/forms/entries.d.ts +68 -0
- package/dist/forms/entries.d.ts.map +1 -0
- package/dist/forms/entries.js +557 -0
- package/dist/forms/entries.js.map +1 -0
- package/dist/forms/files.d.ts +38 -0
- package/dist/forms/files.d.ts.map +1 -0
- package/dist/forms/files.js +134 -0
- package/dist/forms/files.js.map +1 -0
- package/dist/forms/index.d.ts +21 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms/index.js +20 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/notify.d.ts +80 -0
- package/dist/forms/notify.d.ts.map +1 -0
- package/dist/forms/notify.js +357 -0
- package/dist/forms/notify.js.map +1 -0
- package/dist/forms/prefetch.d.ts +17 -0
- package/dist/forms/prefetch.d.ts.map +1 -0
- package/dist/forms/prefetch.js +36 -0
- package/dist/forms/prefetch.js.map +1 -0
- package/dist/forms/public-html.d.ts +22 -0
- package/dist/forms/public-html.d.ts.map +1 -0
- package/dist/forms/public-html.js +145 -0
- package/dist/forms/public-html.js.map +1 -0
- package/dist/forms/service.d.ts +102 -0
- package/dist/forms/service.d.ts.map +1 -0
- package/dist/forms/service.js +625 -0
- package/dist/forms/service.js.map +1 -0
- package/dist/forms/spam.d.ts +33 -0
- package/dist/forms/spam.d.ts.map +1 -0
- package/dist/forms/spam.js +93 -0
- package/dist/forms/spam.js.map +1 -0
- package/dist/forms/submission.d.ts +132 -0
- package/dist/forms/submission.d.ts.map +1 -0
- package/dist/forms/submission.js +348 -0
- package/dist/forms/submission.js.map +1 -0
- package/dist/forms/types.d.ts +259 -0
- package/dist/forms/types.d.ts.map +1 -0
- package/dist/forms/types.js +20 -0
- package/dist/forms/types.js.map +1 -0
- package/dist/forms/validation.d.ts +19 -0
- package/dist/forms/validation.d.ts.map +1 -0
- package/dist/forms/validation.js +166 -0
- package/dist/forms/validation.js.map +1 -0
- package/dist/forms/webhooks.d.ts +60 -0
- package/dist/forms/webhooks.d.ts.map +1 -0
- package/dist/forms/webhooks.js +360 -0
- package/dist/forms/webhooks.js.map +1 -0
- package/dist/forms/webmcp.d.ts +36 -0
- package/dist/forms/webmcp.d.ts.map +1 -0
- package/dist/forms/webmcp.js +67 -0
- package/dist/forms/webmcp.js.map +1 -0
- package/dist/forms/xlsx.d.ts +10 -0
- package/dist/forms/xlsx.d.ts.map +1 -0
- package/dist/forms/xlsx.js +207 -0
- package/dist/forms/xlsx.js.map +1 -0
- package/dist/generated/browser.d.ts +220 -0
- package/dist/generated/browser.js +17 -0
- package/dist/generated/client.d.ts +239 -0
- package/dist/generated/client.js +34 -0
- package/dist/generated/commonInputTypes.d.ts +887 -0
- package/dist/generated/commonInputTypes.js +10 -0
- package/dist/generated/enums.d.ts +83 -0
- package/dist/generated/enums.js +81 -0
- package/dist/generated/internal/class.d.ts +517 -0
- package/dist/generated/internal/class.js +52 -0
- package/dist/generated/internal/prismaNamespace.d.ts +4184 -0
- package/dist/generated/internal/prismaNamespace.js +932 -0
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +968 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +903 -0
- package/dist/generated/models/AiCrawlerHit.d.ts +1115 -0
- package/dist/generated/models/AiCrawlerHit.js +1 -0
- package/dist/generated/models/ApiKey.d.ts +1388 -0
- package/dist/generated/models/ApiKey.js +1 -0
- package/dist/generated/models/ApiRequestMetric.d.ts +1229 -0
- package/dist/generated/models/ApiRequestMetric.js +1 -0
- package/dist/generated/models/AuditLog.d.ts +1062 -0
- package/dist/generated/models/AuditLog.js +1 -0
- package/dist/generated/models/BackupRecord.d.ts +1100 -0
- package/dist/generated/models/BackupRecord.js +1 -0
- package/dist/generated/models/ContentIssue.d.ts +1318 -0
- package/dist/generated/models/ContentIssue.js +1 -0
- package/dist/generated/models/ContentLock.d.ts +1295 -0
- package/dist/generated/models/ContentLock.js +1 -0
- package/dist/generated/models/ContentTemplate.d.ts +1254 -0
- package/dist/generated/models/ContentTemplate.js +1 -0
- package/dist/generated/models/Document.d.ts +3443 -0
- package/dist/generated/models/Document.js +1 -0
- package/dist/generated/models/Folder.d.ts +1689 -0
- package/dist/generated/models/Folder.js +1 -0
- package/dist/generated/models/FormDeliveryAttempt.d.ts +1705 -0
- package/dist/generated/models/FormDeliveryAttempt.js +1 -0
- package/dist/generated/models/FormSchemaVersion.d.ts +1105 -0
- package/dist/generated/models/FormSchemaVersion.js +1 -0
- package/dist/generated/models/FormSubmission.d.ts +2125 -0
- package/dist/generated/models/FormSubmission.js +1 -0
- package/dist/generated/models/FormSubmissionFile.d.ts +1438 -0
- package/dist/generated/models/FormSubmissionFile.js +1 -0
- package/dist/generated/models/FormWebhook.d.ts +1271 -0
- package/dist/generated/models/FormWebhook.js +1 -0
- package/dist/generated/models/InAppNotification.d.ts +1295 -0
- package/dist/generated/models/InAppNotification.js +1 -0
- package/dist/generated/models/Invite.d.ts +1240 -0
- package/dist/generated/models/Invite.js +1 -0
- package/dist/generated/models/Media.d.ts +2759 -0
- package/dist/generated/models/Media.js +1 -0
- package/dist/generated/models/MediaUsage.d.ts +1301 -0
- package/dist/generated/models/MediaUsage.js +1 -0
- package/dist/generated/models/OAuthAccount.d.ts +1300 -0
- package/dist/generated/models/OAuthAccount.js +1 -0
- package/dist/generated/models/PageTemplate.d.ts +1118 -0
- package/dist/generated/models/PageTemplate.js +1 -0
- package/dist/generated/models/PasswordResetToken.d.ts +1225 -0
- package/dist/generated/models/PasswordResetToken.js +1 -0
- package/dist/generated/models/Redirect.d.ts +1342 -0
- package/dist/generated/models/Redirect.js +1 -0
- package/dist/generated/models/Redirect404Hit.d.ts +1107 -0
- package/dist/generated/models/Redirect404Hit.js +1 -0
- package/dist/generated/models/RedirectSuggestion.d.ts +1185 -0
- package/dist/generated/models/RedirectSuggestion.js +1 -0
- package/dist/generated/models/SavedSection.d.ts +1156 -0
- package/dist/generated/models/SavedSection.js +1 -0
- package/dist/generated/models/ScriptTag.d.ts +1291 -0
- package/dist/generated/models/ScriptTag.js +1 -0
- package/dist/generated/models/SeoAuditRun.d.ts +1570 -0
- package/dist/generated/models/SeoAuditRun.js +1 -0
- package/dist/generated/models/SeoIssue.d.ts +1873 -0
- package/dist/generated/models/SeoIssue.js +1 -0
- package/dist/generated/models/Session.d.ts +1330 -0
- package/dist/generated/models/Session.js +1 -0
- package/dist/generated/models/Site.d.ts +1062 -0
- package/dist/generated/models/Site.js +1 -0
- package/dist/generated/models/User.d.ts +4344 -0
- package/dist/generated/models/User.js +1 -0
- package/dist/generated/models/Version.d.ts +1334 -0
- package/dist/generated/models/Version.js +1 -0
- package/dist/generated/models/WebhookDeliveryLog.d.ts +1479 -0
- package/dist/generated/models/WebhookDeliveryLog.js +1 -0
- package/dist/generated/models/WebhookEndpoint.d.ts +1331 -0
- package/dist/generated/models/WebhookEndpoint.js +1 -0
- package/dist/generated/models/WorkflowState.d.ts +1436 -0
- package/dist/generated/models/WorkflowState.js +1 -0
- package/dist/generated/models.d.ts +37 -0
- package/dist/generated/models.js +1 -0
- package/dist/graphql/index.d.ts +11 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +74 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/resolvers.d.ts +8 -0
- package/dist/graphql/resolvers.d.ts.map +1 -0
- package/dist/graphql/resolvers.js +89 -0
- package/dist/graphql/resolvers.js.map +1 -0
- package/dist/graphql/schema-builder.d.ts +3 -0
- package/dist/graphql/schema-builder.d.ts.map +1 -0
- package/dist/graphql/schema-builder.js +103 -0
- package/dist/graphql/schema-builder.js.map +1 -0
- package/dist/health/index.d.ts +27 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +43 -0
- package/dist/health/index.js.map +1 -0
- package/dist/i18n/index.d.ts +22 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +37 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +125 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -0
- package/dist/media/content-hash.d.ts +3 -0
- package/dist/media/content-hash.d.ts.map +1 -0
- package/dist/media/content-hash.js +11 -0
- package/dist/media/content-hash.js.map +1 -0
- package/dist/media/denormalize.d.ts +23 -0
- package/dist/media/denormalize.d.ts.map +1 -0
- package/dist/media/denormalize.js +170 -0
- package/dist/media/denormalize.js.map +1 -0
- package/dist/media/index.d.ts +8 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +5 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/merge-document-media.d.ts +23 -0
- package/dist/media/merge-document-media.d.ts.map +1 -0
- package/dist/media/merge-document-media.js +146 -0
- package/dist/media/merge-document-media.js.map +1 -0
- package/dist/media/metadata-patch.d.ts +21 -0
- package/dist/media/metadata-patch.d.ts.map +1 -0
- package/dist/media/metadata-patch.js +37 -0
- package/dist/media/metadata-patch.js.map +1 -0
- package/dist/media/optimize.d.ts +61 -0
- package/dist/media/optimize.d.ts.map +1 -0
- package/dist/media/optimize.js +168 -0
- package/dist/media/optimize.js.map +1 -0
- package/dist/media/seo.d.ts +110 -0
- package/dist/media/seo.d.ts.map +1 -0
- package/dist/media/seo.js +263 -0
- package/dist/media/seo.js.map +1 -0
- package/dist/media/usage-sync.d.ts +37 -0
- package/dist/media/usage-sync.d.ts.map +1 -0
- package/dist/media/usage-sync.js +234 -0
- package/dist/media/usage-sync.js.map +1 -0
- package/dist/middleware.d.ts +17 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +159 -0
- package/dist/middleware.js.map +1 -0
- package/dist/multisite/index.d.ts +20 -0
- package/dist/multisite/index.d.ts.map +1 -0
- package/dist/multisite/index.js +26 -0
- package/dist/multisite/index.js.map +1 -0
- package/dist/navigation/unresolved-targets.d.ts +35 -0
- package/dist/navigation/unresolved-targets.d.ts.map +1 -0
- package/dist/navigation/unresolved-targets.js +109 -0
- package/dist/navigation/unresolved-targets.js.map +1 -0
- package/dist/next/preview.d.ts +10 -0
- package/dist/next/preview.d.ts.map +1 -0
- package/dist/next/preview.js +17 -0
- package/dist/next/preview.js.map +1 -0
- package/dist/next/script-tags.d.ts +15 -0
- package/dist/next/script-tags.d.ts.map +1 -0
- package/dist/next/script-tags.js +58 -0
- package/dist/next/script-tags.js.map +1 -0
- package/dist/next-site.d.ts +29 -0
- package/dist/next-site.d.ts.map +1 -0
- package/dist/next-site.js +60 -0
- package/dist/next-site.js.map +1 -0
- package/dist/next.d.ts +33 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +142 -0
- package/dist/next.js.map +1 -0
- package/dist/notifications/index.d.ts +20 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +22 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/page-builder/a11y-fix.d.ts +20 -0
- package/dist/page-builder/a11y-fix.d.ts.map +1 -0
- package/dist/page-builder/a11y-fix.js +182 -0
- package/dist/page-builder/a11y-fix.js.map +1 -0
- package/dist/page-builder/ai-pipeline.d.ts +43 -0
- package/dist/page-builder/ai-pipeline.d.ts.map +1 -0
- package/dist/page-builder/ai-pipeline.js +165 -0
- package/dist/page-builder/ai-pipeline.js.map +1 -0
- package/dist/page-builder/blocks.d.ts +27 -0
- package/dist/page-builder/blocks.d.ts.map +1 -0
- package/dist/page-builder/blocks.js +313 -0
- package/dist/page-builder/blocks.js.map +1 -0
- package/dist/page-builder/design-scorer.d.ts +27 -0
- package/dist/page-builder/design-scorer.d.ts.map +1 -0
- package/dist/page-builder/design-scorer.js +612 -0
- package/dist/page-builder/design-scorer.js.map +1 -0
- package/dist/page-builder/index.d.ts +18 -0
- package/dist/page-builder/index.d.ts.map +1 -0
- package/dist/page-builder/index.js +20 -0
- package/dist/page-builder/index.js.map +1 -0
- package/dist/page-builder/schema.d.ts +107 -0
- package/dist/page-builder/schema.d.ts.map +1 -0
- package/dist/page-builder/schema.js +90 -0
- package/dist/page-builder/schema.js.map +1 -0
- package/dist/page-builder/seo-analyzer.d.ts +61 -0
- package/dist/page-builder/seo-analyzer.d.ts.map +1 -0
- package/dist/page-builder/seo-analyzer.js +778 -0
- package/dist/page-builder/seo-analyzer.js.map +1 -0
- package/dist/page-builder/templates.d.ts +9 -0
- package/dist/page-builder/templates.d.ts.map +1 -0
- package/dist/page-builder/templates.js +742 -0
- package/dist/page-builder/templates.js.map +1 -0
- package/dist/page-builder/tree.d.ts +17 -0
- package/dist/page-builder/tree.d.ts.map +1 -0
- package/dist/page-builder/tree.js +160 -0
- package/dist/page-builder/tree.js.map +1 -0
- package/dist/page-builder/types.d.ts +112 -0
- package/dist/page-builder/types.d.ts.map +1 -0
- package/dist/page-builder/types.js +4 -0
- package/dist/page-builder/types.js.map +1 -0
- package/dist/page-builder/validate.d.ts +6 -0
- package/dist/page-builder/validate.d.ts.map +1 -0
- package/dist/page-builder/validate.js +87 -0
- package/dist/page-builder/validate.js.map +1 -0
- package/dist/plan/features.d.ts +17 -0
- package/dist/plan/features.d.ts.map +1 -0
- package/dist/plan/features.js +73 -0
- package/dist/plan/features.js.map +1 -0
- package/dist/posts/query.d.ts +50 -0
- package/dist/posts/query.d.ts.map +1 -0
- package/dist/posts/query.js +105 -0
- package/dist/posts/query.js.map +1 -0
- package/dist/presence/index.d.ts +24 -0
- package/dist/presence/index.d.ts.map +1 -0
- package/dist/presence/index.js +99 -0
- package/dist/presence/index.js.map +1 -0
- package/dist/preview/event-bus.d.ts +39 -0
- package/dist/preview/event-bus.d.ts.map +1 -0
- package/dist/preview/event-bus.js +73 -0
- package/dist/preview/event-bus.js.map +1 -0
- package/dist/preview/index.d.ts +36 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +68 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/preview/sse-stream.d.ts +47 -0
- package/dist/preview/sse-stream.d.ts.map +1 -0
- package/dist/preview/sse-stream.js +111 -0
- package/dist/preview/sse-stream.js.map +1 -0
- package/dist/privacy/index.d.ts +33 -0
- package/dist/privacy/index.d.ts.map +1 -0
- package/dist/privacy/index.js +15 -0
- package/dist/privacy/index.js.map +1 -0
- package/dist/public-redirects.d.ts +78 -0
- package/dist/public-redirects.d.ts.map +1 -0
- package/dist/public-redirects.js +187 -0
- package/dist/public-redirects.js.map +1 -0
- package/dist/public-security-headers.d.ts +27 -0
- package/dist/public-security-headers.d.ts.map +1 -0
- package/dist/public-security-headers.js +40 -0
- package/dist/public-security-headers.js.map +1 -0
- package/dist/publishing/publish-sync.d.ts +50 -0
- package/dist/publishing/publish-sync.d.ts.map +1 -0
- package/dist/publishing/publish-sync.js +124 -0
- package/dist/publishing/publish-sync.js.map +1 -0
- package/dist/purge/adapters.d.ts +60 -0
- package/dist/purge/adapters.d.ts.map +1 -0
- package/dist/purge/adapters.js +81 -0
- package/dist/purge/adapters.js.map +1 -0
- package/dist/purge/index.d.ts +12 -0
- package/dist/purge/index.d.ts.map +1 -0
- package/dist/purge/index.js +10 -0
- package/dist/purge/index.js.map +1 -0
- package/dist/purge/receiver.d.ts +95 -0
- package/dist/purge/receiver.d.ts.map +1 -0
- package/dist/purge/receiver.js +176 -0
- package/dist/purge/receiver.js.map +1 -0
- package/dist/realtime/comments.d.ts +217 -0
- package/dist/realtime/comments.d.ts.map +1 -0
- package/dist/realtime/comments.js +282 -0
- package/dist/realtime/comments.js.map +1 -0
- package/dist/realtime/gateway.d.ts +107 -0
- package/dist/realtime/gateway.d.ts.map +1 -0
- package/dist/realtime/gateway.js +215 -0
- package/dist/realtime/gateway.js.map +1 -0
- package/dist/realtime/index.d.ts +21 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +27 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/notification-bus.d.ts +60 -0
- package/dist/realtime/notification-bus.d.ts.map +1 -0
- package/dist/realtime/notification-bus.js +77 -0
- package/dist/realtime/notification-bus.js.map +1 -0
- package/dist/realtime/notification-stream.d.ts +50 -0
- package/dist/realtime/notification-stream.d.ts.map +1 -0
- package/dist/realtime/notification-stream.js +148 -0
- package/dist/realtime/notification-stream.js.map +1 -0
- package/dist/realtime/notifications.d.ts +201 -0
- package/dist/realtime/notifications.d.ts.map +1 -0
- package/dist/realtime/notifications.js +233 -0
- package/dist/realtime/notifications.js.map +1 -0
- package/dist/realtime/persistence.d.ts +72 -0
- package/dist/realtime/persistence.d.ts.map +1 -0
- package/dist/realtime/persistence.js +53 -0
- package/dist/realtime/persistence.js.map +1 -0
- package/dist/realtime/sse.d.ts +20 -0
- package/dist/realtime/sse.d.ts.map +1 -0
- package/dist/realtime/sse.js +31 -0
- package/dist/realtime/sse.js.map +1 -0
- package/dist/redirects/auto.d.ts +45 -0
- package/dist/redirects/auto.d.ts.map +1 -0
- package/dist/redirects/auto.js +60 -0
- package/dist/redirects/auto.js.map +1 -0
- package/dist/redirects/fix-link.d.ts +15 -0
- package/dist/redirects/fix-link.d.ts.map +1 -0
- package/dist/redirects/fix-link.js +66 -0
- package/dist/redirects/fix-link.js.map +1 -0
- package/dist/redirects/graph.d.ts +92 -0
- package/dist/redirects/graph.d.ts.map +1 -0
- package/dist/redirects/graph.js +176 -0
- package/dist/redirects/graph.js.map +1 -0
- package/dist/redirects/index.d.ts +17 -0
- package/dist/redirects/index.d.ts.map +1 -0
- package/dist/redirects/index.js +10 -0
- package/dist/redirects/index.js.map +1 -0
- package/dist/redirects/legitimacy.d.ts +23 -0
- package/dist/redirects/legitimacy.d.ts.map +1 -0
- package/dist/redirects/legitimacy.js +64 -0
- package/dist/redirects/legitimacy.js.map +1 -0
- package/dist/redirects/recovery.d.ts +92 -0
- package/dist/redirects/recovery.d.ts.map +1 -0
- package/dist/redirects/recovery.js +210 -0
- package/dist/redirects/recovery.js.map +1 -0
- package/dist/redirects/rerank.d.ts +24 -0
- package/dist/redirects/rerank.d.ts.map +1 -0
- package/dist/redirects/rerank.js +82 -0
- package/dist/redirects/rerank.js.map +1 -0
- package/dist/redirects/suggest.d.ts +23 -0
- package/dist/redirects/suggest.d.ts.map +1 -0
- package/dist/redirects/suggest.js +80 -0
- package/dist/redirects/suggest.js.map +1 -0
- package/dist/redirects/typo.d.ts +10 -0
- package/dist/redirects/typo.d.ts.map +1 -0
- package/dist/redirects/typo.js +51 -0
- package/dist/redirects/typo.js.map +1 -0
- package/dist/redirects/validate-destination.d.ts +13 -0
- package/dist/redirects/validate-destination.d.ts.map +1 -0
- package/dist/redirects/validate-destination.js +51 -0
- package/dist/redirects/validate-destination.js.map +1 -0
- package/dist/relationships/index.d.ts +13 -0
- package/dist/relationships/index.d.ts.map +1 -0
- package/dist/relationships/index.js +12 -0
- package/dist/relationships/index.js.map +1 -0
- package/dist/scheduling/index.d.ts +45 -0
- package/dist/scheduling/index.d.ts.map +1 -0
- package/dist/scheduling/index.js +141 -0
- package/dist/scheduling/index.js.map +1 -0
- package/dist/script-tags/index.d.ts +3 -0
- package/dist/script-tags/index.d.ts.map +1 -0
- package/dist/script-tags/index.js +3 -0
- package/dist/script-tags/index.js.map +1 -0
- package/dist/script-tags/invalidate.d.ts +3 -0
- package/dist/script-tags/invalidate.d.ts.map +1 -0
- package/dist/script-tags/invalidate.js +16 -0
- package/dist/script-tags/invalidate.js.map +1 -0
- package/dist/script-tags/resolve.d.ts +41 -0
- package/dist/script-tags/resolve.d.ts.map +1 -0
- package/dist/script-tags/resolve.js +130 -0
- package/dist/script-tags/resolve.js.map +1 -0
- package/dist/search/index.d.ts +25 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +189 -0
- package/dist/search/index.js.map +1 -0
- package/dist/sections/bridge.d.ts +8 -0
- package/dist/sections/bridge.d.ts.map +1 -0
- package/dist/sections/bridge.js +211 -0
- package/dist/sections/bridge.js.map +1 -0
- package/dist/sections/define.d.ts +21 -0
- package/dist/sections/define.d.ts.map +1 -0
- package/dist/sections/define.js +160 -0
- package/dist/sections/define.js.map +1 -0
- package/dist/sections/embed.d.ts +24 -0
- package/dist/sections/embed.d.ts.map +1 -0
- package/dist/sections/embed.js +78 -0
- package/dist/sections/embed.js.map +1 -0
- package/dist/sections/fields.d.ts +37 -0
- package/dist/sections/fields.d.ts.map +1 -0
- package/dist/sections/fields.js +50 -0
- package/dist/sections/fields.js.map +1 -0
- package/dist/sections/helpers.d.ts +149 -0
- package/dist/sections/helpers.d.ts.map +1 -0
- package/dist/sections/helpers.js +373 -0
- package/dist/sections/helpers.js.map +1 -0
- package/dist/sections/index.d.ts +22 -0
- package/dist/sections/index.d.ts.map +1 -0
- package/dist/sections/index.js +18 -0
- package/dist/sections/index.js.map +1 -0
- package/dist/sections/media-enrich.d.ts +45 -0
- package/dist/sections/media-enrich.d.ts.map +1 -0
- package/dist/sections/media-enrich.js +137 -0
- package/dist/sections/media-enrich.js.map +1 -0
- package/dist/sections/migrate.d.ts +42 -0
- package/dist/sections/migrate.d.ts.map +1 -0
- package/dist/sections/migrate.js +66 -0
- package/dist/sections/migrate.js.map +1 -0
- package/dist/sections/registry.d.ts +46 -0
- package/dist/sections/registry.d.ts.map +1 -0
- package/dist/sections/registry.js +598 -0
- package/dist/sections/registry.js.map +1 -0
- package/dist/sections/types.d.ts +192 -0
- package/dist/sections/types.d.ts.map +1 -0
- package/dist/sections/types.js +21 -0
- package/dist/sections/types.js.map +1 -0
- package/dist/sections/validate.d.ts +12 -0
- package/dist/sections/validate.d.ts.map +1 -0
- package/dist/sections/validate.js +186 -0
- package/dist/sections/validate.js.map +1 -0
- package/dist/sections/widget-embed.d.ts +45 -0
- package/dist/sections/widget-embed.d.ts.map +1 -0
- package/dist/sections/widget-embed.js +129 -0
- package/dist/sections/widget-embed.js.map +1 -0
- package/dist/security/access.d.ts +35 -0
- package/dist/security/access.d.ts.map +1 -0
- package/dist/security/access.js +133 -0
- package/dist/security/access.js.map +1 -0
- package/dist/security/anomaly-detection.d.ts +17 -0
- package/dist/security/anomaly-detection.d.ts.map +1 -0
- package/dist/security/anomaly-detection.js +39 -0
- package/dist/security/anomaly-detection.js.map +1 -0
- package/dist/security/api-key-enhanced.d.ts +68 -0
- package/dist/security/api-key-enhanced.d.ts.map +1 -0
- package/dist/security/api-key-enhanced.js +76 -0
- package/dist/security/api-key-enhanced.js.map +1 -0
- package/dist/security/audit.d.ts +46 -0
- package/dist/security/audit.d.ts.map +1 -0
- package/dist/security/audit.js +67 -0
- package/dist/security/audit.js.map +1 -0
- package/dist/security/breach-check.d.ts +3 -0
- package/dist/security/breach-check.d.ts.map +1 -0
- package/dist/security/breach-check.js +27 -0
- package/dist/security/breach-check.js.map +1 -0
- package/dist/security/captcha.d.ts +67 -0
- package/dist/security/captcha.d.ts.map +1 -0
- package/dist/security/captcha.js +186 -0
- package/dist/security/captcha.js.map +1 -0
- package/dist/security/center/gather.d.ts +20 -0
- package/dist/security/center/gather.d.ts.map +1 -0
- package/dist/security/center/gather.js +240 -0
- package/dist/security/center/gather.js.map +1 -0
- package/dist/security/center/index.d.ts +13 -0
- package/dist/security/center/index.d.ts.map +1 -0
- package/dist/security/center/index.js +4 -0
- package/dist/security/center/index.js.map +1 -0
- package/dist/security/center/settings.d.ts +48 -0
- package/dist/security/center/settings.d.ts.map +1 -0
- package/dist/security/center/settings.js +218 -0
- package/dist/security/center/settings.js.map +1 -0
- package/dist/security/center/status.d.ts +10 -0
- package/dist/security/center/status.d.ts.map +1 -0
- package/dist/security/center/status.js +249 -0
- package/dist/security/center/status.js.map +1 -0
- package/dist/security/center/summaries.d.ts +21 -0
- package/dist/security/center/summaries.d.ts.map +1 -0
- package/dist/security/center/summaries.js +65 -0
- package/dist/security/center/summaries.js.map +1 -0
- package/dist/security/center/types.d.ts +142 -0
- package/dist/security/center/types.d.ts.map +1 -0
- package/dist/security/center/types.js +12 -0
- package/dist/security/center/types.js.map +1 -0
- package/dist/security/client-ip.d.ts +23 -0
- package/dist/security/client-ip.d.ts.map +1 -0
- package/dist/security/client-ip.js +97 -0
- package/dist/security/client-ip.js.map +1 -0
- package/dist/security/cors.d.ts +11 -0
- package/dist/security/cors.d.ts.map +1 -0
- package/dist/security/cors.js +33 -0
- package/dist/security/cors.js.map +1 -0
- package/dist/security/csp-nonces.d.ts +5 -0
- package/dist/security/csp-nonces.d.ts.map +1 -0
- package/dist/security/csp-nonces.js +24 -0
- package/dist/security/csp-nonces.js.map +1 -0
- package/dist/security/csrf.d.ts +5 -0
- package/dist/security/csrf.d.ts.map +1 -0
- package/dist/security/csrf.js +20 -0
- package/dist/security/csrf.js.map +1 -0
- package/dist/security/encrypted-fields.d.ts +14 -0
- package/dist/security/encrypted-fields.d.ts.map +1 -0
- package/dist/security/encrypted-fields.js +94 -0
- package/dist/security/encrypted-fields.js.map +1 -0
- package/dist/security/headers.d.ts +11 -0
- package/dist/security/headers.d.ts.map +1 -0
- package/dist/security/headers.js +32 -0
- package/dist/security/headers.js.map +1 -0
- package/dist/security/index.d.ts +40 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +26 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/internal-keys.d.ts +15 -0
- package/dist/security/internal-keys.d.ts.map +1 -0
- package/dist/security/internal-keys.js +33 -0
- package/dist/security/internal-keys.js.map +1 -0
- package/dist/security/ip-allowlist.d.ts +15 -0
- package/dist/security/ip-allowlist.d.ts.map +1 -0
- package/dist/security/ip-allowlist.js +141 -0
- package/dist/security/ip-allowlist.js.map +1 -0
- package/dist/security/ip-canon.d.ts +71 -0
- package/dist/security/ip-canon.d.ts.map +1 -0
- package/dist/security/ip-canon.js +352 -0
- package/dist/security/ip-canon.js.map +1 -0
- package/dist/security/middleware.d.ts +20 -0
- package/dist/security/middleware.d.ts.map +1 -0
- package/dist/security/middleware.js +45 -0
- package/dist/security/middleware.js.map +1 -0
- package/dist/security/rate-limit-policy.d.ts +40 -0
- package/dist/security/rate-limit-policy.d.ts.map +1 -0
- package/dist/security/rate-limit-policy.js +57 -0
- package/dist/security/rate-limit-policy.js.map +1 -0
- package/dist/security/rate-limit.d.ts +55 -0
- package/dist/security/rate-limit.d.ts.map +1 -0
- package/dist/security/rate-limit.js +298 -0
- package/dist/security/rate-limit.js.map +1 -0
- package/dist/security/reauth.d.ts +16 -0
- package/dist/security/reauth.d.ts.map +1 -0
- package/dist/security/reauth.js +56 -0
- package/dist/security/reauth.js.map +1 -0
- package/dist/security/redact.d.ts +12 -0
- package/dist/security/redact.d.ts.map +1 -0
- package/dist/security/redact.js +44 -0
- package/dist/security/redact.js.map +1 -0
- package/dist/security/safe-fetch.d.ts +57 -0
- package/dist/security/safe-fetch.d.ts.map +1 -0
- package/dist/security/safe-fetch.js +71 -0
- package/dist/security/safe-fetch.js.map +1 -0
- package/dist/security/sanitize-document.d.ts +14 -0
- package/dist/security/sanitize-document.d.ts.map +1 -0
- package/dist/security/sanitize-document.js +117 -0
- package/dist/security/sanitize-document.js.map +1 -0
- package/dist/security/sanitize.d.ts +13 -0
- package/dist/security/sanitize.d.ts.map +1 -0
- package/dist/security/sanitize.js +64 -0
- package/dist/security/sanitize.js.map +1 -0
- package/dist/security/secret-storage.d.ts +22 -0
- package/dist/security/secret-storage.d.ts.map +1 -0
- package/dist/security/secret-storage.js +75 -0
- package/dist/security/secret-storage.js.map +1 -0
- package/dist/security/security-txt.d.ts +12 -0
- package/dist/security/security-txt.d.ts.map +1 -0
- package/dist/security/security-txt.js +19 -0
- package/dist/security/security-txt.js.map +1 -0
- package/dist/security/session-limits.d.ts +17 -0
- package/dist/security/session-limits.d.ts.map +1 -0
- package/dist/security/session-limits.js +14 -0
- package/dist/security/session-limits.js.map +1 -0
- package/dist/security/upload.d.ts +32 -0
- package/dist/security/upload.d.ts.map +1 -0
- package/dist/security/upload.js +129 -0
- package/dist/security/upload.js.map +1 -0
- package/dist/security/webhook.d.ts +30 -0
- package/dist/security/webhook.d.ts.map +1 -0
- package/dist/security/webhook.js +133 -0
- package/dist/security/webhook.js.map +1 -0
- package/dist/seo/ai-bots.d.ts +56 -0
- package/dist/seo/ai-bots.d.ts.map +1 -0
- package/dist/seo/ai-bots.js +125 -0
- package/dist/seo/ai-bots.js.map +1 -0
- package/dist/seo/ai-crawler-log.d.ts +14 -0
- package/dist/seo/ai-crawler-log.d.ts.map +1 -0
- package/dist/seo/ai-crawler-log.js +48 -0
- package/dist/seo/ai-crawler-log.js.map +1 -0
- package/dist/seo/alt-text.d.ts +39 -0
- package/dist/seo/alt-text.d.ts.map +1 -0
- package/dist/seo/alt-text.js +67 -0
- package/dist/seo/alt-text.js.map +1 -0
- package/dist/seo/analysis.d.ts +75 -0
- package/dist/seo/analysis.d.ts.map +1 -0
- package/dist/seo/analysis.js +641 -0
- package/dist/seo/analysis.js.map +1 -0
- package/dist/seo/audit-engine.d.ts +124 -0
- package/dist/seo/audit-engine.d.ts.map +1 -0
- package/dist/seo/audit-engine.js +534 -0
- package/dist/seo/audit-engine.js.map +1 -0
- package/dist/seo/audit-runner.d.ts +84 -0
- package/dist/seo/audit-runner.d.ts.map +1 -0
- package/dist/seo/audit-runner.js +372 -0
- package/dist/seo/audit-runner.js.map +1 -0
- package/dist/seo/audit-signals.d.ts +28 -0
- package/dist/seo/audit-signals.d.ts.map +1 -0
- package/dist/seo/audit-signals.js +133 -0
- package/dist/seo/audit-signals.js.map +1 -0
- package/dist/seo/config-store.d.ts +99 -0
- package/dist/seo/config-store.d.ts.map +1 -0
- package/dist/seo/config-store.js +194 -0
- package/dist/seo/config-store.js.map +1 -0
- package/dist/seo/confirm-broken-links.d.ts +48 -0
- package/dist/seo/confirm-broken-links.d.ts.map +1 -0
- package/dist/seo/confirm-broken-links.js +135 -0
- package/dist/seo/confirm-broken-links.js.map +1 -0
- package/dist/seo/content-markdown.d.ts +26 -0
- package/dist/seo/content-markdown.d.ts.map +1 -0
- package/dist/seo/content-markdown.js +235 -0
- package/dist/seo/content-markdown.js.map +1 -0
- package/dist/seo/document-content.d.ts +18 -0
- package/dist/seo/document-content.d.ts.map +1 -0
- package/dist/seo/document-content.js +80 -0
- package/dist/seo/document-content.js.map +1 -0
- package/dist/seo/document-path.d.ts +33 -0
- package/dist/seo/document-path.d.ts.map +1 -0
- package/dist/seo/document-path.js +44 -0
- package/dist/seo/document-path.js.map +1 -0
- package/dist/seo/feeds.d.ts +44 -0
- package/dist/seo/feeds.d.ts.map +1 -0
- package/dist/seo/feeds.js +95 -0
- package/dist/seo/feeds.js.map +1 -0
- package/dist/seo/fix-brief-export.d.ts +47 -0
- package/dist/seo/fix-brief-export.d.ts.map +1 -0
- package/dist/seo/fix-brief-export.js +224 -0
- package/dist/seo/fix-brief-export.js.map +1 -0
- package/dist/seo/index-status.d.ts +56 -0
- package/dist/seo/index-status.d.ts.map +1 -0
- package/dist/seo/index-status.js +193 -0
- package/dist/seo/index-status.js.map +1 -0
- package/dist/seo/index.d.ts +68 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +36 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/issue-batch-briefs.d.ts +37 -0
- package/dist/seo/issue-batch-briefs.d.ts.map +1 -0
- package/dist/seo/issue-batch-briefs.js +85 -0
- package/dist/seo/issue-batch-briefs.js.map +1 -0
- package/dist/seo/issue-content-brief.d.ts +51 -0
- package/dist/seo/issue-content-brief.d.ts.map +1 -0
- package/dist/seo/issue-content-brief.js +267 -0
- package/dist/seo/issue-content-brief.js.map +1 -0
- package/dist/seo/issue-content-research.d.ts +51 -0
- package/dist/seo/issue-content-research.d.ts.map +1 -0
- package/dist/seo/issue-content-research.js +303 -0
- package/dist/seo/issue-content-research.js.map +1 -0
- package/dist/seo/issue-fix-ai.d.ts +39 -0
- package/dist/seo/issue-fix-ai.d.ts.map +1 -0
- package/dist/seo/issue-fix-ai.js +320 -0
- package/dist/seo/issue-fix-ai.js.map +1 -0
- package/dist/seo/issue-fix-redirect.d.ts +18 -0
- package/dist/seo/issue-fix-redirect.d.ts.map +1 -0
- package/dist/seo/issue-fix-redirect.js +153 -0
- package/dist/seo/issue-fix-redirect.js.map +1 -0
- package/dist/seo/issue-fix.d.ts +72 -0
- package/dist/seo/issue-fix.d.ts.map +1 -0
- package/dist/seo/issue-fix.js +266 -0
- package/dist/seo/issue-fix.js.map +1 -0
- package/dist/seo/link-graph.d.ts +54 -0
- package/dist/seo/link-graph.d.ts.map +1 -0
- package/dist/seo/link-graph.js +0 -0
- package/dist/seo/link-graph.js.map +1 -0
- package/dist/seo/link-probe.d.ts +49 -0
- package/dist/seo/link-probe.d.ts.map +1 -0
- package/dist/seo/link-probe.js +147 -0
- package/dist/seo/link-probe.js.map +1 -0
- package/dist/seo/llms-select.d.ts +34 -0
- package/dist/seo/llms-select.d.ts.map +1 -0
- package/dist/seo/llms-select.js +67 -0
- package/dist/seo/llms-select.js.map +1 -0
- package/dist/seo/llms-txt.d.ts +40 -0
- package/dist/seo/llms-txt.d.ts.map +1 -0
- package/dist/seo/llms-txt.js +177 -0
- package/dist/seo/llms-txt.js.map +1 -0
- package/dist/seo/meta-tags.d.ts +108 -0
- package/dist/seo/meta-tags.d.ts.map +1 -0
- package/dist/seo/meta-tags.js +353 -0
- package/dist/seo/meta-tags.js.map +1 -0
- package/dist/seo/migrate-meta.d.ts +23 -0
- package/dist/seo/migrate-meta.d.ts.map +1 -0
- package/dist/seo/migrate-meta.js +114 -0
- package/dist/seo/migrate-meta.js.map +1 -0
- package/dist/seo/migrate-schema.d.ts +28 -0
- package/dist/seo/migrate-schema.d.ts.map +1 -0
- package/dist/seo/migrate-schema.js +119 -0
- package/dist/seo/migrate-schema.js.map +1 -0
- package/dist/seo/page-meta.d.ts +179 -0
- package/dist/seo/page-meta.d.ts.map +1 -0
- package/dist/seo/page-meta.js +710 -0
- package/dist/seo/page-meta.js.map +1 -0
- package/dist/seo/public-site-url.d.ts +16 -0
- package/dist/seo/public-site-url.d.ts.map +1 -0
- package/dist/seo/public-site-url.js +39 -0
- package/dist/seo/public-site-url.js.map +1 -0
- package/dist/seo/resolve-path.d.ts +22 -0
- package/dist/seo/resolve-path.d.ts.map +1 -0
- package/dist/seo/resolve-path.js +53 -0
- package/dist/seo/resolve-path.js.map +1 -0
- package/dist/seo/rewrite-public-origin.d.ts +12 -0
- package/dist/seo/rewrite-public-origin.d.ts.map +1 -0
- package/dist/seo/rewrite-public-origin.js +17 -0
- package/dist/seo/rewrite-public-origin.js.map +1 -0
- package/dist/seo/robots.d.ts +45 -0
- package/dist/seo/robots.d.ts.map +1 -0
- package/dist/seo/robots.js +90 -0
- package/dist/seo/robots.js.map +1 -0
- package/dist/seo/schema-sync.d.ts +65 -0
- package/dist/seo/schema-sync.d.ts.map +1 -0
- package/dist/seo/schema-sync.js +163 -0
- package/dist/seo/schema-sync.js.map +1 -0
- package/dist/seo/score.d.ts +132 -0
- package/dist/seo/score.d.ts.map +1 -0
- package/dist/seo/score.js +216 -0
- package/dist/seo/score.js.map +1 -0
- package/dist/seo/serving-origin.d.ts +42 -0
- package/dist/seo/serving-origin.d.ts.map +1 -0
- package/dist/seo/serving-origin.js +83 -0
- package/dist/seo/serving-origin.js.map +1 -0
- package/dist/seo/site-identity.d.ts +60 -0
- package/dist/seo/site-identity.d.ts.map +1 -0
- package/dist/seo/site-identity.js +109 -0
- package/dist/seo/site-identity.js.map +1 -0
- package/dist/seo/sitemap-notify.d.ts +123 -0
- package/dist/seo/sitemap-notify.d.ts.map +1 -0
- package/dist/seo/sitemap-notify.js +270 -0
- package/dist/seo/sitemap-notify.js.map +1 -0
- package/dist/seo/sitemap-public.d.ts +43 -0
- package/dist/seo/sitemap-public.d.ts.map +1 -0
- package/dist/seo/sitemap-public.js +213 -0
- package/dist/seo/sitemap-public.js.map +1 -0
- package/dist/seo/title-templates.d.ts +80 -0
- package/dist/seo/title-templates.d.ts.map +1 -0
- package/dist/seo/title-templates.js +175 -0
- package/dist/seo/title-templates.js.map +1 -0
- package/dist/server-site.d.ts +55 -0
- package/dist/server-site.d.ts.map +1 -0
- package/dist/server-site.js +184 -0
- package/dist/server-site.js.map +1 -0
- package/dist/setup/index.d.ts +68 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +153 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/site.d.ts +178 -0
- package/dist/site.d.ts.map +1 -0
- package/dist/site.js +222 -0
- package/dist/site.js.map +1 -0
- package/dist/storage/auto-wire.d.ts +14 -0
- package/dist/storage/auto-wire.d.ts.map +1 -0
- package/dist/storage/auto-wire.js +60 -0
- package/dist/storage/auto-wire.js.map +1 -0
- package/dist/storage/index.d.ts +21 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +14 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/templates/index.d.ts +16 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +23 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/upgrade/changelog.d.ts +18 -0
- package/dist/upgrade/changelog.d.ts.map +1 -0
- package/dist/upgrade/changelog.js +60 -0
- package/dist/upgrade/changelog.js.map +1 -0
- package/dist/upgrade/index.d.ts +7 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +4 -0
- package/dist/upgrade/index.js.map +1 -0
- package/dist/upgrade/upgrade-pr.d.ts +16 -0
- package/dist/upgrade/upgrade-pr.d.ts.map +1 -0
- package/dist/upgrade/upgrade-pr.js +128 -0
- package/dist/upgrade/upgrade-pr.js.map +1 -0
- package/dist/upgrade/version-check.d.ts +59 -0
- package/dist/upgrade/version-check.d.ts.map +1 -0
- package/dist/upgrade/version-check.js +142 -0
- package/dist/upgrade/version-check.js.map +1 -0
- package/dist/validation/publish.d.ts +53 -0
- package/dist/validation/publish.d.ts.map +1 -0
- package/dist/validation/publish.js +75 -0
- package/dist/validation/publish.js.map +1 -0
- package/dist/webhooks/index.d.ts +99 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +387 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/signing.d.ts +68 -0
- package/dist/webhooks/signing.d.ts.map +1 -0
- package/dist/webhooks/signing.js +98 -0
- package/dist/webhooks/signing.js.map +1 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +56 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflows/index.d.ts +30 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/index.js +14 -0
- package/dist/workflows/index.js.map +1 -0
- package/package.json +250 -0
- package/prisma/migrations/0001_init/migration.sql +941 -0
- package/prisma/migrations/0002_media_usage_field_path/migration.sql +11 -0
- package/prisma/migrations/0003_ai_crawler_hits/migration.sql +21 -0
- package/prisma/migrations/0004_drop_search_vector/migration.sql +4 -0
- package/prisma/migrations/0005_script_tag_delivery_controls/migration.sql +8 -0
- package/prisma/migrations/0006_totp_last_step/migration.sql +4 -0
- package/prisma/migrations/0007_workflow_fk_drop_dead_enums/migration.sql +27 -0
- package/prisma/migrations/0008_form_delivery_attempts/migration.sql +29 -0
- package/prisma/migrations/0009_webhook_secret_rotation/migration.sql +6 -0
- package/prisma/migrations/0010_media_is_decorative/migration.sql +3 -0
- package/prisma/migrations/0011_media_content_hash/migration.sql +4 -0
- package/prisma/migrations/0012_rename_to_prenta/migration.sql +89 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +995 -0
- package/prisma/seed.ts +82 -0
- package/schema-fragments/cms-schema.prisma +526 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,2974 @@
|
|
|
1
|
+
# @actuate-media/cms-core
|
|
2
|
+
|
|
3
|
+
## 0.118.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5eea995: Product rename: ActuateCMS is now **Prenta**. Every package moves from the
|
|
8
|
+
`@actuate-media/*` scope to `@prenta/*` with simplified names (`@prenta/core`,
|
|
9
|
+
`@prenta/admin`, `create-prenta`), and version numbers continue from where the
|
|
10
|
+
old scope left off.
|
|
11
|
+
|
|
12
|
+
This is a clean break — no backwards-compat aliases:
|
|
13
|
+
- **npm packages**: `@actuate-media/cms-core` → `@prenta/core`,
|
|
14
|
+
`@actuate-media/cms-admin` → `@prenta/admin`, all other packages keep their
|
|
15
|
+
suffix under `@prenta/*`. `create-actuate-cms` → `create-prenta`
|
|
16
|
+
(`npm create prenta`), `create-actuate-astro` → `create-prenta-astro`.
|
|
17
|
+
CLI bins: `actuate`/`actuate-cms` → `prenta`/`prenta-cms`; `actuate-mcp` →
|
|
18
|
+
`prenta-mcp`.
|
|
19
|
+
- **Database**: migration `0012_rename_to_prenta` renames all 36 `actuate_*`
|
|
20
|
+
tables to `prenta_*` in place (plus prefixed constraints, indexes, and
|
|
21
|
+
sequences). Run `prisma migrate deploy` when upgrading.
|
|
22
|
+
- **Runtime identifiers**: `getActuateConfig` → `getPrentaConfig`,
|
|
23
|
+
`handleActuateAPI` → `handlePrentaAPI`, `ActuateClient` → `PrentaClient`,
|
|
24
|
+
`ActuateDB` → `PrentaDB`, config file `actuate.config.ts` →
|
|
25
|
+
`prenta.config.ts`, CSS scopes `.actuate-admin`/`.actuate-canvas` →
|
|
26
|
+
`.prenta-admin`/`.prenta-canvas`, preview protocol `actuate-preview:*` →
|
|
27
|
+
`prenta-preview:*`, webhook headers `X-Actuate-*` → `X-Prenta-*`, session
|
|
28
|
+
cookie `actuate_session` → `prenta_session`, env vars `ACTUATE_*` →
|
|
29
|
+
`PRENTA_*`, purge route `/api/actuate/purge` → `/api/prenta/purge`.
|
|
30
|
+
- **Sessions and webhooks**: all sessions reset on upgrade (cookie + JWT issuer
|
|
31
|
+
changed); webhook consumers must re-verify signatures against the new
|
|
32
|
+
`X-Prenta-*` headers.
|
|
33
|
+
|
|
34
|
+
The old `@actuate-media/*` packages are deprecated on npm and will receive no
|
|
35
|
+
further releases.
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- Updated dependencies [5eea995]
|
|
40
|
+
- @prenta/realtime@0.2.0
|
|
41
|
+
|
|
42
|
+
## 0.117.0
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- 02be20a: Visual editing Stage 3: path-aware section content updates, preview bridge edit/mode/request-media protocol with suppress-echo for canvas text edits, and media field-kind annotations on default section renderers.
|
|
47
|
+
|
|
48
|
+
## 0.116.0
|
|
49
|
+
|
|
50
|
+
### Minor Changes
|
|
51
|
+
|
|
52
|
+
- 73854fc: Add `siteIdentityJsonLd*` / `jsonLdHasOrganization` so any page can emit resolvable WebSite + Organization JSON-LD (fixes dangling `#organization` @ids). Add `assertServingOriginConfigured` ops guard (Launch Gate E) and wire it into the Astro starter middleware + CMS API route boot; BaseLayout falls back to site-identity JSON-LD when page meta lacks Organization.
|
|
53
|
+
|
|
54
|
+
## 0.115.0
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- 576f076: Fix `blogPostCollection` archivePath footgun: deriving with a new slug/urlPrefix rebases `seo.archivePath` onto the urlPrefix unless set explicitly; `defineConfig` warns on duplicate archive paths. Astro starter/example: hybrid prerender defaults (`404.astro` prerendered; CMS catch-all / home / blog index explicit `prerender = false`).
|
|
59
|
+
|
|
60
|
+
## 0.114.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- dfffe08: Add `publicPageCacheControl()` on `@actuate-media/cms-core/astro/cache` (ADR 0011 cache-static defaults: 1h s-maxage / 24h SWR; preview → `private, no-store`) and wire the Astro starter + example site to use it instead of hardcoded Cache-Control strings. Purge remains path/URL-based per ADR 0011.
|
|
65
|
+
|
|
66
|
+
## 0.113.0
|
|
67
|
+
|
|
68
|
+
### Minor Changes
|
|
69
|
+
|
|
70
|
+
- 38aa813: Resilient `createSiteClient`: default 12s `AbortSignal.timeout()` on CMS fetches, and degrade to `null` on non-OK / network / timeout instead of throwing — so a CMS blip fails open for every Astro/Next public consumer. New export: `DEFAULT_SITE_CLIENT_TIMEOUT_MS`; opt out with `timeoutMs: 0`.
|
|
71
|
+
|
|
72
|
+
## 0.112.0
|
|
73
|
+
|
|
74
|
+
### Minor Changes
|
|
75
|
+
|
|
76
|
+
- cf97a40: Launch-critical Astro SEO + freshness: `documentPublicPath` / `rewritePublicOrigin`, first-class `AstroPageMeta.robots`, admin/MCP path builders (#739), Yoast child sitemap + XSL proxies with origin rewrite, production purge 503 without CDN credentials, globals purge webhooks, and Launch Gate docs.
|
|
77
|
+
|
|
78
|
+
## 0.111.0
|
|
79
|
+
|
|
80
|
+
### Minor Changes
|
|
81
|
+
|
|
82
|
+
- 90f9801: Purge-on-publish as product (ADR 0011): split-deploy public sites can now get ISR-grade freshness — cache-static delivery with instant eviction on publish.
|
|
83
|
+
|
|
84
|
+
**cms-core**
|
|
85
|
+
- New `@actuate-media/cms-core/purge` subpath: `createPublishPurgeReceiver()` — a framework-agnostic `(Request) => Response` handler that consumes publish-lifecycle webhooks. Verifies HMAC signature + timestamp skew (via `verifyActuateWebhook`), dedupes on `X-Actuate-Delivery` (pluggable store, in-memory default, marked only after a successful purge so CMS retries re-purge), maps documents to site paths via `resolvePaths` (default: full purge; `createPrefixPathResolver` helper included), and calls a `CachePurgeAdapter`.
|
|
86
|
+
- Shipped purge adapters (zero-dependency, plain fetch): `createCloudflarePurgeAdapter` (zone `purge_cache` by URL with 30-file batching, `purge_everything` fallback), `createVercelDeployHookPurgeAdapter` (full purge via redeploy), `createVercelIsrPurgeAdapter` (targeted, redeploy-free revalidation via the prerender bypass token).
|
|
87
|
+
- New `@actuate-media/cms-core/astro/cache` subpath: `createUpstashSiteCacheAdapter()` — a zero-dependency Upstash REST `SiteCacheAdapter` with `delete()`/`clear()` so the purge receiver can drain the runtime KV layer on publish (`onPurged: () => cache.clear()`).
|
|
88
|
+
- New `document.unpublished` webhook event, emitted when a document leaves PUBLISHED (was previously an indistinguishable `document.updated`). Purge receivers rely on it to evict taken-down pages; endpoints subscribed only to `document.updated` no longer receive unpublish transitions.
|
|
89
|
+
- Publish-sync guidance (`publicHostNote`) now points editors at the purge endpoint (`/api/actuate/purge` in the Astro starter) when no publish webhook is configured, instead of only describing TTL lag.
|
|
90
|
+
|
|
91
|
+
**cms-admin**
|
|
92
|
+
- Settings → Webhooks now creates endpoints subscribed to the full publish lifecycle (`document.published`, `document.unpublished`, `document.updated`, `document.deleted`) so purge/rebuild receivers see unpublishes, live-page edits, and deletions — not just publishes.
|
|
93
|
+
|
|
94
|
+
### Patch Changes
|
|
95
|
+
|
|
96
|
+
- 90f9801: Scheduled publish/unpublish now runs through the same effects pipeline as manual publish. Previously `processScheduledPublish` / `processScheduledUnpublish` did a bare `db.document.update()`, so timed publishes never emitted `document.published` / `document.updated` webhooks (purge-on-publish receivers and rebuild hooks were never notified), never notified the sitemap, never invalidated public caches, and skipped the Version audit row, media-usage sync, and plugin `afterUpdate` hooks. Scheduled transitions now call `updateDocument` with the document's last editor as the acting user, which also means the server-side publish gate applies: a scheduled page/post that fails a hard invariant (no title / no URL) stays SCHEDULED and is reported in the cron result's `errors` instead of going live broken.
|
|
97
|
+
|
|
98
|
+
## 0.110.2
|
|
99
|
+
|
|
100
|
+
### Patch Changes
|
|
101
|
+
|
|
102
|
+
- 56c1309: Fix page tag saves silently no-oping when `tags` / `primaryTag` were undeclared (passthrough + scaffold `page-tags` collection), and replace the status-token tag palette with distinct `--tag-*` colors so pink is no longer warning orange.
|
|
103
|
+
|
|
104
|
+
## 0.110.1
|
|
105
|
+
|
|
106
|
+
### Patch Changes
|
|
107
|
+
|
|
108
|
+
- f4766d4: Bump `sharp` to `^0.35.0` so consumers inherit the patched libvips line (GHSA-f88m-g3jw-g9cj) instead of resolving `0.34.5` from the previous pin.
|
|
109
|
+
|
|
110
|
+
## 0.110.0
|
|
111
|
+
|
|
112
|
+
### Minor Changes
|
|
113
|
+
|
|
114
|
+
- 46ea4a7: Hash final stored media bytes (SHA-256) on upload and return the existing record with `deduplicated` / `duplicateOf` when a match exists, so seed re-runs and byte-identical twins no longer accumulate. Opt out with `forceUpload`.
|
|
115
|
+
|
|
116
|
+
## 0.109.0
|
|
117
|
+
|
|
118
|
+
### Minor Changes
|
|
119
|
+
|
|
120
|
+
- e4e55de: Add `POST /media/bulk-update` and `POST /media/bulk-delete` with per-id results (max 50), plus MCP tools `bulk_update_media` and `bulk_delete_media` for library-wide metadata backfills without one round trip per asset.
|
|
121
|
+
|
|
122
|
+
## 0.108.0
|
|
123
|
+
|
|
124
|
+
### Minor Changes
|
|
125
|
+
|
|
126
|
+
- 6eaffa5: Treat decorative media (`isDecorative` + empty alt) as alt-complete in the media SEO scorer, add a non-scoring `low-quality-alt` advisory, deep-merge media fields on document update so partial `featuredImage` patches keep `url`/`id`, and expose `isDecorative` on `update_media`.
|
|
127
|
+
|
|
128
|
+
## 0.107.3
|
|
129
|
+
|
|
130
|
+
### Patch Changes
|
|
131
|
+
|
|
132
|
+
- 1c2afa5: Give anonymous public read endpoints (`/resolve`, `/public/*`, etc.) their own higher rate-limit budget so SSR front-ends sharing one egress IP are not 429’d by crawler traffic.
|
|
133
|
+
|
|
134
|
+
## 0.107.2
|
|
135
|
+
|
|
136
|
+
### Patch Changes
|
|
137
|
+
|
|
138
|
+
- b305f0c: Confirm internal-link “404” audit findings against the live site before flagging, and never suggest or auto-apply a recovery redirect for a source URL that still returns 2xx/3xx (prevents taking hybrid/native routes offline).
|
|
139
|
+
|
|
140
|
+
## 0.107.1
|
|
141
|
+
|
|
142
|
+
### Patch Changes
|
|
143
|
+
|
|
144
|
+
- a26f61a: Fix Live link health false positives: extract outbound URLs from unescaped document fields (not `JSON.stringify`), strip trailing `\` artifacts, probe with GET + browser UA, treat 3xx as healthy and 403/405/429/999 as unverifiable, and skip first-party Vercel Blob media.
|
|
145
|
+
|
|
146
|
+
## 0.107.0
|
|
147
|
+
|
|
148
|
+
### Minor Changes
|
|
149
|
+
|
|
150
|
+
- f676bcc: Resolve View live / View Site from the effective public site URL (`GET /site/public-url`, DB SEO overrides first), warn when that URL matches the CMS admin host, unlock Settings → General site URL overrides for split deploys, and default Astro-split `NEXT_PUBLIC_SITE_URL` to the Astro apex.
|
|
151
|
+
|
|
152
|
+
## 0.106.1
|
|
153
|
+
|
|
154
|
+
### Patch Changes
|
|
155
|
+
|
|
156
|
+
- 9d5dad3: Harden media storage URL SSRF checks via validateWebhookUrl (#566), clamp negative list `page` params so Prisma never sees a negative skip (#561), and cover DocumentEdit's shared unload guard `returnValue` behavior (#553).
|
|
157
|
+
|
|
158
|
+
## 0.106.0
|
|
159
|
+
|
|
160
|
+
### Minor Changes
|
|
161
|
+
|
|
162
|
+
- 3d1a79f: Timestamp-bound HMAC webhook signing with skew/replay verification (`verifyActuateWebhook`), delivery-id headers on form webhooks, publish secret reveal + 24h rotation grace, and consumer verification docs (#679).
|
|
163
|
+
|
|
164
|
+
## 0.105.0
|
|
165
|
+
|
|
166
|
+
### Minor Changes
|
|
167
|
+
|
|
168
|
+
- 78671c4: Distinguish CMS Published from public-host freshness: publish-sync API, webhook delivery inbox/retry, cleanup-cron webhook retries, and admin Public host sync + Settings → Webhooks UI (#677).
|
|
169
|
+
|
|
170
|
+
## 0.104.0
|
|
171
|
+
|
|
172
|
+
### Minor Changes
|
|
173
|
+
|
|
174
|
+
- 258cbc3: Warn when navigation items link to unpublished or missing internal pages (soft warnings in MCP `validate_navigation_tree`, admin nav builder, and `POST /navigations/validate-targets`).
|
|
175
|
+
|
|
176
|
+
## 0.103.0
|
|
177
|
+
|
|
178
|
+
### Minor Changes
|
|
179
|
+
|
|
180
|
+
- 2f2fe29: First-class CAPTCHA policy on public form submit: enforce toggle, fail-open/closed on provider outage with timeout + audit logging, and Settings → Security status card (#676).
|
|
181
|
+
|
|
182
|
+
## 0.102.0
|
|
183
|
+
|
|
184
|
+
### Minor Changes
|
|
185
|
+
|
|
186
|
+
- f8a2c07: Persist per-submission form notify/webhook delivery attempts with admin Deliveries inbox, retry, and operator alerts on permanent failure (#675).
|
|
187
|
+
|
|
188
|
+
## 0.101.2
|
|
189
|
+
|
|
190
|
+
### Patch Changes
|
|
191
|
+
|
|
192
|
+
- afceb12: Await form submission notification/webhook side effects so they complete on serverless hosts instead of being frozen after the response returns.
|
|
193
|
+
|
|
194
|
+
## 0.101.1
|
|
195
|
+
|
|
196
|
+
### Patch Changes
|
|
197
|
+
|
|
198
|
+
- 81aa012: Fix editor save wiping undeclared SEO/media fields (#671), align analysis/readability word counts, and close related clobber paths: SEO write passthrough keys, nested `{ data }` document PUT flatten, flat pages-service payloads, and `focusKeyphrase` → `focusKeyword` normalization.
|
|
199
|
+
|
|
200
|
+
## 0.101.0
|
|
201
|
+
|
|
202
|
+
### Minor Changes
|
|
203
|
+
|
|
204
|
+
- 58f3ca1: Split-deploy hardening: denormalize media IDs on document write, regenerate JSON-LD on published updates, include Organization parent for Corporation/OnlineBusiness subtypes, and ship Astro security-headers middleware matching Next baselines.
|
|
205
|
+
|
|
206
|
+
## 0.100.0
|
|
207
|
+
|
|
208
|
+
### Minor Changes
|
|
209
|
+
|
|
210
|
+
- cc6ad93: Form notification defaults and shortcode glossary: Reply-To defaults to submitter email, subject includes site name, emails include attribution, Vercel adapter preserves replyTo, and the admin Notifications panel ships a copy-paste shortcode glossary plus Send test.
|
|
211
|
+
- cc6ad93: Split-deploy SEO redirects: ship `applyPublicRedirect` / `createAstroRedirectMiddleware`, wire Astro starters and Next scaffold to the shared engine, clarify Never hit vs Stale, and return 409 on duplicate source updates.
|
|
212
|
+
|
|
213
|
+
### Patch Changes
|
|
214
|
+
|
|
215
|
+
- cc6ad93: Hard-delete API keys on remove (instead of soft-revoke) and replace browser password prompts with an in-app reauth dialog.
|
|
216
|
+
|
|
217
|
+
## 0.99.0
|
|
218
|
+
|
|
219
|
+
### Minor Changes
|
|
220
|
+
|
|
221
|
+
- 80d4e5f: Marketing attribution tracking for forms — captures where every lead came from, automatically.
|
|
222
|
+
|
|
223
|
+
**cms-core**: rebuilt `forms/attribution` (now also published at `@actuate-media/cms-core/forms/attribution`) with a first-touch (30-day) / last-touch (24-hour) model merged at submit, the full ad click-ID registry (gclid, fbclid, msclkid, ttclid, li_fat_id, dclid, twclid, rdt_cid + long-tail IDs in `extraIds`), and referrer classification into marketing channels (paid / organic search / social / email / **ai** / referral / direct) with friendly source names ("ChatGPT" instead of "chat.openai.com"). Form entry payloads now expose the full `attribution` blob, CSV exports gained Source / Medium / Campaign / Channel / Referrer / Landing Page columns, and `form.submitted` webhooks forward the merged attribution to CRM consumers. Note: `AttributionData` gained required fields (`channel`, `referrerName`, `extraIds`, `firstTouchAt`, `lastTouchAt`, `version`) and `clickIds` grew from 5 to 8 networks.
|
|
224
|
+
|
|
225
|
+
**sections-react**: `<ActuateForm>` now records an attribution touch on mount and attaches the merged first/last-touch payload to every submission — no setup required. Capture is consent-gated when `@actuate-media/plugin-consent` is installed (waits for an analytics grant; proceeds when the plugin is absent). New `<AttributionTracker />` component for the site layout improves first-touch fidelity across multi-page journeys.
|
|
226
|
+
|
|
227
|
+
**cms-admin**: the form entry detail pane now shows the full attribution picture — channel with friendly source name, source/medium/campaign/term/content, landing page, referrer, device type, first-touch date, and any ad click IDs — with graceful fallback to the legacy UTM columns for older entries.
|
|
228
|
+
|
|
229
|
+
- 80d4e5f: Complete form entries export: server-side CSV/XLSX for every view.
|
|
230
|
+
|
|
231
|
+
**cms-core**
|
|
232
|
+
- New dependency-free XLSX workbook writer (`forms/xlsx.ts`) — deflate-compressed OOXML, inline strings (injection-safe by construction, values round-trip exactly).
|
|
233
|
+
- `GET /forms/entries/export` now accepts `format=csv|xlsx` and an optional `formId`: omitting it exports entries across **all forms** (Form column + union of data keys). Audit log records scope and format.
|
|
234
|
+
- New `exportFormEntriesXlsx()` service alongside `exportFormEntries()` (both share one export table with attribution columns; `formId` is now optional on both).
|
|
235
|
+
|
|
236
|
+
**cms-admin**
|
|
237
|
+
- The Export buttons on Submissions and All Entries now download the full server-side export (all pages, decrypted values, attribution columns, up to 5,000 rows) instead of only the 25 visible rows — with a CSV / Excel (.xlsx) format menu.
|
|
238
|
+
- All Entries export honors the active form filter.
|
|
239
|
+
- Zapier integration helper now documents the `attribution.*` fields available in webhook payloads.
|
|
240
|
+
|
|
241
|
+
## 0.98.0
|
|
242
|
+
|
|
243
|
+
### Minor Changes
|
|
244
|
+
|
|
245
|
+
- c6b006c: Astro discoverability and best practices: `create-actuate-astro` + `--framework astro-site`, Live Content Collections loader (`@actuate-media/cms-core/astro/content`), islands/preview docs, and fleet first-cutover runbook.
|
|
246
|
+
- 21aa417: Consolidate the two form-editing surfaces onto the typed Forms API and make GA4 form analytics work end-to-end.
|
|
247
|
+
|
|
248
|
+
**cms-admin** — The Edit Form page is rebuilt as the canonical full-page form editor, backed by the same typed Forms API as the Forms list's inline Schema pane (no more legacy `/collections/forms` writes that bypassed schema versioning). It now covers everything in one place: Details (name/description/status + post-submit confirmation), Fields (versioned schema editor), **Notifications** (previously only reachable from the Schema pane), Analytics (GA4), Embed, and Integrations (webhooks). Confirmation settings now persist to `successMessage` / `redirectUrl` — the fields the public submission pipeline actually reads (the old editor wrote a `confirmation` object the runtime ignored). New forms are created through `POST /forms`, gaining slug generation, an initial schema version, and notification defaults, then route into the full editor.
|
|
249
|
+
|
|
250
|
+
**cms-core** — `FormDefinition` gains a sanitized `analytics` (GA4) config, persisted via the forms service (`createForm` / `updateForm` / `duplicateForm`) and exposed on the public form schema only when tracking is enabled. New `normalizeAnalyticsConfig` export and a client-safe `@actuate-media/cms-core/forms/analytics` subpath export.
|
|
251
|
+
|
|
252
|
+
**sections-react** — `<ActuateForm>` now fires GA4 `form_start` / `form_submit` / `form_error` events (with optional conversion value) when the form has analytics enabled. Also fixes the success state to read the submit endpoint's `confirmation` _object_ (`{ type, message, redirectUrl }`) — previously it was treated as a string, which broke the success message and ignored server-driven redirects. New `resolveSubmitOutcome` helper with regression coverage.
|
|
253
|
+
|
|
254
|
+
**plugin-forms** — The `forms` collection field map now declares every key the typed forms service persists (`description`, `notificationSettings`, `embedSettings`, `spamSettings`, `successMessage`, `redirectUrl`, `analytics`, `activeSchemaVersionId`, …). The data layer strips undeclared keys on write, so consumer apps registering this plugin would previously drop notification/embed/spam settings silently. Status options now match the typed `FormStatus` (`active` / `draft` / `archived`).
|
|
255
|
+
|
|
256
|
+
## 0.97.0
|
|
257
|
+
|
|
258
|
+
### Minor Changes
|
|
259
|
+
|
|
260
|
+
- fb89234: Add `createAstroSiteHelpers` (`@actuate-media/cms-core/astro/site`) for framework-neutral / Astro public sites, with optional pluggable cache. `createSiteClient` now also resolves `ACTUATE_CMS_URL` before `NEXT_PUBLIC_SITE_URL` for split-deploy CMS origins.
|
|
261
|
+
|
|
262
|
+
## 0.96.0
|
|
263
|
+
|
|
264
|
+
### Minor Changes
|
|
265
|
+
|
|
266
|
+
- fdfae4c: Make `graphql`, `graphql-yoga`, and `@sentry/node` optional peer dependencies (optional-deps Tier 2).
|
|
267
|
+
|
|
268
|
+
These were hard `dependencies`, so every install paid for them even when the app
|
|
269
|
+
used neither the GraphQL API nor Sentry error reporting. They are now **optional
|
|
270
|
+
peer dependencies** — cms-core loads them lazily and degrades gracefully when
|
|
271
|
+
they're absent.
|
|
272
|
+
|
|
273
|
+
**Breaking changes:**
|
|
274
|
+
- **`createGraphQLHandler` is now async** (`Promise<YogaServerInstance>`). It
|
|
275
|
+
lazily imports `graphql-yoga` on first use. If you call it directly, `await` it:
|
|
276
|
+
```diff
|
|
277
|
+
- const yoga = createGraphQLHandler({ config, secret })
|
|
278
|
+
+ const yoga = await createGraphQLHandler({ config, secret })
|
|
279
|
+
```
|
|
280
|
+
The built-in `/api/cms/graphql` route already awaits it internally — no change
|
|
281
|
+
needed if you use the standard handler.
|
|
282
|
+
- **GraphQL now requires installing its peers.** To use `/api/cms/graphql`,
|
|
283
|
+
install `graphql` + `graphql-yoga`. Without them the endpoint throws a clear,
|
|
284
|
+
actionable error. Apps scaffolded with `create-actuate-cms` include both.
|
|
285
|
+
- **Sentry now requires installing `@sentry/node`.** Setting `SENTRY_DSN` only
|
|
286
|
+
reports errors when the package is installed; otherwise reporting is a silent
|
|
287
|
+
no-op (it never throws). `initServerSentry` / `captureServerException` are now
|
|
288
|
+
async internally (fire-and-forget at their call sites).
|
|
289
|
+
|
|
290
|
+
No change for apps that don't use GraphQL or Sentry — they simply stop pulling in
|
|
291
|
+
these packages.
|
|
292
|
+
|
|
293
|
+
## 0.95.1
|
|
294
|
+
|
|
295
|
+
### Patch Changes
|
|
296
|
+
|
|
297
|
+
- 2ccc0a7: Ship a compiled Prisma client so `@actuate-media/cms-core/generated/client`
|
|
298
|
+
and the DB-adapter helpers actually resolve (#550).
|
|
299
|
+
|
|
300
|
+
The `prisma-client` generator emits **only `.ts`** into the (gitignored)
|
|
301
|
+
`generated/` directory, but nothing ever compiled it: the package `build` was
|
|
302
|
+
`tsc` over `src/` alone, while the `./generated/client` export and the
|
|
303
|
+
`createDatabaseClient*` adapters both pointed at `.js`/`.d.ts` files that were
|
|
304
|
+
never produced. The tarball shipped the raw `.ts` (referencing `.js` siblings),
|
|
305
|
+
so any **non-scaffold** consumer importing the generated client — or using the
|
|
306
|
+
Postgres/MySQL/SQLite adapters — hit a broken import. It went unnoticed because
|
|
307
|
+
scaffolded apps generate their own client and the in-repo adapters have no
|
|
308
|
+
callers, so the monorepo (which transpiles the `.ts` directly) never exercised
|
|
309
|
+
the published path.
|
|
310
|
+
|
|
311
|
+
Fix: a second, isolated build pass (`tsconfig.generated.json`) compiles
|
|
312
|
+
`generated/` into `dist/generated/`, `build` now runs `prisma generate` first
|
|
313
|
+
(the client is gitignored, so it must be regenerated at build time), and the
|
|
314
|
+
`./generated/client` export points at the compiled `dist/generated/client.js` +
|
|
315
|
+
`.d.ts` — the same path the adapters' `../../generated/client.js` resolves to
|
|
316
|
+
from `dist/`. The uncompiled `.ts` is dropped from the published `files`.
|
|
317
|
+
No public API changed; a previously-broken import path now works.
|
|
318
|
+
|
|
319
|
+
## 0.95.0
|
|
320
|
+
|
|
321
|
+
### Minor Changes
|
|
322
|
+
|
|
323
|
+
- 0bd5153: Stop every content write from purging the entire cache (#544).
|
|
324
|
+
|
|
325
|
+
A document create/update/delete used to fire the wholesale `actuate` cache tag,
|
|
326
|
+
so a single edit revalidated the **whole site** — cache hit-rate collapsed under
|
|
327
|
+
editorial load. Writes now fire **narrow** tags only: the collection, the
|
|
328
|
+
document, and the document's public URL path(s) — `actuate:path:/blog/my-post`,
|
|
329
|
+
plus the **old path on a slug rename** and the deleted path on delete. Editing
|
|
330
|
+
one page now busts only that page.
|
|
331
|
+
- The write-side path is built with the same `resolveDocumentUrl` the resolver
|
|
332
|
+
inverts, so it exactly matches the `actuate:path:<path>` tag the `/resolve`
|
|
333
|
+
endpoint and ISR site pages apply on read (locked by a round-trip parity test).
|
|
334
|
+
- **Globals keep the wholesale tag** — site settings / navigation are read on
|
|
335
|
+
every page and change rarely, so they still refresh site-wide instantly.
|
|
336
|
+
- The read side still seeds the `actuate` root tag, so `revalidateTag('actuate')`
|
|
337
|
+
remains an operator "purge everything" escape hatch.
|
|
338
|
+
|
|
339
|
+
**Behavior note (documented):** content that _embeds_ a document via `populate`
|
|
340
|
+
(e.g. an author's name on a posts list) now refreshes on its own ISR TTL rather
|
|
341
|
+
than instantly, since writes tag by the document's own path. Fire
|
|
342
|
+
`revalidateTag('actuate')` for an immediate cross-content purge.
|
|
343
|
+
|
|
344
|
+
## 0.94.0
|
|
345
|
+
|
|
346
|
+
### Minor Changes
|
|
347
|
+
|
|
348
|
+
- d9096d9: Move `pg` and `@prisma/adapter-pg` from dependencies to **optional peer
|
|
349
|
+
dependencies**.
|
|
350
|
+
|
|
351
|
+
These Postgres driver packages were forced onto every consumer — including
|
|
352
|
+
SQLite-only or non-database setups — even though cms-core only imports them
|
|
353
|
+
**dynamically**, when its Postgres driver adapter is actually used. They're now
|
|
354
|
+
optional peers, so they're no longer installed unless you need them.
|
|
355
|
+
|
|
356
|
+
**Migration:** if you connect cms-core to Postgres via its built-in driver
|
|
357
|
+
adapter (`createPostgresAdapter` / the `database: { provider: 'prisma' }`
|
|
358
|
+
auto-wire), install the drivers yourself:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
npm install pg @prisma/adapter-pg
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Apps scaffolded with `create-actuate-cms` already declare both, so the default
|
|
365
|
+
path is unaffected. If they're missing, the adapter throws a clear
|
|
366
|
+
"PostgreSQL support requires pg and @prisma/adapter-pg" error rather than a
|
|
367
|
+
cryptic module-not-found.
|
|
368
|
+
|
|
369
|
+
## 0.93.11
|
|
370
|
+
|
|
371
|
+
### Patch Changes
|
|
372
|
+
|
|
373
|
+
- fe2b661: Add a Report-Only Content-Security-Policy on the admin mount (#567 follow-up).
|
|
374
|
+
|
|
375
|
+
`withActuateCMS()` now emits `Content-Security-Policy-Report-Only` scoped to the
|
|
376
|
+
admin path (`<adminPath>/:path*`). It is **observability, not enforcement** — it
|
|
377
|
+
never blocks a request; the browser reports resources outside the policy (e.g. an
|
|
378
|
+
injected third-party script) so operators can see what an enforcing CSP would
|
|
379
|
+
catch before enabling one. Scoped to the admin (not the whole site) because a CSP
|
|
380
|
+
is content-specific, and kept permissive for the admin's real needs (`'self'` +
|
|
381
|
+
inline scripts/styles + the `https:` storage host). An enforcing CSP and
|
|
382
|
+
`Permissions-Policy` remain opt-in. Composes with any consumer `headers()`.
|
|
383
|
+
|
|
384
|
+
## 0.93.10
|
|
385
|
+
|
|
386
|
+
### Patch Changes
|
|
387
|
+
|
|
388
|
+
- 78c6d0f: Fix TOTP re-authentication silently always failing.
|
|
389
|
+
|
|
390
|
+
`verifyReauth(..., 'totp')` passed the **encrypted** `totpSecret` straight to
|
|
391
|
+
`verifyTOTP` without decrypting it, so a correct authenticator code never matched
|
|
392
|
+
— TOTP-based re-auth for sensitive actions (delete user, change role/settings,
|
|
393
|
+
export data, reset another user's password) could never succeed. It now decrypts
|
|
394
|
+
the secret first, matching the login flow, and fails closed if decryption throws.
|
|
395
|
+
Adds the first tests for `verifyReauth` (the gap that let this ship).
|
|
396
|
+
|
|
397
|
+
## 0.93.9
|
|
398
|
+
|
|
399
|
+
### Patch Changes
|
|
400
|
+
|
|
401
|
+
- 06e7fbc: Schema hygiene: add the WorkflowState→Document foreign key (#559) and drop six
|
|
402
|
+
dead enums (#558).
|
|
403
|
+
- **#559** — `WorkflowState.documentId` was `@unique` with no relation, allowing
|
|
404
|
+
orphan rows and blocking cascade deletes. It now has a real `document`
|
|
405
|
+
relation with `onDelete: Cascade`. The migration purges any orphans first
|
|
406
|
+
(the workflow feature is unbuilt, so the table is empty in practice).
|
|
407
|
+
- **#558** — six enums (`AuditEvent`, `NotificationType`, `OrderStatus`,
|
|
408
|
+
`PaymentStatus`, `DiscountType`, `ProductType`) were emitted as `CREATE TYPE`
|
|
409
|
+
into every consumer database but referenced by no model. Removed from the
|
|
410
|
+
schema and `DROP TYPE IF EXISTS`'d (no `CASCADE`, so a reuse elsewhere fails
|
|
411
|
+
loudly rather than dropping dependent columns).
|
|
412
|
+
|
|
413
|
+
**Migration required:** `0007_workflow_fk_drop_dead_enums` — run
|
|
414
|
+
`prisma migrate deploy`. Synced across all three schema trees; no application
|
|
415
|
+
code change (the dropped enums had no code references; the FK is additive).
|
|
416
|
+
|
|
417
|
+
## 0.93.8
|
|
418
|
+
|
|
419
|
+
### Patch Changes
|
|
420
|
+
|
|
421
|
+
- 28dd310: Make TOTP codes single-use to prevent replay (#565).
|
|
422
|
+
|
|
423
|
+
`verifyTOTP` accepts any code valid within its ±1 time-step window (~90s) with no
|
|
424
|
+
consumed-step tracking, so a captured-but-still-valid authenticator code could be
|
|
425
|
+
replayed to authenticate. The login flow now records the last consumed TOTP
|
|
426
|
+
time-step and rejects any code whose step was already used.
|
|
427
|
+
- New `verifyTOTPStep()` returns the matched time-step counter (and a
|
|
428
|
+
`generateTOTP()` helper for tooling/tests); `verifyTOTP()` is unchanged.
|
|
429
|
+
- `POST /auth/totp/login` consumes the step via a guarded `updateMany`
|
|
430
|
+
(`WHERE lastTotpStep IS NULL OR < step`), which also closes the TOCTOU race
|
|
431
|
+
between two parallel requests carrying the same code. Backup codes keep their
|
|
432
|
+
existing single-use handling.
|
|
433
|
+
|
|
434
|
+
**Migration required:** adds a nullable `User.lastTotpStep` column
|
|
435
|
+
(`0006_totp_last_step`). Additive and safe on existing rows — run
|
|
436
|
+
`prisma migrate deploy`. Behavior change: a valid code can be used only once
|
|
437
|
+
within its 30s window (re-entering the same code makes the user wait for the next
|
|
438
|
+
one), which is the intended replay protection.
|
|
439
|
+
|
|
440
|
+
## 0.93.7
|
|
441
|
+
|
|
442
|
+
### Patch Changes
|
|
443
|
+
|
|
444
|
+
- ac2cf04: Ship baseline security headers on all HTML responses (#567).
|
|
445
|
+
|
|
446
|
+
`getSecurityHeaders()` and friends existed but were wired to nothing, so the
|
|
447
|
+
admin SPA and public SSR HTML shipped with no clickjacking, MIME-sniffing, or
|
|
448
|
+
HSTS defaults (the Security docs claimed otherwise). `withActuateCMS()` now adds
|
|
449
|
+
a conservative baseline via Next's `headers()` for every route, composed with
|
|
450
|
+
(never clobbering) any `headers()` the consumer already defines:
|
|
451
|
+
- `X-Content-Type-Options: nosniff`
|
|
452
|
+
- `X-Frame-Options: SAMEORIGIN` — blocks cross-origin clickjacking while keeping
|
|
453
|
+
the admin's **same-origin** site-preview iframe working (deliberately not
|
|
454
|
+
`DENY`).
|
|
455
|
+
- `Referrer-Policy: strict-origin-when-cross-origin`
|
|
456
|
+
- `Strict-Transport-Security: max-age=63072000; includeSubDomains` (no `preload`)
|
|
457
|
+
|
|
458
|
+
`Content-Security-Policy` and `Permissions-Policy` are intentionally **not**
|
|
459
|
+
defaulted — a blanket policy on `/:path*` would break legitimate consumer
|
|
460
|
+
features or the admin's inline scripts/styles; they remain opt-in. Adds an
|
|
461
|
+
optional `headers?` field to the exported `NextConfigLike`. Security docs
|
|
462
|
+
corrected to describe the actual defaults.
|
|
463
|
+
|
|
464
|
+
## 0.93.6
|
|
465
|
+
|
|
466
|
+
### Patch Changes
|
|
467
|
+
|
|
468
|
+
- 886238a: Enforce collection `access.read` on `GET /collections/:slug/field-values` (#563).
|
|
469
|
+
|
|
470
|
+
The field-value autocomplete endpoint gated only on `requireCollectionScope`
|
|
471
|
+
(which checks API-key scopes, not the collection's `access.read` rule), so any
|
|
472
|
+
authenticated principal could enumerate the distinct in-use values of a field
|
|
473
|
+
(tags, categories, author names, statuses, …) on a collection whose `access.read`
|
|
474
|
+
would deny them. It now calls `assertCollectionAccess(slug, 'read', ctx)` before
|
|
475
|
+
querying — collection-level, exactly like the `list`/`get` routes — and maps the
|
|
476
|
+
resulting denial to `403` via `actionError`.
|
|
477
|
+
|
|
478
|
+
(The related #543 memory-footprint optimization on this handler — projecting the
|
|
479
|
+
field value in SQL instead of loading full document blobs — is deferred to a
|
|
480
|
+
follow-up with real-Postgres coverage, since a raw-query change can't be verified
|
|
481
|
+
against the unit mock DB.)
|
|
482
|
+
|
|
483
|
+
## 0.93.5
|
|
484
|
+
|
|
485
|
+
### Patch Changes
|
|
486
|
+
|
|
487
|
+
- 430ae43: Fix OAuth login failing at the database + honor the session policy (#564).
|
|
488
|
+
|
|
489
|
+
`handleOAuthCallback` created the session row with only `{ userId, expiresAt }`,
|
|
490
|
+
but `Session.token` is a required, unique column — so against real Prisma **every
|
|
491
|
+
OAuth login threw** ("Argument `token` is missing") and bounced the user back to
|
|
492
|
+
the login screen. It passed tests only because the test fake-db skips
|
|
493
|
+
required-field validation (there was no real-DB coverage of the OAuth callback).
|
|
494
|
+
|
|
495
|
+
The callback now mints the session exactly like the password/TOTP flows: prune
|
|
496
|
+
to `maxConcurrentSessions` first, create the JWT with a known session id, and
|
|
497
|
+
write the row with that `id` + `token` + a policy-derived `expiresAt`. OAuth
|
|
498
|
+
sessions therefore now also honor `security.session.timeoutHours` and
|
|
499
|
+
`maxConcurrentSessions` (previously hardcoded to 7 days with no concurrent-session
|
|
500
|
+
enforcement) and record `ipAddress`/`userAgent` for the session UI.
|
|
501
|
+
|
|
502
|
+
Behavior change to note: an OAuth session's lifetime is now the configured
|
|
503
|
+
timeout — identical to today when left at the 168h default, shorter only if an
|
|
504
|
+
admin lowered it. `handleOAuthCallback` gains an optional `session` option
|
|
505
|
+
(injected by the API layer); its `{ token, user }` return is unchanged.
|
|
506
|
+
|
|
507
|
+
## 0.93.4
|
|
508
|
+
|
|
509
|
+
### Patch Changes
|
|
510
|
+
|
|
511
|
+
- 899ca9a: Security: verify OAuth email before account adoption (#562) and require write
|
|
512
|
+
scope for media optimization (#566).
|
|
513
|
+
|
|
514
|
+
**OAuth account takeover (nOAuth, #562).** `handleOAuthCallback` adopted an
|
|
515
|
+
existing passwordless account whenever the provider returned a matching email —
|
|
516
|
+
but Google's `email_verified` claim and Microsoft's identity were never checked,
|
|
517
|
+
and Microsoft is accepted from the multi-tenant `common` authority. An attacker
|
|
518
|
+
whose provider asserts an _unverified_ address equal to a victim's could sign in
|
|
519
|
+
as that account. The callback now adopts an existing account by email **only when
|
|
520
|
+
the provider verified the email**: `getUserProfile` sets `emailVerified` per
|
|
521
|
+
provider (Google from `email_verified`; GitHub `true`; Microsoft `false` — its
|
|
522
|
+
`common`-authority identity can't be trusted), and an unverified match is
|
|
523
|
+
rejected with guidance to link from Settings instead. Already-linked users are
|
|
524
|
+
unaffected (they resolve by `(provider, providerAccountId)` first). Note: this
|
|
525
|
+
adds `emailVerified` to the exported `OAuthUserProfile`.
|
|
526
|
+
|
|
527
|
+
**Media optimize authorization (#566).** `POST /media/:id/optimize` re-encodes
|
|
528
|
+
and **replaces** the stored blob (deleting the original) but required only
|
|
529
|
+
authentication — any VIEWER, self-signup CLIENT, or scopeless API key could
|
|
530
|
+
iterate media IDs and destroy/rewrite originals. It now requires media scope +
|
|
531
|
+
a write role, mirroring `PUT`/`DELETE` on the same resource, and fetches the
|
|
532
|
+
original through `safeFetch` (SSRF preflight + timeout) instead of raw `fetch`.
|
|
533
|
+
|
|
534
|
+
## 0.93.3
|
|
535
|
+
|
|
536
|
+
### Patch Changes
|
|
537
|
+
|
|
538
|
+
- 77b3922: Fix an HTTP 500 when reusing the slug of a soft-deleted document (#560).
|
|
539
|
+
|
|
540
|
+
The `(collection, slug)` unique index counts soft-deleted rows, so deleting a
|
|
541
|
+
document left its slug permanently stranded — creating, renaming, or publishing
|
|
542
|
+
a live document onto that slug threw a Prisma `P2002` that surfaced as an opaque
|
|
543
|
+
`500 Internal server error`, with the blocking row invisible in the admin (all
|
|
544
|
+
readers filter `deletedAt: null`).
|
|
545
|
+
|
|
546
|
+
Two changes:
|
|
547
|
+
- **`deleteDocument` now frees the slug** (sets it to `null`) inside the delete
|
|
548
|
+
transaction, so the URL can be reused immediately. The pre-delete slug is still
|
|
549
|
+
captured for cache/sitemap effects and preserved in the `DELETE` version
|
|
550
|
+
snapshot.
|
|
551
|
+
- **`actionError` maps Prisma `P2002` to `409 Conflict`** with a safe, specific
|
|
552
|
+
message ("A document with this slug already exists in this collection.") — so a
|
|
553
|
+
genuine clash between two _live_ documents returns a clean, client-fixable
|
|
554
|
+
conflict instead of a 500. Covers create, update/rename, publish, and duplicate.
|
|
555
|
+
|
|
556
|
+
## 0.93.2
|
|
557
|
+
|
|
558
|
+
### Patch Changes
|
|
559
|
+
|
|
560
|
+
- 4421c32: Fix media upload 500 ("Cannot find package 'sharp'") on Vercel.
|
|
561
|
+
|
|
562
|
+
cms-core optimizes uploaded images with `sharp` and marks it a Next
|
|
563
|
+
`serverExternalPackage` (a native module can't be bundled). Because it's also
|
|
564
|
+
dynamically imported (`import(/* webpackIgnore */ 'sharp')`), Next never traces
|
|
565
|
+
it — so unless the app depends on `sharp` directly, Vercel omits it from the
|
|
566
|
+
function bundle and every image upload 500s.
|
|
567
|
+
|
|
568
|
+
Two fixes:
|
|
569
|
+
- `create-actuate-cms` now adds `sharp` to the generated `package.json`
|
|
570
|
+
dependencies (like it already does for `@vercel/blob`), so scaffolded sites
|
|
571
|
+
bundle it.
|
|
572
|
+
- The `/media/upload` handler now degrades gracefully: if optimization throws
|
|
573
|
+
(e.g. sharp is missing), it stores the original image and logs a warning
|
|
574
|
+
instead of returning a 500. Uploads succeed; add `sharp` to re-enable
|
|
575
|
+
webp/resize/blur-placeholder.
|
|
576
|
+
|
|
577
|
+
Existing sites hit by this: `npm install sharp` and redeploy.
|
|
578
|
+
|
|
579
|
+
## 0.93.1
|
|
580
|
+
|
|
581
|
+
### Patch Changes
|
|
582
|
+
|
|
583
|
+
- 898ec8b: Slim and bound the `GET /seo/pages` endpoint.
|
|
584
|
+
|
|
585
|
+
`normalizeSeoPage` spread the entire document (`...page`) into every response
|
|
586
|
+
row — including the raw `data` and `structuredData` JSONB. For section-built
|
|
587
|
+
pages those blobs are large, so the response and server memory grew linearly
|
|
588
|
+
with published content (megabytes to render a table that needs a dozen scalar
|
|
589
|
+
fields). It now returns only the derived summary fields
|
|
590
|
+
(id/collection/updatedAt/url/title/score/issues/metaTitle/metaDescription/
|
|
591
|
+
canonical/schemaType).
|
|
592
|
+
|
|
593
|
+
The route is also bounded: it defaults to `take: 1000` (max 5000) and accepts
|
|
594
|
+
`?limit`/`?offset`, returning `{ data, limit, offset }`, so a large site can page
|
|
595
|
+
instead of pulling every published document at once. The derived-field contract
|
|
596
|
+
the admin relies on is unchanged.
|
|
597
|
+
|
|
598
|
+
## 0.93.0
|
|
599
|
+
|
|
600
|
+
### Minor Changes
|
|
601
|
+
|
|
602
|
+
- c231dd0: Add `definePlugin` and migrate all 10 plugins onto the canonical `PluginDefinition` contract.
|
|
603
|
+
|
|
604
|
+
cms-core already exported `PluginDefinition`, but every plugin hand-rolled a
|
|
605
|
+
local `ActuatePlugin` interface — a lossy subset that dropped `version`/`init`
|
|
606
|
+
and, in blocks/email/ai, widened hook payloads to `unknown[]`/`event: string`.
|
|
607
|
+
That's copy-paste drift with no shared source of truth.
|
|
608
|
+
|
|
609
|
+
`cms-core` now exports `definePlugin(plugin: PluginDefinition)` (an identity
|
|
610
|
+
helper like `defineConfig`), and each plugin imports the canonical type instead
|
|
611
|
+
of its own: the local `ActuatePlugin` interfaces are deleted, factory return
|
|
612
|
+
types are `PluginDefinition`, and the widened `hooks.ts` types are retyped as
|
|
613
|
+
`PluginHook[]`. Type-only change — no runtime behavior, plugin `name`, or config
|
|
614
|
+
options changed. Net −69 lines.
|
|
615
|
+
|
|
616
|
+
## 0.92.0
|
|
617
|
+
|
|
618
|
+
### Minor Changes
|
|
619
|
+
|
|
620
|
+
- cc18833: SEO audit remediation — 47 verified fixes across the SEO system.
|
|
621
|
+
|
|
622
|
+
Highlights: redirect middleware no longer excludes legacy `.html/.php/.aspx`
|
|
623
|
+
URLs (migrations); robots.txt now `Allow`s the default OG image (`/api/cms/og.png`)
|
|
624
|
+
so social shares render; RSS always emits the mandatory channel `<description>`;
|
|
625
|
+
structured-data auto-detection no longer emits invalid `Review/Event/JobPosting`
|
|
626
|
+
(required fields enforced or the node is suppressed) and no longer mis-types
|
|
627
|
+
collections by substring (`newsletters`→NewsArticle etc.); the sitemap endpoint
|
|
628
|
+
no longer selects full document JSON for up to 45k rows; IndexNow submits on
|
|
629
|
+
every publish instead of being swallowed by the sitemap debounce; the exported
|
|
630
|
+
`renderJsonLdScript()` now escapes `<`; the editor no longer shows two
|
|
631
|
+
conflicting scores and the Content-tab analysis now understands section-built
|
|
632
|
+
pages; plus pagination `rel`/canonical, `og:locale` normalization + alternates,
|
|
633
|
+
canonical absolutization, VideoObject/Product/WebSite-SearchAction builders,
|
|
634
|
+
redirect loop/case handling, plugin-seo sitemap caps + real `lastmod`, and
|
|
635
|
+
plugin link-health routed through `safeFetch`. Adds several new exports on
|
|
636
|
+
`@actuate-media/cms-core` (e.g. `buildVideoObjectSchema`, `buildProductSchema`).
|
|
637
|
+
|
|
638
|
+
## 0.91.1
|
|
639
|
+
|
|
640
|
+
### Patch Changes
|
|
641
|
+
|
|
642
|
+
- f9513f3: Prevent a global login lockout on deployments where the client IP can't be
|
|
643
|
+
resolved. When no trusted proxy header is present, login / forgot-password /
|
|
644
|
+
reset-password requests all share one `login:unknown` bucket; using the strict
|
|
645
|
+
5-per-15-min login limiter there meant a handful of anonymous requests could
|
|
646
|
+
return HTTP 429 to **every** user (a denial of authentication).
|
|
647
|
+
|
|
648
|
+
The shared unknown-IP bucket now uses a higher-ceiling limiter (100/15min) so a
|
|
649
|
+
few anonymous attempts can't lock everyone out, while still bounding abuse. The
|
|
650
|
+
app also logs a one-time startup warning when it can't resolve a trusted client
|
|
651
|
+
IP, pointing operators to set `ACTUATE_TRUST_PROXY=1` (documented) behind a
|
|
652
|
+
reverse proxy — the real fix for non-Vercel hosts.
|
|
653
|
+
|
|
654
|
+
## 0.91.0
|
|
655
|
+
|
|
656
|
+
### Minor Changes
|
|
657
|
+
|
|
658
|
+
- b53b4a4: Sync `MediaUsage` join rows on every document create, update, delete, duplicate, and global write. Scans document JSON for media field IDs and image URLs in rich-text HTML so the media library "Used on" drawer stays accurate. Adds `backfillMediaUsage()` export and `actuate migrate:media-usage` for existing content.
|
|
659
|
+
- de419f2: Give plugin hooks a frozen, typed event contract. cms-core now exports
|
|
660
|
+
`PluginHook`, `PluginHookEvent`, and `PluginHookPayload` (document + upload
|
|
661
|
+
variants) as the single source of truth, and dispatches a complete, consistent
|
|
662
|
+
set of events: `afterCreate` / `afterUpdate` / `afterDelete` — each also in a
|
|
663
|
+
collection-namespaced form (`afterUpdate:navigations`, `afterCreate:orders`) —
|
|
664
|
+
plus `afterUpload` (fired post-upload from the media route). Every handler now
|
|
665
|
+
receives one typed payload; document field values live on `payload.data`.
|
|
666
|
+
|
|
667
|
+
Previously core fired only bare `afterCreate`/`afterUpdate` with the raw row, so
|
|
668
|
+
several shipped plugins' hooks were dead: `plugin-media`'s `afterUpload` never
|
|
669
|
+
fired, `plugin-navigation` read fields that didn't exist, and the namespaced
|
|
670
|
+
events `plugin-forms`/`plugin-commerce` declared were never dispatched. Each
|
|
671
|
+
plugin has been aligned to the shared type and dispatched events (and
|
|
672
|
+
`plugin-redirects`' request-time `onRedirectMatch` hook — which the lifecycle
|
|
673
|
+
bus never dispatched — was removed). Undispatched event names are now a
|
|
674
|
+
compile-time error.
|
|
675
|
+
|
|
676
|
+
- 898a3f8: Make plugins actually extend the CMS. Previously `config.plugins` only had their
|
|
677
|
+
`hooks` harvested — a plugin's `collections`, `init`, `fields`, and
|
|
678
|
+
`adminNavItems` were declared on the type but never consumed, so adding a plugin
|
|
679
|
+
registered nothing.
|
|
680
|
+
|
|
681
|
+
`defineConfig` now runs a plugin runner (`applyPlugins`): for each plugin, in
|
|
682
|
+
order, it applies `init(config)`, merges the plugin's `collections` (the
|
|
683
|
+
consumer's config and earlier plugins win on slug conflicts, so a plugin can
|
|
684
|
+
never clobber your collection), and accumulates reusable `fields` presets and
|
|
685
|
+
`adminNavItems`. Merged plugin collections flow through the existing collection
|
|
686
|
+
registry, so they are immediately manageable in the admin (the sidebar derives
|
|
687
|
+
its Content nav from `config.collections`) and queryable over REST. The admin
|
|
688
|
+
sidebar also renders plugin-contributed `adminNavItems` (deduped by path).
|
|
689
|
+
|
|
690
|
+
- d6665dd: Enforce access control on populated relations. `?populate` previously spliced a
|
|
691
|
+
referenced document's raw `data` into the response with no filtering, which (1)
|
|
692
|
+
disclosed unpublished (DRAFT/SCHEDULED) referenced documents through the public,
|
|
693
|
+
unauthenticated `/resolve?populate` endpoint, and (2) bypassed field-level
|
|
694
|
+
`access.read` rules and internal `_`-key stripping on authenticated reads.
|
|
695
|
+
|
|
696
|
+
`buildRelationLookup(db, options)` now strips internal keys and applies the
|
|
697
|
+
target collection's `access.read` for the requesting viewer on all populated
|
|
698
|
+
data, and takes a `publicOnly` option (set on `/resolve`) that restricts
|
|
699
|
+
populated targets to PUBLISHED documents. Also hardens document
|
|
700
|
+
create/update/delete against a missing Prisma `mediaUsage` delegate so a
|
|
701
|
+
secondary index write can never abort the primary content write.
|
|
702
|
+
|
|
703
|
+
### Patch Changes
|
|
704
|
+
|
|
705
|
+
- 041a228: Search now uses the `idx_actuate_documents_fts` GIN index. The query previously
|
|
706
|
+
OR-combined the tsvector match with an unindexable `ILIKE`, which forced a
|
|
707
|
+
sequential scan of `actuate_documents` on every search; the FTS match now runs
|
|
708
|
+
as the primary (indexable) query and the `ILIKE` substring scan only runs as a
|
|
709
|
+
fallback when FTS matches nothing. Search page size is also clamped (max 100).
|
|
710
|
+
Additionally ships the `schema-fragments/` directory in the package tarball so
|
|
711
|
+
the `./prisma/schema` subpath export resolves in a published install.
|
|
712
|
+
- 041a228: Add the MIT `license` field to every published package (previously missing) and correct `repository.url` to `github.com/actuate-media/actuatecms` (13 packages carried the wrong org).
|
|
713
|
+
- 4198920: Move `cms-schema.prisma` out of `prisma/` so the Prisma language server no longer merges the consumer fragment with the canonical schema. Add a reference PostgreSQL `datasource` so fragment-native types validate in the IDE. The `@actuate-media/cms-core/prisma/schema` export path is unchanged.
|
|
714
|
+
- Updated dependencies [041a228]
|
|
715
|
+
- @actuate-media/realtime@0.1.2
|
|
716
|
+
|
|
717
|
+
## 0.90.1
|
|
718
|
+
|
|
719
|
+
### Patch Changes
|
|
720
|
+
|
|
721
|
+
- 3b6d7b3: Add client-safe `@actuate-media/cms-core/seo/analysis` export. Restructure the post/page SEO tab into Yoast-style accordion sections (Focus Keyword, Search Appearance, Social, Analysis, Advanced, Insights, Linking) with live content analysis and AI assists.
|
|
722
|
+
|
|
723
|
+
## 0.90.0
|
|
724
|
+
|
|
725
|
+
### Minor Changes
|
|
726
|
+
|
|
727
|
+
- 4afb589: Add legitimacy-gated 404 redirect recovery: wire public 404 logging, filter probe URLs before suggesting redirects, connect broken internal links to the SEO audit approve flow, and seed suggestions during audits.
|
|
728
|
+
- 4afb589: 404 recovery pass 2: AI semantic re-ranking for ambiguous matches, opt-in auto-301 setting, and in-content link fixes for editor typos.
|
|
729
|
+
- 4afb589: Add link graph snapshot and AI-powered internal link suggestions (`GET /seo/links/snapshot`, `POST /seo/links/ai-index`, `POST /seo/links/suggest`). New SEO Operations Center **Links** tab tracks internal/external distribution, orphaned pages, coverage, and live link health scans.
|
|
730
|
+
- 4afb589: Apply the site title template at render time so `%site%` is always included for pages and posts (including RSS feeds and the SEO editor). Clamp AI title/description lengths to industry norms, inherit OG/Twitter from meta tags by default, and accept case-insensitive template tokens like `%Site%`.
|
|
731
|
+
|
|
732
|
+
### Patch Changes
|
|
733
|
+
|
|
734
|
+
- 4afb589: WCAG-aligned AI alt text: improved vision prompts, feature-gated `/ai/media-metadata` with `aiAltText` suggestions, post-upload auto-suggest in Media, MCP `generate_media_alt` tool, and validation warnings for static image paths outside the media library.
|
|
735
|
+
- 4afb589: Media upload optimization: high-quality **lossy WebP** by default (q82 / q86 for transparent PNGs) for strong compression with minimal visible quality loss. Lossless WebP available via `optimizeImage({ lossless: true })`. Original bytes kept when WebP would not reduce file size.
|
|
736
|
+
- 4afb589: Phase 0 SEO hardening: block protocol-relative redirect destinations, enforce admin sitemapEnabled on public sitemap routes, force noindex on preview /resolve responses, unify document robots resolution across sitemap/meta/composePageMeta, audit-log redirect mutations, and batch link-health document scans.
|
|
737
|
+
|
|
738
|
+
## 0.89.0
|
|
739
|
+
|
|
740
|
+
### Minor Changes
|
|
741
|
+
|
|
742
|
+
- 41bd56a: Correctness fixes from the post-Phase-3 polish plan.
|
|
743
|
+
|
|
744
|
+
**plugin-consent — granular consent categories.** Consent is no longer binary: visitors can grant individual categories (`analytics`, `marketing`, `functional`, `preferences`) via a new "Manage preferences" expansion in `ConsentBanner`. Custom selections persist as JSON in the same `am_cookie_consent` key; `all`/`essential` still persist as legacy strings so previously shipped consent gates keep working, and legacy stored values migrate transparently on read. New exports: `CONSENT_CATEGORIES`, `ConsentCategory`, `ConsentState`, `readConsentState`, `writeConsentState`, `setConsentCategories`, `hasCategoryConsent`, `categoriesForLevel`, `normalizeConsentSelection`, `waitForConsentState`. `useConsent` now reports `categories` and an `allows(category)` helper; `loadConsentGatedAnalytics` gates GA4/Google Ads on `analytics` and Meta Pixel on `marketing` when passed a consent state.
|
|
745
|
+
|
|
746
|
+
**cms-core — script-tag consent gate honors categories.** `wrapCodeInConsentGate` now grants when the visitor accepted all cookies _or_ granted the tag's specific category through a custom selection, instead of requiring full consent for every gated tag. Legacy stored `'all'` strings still pass.
|
|
747
|
+
|
|
748
|
+
**cms-admin — router query strings + idle-logout warning.** `useAdminRouter` now includes the query string in `currentPath` on mount and popstate, so deep links like `/pages/new?parent=<id>` and `/settings?tab=users` survive hard reloads and back/forward. New `parseRouteQuery` helper and `search` return value; the command palette's `?selected=` deep links now open the media detail panel and user drawer. `useIdleLogout` gains a warning stage — one minute before idle sign-out a toast offers "Stay signed in" to reset the clock.
|
|
749
|
+
|
|
750
|
+
## 0.88.0
|
|
751
|
+
|
|
752
|
+
### Minor Changes
|
|
753
|
+
|
|
754
|
+
- 9046381: Design parity phase 3 (wave 2): enforced security settings, Post Type editor, Page Builder sections panel, Templates card grid, and the remaining split-pane editor conversions.
|
|
755
|
+
|
|
756
|
+
**cms-core**
|
|
757
|
+
- Security settings gain enforced session and access controls, stored in the settings global: `session.timeoutHours` (1/8/24/168) now drives the JWT expiry, Session row `expiresAt`, and cookie `Max-Age` at login; `session.idleLogoutEnabled` is exposed to clients via a new `GET /auth/session-policy` endpoint; `access.ipAllowlist` is enforced on every API request as the union of the DB list and config `admin.ipAllowlist`, with per-line CIDR validation and a confirmable `IP_LOCKOUT_RISK` guard when a save would lock the caller out; `access.auditLoggingEnabled` makes `logEvent()` a no-op except for security-critical events (failed logins, MFA changes, and security-settings changes are always recorded).
|
|
758
|
+
- Security Center reporting now reflects the configured session max age, audit state, and effective allowlist instead of hardcoded values.
|
|
759
|
+
|
|
760
|
+
**cms-admin**
|
|
761
|
+
- New Post Type editor at `/posts/types/:slug/edit` (replacing the fallthrough to the documents list): toolbar with breadcrumb and post-count badge, identity panel with live API endpoint preview, and a Field Builder that renders the collection's real schema with type/required badges plus an Add Field snippet generator (auto-derived keys, duplicate detection) for the config-defined schema.
|
|
762
|
+
- Settings → Security gains Sessions (timeout select, idle auto-logout toggle) and Access Control (editable IP allowlist with inline validation and lockout confirm, audit logging toggle) sections; idle logout is enforced client-side via a new `useIdleLogout` hook.
|
|
763
|
+
- The node-tree Page Builder gains a collapsible left sections panel (dnd-kit reorder, select-to-inspect, localStorage persistence), and Page Templates is rebuilt as the design's card grid with search, category filters, and Edit/Delete for non-built-in templates.
|
|
764
|
+
- SEO Content, collection Entries, and Forms schema editing move from overlay drawers to inline inspector panes (380/400/390px) using the shared SplitPane primitives, preserving all existing fields, validation, and save flows.
|
|
765
|
+
|
|
766
|
+
## 0.87.0
|
|
767
|
+
|
|
768
|
+
### Minor Changes
|
|
769
|
+
|
|
770
|
+
- 163ae1d: Design parity phase 3 (wave 1): split-pane editors, Script Tag delivery controls, Media SEO Audit, Pages tree view, and Dashboard onboarding.
|
|
771
|
+
|
|
772
|
+
**cms-core**
|
|
773
|
+
- `ScriptTag` gains `loadAsync`, `disableInPreview` (default on), and `consentCategory` fields (migration `0005_script_tag_delivery_controls`), accepted and validated on the script-tag API routes.
|
|
774
|
+
- `resolveScriptTagsForPath` accepts `{ preview }` and skips preview-disabled tags; the public `GET /script-tags/resolve` endpoint supports `?preview=1`. `loadAsync` injects `async` into external scripts, and consent-gated tags are wrapped in a bootstrap that waits for the consent plugin's "accept all" before injecting. The resolved response shape is unchanged.
|
|
775
|
+
- `ScriptTagGroup` now preserves `async`/`defer` attributes on external scripts.
|
|
776
|
+
- `GET /media` items include server-derived `seoScore`, `seoGrade`, and `seoIssues` (from `calculateMediaSeoScore`), and `PUT /media/:id` accepts `caption`, `description`, and `focusKeyword`.
|
|
777
|
+
|
|
778
|
+
**cms-admin**
|
|
779
|
+
- New `SplitPaneLayout` / `InspectorPane` primitives implementing the design's inline editor-pane pattern; Script Tags is converted to a split-pane view with placement tiles, priority stepper, scope tiles, consent category grid, and Load async / Disable in preview toggles.
|
|
780
|
+
- Media gains a Library / SEO Audit tab bar with summary cards, filter pills, an audit table with score bars, and an Edit flow into the library detail panel (which now edits caption, description, and focus keyword).
|
|
781
|
+
- Pages gains a List / Tree view toggle: hierarchy from `parentPageId` with path-prefix fallback, expand/collapse rows, template pills, and an "Add sub-page" action that preselects the parent (also editable via a new Parent page picker in Page settings).
|
|
782
|
+
- The Dashboard shows a getting-started onboarding block (derived 6-step checklist + quick links) in place of the activity grid while the site has no content or media.
|
|
783
|
+
|
|
784
|
+
**create-actuate-cms**
|
|
785
|
+
- Scaffolded middleware flags `?preview=` requests and the site layout resolves script tags with `preview: true` (uncached) so tags marked "disable in preview" never fire during CMS preview; Prisma assets mirror the new migration.
|
|
786
|
+
|
|
787
|
+
## 0.86.0
|
|
788
|
+
|
|
789
|
+
### Minor Changes
|
|
790
|
+
|
|
791
|
+
- 7c105f0: Design-parity phase 2: list-view furniture and header layout per the design handoff.
|
|
792
|
+
|
|
793
|
+
**cms-core**
|
|
794
|
+
- New `POST /collections/:slug/:id/duplicate` endpoint exposing the existing `duplicateDocument` action (create-scope gated, audit-logged, documented in the OpenAPI spec).
|
|
795
|
+
- `GET /posts` rows now include an `image` field — the document's featured-image URL (checks `featuredImage`/`image`/`coverImage`/`thumbnail`, string or `{ url }` media object) for list thumbnails.
|
|
796
|
+
|
|
797
|
+
**cms-admin**
|
|
798
|
+
- Pages and Posts lists gained per-row hover actions (Edit / Duplicate / Delete) via a new shared `RowActions` / `RowActionButton` primitive matching the design's `.ra-icon-btn` spec. Row delete confirms first; the homepage row's delete stays disabled.
|
|
799
|
+
- The Posts list shows the featured-image thumbnail when the post has one, falling back to the type-color block.
|
|
800
|
+
- Media Library header shows Total / Images / Videos / Documents / Storage-used stat pills per the design.
|
|
801
|
+
- Hero CTAs moved from the top bar into the page header (design `.page-hdr` → `.hdr-r`): Dashboard ("New Post" + "View Site"), Pages ("New page"), and Posts ("New Post").
|
|
802
|
+
- The account avatar uses the accent token instead of a hardcoded blue-purple gradient.
|
|
803
|
+
- SEO Technical → Crawl Settings now surfaces all six module toggles the API already supported: sitemap enabled, auto-regenerate on publish, and ping-search-engines join the existing three.
|
|
804
|
+
|
|
805
|
+
## 0.85.3
|
|
806
|
+
|
|
807
|
+
### Patch Changes
|
|
808
|
+
|
|
809
|
+
- 685958f: Correct 0.x upgrade guidance: document that npm update only applies patches within the pinned minor; use `actuate upgrade` for minor bumps. Admin Updates panel and update server now show the full upgrade command chain.
|
|
810
|
+
|
|
811
|
+
Clarify scaffold README populate/script-tag and upgrade steps.
|
|
812
|
+
|
|
813
|
+
## 0.85.2
|
|
814
|
+
|
|
815
|
+
### Patch Changes
|
|
816
|
+
|
|
817
|
+
- 6a6101a: Align admin UI with the ActuateCMS design system v1.0 handoff: canonical tokens, theme remapping, shell layout, shared Toggle/Input/Card primitives, and appearance accent tint variables.
|
|
818
|
+
|
|
819
|
+
## 0.85.1
|
|
820
|
+
|
|
821
|
+
### Patch Changes
|
|
822
|
+
|
|
823
|
+
- 6d254f3: Fix Script Tags delivery and admin toggles: green/red enabled switches with optimistic saves, in-process resolve + executable script rendering in scaffold layouts, pathname header for scoped tags, and cache invalidation on tag writes.
|
|
824
|
+
|
|
825
|
+
## 0.85.0
|
|
826
|
+
|
|
827
|
+
### Minor Changes
|
|
828
|
+
|
|
829
|
+
- 1a3cf91: Agent-ready public forms (WebMCP): declarative tool metadata on public form schemas, zero-JS `renderPublicFormHtml` / `ActuateFormHtml`, urlencoded submit parsing, HTML confirmation responses, scaffold form prefetch, `actuate doctor` check, and docs.
|
|
830
|
+
|
|
831
|
+
## 0.84.0
|
|
832
|
+
|
|
833
|
+
### Minor Changes
|
|
834
|
+
|
|
835
|
+
- e1ad198: Batch 4 (prototype → production): Organization admin UI in Settings → SEO (name, URL, logo, sameAs), organization.url in JSON-LD, and conditional AggregateRating on Service/Product pages when a reviews widget-embed section includes schema rating fields.
|
|
836
|
+
- e1ad198: Platform site-launch defaults: export `createNextSiteHelpers()` from `@actuate-media/cms-core/next/site` for ISR-friendly cached `/resolve`, scaffold new projects with production plugins and cached catch-all pages, and extend `actuate doctor` with site-launch checks (plugins, SEO routes, cached resolve, SSR sections).
|
|
837
|
+
- 6592e09: Add the `widget-embed` section type for lazy-loaded Elfsight, Trustpilot, and Calendly widgets with reserved min-height (CLS-safe) and optional cookie-consent gating via plugin-consent.
|
|
838
|
+
|
|
839
|
+
### Patch Changes
|
|
840
|
+
|
|
841
|
+
- a59a501: Batch 6 site performance and SEO: path-scoped ISR cache tags in createNextSiteHelpers, scaffold CMS API cache wiring with onInvalidate, JSON-LD in the catch-all, font preload template, and doctor checks for API cache and structured data HTML.
|
|
842
|
+
|
|
843
|
+
## 0.83.0
|
|
844
|
+
|
|
845
|
+
### Minor Changes
|
|
846
|
+
|
|
847
|
+
- 3983016: Yoast-style public sitemap URLs (`/post-sitemap.xml`, `/post-sitemap2.xml`, …), human-readable XSLT browser views, and middleware rewrites so collection sitemaps resolve at the site root.
|
|
848
|
+
|
|
849
|
+
## 0.82.0
|
|
850
|
+
|
|
851
|
+
### Minor Changes
|
|
852
|
+
|
|
853
|
+
- c4c41f3: Editor preview improvements for custom consumer templates (#473): configurable `admin.editor.defaultSitePreview`, post canvas reads `data.title` first, and a Site preview nudge on the structure canvas.
|
|
854
|
+
- b22a7bc: Phase 2 batch 1: paginate hot-path redirect/user queries with batched loaders, and enable ISR + Actuate tag revalidation on the dev site public pages. Admin clients fetch paginated redirect rules and request up to 200 team members per page.
|
|
855
|
+
|
|
856
|
+
## 0.81.0
|
|
857
|
+
|
|
858
|
+
### Minor Changes
|
|
859
|
+
|
|
860
|
+
- b8b930b: Drop the unused `searchVector` column (FTS uses `plainText` + `title`), add optional Stripe Billing Meter hooks for AI token and SEO AI usage, and document Prisma Postgres backup retention for DR.
|
|
861
|
+
|
|
862
|
+
## 0.80.1
|
|
863
|
+
|
|
864
|
+
### Patch Changes
|
|
865
|
+
|
|
866
|
+
- af18915: Migrate API handler, actions, cron, resolve, media, and auth hot paths from raw `console.*` to the structured logger with route-scoped error details on unhandled API failures.
|
|
867
|
+
|
|
868
|
+
## 0.80.0
|
|
869
|
+
|
|
870
|
+
### Minor Changes
|
|
871
|
+
|
|
872
|
+
- 5fce703: Enable AI monthly token budget enforcement when a limit is configured. Add plan-tier feature gating for advanced SEO Operations Center features (inline apply, content briefs, research, batch briefs) with admin upgrade messaging and pricing tier documentation.
|
|
873
|
+
|
|
874
|
+
## 0.79.3
|
|
875
|
+
|
|
876
|
+
### Patch Changes
|
|
877
|
+
|
|
878
|
+
- 8229d94: Wire Sentry for server API errors and admin ErrorBoundary reporting. Extend the daily cleanup cron to prune document versions (90 days), webhook delivery logs (30 days), and API request metrics (30 days). Add disaster recovery runbook documentation.
|
|
879
|
+
|
|
880
|
+
## 0.79.2
|
|
881
|
+
|
|
882
|
+
### Patch Changes
|
|
883
|
+
|
|
884
|
+
- 2966293: Strict rate limiters (login, TOTP, AI, uploads) fail closed when Upstash Redis is missing or errors in production; general API rate limiting remains fail-open (roadmap #393).
|
|
885
|
+
|
|
886
|
+
## 0.79.1
|
|
887
|
+
|
|
888
|
+
### Patch Changes
|
|
889
|
+
|
|
890
|
+
- 0ad67c9: Enforce collection `access.read` on document GET/list actions and GraphQL resolvers. Returns 403 when the rule denies the caller (roadmap #392).
|
|
891
|
+
|
|
892
|
+
## 0.79.0
|
|
893
|
+
|
|
894
|
+
### Minor Changes
|
|
895
|
+
|
|
896
|
+
- 39e2419: Phase 1–2: inline SEO audit fixes (deterministic + AI approve). Phase A: markdown export of manual/content fixes for dev-team handoff. Phase B: AI content briefs for thin content, heading, and alt-text issues. Phase C: topic research subagent, research-and-brief flow, and batch brief pre-generation. MCP: seven new tools for the audit fix workflow (`export_seo_fix_brief`, suggest/apply fix, content briefs, research-and-brief, batch generate) plus server instructions.
|
|
897
|
+
|
|
898
|
+
## 0.78.0
|
|
899
|
+
|
|
900
|
+
### Minor Changes
|
|
901
|
+
|
|
902
|
+
- bc51a1d: Align SEO audit with live page meta output, expand audit checks, wire redirect/link signals, and add `actuate migrate:meta` for bulk meta backfill.
|
|
903
|
+
|
|
904
|
+
## 0.77.3
|
|
905
|
+
|
|
906
|
+
### Patch Changes
|
|
907
|
+
|
|
908
|
+
- 9d4e24c: Hide the redundant site name headline on the login screen and in the admin sidebar when a logo image is displayed.
|
|
909
|
+
|
|
910
|
+
Serve favicons as properly sized 32×32 PNGs (and 180×180 apple-touch icons) from dedicated public routes so SVG and oversized sources render correctly in browser tabs.
|
|
911
|
+
|
|
912
|
+
Align SEO audit schema/canonical detection with public page rendering, add `actuate migrate:schema` to backfill structuredDataType on existing content, and fix route-contract tests for new favicon endpoints.
|
|
913
|
+
|
|
914
|
+
- 869620b: Align SEO audit, render, and Content SEO editing: auto-canonical and effective schema type resolution match `composePageMeta()`, schema fields sync on save, Content SEO drawer adds schema type, plugin removes dead `beforeCreate` hook and re-exports cms-core schema detection.
|
|
915
|
+
|
|
916
|
+
## 0.77.2
|
|
917
|
+
|
|
918
|
+
### Patch Changes
|
|
919
|
+
|
|
920
|
+
- 9875fa3: Omit temperature for reasoning and Opus 4.7+ models in the governed AI runtime so brand voice analyze and other features work when the default model is Claude Opus. Surface a clearer admin error when providers reject sampling parameters.
|
|
921
|
+
|
|
922
|
+
## 0.77.1
|
|
923
|
+
|
|
924
|
+
### Patch Changes
|
|
925
|
+
|
|
926
|
+
- 4dafd90: Fix brand voice analyze failures when Settings > AI uses legacy Anthropic curated model ids (`claude-*-4-latest`): remap saved ids to current API model strings, refresh the curated fallback catalog, surface provider error details on HTTP 400/404, and add plugin-ai fallback plus a setup readiness banner when governed AI is incomplete.
|
|
927
|
+
|
|
928
|
+
## 0.77.0
|
|
929
|
+
|
|
930
|
+
### Minor Changes
|
|
931
|
+
|
|
932
|
+
- d97e388: Add brand voice analysis: scan published pages and posts to draft a profile via `POST /ai/brand-voice/analyze`, with a Generate from content panel in Settings > AI.
|
|
933
|
+
- d97e388: Brand voice Phase 2: portable `brand-voice-profile.md` documents with export/import, an advanced markdown editor, and server-side sync between the document and quick-edit fields.
|
|
934
|
+
- d97e388: Brand voice Phase 3: structured rule editors (vocabulary, channel overrides, worked examples), deterministic alignment scoring via `POST /ai/brand-voice/score`, and a check-alignment panel in Settings > AI.
|
|
935
|
+
- d97e388: Phase 4 brand voice: wire alignment scoring into co-author and SEO field generation, add analyze-and-fix endpoint with hybrid deterministic + AI rewrite, and extend profile analyzer with SSRF-safe URL fetch and text file uploads.
|
|
936
|
+
|
|
937
|
+
## 0.76.3
|
|
938
|
+
|
|
939
|
+
### Patch Changes
|
|
940
|
+
|
|
941
|
+
- 7a6a140: Support Vercel Marketplace Blob auth via OIDC (`BLOB_STORE_ID` + `VERCEL_OIDC_TOKEN`) instead of requiring `BLOB_READ_WRITE_TOKEN`. Fix `@vercel/blob` client options to use `oidcToken` + `storeId` (never pass OIDC as `token`). Update env diagnostics, upload errors, and `actuate vercel:blob-link` accordingly.
|
|
942
|
+
|
|
943
|
+
## 0.76.2
|
|
944
|
+
|
|
945
|
+
### Patch Changes
|
|
946
|
+
|
|
947
|
+
- 90b5826: Allow `prisma generate` without DATABASE_URL by using optional env in prisma.config.ts (CI and fresh clones).
|
|
948
|
+
|
|
949
|
+
## 0.76.1
|
|
950
|
+
|
|
951
|
+
### Patch Changes
|
|
952
|
+
|
|
953
|
+
- 4899af9: Fix consumer production builds on Next.js 16 by not injecting a no-op webpack hook in `withActuateCMS`. Add `CRON_SECRET` to scaffold `.env.example`. Remove obsolete `earlyAccess` from scaffolded `prisma.config.ts`. Load project `.env` files in `actuate doctor` and `actuate deploy:check`.
|
|
954
|
+
- df1176a: Remove deprecated `earlyAccess: true` from Prisma config (Prisma 7.8 TypeScript compatibility).
|
|
955
|
+
|
|
956
|
+
## 0.76.0
|
|
957
|
+
|
|
958
|
+
### Minor Changes
|
|
959
|
+
|
|
960
|
+
- c656ddc: Add an `embed` (video) section type for YouTube, Vimeo, and Loom.
|
|
961
|
+
- **cms-core:** new `embed` section in the registry (`url`, `title`, `ratio`, `caption`) plus a shared, deterministic `parseEmbed()` provider allowlist (`@actuate-media/cms-core/page-sections`). Section validation rejects any URL that isn't a recognised YouTube/Vimeo/Loom link, so an arbitrary URL can never reach an iframe.
|
|
962
|
+
- **sections-react:** `EmbedSection` renderer — a lazy-loaded provider iframe inside a fixed aspect-ratio box (16:9 / 4:3 / 1:1 / 9:16) so the embed reserves its space and never causes layout shift (CLS). The URL is re-parsed through the same allowlist at render time.
|
|
963
|
+
- **cms-admin:** matching editor-canvas body for `embed` (provider + ratio placeholder) so the page/post editors preview the section instead of showing a generic "custom section" placeholder.
|
|
964
|
+
- **mcp-server:** `list_section_types` description now lists `embed`.
|
|
965
|
+
|
|
966
|
+
## 0.75.0
|
|
967
|
+
|
|
968
|
+
### Minor Changes
|
|
969
|
+
|
|
970
|
+
- 5446671: SEO sitemap: wire up the "Include images in sitemap" crawl setting and remove two non-functional toggles.
|
|
971
|
+
- **cms-core:** the per-collection XML sitemap now emits Google image-sitemap `<image:image>` entries for each document's featured image and any `hero`/`feature`/`gallery` section images when "Include images in sitemap" is enabled (the default). URLs are absolutized against the site origin, deduped, and capped per page; the `xmlns:image` namespace is declared only when at least one image is present.
|
|
972
|
+
- **cms-admin + cms-core:** removed the `noindexPaginatedArchives` and `noindexTagPages` crawl toggles. They round-tripped through storage but were never applied — paginated/tag archive pages are rendered by the consumer's frontend, which the CMS doesn't control, so these could not be enforced server-side. Removing them avoids a misleading no-op in Settings → SEO → Technical.
|
|
973
|
+
|
|
974
|
+
## 0.74.0
|
|
975
|
+
|
|
976
|
+
### Minor Changes
|
|
977
|
+
|
|
978
|
+
- 2b61b66: Core Web Vitals: give the `hero` section an optional image rendered as the page LCP, and serve AVIF in scaffolded sites.
|
|
979
|
+
- **Hero image (LCP):** the canonical `hero` section now has an optional `imageUrl`/`imageAlt` field. When set, the renderer draws it below the copy as the page's single priority (eager, `fetchpriority=high`) image with a full-width `sizes` hint and intrinsic dimensions — the explicit, dimensioned LCP element the best-practice spec calls for (rather than leaving the LCP to chance). Alt text is required when an image is set (existing accessibility rule). Dimensions are backfilled by the `/resolve` enrichment (hero is now enriched like `feature`).
|
|
980
|
+
- **AVIF delivery:** `create-actuate-cms` now sets `images.formats: ['image/avif', 'image/webp']` so scaffolded sites serve AVIF (≈50% smaller than JPEG) first, then WebP — a direct LCP win via `next/image` format negotiation.
|
|
981
|
+
|
|
982
|
+
## 0.73.0
|
|
983
|
+
|
|
984
|
+
### Minor Changes
|
|
985
|
+
|
|
986
|
+
- 7b7f0be: Core Web Vitals: get intrinsic image dimensions onto every image so `next/image` engages and CLS is eliminated.
|
|
987
|
+
|
|
988
|
+
Without `width`/`height`, consumers silently fall back to an unsized `<img>` (no srcset, layout shift). This closes that gap two ways:
|
|
989
|
+
- **Read-time (`/resolve`) enrichment** now covers the **post featured image** and **`hero` section images** (previously only `feature`/`gallery`). The resolver looks up dimensions + alt from `actuate_media` and returns the featured image as `post.featuredImage = { url, width, height, alt }` (new `ResolveFeaturedImage` type). Exposes a `featuredImageUrl()` helper. Purely additive — the stored document is never mutated.
|
|
990
|
+
- **Author-time persistence**: selecting an image in the section editor's media picker now writes the intrinsic `width`/`height` alongside the URL (`imageUrl` → `imageWidth`/`imageHeight`; gallery `url` → `width`/`height`), so dimensions travel with the document even without `/resolve`. Exposes `companionDimensionKeys()`.
|
|
991
|
+
|
|
992
|
+
`create-actuate-cms` + `apps/dev`: the generated post page now feeds the resolved featured-image dimensions into the post header for a CLS-free, dimensioned LCP image.
|
|
993
|
+
|
|
994
|
+
## 0.72.1
|
|
995
|
+
|
|
996
|
+
### Patch Changes
|
|
997
|
+
|
|
998
|
+
- 2fa4ebb: Fix admin brand logo: always show the logo (never the favicon), make it responsive, and degrade gracefully on broken images.
|
|
999
|
+
- **cms-core**: `resolveAdminBrand` no longer falls back to the favicon source for the sidebar mark. The favicon is a browser-tab icon, and using it in the admin made the brand appear to "switch" between the logo and the favicon (collapsed rail and the brief no-primary boot state). With no dedicated logo mark, the admin mark is now `null` so the sidebar falls back to the primary logo.
|
|
1000
|
+
- **cms-admin**: The sidebar `BrandLogo` now caps logo width (`max-w-full`) so wide horizontal logos scale down instead of overflowing, prefers the logo in the collapsed rail, and attaches `onError` handlers that fall through the logo chain (and finally the neutral placeholder) instead of rendering the browser's broken-image icon when an asset URL is broken/expired/deleted. The Settings → Appearance upload preview also falls back to the placeholder icon on image load failure.
|
|
1001
|
+
|
|
1002
|
+
## 0.72.0
|
|
1003
|
+
|
|
1004
|
+
### Minor Changes
|
|
1005
|
+
|
|
1006
|
+
- 6f140bc: Auto-wire the `platform.storage` shorthand to a real adapter server-side, and keep `actuate.config.ts` client-safe.
|
|
1007
|
+
|
|
1008
|
+
`actuate.config.ts` is imported by the admin client bundle, so putting a platform adapter (e.g. `platform: vercel()`) in it pulls `node:*`/`@vercel/blob` into the client build and breaks it with `Module not found: node:fs`. The config must stay adapter-free.
|
|
1009
|
+
- **cms-core**: When the API handler boots and sees a remote `platform.storage` shorthand (`vercel-blob` → `@actuate-media/platform-vercel`, `s3` → `@actuate-media/platform-aws`) with no adapter registered, it now dynamically imports the matching platform package and registers its storage adapter server-side. The import specifier is computed so bundlers never try to statically resolve an optional peer; a missing package degrades to a clear guidance warning instead of a silent upload failure. An explicitly registered adapter always wins. `setStorageAdapter` / `getStorageAdapter` are now also re-exported from `@actuate-media/cms-core/api` for server-side registration of custom adapters.
|
|
1010
|
+
- **cms-core**: Clearer upload/diagnostics errors — when a Vercel Blob store is linked (`BLOB_STORE_ID`) but `BLOB_READ_WRITE_TOKEN` is missing, messages now explain that Vercel does not provide the token via OIDC and it must be added per-environment.
|
|
1011
|
+
- **create-actuate-cms**: The generated `actuate.config.ts` uses the client-safe `storage: { provider: 'vercel-blob' | 's3' }` shorthand again (reverting the adapter-in-config change that broke the client build); uploads work out of the box via server-side auto-wiring.
|
|
1012
|
+
|
|
1013
|
+
## 0.71.1
|
|
1014
|
+
|
|
1015
|
+
### Patch Changes
|
|
1016
|
+
|
|
1017
|
+
- 1c81772: Make storage configuration honest. `platform.storage: { provider: 'vercel-blob' }` is a descriptive shorthand that never registered an adapter, so uploads silently failed with a misleading `BLOB_READ_WRITE_TOKEN` error. Now:
|
|
1018
|
+
- cms-core logs a clear boot-time warning when a remote `provider` shorthand is set but no adapter is registered, and `/media/upload` (used by media + appearance uploads) returns an error that names the real gap — a missing adapter, not a missing token.
|
|
1019
|
+
- `create-actuate-cms` generates `platform: vercel()` (a real adapter) instead of the inert shorthand, and the docs gain a dedicated Storage section.
|
|
1020
|
+
|
|
1021
|
+
- 9e0d82b: Stop persisting the lifecycle `status` inside document `data`. The lifecycle status is a top-level column, but a lifecycle-valued `status` passed in (or left behind by a migration/import script) was hoisted to the column **and** kept in the `data` JSON, where it confused the admin UI. `createDocument`/`updateDocument` now strip a lifecycle-valued `status` from `data` on write (and an edit cleans up already-polluted documents). Collection-defined `status` fields with non-lifecycle values — e.g. forms' `draft`/`active`/`archived` — are preserved.
|
|
1022
|
+
|
|
1023
|
+
## 0.71.0
|
|
1024
|
+
|
|
1025
|
+
### Minor Changes
|
|
1026
|
+
|
|
1027
|
+
- 9f5e5dc: Add the AI crawler activity panel (llms.txt measurement layer):
|
|
1028
|
+
- **`cms-core`** — new `AiCrawlerHit` model (`actuate_ai_crawler_hits`) + migration, aggregated per `(bot, surface)` so storage stays bounded. `detectAiBot()` classifies known AI user-agents (training vs retrieval) from the existing taxonomy; `llms.txt`, `llms-full.txt`, and `.md` requests from a known crawler are recorded best-effort. New `GET /ai-crawlers` (admin) returns aggregated activity, and an opt-in honeypot (`seo.llms.honeypot`) appends a trap link to `llms.txt` served by `GET /ai-crawlers/trap`.
|
|
1029
|
+
- **`cms-admin`** — new "AI crawler activity" panel under SEO → Technical showing per-bot hits, type, surface, and last-seen, plus totals. Frames the numbers honestly (visibility, not a traffic channel).
|
|
1030
|
+
- **`create-actuate-cms`** — scaffolded Prisma assets include the new migration so fresh projects get crawler tracking out of the box.
|
|
1031
|
+
|
|
1032
|
+
Recording is always best-effort and gracefully no-ops on databases that predate the model.
|
|
1033
|
+
|
|
1034
|
+
## 0.70.0
|
|
1035
|
+
|
|
1036
|
+
### Minor Changes
|
|
1037
|
+
|
|
1038
|
+
- 0bcc567: Add per-page Markdown for the AI/agent layer:
|
|
1039
|
+
- **`GET /md?path=…`** (`@actuate-media/cms-core`) — returns a clean Markdown rendering of a single published page via `documentToMarkdown()`. Served with `X-Robots-Tag: noindex`; noindexed and `llmsInclude: NEVER` documents return `404` (the selection filter is the privacy boundary).
|
|
1040
|
+
- **Clean `/{path}.md` URLs** — `create-actuate-cms` middleware rewrites `/{path}.md` → `/api/cms/md`, so any page is fetchable as Markdown by appending `.md` (e.g. `/about.md`). The HTML page stays canonical and `.md` URLs are never in the sitemap.
|
|
1041
|
+
- **Shared path resolver** — extracted `matchPathToCollection()` so `/md` and `/resolve` agree on which document a public URL maps to (no logic drift).
|
|
1042
|
+
|
|
1043
|
+
## 0.69.0
|
|
1044
|
+
|
|
1045
|
+
### Minor Changes
|
|
1046
|
+
|
|
1047
|
+
- a889054: Add the advanced `llms.txt` layer — clean Markdown content export for AI agents:
|
|
1048
|
+
- **`documentToMarkdown()` serializer** (`@actuate-media/cms-core`) — renders a published document to clean Markdown. Rich-text/HTML slots are converted with Turndown; structured section content (and the page-builder block tree) is walked generically using an allow-list of prose/heading keys, so non-content data (URLs, ids, settings) never leaks and new section types render without per-type code.
|
|
1049
|
+
- **`/llms-full.txt`** — opt-in route (`seo.llms.fullEnabled`) serving the same curated, noindex, sitemap-excluded selection as `/llms.txt` with each page's Markdown inlined and delimited by its canonical URL. Bounded by `seo.llms.maxFullBytes` (default 2 MB) with clean truncation.
|
|
1050
|
+
- **Shared selection** — `/llms.txt` and `/llms-full.txt` now share one selector (`selectLlmsDocs` / `toLlmsPage`) so curation and the noindex privacy boundary can't drift.
|
|
1051
|
+
- **Scaffold** — `create-actuate-cms` proxies `/llms-full.txt` at the domain root (always generated; enabling is config-only).
|
|
1052
|
+
|
|
1053
|
+
## 0.68.0
|
|
1054
|
+
|
|
1055
|
+
### Minor Changes
|
|
1056
|
+
|
|
1057
|
+
- 4bf16c4: Harden the `/llms.txt` AI discovery file:
|
|
1058
|
+
- **Root proxy** — `create-actuate-cms` now scaffolds `app/llms.txt/route.ts`, exposing the file at the spec-required domain root (`/llms.txt`) in addition to `/api/cms/llms.txt`.
|
|
1059
|
+
- **noindex** — the route now sends `X-Robots-Tag: noindex` (and the scaffolded proxy preserves it) so the machine-facing index isn't itself indexed.
|
|
1060
|
+
- **Sanitization** — titles, descriptions, and the site summary are collapsed to a single line and stripped of markdown link-breaking and control/zero-width characters, then length-capped. Reduces corruption and instruction-smuggling risk from editor fields.
|
|
1061
|
+
- **Curation controls** — new `seo.llms` config: `disabled`, `eligibleCollections` (restrict AUTO selection to high-value types), `maxPerSection` (default 25), and `maxEntries` (default 50) so large archives can't flood the file.
|
|
1062
|
+
|
|
1063
|
+
- 47bed20: Add per-document and site-level curation for the `/llms.txt` AI discovery file:
|
|
1064
|
+
- **Per-doc curation** — `llmsInclude` (AUTO/ALWAYS/NEVER), `llmsDescription`, `llmsSection`, `llmsOptional`, and `llmsPriority` are read by the generator and round-tripped through the `/seo/content/:type/:id` API. ALWAYS pins an entry in regardless of the eligibility heuristic; NEVER excludes it.
|
|
1065
|
+
- **Spec correctness** — entries flagged optional are emitted under the reserved `## Optional` section; explicit section overrides and per-section priority sorting are honored; an optional reviewed intro block can follow the summary.
|
|
1066
|
+
- **Noindex exclusion** — noindexed documents are now excluded from `llms.txt` (the selection filter is the privacy boundary).
|
|
1067
|
+
- **Site config** — new `seo.llms.summary` (the reviewed blockquote, falling back to `defaultMetaDescription`) and `seo.llms.intro`.
|
|
1068
|
+
- **Admin UX** — the Edit SEO drawer gains an "AI discovery (llms.txt)" section: include mode, curated agent description with a character counter, section, and an optional toggle.
|
|
1069
|
+
|
|
1070
|
+
## 0.67.0
|
|
1071
|
+
|
|
1072
|
+
### Minor Changes
|
|
1073
|
+
|
|
1074
|
+
- 3ab0649: Add a nuanced AI-crawler policy to robots.txt generation. Training crawlers
|
|
1075
|
+
(GPTBot, ClaudeBot, Google-Extended, CCBot, …) and retrieval/answer-engine
|
|
1076
|
+
crawlers (OAI-SearchBot, PerplexityBot, ChatGPT-User, …) can now be allowed or
|
|
1077
|
+
blocked independently via `seo.robots.aiPolicy: { training, retrieval }`, so a
|
|
1078
|
+
site can stay out of training sets while remaining citable in AI answers. The
|
|
1079
|
+
legacy `seo.robots.blockAIBots` boolean still works (blocks both groups) and is
|
|
1080
|
+
used only when `aiPolicy` is not set. Curated 2026 bot lists are exported as
|
|
1081
|
+
`AI_TRAINING_BOTS` / `AI_RETRIEVAL_BOTS`.
|
|
1082
|
+
- 5545bfd: Auto-create a 301 redirect when a published document's URL changes. When a
|
|
1083
|
+
document that is (and stays) published changes its `slug` or `data.path`, the
|
|
1084
|
+
old URL — already indexed and linked — is kept alive with a permanent redirect
|
|
1085
|
+
to the new URL. Planning is loop-safe and flattens chains: rules pointing at the
|
|
1086
|
+
old path are repointed to the new one, and a stale rule whose source is the
|
|
1087
|
+
now-live new path is removed. Runs post-commit and best-effort, so a redirect
|
|
1088
|
+
failure never rolls back the content save.
|
|
1089
|
+
- 3f7fbf3: Add RSS 2.0 (`/feed.xml`) and JSON Feed 1.1 (`/feed.json`) content feeds.
|
|
1090
|
+
|
|
1091
|
+
Both serve the newest published items across post-type collections (falling back
|
|
1092
|
+
to pages when there are no post collections), newest first, capped at 50, with
|
|
1093
|
+
canonical URLs and per-document `noindex` exclusion. Item descriptions prefer the
|
|
1094
|
+
GEO key takeaway, then the meta description, then the excerpt. Pure builders are
|
|
1095
|
+
exported as `buildRssFeed` / `buildJsonFeed`. New `create-actuate-cms` projects
|
|
1096
|
+
scaffold root `/feed.xml` + `/feed.json` proxy routes.
|
|
1097
|
+
|
|
1098
|
+
- bf00622: Add a GEO "key takeaway" field — a concise, citable 1-2 sentence page summary
|
|
1099
|
+
for generative answer engines.
|
|
1100
|
+
- Editable in the document SEO panel and the SEO Operations Center, each with an
|
|
1101
|
+
AI Generate button.
|
|
1102
|
+
- New `generateKeyTakeaway()` in plugin-ai and a `keyTakeaway` branch on
|
|
1103
|
+
`POST /ai/seo/generate-field`.
|
|
1104
|
+
- Rendered automatically as the Schema.org `abstract` on the page/article node,
|
|
1105
|
+
as a meta-description fallback, and as the preferred `llms.txt` description.
|
|
1106
|
+
- Stored as `data.keyTakeaway` (or `pageSettings.keyTakeaway`).
|
|
1107
|
+
|
|
1108
|
+
- f05c89b: `composePageMeta()` now emits a connected Schema.org `@graph` instead of a
|
|
1109
|
+
single flat node, so every public page (and the `/resolve` + `/seo/meta`
|
|
1110
|
+
responses that build on it) ships one traversable graph: WebSite +
|
|
1111
|
+
Organization (with `sameAs` and an optional SearchAction), the page node
|
|
1112
|
+
(WebPage or Article/BlogPosting), a BreadcrumbList derived from the URL path,
|
|
1113
|
+
FAQ `mainEntity`, a Person node for the author, and a linked specialized entity
|
|
1114
|
+
node for non-page types (LocalBusiness, Service, …). A doc-level
|
|
1115
|
+
`structuredData` override is still honored verbatim.
|
|
1116
|
+
|
|
1117
|
+
This changes the shape of `ComposedPageMeta.jsonLd` from a flat object to
|
|
1118
|
+
`{ '@context', '@graph': [...] }`. Consumers that render it as a single
|
|
1119
|
+
`<script type="application/ld+json">` need no changes; code that read specific
|
|
1120
|
+
top-level keys off `jsonLd` should read them off the relevant `@graph` node.
|
|
1121
|
+
|
|
1122
|
+
- eb3786d: Metadata completeness for the public render path:
|
|
1123
|
+
- `composePageMeta()` now emits a Next.js `metadataBase` (from `seo.siteUrl`) so
|
|
1124
|
+
relative OG/canonical URLs resolve to absolute ones.
|
|
1125
|
+
- `og:image` falls back to the page's own hero / featured / cover image (string
|
|
1126
|
+
or `{ url }`) before the site default and the dynamic `/og.png`.
|
|
1127
|
+
- hreflang alternates are emitted in `<head>` (`<link rel="alternate" hreflang>`)
|
|
1128
|
+
and as `alternates.languages` in the Next metadata, including `x-default`.
|
|
1129
|
+
`/resolve` supplies them from the document's `localeGroupId` siblings.
|
|
1130
|
+
- `/llms.txt` now emits absolute, canonical URLs (honoring `data.path` and
|
|
1131
|
+
`urlPrefix`) and uses the configured `seo.siteName` / `seo.defaultMetaDescription`
|
|
1132
|
+
instead of a hardcoded empty origin and "Actuate CMS" name.
|
|
1133
|
+
|
|
1134
|
+
- 88161e8: Add a server-side publish validation gate. Publishing publicly-routed content
|
|
1135
|
+
(collections typed `page`/`post`) now runs a shared validator in the data layer
|
|
1136
|
+
— so the SDK, MCP tools, agents, and the API can't publish a page that's broken
|
|
1137
|
+
in ways the admin would have caught. Hard, content-agnostic invariants block a
|
|
1138
|
+
publish (a published page must have a title and a URL via slug or `data.path`);
|
|
1139
|
+
softer issues (e.g. missing meta description) are returned as warnings and never
|
|
1140
|
+
block. Configurable via `seo.publishValidation` (`block` default / `warn` /
|
|
1141
|
+
`off`). The documents API now maps these and other action errors to correct
|
|
1142
|
+
status codes (422 validation, 403 permission/access, 404 not-found) instead of a
|
|
1143
|
+
generic 500.
|
|
1144
|
+
- 2f1f398: Add authoring guardrails to the page and post editors. The slug field now shows
|
|
1145
|
+
inline validation (malformed/reserved) using the same `validateSlug` rules the
|
|
1146
|
+
server publish gate enforces, and the client pre-publish check runs that same
|
|
1147
|
+
validation so failures appear in the validation summary instead of only as a
|
|
1148
|
+
post-publish toast. SEO title/description fields gain accessible character
|
|
1149
|
+
counters with quality bands (icon + label, not color alone) and smart
|
|
1150
|
+
placeholders that preview the title/excerpt fallback.
|
|
1151
|
+
|
|
1152
|
+
`@actuate-media/cms-core/client` now re-exports the browser-safe slug helpers
|
|
1153
|
+
(`validateSlug`, `normalizeSlug`, `isValidSlug`, `isReservedSlug`, etc.).
|
|
1154
|
+
|
|
1155
|
+
- a3c5904: Add consistent content freshness signals. A shared `resolveContentDates` helper
|
|
1156
|
+
decides `datePublished`/`dateModified` for every public surface (honoring
|
|
1157
|
+
editorial date fields like `publishedDate`/`publishDate`/`date`), and both
|
|
1158
|
+
JSON-LD and Open Graph now use it so structured data and social tags agree.
|
|
1159
|
+
|
|
1160
|
+
`llms.txt` entries now include an `Updated: YYYY-MM-DD` marker, JSON Feed items
|
|
1161
|
+
emit `date_modified` when newer than publication, the post-archive sitemap
|
|
1162
|
+
`<lastmod>` is derived from the newest post (instead of the current time), and
|
|
1163
|
+
the authenticated SEO preview endpoints now use `publishedAt` for publish time
|
|
1164
|
+
(matching production) instead of `createdAt`.
|
|
1165
|
+
|
|
1166
|
+
- ee53cfc: Unify image alt-text analysis behind a single decorative-aware analyzer
|
|
1167
|
+
(`analyzeImageAlts` / `countMissingAltImages` / `isDecorativeImgTag`) so the SEO
|
|
1168
|
+
score, audit engine, content-health scanner, and cron scan report consistent
|
|
1169
|
+
"missing alt" counts. Decorative images (`alt=""`, `role="presentation"`,
|
|
1170
|
+
`aria-hidden="true"`) are correctly excluded from the missing-alt count.
|
|
1171
|
+
|
|
1172
|
+
The media picker now seeds an inserted image's alt text from the asset's curated
|
|
1173
|
+
library alt text instead of the file name.
|
|
1174
|
+
|
|
1175
|
+
- 218c5b0: Add canonical slug rules and server-side validation. A new `content/slug`
|
|
1176
|
+
module (`normalizeSlug`, `isValidSlug`, `isReservedSlug`, `validateSlug`,
|
|
1177
|
+
`SLUG_PATTERN`, `RESERVED_SLUGS`) is the single source of truth for slug format.
|
|
1178
|
+
The publish gate now rejects malformed (uppercase, spaces, underscores,
|
|
1179
|
+
leading/trailing/double hyphens) and reserved (`admin`, `api`, `sitemap.xml`, …)
|
|
1180
|
+
slugs server-side, and the admin `SlugField` normalizes input to the same rules.
|
|
1181
|
+
|
|
1182
|
+
Unify SEO field-name reads so a value set in any editor surface is honored on
|
|
1183
|
+
the public page: canonical now accepts `canonicalUrl` (plugin-seo / SEO
|
|
1184
|
+
workspace) in addition to `canonical`; robots now accepts `noindex`/`nofollow`
|
|
1185
|
+
(SEO workspace) and `robotsNoindex`/`robotsNofollow` (plugin-seo) alongside
|
|
1186
|
+
`noIndex`/`noFollow`; the SEO analysis endpoint accepts `focusKeyword` alongside
|
|
1187
|
+
`focusKeyphrase`.
|
|
1188
|
+
|
|
1189
|
+
- df5d0de: SEO sitemap hardening: canonical URLs, noindex exclusion, accurate lastmod, hreflang, and IndexNow.
|
|
1190
|
+
|
|
1191
|
+
**cms-core**
|
|
1192
|
+
- Sitemaps and the canonical `<link>` / `og:url` now resolve through a single shared `resolveDocumentUrl()` helper, so they can never disagree. It honours an explicit `data.path` (e.g. `/` for the homepage, `/about/story`), maps `home`/`index` to the site root, and otherwise uses `<urlPrefix>/<slug>`. Fixes homepages being listed as `/home` and any page whose `data.path` differed from `/{slug}`.
|
|
1193
|
+
- Noindexed documents (`noIndex`/`noindex`/`robotsPolicy: noindex*`, collection/site defaults, non-production guard) are now excluded from the sitemap so it never conflicts with a page's own robots meta tag.
|
|
1194
|
+
- The "Include posts in sitemap" crawl setting is now honoured by sitemap generation (both the index and the per-collection sitemap, which 404s when posts are excluded).
|
|
1195
|
+
- The "Index entire site" master switch is now enforced: when off, `robots.txt` emits a blanket `Disallow: /` for all crawlers, the sitemap (index + per-collection) serves empty, and publish-time search-engine/IndexNow pings are suppressed. This governs the public site only — CMS admin/API protection (auth + the admin's own `noindex` meta tag + the always-present `/admin` and `/api/` disallows) is unaffected.
|
|
1196
|
+
- Sitemap-index `<lastmod>` now reflects each collection's newest published change instead of "now", so crawlers can trust it.
|
|
1197
|
+
- `robots.txt` and the SEO settings now advertise the canonical public `/sitemap.xml` instead of the internal `/api/cms/sitemap.xml`.
|
|
1198
|
+
- hreflang `<xhtml:link rel="alternate">` annotations (incl. `x-default`) are emitted for translated documents when i18n is configured with more than one locale. No overhead for single-locale sites.
|
|
1199
|
+
- IndexNow support: when `INDEXNOW_KEY` is set, changed URLs are submitted to IndexNow (Bing, Yandex, …) on publish/update/delete, and the key is served at `/api/cms/indexnow-key.txt`. New exports: `resolveDocumentUrl`, `buildIndexNowSubmission`, `INDEXNOW_ENDPOINT`.
|
|
1200
|
+
|
|
1201
|
+
**create-actuate-cms**
|
|
1202
|
+
- Scaffolded projects ship root `app/sitemap.xml/route.ts` and `app/robots.txt/route.ts` proxy routes so the canonical `/sitemap.xml` and `/robots.txt` URLs resolve out of the box.
|
|
1203
|
+
|
|
1204
|
+
## 0.66.0
|
|
1205
|
+
|
|
1206
|
+
### Minor Changes
|
|
1207
|
+
|
|
1208
|
+
- a6a17e9: Split login: serve the auth screens on their own non-obvious path while the admin app stays at `/admin`.
|
|
1209
|
+
- **cms-core**: new `admin.loginPath` config. `createCMSMiddleware` applies the same guards (IP allowlist, CMS_SECRET check, setup bypass) to the login mount and redirects expired sessions there. Emailed password-reset and invite links point at the login mount when configured.
|
|
1210
|
+
- **cms-admin**: new `LoginRoot` component mounts login (+ MFA), the first-run setup wizard, and forgot/reset-password on a separate route (e.g. `/securelogin`, with setup at `/securelogin/setup`), then hands off to the admin app after sign-in. `<AdminRoot loginPath>` makes the admin mount render a 404 lookalike for signed-out visitors instead of a login form — no redirect, so the login location is never leaked.
|
|
1211
|
+
- **create-actuate-cms**: new `--login-path` flag and prompt (default `/securelogin`). New projects scaffold the split-login layout by default: login/setup at the login path, admin app at the admin path, config + proxy wired accordingly. Pass `--login-path` equal to `--admin-path` for the classic inline login.
|
|
1212
|
+
|
|
1213
|
+
## 0.65.0
|
|
1214
|
+
|
|
1215
|
+
### Minor Changes
|
|
1216
|
+
|
|
1217
|
+
- 3e2daa4: Inline branded preview — see the draft through your real site without leaving the editor.
|
|
1218
|
+
- cms-admin: the page and post editors gain a "Site preview" toggle in the top bar that swaps the structure canvas for an iframe of the public site rendering the draft (same signed `?preview=<token>` contract as "View on site"). Unsaved edits are persisted first, the frame reloads after every save, and the existing viewport controls size the frame.
|
|
1219
|
+
- cms-core: `createSiteClient().resolveDocument(path, { previewToken })` forwards a preview token to `/resolve` so consumer routes can render drafts at their real URL.
|
|
1220
|
+
- create-actuate-cms: the scaffolded catch-all route now forwards `?preview=` to the resolver and shows a draft banner, so new projects support "View on site" and inline preview out of the box.
|
|
1221
|
+
|
|
1222
|
+
## 0.64.0
|
|
1223
|
+
|
|
1224
|
+
### Minor Changes
|
|
1225
|
+
|
|
1226
|
+
- 135d0c7: Taxonomy Phase B — admin facet filters and taxonomy columns on collection lists.
|
|
1227
|
+
- cms-core: `GET /collections/:slug` now accepts `filter.<field>=<value>` query params for fields declared in the collection config. Text/select fields filter by exact match; tag-shaped array fields filter by containment, matching both the canonical `[{ tag }]` row shape and legacy plain-string arrays. Undeclared fields are silently ignored. `listDocuments` gains a `dataFilters` option and `DataFieldFilter` is exported.
|
|
1228
|
+
- cms-admin: the collection list grows a facet filter bar (select fields use config options; free-text `category` and tag-shaped arrays load the collection's in-use values via the field-values API) plus taxonomy columns showing category/tag values per row, with a one-click "Clear filters" action.
|
|
1229
|
+
|
|
1230
|
+
## 0.63.0
|
|
1231
|
+
|
|
1232
|
+
### Minor Changes
|
|
1233
|
+
|
|
1234
|
+
- e3da3bc: Taxonomy Phase A — structured categories and tag autocomplete (UpChat platform feedback P1 + P2).
|
|
1235
|
+
|
|
1236
|
+
**cms-core**
|
|
1237
|
+
- New `GET /collections/:slug/field-values?field=&q=` endpoint returning distinct in-use values for a declared text/select/array field (case-insensitive merge, most-used first, capped at 25). Powers admin tag autocomplete and category suggestions. Auth + collection read scope enforced; undeclared or long-form fields are rejected.
|
|
1238
|
+
- New `categoryField({ options })` field preset — renders as a structured `select` when options are provided, free text otherwise.
|
|
1239
|
+
- `blogPostCollection({ categoryOptions: [...] })` (and every collection preset with a category field) upgrades the free-text category to a select without hand-rolling field overrides. Plain strings are slugified into `{ label, value }` pairs; explicit `fields.category` overrides still win.
|
|
1240
|
+
|
|
1241
|
+
**cms-admin**
|
|
1242
|
+
- New `TagInput` chip editor with autocomplete from the collection's existing vocabulary. Dedupes case-insensitively and resolves to the canonical casing already in use — no more `AI` / `ai` / `Artificial Intelligence` drift.
|
|
1243
|
+
- Post details modal is now field-config aware: a `select` category renders a dropdown (legacy free-text values stay selectable), and an `array` tags field renders the chip input. Free-text configs keep their current inputs.
|
|
1244
|
+
- `EditorPost` gains `tags`, round-tripped as canonical `[{ tag }]` rows; legacy plain-string arrays are still read.
|
|
1245
|
+
|
|
1246
|
+
## 0.62.0
|
|
1247
|
+
|
|
1248
|
+
### Minor Changes
|
|
1249
|
+
|
|
1250
|
+
- d6b21e7: Add Settings > Email: configure the transactional email provider from the admin UI.
|
|
1251
|
+
- **cms-admin**: new Email tab in Settings with a provider picker (Resend, SMTP.com, Amazon SES, generic SMTP), per-provider credential fields, masked stored secrets (last-4 only, write-only inputs), a "Send test email" button, and notices for environment/platform-managed configuration.
|
|
1252
|
+
- **cms-core**: new `__email_config__` store with API keys and SMTP passwords encrypted at rest (`CMS_ENCRYPTION_KEY`), `GET/PUT /email/settings` + `POST /email/settings/test` admin routes (test sends only to the signed-in admin's own address, rate-limited), and a DB-aware `resolveEmailSenderAsync()` used by invites, password resets, and form notifications. Resolution order: platform adapter → admin-configured settings → environment variables.
|
|
1253
|
+
- **plugin-email**: registers a provider factory with cms-core so admin-configured settings build real Resend/SMTP.com/SES/SMTP senders without env vars.
|
|
1254
|
+
|
|
1255
|
+
## 0.61.0
|
|
1256
|
+
|
|
1257
|
+
### Minor Changes
|
|
1258
|
+
|
|
1259
|
+
- 0c3a6eb: Posts UX improvements from external-consumer feedback:
|
|
1260
|
+
- New `admin.excludeFromPostsAggregate: true` collection flag keeps a post-shaped collection out of the admin's aggregate Posts area (cross-collection list, type tabs, New Post picker) while leaving its own admin list and API routes fully accessible. Enforced server-side in `GET /posts` and mirrored in the admin client.
|
|
1261
|
+
- `blogPostCollection()` now satisfies the admin Post Editor's full field contract out of the box: it gains `sections` (json), `category`, and `publishDate` (replacing `publishedDate` — update seeds/queries that referenced the old key), and `body` is no longer hard-required so section-driven posts can publish.
|
|
1262
|
+
- `create-actuate-cms` scaffolds now define `posts: blogPostCollection()` instead of an inline collection, so new projects always start with an editor-compatible posts schema.
|
|
1263
|
+
|
|
1264
|
+
## 0.60.0
|
|
1265
|
+
|
|
1266
|
+
### Minor Changes
|
|
1267
|
+
|
|
1268
|
+
- 854152d: Developer experience improvements from external-consumer feedback:
|
|
1269
|
+
- Every published package now ships its `CHANGELOG.md` in the npm tarball, and packages that were missing a `README.md` (cli, cms-admin, cms-core, import, platform-vercel, all plugins, sections-react) now have one — so release notes and usage docs are visible on npmjs.com and in `node_modules`.
|
|
1270
|
+
- New client-safe form validation entry point: `@actuate-media/cms-core/forms/client` exports `validateFormFieldClient()`, `validateFormValuesClient()`, visibility/conditional helpers, and the exact `FORM_EMAIL_RE` / `FORM_PHONE_RE` regexes the server enforces — zero Node dependencies, so public-site forms can mirror server rules without reimplementing them. The server validator now imports the same primitives, so client and server can never drift.
|
|
1271
|
+
|
|
1272
|
+
### Patch Changes
|
|
1273
|
+
|
|
1274
|
+
- bf5f857: `GET`/`PUT /post-templates/:type` now preserve sections whose type has no registered definition instead of silently dropping them. A template saved from the admin (which round-trips unmanaged sections) no longer loses seed/code-owned section types server-side; the derived `unmanaged` marker is stripped before persisting.
|
|
1275
|
+
- Updated dependencies [854152d]
|
|
1276
|
+
- @actuate-media/realtime@0.1.1
|
|
1277
|
+
|
|
1278
|
+
## 0.59.1
|
|
1279
|
+
|
|
1280
|
+
### Patch Changes
|
|
1281
|
+
|
|
1282
|
+
- 15b3f45: Fix silent media upload data loss: `/media/upload` now returns a 500 (and creates no media record) when blob/adapter storage fails, instead of returning 201 with a dead `/api/cms/media/file/…` URL that nothing serves. Storage errors are logged with their cause (typically a missing `BLOB_READ_WRITE_TOKEN`). `/media/:id/optimize` likewise fails without touching the record when the optimized variant cannot be stored. Successful uploads now include `url` in the response so the admin can render previews immediately.
|
|
1283
|
+
|
|
1284
|
+
## 0.59.0
|
|
1285
|
+
|
|
1286
|
+
### Minor Changes
|
|
1287
|
+
|
|
1288
|
+
- f962e2d: New `defineSectionTypes()` authoring helper for custom section types. It is to `sections.types` what `defineConfig` is to the config file: a typed identity function with eager, fail-fast validation that throws one aggregated error naming every problem (blank/duplicate ids, built-in id collisions, unknown field types, select fields without options, repeaters without a sub-schema — with recursive sub-field checks). This replaces the silent-skip behavior consumers previously hit at runtime registration, where a malformed type simply never appeared in the Add Section gallery. Also exports `validateSectionTypeInputs()` (non-throwing issue list for tooling) and `SECTION_FIELD_TYPES` (runtime mirror of the field-type union). All three are available from both the package root (beside `defineConfig`) and the `/page-sections` subpath, along with the `CustomSectionTypeInput` and `SectionsConfig` types now re-exported from the root.
|
|
1289
|
+
|
|
1290
|
+
### Patch Changes
|
|
1291
|
+
|
|
1292
|
+
- 73a117a: Add API-handler contract tests covering the full REST surface (265 routes). A checked-in contract table pins every registered route (in first-match-wins registration order) to the exact status an unauthenticated request must receive, with a documented allowlist for the 19 deliberately-anonymous endpoints. The suite fails loudly when a route is dropped or renamed, when a protected route accidentally becomes public, when a handler 500s on anonymous traffic, or when an error response stops emitting the `{ error: string }` JSON envelope. No runtime changes.
|
|
1293
|
+
|
|
1294
|
+
## 0.58.0
|
|
1295
|
+
|
|
1296
|
+
### Minor Changes
|
|
1297
|
+
|
|
1298
|
+
- 363bb3e: Strongly-typed database surface (M4): replaced the pervasive `db: any` / `type Db = any` pattern with a structural `ActuateDB` client type — one typed delegate per shipped model plus the `$`-method surface — backed by generated row/enum types for all 34 models and 17 enums (`scripts/generate-db-types.mjs`, output committed at `src/db/model-types.ts`).
|
|
1299
|
+
- `guardedDb()`, `getDB<ActuateDB>()`, action transactions, auth/setup/forms/webhooks/cron/scheduling/SEO services now get delegate-name, method-name, and row-shape checking.
|
|
1300
|
+
- New public type exports: `ActuateDB`, `ModelDelegate`, `QueryArgs`.
|
|
1301
|
+
- Drift is caught two ways in CI: the e2e package compiles the real PrismaClient from the canonical schema and asserts it satisfies `ActuateDB` (`db-type-guard.ts`), and a freshness test verifies every schema model/enum appears in the generated types.
|
|
1302
|
+
- Typed rows surfaced two latent unguarded relation accesses (password-reset token user, webhook retry endpoint) which now fail closed.
|
|
1303
|
+
|
|
1304
|
+
Query arguments deliberately remain loose (`QueryArgs = any`) — reproducing Prisma's generic argument inference by hand isn't maintainable, and anything narrower breaks real-client assignability.
|
|
1305
|
+
|
|
1306
|
+
## 0.57.1
|
|
1307
|
+
|
|
1308
|
+
### Patch Changes
|
|
1309
|
+
|
|
1310
|
+
- a5f8bc0: Internal refactor: split the 14,381-line `api/handlers.ts` monolith into 25 domain route modules under `api/routes/` (auth, documents, media, forms, SEO, users, webhooks, etc.), plus shared `api/route-helpers.ts` and `api/guarded-db.ts` modules. `registerCMSRoutes` is now a thin aggregator that preserves the original route registration order. No behavior or public API changes — `registerCMSRoutes`, `handleActuateAPI`, and all route paths are unchanged.
|
|
1311
|
+
|
|
1312
|
+
## 0.57.0
|
|
1313
|
+
|
|
1314
|
+
### Minor Changes
|
|
1315
|
+
|
|
1316
|
+
- 8e73cf5: Custom section types are now first-class. `actuate.config.ts` accepts `sections.types` as the canonical key (the previously-unreleased `sections.customTypes` remains as a deprecated back-compat alias, resolved via `resolveCustomSectionTypes`). The unscoped `GET /page-sections/types` endpoint now returns custom types alongside built-ins so admins can hydrate their registry from the API.
|
|
1317
|
+
|
|
1318
|
+
Add an explicit `onUnknown` strategy to `coerceSections` (`'drop' | 'preserve' | 'error'`) plus `coerceSectionsWithReport` and `UnknownSectionTypeError`, so callers can warn or block on unknown section types instead of silently dropping them. The legacy `preserveUnknown` flag still maps to `'preserve'`.
|
|
1319
|
+
|
|
1320
|
+
Add a generic `repeater` field type with a nested `fields` sub-schema and `minRows`/`maxRows`/`rowLabel` constraints; `stats` and `gallery` are now repeater presets. New helpers: `resolveRepeaterField`, `isRepeaterField`, `blankRepeaterRow`. Repeater rows (including required sub-fields and image alt text) are validated with row-scoped error/warning paths.
|
|
1321
|
+
|
|
1322
|
+
Add a `pagePathField()` field preset for an explicit page URL path.
|
|
1323
|
+
|
|
1324
|
+
## 0.56.0
|
|
1325
|
+
|
|
1326
|
+
### Minor Changes
|
|
1327
|
+
|
|
1328
|
+
- 99565a1: Add config-declared custom section types and preserve externally-managed sections (Opal feedback).
|
|
1329
|
+
- New `sections.customTypes` on `ActuateCMSConfig` — declare custom section types (id, name, scope, fields, optional presentation/defaults) that are recognized, listed in discovery, and validated like built-ins. Registered at server boot in `handleActuateAPI`.
|
|
1330
|
+
- New registry API on `@actuate-media/cms-core/page-sections`: `registerSectionTypes`, `resetCustomSectionTypes`, `getAllSectionTypes`, plus `CustomSectionTypeInput` / `SectionsConfig` types. `getSectionType` / `getSectionTypesForScope` / `isKnownSectionType` / `sectionTypeLabel` now merge built-ins with custom types (built-ins always win an id collision).
|
|
1331
|
+
- `coerceSections(raw, { preserveUnknown: true })` keeps entries whose type isn't registered, flagged `unmanaged`, with content/settings verbatim and order intact, instead of dropping them. The page-section API (REST + MCP) now loads/persists with preservation so add/reorder/update/remove never drop seed/code-owned sections, and the derived `unmanaged` marker is never written back to storage.
|
|
1332
|
+
- `PageSection.sectionType` is now a `string` (was the built-in `SectionTypeId` union) and `PageSection` gains an optional `unmanaged` flag. `createSection` / `buildSection` accept any registered type id.
|
|
1333
|
+
|
|
1334
|
+
## 0.55.0
|
|
1335
|
+
|
|
1336
|
+
### Minor Changes
|
|
1337
|
+
|
|
1338
|
+
- 74662c0: Standardize `MediaUsage.fieldName` to `fieldPath` and harden Blob diagnostics.
|
|
1339
|
+
- Renames the `MediaUsage.fieldName` column to `fieldPath` to match the API layer, server actions, and `actuate doctor` expectations, with a new additive migration `0002_media_usage_field_path` (run `prisma migrate deploy`). Note: instances that already hand-renamed this column should mark 0002 as applied.
|
|
1340
|
+
- Runtime env diagnostics now escalate a partial Vercel Blob link (a store is connected via `BLOB_STORE_ID`/webhook key but `BLOB_READ_WRITE_TOKEN` is missing) from a warning to an error, since uploads fail in that state.
|
|
1341
|
+
- `createCMSMiddleware` now accepts `ActuateCMSConfig` directly (type-only), so consumers can pass their config without an `as unknown as` cast.
|
|
1342
|
+
- The optional MySQL/SQLite database adapters now mark their lazy `import()`s as bundler-ignored (`webpackIgnore`/`turbopackIgnore`), eliminating spurious "Module not found" build warnings for drivers a Postgres project never installs.
|
|
1343
|
+
|
|
1344
|
+
## 0.54.0
|
|
1345
|
+
|
|
1346
|
+
### Minor Changes
|
|
1347
|
+
|
|
1348
|
+
- 29f32ba: Deepen `GET /api/cms/collections` field introspection (roadmap WS-B4). The discovery endpoint now recursively summarizes `array` element subfields (with `minRows`/`maxRows`) and `blocks` variant subfields (keyed by block `slug`, with labels), in addition to the existing `relationTo`/`hasMany`/`options` hints — so an AI agent can plan a deep create/update without guessing nested shapes. The summary stays an allow-listed projection (no validators/hooks/access functions leak), is depth-capped defensively, and is exposed as a reusable `summarizeCollectionFields()` export (`FieldSummary`/`BlockSummary` types).
|
|
1349
|
+
|
|
1350
|
+
## 0.53.0
|
|
1351
|
+
|
|
1352
|
+
### Minor Changes
|
|
1353
|
+
|
|
1354
|
+
- aa4bd1a: Unify the page content model at write time (ADR 0002). Visual Page-Builder pages now compile their `_layout` tree to the canonical `data.sections[]` on every save, so every reader (SDK, MCP, search, content graph, `/resolve`) sees one contract instead of the lazy read-time bridge.
|
|
1355
|
+
- **cms-core**: `createDocument`/`updateDocument` derive `sections` from a `layout` envelope; the `/page-sections/*` write routes refuse a builder-owned page (one with `_layout`) with HTTP 409 to prevent silently-overwritten edits; the `/resolve` bridge is now a legacy fallback for un-migrated pages. New `migratePageBuilderSections()` export backfills legacy pages (idempotent).
|
|
1356
|
+
- **cms-admin**: the flat Page Editor opens a builder-owned page read-only with an "Open in Page Builder" action; `EditorPage` gains a `builderOwned` flag.
|
|
1357
|
+
- **cli**: new `actuate migrate:sections` command (with `--dry-run` / `--batch-size`) to backfill canonical sections for legacy page-builder documents.
|
|
1358
|
+
|
|
1359
|
+
## 0.52.0
|
|
1360
|
+
|
|
1361
|
+
### Minor Changes
|
|
1362
|
+
|
|
1363
|
+
- 01eef99: Security & reliability hardening from the production-readiness audit.
|
|
1364
|
+
- **Write-time HTML sanitization is now recursive (stored-XSS fix).** Previously only top-level `richText` fields were sanitized on write. HTML nested in `array`/`blocks` fields, the canonical `sections[]` rich-text slots, and the page-builder `_layout` tree was stored raw — a stored-XSS path reachable by any authenticated writer because the public renderer trusts stored HTML. All reachable author-HTML sinks are now sanitized before storage via the new `sanitize-document` module.
|
|
1365
|
+
- **AI analysis routes are rate-limited and input-capped.** `/ai/audit-document` and `/ai/suggest-internal-links` now enforce a per-user limit (60/hr) and a 50k-character input cap, and emit `ai_audit_document` / `ai_suggest_internal_links` audit-log events.
|
|
1366
|
+
- **`/media/upload` gains a dedicated strict limit** (20/hr/user) in addition to the global IP limit.
|
|
1367
|
+
- **Vision image fetches go through `safeFetch`** (SSRF-safe: host re-validation, DNS-rebinding defense, no redirects) with an 8 MB cap; `/ai/media-metadata` now passes real image bytes to the vision model instead of a bare URL.
|
|
1368
|
+
- **`/redirects/record-404` is now reachable from the public site.** It is CSRF-exempt (it only increments an unauthenticated 404 counter) and bounds its stored `path`/`referrer` to 2048 chars.
|
|
1369
|
+
- **`/public/globals/:slug` and `/public/navigations/:slug` no longer serve `ARCHIVED` documents.** They remain unauthenticated site chrome (not PUBLISHED-gated), but an explicitly retired record is excluded.
|
|
1370
|
+
- A runtime warning is logged when a collection's `fields` is defined as an array instead of an object map (silent data loss otherwise).
|
|
1371
|
+
|
|
1372
|
+
## 0.51.2
|
|
1373
|
+
|
|
1374
|
+
### Patch Changes
|
|
1375
|
+
|
|
1376
|
+
- aeee3a4: Docs: correct the migration-baseline upgrade note shipped in 0.51.1.
|
|
1377
|
+
|
|
1378
|
+
The previous note told deployments "previously migrated with `prisma migrate deploy`" to run `prisma migrate resolve --applied 0001_init`. That guidance is wrong for exactly that audience: such a database already has a `0001_init` row in `_prisma_migrations`, so `resolve --applied 0001_init` errors with `P3008: migration is already recorded as applied`. Those deployments need **no action** — `migrate deploy` applies only _pending_ migrations and does not re-validate the squashed baseline, so it is a no-op.
|
|
1379
|
+
|
|
1380
|
+
`prisma migrate resolve --applied 0001_init` is only needed to baseline a database that already has the `actuate_*` tables but has **no** migration history (e.g. one provisioned via `db push`, or restored from a dump) before adopting `migrate deploy`. Run `prisma migrate status` first if unsure.
|
|
1381
|
+
|
|
1382
|
+
## 0.51.1
|
|
1383
|
+
|
|
1384
|
+
### Patch Changes
|
|
1385
|
+
|
|
1386
|
+
- f88a406: Fix document-create/update hijacking a collection's own `status` field, and reconcile the migration history into a clean baseline.
|
|
1387
|
+
- **Forms (and any collection with a `status` field) can be created again.** `createDocument`/`updateDocument` treated a `data.status` value as the document lifecycle status. Collections such as Forms store their own `status` (`draft`/`active`/`archived`) inside `data`, so creating a form threw `Cannot create a document with status "draft"` and activating one would have written an invalid enum value. We now only hoist `status` onto the document column when it is an actual `DocumentStatus`; any other value stays in `data`. Regression tests added in `actions/document-crud.test.ts` and exercised end-to-end in the new `@actuate-media/e2e` harness.
|
|
1388
|
+
- **Migration history baseline.** The committed migrations did not apply from an empty database (`0003` indexed `actuate_documents.title` before any migration created it) and were ~223 columns / several tables behind `schema.prisma`, so `prisma migrate deploy` on a fresh DB crashed. Migrations `0001`–`0012` are squashed into a single `0001_init` baseline generated from the current schema (plus the raw full-text-search GIN index). A PGlite-based drift guard now asserts the migrations apply from empty and reproduce the schema.
|
|
1389
|
+
|
|
1390
|
+
**Action required for existing deployments** that were previously migrated with `prisma migrate deploy`: run `prisma migrate resolve --applied 0001_init` once so Prisma records the baseline as already applied instead of attempting to re-create existing tables. Fresh installs need no action.
|
|
1391
|
+
|
|
1392
|
+
- **Config footgun guard.** `setActuateConfig` now logs a one-time warning when a collection declares `fields` as an array instead of the object-map shape cms-core expects (an array silently drops every custom field on write).
|
|
1393
|
+
|
|
1394
|
+
## 0.51.0
|
|
1395
|
+
|
|
1396
|
+
### Minor Changes
|
|
1397
|
+
|
|
1398
|
+
- a413616: Add MCP relationship-resolver tools (WS-C C2) so agents stop guessing document IDs.
|
|
1399
|
+
- **cms-core (additive, backward-compatible):**
|
|
1400
|
+
- `listDocuments` / `GET /collections/:slug` now accept an exact `slug` filter. Because `slug` is `@@unique([collection, slug])`, this is the canonical single-query slug→id lookup.
|
|
1401
|
+
- The `GET /collections` discovery endpoint now surfaces `relationTo` (for relationship fields), `hasMany`, and select `options` in each field summary, so an agent can learn where a relationship field points. (Overlaps the WS-B4 schema-surfacing work.)
|
|
1402
|
+
- **mcp-server:** three new tools — `get_relationship_fields` (list relationship/media/select fields + their `relationTo`), `resolve_relationship` (exact slug → `{ id, slug, title, status }`), and `list_relationship_targets` (browse candidate documents when the slug is unknown). README updated with a "Resolving relationships" section.
|
|
1403
|
+
|
|
1404
|
+
- 204da7a: WS-E (E3): media resolution + responsive images in the public render path.
|
|
1405
|
+
- **sections-react:** new pluggable `renderImage` `RenderOptions` hook (with
|
|
1406
|
+
exported `RenderImage`/`RenderImageProps` types and a `defaultRenderImage`).
|
|
1407
|
+
The default emits a lazy, `decoding="async"` `<img>` and forwards intrinsic
|
|
1408
|
+
`width`/`height` when known; a Next.js consumer can pass a `next/image`-backed
|
|
1409
|
+
renderer for responsive `srcset` + optimization. The `feature`, `gallery`, and
|
|
1410
|
+
post-header images now route through it (`<PostHeader>` gains an optional
|
|
1411
|
+
`options` prop). The package stays React-only — no `next` dependency.
|
|
1412
|
+
- **cms-core:** the `/resolve` endpoint now enriches section images with
|
|
1413
|
+
intrinsic `width`/`height` and library `altText`, matched from `actuate_media`
|
|
1414
|
+
by stored URL (`storageKey`/`publicUrl`). Additive and best-effort — the
|
|
1415
|
+
stored document is never mutated and a lookup failure never breaks resolve.
|
|
1416
|
+
`feature` sections gain `imageWidth`/`imageHeight` (+ backfilled `imageAlt`);
|
|
1417
|
+
`gallery` rows gain `width`/`height` (+ backfilled `alt`).
|
|
1418
|
+
- **create-actuate-cms:** the generated catch-all page renders CMS images via a
|
|
1419
|
+
`next/image`-backed `renderImage` (falling back to a fluid `<img>` when the
|
|
1420
|
+
CMS doesn't know dimensions), and `next.config` now allow-lists the storage
|
|
1421
|
+
host under `images.remotePatterns` so optimization works out of the box.
|
|
1422
|
+
|
|
1423
|
+
- 2f18c56: WS-E (E2): public form rendering — a built site can now render and submit a CMS form end to end.
|
|
1424
|
+
- **sections-react:** new `<ActuateForm>` Client Component fetches the public form
|
|
1425
|
+
schema (`GET /api/cms/public/forms/:slug`), renders the fields (text/email/
|
|
1426
|
+
url/tel/number/date, textarea, select, checkbox, radio), and submits to the
|
|
1427
|
+
schema's `submitPath`. Spam protection is wired automatically: a hidden
|
|
1428
|
+
honeypot (keyed from `spam.honeypotFieldKey`), an `elapsedMs` "time to submit"
|
|
1429
|
+
timer, and an optional consumer-supplied `captchaToken`. Pre-fetch a schema via
|
|
1430
|
+
the `form` prop to skip the round-trip (SSR/streaming). Adds a `baseUrl`
|
|
1431
|
+
`RenderOptions` field (defaults to same-origin). Exposes pure, tested helpers
|
|
1432
|
+
(`coercePublicForm`, `buildSubmitBody`, `fieldControl`, `firstMissingRequired`).
|
|
1433
|
+
- **cms-core:** new canonical `form` section type. Set `content.formSlug` (plus
|
|
1434
|
+
optional heading/intro) on any page or post and the matching renderer embeds
|
|
1435
|
+
the form. The registry-drift guard now covers it (renderer ↔ type).
|
|
1436
|
+
- **cms-admin:** the form Embed panel now shows the real public endpoints
|
|
1437
|
+
(`/api/cms/public/forms/:slug` + `/submit`) and a copy-paste `<ActuateForm>`
|
|
1438
|
+
snippet, replacing the previously-advertised `/api/forms/*`, iframe, and
|
|
1439
|
+
`embed.js` routes that were never implemented. Adds a `mail` section icon.
|
|
1440
|
+
|
|
1441
|
+
- e2e0bd7: WS-E (E4): public navigation endpoint + navigation rendering in the scaffold.
|
|
1442
|
+
- **cms-core:** new unauthenticated `GET /api/cms/public/navigations/:slug` that
|
|
1443
|
+
returns a clean `{ name, slug, location, items }` tree. Like globals it is site
|
|
1444
|
+
chrome — not status-gated — and safe when the navigation plugin isn't installed
|
|
1445
|
+
(no match → 404). A shared, pure `coercePublicNavItems` helper sanitizes the
|
|
1446
|
+
open `data.items` JSON: drops unlabeled nodes, strips internal `id`s, caps
|
|
1447
|
+
recursion depth, and only passes through `_blank`/`_self` targets. Both
|
|
1448
|
+
`createSiteClient` and `createServerSiteClient` gain `getNavigation(slug)`
|
|
1449
|
+
returning `PublicNavigation | null`, and `PublicNavigation` / `PublicNavItem`
|
|
1450
|
+
are now exported types.
|
|
1451
|
+
- **sections-react:** new presentational `<SiteNav>` — an accessible `<nav>`
|
|
1452
|
+
landmark whose submenus use native `<details>`/`<summary>` disclosure
|
|
1453
|
+
(keyboard-operable, no client JS). New-tab links get
|
|
1454
|
+
`rel="noopener noreferrer"`; an empty menu renders nothing. Exports `SiteNav`
|
|
1455
|
+
plus `SiteNavProps`/`SiteNavItem`.
|
|
1456
|
+
- **create-actuate-cms:** `lib/cms.ts` re-exports `getNavigation`, and the
|
|
1457
|
+
generated site layout fetches the `main` menu and renders a header with the
|
|
1458
|
+
site name + `<SiteNav>`, degrading gracefully to just the name when no menu
|
|
1459
|
+
exists yet.
|
|
1460
|
+
|
|
1461
|
+
## 0.50.0
|
|
1462
|
+
|
|
1463
|
+
### Minor Changes
|
|
1464
|
+
|
|
1465
|
+
- d7a144c: Add a page-builder → canonical sections bridge so existing `_layout` documents aren't stranded (ADR 0001, WS-A5).
|
|
1466
|
+
- New `layoutTreeToSections()` export on the `@actuate-media/cms-core/page-sections` subpath converts a page-builder `_layout` `BlockNode` tree into a best-effort canonical `PageSection[]`: it flattens the page → section → container/row/column → block nesting in document order, maps each block's `blockType` to the closest canonical section type, degrades unknown/complex blocks to a `text` section that surfaces their content, and propagates the enclosing builder section's anchor (`htmlId`) and hidden visibility. It is pure, DB-free, and never throws (`savedSectionRef` nodes are skipped — they require a separate lookup).
|
|
1467
|
+
- The `/resolve` endpoint now applies the bridge at read time: when a page/post has a `_layout` tree but no flat `sections`, the resolved response is enriched with derived sections so the content renders through `@actuate-media/sections-react`'s `<Sections>`. This is non-destructive — the stored document is never mutated, and a document's own `sections` always take precedence.
|
|
1468
|
+
|
|
1469
|
+
## 0.49.0
|
|
1470
|
+
|
|
1471
|
+
### Minor Changes
|
|
1472
|
+
|
|
1473
|
+
- 9ba332c: Converge the scaffolder onto the canonical page-content contract (ADR 0001, WS-A4).
|
|
1474
|
+
- **create-actuate-cms**: New projects now use the canonical `sections` (JSON `PageSection[]`) field for both `pages` and `posts`, and the generated public catch-all renders `data.sections` with `<Sections>` from `@actuate-media/sections-react` — passing cms-core's allow-list `sanitizeHtml` so author HTML (e.g. the article-body section) is sanitized before the DOM, and rendering a `<PostHeader>` for post-type documents. The deprecated Model C path is retired: the `content[]`/`BLOCK_MAP` `BlockRenderer`, `HeroBlock`, `CTABlock`, `TextBlock`, `ImageTextBlock`, `SiteHeader`, and `SiteFooter` generators (and the `blocks`-gated `layout`/headers/footers config) are removed. Generated apps now depend on `@actuate-media/sections-react` and emit the matching Tailwind `@source` directive.
|
|
1475
|
+
- **@actuate-media/cms-core**: `ResolveResult` (the typed `createSiteClient().resolveDocument()` return) now exposes the optional `meta`, `metadata`, `jsonLd`, `jsonLdHtml`, `template`, `post`, and `preview` fields the `/resolve` endpoint already returns, plus the new `ResolvePostAuthor`, `ResolveRelatedPost`, `ResolvePostContext`, and `ResolvePreview` types — so consumers can render the full post experience (header, author, related posts, SEO metadata) without re-declaring the shape.
|
|
1476
|
+
|
|
1477
|
+
## 0.48.0
|
|
1478
|
+
|
|
1479
|
+
### Minor Changes
|
|
1480
|
+
|
|
1481
|
+
- ae1af8f: Authoring correctness fixes for the AI site-building workflow (roadmap WS-B/E quick wins).
|
|
1482
|
+
- **cms-core**: `createDocument` now honors a requested `status` (`DRAFT` or
|
|
1483
|
+
`PUBLISHED`) instead of always forcing `DRAFT`. Publishing on create is gated
|
|
1484
|
+
to EDITOR/ADMIN, stamps `publishedAt`, and emits the `document.published`
|
|
1485
|
+
webhook + sitemap notify. This fixes `POST /page-builder/create?publish=true`
|
|
1486
|
+
(and `status: 'PUBLISHED'`), which previously created a draft silently.
|
|
1487
|
+
Statuses other than `DRAFT`/`PUBLISHED` on create are rejected with guidance
|
|
1488
|
+
to schedule/archive via update.
|
|
1489
|
+
- **client**: `listMedia()` now returns the canonical
|
|
1490
|
+
`{ docs, total, page, pageSize, totalPages }` (`MediaList`) envelope. The
|
|
1491
|
+
server returns the asset array under `items`/`data` (never `docs`), so the
|
|
1492
|
+
previous typing made `listMedia().docs` always `undefined`. Adds exported
|
|
1493
|
+
`MediaAsset` and `MediaList` types and an optional `init` (signal/cache) arg.
|
|
1494
|
+
- **mcp-server**: the `list_media` tool now normalizes the same `items`→`docs`
|
|
1495
|
+
shape so its output matches `list_documents`.
|
|
1496
|
+
- **create-actuate-cms**: generated `TextBlock`/`ImageTextBlock` now sanitize
|
|
1497
|
+
author-supplied HTML with `sanitizeHtml` before `dangerouslySetInnerHTML`
|
|
1498
|
+
(was an XSS vector on hostile content).
|
|
1499
|
+
|
|
1500
|
+
## 0.47.0
|
|
1501
|
+
|
|
1502
|
+
### Minor Changes
|
|
1503
|
+
|
|
1504
|
+
- 7d11b8d: Production-ready Settings → Updates tab with real release intelligence.
|
|
1505
|
+
- **cms-core**: Fix a critical permission bug where `GET/PUT /updates/config` and
|
|
1506
|
+
`POST /updates/apply` rejected real admins (they compared `session.role` to the
|
|
1507
|
+
lowercase `'admin'` while roles are stored uppercase). They now use
|
|
1508
|
+
`requireAdminScope`, so OWNER/ADMIN sessions (and admin-scoped API keys) can
|
|
1509
|
+
manage updates. `GET /updates/check` no longer leaks the GitHub repo to
|
|
1510
|
+
non-admins and reports a `canManage` flag. Update checks now run through a new
|
|
1511
|
+
hybrid resolver `getUpdateStatus()` that queries the Actuate update server
|
|
1512
|
+
first (rich, real changelog/severity/migration/breaking-change data derived
|
|
1513
|
+
from the published CHANGELOG) and transparently falls back to a direct npm
|
|
1514
|
+
registry check. New exports: `getUpdateStatus`, `checkForUpdatesViaServer`, and
|
|
1515
|
+
the `ChangeType` / `ChangelogItem` / `PluginCompatibilityInfo` types; richer
|
|
1516
|
+
`VersionInfo`. `fetchChangelog()` now targets the real `/api/changelog`
|
|
1517
|
+
endpoint (overridable via `ACTUATE_UPDATE_SERVER_URL`).
|
|
1518
|
+
- **cms-admin**: Rebuild the Updates tab as a dedicated `UpdatesTab` + `useUpdates`
|
|
1519
|
+
hook (extracted from `Settings.tsx`). The GitHub integration now uses the shared
|
|
1520
|
+
`SettingsControlRow` + sticky `SettingsSaveBar` dirty-state pattern, and the tab
|
|
1521
|
+
surfaces real release notes (categorized changelog), a security-update badge,
|
|
1522
|
+
"migrations required" and breaking-change callouts, and plugin compatibility.
|
|
1523
|
+
Write actions are permission-aware (hidden for non-admins).
|
|
1524
|
+
- **cli**: `update-check` / `upgrade` now resolve the update-server base URL from
|
|
1525
|
+
`ACTUATE_UPDATE_SERVER_URL` (defaulting to the production host), so the
|
|
1526
|
+
`/api/versions` endpoint is configurable for self-hosted update servers.
|
|
1527
|
+
|
|
1528
|
+
## 0.46.0
|
|
1529
|
+
|
|
1530
|
+
### Minor Changes
|
|
1531
|
+
|
|
1532
|
+
- 4e6ead2: Add Brand Voice. Admins can define a reusable brand voice (tone, audience, guidelines, and
|
|
1533
|
+
sample copy) in Settings > AI. When enabled, the governed AI runtime injects it into the system
|
|
1534
|
+
prompt of content-generation features — co-author and SEO titles/descriptions — so every AI
|
|
1535
|
+
surface writes in one consistent, on-brand voice.
|
|
1536
|
+
- `cms-core`: new `BrandVoiceProfile` type on `AISettings`, a `brand-voice` module
|
|
1537
|
+
(`buildBrandVoiceSystemPrompt`, `applyBrandVoice`, `getActiveBrandVoice`, `normalizeBrandVoice`),
|
|
1538
|
+
a `useBrandVoice` option on `generateTextForFeature`, and `PUT /ai/settings` persistence
|
|
1539
|
+
(length-clamped, no secrets).
|
|
1540
|
+
- `cms-admin`: a Brand Voice card in the AI settings tab wired through `useAiSettings`.
|
|
1541
|
+
|
|
1542
|
+
## 0.45.0
|
|
1543
|
+
|
|
1544
|
+
### Minor Changes
|
|
1545
|
+
|
|
1546
|
+
- df17a3f: Unify AI execution behind a governed runtime. Provider adapters now support text
|
|
1547
|
+
generation (OpenAI/xAI/custom via the OpenAI-compatible chat API, Anthropic via
|
|
1548
|
+
the messages API), and a new `ai/runtime` resolves a feature to its connection +
|
|
1549
|
+
routed model, enforces the feature toggle and monthly token budget, resolves the
|
|
1550
|
+
API key from env or the encrypted store, and records real token usage for the
|
|
1551
|
+
usage meter. The `/ai/coauthor` endpoint now runs through this governed path
|
|
1552
|
+
first — using the provider and per-feature model configured in Settings > AI —
|
|
1553
|
+
and falls back to the legacy `@actuate-media/plugin-ai` global provider only when
|
|
1554
|
+
no governed provider is configured. Disabled features return 403 and budget
|
|
1555
|
+
overruns return 429.
|
|
1556
|
+
|
|
1557
|
+
New exports: `generateTextForFeature`, `resolveFeatureRuntime`,
|
|
1558
|
+
`isAiFeatureEnabled`, and `adapterGenerateText`.
|
|
1559
|
+
|
|
1560
|
+
- 11b4dfa: Route the SEO assistant endpoints through the governed AI runtime. `/ai/seo/generate-field`
|
|
1561
|
+
(titles, meta descriptions, focus keyword), `/ai/seo/explain-issue`, `/ai/seo/generate-schema`,
|
|
1562
|
+
and `/ai/seo/summarize` now generate via the provider + per-feature model configured in
|
|
1563
|
+
Settings > AI (gated by the `ai.features.seoMetaGeneration` toggle, enforcing the monthly token
|
|
1564
|
+
budget and recording real usage). They fall back to the legacy `@actuate-media/plugin-ai`
|
|
1565
|
+
provider only when no governed provider is configured. A disabled feature returns 403, a budget
|
|
1566
|
+
overrun 429, and a provider failure 502; when nothing is configured the response is 503 with a
|
|
1567
|
+
"Connect a provider in Settings > AI" message.
|
|
1568
|
+
|
|
1569
|
+
## 0.44.0
|
|
1570
|
+
|
|
1571
|
+
### Minor Changes
|
|
1572
|
+
|
|
1573
|
+
- fcede90: Settings > AI: per-feature model routing. Each AI feature can now run on a
|
|
1574
|
+
specific model (e.g. a cheap model for alt-text, a frontier model for content
|
|
1575
|
+
writing) or inherit the default. Adds `resolveFeatureModel` /
|
|
1576
|
+
`resolveAllFeatureModels` to cms-core as the authoritative feature→model
|
|
1577
|
+
resolver (capability-aware, with capability-typed and fallback defaults), and
|
|
1578
|
+
extends `PUT /ai/settings` to persist + validate per-feature `modelId`/
|
|
1579
|
+
`providerId` overrides. The admin AI Features card gains a per-feature model
|
|
1580
|
+
picker that filters to capability-compatible models and warns when a routed
|
|
1581
|
+
model is missing a required capability (e.g. a text-only model on a vision
|
|
1582
|
+
feature).
|
|
1583
|
+
|
|
1584
|
+
## 0.43.0
|
|
1585
|
+
|
|
1586
|
+
### Minor Changes
|
|
1587
|
+
|
|
1588
|
+
- 89eebe0: Add Settings > AI (Phases 1-2): DB-backed provider connections + dynamic model catalog.
|
|
1589
|
+
|
|
1590
|
+
**cms-core**
|
|
1591
|
+
- New `src/ai` module: provider connections (OpenAI, Anthropic, xAI, custom OpenAI-compatible), a normalized model catalog, feature flags, and budget settings, all stored in an `__ai_config__` config document (no Prisma migration, mirroring the SEO config-store).
|
|
1592
|
+
- Provider API keys are encrypted at rest with `encryptSecret` and never returned to the client — responses expose only a masked `apiKeyLast4` and a source flag. When a provider's env var (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `XAI_API_KEY`) is set, that key wins and is surfaced as env-managed/read-only.
|
|
1593
|
+
- SSRF-safe adapters: built-in providers use fixed hostnames; custom base URLs are validated and DNS-resolved via the existing webhook guards.
|
|
1594
|
+
- Dynamic catalog merges live `/v1/models` listings with a curated capability/pricing map, with a curated fallback when live listing is unavailable and `unavailable` marking for configured-but-missing models.
|
|
1595
|
+
- Monthly usage summary aggregated from existing audit-log token totals (real counts; 0 until per-run metering lands).
|
|
1596
|
+
- New ADMIN-gated, audited routes: `GET/PUT /ai/settings`, `POST/PUT/DELETE /ai/providers(/:id)`, `POST /ai/providers/:id/test`, `POST /ai/providers/:id/sync-models`, `GET /ai/models`, `GET /ai/usage/summary`.
|
|
1597
|
+
|
|
1598
|
+
**cms-admin**
|
|
1599
|
+
- Replaced the read-only AI status panel with a full `AISettingsTab`: AI Assistant card (default provider + model selector with capability badges and Refresh), provider connection management (add/test/disconnect, masked or env-managed key handling with confirm dialogs), global AI feature toggles, and a real monthly usage meter with a budget limit.
|
|
1600
|
+
- Feature enforcement across modules and per-run usage metering are deferred to later phases; toggles and budget persist now.
|
|
1601
|
+
|
|
1602
|
+
## 0.42.0
|
|
1603
|
+
|
|
1604
|
+
### Minor Changes
|
|
1605
|
+
|
|
1606
|
+
- 8dfeb94: Add a **Settings > SEO** tab for site-wide SEO defaults, backed by the shared SEO configuration service (no duplicate state with `/seo` or Settings > General).
|
|
1607
|
+
- **cms-core**: extend `SiteSEOConfig` with `titleTemplate`, `defaultMetaDescription`, `defaultSocialImageAssetId`, and a `verification` block; add title-template token validation/application, search-engine verification meta-tag rendering, and wire all three into the public page-meta renderer. New `notifySitemapOnPublish` service performs production-gated, SSRF-safe sitemap regeneration + Google/Bing pings, fired as a best-effort effect when public content is published, updated, unpublished, or deleted. Cache invalidation runs on every change while the timestamp write + outbound ping are debounced (default 60s) so bursts of publishes don't hammer the shared `__seo_config__` row or spam search engines. `PUT /seo/config` and `PUT /seo/crawl-settings` now validate new fields and write field-level audit logs (verification token values redacted).
|
|
1608
|
+
- **cms-admin**: new `SeoSettingsTab` (Meta Defaults with SERP/social preview, Search Engine Verification, Sitemap Defaults, full-workspace callout, Advanced disclosure) driven by a `useSeoSettings` hook that reads/writes the shared `/seo/config` and `/seo/crawl-settings` endpoints with dirty tracking, validation, permission-aware read-only states, and dangerous-change confirmation. `MediaPickerModal` gains an `onSelectItem` callback so the default social image stores its asset id.
|
|
1609
|
+
|
|
1610
|
+
## 0.41.0
|
|
1611
|
+
|
|
1612
|
+
### Minor Changes
|
|
1613
|
+
|
|
1614
|
+
- 7336b5f: Add content-ownership transfer to Settings > Users.
|
|
1615
|
+
|
|
1616
|
+
cms-core: new `transferContentOwnership` / `countOwnedContent` helpers reassign
|
|
1617
|
+
a member's authored documents, uploaded media, and created templates to another
|
|
1618
|
+
active user (immutable version/audit history is never touched). New admin-only,
|
|
1619
|
+
audited `POST /users/:id/transfer-content` endpoint validates the recipient
|
|
1620
|
+
(must exist and be active) and applies the same Owner/last-Owner guards as the
|
|
1621
|
+
other per-user actions. `contentOwnedCount` in the user detail now reflects all
|
|
1622
|
+
three content types, not just documents.
|
|
1623
|
+
|
|
1624
|
+
cms-admin: the user detail drawer Overview now offers "Transfer content" with an
|
|
1625
|
+
active-member picker before revoking access. Auxiliary dialogs (transfer,
|
|
1626
|
+
confirm, reauth) now render inside the Drawer's Radix focus scope so they are
|
|
1627
|
+
keyboard-reachable and no longer marked `aria-hidden` behind the drawer.
|
|
1628
|
+
|
|
1629
|
+
## 0.40.0
|
|
1630
|
+
|
|
1631
|
+
### Minor Changes
|
|
1632
|
+
|
|
1633
|
+
- 951963a: Build the Settings > Users access-control system: real invites, roles, seats, and access review.
|
|
1634
|
+
|
|
1635
|
+
**@actuate-media/cms-core**
|
|
1636
|
+
- Schema: add `OWNER` and `VIEWER` to the `UserRole` enum, a new `InviteStatus` enum, a new `Invite` model (`actuate_invites`), and additive user-lifecycle columns (`suspendedAt`, `lockedAt`, `forcePasswordReset`, `mfaRequired`, `failedLoginCount`, `lastActiveAt`, `invitedById`, `acceptedInviteAt`). Two hand-authored migrations (`0011_user_roles_owner_viewer`, `0012_invites_and_user_lifecycle`) — enum values are added in their own migration so Postgres can use them as defaults afterward. All user columns are nullable/defaulted so existing rows migrate cleanly.
|
|
1637
|
+
- Add a `roles` module (also exported as the `@actuate-media/cms-core/roles` subpath): display mapping (`OWNER/ADMIN/EDITOR/VIEWER` plus legacy `AUTHOR→Editor`, `CLIENT→Viewer`), assignable roles, privilege ranking, and per-role permission derivation.
|
|
1638
|
+
- Add an `Invite` lifecycle service (`createInvite`, `listInvites`, `resendInvite`, `cancelInvite`, `updateInviteRole`, `extendInvite`, `expireStaleInvites`, `markInviteAcceptedForEmail`). Invite tokens are hashed at rest, single-use, and expire (7/14/30 days). Completing the password-reset flow for an invited email now marks the invite `ACCEPTED` atomically.
|
|
1639
|
+
- Add a `team` module (`listTeamMembers`, `getUserStats`, `computeAccessReview`) that derives seat usage and access-review recommendations (inactive 90d+, external domain, admin/owner without MFA, stale invite) from real user/security signals.
|
|
1640
|
+
- New admin endpoints: enriched `GET /users` (search/role/status filters), `GET /users/stats`, `PATCH /users/:id/role`, hardened `DELETE /users/:id` (revoke = suspend, last-Owner protection), `GET /invites` + `POST /invites/:id/resend|cancel` + `PATCH /invites/:id`, and `GET /access-review` + `POST /access-review/:id/dismiss`. Role assignment/removal of Owner is Owner-only, self-escalation is blocked, role downgrades revoke sessions, and seat limits are enforced. All sensitive actions are audit-logged.
|
|
1641
|
+
|
|
1642
|
+
**@actuate-media/cms-admin**
|
|
1643
|
+
- Rebuild the Settings > Users tab to match the design: dynamic summary cards (members, pending invites, admins, seats used), an actionable access-review banner, a combined team table (active members + pending invites) with role-filter pills and search, per-row role dropdowns and revoke/resend/cancel actions, and an invite modal with role radio cards and advanced options (custom message, expiry). Includes loading, empty, filtered-empty, and error states; destructive and Owner-assignment actions require confirmation.
|
|
1644
|
+
|
|
1645
|
+
**create-actuate-cms**
|
|
1646
|
+
- Sync the scaffold's Prisma schema and migrations with the new Users-management schema so newly created projects start with the `Invite` model, new roles, and user-lifecycle columns.
|
|
1647
|
+
|
|
1648
|
+
- 951963a: Settings > Users phase 2: per-user detail, sessions, activity, and security actions.
|
|
1649
|
+
|
|
1650
|
+
**@actuate-media/cms-core**
|
|
1651
|
+
- Add a `user-admin` read-model module (`getUserDetail`, `listUserSessions`, `listUserActivity`, `getUserPermissionView`, `parseUserAgent`) that shapes a single user's overview, security posture (MFA / password / lock / SSO-only), sessions (with device parsing + "this device" marker), audit activity (events where the user is actor or target), and derived permissions.
|
|
1652
|
+
- New admin-only, audited per-user endpoints: `GET /users/:id`, `GET /users/:id/sessions`, `DELETE /users/:id/sessions/:sessionId`, `POST /users/:id/sessions/revoke-all`, `GET /users/:id/activity`, `GET /users/:id/permissions`, `POST /users/:id/password-reset`, `POST /users/:id/force-password-reset`, `POST /users/:id/mfa-reset`, `POST /users/:id/require-mfa`, `POST /users/:id/suspend`, `POST /users/:id/reactivate`, `POST /users/:id/lock`, `POST /users/:id/unlock`. Owner actions are Owner-only, self-actions and last-Owner removal are blocked, MFA reset and Admin/Owner password resets require password reauthentication, and session-affecting actions revoke sessions.
|
|
1653
|
+
- Login now rejects suspended (`suspendedAt`) and locked (`lockedAt`) accounts so an admin Suspend/Lock takes effect on the next sign-in attempt — distinct from full deactivation (`isActive=false`), which preserves the account's content and history.
|
|
1654
|
+
|
|
1655
|
+
**@actuate-media/cms-admin**
|
|
1656
|
+
- Add the User detail drawer opened from a Team members row (and the row "View details" action): Overview, Security, Sessions, Activity, and Permissions tabs. Security actions (send/force password reset, reset/require MFA, lock/unlock, suspend/reactivate, revoke one/all sessions) are wired to the new endpoints with confirmation dialogs and an inline re-authentication prompt that retries with the `x-reauth-password` header when the server requires it. Includes loading, empty, and error states across all tabs.
|
|
1657
|
+
|
|
1658
|
+
## 0.39.0
|
|
1659
|
+
|
|
1660
|
+
### Minor Changes
|
|
1661
|
+
|
|
1662
|
+
- f043e18: Fix day-one auth dead-ends and wire up transactional email + user invites.
|
|
1663
|
+
|
|
1664
|
+
**@actuate-media/cms-core**
|
|
1665
|
+
- Add a transactional email sender registry (`setEmailSender`, `getRegisteredEmailSender`, `resolveEmailSender`). Password-reset and form-notification email now resolve a sender via this registry, so apps that configure email purely through `emailPlugin()` actually send (previously only `config.platform.email` was consulted, so reset/notification mail silently never sent).
|
|
1666
|
+
- Password reset links are now built from the configured admin path instead of a hardcoded `/admin`, so resets work on apps mounted elsewhere.
|
|
1667
|
+
- Add an admin-only `POST /users/invite` endpoint plus `createUserInvite` / `normalizeInviteRole` / `VALID_INVITE_ROLES`. Invites provision the user with an unusable password and a single-use, 7-day token, then email a "set your password" link (reusing the existing `/reset-password` page). When no email sender is configured the response returns the link so an admin can share it manually. Rate-limited per admin.
|
|
1668
|
+
|
|
1669
|
+
**@actuate-media/cms-admin**
|
|
1670
|
+
- `AdminRoot` gains an `onLogout` prop. The account-menu "Logout" item now revokes the server session (`POST /auth/logout`), clears local session state, and returns to the login screen — previously it navigated to a non-existent `/logout` route (404) and left the session valid.
|
|
1671
|
+
- The "Invite User" dialog is now a real flow backed by `POST /users/invite` (loading state, error handling, valid roles, optional name) instead of a fake success toast.
|
|
1672
|
+
|
|
1673
|
+
**create-actuate-cms**
|
|
1674
|
+
- The scaffolded admin page now wires `onVerifyTotp` (so MFA users can complete the second-factor step) and `onLogout`, matching the dev app.
|
|
1675
|
+
|
|
1676
|
+
**@actuate-media/plugin-email**
|
|
1677
|
+
- `emailPlugin()` now registers its resolved provider with cms-core's email sender registry, so configuring email via the plugin is sufficient for password-reset, invite, and form-notification email.
|
|
1678
|
+
|
|
1679
|
+
- 05537a8: Replace the decorative AI settings tab with an honest, read-only status panel.
|
|
1680
|
+
|
|
1681
|
+
The previous AI tab shipped a fake API-key field (the key was never saved), a no-op "Verify" button, a misleading "encrypted at rest" claim, hardcoded zero usage metrics, and nine feature toggles that were read by nothing. None of it configured AI — that happens server-side via `@actuate-media/plugin-ai` and environment variables.
|
|
1682
|
+
- `cms-core`: new admin-only `GET /ai/status` endpoint reporting `{ installed, configured, provider }`. It detects whether the AI plugin is installed and a provider is configured, and hints at which provider env var is present — never exposing key values.
|
|
1683
|
+
- `cms-admin`: the AI tab now renders a truthful status panel (active / installed-but-unconfigured / not-installed) plus setup guidance, and no longer pretends to store credentials.
|
|
1684
|
+
|
|
1685
|
+
- 456e5f2: Fix client onboarding so a freshly scaffolded project actually has a working database.
|
|
1686
|
+
|
|
1687
|
+
**create-actuate-cms**
|
|
1688
|
+
- Scaffolded projects now ship the canonical Prisma schema (all `actuate_*` models with `@@map`) and the full migration history, synced from `@actuate-media/cms-core` via `scripts/sync-prisma-assets.ts` and guarded by a drift test. Previously the scaffolder hand-maintained a divergent schema (14 of 33 models, no `@@map`) and shipped no migrations, so `prisma migrate deploy` in the build created no tables and core features (full-text search, i18n, API metrics, API keys, webhooks, notifications, etc.) were broken.
|
|
1689
|
+
- The generated `prisma.config.ts` now warns when migrations would run against the pooled `DATABASE_URL` because `DIRECT_DATABASE_URL` is unset.
|
|
1690
|
+
- The generated admin page now shows an actionable "run migrations" / backend-unreachable message instead of silently dropping to the login screen when the database has no tables or `/setup/status` fails.
|
|
1691
|
+
- Next-steps + deployment docs now use `prisma migrate deploy` (applies the bundled migrations).
|
|
1692
|
+
|
|
1693
|
+
**@actuate-media/cms-core**
|
|
1694
|
+
- `checkSetupRequired` now reports `migrationsRequired: true` (new `SetupStatus` field) when the database is reachable but the tables don't exist yet, and `POST /setup/create-admin` returns an actionable 503 in that case. New exported helper `isSchemaMissingError`.
|
|
1695
|
+
- The admin middleware now returns an actionable HTML page (with remediation steps) when `CMS_SECRET` is missing, instead of a bare `500`.
|
|
1696
|
+
|
|
1697
|
+
- 90e80b6: Fix `plugin-email` test timeouts in CI by importing the email-sender registry from a lightweight subpath.
|
|
1698
|
+
|
|
1699
|
+
`emailPlugin()` previously imported `setEmailSender` from the full `@actuate-media/cms-core` barrel (handlers, middleware, the whole graph). Because the plugin's tests re-import the module on every `vi.resetModules()`, transforming that barrel repeatedly blew past the 5s test timeout on cold CI runners.
|
|
1700
|
+
- `cms-core`: adds a new `@actuate-media/cms-core/email-sender` subpath export exposing `setEmailSender` / `getRegisteredEmailSender` / `resolveEmailSender` (the registry only depends on the config runtime).
|
|
1701
|
+
- `plugin-email`: imports `setEmailSender` from that subpath instead of the barrel. No runtime barrel import remains.
|
|
1702
|
+
|
|
1703
|
+
## 0.38.0
|
|
1704
|
+
|
|
1705
|
+
### Minor Changes
|
|
1706
|
+
|
|
1707
|
+
- 008544f: Complete the MFA loop: account enrollment, login challenge, and backup-code recovery.
|
|
1708
|
+
|
|
1709
|
+
Previously the TOTP backend existed but had no user-facing plumbing — there was no
|
|
1710
|
+
way to enroll from the admin, the login screen couldn't complete a second-factor
|
|
1711
|
+
challenge, and backup codes were generated but never consumed anywhere (a lost
|
|
1712
|
+
authenticator meant permanent lockout). The "Require MFA" policy was therefore
|
|
1713
|
+
impossible to satisfy.
|
|
1714
|
+
|
|
1715
|
+
**cms-core**
|
|
1716
|
+
- `POST /auth/totp/login` now accepts a one-time backup code as well as a TOTP code.
|
|
1717
|
+
Matches are constant-time and single-use (the code is removed and remaining codes
|
|
1718
|
+
re-encrypted); `login_success` audit records whether a backup code was used.
|
|
1719
|
+
- New pure helpers `normalizeBackupCode` / `findBackupCodeMatch` in `auth/totp`.
|
|
1720
|
+
- `GET /auth/me` now returns `totpEnabled` so the admin can show enrollment state.
|
|
1721
|
+
|
|
1722
|
+
**cms-admin**
|
|
1723
|
+
- New `/profile` view (fixes the previously dead account-menu link) with an
|
|
1724
|
+
**Account security** card: password reauth → QR code + manual key + downloadable
|
|
1725
|
+
one-time backup codes → verify, plus a reauth-gated disable flow.
|
|
1726
|
+
- The login screen now renders a **two-factor challenge** step (authenticator code
|
|
1727
|
+
with a backup-code fallback). `AdminRoot`/`Login` gain an `onVerifyTotp` prop and
|
|
1728
|
+
`onLogin` may now return `{ requiresTOTP, mfaPendingToken }`.
|
|
1729
|
+
- The Security tab's "MFA not set up" warning now links to the enrollment flow.
|
|
1730
|
+
- Adds `qrcode.react` for QR rendering.
|
|
1731
|
+
|
|
1732
|
+
## 0.37.0
|
|
1733
|
+
|
|
1734
|
+
### Minor Changes
|
|
1735
|
+
|
|
1736
|
+
- 06940a1: Add the Settings → Security tab as a real Security Center.
|
|
1737
|
+
|
|
1738
|
+
**cms-core**
|
|
1739
|
+
- New `@actuate-media/cms-core/security-center` subpath export with pure, client-safe
|
|
1740
|
+
helpers: `computeSecurityStatus`, `parseSecuritySettings`, `validateSecuritySettings`,
|
|
1741
|
+
`validateIpRange`, `mfaEnableLockoutError`, `summarizeUsers`, `summarizeApiKeys`, and types.
|
|
1742
|
+
- New API routes: `GET /security` (computed posture + cross-module summaries +
|
|
1743
|
+
secret/encryption verdicts, never secret values), `PUT /security` (persists enforced
|
|
1744
|
+
MFA + session policy with server-side lockout-safe MFA enabling, audited as
|
|
1745
|
+
`security_settings_changed`), and `POST /security/sessions/revoke-all` (revokes the
|
|
1746
|
+
caller's other sessions, audited).
|
|
1747
|
+
- Concurrent-session enforcement now honors the stored `security.session.maxConcurrentSessions`
|
|
1748
|
+
policy (config/default precedence preserved when unset; `0` = unlimited).
|
|
1749
|
+
- Login responses include `mfaSetupRequired` when org-wide MFA is required but the account
|
|
1750
|
+
has not enrolled past its grace window.
|
|
1751
|
+
- `settings` global writes now audit nested `security` changes.
|
|
1752
|
+
|
|
1753
|
+
**cms-admin**
|
|
1754
|
+
- New Security tab with a computed Security Status card, Authentication & MFA, Session
|
|
1755
|
+
Policy (with revoke-other-sessions), Access & Request Protection, and Secrets/Encryption/Audit
|
|
1756
|
+
cards. Wired to the real `/security` endpoints with source badges, dangerous-change
|
|
1757
|
+
confirmations, lockout-safe MFA toggle, validation, and read-only states for non-admins.
|
|
1758
|
+
Replaces the previous decorative security toggles.
|
|
1759
|
+
|
|
1760
|
+
## 0.36.0
|
|
1761
|
+
|
|
1762
|
+
### Minor Changes
|
|
1763
|
+
|
|
1764
|
+
- 3667ac2: Add the Settings → Appearance tab: a production-ready brand + admin-theme system.
|
|
1765
|
+
|
|
1766
|
+
**cms-core**
|
|
1767
|
+
- New `@actuate-media/cms-core/appearance` subpath module with pure utilities:
|
|
1768
|
+
logo-shape classification, WCAG contrast checks, admin accent-token
|
|
1769
|
+
generation, and brand/favicon resolution (admin + public fallback chains).
|
|
1770
|
+
- `GET /public/brand` (unauthenticated) resolves public logos + favicon
|
|
1771
|
+
metadata for the public frontend; `GET /appearance` (authenticated) returns
|
|
1772
|
+
stored appearance/brand settings plus resolved admin/public/favicon views and
|
|
1773
|
+
per-asset URLs for editor previews.
|
|
1774
|
+
- `PUT /globals/:slug` audit logging now records nested `appearance`/`brand`
|
|
1775
|
+
changes.
|
|
1776
|
+
|
|
1777
|
+
**cms-admin**
|
|
1778
|
+
- New Appearance tab (Branding, Admin Theme, Typography & Motion, live Brand
|
|
1779
|
+
Preview) wired to the Media Center for logo/favicon uploads (assets referenced
|
|
1780
|
+
by ID, never raw paths), with shape warnings, dark-logo warnings, accent
|
|
1781
|
+
contrast validation, source badges, and read-only/permission states.
|
|
1782
|
+
- `ThemeProvider` now applies color mode, accent color, sidebar density, content
|
|
1783
|
+
font, and reduce-motion live, and feeds the resolved admin brand to the
|
|
1784
|
+
sidebar logo (with site-initials fallback).
|
|
1785
|
+
|
|
1786
|
+
## 0.35.1
|
|
1787
|
+
|
|
1788
|
+
### Patch Changes
|
|
1789
|
+
|
|
1790
|
+
- b3f51f2: Fix "Global not found" error on the Settings page (and any global editor) before the first save. The authenticated `GET /globals/:slug` route now returns an empty object for a global that has no document yet — globals are upsert-on-write, so a never-saved slug is a valid empty state rather than a 404. The public `/public/globals/:slug` route keeps its config-gated 404 for genuinely undeclared globals.
|
|
1791
|
+
|
|
1792
|
+
## 0.35.0
|
|
1793
|
+
|
|
1794
|
+
### Minor Changes
|
|
1795
|
+
|
|
1796
|
+
- 58eff9b: Build a production-ready General Settings tab with a computed Site Index Status card.
|
|
1797
|
+
|
|
1798
|
+
**cms-core**
|
|
1799
|
+
- Add `getSiteIndexStatus()` / `computeSiteIndexStatus()` (exported from the SEO module) that derives the effective crawl/index state (indexable / protected / blocked / partially_blocked / unknown) from the SAME effective SEO config the public renderer uses — environment, site URL, robots, sitemap, and last audit — plus a `robotsTxtBlocksEntireSite()` helper.
|
|
1800
|
+
- Add `GET /seo/index-status` (ADMIN) returning the computed status, per-check rows, and actions.
|
|
1801
|
+
- Audit `PUT /globals/settings` with a per-key before/after diff (`settings_changed`, including source + environment).
|
|
1802
|
+
- Migrate the `GET /seo/meta/:documentId` preview to read robots defaults from the effective SEO config instead of the legacy `settings` global, so preview, public render, and General Settings share one source of truth.
|
|
1803
|
+
|
|
1804
|
+
**cms-admin**
|
|
1805
|
+
- Replace the General tab with a fully wired implementation: Site Index Status card, Site Information (with config-managed source badges + live Site URL validation), Language & Region, and SEO & Robots Defaults — the SEO toggles now read/write the shared `/seo/config` (no duplicate store).
|
|
1806
|
+
- Add dirty-state Save/Reset bar, validation summary, confirmation dialogs for dangerous indexing changes (production noindex on; non-production noindex off), loading skeletons, error/retry, and read-only gating for non-admins.
|
|
1807
|
+
- Use semantic theme tokens (purple `brand` accent, status colors) so the tab matches the design system in light and dark modes.
|
|
1808
|
+
|
|
1809
|
+
## 0.34.0
|
|
1810
|
+
|
|
1811
|
+
### Minor Changes
|
|
1812
|
+
|
|
1813
|
+
- a6d6674: Forms PII/encryption, retention, audit, export, and MCP tools (PR8).
|
|
1814
|
+
|
|
1815
|
+
**cms-core**
|
|
1816
|
+
- **Field-level encryption at rest**: submission values for fields flagged `encrypted` (or high-PII fields when `forms.encryptSensitiveFields` is enabled) are now encrypted with the `enc:v1:` AES-256-GCM envelope before persistence and transparently decrypted on every read path. Plaintext/legacy rows pass through unchanged; missing key on read fails closed.
|
|
1817
|
+
- **PII retention**: `forms.retentionDays` is now wired into the cleanup cron, hard-deleting form submissions (and cascading their files) older than the window. Opt-in — disabled by default.
|
|
1818
|
+
- **Audit logging**: viewing a single entry now logs `form.entry.viewed` and CSV export logs `form.entries.exported`, both with user id and IP, for PII-access traceability.
|
|
1819
|
+
- **CSV export endpoint**: `GET /forms/entries/export?formId=…` streams a decrypted CSV of a form's submissions (bounded to 5000 rows) with formula-injection-safe cells. New exported helpers `exportFormEntries`, `encryptSubmissionData`, `decryptSubmissionData`, `fieldShouldEncrypt`.
|
|
1820
|
+
|
|
1821
|
+
**mcp-server**
|
|
1822
|
+
- New `list_form_entries` and `get_form_entry` tools over the decrypted submissions inbox, both requiring an explicit `formId`/`entryId` (no enumeration) and documented as PII-sensitive. `get_form_entry` reads are audited server-side.
|
|
1823
|
+
|
|
1824
|
+
## 0.33.0
|
|
1825
|
+
|
|
1826
|
+
### Minor Changes
|
|
1827
|
+
|
|
1828
|
+
- 9f4192b: Add the Forms notifications + webhooks pipeline (PR6).
|
|
1829
|
+
- **Email notifications**: on a clean submission, send admin alerts to the form's configured recipients and an optional submitter autoresponder through the configured `platform.email` adapter. Supports `{{placeholder}}` subject/message templates (HTML-escaped), a fields table that can exclude sensitive/PII fields, and a resolved `Reply-To` from a chosen field.
|
|
1830
|
+
- **Per-form webhooks**: new `createFormWebhook` / `listFormWebhooks` / `updateFormWebhook` / `deleteFormWebhook` / `testFormWebhook` / `dispatchFormWebhooks` services. Secrets are stored encrypted and returned only once at creation; payloads are HMAC-SHA256 signed; delivery is SSRF-safe (`safeFetch`) with bounded retries + exponential backoff. Events: `form.submitted`, `form.spam_detected`, `form.entry.starred`, `form.entry.archived`.
|
|
1831
|
+
- **API**: `GET/POST /forms/:id/webhooks`, `PATCH/DELETE /forms/:id/webhooks/:webhookId`, `POST /forms/:id/webhooks/:webhookId/test`, and `POST /forms/:id/notifications/test`.
|
|
1832
|
+
- Side-effects fire non-blocking from the submission pipeline so a slow or failing channel never delays or breaks the public response. Spam submissions suppress email but still fire `form.spam_detected` webhooks.
|
|
1833
|
+
- `EmailAdapter` send options gain optional `replyTo` / `from` (additive, non-breaking).
|
|
1834
|
+
|
|
1835
|
+
## 0.32.0
|
|
1836
|
+
|
|
1837
|
+
### Minor Changes
|
|
1838
|
+
|
|
1839
|
+
- 5a44d79: Add the public form submission pipeline (Forms PR5).
|
|
1840
|
+
- New public, unauthenticated endpoints: `GET /public/forms/:slug` (embeddable
|
|
1841
|
+
schema projection with server-only fields stripped) and
|
|
1842
|
+
`POST /public/forms/:slug/submit`, plus CORS preflight (`OPTIONS`) support.
|
|
1843
|
+
- Server-authoritative submission processing: per-field validation +
|
|
1844
|
+
type-coercion against the active schema version, HTML sanitization of all
|
|
1845
|
+
string values (stored-XSS defense), and dropping of keys not in the schema.
|
|
1846
|
+
- Spam protection strategies: honeypot, time-to-submit, email/domain blocklist,
|
|
1847
|
+
keyword blocklist, and origin domain allowlist. Spam is persisted but hidden
|
|
1848
|
+
from the default inbox.
|
|
1849
|
+
- File-upload validation (MIME allowlist + magic-byte sniffing + size cap) wired
|
|
1850
|
+
to the configured storage adapter; metadata persisted to `FormSubmissionFile`.
|
|
1851
|
+
- Sequential per-form entry numbers assigned in a transaction; client IPs are
|
|
1852
|
+
hashed (never stored raw); attribution/UTM captured; submissions rate-limited
|
|
1853
|
+
per IP+form and CAPTCHA-verified when configured.
|
|
1854
|
+
- The legacy `POST /forms/:id/submit` route now runs through the same pipeline.
|
|
1855
|
+
|
|
1856
|
+
## 0.31.0
|
|
1857
|
+
|
|
1858
|
+
### Minor Changes
|
|
1859
|
+
|
|
1860
|
+
- ca3c1f6: Forms system — data model foundation (PR 1 of 9).
|
|
1861
|
+
|
|
1862
|
+
Adds the persistence layer and shared contracts for the full Forms workflow:
|
|
1863
|
+
- New Prisma models: `FormSchemaVersion` (versioned field-schema snapshots so historical submissions always render against the schema they were captured with), `FormSubmissionFile` (file-upload metadata stored via the storage adapter, cascade-deleted with its submission), and `FormWebhook` (per-form signed outbound webhooks).
|
|
1864
|
+
- Expanded `FormSubmission` with the submission-workflow columns: `entryNumber`, `schemaVersionId`, `normalizedData`, `senderName/Email/Phone`, `preview`, `unread`/`starred`/`archived`, `spamStatus`, attribution metadata (`ipAddressHash`, `userAgent`, `referrer`, `pageUrl`, `utm*`), `readAt`, `archivedAt`, and `updatedAt`. The migration is safe on populated databases (all additive/defaulted, `updatedAt` backfilled, existing rows marked read).
|
|
1865
|
+
- New shared TypeScript contracts under `@actuate-media/cms-core` (`FormDefinition`, `FormField`, `FormFieldType`, `FormSchemaVersion`, `FormSubmission`, `FormSubmissionFile`, `FormNotificationSettings`, `FormSpamSettings`, `FormEmbedSettings`, `FormWebhookConfig`, query-param and stats types).
|
|
1866
|
+
- New `forms` site-config section (`FormsConfig`): feature flag, allowed field types, spam defaults, file-upload limits, notification/webhook/autoresponder toggles, retention, PII encryption, and the public submission path.
|
|
1867
|
+
|
|
1868
|
+
No runtime behavior changes yet; subsequent PRs wire services, API routes, admin UI, public submission, notifications, and seed data on top of this model.
|
|
1869
|
+
|
|
1870
|
+
- 15d8c08: Forms system — core services + admin API routes (PR 2 of 9).
|
|
1871
|
+
|
|
1872
|
+
Adds the service layer and REST endpoints that back the Forms admin UI:
|
|
1873
|
+
- **Forms service** (`forms/service.ts`): `listForms`, `getFormById`, `createForm`, `updateForm`, `duplicateForm`, `archiveForm`/`restoreForm`, `deleteForm` (blocks deletion when submissions exist unless forced), schema versioning (`saveFormSchema`/`getFormSchema` creates a new `FormSchemaVersion` on every save), per-form notification/embed settings, `getFormStats`, and `getFormsSidebarCounts`. Form definitions are stored via the standard document data-layer (inheriting versioning, cache invalidation, and audit logging). Schema validation enforces unique/valid keys, allowed field types, option presence, and the configured field cap.
|
|
1874
|
+
- **Entries service** (`forms/entries.ts`): `listEntries` with search + form/unread/starred/archived/spam/date filters (archived and spam excluded by default), `getEntryWithSchema` (renders an entry against the schema version it was captured with), `getEntryCounts` (chip totals + per-form unread map), read/unread/star/archive mutations, `bulkUpdateEntries`, and `deleteEntry`. A normalization layer derives sender/preview from the payload for legacy rows.
|
|
1875
|
+
- **API routes**: `GET/POST /forms`, `GET/PATCH/DELETE /forms/:id`, `POST /forms/:id/duplicate`, `GET/PUT /forms/:id/schema`, `GET/PUT /forms/:id/notifications`, `PUT /forms/:id/notify`, `GET/PUT /forms/:id/embed`, `GET /forms/stats`, `GET /forms/sidebar-counts`, `GET /forms/entries`, `GET /forms/entries/counts`, `GET/PATCH/DELETE /forms/entries/:entryId`, and `POST /forms/entries/bulk`. All require an authenticated write role and emit audit-log events.
|
|
1876
|
+
|
|
1877
|
+
Behavior change: `GET /forms` now returns the typed `FormDefinition` shape (including entry counters). `fields` is now the field array; the legacy `fields`-as-count value is available as `fieldCount`, and `submissions` mirrors `totalEntries` so existing admin views keep working until they migrate (PR 3).
|
|
1878
|
+
|
|
1879
|
+
Also fixes a pre-existing sitemap test that omitted the `type: 'page'` collection flag the SEO scoping now requires.
|
|
1880
|
+
|
|
1881
|
+
- 8891547: Scope the SEO surfaces to real front-end content and keep the admin out of search indexes.
|
|
1882
|
+
- **cms-core**: The Content SEO list, audit, scan, internal-link candidates, **and the XML sitemap** now only include documents from collections explicitly typed `page` or `post` (visible and sitemap-eligible). Structural/utility collections (navigation menus, footers, tags, templates) — which have no `type` — and ad-hoc rows whose collection isn't declared in the config (e.g. benchmark/test data) are excluded, so a menu can no longer leak into the sitemap as an indexable URL. Adds the `frontEndContentCollectionTypes(config)` helper and threads a `collectionTypes` option through `gatherAuditEntities` / `runAndPersistAudit`; `processSeoScan` gains an `allowedCollections` option. The `POST /seo/scan` route now reuses `processSeoScan` (also fixing its case-sensitive `alt=`/`<h1` checks).
|
|
1883
|
+
- **cms-admin**: `AdminRoot` now ensures a `noindex, nofollow` robots meta tag while the admin is mounted, so private admin UI is never indexed regardless of how the consumer renders the admin page.
|
|
1884
|
+
|
|
1885
|
+
## 0.30.0
|
|
1886
|
+
|
|
1887
|
+
### Minor Changes
|
|
1888
|
+
|
|
1889
|
+
- 2cd5d3a: SEO production-readiness (Important tier, batch 1 — backend security/access):
|
|
1890
|
+
- **Read-endpoint access control.** `/seo/analysis`, `/seo/readability`, `/seo/internal-links`, `/seo/schema` and `/seo/meta` now enforce the target collection's `access.read` rule (and API-key collection scope), so a principal scoped to one collection can no longer pull SEO intelligence for documents in another. Adds a new exported `assertCollectionAccess(collection, operation, ctx, doc?)` helper.
|
|
1891
|
+
- **AI audit logging.** Every successful `/ai/seo/*` generation (`generate-field`, `explain-issue`, `generate-schema`, `summarize`) now writes an `ai_seo_generate` audit-log event — these calls are billable and feed public meta/schema, so they need a forensic trail.
|
|
1892
|
+
- **Prompt-injection + token-cost hardening.** Untrusted document/issue content interpolated into AI prompts is now length-clamped (2k chars) and fenced with explicit "treat as data, never instructions" delimiters across all SEO AI endpoints, including the title/description branches that previously sent unbounded content.
|
|
1893
|
+
|
|
1894
|
+
- 8d45b42: SEO production-readiness (Important tier, batch 2 — backend reliability/correctness):
|
|
1895
|
+
- **Severity ordering.** Issue lists (`/seo/issues`, `/seo/overview`, AI summary) now rank by explicit risk (critical → warning → info) instead of an alphabetical `severity` sort that wrongly ranked `info` above `warning`.
|
|
1896
|
+
- **Sitemap pagination.** Per-collection sitemaps no longer cap at 5,000 URLs and silently drop the rest. Collections larger than the 50k protocol limit are split across `?page=N` files referenced from the sitemap index (45k URLs/file). Sitemap-index `<loc>` values are now XML-escaped.
|
|
1897
|
+
- **Consistent robots resolution.** `composePageMeta` now resolves robots through the shared `resolveRobotsDirectives`, honoring the `robotsPolicy` select field and a new `seo.robots.noIndexNonProduction` guard (plus `defaultNoIndex`/`defaultNoFollow`) so staging/preview deploys aren't indexed. Adds an optional `environment` input.
|
|
1898
|
+
- **Audit reliability.** `runAndPersistAudit` refuses to start while another audit is RUNNING (prevents parallel runs duplicating `SeoIssue` rows), `gatherAuditEntities` is bounded with a `take` cap, and the expensive `/seo/scan` endpoint is now rate-limited and bounded.
|
|
1899
|
+
|
|
1900
|
+
- 90f53a6: Fix SEO Operations Center production-readiness blockers.
|
|
1901
|
+
- **SSRF (`GET /seo/summary`)**: the link-health probe now uses `safeFetch`
|
|
1902
|
+
(blocks private/loopback IPs, no redirect following), only probes same-origin
|
|
1903
|
+
internal links, skips all probing when `NEXT_PUBLIC_SITE_URL` is unset, and is
|
|
1904
|
+
gated to EDITOR+ — matching `/seo/link-health`.
|
|
1905
|
+
- **`GET /llms.txt` leak**: now excludes soft-deleted documents and
|
|
1906
|
+
internal/system collections (`__seo_config__`, `__*`, etc.) via the shared
|
|
1907
|
+
`isInternalCollection` helper, so "deleted" and internal content can no longer
|
|
1908
|
+
appear on a public surface.
|
|
1909
|
+
- **`PUT /seo/content` data-layer bypass**: SEO field edits now route through the
|
|
1910
|
+
new `updateDocumentSeoFields` action, which enforces the collection's
|
|
1911
|
+
`access.update` rule, snapshots a `Version` row, recomputes the search index,
|
|
1912
|
+
and fires the `afterUpdate`/cache-invalidate effects — while preserving the
|
|
1913
|
+
undeclared SEO convention keys the generic field-access layer would strip.
|
|
1914
|
+
Inbound SEO fields are now type- and length-validated.
|
|
1915
|
+
- **cms-admin**: SEO read fetchers in `seo-service` now re-throw on `cmsApi`
|
|
1916
|
+
errors so the error/retry UI works instead of rendering a misleading empty
|
|
1917
|
+
fallback (e.g. a fake score of 0 / grade "Critical") on a backend failure.
|
|
1918
|
+
|
|
1919
|
+
### Patch Changes
|
|
1920
|
+
|
|
1921
|
+
- 1bd4a49: SEO production-readiness Polish tier — post-launch nice-to-haves from the SEO review.
|
|
1922
|
+
|
|
1923
|
+
cms-core:
|
|
1924
|
+
- **Markdown-aware heading analysis** — `analyzeContent` and `countH1` now detect
|
|
1925
|
+
Markdown ATX (`#`/`##`) and Setext (`===`/`---`) headings in addition to HTML
|
|
1926
|
+
tags, so markdown-bodied posts are no longer wrongly flagged "No H1 found" /
|
|
1927
|
+
"no subheadings".
|
|
1928
|
+
- **Correct default OG image URL** — the auto-generated `og:image` now points at
|
|
1929
|
+
`/api/cms/og.png` (where the endpoint is actually mounted) instead of `/og.png`,
|
|
1930
|
+
which 404'd for social crawlers.
|
|
1931
|
+
- **Consistent environment resolution** — `resolveSeoEnvironment()` centralises the
|
|
1932
|
+
production gate, preferring `VERCEL_ENV` over `NODE_ENV` everywhere (preview
|
|
1933
|
+
deploys report `NODE_ENV=production`), so `noIndexNonProduction` reliably
|
|
1934
|
+
noindexes preview deployments.
|
|
1935
|
+
- **Admin route boundary** — the CMS middleware matches `/admin` and `/api/cms` on
|
|
1936
|
+
a path-segment boundary, so sibling public routes like `/administrators` or
|
|
1937
|
+
`/admin-news` are no longer treated as admin routes.
|
|
1938
|
+
- **AI status semantics** — SEO AI endpoints now return `503` when the provider is
|
|
1939
|
+
installed-but-unconfigured (vs `501` when the plugin is genuinely missing).
|
|
1940
|
+
- Removed a duplicated `gradeForScoreBand` helper in favour of the canonical
|
|
1941
|
+
`gradeForSeoScore`.
|
|
1942
|
+
|
|
1943
|
+
cms-admin:
|
|
1944
|
+
- SEO filter chips use accessible toggle-button semantics (`aria-pressed`) instead
|
|
1945
|
+
of misused `role="tab"`.
|
|
1946
|
+
- Crawl-settings toggles use the Radix `Switch` primitive and are disabled while a
|
|
1947
|
+
save is in flight.
|
|
1948
|
+
- Score progress bars expose an accessible name and `aria-valuetext`; drawers
|
|
1949
|
+
without a description no longer trigger Radix's missing-description warning.
|
|
1950
|
+
- Redirect CSV export defers object-URL revocation (and mounts the anchor) so the
|
|
1951
|
+
download isn't cancelled before it starts.
|
|
1952
|
+
- The SEO AI service treats both `501` and `503` as the graceful "AI unavailable"
|
|
1953
|
+
state.
|
|
1954
|
+
|
|
1955
|
+
## 0.29.0
|
|
1956
|
+
|
|
1957
|
+
### Minor Changes
|
|
1958
|
+
|
|
1959
|
+
- a3b702f: Media Center foundation (phase 1):
|
|
1960
|
+
- New media SEO scoring module (`calculateMediaSeoScore` + types) that grades assets on alt text, filename quality, dimensions, captions, and size.
|
|
1961
|
+
- Additive `Media` schema fields (`originalFileName`, `caption`, `description`, `focusKeyword`, `tags`, `publicUrl`, `storageProvider`, `durationSeconds`, `thumbnailUrl`, `responsiveVariants`, `status`, `deletedAt`) plus a `MediaStatus` enum and supporting indexes, with the `0008_media_center` migration.
|
|
1962
|
+
|
|
1963
|
+
All fields are nullable or defaulted, so the change is additive and safe to apply to an existing database.
|
|
1964
|
+
|
|
1965
|
+
- 5fa84d3: Media library now reports real usage. `GET /media` joins the `MediaUsage` table and returns a `usedOn` array (page title + admin editor path) per asset, so the "orphaned" indicator is accurate instead of flagging every file. Adds `POST /ai/media-metadata`, which generates accessible alt text (and a derived title) for an image via the AI vision provider, with a graceful 501 when the AI plugin/provider isn't configured.
|
|
1966
|
+
- 5fa84d3: SEO Operations Center — a full detect → prioritize → explain → route → fix → audit → generate workflow.
|
|
1967
|
+
|
|
1968
|
+
**cms-core**
|
|
1969
|
+
- Additive `0009_seo` migration (mirrored to `apps/dev`): `SEOAuditRun`, `SEOIssue` (+ severity/status/category enums), `Redirect` extensions (`hits`, `lastHitAt`, `enabled`, `priority`, `preserveQuery`, `source`), `Redirect404Hit`, and `RedirectSuggestion`. All columns are nullable or defaulted, so it is safe to apply to an existing database.
|
|
1970
|
+
- Pure, explainable scoring model (`seo/score.ts`): `calculatePageSeoScore` / `calculateSiteSeoScore` with documented weights and breakdowns.
|
|
1971
|
+
- Persistent audit engine (`seo/audit-engine.ts`) with cross-run issue lifecycle (open / resolved / ignored / recurring) and a redirect engine (chain/loop detection, safe flatten, hit tracking, 404 logging, gated AI 404 recovery).
|
|
1972
|
+
- New endpoints: `POST /seo/audit`, `GET /seo/audits[/:id]`, `GET /seo/issues[/:id]`, issue `resolve`/`ignore`/`reopen`/`fix`, `GET /seo/overview`, redirect CRUD + import/export + chains/loops/flatten/suggest, technical settings (`/seo/sitemap/*`, `/seo/crawl-settings`, `/seo/robots`), and approval-gated `/ai/seo/*` (generate-field, explain-issue, generate-schema, summarize) that degrade gracefully with 501 when `plugin-ai` is unconfigured.
|
|
1973
|
+
- `/resolve` now returns Next.js-ready `metadata` plus validated JSON-LD; public `robots.txt` honors the admin-edited body.
|
|
1974
|
+
|
|
1975
|
+
**cms-admin**
|
|
1976
|
+
- Rebuilt `SEO.tsx` into a five-tab Operations Center (Overview / Content / Technical / Redirects / Audit) with `?tab=` deep-link routing, dynamic badge counts, a typed `seo-service.ts` client, reusable token-only primitives and a focus-trapped drawer, editor/fix/copilot drawers, and full loading / empty / error / permission states.
|
|
1977
|
+
|
|
1978
|
+
**mcp-server**
|
|
1979
|
+
- New SEO tools: `seo_overview`, `run_seo_audit`, `list_seo_audit_runs`, `list_seo_issues`, `get_seo_issue`, `set_seo_issue_status`, `list_redirect_rules`, `seo_redirect_overview`, `seo_redirect_chains`.
|
|
1980
|
+
|
|
1981
|
+
**create-actuate-cms**
|
|
1982
|
+
- Scaffolded projects now ship a redirect-resolving `middleware.ts` backed by the database redirect table.
|
|
1983
|
+
|
|
1984
|
+
### Patch Changes
|
|
1985
|
+
|
|
1986
|
+
- 766b63c: Surface database-unavailable errors during first-run setup instead of masking them as "setup required". `checkSetupRequired()` and `createInitialAdmin()` now rethrow connection/initialization failures (Prisma `P1001`/`P1002`, `ECONNREFUSED`, `PrismaClientInitializationError`, etc.) so the `/setup/status` and `/setup/create-admin` endpoints return a 5xx for a DB outage rather than silently rendering the account-creation wizard and then failing with an opaque 400.
|
|
1987
|
+
|
|
1988
|
+
## 0.28.1
|
|
1989
|
+
|
|
1990
|
+
### Patch Changes
|
|
1991
|
+
|
|
1992
|
+
- fa54768: Accept SMTP.com's `SMTP_KEY` / `SMTP_CHANNEL` / `SMTP_EMAIL` env vars as
|
|
1993
|
+
aliases for `SMTPCOM_API_KEY` / `SMTPCOM_CHANNEL` / `EMAIL_FROM`.
|
|
1994
|
+
|
|
1995
|
+
`resolveEmailConfigFromEnv()` now infers the `smtpcom` provider from either
|
|
1996
|
+
naming scheme (the generic relay's `SMTP_HOST` still takes precedence when
|
|
1997
|
+
present), and the `/health` env diagnostic recognises the same keys so a
|
|
1998
|
+
correctly-configured SMTP.com deployment no longer warns about a missing
|
|
1999
|
+
`RESEND_API_KEY`.
|
|
2000
|
+
|
|
2001
|
+
## 0.28.0
|
|
2002
|
+
|
|
2003
|
+
### Minor Changes
|
|
2004
|
+
|
|
2005
|
+
- bdeadb2: Resolve real author and related-posts data for post-type documents.
|
|
2006
|
+
|
|
2007
|
+
`GET /resolve` now returns a `post` object for post-type collections:
|
|
2008
|
+
- `post.author` — `{ name, avatarUrl }` derived from the document's creator, so the Author Bio section shows the real author instead of a placeholder.
|
|
2009
|
+
- `post.relatedPosts` — up to 3 recent published siblings in the same collection (`{ title, url, excerpt }`), so the Related Posts section links real content.
|
|
2010
|
+
|
|
2011
|
+
Both fields are additive and optional; existing consumers are unaffected.
|
|
2012
|
+
|
|
2013
|
+
- 1768a8a: Render the post type's template header on the public site so the live page matches the editor canvas and preview.
|
|
2014
|
+
- `GET /resolve` now returns `template.header` (a normalized `PostHeaderConfig`) for post-type documents, resolved from the post type's `post-templates` doc at read time. The template stays the single source of truth, so editing it restyles every post of that type without re-saving each post.
|
|
2015
|
+
- New `computeReadingTime()` export drives the header's "min read" figure from the post body (200 wpm, HTML stripped, min 1 minute) instead of the previous hardcoded "5 min read".
|
|
2016
|
+
|
|
2017
|
+
- d85e89e: Add a server-side aggregate `GET /posts` endpoint for the admin Posts area.
|
|
2018
|
+
Filtering, sorting and pagination now run at the database level (one query plus
|
|
2019
|
+
one count) and respect per-collection read scope, so the Posts list scales past
|
|
2020
|
+
the old ~500-row-per-type client cap instead of pulling every document into the
|
|
2021
|
+
browser. The admin `fetchPosts` service now calls this endpoint; its public
|
|
2022
|
+
`Post` / `PostType` / `PostAuthor` shapes are unchanged.
|
|
2023
|
+
|
|
2024
|
+
### Patch Changes
|
|
2025
|
+
|
|
2026
|
+
- 5567daf: Post templates: seed sensible defaults and route the admin through the dedicated endpoint.
|
|
2027
|
+
- A post type with no saved template now returns the default starting template (centered header + article body + related posts) instead of an empty one, so new posts and the template editor are never blank. Applied on the server (`GET /post-templates/:type`) and defended client-side.
|
|
2028
|
+
- The admin post-editor service now reads/writes templates through the dedicated, audited `/post-templates/:type` endpoints (scoped + logged) instead of the raw `post-templates` collection API.
|
|
2029
|
+
|
|
2030
|
+
## 0.27.4
|
|
2031
|
+
|
|
2032
|
+
### Patch Changes
|
|
2033
|
+
|
|
2034
|
+
- c091125: Recognise Vercel Marketplace Upstash credentials. The Vercel Upstash (Redis/KV) integration provisions `KV_REST_API_URL` / `KV_REST_API_TOKEN`, not `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN`, so a correctly-provisioned store previously fell back to the in-memory rate limiter and the `/health` check kept warning.
|
|
2035
|
+
|
|
2036
|
+
The rate limiter (`createRateLimiter` / `resolveUpstashCredentials`), the platform-vercel rate-limit adapter, and the env health check now read the `KV_REST_API_*` names as a fallback (native `UPSTASH_*` names still win when both are present). No config change is needed for stores provisioned through the Vercel Marketplace.
|
|
2037
|
+
|
|
2038
|
+
## 0.27.3
|
|
2039
|
+
|
|
2040
|
+
### Patch Changes
|
|
2041
|
+
|
|
2042
|
+
- 6f05e8d: Make the email health check (`RESEND_API_KEY` entry in `validateEnvShape` / `/api/cms/health`) provider-aware. It now reports "Configured" when any supported email provider is set up via env (SMTP, SES, or SMTP.com), instead of always warning about a missing Resend key. Mirrors the env resolution in `@actuate-media/plugin-email`.
|
|
2043
|
+
|
|
2044
|
+
## 0.27.2
|
|
2045
|
+
|
|
2046
|
+
### Patch Changes
|
|
2047
|
+
|
|
2048
|
+
- 07f52af: Accept Prisma Accelerate / Prisma Postgres connection schemes (`prisma://`,
|
|
2049
|
+
`prisma+postgres://`) in the `DATABASE_URL` env-shape validator. Previously the
|
|
2050
|
+
`/health` and admin diagnostics endpoints flagged a valid Accelerate URL as an
|
|
2051
|
+
invalid scheme, reporting the instance as unhealthy even though the runtime
|
|
2052
|
+
connects successfully through the Accelerate transport.
|
|
2053
|
+
|
|
2054
|
+
## 0.27.1
|
|
2055
|
+
|
|
2056
|
+
### Patch Changes
|
|
2057
|
+
|
|
2058
|
+
- 4fb0097: Dashboard performance: cache the expensive SEO compute and reduce refetch churn.
|
|
2059
|
+
- **cms-core:** `/stats` now caches its average-SEO-score computation per content-scope for a short TTL (60s). That score otherwise reads and scans the `data` JSON of up to 200 published documents on every dashboard load — the heaviest part of the endpoint. On a warm cache the read is skipped entirely; transient DB failures are not cached. Module-level so it survives across warm requests; a cold start recomputes once.
|
|
2060
|
+
- **cms-admin:** `useApiData` gains an opt-in `cacheMs` option. When set, successful responses are cached in-memory by endpoint and reused on the next mount (cache-first), so views that remount frequently don't re-issue identical requests. `refetch()` always bypasses the cache. Default behaviour is unchanged for existing callers. The Dashboard enables a 30s cache on its four requests, so navigating away and back no longer re-fires them; first load and independent streaming are preserved.
|
|
2061
|
+
|
|
2062
|
+
## 0.27.0
|
|
2063
|
+
|
|
2064
|
+
### Minor Changes
|
|
2065
|
+
|
|
2066
|
+
- 91885de: Add a section-aware bridge so AI agents (e.g. in Cursor / VS Code) can build pages section-by-section through MCP, producing real, editable, publishable Page Editor sections rather than disposable markup.
|
|
2067
|
+
- **cms-core**: new `@actuate-media/cms-core/page-sections` subpath exporting the flat Page Editor section registry, pure section-array helpers, and a deterministic `validateSectionContent`. This is now the single source of truth shared by the admin editor, the REST API, and MCP. Adds REST routes: `GET /page-sections/types`, `POST /page-sections/validate`, and section CRUD over a page document's `data.sections` (`GET|POST /page-sections/:collection/:id`, `PATCH|DELETE /page-sections/:collection/:id/:sectionId`, `POST /page-sections/:collection/:id/reorder`). Mutations go through the audited `updateDocument` path (versions, hooks, cache invalidation, live-preview push) and reuse collection scope checks. Writes are rejected on collections that don't declare a `sections` JSON field.
|
|
2068
|
+
- **mcp-server**: nine new tools — `list_section_types`, `get_section_schema`, `validate_section_content`, `list_page_sections`, `add_page_section`, `update_page_section`, `reorder_page_sections`, `remove_page_section`, `toggle_page_section_visibility`.
|
|
2069
|
+
- **cms-admin**: the Page Editor section registry and section helpers now re-export from `@actuate-media/cms-core/page-sections`; per-section publish validation is delegated to the shared `validateSectionContent`. No behavior change — internal refactor for a single source of truth.
|
|
2070
|
+
|
|
2071
|
+
- 91885de: Add a design-driven Posts template system that mirrors the Pages section editor.
|
|
2072
|
+
- **cms-core**: tag section types with a `scope` (`page` | `post`), add post-oriented section types (`article-body`, `quote`, `author-bio`, `related-posts`, `gallery`), `PostHeaderConfig` / `PostTemplate` types, the `getSectionTypesForScope` helper, and new field types (`richText`, `boolean`, `relationship`, `gallery`). Adds `GET/PUT /post-templates/:type` API routes and a `?scope=` filter on `GET /page-sections/types`.
|
|
2073
|
+
- **cms-admin**: new `PostSectionEditor`, `PostTemplateEditor` (with header panel), `PostPreview`, post section renderers, and a `post-editor-service` data layer. The Add Section gallery now filters by scope, and post type cards expose an "Edit template" action.
|
|
2074
|
+
- **mcp-server**: scope-aware `list_section_types`, plus `get_post_template` and `save_post_template` tools so an external AI agent can read and build a post type's template.
|
|
2075
|
+
|
|
2076
|
+
### Patch Changes
|
|
2077
|
+
|
|
2078
|
+
- 91885de: Add the Posts admin area (All Posts + Post Types).
|
|
2079
|
+
- `/posts` aggregates every collection marked `type: 'post'` (or
|
|
2080
|
+
`admin.group: 'Posts'`) into a single sortable, filterable table
|
|
2081
|
+
with type tabs, search, status/author filters, bulk actions
|
|
2082
|
+
(publish / move to draft / delete), and pagination. Counts come
|
|
2083
|
+
from the data layer, not constants.
|
|
2084
|
+
- `/posts/types` shows a card grid per post-type collection with
|
|
2085
|
+
accent border, icon, description, post count, field count, and
|
|
2086
|
+
URL base — plus a dashed "+ New Post Type" card linking to setup
|
|
2087
|
+
instructions.
|
|
2088
|
+
- `/posts/new` lands on a type picker before opening the existing
|
|
2089
|
+
per-collection editor.
|
|
2090
|
+
- `/posts/types/new` offers a **Quick Post Type** gallery (Case
|
|
2091
|
+
Studies, News, Portfolio, Events, Team) that pre-fills a tailored
|
|
2092
|
+
field set, icon, and accent color, plus a manual form — both feed a
|
|
2093
|
+
paste-ready `actuate.config.ts` snippet generator. Collections
|
|
2094
|
+
remain code-defined because the read-only serverless filesystem
|
|
2095
|
+
rules out runtime config writes.
|
|
2096
|
+
- Sidebar collapses every post-type collection under a single
|
|
2097
|
+
"Posts" parent with fixed `All Posts` / `Post Types` children.
|
|
2098
|
+
- `/api/cms/collections` now surfaces `admin.description`, `icon`,
|
|
2099
|
+
`color`, and `group` so the admin client can render type cards
|
|
2100
|
+
and tabs without a separate fetch.
|
|
2101
|
+
- New `admin.color` field on `CollectionDefinition` (accent token).
|
|
2102
|
+
|
|
2103
|
+
V1 deliberately skips an in-app Post Type editor / field builder —
|
|
2104
|
+
the cards click through to the existing per-collection list and
|
|
2105
|
+
new Post Types are added via the snippet generator. A follow-up PR
|
|
2106
|
+
introduces a DB-overlay for visual metadata (icon / color /
|
|
2107
|
+
description) editable from the admin UI.
|
|
2108
|
+
|
|
2109
|
+
- 4451a57: `/api/cms/stats` now filters the dashboard's recent activity feed,
|
|
2110
|
+
total document count, per-collection counts, status counts, and SEO
|
|
2111
|
+
score corpus to "user-facing CMS content" only.
|
|
2112
|
+
- If the CMS config registers collections (the production shape),
|
|
2113
|
+
those slugs are the allowlist. Anything else — benchmark fixtures,
|
|
2114
|
+
orphan rows from removed collections, test data — is hidden.
|
|
2115
|
+
- If `config.collections` is empty (dev consumers that ship
|
|
2116
|
+
collections to the admin client only), the endpoint falls back to
|
|
2117
|
+
a small denylist of internal slugs (`benchmarks`, `bench`).
|
|
2118
|
+
|
|
2119
|
+
This stops the benchmark suite's `benchmarks` collection from leaking
|
|
2120
|
+
into the dashboard.
|
|
2121
|
+
|
|
2122
|
+
## 0.26.0
|
|
2123
|
+
|
|
2124
|
+
### Minor Changes
|
|
2125
|
+
|
|
2126
|
+
- 560d1de: Dashboard redesign + real-data backends.
|
|
2127
|
+
|
|
2128
|
+
`@actuate-media/cms-admin`:
|
|
2129
|
+
- Sidebar now uses explicit Content and Settings group headers with nested
|
|
2130
|
+
Pages children (Saved Sections, Templates). The collapse control moved
|
|
2131
|
+
to the sidebar footer.
|
|
2132
|
+
- Header hoists primary page actions (e.g. "+ New Post", "View Site")
|
|
2133
|
+
into the top utility bar so the page hero is no longer the source of
|
|
2134
|
+
truth for those affordances. The notification bell is wired to the
|
|
2135
|
+
real `NotificationBell` component.
|
|
2136
|
+
- Dashboard greeting picks the user's first name, and the "Delivery API"
|
|
2137
|
+
- "Content Health" cards now read live data from new endpoints.
|
|
2138
|
+
|
|
2139
|
+
`@actuate-media/cms-core`:
|
|
2140
|
+
- New `ApiRequestMetric` table (minute-bucketed counter + log-scale
|
|
2141
|
+
latency histogram) and `recordApiRequest` instrumentation wired into
|
|
2142
|
+
`handler-factory.ts`. Writes are fire-and-forget — request paths are
|
|
2143
|
+
never blocked on the metric DB.
|
|
2144
|
+
- New `ContentIssue` table + nightly `processContentHealthScan` cron
|
|
2145
|
+
(`/api/cms/cron/content-health`) that runs a pure-logic scanner for
|
|
2146
|
+
missing meta descriptions, missing alt text, outdated content
|
|
2147
|
+
(>90 days), and broken internal links via HEAD probes.
|
|
2148
|
+
- New endpoints `GET /api/cms/stats/api-delivery` and
|
|
2149
|
+
`GET /api/cms/stats/content-health` return the dashboard's
|
|
2150
|
+
aggregates with stable, never-undefined response shapes.
|
|
2151
|
+
- Migration `0007_dashboard_metrics` adds both tables with the indexes
|
|
2152
|
+
the new queries need. Safe to apply to populated DBs (no backfill).
|
|
2153
|
+
|
|
2154
|
+
`create-actuate-cms`:
|
|
2155
|
+
- Scaffolded `vercel.json` now includes the nightly content-health
|
|
2156
|
+
cron entry, and the deployment guide documents it.
|
|
2157
|
+
|
|
2158
|
+
## 0.25.1
|
|
2159
|
+
|
|
2160
|
+
### Patch Changes
|
|
2161
|
+
|
|
2162
|
+
- e72948a: Fix `compareToDummyHash` eager-init parity test: also warm Node's
|
|
2163
|
+
libuv crypto worker pool before measuring. PR #170 awaited the
|
|
2164
|
+
eager-init promise but missed that the first PBKDF2 call across the
|
|
2165
|
+
entire process pays an additional ~100-150ms cold-start to spawn the
|
|
2166
|
+
worker thread + initialise the C++ binding context. The first run on
|
|
2167
|
+
`main` (run 26599207053) failed with e1=291ms vs e2=150ms — same ~2×
|
|
2168
|
+
ratio as the historical bug, but caused by worker-pool warmup rather
|
|
2169
|
+
than a real regression. The test now does a throwaway warmup call
|
|
2170
|
+
after the eager-init wait so both measurements run on a hot worker
|
|
2171
|
+
pool.
|
|
2172
|
+
- 40d48da: Two flake fixes from the OSS maintenance pass:
|
|
2173
|
+
- **`NotificationBell` SSE/REST race.** The `seenIdsRef` lookup index
|
|
2174
|
+
was reconciled with `items` via `useEffect([items])`, which runs
|
|
2175
|
+
after React commits. An SSE push that arrived between a REST
|
|
2176
|
+
refresh resolving and the effect running would read a stale ref,
|
|
2177
|
+
fall through the de-dup gate, and append a duplicate row. The ref
|
|
2178
|
+
is now maintained synchronously at every items-mutation call site,
|
|
2179
|
+
and the SSE handler also re-checks the latest snapshot inside the
|
|
2180
|
+
`setItems` updater as a final safety net.
|
|
2181
|
+
- **`compareToDummyHash` eager-init test determinism.** Exported a
|
|
2182
|
+
test-only `_waitForDummyHashReady()` so the timing-parity test can
|
|
2183
|
+
measure verify-only latency (rather than racing the eager hash
|
|
2184
|
+
precompute on a cold module). Added a paired test that asserts the
|
|
2185
|
+
eager-init promise resolves with no work pending on the consumer
|
|
2186
|
+
side — catches future refactors that accidentally drop the
|
|
2187
|
+
top-level invocation.
|
|
2188
|
+
|
|
2189
|
+
## 0.25.0
|
|
2190
|
+
|
|
2191
|
+
### Minor Changes
|
|
2192
|
+
|
|
2193
|
+
- a0f6eba: Rich `@`-mention picker for comments (last Phase 7 carry-over).
|
|
2194
|
+
|
|
2195
|
+
**`cms-core`**: New `GET /api/cms/users/search?q=<query>` endpoint —
|
|
2196
|
+
authenticated (any role), case-insensitive `contains` matching against
|
|
2197
|
+
`name` + `email`, capped at 10 results, returns only `{ id, name,
|
|
2198
|
+
email }` (no role / hash / audit metadata). Empty / whitespace
|
|
2199
|
+
queries return an empty array so the endpoint can't be used as a
|
|
2200
|
+
directory-enumeration tool.
|
|
2201
|
+
|
|
2202
|
+
**`cms-admin`**: New `<MentionableTextarea>` component plus pure
|
|
2203
|
+
helpers in `lib/mention-picker.ts` (`detectMentionContext`,
|
|
2204
|
+
`insertMentionToken`, `searchMentionUsers`). Wired into the three
|
|
2205
|
+
comment composer surfaces in `CommentSidePanel`: the new-thread
|
|
2206
|
+
textarea, the reply input, and the in-place edit textarea. The
|
|
2207
|
+
picker is keyboard-driven (Arrow Up / Down, Enter / Tab to commit,
|
|
2208
|
+
Escape to close) with a Radix-style listbox + ARIA roles, and
|
|
2209
|
+
gracefully handles slow responses (drops stale requests when typing
|
|
2210
|
+
quickly).
|
|
2211
|
+
|
|
2212
|
+
**`mcp-server`**: New `search_users(query)` tool exposes the same
|
|
2213
|
+
lookup so AI agents can resolve teammates by name fragment before
|
|
2214
|
+
drafting a comment. Wire format is unchanged — the picker writes
|
|
2215
|
+
`@<userId>` tokens that the existing `extractMentionedUserIds`
|
|
2216
|
+
parser already understands, so no notification schema migration is
|
|
2217
|
+
required.
|
|
2218
|
+
|
|
2219
|
+
No breaking changes.
|
|
2220
|
+
|
|
2221
|
+
## 0.24.0
|
|
2222
|
+
|
|
2223
|
+
### Minor Changes
|
|
2224
|
+
|
|
2225
|
+
- 4732184: Per-user SSE push channel for the notification bell (Phase 7 carry-over).
|
|
2226
|
+
|
|
2227
|
+
`@actuate-media/cms-core` exposes a new authenticated SSE endpoint:
|
|
2228
|
+
|
|
2229
|
+
GET /api/cms/notifications/stream
|
|
2230
|
+
|
|
2231
|
+
The stream pushes each `comment_reply`, `comment_mention`, and
|
|
2232
|
+
`comment_resolved` notification the instant it's persisted to
|
|
2233
|
+
`DocumentNotification`. The wire format is `event: ready` (once on
|
|
2234
|
+
connect, with the subscribed `userId`) followed by `event:
|
|
2235
|
+
notification` frames carrying the full DTO, plus a 25 s heartbeat
|
|
2236
|
+
keepalive and a 4 min server-initiated close so `EventSource`
|
|
2237
|
+
reconnects before platform max-duration caps fire.
|
|
2238
|
+
|
|
2239
|
+
`@actuate-media/cms-admin` switches `NotificationBell` over to an
|
|
2240
|
+
`EventSource`-backed subscription with the existing REST endpoints as
|
|
2241
|
+
a convergence backstop (the in-process bus is single-node by design;
|
|
2242
|
+
the poller now runs every 60 s instead of 30 s). The new
|
|
2243
|
+
`subscribeNotificationStream` helper is exported for any consumer that
|
|
2244
|
+
wants to drive its own UI off the same channel.
|
|
2245
|
+
|
|
2246
|
+
Both packages bump `minor`: the cms-core change adds a new public
|
|
2247
|
+
route and a new realtime export surface; the cms-admin change adds a
|
|
2248
|
+
new `NotificationsApi.subscribe` member and exports the
|
|
2249
|
+
`NotificationStreamHandle` / `NotificationStreamOptions` /
|
|
2250
|
+
`EventSourceLike` types. No breaking changes — callers who don't pass
|
|
2251
|
+
a custom `api` continue to work unchanged.
|
|
2252
|
+
|
|
2253
|
+
## 0.23.0
|
|
2254
|
+
|
|
2255
|
+
### Minor Changes
|
|
2256
|
+
|
|
2257
|
+
- 21db54b: Phase 7 PR F — MCP-parity audit stylistic cleanup.
|
|
2258
|
+
|
|
2259
|
+
This is the final follow-up to `docs/mcp-parity-audit.md`. After this
|
|
2260
|
+
PR the audit is fully retired — P0, P1, and stylistic notes are all
|
|
2261
|
+
addressed.
|
|
2262
|
+
|
|
2263
|
+
### `@actuate-media/cms-core`
|
|
2264
|
+
|
|
2265
|
+
Adds `GET /api/cms/collections`: a lightweight discovery endpoint
|
|
2266
|
+
sourced directly from `getActuateConfig()`. Returns
|
|
2267
|
+
`{ data: CollectionMeta[] }` with one entry per configured collection
|
|
2268
|
+
(`slug`, `labels`, `type`, `urlPrefix`, `hidden`, `fieldCount`,
|
|
2269
|
+
`fields[{name,type,required}]`). Unauthenticated by design — the
|
|
2270
|
+
response only exposes schema-shape metadata that is already public via
|
|
2271
|
+
`/openapi.json` — and the contract is intentionally tight (only
|
|
2272
|
+
`name` / `type` / `required` per field, no validators, hooks, or
|
|
2273
|
+
access closures leak).
|
|
2274
|
+
|
|
2275
|
+
### `@actuate-media/mcp-server`
|
|
2276
|
+
|
|
2277
|
+
Three audit-mandated stylistic cleanups:
|
|
2278
|
+
1. **AI-create-X collapse (6 tools → 3).** Removed `create_service`,
|
|
2279
|
+
`create_location`, `create_team_member`. Their behaviour is folded
|
|
2280
|
+
into `create_in_collection` via an optional `template` arg
|
|
2281
|
+
(`'service' | 'location' | 'team_member' | 'generic'`) that
|
|
2282
|
+
selects a built-in prompt-enrichment recipe plus a default
|
|
2283
|
+
collection slug. Kept `create_blog_post` and `create_case_study`
|
|
2284
|
+
as type-specific tools because their field-sets are distinctive
|
|
2285
|
+
enough to be standalone agent intents.
|
|
2286
|
+
|
|
2287
|
+
**Breaking change** (acknowledged — pre-1.0, audit-recommended):
|
|
2288
|
+
the three removed tool names will return "tool not found" on next
|
|
2289
|
+
release. Migration is mechanical:
|
|
2290
|
+
- `create_service({ category, ... })`
|
|
2291
|
+
→ `create_in_collection({ template: 'service', category, ... })`
|
|
2292
|
+
- `create_location({ cityState, ... })`
|
|
2293
|
+
→ `create_in_collection({ template: 'location', cityState, ... })`
|
|
2294
|
+
- `create_team_member({ name, role, ... })`
|
|
2295
|
+
→ `create_in_collection({ template: 'team_member', name, role, ... })`
|
|
2296
|
+
|
|
2297
|
+
2. **`audit_document` split.** Replaced with `audit_document_by_id`
|
|
2298
|
+
(requires `collection` + `id`) and `audit_inline_content` (requires
|
|
2299
|
+
`title` + `body`). Both forward to the same `/ai/audit-document`
|
|
2300
|
+
endpoint; the split exists so agents pick the right mode at the
|
|
2301
|
+
tool boundary instead of constructing a half-filled blob and
|
|
2302
|
+
discovering the "must provide either (collection,id) or body" rule
|
|
2303
|
+
via a server-side error.
|
|
2304
|
+
|
|
2305
|
+
**Breaking change** (audit-recommended): `audit_document` is
|
|
2306
|
+
removed. Migration: pick the explicit tool that matches the
|
|
2307
|
+
intent — `audit_document_by_id` for stored docs,
|
|
2308
|
+
`audit_inline_content` for ad-hoc text.
|
|
2309
|
+
|
|
2310
|
+
3. **`list_collections` reliability.** Now prefers the new
|
|
2311
|
+
`/api/cms/collections` endpoint as the primary source so the tool
|
|
2312
|
+
stays accurate when the OpenAPI generator drifts. Falls back to
|
|
2313
|
+
OpenAPI only on a real HTTP 404 (older CMS server) — the decision
|
|
2314
|
+
uses `ActuateClientError.status` (newly exported from the client),
|
|
2315
|
+
not a regex against the error message, so an unrelated server
|
|
2316
|
+
error that happens to contain "not found" in its body no longer
|
|
2317
|
+
triggers the fallback. Non-404 errors propagate.
|
|
2318
|
+
|
|
2319
|
+
**Return-shape change** (consistent contract): `data` is now
|
|
2320
|
+
ALWAYS a `CollectionSummary[]` array regardless of which path
|
|
2321
|
+
responded. The OpenAPI fallback path normalises into the same
|
|
2322
|
+
array shape that the new `/collections` endpoint emits, so
|
|
2323
|
+
callers no longer need to branch on `source` to know whether they
|
|
2324
|
+
got an array or an object map. The returned envelope is
|
|
2325
|
+
`{ data: CollectionSummary[], source: 'collections' | 'openapi' }`.
|
|
2326
|
+
|
|
2327
|
+
**Newly exported:** `ActuateClientError` (subclass of `Error`
|
|
2328
|
+
carrying `status: number`). Useful for any consumer that needs
|
|
2329
|
+
to branch on the HTTP status from a `request()` failure.
|
|
2330
|
+
|
|
2331
|
+
### Tests
|
|
2332
|
+
- mcp-server: 113 tests total (was 97 in PR E). 16 new — template
|
|
2333
|
+
routing for each of service / location / team_member, audit split
|
|
2334
|
+
validation, list_collections primary + fallback + 500-propagation.
|
|
2335
|
+
- cms-core: 4 new tests for the discovery endpoint covering field
|
|
2336
|
+
summaries, `hidden` flag, missing-config 500, and the
|
|
2337
|
+
no-leak-of-validators-or-hooks contract.
|
|
2338
|
+
|
|
2339
|
+
## 0.22.0
|
|
2340
|
+
|
|
2341
|
+
### Minor Changes
|
|
2342
|
+
|
|
2343
|
+
- abc7924: Phase 5.5 #1: tag-based edge caching for public document reads.
|
|
2344
|
+
- **`@actuate-media/client`**: `list`, `get`, `global`, `resolve`, and
|
|
2345
|
+
`listMedia` now automatically attach per-resource cache tags
|
|
2346
|
+
(`actuate:collection:<slug>`, `actuate:doc:<id>`,
|
|
2347
|
+
`actuate:global:<slug>`, `actuate:path:<path>`) alongside the existing
|
|
2348
|
+
wholesale `actuate` tag. New exported helpers `actuateTag(kind, slug)`
|
|
2349
|
+
and `actuateTags(kind, ...)` return the canonical tag set so a
|
|
2350
|
+
webhook handler can call `revalidateTag()` with the exact same
|
|
2351
|
+
strings the SDK uses for reads. The `tags` field on `defaultCache`
|
|
2352
|
+
remains opt-in; resource tags are merged on top of it and deduped.
|
|
2353
|
+
`revalidate: false` (no-store) bypasses tag emission entirely.
|
|
2354
|
+
- **`@actuate-media/cms-core`**: new top-level `cache?: CacheConfig`
|
|
2355
|
+
field on `ActuateCMSConfig`. When `cache.publicReads.enabled` is true
|
|
2356
|
+
the four read endpoints (`GET /collections/:slug`,
|
|
2357
|
+
`GET /collections/:slug/:id`, `GET /globals/:slug`, `GET /resolve`)
|
|
2358
|
+
emit `Cache-Control: public, s-maxage=<maxAge>, stale-while-revalidate=<swr>`
|
|
2359
|
+
for unauthenticated and API-key requests. Session-cookie requests
|
|
2360
|
+
always get `private, no-store`; preview-token resolve requests bypass
|
|
2361
|
+
the cache. New `cache.onInvalidate` hook fires after every
|
|
2362
|
+
create/update/delete and global update with the canonical tag set;
|
|
2363
|
+
failures are swallowed so cache invalidation can never roll back a
|
|
2364
|
+
successful write. The cache layer is off by default — existing
|
|
2365
|
+
consumers see no behaviour change until they opt in. See
|
|
2366
|
+
`docs/caching.md` for the full contract.
|
|
2367
|
+
|
|
2368
|
+
- d0a5275: Phase 5.5 #5 — Vercel Runtime Cache (layer 2) for public reads.
|
|
2369
|
+
|
|
2370
|
+
Adds an optional second cache layer between the CDN tier shipped in
|
|
2371
|
+
Phase 5.5 #1 and the database. When a CDN entry expires, the request
|
|
2372
|
+
reaches the function — and a runtime-cache hit lets the function
|
|
2373
|
+
return in ~10–20 ms instead of paying the full ~80 ms Postgres
|
|
2374
|
+
roundtrip.
|
|
2375
|
+
- New `CacheConfig.runtimeCache` config field — `{ adapter, ttl? }`.
|
|
2376
|
+
The adapter contract (`get` + `set` with TTL + tags) matches Vercel's
|
|
2377
|
+
`getCache()` from `@vercel/functions` so it can be passed through
|
|
2378
|
+
unchanged. Self-hosted projects can plug in any KV store (Upstash,
|
|
2379
|
+
in-memory, Redis) by satisfying the same interface.
|
|
2380
|
+
- New `cachedRead` helper in `packages/cms-core/src/cache/http.ts`.
|
|
2381
|
+
Wraps the four public read endpoints
|
|
2382
|
+
(`GET /collections/:slug`, `GET /collections/:slug/:id`,
|
|
2383
|
+
`GET /globals/:slug`, `GET /resolve`). Stamps responses with
|
|
2384
|
+
`X-Actuate-Cache: HIT|MISS`. Session-cookie requests bypass the
|
|
2385
|
+
layer entirely (per-user data must never enter a shared cache),
|
|
2386
|
+
matching the existing CDN-layer rule.
|
|
2387
|
+
- New `cacheKeyFor` helper — derives cache keys from `pathname` plus
|
|
2388
|
+
sorted query, so `?page=1&pageSize=20` and `?pageSize=20&page=1`
|
|
2389
|
+
share a cache entry while `?populate=author` cleanly separates
|
|
2390
|
+
populated/non-populated reads.
|
|
2391
|
+
- Adapter failures (`get` or `set` throwing) are logged and swallowed.
|
|
2392
|
+
Cache outages degrade silently to a direct DB read; user-visible
|
|
2393
|
+
requests never fail because the cache fell over.
|
|
2394
|
+
- Size guard: payloads > ~1.5 MB skip the `set` call to stay under
|
|
2395
|
+
Vercel's 2 MB runtime-cache item ceiling.
|
|
2396
|
+
|
|
2397
|
+
No public API changes for existing consumers. The new field is
|
|
2398
|
+
optional and the four read endpoints behave identically when it's
|
|
2399
|
+
unset.
|
|
2400
|
+
|
|
2401
|
+
### Patch Changes
|
|
2402
|
+
|
|
2403
|
+
- 1957416: Fix silent production regression introduced in PR #132 (`relationLoadStrategy: 'join'` queries on the dashboard recent-docs panel, document versions list, and `doc_get` bench scenario):
|
|
2404
|
+
- Add the `relationJoins` Preview-feature flag to `packages/cms-core/prisma/schema.prisma` and `apps/dev/prisma/schema.prisma`. Without it Prisma 7 rejects `relationLoadStrategy: 'join'` as an "Unknown argument" — the optimisation never actually fired, the dashboard recent-docs panel silently returned empty (`safeFindMany` swallowed the error), the versions panel returned 500, and `/api/bench` reported 100% errors for `doc_get`.
|
|
2405
|
+
- Stop the `safeCount` / `safeFindMany` / `safeGroupBy` helpers in `api/handlers.ts` from swallowing all errors. They still return the empty value (`0` / `[]`) to keep the request path alive when an optional Prisma model is missing — but now log unexpected errors to `console.warn` so the next time a query is silently failing in production it shows up in Vercel + Sentry logs.
|
|
2406
|
+
- New regression test (`schema-preview-features.test.ts`) pins both `relationJoins` and `fullTextSearchPostgres` on the schema so this class of bug can't recur.
|
|
2407
|
+
|
|
2408
|
+
`apps/dev`: regenerated Prisma client (no source changes — the new flag enables the existing `relationLoadStrategy: 'join'` calls).
|
|
2409
|
+
|
|
2410
|
+
- 93a910a: Phase 5.5 #2 + #3 — cache-miss latency pass.
|
|
2411
|
+
- `populateRelations` / `validateRelations` now batch every relation
|
|
2412
|
+
fetch into a single `RelationLookup.fetchByIds(allCollections, allIds)`
|
|
2413
|
+
call instead of issuing one DB round-trip per target collection.
|
|
2414
|
+
Documents that link into N collections now cost 1 round-trip on
|
|
2415
|
+
populate, not N.
|
|
2416
|
+
- Dashboard recent-docs and version-list queries opt into Prisma 7's
|
|
2417
|
+
`relationLoadStrategy: 'join'`, folding the `createdBy` / `updatedBy`
|
|
2418
|
+
/ `changedBy` user joins into one SQL round-trip. Same shape applied
|
|
2419
|
+
to the benchmarks Prisma provider's `getDocument` scenario so
|
|
2420
|
+
`/api/bench` measures the new geometry.
|
|
2421
|
+
- `apps/dev` API routes pin `preferredRegion = 'iad1'` so the deployed
|
|
2422
|
+
Vercel Function co-locates with the Prisma Postgres tier the
|
|
2423
|
+
Marketplace integration provisions in the same region. Override
|
|
2424
|
+
documented in `docs/deployment.md`.
|
|
2425
|
+
|
|
2426
|
+
- 0f50407: Security: bump `sanitize-html` from 2.17.3 to 2.17.4 to fix a default XSS via raw-text `<xmp>` passthrough (GHSA-vjp9-mfvj-w4qg, CRITICAL). The advisory only fires when content is rendered with the default `allowedTags`; cms-core's bundled defaults are unaffected, but the patch closes the entire class of bypasses for any downstream caller that uses the bundled sanitizer.
|
|
2427
|
+
|
|
2428
|
+
## 0.21.0
|
|
2429
|
+
|
|
2430
|
+
### Minor Changes
|
|
2431
|
+
|
|
2432
|
+
- 1675eac: Add component-aware blocks — Phase 4 foundation slice.
|
|
2433
|
+
|
|
2434
|
+
Ships the new `@actuate-media/component-blocks` package, which reads a directory of React component source files and emits a typed `Manifest` describing every exported component's props (`string` / `number` / `boolean` / `enum` / `union` / `object` / `array`). Same-file type aliases are resolved automatically. Includes a CLI (`actuate-component-blocks extract --root <dir> --out <file>`) and a programmatic `extractManifest()` API.
|
|
2435
|
+
|
|
2436
|
+
Adds the `componentBlock` field type to `@actuate-media/cms-core`, which consumes a manifest and validates stored block values — rejecting unknown components, missing required props, wrong-type values, and out-of-set enum picks. Field-level `allow` and `defaultComponent` options are supported.
|
|
2437
|
+
|
|
2438
|
+
This is the foundation slice; admin form generation, migration-diff CLI, and discriminated-union pickers ship in follow-up PRs. See `docs/component-blocks.md`.
|
|
2439
|
+
|
|
2440
|
+
## 0.20.0
|
|
2441
|
+
|
|
2442
|
+
### Minor Changes
|
|
2443
|
+
|
|
2444
|
+
- 8f2d3f8: Redesign the admin Dashboard and fix the recurring "content cut off on the left" layout bug.
|
|
2445
|
+
|
|
2446
|
+
**Dashboard (`@actuate-media/cms-admin`)**
|
|
2447
|
+
- New `Dashboard.tsx` matching the production design: time-of-day greeting, hero action row (New Post / View Site), horizontally-scrollable quick actions, five stat cards, two-column main grid (Recent Activity + Publishing Queue + Content Health), and a four-tile Content Delivery footer (Total Documents, Scheduled, Form Responses, Active Webhooks).
|
|
2448
|
+
- All data derived from the existing `/stats` and `/health` endpoints — no additional network round-trips. Recent activity, publishing queue, content health, and delivery tiles are computed client-side via `useMemo` so the backend never blocks the frontend.
|
|
2449
|
+
- Fully responsive: stat cards collapse from 5 → 3 → 2 columns at `lg` / `sm` / mobile breakpoints, the main grid stacks below `lg`, and quick actions scroll horizontally on narrow screens.
|
|
2450
|
+
- Deterministic per-author avatar colors (hash-based) so the activity feed is stable across renders without state or extra fetches.
|
|
2451
|
+
|
|
2452
|
+
**Layout cutoff fix (`@actuate-media/cms-admin`)**
|
|
2453
|
+
- Added `lg:shrink-0` to the desktop Sidebar wrapper. Without it, on viewports between `lg` (1024px) and ~1280px the flex sidebar column could compress below the Sidebar's intrinsic 256px width, causing dashboard content to overflow into and visually overlap the sidebar. Combined with the existing `min-w-0` on the main column, the row now reliably renders as `[256px Sidebar][1fr Main]` at every desktop width.
|
|
2454
|
+
- Dashboard is wrapped in `max-w-[1400px] mx-auto w-full` so on ultra-wide displays the content stays in a comfortable reading column instead of stretching edge-to-edge.
|
|
2455
|
+
|
|
2456
|
+
**Stats endpoint extension (`@actuate-media/cms-core`)**
|
|
2457
|
+
- `GET /stats` now returns `webhookCount` and `webhookActiveCount`, calculated via two cheap `safeCount` calls in the existing `Promise.allSettled` batch. The dashboard's "Active Webhooks" tile uses these instead of issuing a second request, keeping page load to a single stats fetch.
|
|
2458
|
+
|
|
2459
|
+
- 0ac9024: Inline AI co-author HTTP endpoint + MCP tool (Phase 2).
|
|
2460
|
+
|
|
2461
|
+
Adds `POST /api/cms/ai/coauthor` with four sub-actions (`rewrite`,
|
|
2462
|
+
`expand`, `compress`, `proofread`) that wrap the plugin-ai writing
|
|
2463
|
+
primitives behind a single endpoint. Rate-limited under the same
|
|
2464
|
+
bucket as content generation (20 calls/hour/user). Missing provider
|
|
2465
|
+
configuration surfaces as HTTP 501 instead of 500.
|
|
2466
|
+
|
|
2467
|
+
Exposed to AI agents via the MCP tool `coauthor_text` with the same
|
|
2468
|
+
input shape, so the admin UI and AI agents can drive identical
|
|
2469
|
+
text-transformation workflows.
|
|
2470
|
+
|
|
2471
|
+
Tests: 5 endpoint tests covering each action + validation errors.
|
|
2472
|
+
|
|
2473
|
+
- 5d18097: Wire per-type SEO defaults into the admin Settings panel — the last loose end from Slice A (SEO Completeness).
|
|
2474
|
+
|
|
2475
|
+
**`cms-core`**
|
|
2476
|
+
- New `seo/config-store` module persists admin edits in a single reserved-slug document (`__seo_config__`) and merges them on top of the static `actuate.config.ts` `seo` block at runtime, field-by-field.
|
|
2477
|
+
- New endpoints `GET /seo/config` and `PUT /seo/config` (admin-only). Saving uses partial-update semantics so editing the site-wide section never wipes per-collection overrides (and vice versa).
|
|
2478
|
+
- `/sitemap.xml`, `/sitemaps/:slug.xml`, `/robots.txt`, `/og.png`, and `/resolve` now load the merged config per request via `loadEffectiveConfig()`, so admin changes are live immediately with no redeploy.
|
|
2479
|
+
- Empty strings in PUT payloads are treated as "unset" (fall back to static config) — the panel is purely additive.
|
|
2480
|
+
|
|
2481
|
+
**`cms-admin`**
|
|
2482
|
+
- New **Settings → SEO** tab backed by `<SEOConfigPanel />`. Edits site-wide defaults (siteUrl, siteName, OG image, Twitter handle, change-freq, priority, robots toggles, AI-bot blocking) and per-collection defaults (Schema.org type, archive path, sitemap priority/changefreq, noindex/nofollow, exclude-from-sitemap).
|
|
2483
|
+
- Static `actuate.config.ts` values render as input placeholders so admins always see what they're overriding.
|
|
2484
|
+
|
|
2485
|
+
12 new unit tests cover the merge semantics, unset behaviour, DB roundtrip, upsert (no duplicate rows), and DB-error fallback.
|
|
2486
|
+
|
|
2487
|
+
- 313a23f: Add Slice F — AI quality pass.
|
|
2488
|
+
|
|
2489
|
+
`plugin-ai` exposes a new `auditDocument()` composer that combines SEO,
|
|
2490
|
+
readability (Flesch-Kincaid / Gunning Fog / SMOG averaged + remapped to a
|
|
2491
|
+
0-100 score), accessibility, and freshness scoring into a single
|
|
2492
|
+
composite grade with prioritized actionable issues. The composer is
|
|
2493
|
+
deterministic — it never calls an LLM — so it's cheap enough to invoke
|
|
2494
|
+
on every save or debounced keystroke.
|
|
2495
|
+
|
|
2496
|
+
`cms-core` adds two HTTP endpoints:
|
|
2497
|
+
- `POST /ai/audit-document` — runs the composite audit against a stored
|
|
2498
|
+
document (`{ collection, id }`) or ad-hoc content. Honors field-level
|
|
2499
|
+
access control when loading from the DB.
|
|
2500
|
+
- `POST /ai/suggest-internal-links` — scans published documents for
|
|
2501
|
+
phrases the supplied content could link to, returning ranked
|
|
2502
|
+
anchor-text + target-URL suggestions. Optionally scopes to a single
|
|
2503
|
+
collection.
|
|
2504
|
+
|
|
2505
|
+
`mcp-server` exposes both endpoints as MCP tools (`audit_document` and
|
|
2506
|
+
`suggest_internal_links`) so AI agents can grade drafts and propose
|
|
2507
|
+
internal links before publish.
|
|
2508
|
+
|
|
2509
|
+
Docs: `docs/ai-quality-audit.md`.
|
|
2510
|
+
|
|
2511
|
+
## 0.19.0
|
|
2512
|
+
|
|
2513
|
+
### Minor Changes
|
|
2514
|
+
|
|
2515
|
+
- 39f5941: Slice E — Preview & Publishing Polish.
|
|
2516
|
+
|
|
2517
|
+
**Draft preview links**
|
|
2518
|
+
- Preview tokens now accept a configurable `ttlSeconds` (clamped to
|
|
2519
|
+
`[60, 2,592,000]`, default 7 days). The old 5-minute hard-coded TTL
|
|
2520
|
+
was too short for sharing with clients.
|
|
2521
|
+
- `POST /preview/token` now verifies the document exists before
|
|
2522
|
+
issuing a token, includes the doc's `slug` in the response so the
|
|
2523
|
+
admin can build a friendly URL, and records the issuing user in the
|
|
2524
|
+
JWT `sub` claim for audit logs.
|
|
2525
|
+
- `GET /resolve?preview=<token>` drops the `status: PUBLISHED` filter
|
|
2526
|
+
when a valid token is presented, returning draft content at the
|
|
2527
|
+
document's real URL. The response includes a `preview: { active,
|
|
2528
|
+
expiresAt }` block so frontends can render a "preview mode" banner.
|
|
2529
|
+
Tokens are bound to a single `(collection, documentId)` — using one
|
|
2530
|
+
against a different URL returns `403`.
|
|
2531
|
+
|
|
2532
|
+
**Scheduled publishing**
|
|
2533
|
+
- New `POST /collections/:slug/:id/schedule` endpoint sets
|
|
2534
|
+
`scheduledAt` and/or `scheduledUnpublishAt` (validated as future
|
|
2535
|
+
ISO 8601 timestamps with `unpublish > publish`) and flips status to
|
|
2536
|
+
`SCHEDULED`. The existing scheduling cron picks the doc up at the
|
|
2537
|
+
scheduled time.
|
|
2538
|
+
- New `DELETE /collections/:slug/:id/schedule` cancels a pending
|
|
2539
|
+
schedule. `SCHEDULED` docs revert to `DRAFT`; `PUBLISHED` docs with
|
|
2540
|
+
a pending unpublish stay live.
|
|
2541
|
+
|
|
2542
|
+
**Admin UI**
|
|
2543
|
+
- New **Share preview** toolbar button on `DocumentEdit` opens a
|
|
2544
|
+
dialog to generate signed preview URLs with selectable TTL
|
|
2545
|
+
(1 hour / 1 day / 7 days / 30 days) and a copy-to-clipboard control.
|
|
2546
|
+
- New **Schedule** button in the Status panel opens a datetime picker
|
|
2547
|
+
for setting both publish and (optionally) unpublish times. Active
|
|
2548
|
+
schedules render an inline blue pill with the times. "Reschedule"
|
|
2549
|
+
and "Cancel schedule" supported.
|
|
2550
|
+
- New exported components: `SharePreviewLinkDialog`,
|
|
2551
|
+
`SchedulePublishDialog`.
|
|
2552
|
+
|
|
2553
|
+
**Tests & docs**
|
|
2554
|
+
- 20 new tests: preview adapter TTL clamping, JWT round-trip, secret
|
|
2555
|
+
isolation; `/preview/token` + `/resolve?preview=` happy/sad paths;
|
|
2556
|
+
schedule create/cancel/validation paths.
|
|
2557
|
+
- New `docs/preview-and-scheduling.md` walkthrough; `docs/api-reference.md`
|
|
2558
|
+
expanded with the new endpoints.
|
|
2559
|
+
|
|
2560
|
+
## 0.18.0
|
|
2561
|
+
|
|
2562
|
+
### Minor Changes
|
|
2563
|
+
|
|
2564
|
+
- 4433301: Phase 2 Slice D — Content Relations & References.
|
|
2565
|
+
- **cms-core**: runtime support for the `relationship` field type — referential validation and opt-in depth-1 population.
|
|
2566
|
+
- **Validation**: every create/update verifies that referenced IDs point at live (non-deleted) documents in the declared target collection(s). Broken references fail with a precise error (`Relation field "author" references document "team-x42" which does not exist in collection "team".`). Updates only re-validate fields the caller actually touched.
|
|
2567
|
+
- **Population**: pass `?populate=author,relatedPosts` (or `*`) on `GET /collections/:slug/:id` and `GET /resolve` to expand stored IDs into `{ id, collection, title, slug, status, data }` summaries. Batches by target collection (≤ 1 query per collection per request); soft-deleted refs fall back to raw IDs.
|
|
2568
|
+
- **Presets**: new `relationField()` and `relatedDocsField()` helpers. Built-in collection presets now wire `posts.relatedPosts → posts`, `case-studies.services → services` (the `locations.servicesOffered → services` relation continues unchanged).
|
|
2569
|
+
- **Multi-target**: `relationTo: ['posts', 'case-studies']` continues to work — validator accepts a doc if it lives in any target.
|
|
2570
|
+
- Public API: `validateRelations`, `populateRelations`, `collectRelationRefs`, `parsePopulateParam`, `buildRelationLookup`, plus types `RelationLookup`, `RelationSummary`, `PopulateOptions`.
|
|
2571
|
+
- **plugin-ai**: the document content generator now skips `relationship` fields when building the LLM prompt (the model can't know your document IDs) and accepts ID strings only on output. Prevents the AI from inventing broken references that the validator would then reject.
|
|
2572
|
+
- **23 new tests** in `cms-core/src/__tests__/fields/relations.test.ts` covering ref collection, validation, population, populate-param parsing, multi-target relations, and array-nested relations. Full suite: 620 cms-core tests passing.
|
|
2573
|
+
- **Docs**: new `docs/relations.md` (declaration, validation, populate, admin UI, common patterns), `?populate=` documented in `docs/api-reference.md` for both `/collections/:slug/:id` and `/resolve`.
|
|
2574
|
+
- **Admin UI**: the existing `RelationshipField` picker continues to work — search, multi-select, status badges, "Create new" shortcut. No changes required.
|
|
2575
|
+
|
|
2576
|
+
## 0.17.0
|
|
2577
|
+
|
|
2578
|
+
### Minor Changes
|
|
2579
|
+
|
|
2580
|
+
- e6a18b5: Phase 2 Slice C — AI Content Authoring for any collection.
|
|
2581
|
+
- **plugin-ai**: new `generateDocumentContent()` helper that maps a prompt onto a collection's field schema and returns structured field data ready to persist. Supports tone, target audience, brand voice profile, and existing-content context. Coerces LLM output to declared field types (numbers, booleans, dates, select options, array items) and drops fields the schema doesn't declare.
|
|
2582
|
+
- **cms-core**: new `POST /collections/:slug/ai-create` endpoint — the non-page counterpart to `/page-builder/create`. Generates a complete document for any collection, persists as draft, optionally publishes via a follow-up update when the caller has admin scope. Refuses page-builder collections (use `/page-builder/create` instead). Hard caps prompt at 4000 chars and shares the per-user 20/hour AI rate-limit bucket. Returns a `warning` field when create succeeds but publish is denied.
|
|
2583
|
+
- **mcp-server**: six new tools — `create_in_collection` (generic) plus per-type sugar tools `create_blog_post`, `create_case_study` (with `clientName` / `industry` hints), `create_service` (`category` hint), `create_location` (`cityState` hint), and `create_team_member` (`name` / `role` hints). Each per-type tool targets the conventional collection slug shipped by the cms-core collection presets, with an explicit `collection` override for sites using custom slugs.
|
|
2584
|
+
- 19 new tests (10 unit tests in plugin-ai for the content generator, 9 integration tests in cms-core for the API endpoint covering auth, validation, persistence, publish flow, and the page-builder refusal path).
|
|
2585
|
+
- Docs: updated `docs/api-reference.md` with the new endpoint, `docs/mcp.md` and `packages/mcp-server/README.md` with the new tools and example agent prompts.
|
|
2586
|
+
|
|
2587
|
+
## 0.16.0
|
|
2588
|
+
|
|
2589
|
+
### Minor Changes
|
|
2590
|
+
|
|
2591
|
+
- 9377c39: **Post-type Polish (Phase 2, Slice B)** — one-line content types for the eight patterns nearly every marketing site needs, plus matching page-builder starter templates.
|
|
2592
|
+
- **Collection presets** — `blogPostCollection()`, `caseStudyCollection()`, `portfolioCollection()`, `serviceCollection()`, `locationCollection()`, `teamMemberCollection()`, `faqCollection()`, `testimonialCollection()`. Each returns a complete `CollectionDefinition` with sensible fields, admin defaults, `seo.defaultSchemaType` set to the right Schema.org type (BlogPosting / Article / CreativeWork / Service / LocalBusiness / Person / FAQPage / Review), archive paths for post-type collections, sitemap priorities, and admin grouping. All accept `CollectionPresetOverrides` (extraFields, seo merge, label / slug / urlPrefix overrides).
|
|
2593
|
+
- **Field presets** — reusable bundles you can spread into any collection: `titleField`, `slugField`, `excerptField`, `richContentField`, `featuredImageField`, `tagsField`, `publishedDateField`, `authorField`, `quoteField`, `ctaField`, `seoFields`, `locationFields`, `geoFields`, `priceFields`.
|
|
2594
|
+
- **Four new page-builder templates** — `case-study` (hero / metrics / challenge / solution / results / quote / CTA), `portfolio` (cover / info strip / description / gallery / CTA), `service` (hero with CTA / description / benefits / pricing / FAQ), `team-member` (avatar hero / bio / contact links). Joins the existing `blank`, `landing`, `about`, `contact`, `location`, and `blog-post` templates.
|
|
2595
|
+
- **Per-collection template suggestion** — `GET /api/cms/page-templates?collection=:slug` now reorders the response so suggested templates bubble to the top, annotates each template with `suggested` + `suggestionRank`, and returns a `suggestion` object pointing at the primary template ID. `getTemplatesForCollection('posts')` exposes the mapping in code.
|
|
2596
|
+
- **33 new tests** (16 unit field-preset + 17 unit collection-preset + suggestion lookup). Full suite: 588 passed.
|
|
2597
|
+
- **Docs updated** — `docs/configuration.md` adds the Collection Presets, Field Presets, and Page-builder Templates per Type sections.
|
|
2598
|
+
|
|
2599
|
+
## 0.15.0
|
|
2600
|
+
|
|
2601
|
+
### Minor Changes
|
|
2602
|
+
|
|
2603
|
+
- 00ffcf7: **SEO Completeness (Phase 2, Slice A)** — production-grade SEO surfaces wired into the core CMS with zero extra plugins.
|
|
2604
|
+
- **New `composePageMeta()` helper** — One-shot SEO composer: pass a resolved document, its collection, and the CMS config and get back a Next.js-ready `metadata` object, computed JSON-LD (Article / BlogPosting / LocalBusiness / Service / etc.), pre-rendered `<meta>` / `<script>` HTML, and the canonical URL. Auto-detects Schema.org type from collection slug (`posts → BlogPosting`, `locations → LocalBusiness`, `services → Service`, …) and respects per-collection `defaultSchemaType` / `defaultRobots` / `defaultOgImage` overrides.
|
|
2605
|
+
- **`/resolve` now returns `meta`, `jsonLd`, and `jsonLdHtml`** — SSR consumers (Next.js apps, AI agents, MCP clients) get a ready-to-render meta tag block and JSON-LD `<script>` tag without re-deriving anything from raw doc data.
|
|
2606
|
+
- **Public sitemap + robots + OG routes** — `GET /sitemap.xml`, `GET /sitemaps/:slug.xml`, `GET /robots.txt`, and `GET /og.png` are served automatically from the CMS handler once `seo.siteUrl` is set. Each route can be disabled individually (`seo.sitemap.disabled`, `seo.robots.disabled`, `seo.ogImage.disabled`).
|
|
2607
|
+
- **Per-collection SEO config** — `CollectionDefinition.seo` now accepts `defaultSchemaType`, `defaultRobots`, `defaultOgImage`, in addition to the existing `sitemapPriority` / `sitemapChangeFreq` / `excludeFromSitemap` / `archivePath`.
|
|
2608
|
+
- **Site-wide SEO config** — New top-level `seo` block on `ActuateCMSConfig` for `siteUrl`, `siteName`, `defaultOgImage`, `twitterHandle`, `organization`, `robots`, `sitemap`, and `ogImage` defaults.
|
|
2609
|
+
- **AI-bot blocking** — `seo.robots.blockAIBots: true` adds disallow rules for GPTBot, ChatGPT-User, ClaudeBot, Claude-Web, anthropic-ai, Bytespider, CCBot, and Google-Extended.
|
|
2610
|
+
- **27 new tests** covering `composePageMeta()` (15) and the public SEO routes (12).
|
|
2611
|
+
- **Updated docs** — `docs/seo.md` adds the zero-config-surfaces section and `composePageMeta` walkthrough; `docs/api-reference.md` documents the new public routes and the enriched `/resolve` response.
|
|
2612
|
+
|
|
2613
|
+
## 0.14.0
|
|
2614
|
+
|
|
2615
|
+
### Minor Changes
|
|
2616
|
+
|
|
2617
|
+
- e87471d: Phase 1: AI access. API key authentication (act*sk*\* bearer tokens) with scope-based authorization across collections, globals, media, and the page builder. New /api-keys admin endpoints + UI for creating, listing, and revoking keys with password reauth. CSRF is skipped for API-key requests. New /page-builder/create one-shot endpoint that generates an AI page and persists it as a document in a single call. New @actuate-media/mcp-server package exposes the CMS to AI agents (Claude, Cursor, ChatGPT, etc.) via the Model Context Protocol — full CRUD, globals, media, page generation, and one-shot create tools. Adds 44 new tests covering the API key auth path, scope enforcement, CSRF skip, and MCP client/tools.
|
|
2618
|
+
|
|
2619
|
+
## 0.13.0
|
|
2620
|
+
|
|
2621
|
+
### Minor Changes
|
|
2622
|
+
|
|
2623
|
+
- 9cf5d10: Engineering audit — medium and low items.
|
|
2624
|
+
|
|
2625
|
+
**New features**
|
|
2626
|
+
- `validateEnvShape()` (`diagnostics/env.ts`): runtime check that `CMS_SECRET`, `CMS_ENCRYPTION_KEY`, `DATABASE_URL`, `BLOB_READ_WRITE_TOKEN`, `UPSTASH_REDIS_*`, `RESEND_API_KEY`, and `CRON_SECRET` are not just present but well-formed (length, hex, placeholder detection). Surfaced via `/api/cms/health` so misconfiguration is caught at deploy time, not at first runtime use. Adds an `unhealthy` status when any required var is malformed.
|
|
2627
|
+
- `createLogger(scope)` (`diagnostics/logger.ts`): structured per-scope logger that respects `ACTUATE_LOG_LEVEL` (`silent`/`error`/`warn`/`info`/`debug`) and `ACTUATE_LOG_FORMAT=json` for log-aggregator ingestion. Defaults to `warn` in production, `info` elsewhere.
|
|
2628
|
+
- `getActuateConfig()` / `setActuateConfig()` (`config/runtime.ts`): typed accessor for the per-process CMS config, replacing scattered `(globalThis as any).__actuateConfig` casts. Documents the single-process / set-once contract in detail.
|
|
2629
|
+
|
|
2630
|
+
**Hardening**
|
|
2631
|
+
- `/media/upload` now requires a valid `Content-Length` header before parsing the body, closing a chunked-encoding bypass that allowed unbounded memory consumption (audit M7).
|
|
2632
|
+
- In-memory rate limiter (`createInMemoryRateLimiter`) now has a 10,000-key LRU cap with periodic expired-entry sweeping. Prevents unbounded `Map` growth under enumeration / IP-spoofing (audit M1).
|
|
2633
|
+
- Upstash rate-limiter outages now emit a throttled `rate_limit.degraded` audit log entry in addition to the existing `console.error`, giving operators a durable record of when the limiter was failing open (audit M2).
|
|
2634
|
+
|
|
2635
|
+
**Schema**
|
|
2636
|
+
- `AuthConfig` now properly types `maxConcurrentSessions`, `passwordPolicy`, and `oauth.{providers, allowSelfSignup}` — these were already consumed at runtime but cast through `any`.
|
|
2637
|
+
- `ActuateCMSConfig.redirects.allowedExternalHosts` is now part of the public type.
|
|
2638
|
+
|
|
2639
|
+
**Bug fixes**
|
|
2640
|
+
- Removed dead `config.plugins.forms.hooks` access in the form-submission handler — `plugins` is an array, not an object keyed by name, so this path always evaluated to `undefined` and was masked by the `as any` cast.
|
|
2641
|
+
- `/api/cms/health` now derives the response status from env validation in **both** the success and the DB-failure paths (Bugbot review on PR #41, Medium). The early-return on `db()` failure previously hardcoded `status: 'degraded'`, so a deploy with a broken DB _and_ a malformed `CMS_SECRET` would report `degraded` instead of the more accurate `unhealthy` — defeating the M6 goal of catching env misconfig at `/health` time.
|
|
2642
|
+
- `validateEnvShape()` "ok" messages no longer disclose exact secret lengths (Bugbot review on PR #41, Low). The previous wording — `Configured (42 chars).` — leaked configured `CMS_SECRET` and `CRON_SECRET` lengths via the unauthenticated `/health` endpoint, narrowing brute-force search space with no operational benefit. Lengths still appear in error/warn messages because those only surface on misconfiguration that the operator must fix.
|
|
2643
|
+
- Removed dead exported `log` singleton from `diagnostics/logger.ts` (Bugbot review on PR #41, Low). Every consumer uses `createLogger(scope)`; the convenience export was never imported.
|
|
2644
|
+
- Removed unreachable `isPlaceholder(encKey)` branch in `validateEnvShape()` (Bugbot review on PR #41, Low). After the `length === 64` and `HEX_RE` checks pass, the value is guaranteed pure hex characters, but every pattern in `PLACEHOLDER_PATTERNS` requires non-hex letters — so the branch could never fire. The `HEX_RE` failure message already reads "Looks like a placeholder" verbally, which covers the common case (e.g. `aes256-local-dev-key-change-in-prod`).
|
|
2645
|
+
- `/api/cms/health` now consults all three legitimate `CMS_SECRET` sources via a wrapped `EnvSource` passed to `validateEnvShape()` — `process.env.CMS_SECRET`, the legacy `process.env.CMS_SESSION_SECRET`, and `getActuateConfig()?.secret` (Bugbot review on PR #43, Medium). Previously the validator only inspected `process.env.CMS_SECRET`, so a deploy that configured the secret via `actuate.config.ts` got the contradictory response `secretConfigured: true` AND `status: "unhealthy"` — false alarm that would trip Kubernetes readiness probes / load-balancer health checks. The validator's `'missing'` message now also names the config-file path so the diagnostic matches `getSessionSecret()`'s own behavior.
|
|
2646
|
+
|
|
2647
|
+
**Documentation**
|
|
2648
|
+
- `README.md`: filled in with project overview, repo layout, dev quickstart, Windows long-path setup instructions, env-var reference, and release flow.
|
|
2649
|
+
- `.cursor/rules/security.mdc`: corrected to say `sanitize-html` (matches code) instead of `isomorphic-dompurify`.
|
|
2650
|
+
- `.cursor/rules/architecture.mdc`: documents the `getActuateConfig()` accessor and forbids direct `globalThis` reads.
|
|
2651
|
+
|
|
2652
|
+
**Tests** (+ 80 net new tests; 525 total passing across cms-core)
|
|
2653
|
+
- `__tests__/auth/oauth.test.ts` (29): PKCE primitives, state JWT round-trip, nonce binding, provider mismatch rejection, password-protected account refusal, self-signup gating, OAuthAccount-based linking, session creation.
|
|
2654
|
+
- `__tests__/auth/reset.test.ts` (17): single-use tokens, expiry, deactivated-user rejection, password policy enforcement, atomic update + session revocation, replay protection.
|
|
2655
|
+
- `__tests__/diagnostics/env.test.ts` (12): shape validation across every required and optional env var, plus 3 parametrized regression cases asserting "ok" messages do not include exact lengths for `CMS_SECRET`, `CMS_ENCRYPTION_KEY`, or `CRON_SECRET`.
|
|
2656
|
+
- `__tests__/diagnostics/logger.test.ts` (7): level filtering, JSON output, prefix formatting.
|
|
2657
|
+
- `__tests__/security/rate-limit.test.ts` (+2): LRU cap eviction and most-recently-used preservation.
|
|
2658
|
+
- `__tests__/api/health.test.ts` (5): `/health` returns `unhealthy` (not `degraded`) when env is broken AND db throws; `/health` returns `degraded` when only db throws and env is fine; `/health` JSON contains no `\d+\s*chars?` pattern (length-leak regression); `/health` does not report `unhealthy` when the secret is supplied via `CMS_SESSION_SECRET` (legacy env); `/health` does not report `unhealthy` when the secret is supplied via `actuate.config.ts → secret` (config-file path).
|
|
2659
|
+
|
|
2660
|
+
**Internal**
|
|
2661
|
+
- `turbo.json`: explicit `outputs: []` for `@actuate-media/update-server#build` (it runs `tsc --noEmit`, no artifacts).
|
|
2662
|
+
- Migrated `console.*` calls in `rate-limit`, `audit`, and `oauth` to the new structured logger.
|
|
2663
|
+
- Replaced `@ts-ignore` with `@ts-expect-error` on the streaming `RequestInit` cast.
|
|
2664
|
+
|
|
2665
|
+
## 0.12.0
|
|
2666
|
+
|
|
2667
|
+
### Minor Changes
|
|
2668
|
+
|
|
2669
|
+
- 370fa95: Security hardening (engineering-audit High items):
|
|
2670
|
+
- **SSRF DNS-rebinding defense**: `safeFetch` now resolves the hostname before fetching and rejects when any A/AAAA record lands in a private range. Previously the URL was only validated as written, so an attacker controlling DNS for `evil.tld` could return a public IP at validation time and `127.0.0.1` at fetch time.
|
|
2671
|
+
- **SSRF IP canonicalization rewrite**: replaced regex-based hostname matching in `validateWebhookUrl` with a proper IP canonicalizer (`security/ip-canon.ts`). Now blocks every encoding `getaddrinfo` accepts — decimal (`http://2130706433`), octal (`http://0177.0.0.1`), hex (`http://0x7f.0.0.1`), short-form (`http://127.1`), IPv4-mapped IPv6 (`http://[::ffff:127.0.0.1]`), bracketed IPv6, and the carrier-grade NAT range (`100.64.0.0/10`). Cloud-metadata hostnames (`metadata.google.internal`, `*.internal`) are also rejected explicitly.
|
|
2672
|
+
- **AES-256 key validation**: `encryptField` / `decryptField` now throw `InvalidEncryptionKeyError` at the call site if `CMS_ENCRYPTION_KEY` isn't exactly 64 hex chars. Previously a misconfigured key (e.g. the placeholder `aes256-local-dev-key-change-in-prod`) only surfaced as an opaque WebCrypto error the first time a field was written — late, in production, hard to diagnose.
|
|
2673
|
+
- **JWT payload shape validation**: `verifySession` and OAuth `verifyState` now assert the decoded payload has the expected fields (`userId`, `role`, `sessionId`) and strip extra attacker-supplied claims before returning. Throws `InvalidSessionPayloadError` / `InvalidOAuthStateError` on malformed payloads.
|
|
2674
|
+
- **Login user-enumeration timing defense**: when the email doesn't exist OR the account is OAuth-only, the login handler now still runs PBKDF2 against a dummy hash before returning the error. Previously the response-time delta let attackers enumerate valid emails.
|
|
2675
|
+
- **PBKDF2 strength bump**: hash iterations raised from 100,000 → 600,000 (OWASP / NIST 2023+ guidance for PBKDF2-HMAC-SHA256). The hash format embeds the iteration count, so old 100k hashes still verify; `verifyPassword` reads the count from the stored string. Login opportunistically re-hashes weak stored hashes after a successful sign-in.
|
|
2676
|
+
- **Cron endpoints**: added `/api/cms/cron/publish`, `/api/cms/cron/cleanup`, `/api/cms/cron/seo-scan` matching the documented Vercel cron paths. Auth is via `Authorization: Bearer ${CRON_SECRET}` (constant-time compare, fail-closed when the env var is unset). Cleanup hard-deletes expired sessions, old audit logs (90d), trashed documents (30d), and used password reset tokens (1d).
|
|
2677
|
+
|
|
2678
|
+
New tests: 99 added across `auth/password`, `auth/session`, `security/encrypted-fields`, `security/ssrf`, `security/safe-fetch`, and `cron/`.
|
|
2679
|
+
|
|
2680
|
+
### Patch Changes
|
|
2681
|
+
|
|
2682
|
+
- 370fa95: Security follow-ups from the Bugbot review of PR #40 (engineering-audit High items):
|
|
2683
|
+
- **SSRF — malformed IP literals now fail closed.** `canonicalizeHostname()` previously returned `{isHostname: false}` for IP-shaped strings that failed to parse (e.g. `::1::1`, `300.0.0.1`) without setting `ipv4`/`ipv6`. `validateWebhookUrl` and `safeFetch.resolveAndCheck` then treated that as "valid public IP, no DNS lookup needed" and let the request through. The canonicalizer now exposes an explicit `isValidIp` flag and the SSRF callsites reject malformed IP literals up front.
|
|
2684
|
+
- **Login timing — dummy PBKDF2 hash is now eager.** `compareToDummyHash` previously initialized the dummy hash lazily on first call, so the first unknown-email login after a cold start was ~1s slower than subsequent ones, partially defeating the timing-side-channel defense added in the original High fix. The dummy hash is now precomputed at module load (top-level Promise) and `compareToDummyHash` awaits it.
|
|
2685
|
+
- **Cron auth — constant-time compare no longer leaks secret length.** `isAuthorizedCronRequest` compared the bearer token to `CRON_SECRET` after an early `if (a.length !== b.length) return false`, which made the response time depend on the submitted token's length. Replaced with HMAC-SHA256 of both inputs (always 32 bytes) compared via `node:crypto.timingSafeEqual` against a per-process random key.
|
|
2686
|
+
- **Cron endpoints — accept GET (Vercel Cron) in addition to POST.** Vercel Cron sends GET requests by default. The original implementation registered the cron paths as POST-only, so every Vercel-scheduled invocation would have returned 404/405 and silently skipped. Endpoints are now registered for both GET and POST so Vercel Cron and self-hosted POST schedulers (k8s CronJob, GH Actions `curl -X POST`, EventBridge) both work.
|
|
2687
|
+
- **Cron `modelExists` correctly rejects null delegates.** The guard used `typeof db[name] === 'object'`, which is `true` for `null` (the historical `typeof null === 'object'` quirk). With `{ session: null }` the guard returned true and the subsequent `db.session.deleteMany(...)` threw a TypeError that the outer try/catch only papered over. Now explicitly checks `delegate !== null`.
|
|
2688
|
+
- **Cron SEO scan H1 detection is case-insensitive.** The image-missing-alt scan uses `/gi`, but the H1-presence check used `content.includes('<h1')`, so documents with valid `<H1>` headings were falsely flagged as missing. Replaced with `/<h1\b/i.test(content)` to match the HTML spec and the rest of the function's behavior.
|
|
2689
|
+
- **Cron SEO scan alt-text check is case-insensitive.** Sibling bug to the H1 fix above: the `<img>` regex was `/gi` (case-insensitive), but the `alt=` substring test inside the filter was case-sensitive (`img.includes('alt=')`), so `<IMG ALT="text">` matched the regex and was then falsely counted as missing alt text. Replaced with `/\balt\s*=/i.test(img)` so all uppercase / mixed-case attribute spellings are recognized.
|
|
2690
|
+
|
|
2691
|
+
New tests:
|
|
2692
|
+
- `security/ssrf.test.ts` — 9 cases covering malformed IPv4/IPv6 literals, embedded-null hostnames, oversize labels, and `validateWebhookUrl`/`resolveAndCheck` rejecting them.
|
|
2693
|
+
- `auth/password.test.ts` — regression assertion that the first `compareToDummyHash` call is not significantly slower than later calls.
|
|
2694
|
+
- `cron/cron.test.ts` — 5 cases covering varying-length `CRON_SECRET` attempts (constant-time guarantee), plus 1 regression for null model delegates, 4 parametrized cases for case-insensitive H1 detection, and 5 parametrized cases for case-insensitive alt-text detection.
|
|
2695
|
+
- `api/cron-routes.test.ts` — 12 cases covering GET (Vercel Cron path) and POST for all three cron endpoints, plus 401 paths.
|
|
2696
|
+
|
|
2697
|
+
## 0.11.2
|
|
2698
|
+
|
|
2699
|
+
### Patch Changes
|
|
2700
|
+
|
|
2701
|
+
- 6d2ea6e: Two fixes that emerged while stabilizing the e2e suite:
|
|
2702
|
+
- **cms-core**: `createRateLimiter()` now respects `ACTUATE_DISABLE_RATE_LIMIT=1` and returns a permissive limiter that always allows requests. This is for test harnesses where every test shares the same client IP and exhausts the strict per-IP buckets within the first few login attempts. Production never sets this var; the deploy guide and Vercel templates omit it. Previously the env var bypass only existed at the per-handler `checkRateLimitAsync` wrapper in `handlers.ts`, so the global API rate limit in `handler-factory.ts` (which uses `rateLimiter.check()` directly) ignored it and broke retries with HTTP 429.
|
|
2703
|
+
- **cms-admin**: `<SEO />` view crashed with "M.filter is not a function" the first time it rendered. The `/seo/link-health` endpoint returns `{ truncated, checked, issues: [...] }` (an object), but `useApiData<any[]>` typed it as an array and the `linkHealth = linkHealthData ?? []` coercion silently put the wrapper object into the `linkHealth` variable. Calls like `linkHealth.filter(l => l.status === 404)` then threw on every render — including when the user was on the (default) Pages tab, because Radix `<Tabs.Content>`'s children are evaluated eagerly even when the tab is unmounted. Fixed the typing and switched to `linkHealthData?.issues ?? []`.
|
|
2704
|
+
|
|
2705
|
+
## 0.11.1
|
|
2706
|
+
|
|
2707
|
+
### Patch Changes
|
|
2708
|
+
|
|
2709
|
+
- 22c314b: Add `ACTUATE_DISABLE_RATE_LIMIT=1` env-var escape hatch for test
|
|
2710
|
+
harnesses. Production never sets it; the wired bypass lives at the
|
|
2711
|
+
single `checkRateLimitAsync` chokepoint in `api/handlers.ts`.
|
|
2712
|
+
|
|
2713
|
+
Why: the e2e Playwright suite shares a single `login:unknown` rate
|
|
2714
|
+
limiter bucket across all 12 specs (the dev server doesn't set
|
|
2715
|
+
`X-Forwarded-For`, so `getClientIp()` returns `'unknown'`). The strict
|
|
2716
|
+
5-attempts-per-15-min login limiter exhausts after the first three
|
|
2717
|
+
specs, causing every subsequent test to fail with "Too many login
|
|
2718
|
+
attempts." This was masked for months by an unrelated `pnpm
|
|
2719
|
+
format:check` CI failure that gated the whole `e2e` job.
|
|
2720
|
+
|
|
2721
|
+
The bypass is keyed off an explicit env var rather than `NODE_ENV ===
|
|
2722
|
+
'test'` because some Next.js build steps run with `NODE_ENV=test` and
|
|
2723
|
+
we want the rate limiter exercised by unit tests that don't opt in.
|
|
2724
|
+
|
|
2725
|
+
## 0.11.0
|
|
2726
|
+
|
|
2727
|
+
### Minor Changes
|
|
2728
|
+
|
|
2729
|
+
- 6db988e: Major engineering quality + integration upgrades.
|
|
2730
|
+
|
|
2731
|
+
**Security & reliability (cms-core)**
|
|
2732
|
+
- Audit log: fixed runtime crash from `orderBy: createdAt` → `timestamp`; never throws on insert; allows null `details`.
|
|
2733
|
+
- IPv4 + IPv6 + CIDR allowlist with canonical comparison; reject `unknown` IPs against non-empty allowlists.
|
|
2734
|
+
- Centralised trusted client IP extraction via `x-vercel-forwarded-for` / `x-real-ip` / `x-forwarded-for` (with explicit `trustProxy`).
|
|
2735
|
+
- Atomic Upstash `INCR` + `EXPIRE NX` rate limiter, fail-open with logging.
|
|
2736
|
+
- Encryption at rest for OAuth tokens, TOTP secrets, backup codes, and webhook secrets via `CMS_ENCRYPTION_KEY`.
|
|
2737
|
+
- Hardened TOTP login: `mfa_pending` JWT + per-IP/per-user rate limit + request fingerprint binding (no userId leak between steps).
|
|
2738
|
+
- Reauth required for TOTP setup/disable; revoke other sessions on TOTP changes.
|
|
2739
|
+
- OAuth state nonce bound to a cookie; `allowSelfSignup` flag; never silently link to password-protected accounts.
|
|
2740
|
+
- SSRF-safe `safeFetch()` for webhooks and `/seo/link-health`; concurrency cap + URL count cap; private IPs/loopback/link-local rejected.
|
|
2741
|
+
- Magic-byte + MIME validation + DOMPurify SVG sanitisation on `/media/upload`; tightened WebP/AVIF/GIF/SVG detectors.
|
|
2742
|
+
- Webhook + plugin hook dispatch moved outside the DB transaction so a rollback never fires events for a doc that no longer exists.
|
|
2743
|
+
- AI: per-user rate limit + token/cost budget + input length cap + balanced-brace JSON extractor + `validateTree` after every step + secret redaction in audit prompts.
|
|
2744
|
+
- CSRF exemption tightened to exact-prefix match on the rewritten path (no more `pathname.includes`).
|
|
2745
|
+
- `refreshSession`: validate session row exists & is active before re-issuing.
|
|
2746
|
+
- Public SEO endpoints require auth; `/llms.txt` filters PUBLISHED + non-deleted.
|
|
2747
|
+
- `/forms`, `/forms/:id/submissions`, `/search/global` users restricted to EDITOR+.
|
|
2748
|
+
- Open-redirect host allowlist for `/redirects`; folder routes validate scope on move/delete; password-reset email enumeration blocked.
|
|
2749
|
+
- `/script-tags/resolve` returns proper public cache headers.
|
|
2750
|
+
|
|
2751
|
+
**Integration fixes (consumer feedback)**
|
|
2752
|
+
- **`createServerSiteClient(prisma, config)`** — new server-only Site client that reads from Prisma directly. Use in Server Components, `generateMetadata`, route loaders. Avoids the `Invalid URL` crash that `createSiteClient()` throws under bare Node, and skips the self-fetch overhead.
|
|
2753
|
+
- **`createSiteClient()`** now auto-resolves `baseUrl` from `NEXT_PUBLIC_SITE_URL` or `VERCEL_URL` when not explicitly provided.
|
|
2754
|
+
- **`/public/globals/:slug`** defaults to public when no `access.read` is set (was a 403 footgun).
|
|
2755
|
+
- **`cmsApi`** auto-bootstraps the `actuate_csrf` cookie before non-GET requests; `AdminRoot` also primes it on mount when a session exists. Fixes the silent 403 every consumer hit when signing in via a custom Server Action.
|
|
2756
|
+
- **AdminShell** routes collections with `type: 'page'` through `<PageBuilder>` instead of `<DocumentEdit>`. Built-in admin paths (`/users`, `/media`, etc.) are reserved before collection routes so a user-defined `users` collection can't shadow the Users view.
|
|
2757
|
+
- **`BlockCatalog`** now reads custom blocks from `globalThis.__actuateConfig.pageBuilder.blocks` automatically. Also accepts an explicit `customBlocks` option and an `includeCore: false` flag.
|
|
2758
|
+
- **Page Builder envelope** unified at the top level — `layout` and `pageSettings` are accepted on POST/PUT and returned at top-level on GET. Storage shape is `data._layout` / `data._pageSettings`; both wire shapes accepted on input for backwards compatibility.
|
|
2759
|
+
- **`/media/upload`** now uses the configured platform storage adapter (e.g. `platform-vercel`) when set; honors `token`/`prefix` options. Falls back to direct `@vercel/blob` import for legacy installs.
|
|
2760
|
+
- **BlockEditor** field types fleshed out: real TipTap editor for `richText`, working media picker for `media`, JSON editor with validation for `json`, plus `slug`, `date`, and an array editor for primitive arrays.
|
|
2761
|
+
- **`pageBuilder.previewTheme`** now applied to the canvas via scoped CSS variables.
|
|
2762
|
+
- OpenAPI: documented `/public/globals/:slug` and `/resolve`.
|
|
2763
|
+
|
|
2764
|
+
**DX**
|
|
2765
|
+
- `.env.example` documents required formats for `CMS_ENCRYPTION_KEY` (64-hex), `BLOB_READ_WRITE_TOKEN`, and `RESEND_*`.
|
|
2766
|
+
- Tests: 266 passing (was 230); regression tests for IP allowlist, client-ip, redact, secret-storage, audit, upload-magic, server-site, custom block discovery.
|
|
2767
|
+
|
|
2768
|
+
## 0.10.4
|
|
2769
|
+
|
|
2770
|
+
### Patch Changes
|
|
2771
|
+
|
|
2772
|
+
- 58bf2e6: Add site-wide SEO robots defaults with per-page index/follow override controls.
|
|
2773
|
+
|
|
2774
|
+
## 0.10.3
|
|
2775
|
+
|
|
2776
|
+
### Patch Changes
|
|
2777
|
+
|
|
2778
|
+
- e2fabee: Add AI-friendly deployment diagnostics, align generated schemas with first-run admin features, and harden setup guidance for design-first deployments.
|
|
2779
|
+
|
|
2780
|
+
## 0.10.2
|
|
2781
|
+
|
|
2782
|
+
### Patch Changes
|
|
2783
|
+
|
|
2784
|
+
- b66800c: Align admin-facing API response shapes with cms-admin consumers, add Vercel Blob image configuration, and harden admin views against partial data.
|
|
2785
|
+
|
|
2786
|
+
## 0.10.1
|
|
2787
|
+
|
|
2788
|
+
### Patch Changes
|
|
2789
|
+
|
|
2790
|
+
- b224105: Fix post-release developer experience regressions in public site globals, scaffolded Prisma wiring, and seed/populate database initialization.
|
|
2791
|
+
|
|
2792
|
+
## 0.10.0
|
|
2793
|
+
|
|
2794
|
+
### Minor Changes
|
|
2795
|
+
|
|
2796
|
+
- 266b657: Add first-class public site helpers, CLI init/populate workflows, and generated frontend boilerplate for globals, document resolution, and media normalization.
|
|
2797
|
+
|
|
2798
|
+
## 0.9.0
|
|
2799
|
+
|
|
2800
|
+
### Minor Changes
|
|
2801
|
+
|
|
2802
|
+
- c3c55c7: Add AI-powered page generation pipeline with prompt-to-page workflow.
|
|
2803
|
+
|
|
2804
|
+
**cms-core**: AI pipeline types (GenerationContext, GenerationStep, StepResult, GenerationResult), prompt template builders for structure/content/SEO/accessibility steps, deterministic accessibility auto-fix engine (auditAccessibility, fixAccessibility), and API endpoints for /page-builder/generate, /page-builder/generate-block, /page-builder/audit-a11y, and /page-builder/fix-a11y.
|
|
2805
|
+
|
|
2806
|
+
**plugin-ai**: Multi-step generatePage() pipeline that runs structure → content → SEO → accessibility steps sequentially via AIProvider, with validation retry, graceful fallback, progress callbacks, and token tracking. Single-block generateBlockContent() for context-aware content generation.
|
|
2807
|
+
|
|
2808
|
+
**cms-admin**: AI Generate Dialog with prompt input, tone selection, step toggles, business context fields, real-time generation progress, and step-by-step review with accept/regenerate flow. AI Block Assist button for one-click content generation per block. AI button integrated into the builder toolbar.
|
|
2809
|
+
|
|
2810
|
+
- 2ef3505: Add SEO analysis and design scoring for the page builder. cms-core gains `analyzeSEO()` (content extraction, readability, 15+ SEO checks, per-block hints), `analyzeDesign()` (6-category scoring for visual hierarchy, spacing consistency, content density, color usage, image quality, and mobile readiness), and semantic block intelligence with smart suggestions. cms-admin replaces the placeholder SEO and Design tabs in the page builder ContextPanel with fully functional `BuilderSEOPanel` (score ring, checks, readability grid, SERP/social previews, inline editing) and `DesignScorePanel` (score ring, category progress bars, expandable checks, severity-ranked suggestions).
|
|
2811
|
+
- 2269f10: Add page templates and saved sections for the page builder. cms-core gains 6 built-in page templates (blank, landing, about, contact, location, blog-post) with full container tree structures, plus CRUD API endpoints for both page templates and saved sections with tree validation, auto-seeding, and audit logging. cms-admin adds a TemplatePicker modal for new page creation, a SavedSections CRUD management view with category filtering, and admin routing for the new views.
|
|
2812
|
+
|
|
2813
|
+
## 0.8.0
|
|
2814
|
+
|
|
2815
|
+
### Minor Changes
|
|
2816
|
+
|
|
2817
|
+
- e19430e: Add page builder foundation: container tree types, Zod validation schema, tree manipulation utilities, unified block catalog with 10 core types and named variants, and Prisma models for page templates and saved sections. New `pageBuilder` config option on `ActuateCMSConfig`. New `./page-builder` export path.
|
|
2818
|
+
|
|
2819
|
+
## 0.7.0
|
|
2820
|
+
|
|
2821
|
+
### Minor Changes
|
|
2822
|
+
|
|
2823
|
+
- b5de8dd: Add password reset flow for admin login. Users click "Forgot Password?" on the login page, enter their email, and receive a time-limited reset link via the configured email adapter (Resend). The reset token is hashed before storage and expires after 1 hour. On successful reset, all existing sessions are revoked.
|
|
2824
|
+
- New PasswordResetToken Prisma model with migration 0005_password_reset_tokens.
|
|
2825
|
+
- POST /auth/forgot-password and POST /auth/reset-password API endpoints with rate limiting.
|
|
2826
|
+
- Core auth module (reset.ts) with token generation, email dispatch, and password update logic.
|
|
2827
|
+
- ForgotPassword and ResetPassword admin views with full form validation.
|
|
2828
|
+
- AdminRoot routes /forgot-password and /reset-password in the unauthenticated shell.
|
|
2829
|
+
|
|
2830
|
+
## 0.6.0
|
|
2831
|
+
|
|
2832
|
+
### Minor Changes
|
|
2833
|
+
|
|
2834
|
+
- 54c7a3b: Add Script Tags management system for injecting tracking codes, analytics, and custom scripts into public site pages. Admins create tags with HTML/JS code, choose placement (head, body open, body close), set scope (entire site, parent pages + descendants, or exact URLs), and assign priority order. Includes new ScriptTag database model, CRUD + public resolve API endpoints, admin list and editor views, and updated scaffolder site layout with automatic tag rendering.
|
|
2835
|
+
|
|
2836
|
+
## 0.5.0
|
|
2837
|
+
|
|
2838
|
+
### Minor Changes
|
|
2839
|
+
|
|
2840
|
+
- c08cf8b: Add layout variants system for customizable per-page headers, footers, and other layout regions. Layout regions are defined in config, stored as regular collection documents, and assigned to pages with automatic parent-to-child URL path inheritance. The /resolve endpoint returns resolved layout variants alongside page data. Admin UI includes layout assignment on document edit, collection grouping in sidebar, and default layout settings.
|
|
2841
|
+
|
|
2842
|
+
### Patch Changes
|
|
2843
|
+
|
|
2844
|
+
- 80aa939: Add scaffold site rendering layer with catch-all route, block components, layout components, and critical config wiring fixes.
|
|
2845
|
+
- Fix API route template to pass config to handleActuateAPI (enables /resolve and layout regions)
|
|
2846
|
+
- Fix admin page template to pass real config to AdminRoot (enables layout sidebar and blocks field)
|
|
2847
|
+
- Fix /resolve endpoint to handle root path (/) by looking up "home" or "index" slug
|
|
2848
|
+
- Add blocks-first page fields, header/footer collections, and layout config to scaffold (conditional on blocks plugin)
|
|
2849
|
+
- Generate catch-all page route with dynamic SEO metadata and first-boot welcome page
|
|
2850
|
+
- Generate BlockRenderer, HeroBlock, CTABlock, TextBlock, ImageTextBlock components
|
|
2851
|
+
- Generate SiteHeader and SiteFooter layout components with graceful fallbacks
|
|
2852
|
+
|
|
2853
|
+
## 0.4.0
|
|
2854
|
+
|
|
2855
|
+
### Minor Changes
|
|
2856
|
+
|
|
2857
|
+
- f2cf7d7: Redesign Dashboard and Pages views: extended /stats API with statusCounts, formCount, avgSeoScore, and author names; new /seo/summary endpoint; recharts donut chart for content overview; SEO performance panel; folder color badges and SEO score column in Pages view.
|
|
2858
|
+
|
|
2859
|
+
## 0.3.1
|
|
2860
|
+
|
|
2861
|
+
### Patch Changes
|
|
2862
|
+
|
|
2863
|
+
- 0621037: Make Dashboard collection-aware instead of hardcoding Posts/Pages labels.
|
|
2864
|
+
|
|
2865
|
+
**cms-core:**
|
|
2866
|
+
- `/stats` API now returns `collectionCounts` (per-collection document counts via groupBy)
|
|
2867
|
+
- Recent documents include `collection` field in response
|
|
2868
|
+
|
|
2869
|
+
**cms-admin:**
|
|
2870
|
+
- Dashboard reads `config.collections` to derive stat card labels, counts, routes, and quick actions
|
|
2871
|
+
- "Edit" links use `/{doc.collection}/{doc.id}` instead of hardcoded `/posts/{id}`
|
|
2872
|
+
- "New" quick actions are generated from configured collections
|
|
2873
|
+
- Falls back to generic "Documents" / "Pages" labels when no config is provided
|
|
2874
|
+
|
|
2875
|
+
## 0.3.0
|
|
2876
|
+
|
|
2877
|
+
### Minor Changes
|
|
2878
|
+
|
|
2879
|
+
- 4e3a5eb: RC2-RC4 and hardening release.
|
|
2880
|
+
|
|
2881
|
+
**cms-core:**
|
|
2882
|
+
- Replace isomorphic-dompurify with sanitize-html (eliminates Vercel ESM crash)
|
|
2883
|
+
- Remove cms-core from serverExternalPackages, move to transpilePackages
|
|
2884
|
+
- Add GET /api/cms/health endpoint with model probing and diagnostics
|
|
2885
|
+
- Harden db() Proxy with try/catch on property access and rawDb()
|
|
2886
|
+
- Harden safeCount/safeFindMany against Proxy throws
|
|
2887
|
+
- Add startup validation for missing CMS_SECRET (503 instead of 500)
|
|
2888
|
+
- Bypass auth on /stats when secret is missing (returns zero-data)
|
|
2889
|
+
- Loosen NextConfigLike types (experimental/images/webpack as unknown)
|
|
2890
|
+
- Implement login anomaly detection (new IP, brute force, unusual hours)
|
|
2891
|
+
- Implement webhook DNS resolution with private range checking
|
|
2892
|
+
- Implement TOTP reauth verification
|
|
2893
|
+
- Add Zod validation for secret in defineConfig (min 32 chars)
|
|
2894
|
+
- Read CMS_CORE_VERSION from package.json at runtime instead of hardcoding '0.1.0'
|
|
2895
|
+
|
|
2896
|
+
**cms-admin:**
|
|
2897
|
+
- Ship pre-compiled CSS (dist/actuate-admin.css) with all 48 responsive classes
|
|
2898
|
+
- New export: @actuate-media/cms-admin/styles/precompiled.css
|
|
2899
|
+
- Add CSS containment (`contain: layout style`) on .actuate-admin
|
|
2900
|
+
- Fix ShortcutHelp modal using React state instead of DOM manipulation
|
|
2901
|
+
- Add fixed-position layout isolation wrapper to AdminRoot
|
|
2902
|
+
- Add useApiData retry limits (maxRetries=3) with exponential backoff
|
|
2903
|
+
- Dashboard shows health-aware "Database Setup Required" banner
|
|
2904
|
+
|
|
2905
|
+
**cli:**
|
|
2906
|
+
- Add `actuate db:init` command (injects CMS models into Prisma schema)
|
|
2907
|
+
- Add `actuate db:status` command (checks model presence and DB connectivity)
|
|
2908
|
+
|
|
2909
|
+
**create-actuate-cms:**
|
|
2910
|
+
- Embed full CMS schema (9 models + enum) in generated schema.prisma
|
|
2911
|
+
- Add @actuate-media/cli to scaffolded devDependencies
|
|
2912
|
+
- Use pre-compiled CSS import in scaffolded admin layout
|
|
2913
|
+
|
|
2914
|
+
## 0.2.3
|
|
2915
|
+
|
|
2916
|
+
### Patch Changes
|
|
2917
|
+
|
|
2918
|
+
- Fix /stats endpoint: use Promise.allSettled so missing Prisma models return 0 instead of 500. Dashboard now shows a soft warning instead of a red error banner when stats are unavailable. Add !important to font-family on .actuate-admin to prevent consumer site fonts from cascading into the admin panel.
|
|
2919
|
+
|
|
2920
|
+
## 0.2.2
|
|
2921
|
+
|
|
2922
|
+
### Patch Changes
|
|
2923
|
+
|
|
2924
|
+
- Loosen withActuateCMS types: accept minimal config objects without requiring full CollectionDefinition (labels, fields). Replace Partial<ActuateCMSConfig> with WithActuateCMSOptions and NextConfigWithActuate with NextConfigLike that aligns with Next.js's NextConfig shape. Consumers no longer need `as unknown` casts.
|
|
2925
|
+
|
|
2926
|
+
## 0.2.1
|
|
2927
|
+
|
|
2928
|
+
### Patch Changes
|
|
2929
|
+
|
|
2930
|
+
- Fix Turbopack conflict: remove cms-core from transpilePackages since it's already in serverExternalPackages. Turbopack forbids a package in both lists. Also add a safety filter so any overlap between the two lists is resolved in favor of serverExternalPackages.
|
|
2931
|
+
|
|
2932
|
+
## 0.2.0
|
|
2933
|
+
|
|
2934
|
+
### Minor Changes
|
|
2935
|
+
|
|
2936
|
+
- 682dac7: Fix consumer integration issues discovered during MaidPro.co deployment
|
|
2937
|
+
- **cms-core**: API handlers gracefully degrade when CMS Prisma models are missing (returns 501 instead of 500)
|
|
2938
|
+
- **cms-core**: Accept CMS secret via `actuate.config.ts` (`secret` field) in addition to `CMS_SECRET` env var
|
|
2939
|
+
- **cms-core**: `withActuateCMS` now accepts `Partial<ActuateCMSConfig>` (no more forced type casts)
|
|
2940
|
+
- **cms-core**: `withActuateCMS` auto-injects all `@actuate-media/*` packages into `transpilePackages`
|
|
2941
|
+
- **cms-core**: Expanded Prisma schema fragment with all CMS models for consumer merge
|
|
2942
|
+
- **cms-admin**: Removed duplicate Tailwind import from admin styles (consumers already load Tailwind)
|
|
2943
|
+
- **platform-vercel**: Email adapter accepts both `defaultFrom` and `from` for sender address
|
|
2944
|
+
- **plugin-email**: Email plugin accepts both `defaultFrom` and `from` for consistency
|
|
2945
|
+
|
|
2946
|
+
### Patch Changes
|
|
2947
|
+
|
|
2948
|
+
- 3fd38a2: Fix all 13 integration issues discovered during MaidPro.co deployment
|
|
2949
|
+
|
|
2950
|
+
**cms-core:**
|
|
2951
|
+
- Add .js extensions to all ESM relative imports (58 files)
|
|
2952
|
+
- Replace workspace:\* with versioned deps in all packages
|
|
2953
|
+
- Add sideEffects:false for proper tree-shaking
|
|
2954
|
+
- Subpath exports (/config, /api, /middleware, /next) avoid barrel side effects
|
|
2955
|
+
- Stats/search routes use safeCount/safeFindMany for missing Prisma models
|
|
2956
|
+
- Ship prisma/cms-schema.prisma with all required CMS models
|
|
2957
|
+
- Accept both CMS_SECRET and CMS_SESSION_SECRET with clear error messages
|
|
2958
|
+
- Fix hardcoded /admin paths in OAuth flow (now uses getAdminPath())
|
|
2959
|
+
- Loosen withActuateCMS and createCMSMiddleware parameter types
|
|
2960
|
+
- Auto-inject transpilePackages and serverExternalPackages in withActuateCMS
|
|
2961
|
+
- Unify PluginDefinition with ActuatePlugin interface
|
|
2962
|
+
|
|
2963
|
+
**cms-admin:**
|
|
2964
|
+
- Scope all 40+ CSS theme variables to .actuate-admin instead of :root
|
|
2965
|
+
- ThemeProvider applies dark class to .actuate-admin, not document.documentElement
|
|
2966
|
+
- Scaffold uses (admin)/(site) route groups for full CSS/script isolation
|
|
2967
|
+
- Sidebar branding supports custom logo via config.admin.branding
|
|
2968
|
+
|
|
2969
|
+
**platform-vercel:**
|
|
2970
|
+
- Email adapter accepts both `from` and `defaultFrom`
|
|
2971
|
+
|
|
2972
|
+
**All plugin/platform packages:**
|
|
2973
|
+
- Fix ESM .js extensions in compiled output
|
|
2974
|
+
- Replace workspace:\* with ^0.1.0 for publishable deps
|