@catheadowl/ukp 0.1.1 → 0.2.0
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 +34 -24
- package/package.json +2 -4
- package/src/capabilities/nav.ts +4 -2
- package/src/capabilities/read.ts +33 -3
- package/src/capabilities/remote-client.ts +306 -60
- package/src/capabilities/rg.ts +2 -0
- package/src/capabilities/search.ts +25 -5
- package/src/capabilities/tls-identity.ts +172 -30
- package/src/cli.ts +1 -1
- package/src/commands/guide-content/client.txt +12 -0
- package/src/commands/guide-content/remote.txt +64 -0
- package/src/commands/guide-content/service-qmd.txt +1 -1
- package/src/commands/guide-content/service.txt +33 -32
- package/src/commands/guide.ts +11 -4
- package/src/commands/init.ts +9 -12
- package/src/commands/inventory.ts +188 -47
- package/src/commands/nav.ts +1 -1
- package/src/commands/read.ts +48 -9
- package/src/commands/rg.ts +4 -1
- package/src/commands/search.ts +4 -1
- package/src/commands/serve.ts +86 -12
- package/src/config/manifest.ts +6 -0
- package/src/registry.ts +104 -16
- package/src/server.ts +267 -44
- package/bun.lock +0 -35
- package/tsconfig.json +0 -12
package/src/server.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { readFileSync, writeFileSync, fstatSync, constants as fsConstants } from "node:fs";
|
|
3
|
+
import { createServer as netCreateServer, connect as netConnect, type Server as NetServer } from "node:net";
|
|
3
4
|
import { ENDPOINT_NAME, loadManifest, type LoadedManifest } from "./config/manifest.ts";
|
|
4
5
|
import { localPathOf, readRegistry } from "./registry.ts";
|
|
5
6
|
import { FILE_NATIVE_CAPABILITIES, resolveFileNativeCapability } from "./config/file-native.ts";
|
|
@@ -83,11 +84,24 @@ export interface ServeConfig {
|
|
|
83
84
|
* identity under the Service folder (`.ukp/tls/`) — in door mode under the
|
|
84
85
|
* registry's directory, since a door has no single Service folder;
|
|
85
86
|
* `certificates` serves operator-provided PEM files (Let's Encrypt IP
|
|
86
|
-
* certs, mkcert, private CA). `
|
|
87
|
-
*
|
|
87
|
+
* certs, mkcert, private CA). `sanEntries` (the `--tls-san` flag, already
|
|
88
|
+
* normalized `IP:x`/`DNS:y`) merges extra coverage into the self-signed
|
|
89
|
+
* SAN — a NAT/EIP public address no NIC carries; a persisted certificate
|
|
90
|
+
* lacking a requested entry is re-signed over the same key (pin unchanged,
|
|
91
|
+
* clients re-anchor). `opensslCommand` is a test injection point for the
|
|
92
|
+
* self-signing. */
|
|
88
93
|
tls?:
|
|
89
|
-
| { mode: "self-signed"; opensslCommand?: readonly string[] }
|
|
94
|
+
| { mode: "self-signed"; opensslCommand?: readonly string[]; sanEntries?: readonly string[] }
|
|
90
95
|
| { mode: "certificates"; certPath: string; keyPath: string };
|
|
96
|
+
/** W9 / ADR-REM-006: self-reap after this many seconds without requests.
|
|
97
|
+
* The orphan backstop for on-demand-woken doors — a no-TTY remote command
|
|
98
|
+
* can escape SIGHUP on an abrupt disconnect, so the door bounds its own
|
|
99
|
+
* lifetime instead of trusting the session to reap it. */
|
|
100
|
+
maxIdleSeconds?: number;
|
|
101
|
+
/** W10 / ADR-REM-006: serve on the systemd socket-activation listener
|
|
102
|
+
* (LISTEN_FDS fd 3) instead of binding a port — the port belongs to the
|
|
103
|
+
* socket unit; the process only exists while in use. Linux-only. */
|
|
104
|
+
systemdSocket?: boolean;
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
export interface DiscoveryDocument {
|
|
@@ -136,11 +150,57 @@ export interface ServeInfo {
|
|
|
136
150
|
port: number;
|
|
137
151
|
authRequired: boolean;
|
|
138
152
|
/** TLS identity summary (W5' / D-079) when serving over HTTPS; the pin is
|
|
139
|
-
* what remote clients TOFU-pin at registration.
|
|
153
|
+
* what remote clients TOFU-pin at registration. `source` "re-signed" marks
|
|
154
|
+
* the one start that grew SAN coverage over the existing key (--tls-san
|
|
155
|
+
* added an entry the persisted certificate lacked) — pin unchanged. */
|
|
140
156
|
tls?: {
|
|
141
157
|
pin: string;
|
|
142
158
|
san: string;
|
|
143
|
-
source: "generated" | "persisted" | "operator";
|
|
159
|
+
source: "generated" | "re-signed" | "persisted" | "operator";
|
|
160
|
+
};
|
|
161
|
+
/** Present when --max-idle armed the self-reap timer (W9). */
|
|
162
|
+
maxIdleSeconds?: number;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** W9 / ADR-REM-006 (--max-idle): wrap a serve handler so the listener stops
|
|
166
|
+
* itself after `maxIdleSeconds` without requests. Requests re-arm the timer
|
|
167
|
+
* on entry and again once the response settles; the exit mirrors the SIGINT
|
|
168
|
+
* path (stderr line + server.stop(true)), after which the process exits with
|
|
169
|
+
* its already-set code. The timer is unref'd so a SIGINT/SIGTERM stop is
|
|
170
|
+
* immediate — the listener, not the timer, is what holds the serve process
|
|
171
|
+
* open. `stopped` fences the settle-rearm of a request that outlived the
|
|
172
|
+
* window (no second fire after the first stop). The bind indirection exists
|
|
173
|
+
* because Bun.serve hands back the server only after `fetch` is fixed. */
|
|
174
|
+
function armIdleExit(
|
|
175
|
+
handler: (request: Request) => Response | Promise<Response>,
|
|
176
|
+
maxIdleSeconds: number,
|
|
177
|
+
): { fetch: (request: Request) => Response | Promise<Response>; bind: (stop: () => void) => void } {
|
|
178
|
+
const control = { stop: (): void => {} };
|
|
179
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
180
|
+
let stopped = false;
|
|
181
|
+
const rearm = () => {
|
|
182
|
+
if (stopped) return;
|
|
183
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
184
|
+
timer = setTimeout(() => {
|
|
185
|
+
stopped = true;
|
|
186
|
+
console.error(`ukp serve: idle exit (--max-idle ${maxIdleSeconds}s without requests)`);
|
|
187
|
+
control.stop();
|
|
188
|
+
}, maxIdleSeconds * 1000);
|
|
189
|
+
// Best-effort unref: present in Bun and Node; a missing no-op keeps this
|
|
190
|
+
// portable without changing the firing behavior while serving.
|
|
191
|
+
(timer as { unref?: () => void }).unref?.();
|
|
192
|
+
};
|
|
193
|
+
rearm();
|
|
194
|
+
return {
|
|
195
|
+
fetch: (request: Request) => {
|
|
196
|
+
rearm();
|
|
197
|
+
const response = handler(request);
|
|
198
|
+
Promise.resolve(response).then(rearm, () => {});
|
|
199
|
+
return response;
|
|
200
|
+
},
|
|
201
|
+
bind(stop: () => void) {
|
|
202
|
+
control.stop = stop;
|
|
203
|
+
},
|
|
144
204
|
};
|
|
145
205
|
}
|
|
146
206
|
|
|
@@ -449,45 +509,202 @@ function parseRgParams(
|
|
|
449
509
|
export interface StartedServe {
|
|
450
510
|
server: ReturnType<typeof Bun.serve>;
|
|
451
511
|
info: ServeInfo;
|
|
512
|
+
/** Composite stop: in socket-activation mode this also closes the fd3
|
|
513
|
+
* acceptor (Bun.serve's own stop alone would leave the process alive
|
|
514
|
+
* holding the systemd-passed listener). Safe to call from the SIGINT/
|
|
515
|
+
* SIGTERM/SIGHUP handlers and the idle-exit path alike. */
|
|
516
|
+
stopAll: () => void;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** W10 / ADR-REM-006: parse the systemd LISTEN_FDS convention — exactly one
|
|
520
|
+
* listening fd expected (fd 3), PID-guarded, and the variables are CONSUMED
|
|
521
|
+
* (unset) so any child process does not double-count inherited activation
|
|
522
|
+
* fds (the podman fd-counting pitfall, knowledge §5.2). */
|
|
523
|
+
const S_ISSOCK = fsConstants.S_IFSOCK;
|
|
524
|
+
|
|
525
|
+
export function parseListenFds(env: NodeJS.ProcessEnv): { fd: number } | { error: string } {
|
|
526
|
+
if ((env.LISTEN_FDS ?? "") === "" || (env.LISTEN_PID ?? "") === "") {
|
|
527
|
+
return {
|
|
528
|
+
error:
|
|
529
|
+
"--systemd-socket requires systemd socket activation: LISTEN_FDS/LISTEN_PID are not set (run under a .socket unit or systemd-socket-activate)",
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (Number(env.LISTEN_PID) !== process.pid) {
|
|
533
|
+
return { error: `LISTEN_PID ${env.LISTEN_PID} does not match this process (${process.pid}) — inherited activation variables belong to another process` };
|
|
534
|
+
}
|
|
535
|
+
const count = Number(env.LISTEN_FDS);
|
|
536
|
+
if (!Number.isSafeInteger(count) || count < 1) {
|
|
537
|
+
return { error: `LISTEN_FDS=${String(env.LISTEN_FDS)} is not a positive integer` };
|
|
538
|
+
}
|
|
539
|
+
if (count !== 1) {
|
|
540
|
+
return { error: `expected exactly one listening fd (LISTEN_FDS=1), got ${count} — run one socket unit per serve instance` };
|
|
541
|
+
}
|
|
542
|
+
delete env.LISTEN_FDS;
|
|
543
|
+
delete env.LISTEN_PID;
|
|
544
|
+
return { fd: 3 };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** W10 / ADR-REM-006: the in-process bridge. Bun.serve cannot adopt a raw
|
|
548
|
+
* listening fd (no public API; ali probe 2026-09-19), but node:net can
|
|
549
|
+
* (`createServer().listen({fd: 3})` verified under bun). Each connection the
|
|
550
|
+
* systemd-held listener accepts is piped to the real Bun.serve listener on
|
|
551
|
+
* an ephemeral loopback port — the fake-ssh proxy pattern, in-process. */
|
|
552
|
+
export function createSocketBridge(acceptor: NetServer, targetPort: number): { destroyAll: () => void } {
|
|
553
|
+
// Live connections are tracked so stopAll can force-close the pairs — a
|
|
554
|
+
// peer that receives FIN but never closes (half-open keep-alive shape)
|
|
555
|
+
// would otherwise hold the process open after the idle exit forever
|
|
556
|
+
// (review P2: probe-confirmed liveness hang), letting stale serve
|
|
557
|
+
// processes accumulate beside systemd's fresh spawns.
|
|
558
|
+
const live = new Set<{ socket: import("node:net").Socket; upstream: import("node:net").Socket }>();
|
|
559
|
+
acceptor.on("connection", (socket) => {
|
|
560
|
+
const upstream = netConnect({ host: "127.0.0.1", port: targetPort });
|
|
561
|
+
const pair = { socket, upstream };
|
|
562
|
+
live.add(pair);
|
|
563
|
+
socket.pipe(upstream);
|
|
564
|
+
upstream.pipe(socket);
|
|
565
|
+
const drop = () => {
|
|
566
|
+
live.delete(pair);
|
|
567
|
+
socket.destroy();
|
|
568
|
+
upstream.destroy();
|
|
569
|
+
};
|
|
570
|
+
socket.on("error", drop);
|
|
571
|
+
upstream.on("error", drop);
|
|
572
|
+
socket.on("close", () => {
|
|
573
|
+
live.delete(pair);
|
|
574
|
+
upstream.destroy();
|
|
575
|
+
});
|
|
576
|
+
upstream.on("close", () => {
|
|
577
|
+
live.delete(pair);
|
|
578
|
+
socket.destroy();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
return {
|
|
582
|
+
destroyAll() {
|
|
583
|
+
for (const pair of live) {
|
|
584
|
+
pair.socket.destroy();
|
|
585
|
+
pair.upstream.destroy();
|
|
586
|
+
}
|
|
587
|
+
live.clear();
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** One serve listener, both modes (W10): plain binding by default; under
|
|
593
|
+
* `systemdSocket` the systemd-passed fd 3 is adopted via node:net and
|
|
594
|
+
* bridged to the real Bun.serve listener on an ephemeral loopback port. */
|
|
595
|
+
function startServeListener(
|
|
596
|
+
handler: (request: Request) => Response | Promise<Response>,
|
|
597
|
+
options: {
|
|
598
|
+
host: string;
|
|
599
|
+
port?: number;
|
|
600
|
+
tlsMaterial?: { cert: string; key: string };
|
|
601
|
+
maxIdleSeconds?: number;
|
|
602
|
+
systemdSocket?: boolean;
|
|
603
|
+
},
|
|
604
|
+
): { server: ReturnType<typeof Bun.serve>; stopAll: () => void; publicUrl: string; port: number } {
|
|
605
|
+
const idle = options.maxIdleSeconds !== undefined ? armIdleExit(handler, options.maxIdleSeconds) : undefined;
|
|
606
|
+
const fetch = idle?.fetch ?? handler;
|
|
607
|
+
const tls = options.tlsMaterial !== undefined ? { tls: { cert: options.tlsMaterial.cert, key: options.tlsMaterial.key } } : {};
|
|
608
|
+
const scheme = options.tlsMaterial !== undefined ? "https" : "http";
|
|
609
|
+
|
|
610
|
+
if (options.systemdSocket !== true) {
|
|
611
|
+
const server = Bun.serve({ hostname: options.host, port: options.port ?? 8570, ...tls, fetch });
|
|
612
|
+
idle?.bind(() => server.stop(true));
|
|
613
|
+
const port = server.port ?? (options.port ?? 8570);
|
|
614
|
+
return { server, stopAll: () => server.stop(true), publicUrl: `${scheme}://${options.host}:${port}`, port };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (process.platform !== "linux") {
|
|
618
|
+
throw new ServeSetupError("--systemd-socket is Linux-only (systemd socket activation)");
|
|
619
|
+
}
|
|
620
|
+
const parsed = parseListenFds(process.env);
|
|
621
|
+
if ("error" in parsed) throw new ServeSetupError(parsed.error);
|
|
622
|
+
// Validate the fd synchronously: systemd passes a bound LISTENING socket;
|
|
623
|
+
// anything else (EBADF, wrong type) must fail setup, not crash later.
|
|
624
|
+
let isSocket = false;
|
|
625
|
+
try {
|
|
626
|
+
const stats = fstatSync(parsed.fd);
|
|
627
|
+
isSocket = (stats.mode & S_ISSOCK) === S_ISSOCK;
|
|
628
|
+
} catch {
|
|
629
|
+
isSocket = false;
|
|
630
|
+
}
|
|
631
|
+
if (!isSocket) {
|
|
632
|
+
throw new ServeSetupError(`fd ${parsed.fd} is not an open socket — socket activation must pass a bound listening socket`);
|
|
633
|
+
}
|
|
634
|
+
const server = Bun.serve({ hostname: "127.0.0.1", port: 0, ...tls, fetch });
|
|
635
|
+
const acceptor = netCreateServer();
|
|
636
|
+
const bridge = createSocketBridge(acceptor, server.port ?? 0);
|
|
637
|
+
acceptor.on("error", (error) => {
|
|
638
|
+
// Async setup failure channel (e.g. fd 3 is a CONNECTED socket — passes
|
|
639
|
+
// the S_IFSOCK pre-check but cannot listen): no hard exit from library
|
|
640
|
+
// code — report, stop both listeners, and let the loop drain with a
|
|
641
|
+
// failing exit code.
|
|
642
|
+
console.error(`ukp serve: fd${parsed.fd} acceptor failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
643
|
+
process.exitCode = 1;
|
|
644
|
+
acceptor.close();
|
|
645
|
+
bridge.destroyAll();
|
|
646
|
+
server.stop(true);
|
|
647
|
+
});
|
|
648
|
+
acceptor.listen({ fd: parsed.fd });
|
|
649
|
+
const stopAll = () => {
|
|
650
|
+
acceptor.close();
|
|
651
|
+
bridge.destroyAll();
|
|
652
|
+
server.stop(true);
|
|
653
|
+
};
|
|
654
|
+
idle?.bind(stopAll);
|
|
655
|
+
return {
|
|
656
|
+
server,
|
|
657
|
+
stopAll,
|
|
658
|
+
publicUrl: `systemd:fd${parsed.fd} (bridge ${scheme}://127.0.0.1:${server.port})`,
|
|
659
|
+
port: server.port ?? 0,
|
|
660
|
+
};
|
|
452
661
|
}
|
|
453
662
|
|
|
454
663
|
/** TLS material resolution (W5' / D-079), shared by both serve modes: a
|
|
455
|
-
* self-signed identity is generated/persisted under `identityDir`
|
|
456
|
-
* Service folder's `.ukp/tls/` in endpoint mode, the registry directory
|
|
457
|
-
* door mode — a door has no single Service folder), explicit certificates
|
|
664
|
+
* self-signed identity is generated/re-signed/persisted under `identityDir`
|
|
665
|
+
* (the Service folder's `.ukp/tls/` in endpoint mode, the registry directory
|
|
666
|
+
* in door mode — a door has no single Service folder), explicit certificates
|
|
458
667
|
* are read from the operator's paths. Bun.serve takes PEM contents
|
|
459
|
-
* (path-string handling is platform-dependent).
|
|
668
|
+
* (path-string handling is platform-dependent). Every failure — unreadable
|
|
669
|
+
* operator files, an unusable persisted identity, openssl unable to sign —
|
|
670
|
+
* surfaces as one ServeSetupError naming the certificate path. */
|
|
460
671
|
function resolveTlsMaterial(
|
|
461
672
|
config: ServeConfig,
|
|
462
673
|
identityDir: string,
|
|
463
|
-
): { cert: string; key: string; pin: string; san: string; source: "generated" | "persisted" | "operator" } | undefined {
|
|
674
|
+
): { cert: string; key: string; pin: string; san: string; source: "generated" | "re-signed" | "persisted" | "operator" } | undefined {
|
|
464
675
|
if (config.tls === undefined) return undefined;
|
|
465
676
|
const tls = config.tls;
|
|
466
|
-
|
|
467
|
-
? (() => {
|
|
468
|
-
const identity = ensureSelfSignedTlsFiles(
|
|
469
|
-
identityDir,
|
|
470
|
-
tls.mode === "self-signed" && tls.opensslCommand !== undefined ? { opensslCommand: tls.opensslCommand } : {},
|
|
471
|
-
);
|
|
472
|
-
return {
|
|
473
|
-
certPath: identity.certPath,
|
|
474
|
-
keyPath: identity.keyPath,
|
|
475
|
-
source: identity.created ? ("generated" as const) : ("persisted" as const),
|
|
476
|
-
};
|
|
477
|
-
})()
|
|
478
|
-
: { certPath: tls.certPath, keyPath: tls.keyPath, source: "operator" as const };
|
|
677
|
+
let certPath: string | undefined;
|
|
479
678
|
try {
|
|
480
|
-
|
|
679
|
+
let keyPath: string;
|
|
680
|
+
let source: "generated" | "re-signed" | "persisted" | "operator";
|
|
681
|
+
if (tls.mode === "self-signed") {
|
|
682
|
+
const identity = ensureSelfSignedTlsFiles(
|
|
683
|
+
identityDir,
|
|
684
|
+
{
|
|
685
|
+
...(tls.opensslCommand !== undefined ? { opensslCommand: tls.opensslCommand } : {}),
|
|
686
|
+
...(tls.sanEntries !== undefined ? { extraSanEntries: tls.sanEntries } : {}),
|
|
687
|
+
},
|
|
688
|
+
);
|
|
689
|
+
certPath = identity.certPath;
|
|
690
|
+
keyPath = identity.keyPath;
|
|
691
|
+
source = identity.source;
|
|
692
|
+
} else {
|
|
693
|
+
certPath = tls.certPath;
|
|
694
|
+
keyPath = tls.keyPath;
|
|
695
|
+
source = "operator";
|
|
696
|
+
}
|
|
697
|
+
const cert = readFileSync(certPath, "utf8");
|
|
481
698
|
return {
|
|
482
699
|
cert,
|
|
483
|
-
key: readFileSync(
|
|
700
|
+
key: readFileSync(keyPath, "utf8"),
|
|
484
701
|
pin: spkiPinOf(cert),
|
|
485
702
|
san: certSanOf(cert),
|
|
486
|
-
source
|
|
703
|
+
source,
|
|
487
704
|
};
|
|
488
705
|
} catch (error) {
|
|
489
706
|
const reason = error instanceof Error ? error.message : String(error);
|
|
490
|
-
throw new ServeSetupError(`TLS material is unusable (${
|
|
707
|
+
throw new ServeSetupError(`TLS material is unusable (${certPath ?? join(identityDir, "cert.pem")}): ${reason}`);
|
|
491
708
|
}
|
|
492
709
|
}
|
|
493
710
|
|
|
@@ -650,12 +867,14 @@ function startDoorServer(config: ServeConfig, host: string, tlsMaterial: ReturnT
|
|
|
650
867
|
return doorNotFound(undefined, config.registryPath);
|
|
651
868
|
};
|
|
652
869
|
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
port: config.port
|
|
656
|
-
...(tlsMaterial !== undefined ? {
|
|
657
|
-
|
|
870
|
+
const listener = startServeListener(handler, {
|
|
871
|
+
host,
|
|
872
|
+
...(config.port !== undefined ? { port: config.port } : {}),
|
|
873
|
+
...(tlsMaterial !== undefined ? { tlsMaterial } : {}),
|
|
874
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
875
|
+
...(config.systemdSocket === true ? { systemdSocket: true } : {}),
|
|
658
876
|
});
|
|
877
|
+
const { server } = listener;
|
|
659
878
|
|
|
660
879
|
const info: ServeInfo = {
|
|
661
880
|
mode: "door",
|
|
@@ -663,15 +882,16 @@ function startDoorServer(config: ServeConfig, host: string, tlsMaterial: ReturnT
|
|
|
663
882
|
endpoints: listDoorEndpoints(config.registryPath).map((endpoint) => endpoint.name),
|
|
664
883
|
write: listWriteEndpoints(config.registryPath),
|
|
665
884
|
},
|
|
666
|
-
url:
|
|
885
|
+
url: listener.publicUrl,
|
|
667
886
|
host,
|
|
668
|
-
port:
|
|
887
|
+
port: listener.port,
|
|
669
888
|
authRequired: (config.tokens?.length ?? 0) > 0,
|
|
670
889
|
...(tlsMaterial !== undefined
|
|
671
890
|
? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
|
|
672
891
|
: {}),
|
|
892
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
673
893
|
};
|
|
674
|
-
return { server, info };
|
|
894
|
+
return { server, info, stopAll: listener.stopAll };
|
|
675
895
|
}
|
|
676
896
|
|
|
677
897
|
export function startUkpServer(config: ServeConfig): StartedServe {
|
|
@@ -781,27 +1001,30 @@ export function startUkpServer(config: ServeConfig): StartedServe {
|
|
|
781
1001
|
);
|
|
782
1002
|
};
|
|
783
1003
|
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
port: config.port
|
|
787
|
-
...(tlsMaterial !== undefined ? {
|
|
788
|
-
|
|
1004
|
+
const listener = startServeListener(handler, {
|
|
1005
|
+
host,
|
|
1006
|
+
...(config.port !== undefined ? { port: config.port } : {}),
|
|
1007
|
+
...(tlsMaterial !== undefined ? { tlsMaterial } : {}),
|
|
1008
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
1009
|
+
...(config.systemdSocket === true ? { systemdSocket: true } : {}),
|
|
789
1010
|
});
|
|
1011
|
+
const { server } = listener;
|
|
790
1012
|
|
|
791
1013
|
const info: ServeInfo = {
|
|
792
1014
|
mode: "endpoint",
|
|
793
1015
|
endpoint: binding.name,
|
|
794
1016
|
folder: serviceFolder,
|
|
795
1017
|
instanceUid: readInstanceUid(serviceFolder),
|
|
796
|
-
url:
|
|
1018
|
+
url: listener.publicUrl,
|
|
797
1019
|
host,
|
|
798
|
-
port:
|
|
1020
|
+
port: listener.port,
|
|
799
1021
|
authRequired: (config.tokens?.length ?? 0) > 0,
|
|
800
1022
|
...(tlsMaterial !== undefined
|
|
801
1023
|
? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
|
|
802
1024
|
: {}),
|
|
1025
|
+
...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
|
|
803
1026
|
};
|
|
804
|
-
return { server, info };
|
|
1027
|
+
return { server, info, stopAll: listener.stopAll };
|
|
805
1028
|
}
|
|
806
1029
|
|
|
807
1030
|
async function handleSearch(
|
package/bun.lock
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"configVersion": 1,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "@catheadowl/ukp",
|
|
7
|
-
"dependencies": {
|
|
8
|
-
"commander": "15.0.0",
|
|
9
|
-
"smol-toml": "1.7.1",
|
|
10
|
-
"zod": "4.4.3",
|
|
11
|
-
},
|
|
12
|
-
"devDependencies": {
|
|
13
|
-
"@types/bun": "1.3.5",
|
|
14
|
-
"typescript": "5.9.3",
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
"packages": {
|
|
19
|
-
"@types/bun": ["@types/bun@1.3.5", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.5.tgz", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
|
20
|
-
|
|
21
|
-
"@types/node": ["@types/node@26.1.1", "https://registry.npmmirror.com/@types/node/-/node-26.1.1.tgz", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
|
22
|
-
|
|
23
|
-
"bun-types": ["bun-types@1.3.5", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.5.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
|
24
|
-
|
|
25
|
-
"commander": ["commander@15.0.0", "https://registry.npmmirror.com/commander/-/commander-15.0.0.tgz", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="],
|
|
26
|
-
|
|
27
|
-
"smol-toml": ["smol-toml@1.7.1", "https://registry.npmmirror.com/smol-toml/-/smol-toml-1.7.1.tgz", {}, "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ=="],
|
|
28
|
-
|
|
29
|
-
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
30
|
-
|
|
31
|
-
"undici-types": ["undici-types@8.3.0", "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
|
32
|
-
|
|
33
|
-
"zod": ["zod@4.4.3", "https://registry.npmmirror.com/zod/-/zod-4.4.3.tgz", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
|
34
|
-
}
|
|
35
|
-
}
|
package/tsconfig.json
DELETED