Lean build: always ship core (manifest-less) frontends

A frontend dir under plugins/ with no manifest.json is a CORE feature, not a
per-site plugin - applications is one (backend is shopdb/core/api/applications.py,
nav is advertised as core in dashboard.py). stage-frontend.mjs treated it like
a plugin and dropped it under SITE_PLUGINS, so a lean site showed the core
Applications nav item but had no route for it -> blank page. Now manifest-less
frontends always stage regardless of SITE_PLUGINS; SITE_PLUGINS selection applies
only to real plugins. CI lean-build job asserts ApplicationsList ships in a lean
bundle. Found while testing a live machines+printers lean site.
This commit is contained in:
cproudlock
2026-07-19 12:10:40 -04:00
parent c386e211df
commit d009ac94fb
2 changed files with 20 additions and 4 deletions

View File

@@ -82,6 +82,11 @@ jobs:
echo "FAIL: chosen-plugin code '$code' missing from the lean bundle"
exit 1; }
done
# Core frontends (no manifest, e.g. applications) must ship in EVERY
# build regardless of SITE_PLUGINS, or a lean site loses a core page.
grep -rqoh "ApplicationsList" "$assets"/*.js || {
echo "FAIL: core page 'ApplicationsList' missing from the lean bundle"
exit 1; }
test -d /tmp/leansite/plugins/machines
test ! -d /tmp/leansite/plugins/printedparts
echo "lean build verified: only chosen plugins present"

View File

@@ -5,7 +5,9 @@
// plugins' frontend dirs into frontend/src/.plugins-staged/<name>/ and codegens
// frontend/src/router/routes.gen.js, which the router imports. A per-site build
// selects plugins via the SITE_PLUGINS env (comma-separated); with none set,
// every plugin that has a frontend/ is staged (the default full build).
// every plugin that has a frontend/ is staged (the default full build). A
// frontend dir with no manifest.json is a CORE feature and is ALWAYS staged
// regardless of SITE_PLUGINS.
//
// Both outputs are generated (gitignored). Run by npm predev/prebuild.
@@ -26,9 +28,18 @@ const withFrontend = readdirSync(pluginsDir, { withFileTypes: true })
&& existsSync(join(pluginsDir, entry.name, 'frontend', 'routes.js')))
.map(entry => entry.name)
const chosen = requested.length
? withFrontend.filter(name => requested.includes(name))
: withFrontend
// A frontend dir with no manifest.json is a CORE feature (e.g. applications),
// not a per-site plugin - it MUST always ship, or a lean build loses a core
// page (its nav is advertised by the core, so the route would 404 to a blank
// screen). SITE_PLUGINS selection applies only to real plugins (those with a
// manifest).
const isPlugin = name => existsSync(join(pluginsDir, name, 'manifest.json'))
const coreFrontend = withFrontend.filter(name => !isPlugin(name))
const pluginFrontend = withFrontend.filter(isPlugin)
const chosenPlugins = requested.length
? pluginFrontend.filter(name => requested.includes(name))
: pluginFrontend
const chosen = [...coreFrontend, ...chosenPlugins]
// Copy each chosen plugin's frontend dir into the staging area (clean first so
// a removed plugin does not linger).