feat: Effect services + HttpApi implementation + kit entry/CLI/dev server

- RomConfig/RomCache on the kit's ConfigService/CacheStore; RomSync wires the
  pure domain into the kit SyncEngine (poll/watch/coalesce/fingerprint) and
  ProviderClient; EngineStatus assembly shared by REST + SSE
- makeApi: HttpApiBuilder groups over live service values (handler runtime
  shares the plugin runtime's singletons) + sseRoute status feed
- index.ts: definePluginKit entry (async-main boundary); headless-first (UI
  serve failure logged, engine continues)
- cli.ts on the kit dispatcher: scan/detect/preview offline, sync/uninstall
  online; set-password is gone with the standalone server
- dev.ts: auth-free loopback API server (:5885) — the dev:live proxy target;
  never bundled
- verified live host-less: raw config round-trip on disk, status/platforms/
  preview endpoints, soft-fail reconcile without a host
- CI: workspace install, per-package typecheck, publish from plugin/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 19:04:25 +02:00
parent d6cf5f3d36
commit 899028e20f
43 changed files with 811 additions and 1558 deletions
+1 -1
View File
@@ -5,8 +5,8 @@ import {
LibretroProvider,
sanitizeName,
} from "../src/art/libretro.js";
import { parseTitle } from "../src/domain/titles.js";
import { platformById } from "../src/domain/platforms.js";
import { parseTitle } from "../src/domain/titles.js";
const snes = platformById("snes")!;
const nswitch = platformById("switch")!;
+5 -1
View File
@@ -1,5 +1,9 @@
import { describe, expect, test } from "bun:test";
import { quotePosix, quoteWindows, renderCommand } from "../src/domain/quote.js";
import {
quotePosix,
quoteWindows,
renderCommand,
} from "../src/domain/quote.js";
describe("quotePosix", () => {
test("wraps a plain path in single quotes", () => {
+1 -1
View File
@@ -1,9 +1,9 @@
import { describe, expect, test } from "bun:test";
import { resolveConfig } from "@rom-manager/contract";
import type { ArtVerdict } from "../src/art/provider.js";
import type { DetectedEmulator } from "../src/domain/emulators.js";
import { computeDesired } from "../src/domain/reconcile.js";
import type { RomCandidate } from "../src/domain/scanner.js";
import type { ArtVerdict } from "../src/art/provider.js";
const RA: DetectedEmulator = {
id: "retroarch",
+1 -1
View File
@@ -1,11 +1,11 @@
import { describe, expect, test } from "bun:test";
import { platformById } from "../src/domain/platforms.js";
import {
type DirEnt,
makeExcluder,
type ScanFs,
scanRoot,
} from "../src/domain/scanner.js";
import { platformById } from "../src/domain/platforms.js";
/** A synthetic filesystem from a flat `{ absPath: contents }` map (dirs inferred). */
const synthFs = (files: Record<string, string>): ScanFs => {
+1 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test";
import { SteamGridDbProvider } from "../src/art/steamgriddb.js";
import { parseTitle } from "../src/domain/titles.js";
import { platformById } from "../src/domain/platforms.js";
import { parseTitle } from "../src/domain/titles.js";
const snes = platformById("snes")!;