feat(phase-bc): TOML plugin manifests + Solid vitest preset

Plugin TOML support (-tags wazero):
- manifest_toml.go: tryLoadPluginTOML using BurntSushi/toml v1.6.0
- manifest_nottoml.go: no-op stub for default build
- loader.go: prefers plugin.toml, falls back to plugin.json

Solid vitest preset:
- vitest.config.ts: add vite-plugin-solid, expand include to
  src/components/solid/**/*.test.tsx — Badge.test.tsx now runs
  automatically as part of npm test (112 files / 3188 tests)
This commit is contained in:
J3vb
2026-04-06 23:25:16 +02:00
parent ddaaadcd43
commit ae496082b3
7 changed files with 91 additions and 18 deletions
+12 -1
View File
@@ -1,7 +1,14 @@
import { defineConfig } from "vitest/config";
import { resolve } from "path";
import solidPlugin from "vite-plugin-solid";
export default defineConfig({
plugins: [
// Transform Solid JSX/TSX for component tests under src/components/solid/.
solidPlugin({
include: ["src/components/solid/**/*.{ts,tsx,js,jsx}"],
}),
],
resolve: {
alias: {
"@lib": resolve(__dirname, "src/lib"),
@@ -13,7 +20,11 @@ export default defineConfig({
},
test: {
environment: "jsdom",
include: ["tests/**/*.test.ts"],
include: [
"tests/**/*.test.ts",
// Solid component tests live next to the source they test.
"src/components/solid/**/*.test.tsx",
],
coverage: {
provider: "v8",
include: ["src/**/*.ts"],
+7 -7
View File
@@ -58,9 +58,9 @@ Still TODO locally:
in containers with native Solid components and delete the old
vanilla DOM utilities (`createComponent`, factory shells) referenced
from `src/components/`.
- [ ] Add a Vitest config preset under `vitest.config.ts` that pulls in
`@solidjs/testing-library` automatically (currently the test imports
it directly).
- [x] Add Vitest config preset: vite-plugin-solid added to vitest.config.ts,
include expanded to pick up src/components/solid/**/*.test.tsx.
Badge.test.tsx now runs automatically (112 files, 3188 tests pass).
---
@@ -189,10 +189,10 @@ Still TODO locally:
with real wazero runtime: lazy ensureRuntimeLocked, WASI host
instantiation, CompileModule + InstantiateModule, JSON-ABI command
dispatch, listExportedCommands via list_commands export.
- [ ] Replace JSON-only manifest parsing with TOML support behind the
`wazero` build tag (the design doc names `plugin.toml`). Add
`github.com/BurntSushi/toml` and a `parseTOML` shim that falls back
to the existing `ParseManifest` if no `plugin.toml` is found.
- [x] Replace JSON-only manifest parsing with TOML support behind the
`wazero` build tag. Added BurntSushi/toml v1.6.0, manifest_toml.go
(wazero) + manifest_nottoml.go (!wazero), loader.go prefers plugin.toml
then falls back to plugin.json.
- [x] Wire `Server/plugin/host_events.go` into the WS pub/sub hub.
Landed: `EventSink.SetBroadcaster`/`Emit` added; hub gains
`SetPluginEventSink`; `deliverBroadcast` calls `sink.Dispatch`
+1
View File
@@ -40,6 +40,7 @@ require (
buf.build/go/protovalidate v1.1.2 // indirect
buf.build/go/protoyaml v0.6.0 // indirect
cel.dev/expr v0.25.1 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
+2
View File
@@ -12,6 +12,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
+20 -10
View File
@@ -49,17 +49,27 @@ func scanPluginDirectory(dir string) ([]foundPlugin, error) {
continue
}
pluginDir := filepath.Join(dir, e.Name())
manifestPath := filepath.Join(pluginDir, "plugin.json")
raw, rdErr := os.ReadFile(manifestPath)
if rdErr != nil {
if os.IsNotExist(rdErr) {
continue
}
return nil, fmt.Errorf("plugin %q: read plugin.json: %w", e.Name(), rdErr)
// Prefer plugin.toml (wazero build) over plugin.json.
manifest, ok, tomlErr := tryLoadPluginTOML(pluginDir)
if tomlErr != nil {
return nil, fmt.Errorf("plugin %q: %w", e.Name(), tomlErr)
}
manifest, parseErr := ParseManifest(raw)
if parseErr != nil {
return nil, fmt.Errorf("plugin %q: %w", e.Name(), parseErr)
if !ok {
// Fall back to plugin.json.
manifestPath := filepath.Join(pluginDir, "plugin.json")
raw, rdErr := os.ReadFile(manifestPath)
if rdErr != nil {
if os.IsNotExist(rdErr) {
continue
}
return nil, fmt.Errorf("plugin %q: read plugin.json: %w", e.Name(), rdErr)
}
var parseErr error
manifest, parseErr = ParseManifest(raw)
if parseErr != nil {
return nil, fmt.Errorf("plugin %q: %w", e.Name(), parseErr)
}
}
// Reject any symlinks anywhere in the plugin directory tree. The asset
// handler enforces that resolved paths stay rooted at pluginDir, but
+10
View File
@@ -0,0 +1,10 @@
//go:build !wazero
// Default build stub — TOML manifest parsing is not compiled in without -tags wazero.
package plugin
// tryLoadPluginTOML always reports "not present" in the default build so the
// loader unconditionally falls through to plugin.json.
func tryLoadPluginTOML(_ string) (*Manifest, bool, error) {
return nil, false, nil
}
+39
View File
@@ -0,0 +1,39 @@
//go:build wazero
// TOML manifest support — compiled only with -tags wazero.
// Plugins may ship either plugin.json or plugin.toml; this file provides
// tryLoadPluginTOML which the loader calls before falling back to JSON.
package plugin
import (
"fmt"
"os"
"path/filepath"
"github.com/BurntSushi/toml"
)
// tryLoadPluginTOML attempts to parse a plugin.toml from pluginDir.
// Returns the parsed Manifest and true on success.
// Returns nil, false if no plugin.toml exists (caller should try plugin.json).
// Returns nil, false plus logs if plugin.toml exists but is malformed — the
// caller will skip the plugin and log the error.
func tryLoadPluginTOML(pluginDir string) (*Manifest, bool, error) {
tomlPath := filepath.Join(pluginDir, "plugin.toml")
raw, err := os.ReadFile(tomlPath)
if os.IsNotExist(err) {
return nil, false, nil // not present; try JSON
}
if err != nil {
return nil, false, fmt.Errorf("read plugin.toml: %w", err)
}
var m Manifest
if _, err := toml.Decode(string(raw), &m); err != nil {
return nil, false, fmt.Errorf("parse plugin.toml: %w", err)
}
if err := m.Validate(); err != nil {
return nil, false, fmt.Errorf("invalid plugin.toml: %w", err)
}
return &m, true, nil
}