From ae496082b393c4688edef883e998c8008e00a77d Mon Sep 17 00:00:00 2001 From: J3vb Date: Mon, 6 Apr 2026 23:25:16 +0200 Subject: [PATCH] feat(phase-bc): TOML plugin manifests + Solid vitest preset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- Client/tauri-client/vitest.config.ts | 13 +++++++++- PHASE_BC_LOCAL_TODO.md | 14 +++++----- Server/go.mod | 1 + Server/go.sum | 2 ++ Server/plugin/loader.go | 30 ++++++++++++++------- Server/plugin/manifest_nottoml.go | 10 +++++++ Server/plugin/manifest_toml.go | 39 ++++++++++++++++++++++++++++ 7 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 Server/plugin/manifest_nottoml.go create mode 100644 Server/plugin/manifest_toml.go diff --git a/Client/tauri-client/vitest.config.ts b/Client/tauri-client/vitest.config.ts index f0af448a..6d04db32 100644 --- a/Client/tauri-client/vitest.config.ts +++ b/Client/tauri-client/vitest.config.ts @@ -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"], diff --git a/PHASE_BC_LOCAL_TODO.md b/PHASE_BC_LOCAL_TODO.md index 66d974b7..f388338c 100644 --- a/PHASE_BC_LOCAL_TODO.md +++ b/PHASE_BC_LOCAL_TODO.md @@ -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` diff --git a/Server/go.mod b/Server/go.mod index ba066fc2..41256ddf 100644 --- a/Server/go.mod +++ b/Server/go.mod @@ -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 diff --git a/Server/go.sum b/Server/go.sum index 95df4429..10b595bf 100644 --- a/Server/go.sum +++ b/Server/go.sum @@ -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= diff --git a/Server/plugin/loader.go b/Server/plugin/loader.go index 80636635..3173739f 100644 --- a/Server/plugin/loader.go +++ b/Server/plugin/loader.go @@ -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 diff --git a/Server/plugin/manifest_nottoml.go b/Server/plugin/manifest_nottoml.go new file mode 100644 index 00000000..9a6938c2 --- /dev/null +++ b/Server/plugin/manifest_nottoml.go @@ -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 +} diff --git a/Server/plugin/manifest_toml.go b/Server/plugin/manifest_toml.go new file mode 100644 index 00000000..4b856bfb --- /dev/null +++ b/Server/plugin/manifest_toml.go @@ -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 +}