@norskvideo/ctl-test-harness 0.1.79 → 0.1.81
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/demo/cli.d.ts +3 -1
- package/demo/cli.js +16 -1
- package/demo/harness.d.ts +2 -0
- package/demo/harness.js +35 -2
- package/demo/index.d.ts +1 -1
- package/demo/run.d.ts +3 -1
- package/demo/spec.d.ts +3 -0
- package/package.json +1 -1
package/demo/cli.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { type DevLoopOptions, type DevLoopResult } from "./dev-loop.js";
|
|
3
3
|
import { type DemoUi, type PaneLayout } from "./panes.js";
|
|
4
4
|
import { type DemoRunOptions, type DemoRunResult, demoPorts } from "./run.js";
|
|
5
|
-
import type { DemoDaemonPolicy, DemoMode, DemoSpec } from "./spec.js";
|
|
5
|
+
import type { DemoDaemonPolicy, DemoMode, DemoPortPolicy, DemoSpec } from "./spec.js";
|
|
6
6
|
export interface DemoArgs {
|
|
7
7
|
/** `dev-loop <action>`: the standalone tier's verbs. */
|
|
8
8
|
devLoop: boolean;
|
|
@@ -17,6 +17,8 @@ export interface DemoArgs {
|
|
|
17
17
|
ui?: DemoUi;
|
|
18
18
|
/** Default: PUBLIC_HOST in the environment. */
|
|
19
19
|
publicHost?: string;
|
|
20
|
+
/** `up`/`check`: absent is `assigned`. */
|
|
21
|
+
ports?: DemoPortPolicy;
|
|
20
22
|
}
|
|
21
23
|
export declare function parseDemoArgs(argv: string[]): DemoArgs;
|
|
22
24
|
export interface ResolvedSpecView {
|
package/demo/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ import { runDemo } from "./harness.js";
|
|
|
30
30
|
import { demoPaneList, devLoopPaneList, launchLayout, renderLayout, standaloneDirs, } from "./panes.js";
|
|
31
31
|
import { demoDown, demoPorts, demoSlug, runExportCheck } from "./run.js";
|
|
32
32
|
const USAGE = `usage: demo <up|check|down|spec|ui> [--mode dev|image|standalone] [--daemon private|reuse] [--export-only] [--json]
|
|
33
|
-
[--ui zellij|tmux|none] [--public-host <host>] [--spec <path>]
|
|
33
|
+
[--ports assigned|band] [--ui zellij|tmux|none] [--public-host <host>] [--spec <path>]
|
|
34
34
|
demo dev-loop <up|refresh|down|ui> [--ui zellij|tmux|none] [--public-host <host>] [--spec <path>]
|
|
35
35
|
|
|
36
36
|
up launch, print the URLs, hold (Ctrl-C tears down)
|
|
@@ -54,6 +54,10 @@ const USAGE = `usage: demo <up|check|down|spec|ui> [--mode dev|image|standalone]
|
|
|
54
54
|
--daemon reuse your listening daemon and real store; the driver deletes the instance,
|
|
55
55
|
removes the template and product, then adds again (stored templates
|
|
56
56
|
are immutable by name)
|
|
57
|
+
--ports assigned up/check: the daemon and proxy take ports the system assigns, so two runs
|
|
58
|
+
never collide (default)
|
|
59
|
+
--ports band up/check: the product's fixed band instead, so the proxy URL (and a
|
|
60
|
+
browser's secure-origin exception for it) survives a relaunch
|
|
57
61
|
--ui zellij|tmux|none the multiplexer (\`none\` prints the pane commands); zellij unless said
|
|
58
62
|
--public-host <host> the name other machines reach this box by: every URL, the daemon's
|
|
59
63
|
publicHost, Studio's MoQ preview (default: PUBLIC_HOST)
|
|
@@ -127,12 +131,22 @@ export function parseDemoArgs(argv) {
|
|
|
127
131
|
case "--public-host":
|
|
128
132
|
args.publicHost = value();
|
|
129
133
|
break;
|
|
134
|
+
case "--ports": {
|
|
135
|
+
const p = value();
|
|
136
|
+
if (p !== "assigned" && p !== "band")
|
|
137
|
+
throw new Error(`unknown ports '${p}'\n${USAGE}`);
|
|
138
|
+
args.ports = p;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
130
141
|
default:
|
|
131
142
|
throw new Error(`unknown flag '${flag}'\n${USAGE}`);
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
145
|
if (args.ui !== undefined && args.action !== "ui")
|
|
135
146
|
throw new Error("--ui belongs to ui (or dev-loop ui)");
|
|
147
|
+
if (args.ports !== undefined && (devLoop || (args.action !== "up" && args.action !== "check"))) {
|
|
148
|
+
throw new Error("--ports belongs to up and check");
|
|
149
|
+
}
|
|
136
150
|
if (devLoop && (args.mode !== undefined || args.daemon !== "private" || args.exportOnly || args.json)) {
|
|
137
151
|
throw new Error("dev-loop takes only --ui, --public-host and --spec (no --mode, --daemon, --export-only or --json): it is always the source tier on a private daemon");
|
|
138
152
|
}
|
|
@@ -290,6 +304,7 @@ export async function demoMain(argv, io) {
|
|
|
290
304
|
daemon: args.daemon,
|
|
291
305
|
cwd: io.cwd,
|
|
292
306
|
...publicHost,
|
|
307
|
+
...(args.ports !== undefined ? { ports: args.ports } : {}),
|
|
293
308
|
...(io.abort ? { abort: io.abort } : {}),
|
|
294
309
|
});
|
|
295
310
|
return 0;
|
package/demo/harness.d.ts
CHANGED
|
@@ -115,6 +115,8 @@ export declare class ProductHarness {
|
|
|
115
115
|
* daemon, the dev backend and the store. Throws if the store outlives the
|
|
116
116
|
* root-container nuke: a leak a runner would otherwise accumulate. */
|
|
117
117
|
teardown(): Promise<void>;
|
|
118
|
+
private instancePortsIssued;
|
|
119
|
+
private nextInstancePort;
|
|
118
120
|
private nextStudioHostPort;
|
|
119
121
|
}
|
|
120
122
|
/** The demo: a ProductHarness set up, run, held (`up`) or not (`check`), torn down. */
|
package/demo/harness.js
CHANGED
|
@@ -23,6 +23,12 @@ const STUDIO_HOST_PORT_PARAM = "STUDIO_HOST_PORT";
|
|
|
23
23
|
/** Launches after the first take studio host ports above the proxy's, inside
|
|
24
24
|
* the band and below `instancePortBase`: six of them. */
|
|
25
25
|
const EXTRA_STUDIO_PORTS = 6;
|
|
26
|
+
/** Allocated-port params come from `instancePortBase` to the band's end: ten. */
|
|
27
|
+
const INSTANCE_PORTS = 10;
|
|
28
|
+
/** A template's allocated host port: a `*_PORT` param defaulting to `auto`,
|
|
29
|
+
* which ctl resolves to the first free port from the template's PREFERRED
|
|
30
|
+
* one (probe's MOQ_PORT: 4443). */
|
|
31
|
+
const isAllocatedPortParam = (name, dflt) => dflt === "auto" && /_PORT$/.test(name);
|
|
26
32
|
export class ProductHarness {
|
|
27
33
|
spec;
|
|
28
34
|
slug;
|
|
@@ -135,6 +141,24 @@ export class ProductHarness {
|
|
|
135
141
|
else if (!internalOnly) {
|
|
136
142
|
launchArgv.push("--host-ports", `${studioHostPort}:${STUDIO_INTERNAL_PORT}@studio`);
|
|
137
143
|
}
|
|
144
|
+
// Allocated ports get a port from this run's band, not ctl's `auto`. Auto
|
|
145
|
+
// is "the first free port from the template's preferred one", and on a
|
|
146
|
+
// shared docker host every daemon prefers the same one: on 2026-10-08
|
|
147
|
+
// probe's guide gate lost MOQ_PORT's 4443 between ctl's free-port check
|
|
148
|
+
// and container start ("host port 4443 was taken between validation and
|
|
149
|
+
// container start") -- to another instance falling back to its own MoQ
|
|
150
|
+
// listener, or to the node MoQ relay, which publishes 4443 itself. The
|
|
151
|
+
// band is per product and below the ephemeral range (port-bands.test.ts),
|
|
152
|
+
// so nothing else on the host reaches for these. An internal-only launch
|
|
153
|
+
// publishes no host port at all, so `auto` is harmless there and stays.
|
|
154
|
+
if (!internalOnly) {
|
|
155
|
+
for (const [name, dflt] of declared) {
|
|
156
|
+
if (name === STUDIO_HOST_PORT_PARAM || specParams[name] !== undefined)
|
|
157
|
+
continue;
|
|
158
|
+
if (isAllocatedPortParam(name, dflt))
|
|
159
|
+
params.push(`${name}=${this.nextInstancePort(name)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
138
162
|
for (const p of params)
|
|
139
163
|
launchArgv.push("--param", p);
|
|
140
164
|
const hardware = overrides.hardware ?? spec.launch?.hardware;
|
|
@@ -260,6 +284,14 @@ export class ProductHarness {
|
|
|
260
284
|
throw new Error(`store ${s.storeDir} still present after the root-container nuke`);
|
|
261
285
|
}
|
|
262
286
|
}
|
|
287
|
+
instancePortsIssued = 0;
|
|
288
|
+
nextInstancePort(param) {
|
|
289
|
+
const n = this.instancePortsIssued++;
|
|
290
|
+
if (n >= INSTANCE_PORTS) {
|
|
291
|
+
throw new Error(`${param}: the band holds ${INSTANCE_PORTS} allocated instance ports and this run has used them all -- pass params.${param}`);
|
|
292
|
+
}
|
|
293
|
+
return this.ports.instancePortBase + n;
|
|
294
|
+
}
|
|
263
295
|
nextStudioHostPort() {
|
|
264
296
|
const n = this.launchedIds.length;
|
|
265
297
|
if (n === 0)
|
|
@@ -368,8 +400,9 @@ export async function runDemo(spec, opts, deps = defaultDemoDeps(opts.cwd)) {
|
|
|
368
400
|
...(opts.daemonPort !== undefined ? { daemonPort: opts.daemonPort } : {}),
|
|
369
401
|
...(opts.timeouts !== undefined ? { timeouts: opts.timeouts } : {}),
|
|
370
402
|
...(opts.publicHost !== undefined ? { publicHost: opts.publicHost } : {}),
|
|
371
|
-
// The driver owns every port of this run, so none can be left behind
|
|
372
|
-
|
|
403
|
+
// The driver owns every port of this run, so none can be left behind;
|
|
404
|
+
// `band` trades that for URLs that survive a relaunch.
|
|
405
|
+
ephemeralPorts: opts.ports !== "band",
|
|
373
406
|
});
|
|
374
407
|
if (opts.action === "up") {
|
|
375
408
|
await refuseIfUp(spec, h.session, deps);
|
package/demo/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ export type { DemoPane, DemoPaneOptions, DemoUi, DevLoopPaneOptions, PaneLayout,
|
|
|
9
9
|
export { DEV_LOOP_SOURCES, DEV_LOOP_STAMP, demoPaneList, devLoopPaneList, launchLayout, paneArgv, paneCommandLine, paneShellLine, renderLayout, shellQuote, standaloneDirs, } from "./panes.js";
|
|
10
10
|
export type { CliResult, CliStep, DemoDeps, DemoPorts, DemoRunOptions, DemoRunResult, DemoState, DemoStateKind, DemoStateStore, DemoTimeouts, ExportCheckOptions, IngestPortRow, PinMismatch, ProcessHandle, TemplateParams, } from "./run.js";
|
|
11
11
|
export { checkTemplatePins, defaultDemoDeps, demoDown, demoPorts, demoSlug, expandHome, exportStandaloneWorkdir, fileStateStore, findBrokenSymlinks, killGroup, resolveIngestPort, runExportCheck, } from "./run.js";
|
|
12
|
-
export type { DemoContext, DemoDaemonPolicy, DemoExtra, DemoIngest, DemoLaunchContext, DemoMode, DemoOpen, DemoReady, DemoSource, DemoSpec, DemoTemplate, DemoUrl, } from "./spec.js";
|
|
12
|
+
export type { DemoContext, DemoDaemonPolicy, DemoExtra, DemoIngest, DemoLaunchContext, DemoMode, DemoOpen, DemoPortPolicy, DemoReady, DemoSource, DemoSpec, DemoTemplate, DemoUrl, } from "./spec.js";
|
|
13
13
|
export { DemoSpecSchema, defineDemo, formatSpecIssues } from "./spec.js";
|
package/demo/run.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ensureRunnerOnNetwork } from "../container-net.js";
|
|
|
3
3
|
import { type DaemonProcess, type RunningDaemon, type StartDaemonOptions } from "../daemon.js";
|
|
4
4
|
import { type BaseHarnessPorts } from "../harness-config.js";
|
|
5
5
|
import { type SourceHandle, type SrtPumpTarget } from "../source-pump.js";
|
|
6
|
-
import type { DemoDaemonPolicy, DemoIngest, DemoMode, DemoSpec, DemoTemplate } from "./spec.js";
|
|
6
|
+
import type { DemoDaemonPolicy, DemoIngest, DemoMode, DemoPortPolicy, DemoSpec, DemoTemplate } from "./spec.js";
|
|
7
7
|
export interface CliResult {
|
|
8
8
|
stdout: string;
|
|
9
9
|
stderr: string;
|
|
@@ -127,6 +127,8 @@ export interface DemoRunOptions {
|
|
|
127
127
|
/** The hostname other machines reach this box by: every printed URL uses
|
|
128
128
|
* it, and a private daemon is initialised with it as its publicHost. */
|
|
129
129
|
publicHost?: string;
|
|
130
|
+
/** Default assigned. */
|
|
131
|
+
ports?: DemoPortPolicy;
|
|
130
132
|
}
|
|
131
133
|
export interface DemoRunResult {
|
|
132
134
|
instanceId: string;
|
package/demo/spec.d.ts
CHANGED
|
@@ -37,6 +37,9 @@ export type DemoMode = "dev" | "image";
|
|
|
37
37
|
* `check` always uses), or `reuse` the developer's listening daemon and its
|
|
38
38
|
* real store — where `product add` refuses an existing name and stored
|
|
39
39
|
* templates are immutable, so the driver deletes/removes/re-adds first. */
|
|
40
|
+
/** Whether a run's daemon and proxy take system-assigned ports, or the
|
|
41
|
+
* product's fixed band (stable URLs across relaunches, for development). */
|
|
42
|
+
export type DemoPortPolicy = "assigned" | "band";
|
|
40
43
|
export type DemoDaemonPolicy = "private" | "reuse";
|
|
41
44
|
/** What `beforeLaunch` sees: the template is stored, nothing is launched. */
|
|
42
45
|
export interface DemoLaunchContext {
|