From 0ebe8144c20034b33cc79d921396ce632e902c1c Mon Sep 17 00:00:00 2001 From: lilleman Date: Tue, 16 Jun 2026 11:58:28 +0200 Subject: [PATCH] Document how plugins get into the container (README); bind-mount to /app/plugins/ or bake in, front-and-center under Building a plugin --- README.md | 44 +++++++++++++++++++++++++++++++++++++++-- docs/plugin-contract.md | 10 ++++++---- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6b63a8d..30307bc 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,8 @@ docker compose up # http://localhost:3000, live reload via `node --wa `docker compose up` merges `compose.override.yml`, which mounts the source and restarts the server on change. _(The Ory + Postgres services join this compose -file as they land — planned.)_ +file as they land — planned.)_ To work on your own plugin, see +[Where plugins live](#where-plugins-live-and-how-to-mount-them). ## Configuration @@ -213,6 +214,45 @@ it — the plugin holds no state of its own (see below). Each plugin is **self-contained** (its own nav, routes, views, CSS), so installing one is "drop the folder, restart." An operator stays in control via a central override. +### Where plugins live (and how to mount them) + +The host scans **`/app/plugins/`** inside the `web` container — so "installing a +plugin" means getting its folder there. There are two ways, depending on where the +plugin's source lives: + +**1. In your clone (the default dev loop).** Create `plugins//` in the working +tree. `docker compose up` already bind-mounts the whole tree (`compose.override.yml`: +`.:/app`), so the folder is live in the container — restart to pick it up. This is the +"copy the example plugin and go" path. + +**2. A plugin kept in its own repo, or added to a prebuilt image.** Bind-mount the +plugin folder onto `/app/plugins/` with a small compose override. Plugins are +stateless, so mount it read-only: + +```yaml +# compose.plugins.yml — mount external plugin folders into the host +services: + web: + volumes: + - ../scheduling-plugin:/app/plugins/scheduling:ro # host path : /app/plugins/ +``` + +```bash +# Dev: list the files explicitly (a third file disables the implicit override merge) +docker compose -f compose.yml -f compose.override.yml -f compose.plugins.yml up +# Prod (image already built, no source mount): +docker compose -f compose.yml -f compose.plugins.yml up -d +``` + +A named volume or volume container works the same way (target `/app/plugins/`), +but a bind mount matches the edit-and-reload loop. For a **baked** production image, +just keep the plugin in the build context and it's `COPY`'d in at build time — pinned +and reproducible; mount a volume only to add plugins to an already-built image. + +> _(Planned, §2.)_ Discovery — scanning `plugins/`, importing each `plugin.ts`, and +> validating it — is the next plugin-host work. The mount mechanics above are how the +> files get into the container regardless of how discovery lands. + ## The menu system _(planned)_ The menu is **driven entirely by config** and assembled from two sources: @@ -393,7 +433,7 @@ src/plugin.ts Plugin contract: manifest types, definePlugin(), version + views/ Core EJS templates (index = the app-shell People dashboard, 403/404/500, partials/ incl. app shell, nav tree, filter bar, data table, pagination, form field, auth card, menu/popover, theme switch, icon sprite) public/ Static assets under /public/ (css/styles.css + auth.css, favicon, robots.txt) config/menu.ts Central menu override + branding (planned) -plugins/ Drop-in plugin folders, auto-discovered (planned) +plugins/ Drop-in plugin folders (scanned at /app/plugins; bind-mount or bake in) (planned) docs/ Reference docs (plugin-contract.md — the authoritative plugin API) e2e/ Playwright visual + functional E2E (Dockerfile.e2e + compose.e2e.yml run it) html-css-foundation/ HTML design mockups — the source for the building-block diff --git a/docs/plugin-contract.md b/docs/plugin-contract.md index a34c4ba..a4ce653 100644 --- a/docs/plugin-contract.md +++ b/docs/plugin-contract.md @@ -234,10 +234,12 @@ A plugin is a normal folder of TypeScript, so an author tests it the same way th docker compose run --rm web npm test ``` -2. **Run one plugin against the host.** Drop the folder in `plugins/` and `docker compose up`; - the host discovers it. For an isolated harness, the §2 host exposes plugin injection - (`createApp({ plugins: [myPlugin] })`) so a test can mount a single manifest and assert its - routes, nav, and gating without the rest of the stack. +2. **Run one plugin against the host.** Get the folder into the container's `/app/plugins/` + — either in your clone (the dev compose bind-mounts the tree) or by bind-mounting an external + folder (README → *Where plugins live*) — and `docker compose up`; the host discovers it. For + an isolated harness, the §2 host exposes plugin injection (`createApp({ plugins: [myPlugin] })`) + so a test can mount a single manifest and assert its routes, nav, and gating without the rest + of the stack. 3. **E2E the user-facing flow.** Per AGENTS.md §6, every plugin page/form ships *with* a Playwright test in `e2e/`, side-effect-free so the suite stays `fullyParallel`. The test runs