mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
- Web: base bundles built from the app's entry at start (content-hashed, served forever), plugin scripts that register themselves and one combined file, WebScripts/TuiScripts showing the current set, startPage with the connection loop, in-place plugin reloads and a reload on a new base bundle. - React bindings: HostProvider, useHold, useWatch, useRegistry, useRefState. - Terminal: startTerminal, a launcher that signs in with a token, a token file or device pairing and always runs the app's terminal code in a sandboxed child process (pipes, empty environment, --permission granting nothing, no SQLite, a builtin allowlist and a frozen process inside); output filter, confirmed copy/open/attach actions; startTerminalRuntime and TuiScope inside. - Themes: the Themes registry, ThemeChoice per principal, CSS variables on the page, terminal colours by role; CSS modules in plugin builds. - App kit: startServer gains web and tui; startApp renamed startServer, exportAddress/importAddress renamed exportData/importData and export no longer stops the worker; ephemeral sessions end with their client's last link. - Examples 56-web, 57-tui, 58-themes, 59-chat.
56 lines
3 KiB
TypeScript
56 lines
3 KiB
TypeScript
// Tests of the terminal surface's server side (slice 11 §4), through the app kit: the start a launcher downloads (signed
|
|
// in only), the base hash (public), the base bundle (an ES module answering builtins for bundled CommonJS) and the
|
|
// plugin scripts registering themselves. A terminal plugin has no assets: a Node build refuses them (Opus L5).
|
|
|
|
import { readFile } from "node:fs/promises";
|
|
import { describe, expect, it } from "vitest";
|
|
import { BACKGROUND_CONTEXT as context } from "../../../src/context/index.ts";
|
|
import { TuiScripts } from "../../../src/next/index.ts";
|
|
import type { TuiStart } from "../../../src/next-node/surfaces/index.ts";
|
|
import { send } from "../http/support.ts";
|
|
import { writePlugin } from "../plugins/support.ts";
|
|
import { tuiApp } from "../tui/support.ts";
|
|
|
|
describe("the terminal surface", () => {
|
|
it("serves a signed-in launcher its start, the base bundle and the scripts; the base hash to anyone", async () => {
|
|
const { app, port, root } = await tuiApp();
|
|
try {
|
|
const headers = { authorization: `Bearer ${(await readFile(app.ownerTokenFile, "utf8")).trim()}` };
|
|
expect((await send(port, "GET", "/_chord/tui/start")).status).toBe(401);
|
|
const start = JSON.parse(
|
|
(await send(port, "GET", "/_chord/tui/start", { headers })).body.toString(),
|
|
) as TuiStart;
|
|
expect(start).toEqual({
|
|
base: app.tui!.base,
|
|
script: `/_chord/tui/base.${app.tui!.base}.js`,
|
|
combined: expect.stringMatching(/^\/_chord\/tui\/plugins\.[0-9a-f]{16}\.js$/),
|
|
});
|
|
expect(JSON.parse((await send(port, "GET", "/_chord/tui/base")).body.toString())).toBe(app.tui!.base);
|
|
const base = (await send(port, "GET", start.script, { headers })).body.toString();
|
|
// A CommonJS script (the sandbox gives it its own `require`), no ES module.
|
|
expect(base).toContain("require(");
|
|
expect(base).not.toMatch(/^\s*(?:import|export)\b/m);
|
|
const scripts = await app.host.get(TuiScripts, {}, context);
|
|
const [entry] = scripts.set.value.plugins;
|
|
expect(entry!.url).toBe(`/_chord/tui/plugin/@test~lines.${entry!.digest}.js`);
|
|
const script = (await send(port, "GET", entry!.url, { headers })).body.toString();
|
|
expect(script.startsWith(`chordFacet("@test/lines","${entry!.digest}",`)).toBe(true);
|
|
expect((await send(port, "GET", start.combined, { headers })).body.toString()).toBe(
|
|
script.replace(/\n\/\/# sourceMappingURL=data:[^\n]*/, ""),
|
|
);
|
|
scripts.release();
|
|
// Terminal facets are Node builds: an asset import fails the build, so the surface serves none.
|
|
await writePlugin(root, "pictured", "@test/pictured", {
|
|
"tui.ts": 'import icon from "./icon.svg?url";\nexport default { setup: () => icon };',
|
|
"icon.svg": "<svg/>",
|
|
});
|
|
const built = await app.plugins.component.build(["@test/pictured"], context);
|
|
expect(built.facets.find(({ plugin }) => plugin === "@test/pictured")).toMatchObject({
|
|
kind: "failed",
|
|
errors: [expect.stringContaining("assets by URL are for browser builds")],
|
|
});
|
|
} finally {
|
|
await app.close(context);
|
|
}
|
|
});
|
|
});
|