@biffo/cli 0.281.0 → 0.283.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/_skeletons/plugin-template/README.md +41 -4
- package/_skeletons/plugin-template/biffo.plugin.json +4 -0
- package/_skeletons/plugin-template/db/seed/000_default_widget.sql +47 -0
- package/_skeletons/plugin-template/registry-schema.json +19 -0
- package/_skeletons/plugin-template/src/example_plugin/plugin.py +15 -4
- package/_skeletons/registry/registry-schema.json +19 -0
- package/dist/index.js +335 -230
- package/package.json +1 -1
|
@@ -69,8 +69,42 @@ plugin deploying clean with empty tables and the symptom surfacing wherever
|
|
|
69
69
|
those rows were read
|
|
70
70
|
([#709](https://github.com/keiranholloway/biffo-template/issues/709)).
|
|
71
71
|
|
|
72
|
-
Baseline data
|
|
73
|
-
|
|
72
|
+
**Baseline data — the rows a feature needs before it works at all, not a
|
|
73
|
+
single plugin's own config — has a declared mechanism now
|
|
74
|
+
([#1554](https://github.com/keiranholloway/biffo-template/issues/1554)):**
|
|
75
|
+
`biffo.plugin.json`'s `seed` block. This skeleton demonstrates it:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
"seed": {
|
|
79
|
+
"dir": "db/seed",
|
|
80
|
+
"baseline_tables": ["example_widgets"]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`dir` names a directory of idempotent `.sql` files (`db/seed/000_default_widget.sql`
|
|
85
|
+
here) — `biffo plugin install`/`upgrade` vendor them into the installing
|
|
86
|
+
instance's `db/imports/_plugin-example-plugin/`, where that instance's
|
|
87
|
+
existing "Apply DDL imports" deploy step (ADR-0005) applies them on every
|
|
88
|
+
deploy. No token, no per-tenant API call, no new deploy machinery — it is the
|
|
89
|
+
same mechanism `db/imports/<name>/` always was, just declared in the manifest
|
|
90
|
+
instead of a step nobody was told to perform. `baseline_tables` names which of
|
|
91
|
+
this manifest's own `tables` the seed populates; the instance's deploy then
|
|
92
|
+
fails loudly, by table and by tenant, if one ends up empty
|
|
93
|
+
(`biffo:plugin-baseline-check`) — instead of the feature silently rendering
|
|
94
|
+
nothing, which is what actually happened and is the incident #1554 records.
|
|
95
|
+
|
|
96
|
+
**The idempotency contract is not optional and is checksum-enforced.** Once a
|
|
97
|
+
seed file has been applied anywhere, editing it makes the next deploy fail
|
|
98
|
+
loudly (ADR-0005 §4) rather than silently re-applying or silently skipping. A
|
|
99
|
+
later change to your baseline data ships as a new, additively-numbered file —
|
|
100
|
+
see `db/seed/000_default_widget.sql`'s own header comment for the full
|
|
101
|
+
contract and the `INSERT ... SELECT ... WHERE NOT EXISTS` shape every file
|
|
102
|
+
here must follow.
|
|
103
|
+
|
|
104
|
+
This is a second, complementary mechanism to the one below — not a
|
|
105
|
+
replacement for it. `seed_default_widget()` in `plugin.py` still demonstrates
|
|
106
|
+
**self-seeding at startup**, which is the right tool for a plugin's own
|
|
107
|
+
runtime config rather than tenant-scoped table rows:
|
|
74
108
|
|
|
75
109
|
- **If your plugin declares an `api_ingress` ASGI app** (ADR-0021), seed from
|
|
76
110
|
that app's lifespan. The shared plugin host runs each mounted app's lifespan
|
|
@@ -82,8 +116,11 @@ Baseline data goes in one of two places instead, and `seed_default_widget()` in
|
|
|
82
116
|
[#1000](https://github.com/keiranholloway/biffo-template/pull/1000), so treat
|
|
83
117
|
this route as new and check your own.
|
|
84
118
|
- **If it doesn't** — like `example_plugin`, which is event-only — there is no
|
|
85
|
-
startup to hang anything on.
|
|
86
|
-
|
|
119
|
+
startup to hang anything on. The `seed` manifest block above is its only
|
|
120
|
+
option, and is now the recommended default for baseline table rows even for
|
|
121
|
+
a plugin that does have an ASGI app: it reaches every tenant in one
|
|
122
|
+
statement, needs no running plugin process, and fails the deploy loudly
|
|
123
|
+
instead of only whenever a request happens to hit the lifespan hook.
|
|
87
124
|
|
|
88
125
|
> Earlier revisions of this README pointed at an RBAC reference plugin at
|
|
89
126
|
> `services/rbac/`. That plugin was removed by
|
|
@@ -83,6 +83,10 @@
|
|
|
83
83
|
{ "type": "nav-link", "label": "Example Plugin", "path": "/admin/example-plugin" },
|
|
84
84
|
{ "type": "page", "label": "Widgets", "path": "/admin/example-plugin/widgets" }
|
|
85
85
|
],
|
|
86
|
+
"seed": {
|
|
87
|
+
"dir": "db/seed",
|
|
88
|
+
"baseline_tables": ["example_widgets"]
|
|
89
|
+
},
|
|
86
90
|
"dependencies": {
|
|
87
91
|
"biffo-plugin-sdk": "^1.0"
|
|
88
92
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- example-plugin's baseline-row seed (biffo-template#1554).
|
|
2
|
+
--
|
|
3
|
+
-- Declared by biffo.plugin.json's `seed` block (`dir: "db/seed"`,
|
|
4
|
+
-- `baseline_tables: ["example_widgets"]`). `biffo plugin install`/`upgrade`
|
|
5
|
+
-- vendor every *.sql file in this directory into the installing instance's
|
|
6
|
+
-- `db/imports/_plugin-example-plugin/`, where that instance's existing
|
|
7
|
+
-- "Apply DDL imports" deploy step (ADR-0005) applies it on every deploy —
|
|
8
|
+
-- no token, no per-tenant API call, no new deploy machinery. See
|
|
9
|
+
-- README.md's "Seeding" section and `plugin.py`'s `seed_default_widget()`
|
|
10
|
+
-- docstring for the two-mechanism picture this is one half of.
|
|
11
|
+
--
|
|
12
|
+
-- IDEMPOTENCY CONTRACT (read before copying this into your own plugin):
|
|
13
|
+
-- once applied, this file is checksum-tracked in ddl_import_history — an
|
|
14
|
+
-- edit to it after that point makes the next deploy fail loudly rather than
|
|
15
|
+
-- silently re-applying or silently skipping (ADR-0005 section 4). A later
|
|
16
|
+
-- change to this plugin's baseline data must ship as a NEW, additively
|
|
17
|
+
-- numbered file (001_..., 002_...), never as an edit to this one.
|
|
18
|
+
--
|
|
19
|
+
-- INSERT ... SELECT ... WHERE NOT EXISTS is the shape every seed file here
|
|
20
|
+
-- must follow: it reads the known tenants from `users` (Core's own
|
|
21
|
+
-- Alembic-managed table — see plugin_baseline_check.py's module docstring
|
|
22
|
+
-- for why that, not a generic `tenants` table, is what "known tenant" means
|
|
23
|
+
-- in this template) and inserts exactly one starter widget per tenant that
|
|
24
|
+
-- doesn't already have one, so re-running this file on every deploy is
|
|
25
|
+
-- always a no-op once each tenant has its row. Mirrors the shape (same
|
|
26
|
+
-- name, same description, same idempotent "insert if missing by name" logic)
|
|
27
|
+
-- of `seed_default_widget()` in `plugin.py` — that one seeds lazily, over
|
|
28
|
+
-- the API, from one plugin process's own ASGI lifespan; this one seeds every
|
|
29
|
+
-- known tenant in a single statement, from the instance's own deploy step.
|
|
30
|
+
-- Both are legitimate; which one to use for YOUR plugin is exactly the
|
|
31
|
+
-- question BiffoPluginBase's class docstring answers.
|
|
32
|
+
INSERT INTO example_widgets (id, tenant_id, name, description, is_active, created_at, updated_at)
|
|
33
|
+
SELECT
|
|
34
|
+
gen_random_uuid()::text,
|
|
35
|
+
known_tenant.tenant_id,
|
|
36
|
+
'starter-widget',
|
|
37
|
+
'Created by this plugin''s baseline seed. Safe to delete.',
|
|
38
|
+
true,
|
|
39
|
+
now(),
|
|
40
|
+
now()
|
|
41
|
+
FROM (SELECT DISTINCT tenant_id FROM users) AS known_tenant
|
|
42
|
+
WHERE NOT EXISTS (
|
|
43
|
+
SELECT 1
|
|
44
|
+
FROM example_widgets existing
|
|
45
|
+
WHERE existing.tenant_id = known_tenant.tenant_id
|
|
46
|
+
AND existing.name = 'starter-widget'
|
|
47
|
+
);
|
|
@@ -284,6 +284,25 @@
|
|
|
284
284
|
},
|
|
285
285
|
"description": "Portal UI elements the plugin adds."
|
|
286
286
|
},
|
|
287
|
+
"seed": {
|
|
288
|
+
"type": "object",
|
|
289
|
+
"additionalProperties": false,
|
|
290
|
+
"required": ["dir"],
|
|
291
|
+
"properties": {
|
|
292
|
+
"dir": {
|
|
293
|
+
"type": "string",
|
|
294
|
+
"pattern": "^[\\w][\\w./-]*$",
|
|
295
|
+
"description": "Plugin-relative directory of baseline-seed .sql files (ADR-0005 DDL import). `biffo plugin install`/`upgrade` vendor every *.sql file directly under this directory (non-recursive) into the instance's db/imports/_plugin-<name>/, where the instance's existing deploy step applies it idempotently on every deploy via ddl_import_history checksum tracking. IDEMPOTENCY CONTRACT: files here must be safe to re-run — an INSERT ... SELECT ... WHERE NOT EXISTS pattern against a stable natural key, never a bare INSERT. They are checksum-locked once applied: editing an already-shipped file makes the next deploy fail loudly (ADR-0005 section 4) rather than silently re-applying or silently skipping. Ship a new, additively-numbered file for a later change instead of editing one already released."
|
|
296
|
+
},
|
|
297
|
+
"baseline_tables": {
|
|
298
|
+
"type": "array",
|
|
299
|
+
"items": { "type": "string" },
|
|
300
|
+
"default": [],
|
|
301
|
+
"description": "Names of this manifest's own `tables` that the seed DDL in `dir` guarantees are populated for every known tenant. Checked post-deploy (the `biffo:plugin-baseline-check` Lambda event, run from the deploy workflow after DDL imports are applied) — a table listed here with no rows for a tenant this deployment already knows about is a loud, specific deploy failure instead of a silently-empty feature (biffo-template#1554)."
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
"description": "Declares this plugin's tenant-scoped baseline-row seed. Optional — a plugin with no baseline data omits this key entirely, and nothing is vendored or checked for it."
|
|
305
|
+
},
|
|
287
306
|
"tools": {
|
|
288
307
|
"type": "array",
|
|
289
308
|
"items": {
|
|
@@ -64,6 +64,18 @@ class ExamplePlugin(BiffoPluginBase):
|
|
|
64
64
|
async def seed_default_widget(self) -> None:
|
|
65
65
|
"""Create this plugin's baseline row, idempotently.
|
|
66
66
|
|
|
67
|
+
**This is the ASGI-lifespan self-seeding path — for tenant-scoped
|
|
68
|
+
BASELINE TABLE rows (what a fresh install needs before the feature
|
|
69
|
+
works at all), prefer the declared `seed` manifest block instead
|
|
70
|
+
(biffo-template#1554): see `biffo.plugin.json`'s `seed` key and
|
|
71
|
+
`db/seed/000_default_widget.sql`, which seeds this same table for
|
|
72
|
+
every known tenant in one statement via the instance's existing
|
|
73
|
+
DDL-import deploy step, with no running plugin process and no
|
|
74
|
+
per-tenant request needed to trigger it.** This method stays as the
|
|
75
|
+
worked example of the *other* legitimate path — a plugin's own
|
|
76
|
+
runtime config, or anything that genuinely needs to run as this
|
|
77
|
+
plugin's own code rather than as SQL.
|
|
78
|
+
|
|
67
79
|
Nothing in this skeleton calls this — deliberately. **Where you call
|
|
68
80
|
it from depends on what kind of plugin you are building**, and
|
|
69
81
|
neither answer is `on_install()`:
|
|
@@ -88,10 +100,9 @@ class ExamplePlugin(BiffoPluginBase):
|
|
|
88
100
|
idempotent until #1000. Verify your seed; do not assume it.
|
|
89
101
|
|
|
90
102
|
- **An event-only plugin like this one** declares no `api_ingress`,
|
|
91
|
-
so it has no startup at all. Its baseline rows belong in
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
plugins use.
|
|
103
|
+
so it has no startup at all. Its baseline rows belong in the `seed`
|
|
104
|
+
manifest block described above — the mechanism the first-party
|
|
105
|
+
plugins use, now declared rather than a hand-run script.
|
|
95
106
|
|
|
96
107
|
Idempotency: generic CRUD (issue #19) has no upsert — every POST
|
|
97
108
|
creates a new row — so this lists existing widgets first and only
|
|
@@ -284,6 +284,25 @@
|
|
|
284
284
|
},
|
|
285
285
|
"description": "Portal UI elements the plugin adds."
|
|
286
286
|
},
|
|
287
|
+
"seed": {
|
|
288
|
+
"type": "object",
|
|
289
|
+
"additionalProperties": false,
|
|
290
|
+
"required": ["dir"],
|
|
291
|
+
"properties": {
|
|
292
|
+
"dir": {
|
|
293
|
+
"type": "string",
|
|
294
|
+
"pattern": "^[\\w][\\w./-]*$",
|
|
295
|
+
"description": "Plugin-relative directory of baseline-seed .sql files (ADR-0005 DDL import). `biffo plugin install`/`upgrade` vendor every *.sql file directly under this directory (non-recursive) into the instance's db/imports/_plugin-<name>/, where the instance's existing deploy step applies it idempotently on every deploy via ddl_import_history checksum tracking. IDEMPOTENCY CONTRACT: files here must be safe to re-run — an INSERT ... SELECT ... WHERE NOT EXISTS pattern against a stable natural key, never a bare INSERT. They are checksum-locked once applied: editing an already-shipped file makes the next deploy fail loudly (ADR-0005 section 4) rather than silently re-applying or silently skipping. Ship a new, additively-numbered file for a later change instead of editing one already released."
|
|
296
|
+
},
|
|
297
|
+
"baseline_tables": {
|
|
298
|
+
"type": "array",
|
|
299
|
+
"items": { "type": "string" },
|
|
300
|
+
"default": [],
|
|
301
|
+
"description": "Names of this manifest's own `tables` that the seed DDL in `dir` guarantees are populated for every known tenant. Checked post-deploy (the `biffo:plugin-baseline-check` Lambda event, run from the deploy workflow after DDL imports are applied) — a table listed here with no rows for a tenant this deployment already knows about is a loud, specific deploy failure instead of a silently-empty feature (biffo-template#1554)."
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
"description": "Declares this plugin's tenant-scoped baseline-row seed. Optional — a plugin with no baseline data omits this key entirely, and nothing is vendored or checked for it."
|
|
305
|
+
},
|
|
287
306
|
"tools": {
|
|
288
307
|
"type": "array",
|
|
289
308
|
"items": {
|