- Rename all user-facing and technical identifiers from OpenPets/Pet to FamiliarOS/Familiar. - Rename packages from @open-pets/* to @familiaros/*; rename install-pet/pet-format packages. - Rename plugin IDs and directories from openpets.* to familiaros.*. - Rename IPC namespace from openpets:* to familiaros:* and state filenames from openpets-* to familiaros-* with legacy migration. - Rename source files (pet-window, built-in-pet, default-pet-controller, etc.) to familiar equivalents. - Update locales (en, es-419, ja, ko, pt-BR, zh-Hans, zh-Hant) and tray/pet context menu strings. - Add Familiar naming feature: preference, settings input, tray menu display. - Update assets and packaging config; all desktop tests pass.
68 lines
3.7 KiB
TypeScript
68 lines
3.7 KiB
TypeScript
/**
|
|
* Drift guard between the runtime plugin SDK and the published
|
|
* `@familiaros/plugin-sdk` type contract.
|
|
*
|
|
* These are compile-time assertions: if the runtime's plugin-facing surface
|
|
* (namespaces) or the JavaScript permission set ever diverges from what the
|
|
* published package promises authors, `tsc` fails here. Keep the package and
|
|
* the bridge in lockstep.
|
|
*/
|
|
import type { FamiliarOSContext, FamiliarOSPermission } from "@familiaros/plugin-sdk";
|
|
import { readFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import type { PluginJavascriptPermission } from "./plugin-manifest.js";
|
|
import type { PluginSdkApi } from "./plugin-sdk-bridge.js";
|
|
import type { sdkCallHandlers } from "./plugin-js-host.js";
|
|
import { pluginSdkAsyncRoutes, pluginSdkSyncRoutes, type PluginSdkRoute } from "./plugin-sdk-routes.js";
|
|
|
|
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
|
|
type Expect<T extends true> = T;
|
|
|
|
// Every namespace the published SDK exposes (ctx.familiar, ctx.schedule, …) must
|
|
// exist on the runtime API, and the runtime must expose nothing extra.
|
|
type _NamespacesMatch = Expect<Equal<keyof PluginSdkApi, keyof FamiliarOSContext>>;
|
|
|
|
// The JavaScript plugin permission union must match the published contract.
|
|
type _PermissionsMatch = Expect<Equal<PluginJavascriptPermission, FamiliarOSPermission>>;
|
|
type _HostRoutesMatch = Expect<Equal<keyof typeof sdkCallHandlers, PluginSdkRoute>>;
|
|
|
|
// Reference the aliases so unused-type tooling never strips the guard.
|
|
export type PluginSdkConformance = [_NamespacesMatch, _PermissionsMatch, _HostRoutesMatch];
|
|
|
|
const preload = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "plugin-sdk-preload.cjs"), "utf8");
|
|
const preloadAsyncRoutes = extractPreloadAsyncRoutes(preload);
|
|
const preloadSyncRoutes = extractLiteralFirstArgs(preload, "callSync");
|
|
const hostOnlyRoutes = new Set<string>(["assets.resolve"]);
|
|
const expectedPreloadAsyncRoutes = new Set(pluginSdkAsyncRoutes.filter((route) => !hostOnlyRoutes.has(route)));
|
|
assertSetEqual("Plugin SDK preload async routes", preloadAsyncRoutes, expectedPreloadAsyncRoutes);
|
|
assertSetEqual("Plugin SDK preload sync routes", preloadSyncRoutes, new Set(pluginSdkSyncRoutes));
|
|
|
|
console.error("Plugin SDK conformance validation passed.");
|
|
|
|
function extractPreloadAsyncRoutes(source: string): Set<string> {
|
|
const routes = new Set<string>([...extractLiteralFirstArgs(source, "call"), ...extractSubscriptionRoutes(source)]);
|
|
if (/call\(`log\.\$\{level\}`/.test(source)) for (const level of ["debug", "info", "warn", "error"]) routes.add(`log.${level}`);
|
|
return routes;
|
|
}
|
|
|
|
function extractSubscriptionRoutes(source: string): Set<string> {
|
|
const routes = new Set<string>();
|
|
const pattern = /\bsubscription\(\s*"([^"]+)"\s*,\s*"([^"]+)"/g;
|
|
for (let match = pattern.exec(source); match; match = pattern.exec(source)) { routes.add(match[1] ?? ""); routes.add(match[2] ?? ""); }
|
|
return routes;
|
|
}
|
|
|
|
function extractLiteralFirstArgs(source: string, callee: string): Set<string> {
|
|
const routes = new Set<string>();
|
|
const pattern = new RegExp(`\\b${callee}\\(\\s*"([^"]+)"`, "g");
|
|
for (let match = pattern.exec(source); match; match = pattern.exec(source)) routes.add(match[1] ?? "");
|
|
return routes;
|
|
}
|
|
|
|
function assertSetEqual(label: string, actual: Set<string>, expected: Set<string>): void {
|
|
const missing = [...expected].filter((route) => !actual.has(route)).sort();
|
|
const extra = [...actual].filter((route) => !expected.has(route)).sort();
|
|
if (missing.length > 0 || extra.length > 0) throw new Error(`${label} drift. Missing: ${missing.join(", ") || "none"}; extra: ${extra.join(", ") || "none"}.`);
|
|
}
|