Repository navigation
v2.14.0
·
101 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
Added
min_framework_versionandmin_app_versionmanifest keys (#372) —min_host_versionread like "minimum host application version" but was always compared against the cms-framework package version, so a plugin declaring"min_host_version": "1.0.0"meaning "Keystone 1.0" always passed.min_framework_versionis the new name for that check.min_app_versionis compared against the host application's version, which the host supplies through the newcms.plugins.appVersionconfig (envCMS_APP_VERSION), because the framework cannot detect it. Set it from the environment or from a service provider'sregister(), not withconfig()insideconfig/cms/plugins.php, where another file's key may not have loaded yet. Both checks run on activation and before an update reactivates a plugin.IncompatiblePluginExceptiongains aconstraintproperty (frameworkorapp) and aforAppVersion()factory, and the activate and update endpoints'409plugin_incompatiblepayload gainsconstraint. When a check cannot run, because the version is unknown or not a comparable semver, activation still goes ahead, and the framework now firesap.cmsFramework.plugin.versionCheckSkippedwith(string $slug, array $details)alongside the logged warning, so a host can surface the skip.- Plugins can serve their federated bundle without an asset controller (#371) — a
federated_module.entrythat is a path inside a subdirectory of the plugin (e.g.dist/remoteEntry.js) publishes that directory on a new public route,GET /plugins/{slug}/assets/{path}(plugins.assets). The descriptor carries the absolute URL. Only active plugins are served. Files must resolve inside the directory, carry an allowed extension (JS, CSS, source maps, JSON, WASM, images, fonts) and not be dot-files. Files whose name carries a bundler content hash are cachedimmutablefor a year. The entry and every other file revalidate with an ETag and Last-Modified, so a plugin update is picked up. Responses sendnosniff, and SVGs are sandboxed and sent as attachments. A plugin publishes one asset directory. The route loads after every provider boots, so a plugin's own/plugins/{slug}/assets/{path}route still matches first. Hosts with a GET catch-all route must exclude theplugins/prefix, or the catch-all answers first. Hosts that serve plugin assets themselves can turn the route off withcms.plugins.assetRoute(envCMS_PLUGINS_ASSET_ROUTE). federated_module.boot_module(#371) — names an exposed module the host preloads before the first plugin page mounts, so a plugin can register its JS hooks first. It is bridged into the descriptor asbootModule, andregisterFederatedModule()takes it as a new optional fourth argument,$bootModule.- TypeScript types for the incompatibility response and federated descriptors —
PluginIncompatibleResponseandFederatedModuleDescriptorinresources/types/plugins.d.ts.index.d.tsnow re-exports every plugins type, not onlyPluginResponseandUpdateType.
Changed
federated_modulemanifest validation is stricter (#371) — Breaking for manifests that 2.13 accepted. The rules run on install, on update, and whenplugins:sync/syncFromDisk()refreshes an installed plugin. A refreshed manifest that fails is reported asfailedand leaves the plugin's row unchanged, while the plugin keeps running. An update with such a manifest rolls back. Republish affected plugins with a compliantplugin.json:entrymust be an absolutehttpsURL, a plainhttpURL to a loopback dev server (localhost,*.localhost,127.0.0.0/8,[::1]), or a path inside a subdirectory of the plugin. Move a plugin-root entry into a directory such asdist/. Usehttpsfor remote hosts. Register a root-relative host URL (/build/remoteEntry.js) from PHP withregisterFederatedModule()instead of the manifest. URLs with embedded credentials are refused.exposesmust be a list of module ids. Turn a{ "./Page": "./src/Page.tsx" }map into["./Page"].nameandboot_modulemust be non-empty strings. Unknown keys are logged and ignored.
registerFederatedModule()resolves relative and filesystem-path entries — a path relative to the plugin directory is served throughplugins.assets, as above. An absolute filesystem path inside the plugin, the$this->pluginPath( 'dist/remoteEntry.js' )pattern the hello-world example used, is now treated as that relative path instead of being handed to hosts as a "URL" that leaked the server's install path and never loaded. A path underpublic_path()becomes its root-relative URL. Any other filesystem path is logged and not registered. Absolute and root-relative URLs pass through as before.
Deprecated
min_host_version(#372) — a deprecated alias ofmin_framework_version, still enforced. A manifest may declare both, so hosts older than 2.14.0, which read onlymin_host_version, keep enforcing the minimum. When both are declared, their values must match.Plugin::$min_host_versionis deprecated in favour ofPlugin::$min_framework_version.
Fixed
- A legacy
update_urlfeed no longer reports "up to date" for a login page or a malformed response (#373) — a feed behind auth answered302to its login page. The check followed the redirect, read the200 text/htmlas having no version, and cached "no update" for the whole TTL with nothing logged. A body that is not a JSON object now fails withinvalidJsonResponse(), and a missing or blankversionfails withmissingRequiredField( 'version' ). Neither is cached, and the scheduled check reports both as failures. The feed is now requested withAccept: application/json. It follows up to five redirects, restricted tohttps(andhttponly whencms.updates.allow_insecure_transportis on), so release-download URLs and CDNs keep working and a redirect can't downgrade the transport. The feed URL in error messages has credentials redacted, and the version written to the plugin row is trimmed. - The hello-world example registers a servable federated entry — it passed
pluginPath( 'dist/remoteEntry.js' ), a filesystem path, which overrode the manifest's servable entry. It now passesdist/remoteEntry.js.