Files
punktfunk/plugin-kit/src/wire.ts
T
enricobuehlerandClaude Opus 5 1ee06defa6
apple / swift (push) Successful in 5m38s
windows-host / package (push) Successful in 19m0s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m4s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m15s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m21s
ci / bench (push) Successful in 5m41s
ci / rust-arm64 (push) Successful in 9m32s
ci / rust (push) Failing after 12m19s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m46s
android / android (push) Successful in 17m49s
decky / build-publish (push) Successful in 1m11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 16s
arch / build-publish (push) Successful in 19m46s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 39s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 22s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 5m22s
deb / build-publish (push) Successful in 9m9s
deb / build-publish-client-arm64 (push) Successful in 7m26s
deb / build-publish-host (push) Successful in 9m46s
flatpak / build-publish (push) Failing after 8m33s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m56s
docker / build-push-arm64cross (push) Successful in 9s
docker / deploy-docs (push) Successful in 11s
apple / screenshots (push) Successful in 25m51s
feat(library): descriptive metadata on every entry — platform, year, genres, and friends
Emulation-and-beyond libraries need more than a title and a poster. Every
library shape (GameEntry, CustomEntry, CustomInput, ProviderEntryInput)
now carries a shared, flattened GameMeta: platform, description,
developer, publisher, release_year, genres, tags, region, players. All
fields are optional and flat on the wire, so existing library.json files,
provider plugins, and clients keep working unchanged.

- Installed-store scanners (Steam, Lutris, Heroic, Epic, GOG, Xbox) stamp
  platform=PC; custom/provider entries carry whatever was authored.
- GET /library grows a ?platform= filter (case-insensitive) beside
  ?provider=.
- Console: the add/edit form gets a Details section (round-tripping every
  field, since update replaces the entry), the poster tile a platform
  badge (non-PC only — the store badge already implies PC) and the year.
- plugin-kit: ProviderEntry accepts the same fields (new GameMeta schema,
  spread flat); SDK + OpenAPI spec regenerated.
- pf-client-core decodes platform for future client badges.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 19:00:30 +02:00

82 lines
3.8 KiB
TypeScript

// The library-provider wire schemas — a browser-safe module (no node imports) so plugin
// CONTRACTS can share these types with their UIs. Mirrors the host's `ProviderEntryInput`
// (crates/punktfunk-host mgmt/library.rs). Identity codecs: plain JSON shapes, so values
// pass through unencoded; the value is the shared type + authoring validation.
import { Schema } from "effect";
export const Artwork = Schema.Struct({
portrait: Schema.optionalKey(Schema.NullOr(Schema.String)),
hero: Schema.optionalKey(Schema.NullOr(Schema.String)),
logo: Schema.optionalKey(Schema.NullOr(Schema.String)),
header: Schema.optionalKey(Schema.NullOr(Schema.String)),
});
export type Artwork = typeof Artwork.Type;
export const LaunchSpec = Schema.Struct({
kind: Schema.Literal("command"),
value: Schema.String,
});
export type LaunchSpec = typeof LaunchSpec.Type;
export const PrepStep = Schema.Struct({
do: Schema.String,
undo: Schema.optionalKey(Schema.NullOr(Schema.String)),
});
export type PrepStep = typeof PrepStep.Type;
/**
* How the host should recognize a title's process once it is running.
*
* Every field is optional, and omitting the whole thing is fine: the host tracks the process it
* spawns for the entry anyway. It matters when your launch command hands off and exits — a launcher
* client, a `flatpak run`, a front-end that starts an emulator — because then the host has nothing
* left to watch, and the two behaviors this feeds ("end the session when the game exits" and "end the
* game when the session ends") go quiet for that title.
*
* Send whatever you actually know. `install_dir` is the one worth sending if you send only one: any
* process running from under it counts as the game.
*/
export const DetectHint = Schema.Struct({
/** Where the title is installed (absolute path on the host). */
install_dir: Schema.optionalKey(Schema.NullOr(Schema.String)),
/** The game's own executable (absolute path on the host). */
exe: Schema.optionalKey(Schema.NullOr(Schema.String)),
/** The executable's file name (`Hades.exe`), when its location isn't fixed. Weakest signal. */
process_name: Schema.optionalKey(Schema.NullOr(Schema.String)),
});
export type DetectHint = typeof DetectHint.Type;
/** Descriptive metadata, flat on the wire beside `title` (mirrors the host's flattened
* `GameMeta`). All fields optional; values are free-form display strings — the host does not
* normalize platform/genre vocabularies. */
export const GameMeta = Schema.Struct({
/** The system the title runs on — `"PS2"`, `"Xbox 360"`, `"SNES"`, … */
platform: Schema.optionalKey(Schema.NullOr(Schema.String)),
/** Short blurb for a details pane. */
description: Schema.optionalKey(Schema.NullOr(Schema.String)),
developer: Schema.optionalKey(Schema.NullOr(Schema.String)),
publisher: Schema.optionalKey(Schema.NullOr(Schema.String)),
/** Year of first release. */
release_year: Schema.optionalKey(Schema.NullOr(Schema.Number)),
/** Genre taxonomy from the metadata source (`"RPG"`, `"Platformer"`, …). */
genres: Schema.optionalKey(Schema.Array(Schema.String)),
/** Free-form organizational labels (`"co-op"`, `"kids"`, …). */
tags: Schema.optionalKey(Schema.Array(Schema.String)),
/** Release region — `"NTSC-U"`, `"PAL"`, `"NTSC-J"`. */
region: Schema.optionalKey(Schema.NullOr(Schema.String)),
/** Maximum simultaneous (local) players. */
players: Schema.optionalKey(Schema.NullOr(Schema.Number)),
});
export type GameMeta = typeof GameMeta.Type;
export const ProviderEntry = Schema.Struct({
external_id: Schema.String,
title: Schema.String,
art: Schema.optionalKey(Artwork),
launch: Schema.optionalKey(Schema.NullOr(LaunchSpec)),
prep: Schema.optionalKey(Schema.Array(PrepStep)),
detect: Schema.optionalKey(DetectHint),
...GameMeta.fields,
});
export type ProviderEntry = typeof ProviderEntry.Type;