@biffo/cli 0.306.0 → 0.307.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 +40 -0
- package/dist/index.js +51 -0
- package/package.json +1 -1
|
@@ -127,6 +127,46 @@ runtime config rather than tenant-scoped table rows:
|
|
|
127
127
|
> [ADR-0011](https://github.com/keiranholloway/biffo-template/blob/main/docs/ADR/0011-authorization-is-a-core-concern.md)
|
|
128
128
|
> (authorization is a Core concern, not a plugin) and no longer exists.
|
|
129
129
|
|
|
130
|
+
## User-facing frontend: the `user_frontend` contract (ADR-0021 §2)
|
|
131
|
+
|
|
132
|
+
If your plugin ships a founder-facing UI, it declares a `user_frontend` block
|
|
133
|
+
in `biffo.plugin.json`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
"user_frontend": {
|
|
137
|
+
"dir": "web/dist",
|
|
138
|
+
"required_group": "founder"
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
and ships **`web/` and nothing else** — a built static bundle (`index.html`
|
|
143
|
+
plus hashed `assets/*`, the ordinary Vite build-output shape) at the
|
|
144
|
+
repo-relative path `dir` names. That is the entire contract. In particular a
|
|
145
|
+
`user_frontend` plugin ships **no** `terraform/` frontend resources and
|
|
146
|
+
**no** `deploy-frontend.yml` workflow of its own.
|
|
147
|
+
|
|
148
|
+
**Who serves it, and why there is nothing to provision.** The shared plugin
|
|
149
|
+
host serves the bundle directly — `GET /api/v1/plugins/<name>/ui` and
|
|
150
|
+
`GET /api/v1/plugins/<name>/ui/{proxy+}` on the API Gateway routes every
|
|
151
|
+
installed plugin already shares — the same way it already serves
|
|
152
|
+
`admin_ingress`'s admin shell. There is no per-plugin S3 origin, no per-plugin
|
|
153
|
+
CloudFront behaviour, and nothing for `biffo plugin install` to provision:
|
|
154
|
+
committing `web/dist/` is the whole deploy. See
|
|
155
|
+
[ADR-0021](../../docs/ADR/0021-shared-plugin-hosting.md) section 2 for the
|
|
156
|
+
full design and why this superseded the older, per-plugin hosting model.
|
|
157
|
+
|
|
158
|
+
**This retires ADR-0018 section 2's per-plugin frontend shape** — a dedicated
|
|
159
|
+
`aws_s3_bucket` behind the shared CloudFront distribution, wired through a
|
|
160
|
+
`cdn_distribution_arn` module variable and a `frontend_bucket_*` output. That
|
|
161
|
+
shape is fail-closed at install time: `biffo plugin install` and
|
|
162
|
+
`biffo plugin upgrade` **refuse** a plugin whose `terraform/` declares it —
|
|
163
|
+
an `aws_s3_bucket` whose name interpolates `-plugin-…-web`, an output named
|
|
164
|
+
`frontend_bucket_*`, or a `cdn_distribution_arn` variable — with an error
|
|
165
|
+
naming ADR-0021 section 2 and this contract. If you are updating an older
|
|
166
|
+
plugin that still ships one of these, delete `terraform/`'s frontend
|
|
167
|
+
resources (and `deploy-frontend.yml`, if present) and adopt `user_frontend`
|
|
168
|
+
above instead; there is nothing left to provision once you do.
|
|
169
|
+
|
|
130
170
|
## Standalone repo, not a monorepo package
|
|
131
171
|
|
|
132
172
|
Unlike biffo-template's own in-monorepo plugins (e.g. `services/_plugins/orchestrator/`,
|
package/dist/index.js
CHANGED
|
@@ -3504,6 +3504,45 @@ function declaredOutputs(moduleDir) {
|
|
|
3504
3504
|
}
|
|
3505
3505
|
return names;
|
|
3506
3506
|
}
|
|
3507
|
+
var RETIRED_FRONTEND_BUCKET_PATTERN = /bucket\s*=\s*"[^"]*-plugin-[^"]*-web"/;
|
|
3508
|
+
function declaresRetiredFrontendBucket(moduleDir) {
|
|
3509
|
+
let entries;
|
|
3510
|
+
try {
|
|
3511
|
+
entries = readdirSync3(moduleDir, { withFileTypes: true });
|
|
3512
|
+
} catch {
|
|
3513
|
+
return false;
|
|
3514
|
+
}
|
|
3515
|
+
for (const entry of entries) {
|
|
3516
|
+
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
3517
|
+
let contents;
|
|
3518
|
+
try {
|
|
3519
|
+
contents = readFileSync10(join13(moduleDir, entry.name), "utf8");
|
|
3520
|
+
} catch {
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
if (RETIRED_FRONTEND_BUCKET_PATTERN.test(contents)) return true;
|
|
3524
|
+
}
|
|
3525
|
+
return false;
|
|
3526
|
+
}
|
|
3527
|
+
function findRetiredFrontendShape(terraformDir) {
|
|
3528
|
+
const reasons = [];
|
|
3529
|
+
if (!existsSync12(terraformDir)) return reasons;
|
|
3530
|
+
if (declaresRetiredFrontendBucket(terraformDir)) {
|
|
3531
|
+
reasons.push('an aws_s3_bucket whose name interpolates "-plugin-...-web"');
|
|
3532
|
+
}
|
|
3533
|
+
for (const name of [...declaredOutputs(terraformDir)].sort()) {
|
|
3534
|
+
if (name.startsWith("frontend_bucket")) {
|
|
3535
|
+
reasons.push(`an output named "${name}"`);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
if (declaredVariables(terraformDir).has("cdn_distribution_arn")) {
|
|
3539
|
+
reasons.push('a "cdn_distribution_arn" variable');
|
|
3540
|
+
}
|
|
3541
|
+
return reasons;
|
|
3542
|
+
}
|
|
3543
|
+
function retiredFrontendShapeError(pluginName, reasons) {
|
|
3544
|
+
return `Plugin '${pluginName}' ships a terraform/ frontend on the retired ADR-0018 \xA72 per-plugin hosting shape: it declares ${reasons.join(", ")}. ADR-0021 \xA72 replaced this \u2014 a user-facing plugin's frontend is served by the shared plugin host directly from the manifest's \`user_frontend\` field (\`{ dir, required_group }\`); it ships \`web/\` and nothing else, with no terraform/ frontend and no deploy-frontend.yml. Update the plugin to the \`user_frontend\` contract (see _skeletons/plugin-template/README.md) before installing it here.`;
|
|
3545
|
+
}
|
|
3507
3546
|
function renderArguments(args, indent) {
|
|
3508
3547
|
const width = Math.max(...args.map(([key]) => key.length));
|
|
3509
3548
|
return args.map(([key, value]) => `${indent}${key.padEnd(width)} = ${value}`).join("\n");
|
|
@@ -9488,6 +9527,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9488
9527
|
log.success(
|
|
9489
9528
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9490
9529
|
);
|
|
9530
|
+
const retiredShapeReasons = findRetiredFrontendShape(join33(source.sourceDir, "terraform"));
|
|
9531
|
+
if (retiredShapeReasons.length > 0) {
|
|
9532
|
+
throw new Error(retiredFrontendShapeError(pluginName, retiredShapeReasons));
|
|
9533
|
+
}
|
|
9491
9534
|
if (inTreeSource) {
|
|
9492
9535
|
log.info(`${relTargetDir}/ is already in this checkout \u2014 installing in place.`);
|
|
9493
9536
|
} else {
|
|
@@ -10256,6 +10299,10 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10256
10299
|
log.success(
|
|
10257
10300
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
10258
10301
|
);
|
|
10302
|
+
const retiredShapeReasons = findRetiredFrontendShape(join38(tmpDir, "terraform"));
|
|
10303
|
+
if (retiredShapeReasons.length > 0) {
|
|
10304
|
+
throw new Error(retiredFrontendShapeError(entry.name, retiredShapeReasons));
|
|
10305
|
+
}
|
|
10259
10306
|
if (!existsSync36(join38(tmpDir, "terraform"))) {
|
|
10260
10307
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, entry.name);
|
|
10261
10308
|
}
|
|
@@ -10370,6 +10417,10 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
10370
10417
|
log.success(
|
|
10371
10418
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
10372
10419
|
);
|
|
10420
|
+
const retiredShapeReasons = findRetiredFrontendShape(join38(source.sourceDir, "terraform"));
|
|
10421
|
+
if (retiredShapeReasons.length > 0) {
|
|
10422
|
+
throw new Error(retiredFrontendShapeError(source.name, retiredShapeReasons));
|
|
10423
|
+
}
|
|
10373
10424
|
if (!existsSync36(join38(source.sourceDir, "terraform"))) {
|
|
10374
10425
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, source.name);
|
|
10375
10426
|
}
|