@pi-harness/pi-harness 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/apps/web/dist/assets/index-C4tLweAB.js +20 -0
- package/apps/web/dist/assets/index-DPgRbQ4_.css +1 -0
- package/apps/web/dist/index.html +14 -0
- package/apps/web/index.html +13 -0
- package/apps/web/package.json +24 -0
- package/apps/web/profile/cordis.yml +44 -0
- package/apps/web/server-dist/bin.d.ts +3 -0
- package/apps/web/server-dist/bin.d.ts.map +1 -0
- package/apps/web/server-dist/bin.js +67 -0
- package/apps/web/server-dist/bin.js.map +1 -0
- package/apps/web/src/command-palette.ts +84 -0
- package/apps/web/src/file-diff.ts +42 -0
- package/apps/web/src/main.ts +12 -0
- package/apps/web/src/runtime.css +4 -0
- package/apps/web/src/server/bin.ts +64 -0
- package/apps/web/src/style.css +7 -0
- package/apps/web/tsconfig.build.json +1 -0
- package/apps/web/tsconfig.json +1 -0
- package/package.json +48 -0
- package/packages/api-gateway/dist/index.d.ts +8 -0
- package/packages/api-gateway/dist/index.d.ts.map +1 -0
- package/packages/api-gateway/dist/index.js +582 -0
- package/packages/api-gateway/dist/index.js.map +1 -0
- package/packages/api-gateway/package.json +29 -0
- package/packages/api-gateway/src/index.ts +577 -0
- package/packages/api-gateway/test/index.test.ts +327 -0
- package/packages/api-gateway/tsconfig.build.json +1 -0
- package/packages/api-gateway/tsconfig.json +1 -0
- package/packages/bundle-web-app/cordis.patch.yml +12 -0
- package/packages/bundle-web-app/dist/index.d.ts +12 -0
- package/packages/bundle-web-app/dist/index.d.ts.map +1 -0
- package/packages/bundle-web-app/dist/index.js +63 -0
- package/packages/bundle-web-app/dist/index.js.map +1 -0
- package/packages/bundle-web-app/package.json +30 -0
- package/packages/bundle-web-app/src/index.ts +69 -0
- package/packages/bundle-web-app/test/index.test.ts +29 -0
- package/packages/bundle-web-app/tsconfig.build.json +1 -0
- package/packages/bundle-web-app/tsconfig.json +1 -0
- package/packages/cli/dist/args.d.ts +16 -0
- package/packages/cli/dist/args.d.ts.map +1 -0
- package/packages/cli/dist/args.js +53 -0
- package/packages/cli/dist/args.js.map +1 -0
- package/packages/cli/dist/bin.d.ts +3 -0
- package/packages/cli/dist/bin.d.ts.map +1 -0
- package/packages/cli/dist/bin.js +40 -0
- package/packages/cli/dist/bin.js.map +1 -0
- package/packages/cli/dist/main.d.ts +14 -0
- package/packages/cli/dist/main.d.ts.map +1 -0
- package/packages/cli/dist/main.js +148 -0
- package/packages/cli/dist/main.js.map +1 -0
- package/packages/cli/dist/node-stdio.d.ts +10 -0
- package/packages/cli/dist/node-stdio.d.ts.map +1 -0
- package/packages/cli/dist/node-stdio.js +34 -0
- package/packages/cli/dist/node-stdio.js.map +1 -0
- package/packages/cli/dist/relaunch.d.ts +5 -0
- package/packages/cli/dist/relaunch.d.ts.map +1 -0
- package/packages/cli/dist/relaunch.js +52 -0
- package/packages/cli/dist/relaunch.js.map +1 -0
- package/packages/cli/package.json +25 -0
- package/packages/cli/src/args.ts +50 -0
- package/packages/cli/src/bin.ts +41 -0
- package/packages/cli/src/main.ts +155 -0
- package/packages/cli/src/node-stdio.ts +40 -0
- package/packages/cli/src/relaunch.ts +48 -0
- package/packages/cli/test/args.test.ts +28 -0
- package/packages/cli/test/cli.test.ts +153 -0
- package/packages/cli/test/relaunch.test.ts +29 -0
- package/packages/cli/tsconfig.build.json +11 -0
- package/packages/cli/tsconfig.json +10 -0
- package/packages/client-web/dist/control-room.d.ts +102 -0
- package/packages/client-web/dist/control-room.d.ts.map +1 -0
- package/packages/client-web/dist/control-room.js +12 -0
- package/packages/client-web/dist/control-room.js.map +1 -0
- package/packages/client-web/dist/design-contract.d.ts +85 -0
- package/packages/client-web/dist/design-contract.d.ts.map +1 -0
- package/packages/client-web/dist/design-contract.js +99 -0
- package/packages/client-web/dist/design-contract.js.map +1 -0
- package/packages/client-web/dist/index.d.ts +17 -0
- package/packages/client-web/dist/index.d.ts.map +1 -0
- package/packages/client-web/dist/index.js +19 -0
- package/packages/client-web/dist/index.js.map +1 -0
- package/packages/client-web/dist/react-room.d.ts +6 -0
- package/packages/client-web/dist/react-room.d.ts.map +1 -0
- package/packages/client-web/dist/react-room.js +103 -0
- package/packages/client-web/dist/react-room.js.map +1 -0
- package/packages/client-web/package.json +29 -0
- package/packages/client-web/src/control-room.ts +10 -0
- package/packages/client-web/src/design-contract.ts +112 -0
- package/packages/client-web/src/index.ts +21 -0
- package/packages/client-web/src/react-room.tsx +78 -0
- package/packages/client-web/test/design-contract.test.ts +24 -0
- package/packages/client-web/tsconfig.build.json +1 -0
- package/packages/client-web/tsconfig.json +1 -0
- package/packages/core/dist/boot.d.ts +13 -0
- package/packages/core/dist/boot.d.ts.map +1 -0
- package/packages/core/dist/boot.js +96 -0
- package/packages/core/dist/boot.js.map +1 -0
- package/packages/core/dist/context.d.ts +3 -0
- package/packages/core/dist/context.d.ts.map +1 -0
- package/packages/core/dist/context.js +2 -0
- package/packages/core/dist/context.js.map +1 -0
- package/packages/core/dist/index.d.ts +7 -0
- package/packages/core/dist/index.d.ts.map +1 -0
- package/packages/core/dist/index.js +6 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/core/dist/plugins/model.d.ts +8 -0
- package/packages/core/dist/plugins/model.d.ts.map +1 -0
- package/packages/core/dist/plugins/model.js +12 -0
- package/packages/core/dist/plugins/model.js.map +1 -0
- package/packages/core/dist/plugins/models.d.ts +16 -0
- package/packages/core/dist/plugins/models.d.ts.map +1 -0
- package/packages/core/dist/plugins/models.js +24 -0
- package/packages/core/dist/plugins/models.js.map +1 -0
- package/packages/core/dist/plugins/resources.d.ts +18 -0
- package/packages/core/dist/plugins/resources.d.ts.map +1 -0
- package/packages/core/dist/plugins/resources.js +48 -0
- package/packages/core/dist/plugins/resources.js.map +1 -0
- package/packages/core/dist/plugins/runtime.d.ts +14 -0
- package/packages/core/dist/plugins/runtime.d.ts.map +1 -0
- package/packages/core/dist/plugins/runtime.js +68 -0
- package/packages/core/dist/plugins/runtime.js.map +1 -0
- package/packages/core/dist/plugins/session.d.ts +15 -0
- package/packages/core/dist/plugins/session.d.ts.map +1 -0
- package/packages/core/dist/plugins/session.js +20 -0
- package/packages/core/dist/plugins/session.js.map +1 -0
- package/packages/core/dist/plugins/stdio.d.ts +8 -0
- package/packages/core/dist/plugins/stdio.d.ts.map +1 -0
- package/packages/core/dist/plugins/stdio.js +20 -0
- package/packages/core/dist/plugins/stdio.js.map +1 -0
- package/packages/core/dist/plugins/tools.d.ts +13 -0
- package/packages/core/dist/plugins/tools.d.ts.map +1 -0
- package/packages/core/dist/plugins/tools.js +17 -0
- package/packages/core/dist/plugins/tools.js.map +1 -0
- package/packages/core/dist/profile.d.ts +8 -0
- package/packages/core/dist/profile.d.ts.map +1 -0
- package/packages/core/dist/profile.js +28 -0
- package/packages/core/dist/profile.js.map +1 -0
- package/packages/core/dist/runtime.d.ts +16 -0
- package/packages/core/dist/runtime.d.ts.map +1 -0
- package/packages/core/dist/runtime.js +33 -0
- package/packages/core/dist/runtime.js.map +1 -0
- package/packages/core/dist/services.d.ts +63 -0
- package/packages/core/dist/services.d.ts.map +1 -0
- package/packages/core/dist/services.js +39 -0
- package/packages/core/dist/services.js.map +1 -0
- package/packages/core/dist/stdio.d.ts +25 -0
- package/packages/core/dist/stdio.d.ts.map +1 -0
- package/packages/core/dist/stdio.js +74 -0
- package/packages/core/dist/stdio.js.map +1 -0
- package/packages/core/package.json +44 -0
- package/packages/core/profiles/default/cordis.yml +35 -0
- package/packages/core/profiles/development/cordis.yml +47 -0
- package/packages/core/src/boot.ts +103 -0
- package/packages/core/src/context.ts +3 -0
- package/packages/core/src/index.ts +6 -0
- package/packages/core/src/plugins/model.ts +12 -0
- package/packages/core/src/plugins/models.ts +32 -0
- package/packages/core/src/plugins/resources.ts +57 -0
- package/packages/core/src/plugins/runtime.ts +73 -0
- package/packages/core/src/plugins/session.ts +27 -0
- package/packages/core/src/plugins/stdio.ts +20 -0
- package/packages/core/src/plugins/tools.ts +23 -0
- package/packages/core/src/profile.ts +31 -0
- package/packages/core/src/runtime.ts +39 -0
- package/packages/core/src/services.ts +105 -0
- package/packages/core/src/stdio.ts +88 -0
- package/packages/core/test/boot.test.ts +86 -0
- package/packages/core/test/profile.test.ts +39 -0
- package/packages/core/test/profiles.integration.test.ts +93 -0
- package/packages/core/test/runtime-fixture.ts +31 -0
- package/packages/core/test/runtime.test.ts +54 -0
- package/packages/core/test/services.test.ts +131 -0
- package/packages/core/test/stdio.test.ts +87 -0
- package/packages/core/tsconfig.build.json +10 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/host-webserver/dist/index.d.ts +29 -0
- package/packages/host-webserver/dist/index.d.ts.map +1 -0
- package/packages/host-webserver/dist/index.js +73 -0
- package/packages/host-webserver/dist/index.js.map +1 -0
- package/packages/host-webserver/package.json +27 -0
- package/packages/host-webserver/src/index.ts +97 -0
- package/packages/host-webserver/test/index.test.ts +43 -0
- package/packages/host-webserver/tsconfig.build.json +1 -0
- package/packages/host-webserver/tsconfig.json +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChG;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACtC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,SAAS,EAAE,SAAS,CAAC;KACtB;CACF;;;mBAkBsB,OAAO,UAAU,eAAe;;AAFvD,wBAsDE"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
function normalizePath(path) {
|
|
3
|
+
if (!path.startsWith("/") || (path.length > 1 && path.endsWith("/")))
|
|
4
|
+
throw new Error("Web route path must be absolute without a trailing slash: " + path);
|
|
5
|
+
return path;
|
|
6
|
+
}
|
|
7
|
+
function notFound(response) {
|
|
8
|
+
response.writeHead(404, { "content-type": "application/json; charset=utf-8" });
|
|
9
|
+
response.end(JSON.stringify({ error: "Not found" }));
|
|
10
|
+
}
|
|
11
|
+
function hostForUrl(host) {
|
|
12
|
+
return host.includes(":") && !host.startsWith("[") ? "[" + host + "]" : host;
|
|
13
|
+
}
|
|
14
|
+
export default {
|
|
15
|
+
name: "pi-webserver",
|
|
16
|
+
async apply(context, config) {
|
|
17
|
+
const host = config.host ?? "127.0.0.1";
|
|
18
|
+
const port = config.port ?? 3080;
|
|
19
|
+
const routes = new Map();
|
|
20
|
+
let fallback;
|
|
21
|
+
const server = createServer((request, response) => {
|
|
22
|
+
const path = new URL(request.url ?? "/", "http://" + hostForUrl(host)).pathname;
|
|
23
|
+
const handler = routes.get(path) ?? fallback;
|
|
24
|
+
if (handler === undefined) {
|
|
25
|
+
notFound(response);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
void Promise.resolve(handler(request, response)).catch((error) => {
|
|
29
|
+
if (response.headersSent) {
|
|
30
|
+
response.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
response.writeHead(500, { "content-type": "application/json; charset=utf-8" });
|
|
34
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
await new Promise((resolve, reject) => {
|
|
38
|
+
server.once("error", reject);
|
|
39
|
+
server.listen(port, host, () => {
|
|
40
|
+
server.off("error", reject);
|
|
41
|
+
resolve();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
const address = server.address();
|
|
45
|
+
if (address === null || typeof address === "string")
|
|
46
|
+
throw new Error("Web server did not expose a TCP address");
|
|
47
|
+
const service = {
|
|
48
|
+
host,
|
|
49
|
+
port: address.port,
|
|
50
|
+
url: "http://" + hostForUrl(host) + ":" + address.port,
|
|
51
|
+
register(route) {
|
|
52
|
+
const path = normalizePath(route.path);
|
|
53
|
+
if (routes.has(path))
|
|
54
|
+
throw new Error("Web route already registered: " + path);
|
|
55
|
+
routes.set(path, route.handler);
|
|
56
|
+
return () => routes.delete(path);
|
|
57
|
+
},
|
|
58
|
+
registerFallback(handler) {
|
|
59
|
+
if (fallback !== undefined)
|
|
60
|
+
throw new Error("Web fallback is already registered");
|
|
61
|
+
fallback = handler;
|
|
62
|
+
return () => {
|
|
63
|
+
if (fallback === handler)
|
|
64
|
+
fallback = undefined;
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())),
|
|
68
|
+
};
|
|
69
|
+
context.provide("webServer", service);
|
|
70
|
+
context.effect(() => async () => service.close());
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-harness/host-webserver",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Cordis route-registration service for the Pi Harness web host",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.19.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.build.json",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@deepseek-ai/cordis": "4.0.1"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
|
|
4
|
+
export interface WebRoute {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly handler: (request: IncomingMessage, response: ServerResponse) => void | Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface WebServerConfig {
|
|
10
|
+
readonly host?: string;
|
|
11
|
+
readonly port?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface WebServer {
|
|
15
|
+
readonly host: string;
|
|
16
|
+
readonly port: number;
|
|
17
|
+
readonly url: string;
|
|
18
|
+
register(route: WebRoute): () => void;
|
|
19
|
+
registerFallback(handler: WebRoute["handler"]): () => void;
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module "@deepseek-ai/cordis" {
|
|
24
|
+
interface Context {
|
|
25
|
+
webServer: WebServer;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizePath(path: string): string {
|
|
30
|
+
if (!path.startsWith("/") || (path.length > 1 && path.endsWith("/"))) throw new Error("Web route path must be absolute without a trailing slash: " + path);
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function notFound(response: ServerResponse): void {
|
|
35
|
+
response.writeHead(404, { "content-type": "application/json; charset=utf-8" });
|
|
36
|
+
response.end(JSON.stringify({ error: "Not found" }));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hostForUrl(host: string): string {
|
|
40
|
+
return host.includes(":") && !host.startsWith("[") ? "[" + host + "]" : host;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
name: "pi-webserver",
|
|
45
|
+
async apply(context: Context, config: WebServerConfig) {
|
|
46
|
+
const host = config.host ?? "127.0.0.1";
|
|
47
|
+
const port = config.port ?? 3080;
|
|
48
|
+
const routes = new Map<string, WebRoute["handler"]>();
|
|
49
|
+
let fallback: WebRoute["handler"] | undefined;
|
|
50
|
+
const server = createServer((request, response) => {
|
|
51
|
+
const path = new URL(request.url ?? "/", "http://" + hostForUrl(host)).pathname;
|
|
52
|
+
const handler = routes.get(path) ?? fallback;
|
|
53
|
+
if (handler === undefined) {
|
|
54
|
+
notFound(response);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
void Promise.resolve(handler(request, response)).catch((error: unknown) => {
|
|
58
|
+
if (response.headersSent) {
|
|
59
|
+
response.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
response.writeHead(500, { "content-type": "application/json; charset=utf-8" });
|
|
63
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
await new Promise<void>((resolve, reject) => {
|
|
67
|
+
server.once("error", reject);
|
|
68
|
+
server.listen(port, host, () => {
|
|
69
|
+
server.off("error", reject);
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
const address = server.address();
|
|
74
|
+
if (address === null || typeof address === "string") throw new Error("Web server did not expose a TCP address");
|
|
75
|
+
const service: WebServer = {
|
|
76
|
+
host,
|
|
77
|
+
port: address.port,
|
|
78
|
+
url: "http://" + hostForUrl(host) + ":" + address.port,
|
|
79
|
+
register(route) {
|
|
80
|
+
const path = normalizePath(route.path);
|
|
81
|
+
if (routes.has(path)) throw new Error("Web route already registered: " + path);
|
|
82
|
+
routes.set(path, route.handler);
|
|
83
|
+
return () => routes.delete(path);
|
|
84
|
+
},
|
|
85
|
+
registerFallback(handler) {
|
|
86
|
+
if (fallback !== undefined) throw new Error("Web fallback is already registered");
|
|
87
|
+
fallback = handler;
|
|
88
|
+
return () => {
|
|
89
|
+
if (fallback === handler) fallback = undefined;
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
close: () => new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())),
|
|
93
|
+
};
|
|
94
|
+
context.provide("webServer", service);
|
|
95
|
+
context.effect(() => async () => service.close());
|
|
96
|
+
},
|
|
97
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
3
|
+
import webServerPlugin from "../src/index.js";
|
|
4
|
+
|
|
5
|
+
const contexts: Context[] = [];
|
|
6
|
+
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await Promise.all(contexts.splice(0).map(async (context) => context.fiber.dispose()));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("web server plugin", () => {
|
|
12
|
+
test("serves registered routes and disposes the listener with the Cordis fiber", async () => {
|
|
13
|
+
const context = new Context();
|
|
14
|
+
contexts.push(context);
|
|
15
|
+
await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
|
|
16
|
+
const server = context.webServer;
|
|
17
|
+
const disposeRoute = server.register({
|
|
18
|
+
path: "/health",
|
|
19
|
+
handler(_request, response) {
|
|
20
|
+
response.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
|
|
21
|
+
response.end("ok");
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
await expect(fetch(server.url + "/health").then(async (response) => ({ status: response.status, body: await response.text() }))).resolves.toEqual({ status: 200, body: "ok" });
|
|
26
|
+
expect(() => server.register({ path: "/health", handler() {} })).toThrow(/already registered/);
|
|
27
|
+
|
|
28
|
+
disposeRoute();
|
|
29
|
+
await expect(fetch(server.url + "/health")).resolves.toMatchObject({ status: 404 });
|
|
30
|
+
await context.fiber.dispose();
|
|
31
|
+
await expect(fetch(server.url + "/health")).rejects.toThrow();
|
|
32
|
+
contexts.splice(contexts.indexOf(context), 1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("rejects malformed route paths", async () => {
|
|
36
|
+
const context = new Context();
|
|
37
|
+
contexts.push(context);
|
|
38
|
+
await context.plugin(webServerPlugin, { port: 0 });
|
|
39
|
+
|
|
40
|
+
expect(() => context.webServer.register({ path: "health", handler() {} })).toThrow(/absolute/);
|
|
41
|
+
expect(() => context.webServer.register({ path: "/health/", handler() {} })).toThrow(/trailing slash/);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"extends":"../../tsconfig.build.json","compilerOptions":{"outDir":"dist","paths":{},"rootDir":"src"},"include":["src/**/*.ts"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"extends":"../../tsconfig.json","compilerOptions":{"paths":{}},"include":["src/**/*.ts"]}
|