Plugin packages
Plugin packages
When no block covers a need, a developer writes one as a plugin package, and the published image
stays as it is. A plugin is an npm package that ships built JavaScript, like this one does, and whose
default export is definePlugin:
import { defineBlock, definePlugin } from "barakopress";
type TallyProps = { count: number; label: string };
const tally = defineBlock<TallyProps>({
type: "tally",
label: "Tally",
fields: [
{ name: "count", kind: "number", required: true },
{ name: "label", kind: "text", required: true },
],
component: ({ props, theme }) => (
<p style={{ color: theme.colors.accent }}>{props.count} {props.label}</p>
),
});
export default definePlugin({ name: "tally", blocks: [tally] });
defineBlock checks the fields against the props at compile time, the same as the built-ins: a field
name the props do not have, a kind that does not suit the prop's type, or an optional field for a prop
the component treats as always there does not compile. examples/plugin-sample is a complete one,
with its package.json and tsconfig.json; CI packs it, builds an image with it and renders it.
A plugin block is passed what every block is passed: its props, checked against its fields, its
slots, rendered, and the theme. It is not passed the config, the CMS address or a token, and data from
the CMS should reach it through a binding in its props or a module endpoint, not a credential. That is
what it is handed, not what it can reach: its code runs in the server with full access, process.env
and every barakopress export included. Installing a plugin means trusting it with the deployment.
Its name may not be one a built-in, a library block, the site or another plugin already registered. Replacing one would change that block for every tenant, including the ones that never enabled the plugin.
Installing: a derived image. Blocks are registered when the Next build runs, so a package cannot be added to a built image. The published image holds the built server and not the source or the toolchain, so the derived image is built from the engine's source at a release tag, which is what the published image of that tag was built from, with the plugins handed in as a build context:
mkdir plugins
(cd ../my-plugin && npm pack --pack-destination ../site/plugins) # your own plugin
npm pack barakopress-plugin-tally@1.2.0 --pack-destination plugins # one from npm
docker buildx build \
--build-context plugins=./plugins \
-t my-press:0.8.0-plugins \
https://github.com/BaryoDev/barakoPress.git#v0.8.0
examples/derived-image/compose.yml is the same thing in compose. The plugins directory holds
tarballs and nothing else, so what is built is exactly the bytes that were packed. The install runs
offline, so a plugin's own dependencies must travel inside its tarball: list each one in
bundleDependencies, and a tarball with a dependency it does not bundle is refused. So is a plugin
named like a package the engine already has (react, next, anything in its lockfile), which would
otherwise be linked over it. The build installs them beside the engine with no install scripts,
and writes press.plugins.ts, which the reference
press.config.ts passes to createBlockRegistry(config, [], { plugins }). An overlay with its own
press.config.ts imports plugins from @/press.plugins and passes it the same way. Pin the tag,
and next to it the commit it points at (git ls-remote https://github.com/BaryoDev/barakoPress.git v0.8.0), since a tag can be moved; building from #<commit> is the strict form. The v0.8.0 tag
exists from the 0.8.0 release on. Moving it is an engine upgrade, and a plugin should be rebuilt and
checked against it.
Enabling: per tenant. One derived image carries every plugin the deployment installs. A tenant
renders a plugin's blocks only when the Plugins setting in its site settings entry names it:
{ "Plugins": ["tally"] }
Until then, it is as if the plugin were not installed for that tenant: /api/blocks does not offer its
blocks, a page that holds one renders everything around it and not the block, and a preset whose body
draws one is left out. A list saved empty turns every plugin off; a missing field leaves the ones the
config names (plugins in defineConfig, empty unless set). A build-time site enables plugins with
plugins in its config. /api/blocks also answers plugins, every installed plugin with whether the
tenant enabled it, which is what barakoBrew reads to show the switch. Each block a plugin added carries
plugin with its name.
What enablement does not do is keep code apart. Every plugin's module is loaded in the container for every tenant it serves, and a tenant that has not enabled it is only kept from rendering it. So a plugin nobody on the deployment trusts is not installed there. When tenants must not share plugins, the answer is a separate deployment, its own barakoCMS, barakoBrew and barakoPress, not a second barakoPress against the same API. A hotel's branch landing pages share one deployment; its booking system gets its own.