@biffo/cli 0.313.4 → 0.314.1

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.
@@ -80,8 +80,8 @@
80
80
  }
81
81
  ],
82
82
  "ui_components": [
83
- { "type": "nav-link", "label": "Example Plugin", "path": "/admin/example-plugin" },
84
- { "type": "page", "label": "Widgets", "path": "/admin/example-plugin/widgets" }
83
+ { "type": "nav-link", "label": "Example Plugin", "path": "/admin/plugins/example-plugin" },
84
+ { "type": "page", "label": "Widgets", "path": "/admin/plugins/example-plugin/widgets" }
85
85
  ],
86
86
  "seed": {
87
87
  "dir": "db/seed",
@@ -89,5 +89,17 @@
89
89
  },
90
90
  "dependencies": {
91
91
  "biffo-plugin-sdk": "^1.0"
92
+ },
93
+ "user_ingress": {
94
+ "required_group": "founder",
95
+ "app": "example_plugin.user_app:app"
96
+ },
97
+ "admin_ingress": {
98
+ "required_group": "admin",
99
+ "app": "example_plugin.admin_app:app"
100
+ },
101
+ "user_frontend": {
102
+ "dir": "web/dist",
103
+ "required_group": "founder"
92
104
  }
93
105
  }
@@ -15,6 +15,12 @@ dependencies = [
15
15
  "biffo-plugin-sdk>=1.0,<2.0",
16
16
  "aws-lambda-powertools[tracer]>=3.4.0",
17
17
  "httpx>=0.28.1",
18
+ # ASGI apps for the `user_ingress`/`admin_ingress` surfaces (ADR-0021) —
19
+ # the shared plugin host imports and mounts `<module>:app` directly, so this
20
+ # is the only new runtime dependency the two placeholder apps need (no
21
+ # mangum/pyjwt: the host provides the Lambda entry and enforces
22
+ # `required_group` itself — see user_app.py's docstring).
23
+ "fastapi>=0.115.0",
18
24
  ]
19
25
 
20
26
  [dependency-groups]
@@ -0,0 +1,50 @@
1
+ """Placeholder ASGI app backing this plugin's `admin_ingress` surface (ADR-0021).
2
+
3
+ `biffo.plugin.json`'s `admin_ingress.app` names this module's `app` object. The
4
+ **shared plugin host** imports it, provides the Lambda entry, and mounts it at
5
+ `/api/v1/plugins/<name>/admin/*`, enforcing `admin_ingress.required_group`
6
+ (ADR-0011) before a request ever reaches here — see `user_app.py`'s docstring
7
+ for why this file does no auth of its own.
8
+
9
+ **This is also `web-admin/`'s one reader (biffo-template#2008).** The skeleton
10
+ shipped a full admin frontend at `web-admin/` that no manifest field referenced
11
+ — an orphan directory nothing would ever build or serve. `AdminIngress` in the
12
+ SDK is explicitly "the plugin's admin-gated API and optional static UI bundle":
13
+ unlike `user_frontend` (a separate declarative `dir`, served directly by the
14
+ host with no plugin code involved), the admin surface's static bundle is served
15
+ *by the app this field names* — `web-admin/`'s own `vite.config.ts` already
16
+ targets exactly this mount point (`base:
17
+ '/api/v1/plugins/example-plugin/admin/'`).
18
+
19
+ `web-admin/dist/` is the Vite build output — gitignored, and not built by this
20
+ repo's CI (that wiring is follow-up work; #2008's scope is declaring the
21
+ surfaces, not shipping the built bundle, mirroring `user_frontend.dir` above
22
+ pointing at `web/dist` before that directory exists either — see #2009). The
23
+ mount below is skipped when `dist/` is absent, so a fresh checkout and `pytest`
24
+ need no build step and no edits (this issue's "done when" criterion) — once
25
+ `web-admin` is built, dropping its `dist/` in place is enough to serve it, with
26
+ no further code change.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from pathlib import Path
32
+
33
+ from fastapi import FastAPI
34
+ from fastapi.staticfiles import StaticFiles
35
+
36
+ app = FastAPI(title="example-plugin admin ingress")
37
+
38
+ # src/example_plugin/admin_app.py -> src/example_plugin -> src -> repo root
39
+ # (plugin root, where web-admin/ lives alongside biffo.plugin.json).
40
+ WEB_ADMIN_DIST = Path(__file__).resolve().parent.parent.parent / "web-admin" / "dist"
41
+
42
+
43
+ @app.get("/ping")
44
+ def ping() -> dict[str, str]:
45
+ """Identifies the plugin, so the mount can be proven real rather than guessed at."""
46
+ return {"plugin": "example-plugin", "surface": "admin_ingress"}
47
+
48
+
49
+ if WEB_ADMIN_DIST.is_dir():
50
+ app.mount("/", StaticFiles(directory=str(WEB_ADMIN_DIST), html=True), name="web-admin")
@@ -0,0 +1,31 @@
1
+ """Placeholder ASGI app backing this plugin's `user_ingress` surface (ADR-0021).
2
+
3
+ `biffo.plugin.json`'s `user_ingress.app` names this module's `app` object. The
4
+ **shared plugin host** imports it, provides the Lambda entry, and mounts it at
5
+ `/api/v1/plugins/<name>/*` — a plugin declaring `user_ingress` ships no Lambda
6
+ handler and no infrastructure of its own for this surface (see `terraform/`:
7
+ nothing here needs a per-plugin resource).
8
+
9
+ **No auth in this file, on purpose.** The host enforces `user_ingress.required_group`
10
+ (ADR-0011) before a request ever reaches a mounted app — see
11
+ `biffo_plugin_sdk.user_serving`'s module docstring: its `require_group` dependency
12
+ is only for an `isolated: true` plugin running in its own Lambda outside the
13
+ shared host. Adding a second check here would be redundant at best and a
14
+ divergent enforcement path at worst.
15
+
16
+ This is intentionally a placeholder (biffo-template#2008): one route that
17
+ identifies the plugin, enough to prove the mount is real, not enough to be
18
+ mistaken for a feature. Replace it with your own user-facing routes.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from fastapi import FastAPI
24
+
25
+ app = FastAPI(title="example-plugin user ingress")
26
+
27
+
28
+ @app.get("/ping")
29
+ def ping() -> dict[str, str]:
30
+ """Identifies the plugin, so the mount can be proven real rather than guessed at."""
31
+ return {"plugin": "example-plugin", "surface": "user_ingress"}
@@ -0,0 +1,119 @@
1
+ """Pins the three platform surfaces `biffo.plugin.json` declares (biffo-template#2008).
2
+
3
+ Before this, `_skeletons/plugin-template` declared none of `user_ingress`,
4
+ `admin_ingress` or `user_frontend` — the platform already serves all three
5
+ (the working reference is `keiranholloway/biffo-plugin-ideation`'s manifest),
6
+ but every scaffolded plugin started life exposing none of them. This test
7
+ guards three things at once:
8
+
9
+ 1. the manifest actually validates against the SDK's real `PluginManifest`
10
+ (`extra="forbid"`, so a typo in a block's keys fails loudly rather than
11
+ silently dropping the surface — see `README.md`'s "Manifest validation"
12
+ section for why that model, not `registry-schema.json`, is authoritative);
13
+ 2. each `*_ingress.app` reference actually resolves to a real, importable ASGI
14
+ app — not merely a string that looks right; and
15
+ 3. each placeholder app answers with something that identifies the plugin,
16
+ so the mount is real rather than guessed at (this issue's own "done when").
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import importlib
22
+
23
+ from biffo_plugin_sdk.plugin import load_manifest
24
+ from fastapi import FastAPI
25
+ from fastapi.testclient import TestClient
26
+
27
+ from example_plugin.manifest import MANIFEST_PATH
28
+
29
+
30
+ def _resolve_app(app_ref: str) -> FastAPI:
31
+ """Resolve a manifest `*_ingress.app` string ('<module>:<attr>') the same
32
+ way the shared plugin host would — a plain dynamic import, not a literal
33
+ reference to this test's own imports, so a manifest string that drifted
34
+ from the real module path would be caught here rather than passing by
35
+ coincidence."""
36
+ module_name, _, attr = app_ref.partition(":")
37
+ module = importlib.import_module(module_name)
38
+ return getattr(module, attr)
39
+
40
+
41
+ class TestManifestDeclaresAllThreeSurfaces:
42
+ def test_manifest_validates_and_declares_user_ingress(self) -> None:
43
+ manifest = load_manifest(MANIFEST_PATH)
44
+ assert manifest.user_ingress is not None
45
+ assert manifest.user_ingress.required_group == "founder"
46
+ assert manifest.user_ingress.app == "example_plugin.user_app:app"
47
+
48
+ def test_manifest_declares_admin_ingress(self) -> None:
49
+ manifest = load_manifest(MANIFEST_PATH)
50
+ assert manifest.admin_ingress is not None
51
+ assert manifest.admin_ingress.required_group == "admin"
52
+ assert manifest.admin_ingress.app == "example_plugin.admin_app:app"
53
+
54
+ def test_manifest_declares_user_frontend(self) -> None:
55
+ manifest = load_manifest(MANIFEST_PATH)
56
+ assert manifest.user_frontend is not None
57
+ assert manifest.user_frontend.dir == "web/dist"
58
+ assert manifest.user_frontend.required_group == "founder"
59
+
60
+ def test_ui_components_no_longer_point_at_a_dead_admin_path(self) -> None:
61
+ """The old `/admin/example-plugin` paths matched none of the three real
62
+ surfaces (this issue's own finding) — pin that they now sit under the
63
+ portal's real installed-plugin route instead."""
64
+ manifest = load_manifest(MANIFEST_PATH)
65
+ paths = {c.path for c in manifest.ui_components}
66
+ assert paths == {
67
+ "/admin/plugins/example-plugin",
68
+ "/admin/plugins/example-plugin/widgets",
69
+ }
70
+
71
+
72
+ class TestIngressAppsResolveAndIdentifyThePlugin:
73
+ def test_user_ingress_app_resolves_and_answers(self) -> None:
74
+ manifest = load_manifest(MANIFEST_PATH)
75
+ assert manifest.user_ingress is not None
76
+ app = _resolve_app(manifest.user_ingress.app)
77
+
78
+ response = TestClient(app).get("/ping")
79
+
80
+ assert response.status_code == 200
81
+ assert response.json() == {"plugin": "example-plugin", "surface": "user_ingress"}
82
+
83
+ def test_admin_ingress_app_resolves_and_answers(self) -> None:
84
+ manifest = load_manifest(MANIFEST_PATH)
85
+ assert manifest.admin_ingress is not None
86
+ app = _resolve_app(manifest.admin_ingress.app)
87
+
88
+ response = TestClient(app).get("/ping")
89
+
90
+ assert response.status_code == 200
91
+ assert response.json() == {"plugin": "example-plugin", "surface": "admin_ingress"}
92
+
93
+
94
+ class TestAdminIngressWebAdminOrphanResolution:
95
+ """`web-admin/` had no manifest field referencing it (this issue's own
96
+ finding). `admin_app.py` is now its one reader — these pin that the
97
+ reference is real (the directory `admin_app` looks for is exactly
98
+ `web-admin/`, not a typo'd sibling) and that a repo state with no built
99
+ `dist/` yet (true of this skeleton and every fresh checkout, since `dist/`
100
+ is gitignored and nothing builds it here — see admin_app.py's docstring)
101
+ still imports and serves cleanly rather than raising at import time.
102
+ """
103
+
104
+ def test_web_admin_dist_path_points_at_the_real_directory(self) -> None:
105
+ from example_plugin.admin_app import WEB_ADMIN_DIST
106
+
107
+ assert WEB_ADMIN_DIST.parent.name == "web-admin"
108
+ assert WEB_ADMIN_DIST.name == "dist"
109
+ assert WEB_ADMIN_DIST.parent.is_dir(), "web-admin/ itself must still exist"
110
+
111
+ def test_static_mount_is_skipped_when_dist_is_absent(self) -> None:
112
+ from example_plugin.admin_app import WEB_ADMIN_DIST, app
113
+
114
+ assert not WEB_ADMIN_DIST.is_dir(), (
115
+ "this test asserts the no-build-yet behaviour; if dist/ has been "
116
+ "built locally, the skip path this test pins is not being exercised"
117
+ )
118
+ response = TestClient(app).get("/some/never-built/asset.js")
119
+ assert response.status_code == 404
@@ -2,6 +2,15 @@ version = 1
2
2
  revision = 3
3
3
  requires-python = ">=3.13"
4
4
 
5
+ [[package]]
6
+ name = "annotated-doc"
7
+ version = "0.0.5"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
12
+ ]
13
+
5
14
  [[package]]
6
15
  name = "annotated-types"
7
16
  version = "0.8.0"
@@ -70,6 +79,7 @@ source = { editable = "." }
70
79
  dependencies = [
71
80
  { name = "aws-lambda-powertools", extra = ["tracer"] },
72
81
  { name = "biffo-plugin-sdk" },
82
+ { name = "fastapi" },
73
83
  { name = "httpx" },
74
84
  ]
75
85
 
@@ -88,6 +98,7 @@ dev = [
88
98
  requires-dist = [
89
99
  { name = "aws-lambda-powertools", extras = ["tracer"], specifier = ">=3.4.0" },
90
100
  { name = "biffo-plugin-sdk", specifier = ">=1.0,<2.0" },
101
+ { name = "fastapi", specifier = ">=0.115.0" },
91
102
  { name = "httpx", specifier = ">=0.28.1" },
92
103
  ]
93
104
 
@@ -398,6 +409,22 @@ wheels = [
398
409
  { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
399
410
  ]
400
411
 
412
+ [[package]]
413
+ name = "fastapi"
414
+ version = "0.141.1"
415
+ source = { registry = "https://pypi.org/simple" }
416
+ dependencies = [
417
+ { name = "annotated-doc" },
418
+ { name = "pydantic" },
419
+ { name = "starlette" },
420
+ { name = "typing-extensions" },
421
+ { name = "typing-inspection" },
422
+ ]
423
+ sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" }
424
+ wheels = [
425
+ { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" },
426
+ ]
427
+
401
428
  [[package]]
402
429
  name = "filelock"
403
430
  version = "3.32.3"
@@ -1005,6 +1032,18 @@ wheels = [
1005
1032
  { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
1006
1033
  ]
1007
1034
 
1035
+ [[package]]
1036
+ name = "starlette"
1037
+ version = "1.6.0"
1038
+ source = { registry = "https://pypi.org/simple" }
1039
+ dependencies = [
1040
+ { name = "anyio" },
1041
+ ]
1042
+ sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" }
1043
+ wheels = [
1044
+ { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
1045
+ ]
1046
+
1008
1047
  [[package]]
1009
1048
  name = "tomli"
1010
1049
  version = "2.4.1"
@@ -67,7 +67,7 @@ importers:
67
67
  version: 6.4.3(@types/node@26.2.0)
68
68
  vitest:
69
69
  specifier: ^4.1.0
70
- version: 4.1.10(@types/node@26.2.0)(jsdom@30.0.1)(vite@6.4.3(@types/node@26.2.0))
70
+ version: 4.1.11(@types/node@26.2.0)(jsdom@30.0.1)(vite@6.4.3(@types/node@26.2.0))
71
71
 
72
72
  packages:
73
73
 
@@ -461,6 +461,9 @@ packages:
461
461
  '@jridgewell/sourcemap-codec@1.5.5':
462
462
  resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
463
463
 
464
+ '@jridgewell/sourcemap-codec@1.6.0':
465
+ resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
466
+
464
467
  '@jridgewell/trace-mapping@0.3.31':
465
468
  resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
466
469
 
@@ -740,11 +743,11 @@ packages:
740
743
  peerDependencies:
741
744
  vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
742
745
 
743
- '@vitest/expect@4.1.10':
744
- resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
746
+ '@vitest/expect@4.1.11':
747
+ resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==}
745
748
 
746
- '@vitest/mocker@4.1.10':
747
- resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==}
749
+ '@vitest/mocker@4.1.11':
750
+ resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==}
748
751
  peerDependencies:
749
752
  msw: ^2.4.9
750
753
  vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -754,20 +757,20 @@ packages:
754
757
  vite:
755
758
  optional: true
756
759
 
757
- '@vitest/pretty-format@4.1.10':
758
- resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
760
+ '@vitest/pretty-format@4.1.11':
761
+ resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==}
759
762
 
760
- '@vitest/runner@4.1.10':
761
- resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
763
+ '@vitest/runner@4.1.11':
764
+ resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==}
762
765
 
763
- '@vitest/snapshot@4.1.10':
764
- resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
766
+ '@vitest/snapshot@4.1.11':
767
+ resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==}
765
768
 
766
- '@vitest/spy@4.1.10':
767
- resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
769
+ '@vitest/spy@4.1.11':
770
+ resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==}
768
771
 
769
- '@vitest/utils@4.1.10':
770
- resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
772
+ '@vitest/utils@4.1.11':
773
+ resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==}
771
774
 
772
775
  acorn-jsx@5.3.2:
773
776
  resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -913,8 +916,8 @@ packages:
913
916
  resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
914
917
  engines: {node: '>=20.19.0'}
915
918
 
916
- es-module-lexer@2.3.1:
917
- resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==}
919
+ es-module-lexer@2.3.2:
920
+ resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
918
921
 
919
922
  esbuild@0.25.12:
920
923
  resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
@@ -1190,8 +1193,8 @@ packages:
1190
1193
  resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==}
1191
1194
  engines: {node: '>=18'}
1192
1195
 
1193
- obug@2.1.4:
1194
- resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
1196
+ obug@2.2.1:
1197
+ resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==}
1195
1198
  engines: {node: '>=12.20.0'}
1196
1199
 
1197
1200
  optionator@0.9.4:
@@ -1231,6 +1234,10 @@ packages:
1231
1234
  resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
1232
1235
  engines: {node: '>=12'}
1233
1236
 
1237
+ picomatch@4.0.7:
1238
+ resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
1239
+ engines: {node: '>=12'}
1240
+
1234
1241
  postcss@8.5.26:
1235
1242
  resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
1236
1243
  engines: {node: ^10 || ^12 || >=14}
@@ -1335,8 +1342,8 @@ packages:
1335
1342
  tinybench@2.9.0:
1336
1343
  resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
1337
1344
 
1338
- tinyexec@1.3.0:
1339
- resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
1345
+ tinyexec@1.3.1:
1346
+ resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
1340
1347
  engines: {node: '>=18'}
1341
1348
 
1342
1349
  tinyglobby@0.2.17:
@@ -1452,20 +1459,20 @@ packages:
1452
1459
  yaml:
1453
1460
  optional: true
1454
1461
 
1455
- vitest@4.1.10:
1456
- resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==}
1462
+ vitest@4.1.11:
1463
+ resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==}
1457
1464
  engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
1458
1465
  hasBin: true
1459
1466
  peerDependencies:
1460
1467
  '@edge-runtime/vm': '*'
1461
1468
  '@opentelemetry/api': ^1.9.0
1462
1469
  '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
1463
- '@vitest/browser-playwright': 4.1.10
1464
- '@vitest/browser-preview': 4.1.10
1465
- '@vitest/browser-webdriverio': 4.1.10
1466
- '@vitest/coverage-istanbul': 4.1.10
1467
- '@vitest/coverage-v8': 4.1.10
1468
- '@vitest/ui': 4.1.10
1470
+ '@vitest/browser-playwright': 4.1.11
1471
+ '@vitest/browser-preview': 4.1.11
1472
+ '@vitest/browser-webdriverio': 4.1.11
1473
+ '@vitest/coverage-istanbul': 4.1.11
1474
+ '@vitest/coverage-v8': 4.1.11
1475
+ '@vitest/ui': 4.1.11
1469
1476
  happy-dom: '*'
1470
1477
  jsdom: '*'
1471
1478
  vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -1887,6 +1894,8 @@ snapshots:
1887
1894
 
1888
1895
  '@jridgewell/sourcemap-codec@1.5.5': {}
1889
1896
 
1897
+ '@jridgewell/sourcemap-codec@1.6.0': {}
1898
+
1890
1899
  '@jridgewell/trace-mapping@0.3.31':
1891
1900
  dependencies:
1892
1901
  '@jridgewell/resolve-uri': 3.1.2
@@ -2162,44 +2171,44 @@ snapshots:
2162
2171
  transitivePeerDependencies:
2163
2172
  - supports-color
2164
2173
 
2165
- '@vitest/expect@4.1.10':
2174
+ '@vitest/expect@4.1.11':
2166
2175
  dependencies:
2167
2176
  '@standard-schema/spec': 1.1.0
2168
2177
  '@types/chai': 5.2.3
2169
- '@vitest/spy': 4.1.10
2170
- '@vitest/utils': 4.1.10
2178
+ '@vitest/spy': 4.1.11
2179
+ '@vitest/utils': 4.1.11
2171
2180
  chai: 6.2.2
2172
2181
  tinyrainbow: 3.1.1
2173
2182
 
2174
- '@vitest/mocker@4.1.10(vite@6.4.3(@types/node@26.2.0))':
2183
+ '@vitest/mocker@4.1.11(vite@6.4.3(@types/node@26.2.0))':
2175
2184
  dependencies:
2176
- '@vitest/spy': 4.1.10
2185
+ '@vitest/spy': 4.1.11
2177
2186
  estree-walker: 3.0.3
2178
2187
  magic-string: 0.30.21
2179
2188
  optionalDependencies:
2180
2189
  vite: 6.4.3(@types/node@26.2.0)
2181
2190
 
2182
- '@vitest/pretty-format@4.1.10':
2191
+ '@vitest/pretty-format@4.1.11':
2183
2192
  dependencies:
2184
2193
  tinyrainbow: 3.1.1
2185
2194
 
2186
- '@vitest/runner@4.1.10':
2195
+ '@vitest/runner@4.1.11':
2187
2196
  dependencies:
2188
- '@vitest/utils': 4.1.10
2197
+ '@vitest/utils': 4.1.11
2189
2198
  pathe: 2.0.3
2190
2199
 
2191
- '@vitest/snapshot@4.1.10':
2200
+ '@vitest/snapshot@4.1.11':
2192
2201
  dependencies:
2193
- '@vitest/pretty-format': 4.1.10
2194
- '@vitest/utils': 4.1.10
2202
+ '@vitest/pretty-format': 4.1.11
2203
+ '@vitest/utils': 4.1.11
2195
2204
  magic-string: 0.30.21
2196
2205
  pathe: 2.0.3
2197
2206
 
2198
- '@vitest/spy@4.1.10': {}
2207
+ '@vitest/spy@4.1.11': {}
2199
2208
 
2200
- '@vitest/utils@4.1.10':
2209
+ '@vitest/utils@4.1.11':
2201
2210
  dependencies:
2202
- '@vitest/pretty-format': 4.1.10
2211
+ '@vitest/pretty-format': 4.1.11
2203
2212
  convert-source-map: 2.0.0
2204
2213
  tinyrainbow: 3.1.1
2205
2214
 
@@ -2331,7 +2340,7 @@ snapshots:
2331
2340
 
2332
2341
  entities@8.0.0: {}
2333
2342
 
2334
- es-module-lexer@2.3.1: {}
2343
+ es-module-lexer@2.3.2: {}
2335
2344
 
2336
2345
  esbuild@0.25.12:
2337
2346
  optionalDependencies:
@@ -2590,7 +2599,7 @@ snapshots:
2590
2599
 
2591
2600
  magic-string@0.30.21:
2592
2601
  dependencies:
2593
- '@jridgewell/sourcemap-codec': 1.5.5
2602
+ '@jridgewell/sourcemap-codec': 1.6.0
2594
2603
 
2595
2604
  mdn-data@2.27.1: {}
2596
2605
 
@@ -2616,7 +2625,7 @@ snapshots:
2616
2625
 
2617
2626
  node-releases@2.0.53: {}
2618
2627
 
2619
- obug@2.1.4: {}
2628
+ obug@2.2.1: {}
2620
2629
 
2621
2630
  optionator@0.9.4:
2622
2631
  dependencies:
@@ -2653,6 +2662,8 @@ snapshots:
2653
2662
 
2654
2663
  picomatch@4.0.5: {}
2655
2664
 
2665
+ picomatch@4.0.7: {}
2666
+
2656
2667
  postcss@8.5.26:
2657
2668
  dependencies:
2658
2669
  nanoid: 3.3.18
@@ -2759,7 +2770,7 @@ snapshots:
2759
2770
 
2760
2771
  tinybench@2.9.0: {}
2761
2772
 
2762
- tinyexec@1.3.0: {}
2773
+ tinyexec@1.3.1: {}
2763
2774
 
2764
2775
  tinyglobby@0.2.17:
2765
2776
  dependencies:
@@ -2837,24 +2848,24 @@ snapshots:
2837
2848
  '@types/node': 26.2.0
2838
2849
  fsevents: 2.3.3
2839
2850
 
2840
- vitest@4.1.10(@types/node@26.2.0)(jsdom@30.0.1)(vite@6.4.3(@types/node@26.2.0)):
2851
+ vitest@4.1.11(@types/node@26.2.0)(jsdom@30.0.1)(vite@6.4.3(@types/node@26.2.0)):
2841
2852
  dependencies:
2842
- '@vitest/expect': 4.1.10
2843
- '@vitest/mocker': 4.1.10(vite@6.4.3(@types/node@26.2.0))
2844
- '@vitest/pretty-format': 4.1.10
2845
- '@vitest/runner': 4.1.10
2846
- '@vitest/snapshot': 4.1.10
2847
- '@vitest/spy': 4.1.10
2848
- '@vitest/utils': 4.1.10
2849
- es-module-lexer: 2.3.1
2853
+ '@vitest/expect': 4.1.11
2854
+ '@vitest/mocker': 4.1.11(vite@6.4.3(@types/node@26.2.0))
2855
+ '@vitest/pretty-format': 4.1.11
2856
+ '@vitest/runner': 4.1.11
2857
+ '@vitest/snapshot': 4.1.11
2858
+ '@vitest/spy': 4.1.11
2859
+ '@vitest/utils': 4.1.11
2860
+ es-module-lexer: 2.3.2
2850
2861
  expect-type: 1.4.0
2851
2862
  magic-string: 0.30.21
2852
- obug: 2.1.4
2863
+ obug: 2.2.1
2853
2864
  pathe: 2.0.3
2854
- picomatch: 4.0.5
2865
+ picomatch: 4.0.7
2855
2866
  std-env: 4.2.0
2856
2867
  tinybench: 2.9.0
2857
- tinyexec: 1.3.0
2868
+ tinyexec: 1.3.1
2858
2869
  tinyglobby: 0.2.17
2859
2870
  tinyrainbow: 3.1.1
2860
2871
  vite: 6.4.3(@types/node@26.2.0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.313.4",
3
+ "version": "0.314.1",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",