ADR-013 Phase 4: frontend staging machinery + relocate printedparts; fix router crash
The staging step that makes lean per-site frontend builds possible, plus the
first plugin relocated as the pilot.
- scripts/stage-frontend.mjs: copies each chosen plugin's plugins/<name>/frontend/
into frontend/src/.plugins-staged/<name>/ and codegens routes.gen.js. Plugin
selection via SITE_PLUGINS (comma-separated); empty = all plugins that have a
frontend/ (the full build). Wired as npm predev/prebuild; outputs gitignored.
- Router imports routes.gen.js and merges staged routes with the in-tree
./routes/*.js glob - dual-location during the transition.
- printedparts relocated: its 6 views (list/detail/form/kiosk + the settings and
labels views from the shared dirs) moved into plugins/printedparts/frontend/
views/, core imports rewritten to the @/ alias; routes.js is the self-contained
route module. Its old in-tree route file is removed.
Also fixes a crash the previous commit (37c764b) shipped: slides.js exports only
`toplevel` (its child routes live in core.js), so the router's
flatMap(m => m.default) produced an undefined child and threw
"Cannot read properties of undefined (reading 'path')" at load - the whole SPA
went blank. Guarded with `m.default || []`. (The earlier "print pages are blank"
reading was this crash, not page nature.)
Verified live: /machines renders again; the relocated /printedparts list renders
identically from the staged plugin frontend; SITE_PLUGINS=machines excludes
printedparts from routes.gen. Build (via npm, runs stage) + vitest + naming green.
This commit is contained in:
61
scripts/stage-frontend.mjs
Normal file
61
scripts/stage-frontend.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
// Stage plugin frontends into the Vite tree (ADR-013 Phase 4).
|
||||
//
|
||||
// Each plugin that owns UI keeps it self-contained under
|
||||
// plugins/<name>/frontend/ (routes.js + views/). This script copies the CHOSEN
|
||||
// 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).
|
||||
//
|
||||
// Both outputs are generated (gitignored). Run by npm predev/prebuild.
|
||||
|
||||
import { readdirSync, existsSync, rmSync, cpSync, writeFileSync, mkdirSync } from 'fs'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const pluginsDir = join(repoRoot, 'plugins')
|
||||
const stagedDir = join(repoRoot, 'frontend', 'src', '.plugins-staged')
|
||||
const genFile = join(repoRoot, 'frontend', 'src', 'router', 'routes.gen.js')
|
||||
|
||||
const requested = (process.env.SITE_PLUGINS || '')
|
||||
.split(',').map(s => s.trim()).filter(Boolean)
|
||||
|
||||
const withFrontend = readdirSync(pluginsDir, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory()
|
||||
&& existsSync(join(pluginsDir, entry.name, 'frontend', 'routes.js')))
|
||||
.map(entry => entry.name)
|
||||
|
||||
const chosen = requested.length
|
||||
? withFrontend.filter(name => requested.includes(name))
|
||||
: withFrontend
|
||||
|
||||
// Copy each chosen plugin's frontend dir into the staging area (clean first so
|
||||
// a removed plugin does not linger).
|
||||
rmSync(stagedDir, { recursive: true, force: true })
|
||||
mkdirSync(stagedDir, { recursive: true })
|
||||
for (const name of chosen) {
|
||||
cpSync(join(pluginsDir, name, 'frontend'), join(stagedDir, name),
|
||||
{ recursive: true })
|
||||
}
|
||||
|
||||
// Codegen the aggregation the router imports. `toplevel` is optional per plugin.
|
||||
const lines = ['// AUTO-GENERATED by scripts/stage-frontend.mjs - do not edit.']
|
||||
const childParts = []
|
||||
const topParts = []
|
||||
for (const name of chosen) {
|
||||
const id = 'p_' + name.replace(/[^a-zA-Z0-9]/g, '_')
|
||||
lines.push(
|
||||
`import ${id}Default, { toplevel as ${id}Top } from `
|
||||
+ `'../.plugins-staged/${name}/routes.js'`)
|
||||
childParts.push(`...(${id}Default || [])`)
|
||||
topParts.push(`...(${id}Top || [])`)
|
||||
}
|
||||
lines.push('')
|
||||
lines.push(`export const stagedChildren = [${childParts.join(', ')}]`)
|
||||
lines.push(`export const stagedToplevel = [${topParts.join(', ')}]`)
|
||||
lines.push('')
|
||||
writeFileSync(genFile, lines.join('\n'))
|
||||
|
||||
console.log(`staged ${chosen.length} plugin frontend(s): `
|
||||
+ (chosen.join(', ') || '(none)'))
|
||||
Reference in New Issue
Block a user