doxa-maps/internal/check-targets.mjs
Doxa Maps 7bed09f2a1
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Update from source 7c360016f
2026-09-28 07:37:08 +00:00

75 lines
4.3 KiB
JavaScript

#!/usr/bin/env node
/**
* check-targets — where each plugin runs, as its <profile>.meta.json "targets" says. Headless.
*
* Rules (plugin-targets.mjs holds the shared reading):
* - targets, when declared, is a non-empty list of known values: web, mobile, desktop, widget.
* Absent = web: a meta never has to declare it.
* - "widget" in targets and a meta "widget": { "corner" } go together (the Studio mounts a widget by its corner).
* - "backend": "backend/<name>" names an existing folder (desktop:build packages it as the app's sidecar).
* - A plugin that is not built for the web (targets without "web") is NOT in the built web tree: no
* public/<tree>/<bundle>/ folder and no manifest entry, so neither the /cdn export nor the Research
* Gallery can carry it. desktop:build packages it instead.
* --self-test plants each violation and confirms it is caught.
* EXIT 0 pass · 1 fail
*/
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'
import { join, resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { KNOWN_TARGETS, profileTargets, isWebBuilt } from './plugin-targets.mjs'
import { TREE_NAME } from './_tree-name.mjs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
// One profile's meta against the rules; returns the problems.
export function metaProblems(where, meta, backendExists = (p) => existsSync(join(ROOT, p))) {
const out = []
const t = meta.targets
if (t !== undefined) {
if (!Array.isArray(t) || !t.length) out.push(`${where}: "targets" must be a non-empty list`)
else for (const v of t) if (!KNOWN_TARGETS.includes(v)) out.push(`${where}: unknown target "${v}" (known: ${KNOWN_TARGETS.join(', ')})`)
if (Array.isArray(t) && t.includes('widget') && !meta.widget?.corner) out.push(`${where}: targets include "widget" but there is no "widget": { "corner" }`)
if (Array.isArray(t) && meta.widget && !t.includes('widget')) out.push(`${where}: has a "widget" corner but its targets leave out "widget"`)
}
if (meta.backend !== undefined && !(typeof meta.backend === 'string' && backendExists(meta.backend))) out.push(`${where}: "backend" names no folder ${meta.backend}`)
return out
}
if (process.argv.includes('--self-test')) {
const planted = [
['unknown value', { targets: ['tv'] }],
['empty list', { targets: [] }],
['widget without corner', { targets: ['widget'] }],
['corner without widget target', { targets: ['desktop'], widget: { corner: 'bottom-right' } }],
['missing backend', { targets: ['desktop'], backend: 'backend/no-such-engine' }],
]
let missed = 0
for (const [name, meta] of planted) if (!metaProblems('planted', meta, () => false).length) { missed++; console.log(` MISSED ${name}`) }
if (metaProblems('ok', { targets: ['desktop', 'widget'], widget: { corner: 'bottom-right' }, backend: 'backend/x' }, () => true).length) { missed++; console.log(' FALSE ALARM on a valid meta') }
if (missed) { console.log(`TARGETS self-test: FAIL — ${missed}`); process.exit(1) }
console.log(`TARGETS self-test: PASS — ${planted.length} planted violations caught, a valid meta passes.`)
process.exit(0)
}
const problems = []
const DESIGNS = join(ROOT, 'plugin-designs')
const TREE = join(ROOT, 'public', TREE_NAME)
let manifest = null
try { manifest = JSON.parse(readFileSync(join(TREE, 'manifest.json'), 'utf8')) } catch { /* no built tree: tree rules skipped */ }
let declared = 0, notWeb = 0
for (const b of readdirSync(DESIGNS)) {
const dir = join(DESIGNS, b)
if (!statSync(dir).isDirectory()) continue
for (const p of profileTargets(dir)) {
if (p.targets) declared++
problems.push(...metaProblems(`${b}/${p.profile}.meta.json`, p.meta))
}
if (!isWebBuilt(dir)) {
notWeb++
if (existsSync(join(TREE, b))) problems.push(`${b} is not a web plugin but public/${TREE_NAME}/${b}/ exists (it would ship in the /cdn export)`)
if (manifest && (manifest.bundles || []).some((x) => x.bundle === b)) problems.push(`${b} is not a web plugin but the built manifest lists it (the gallery would offer it)`)
}
}
for (const p of problems) console.log(' FAIL ' + p)
if (problems.length) { console.log(`TARGETS: FAIL — ${problems.length} problem(s).`); process.exit(1) }
console.log(`TARGETS: PASS — ${declared} profile(s) declare targets (absent = web); ${notWeb} non-web plugin(s), none in the built web tree.`)