@norskvideo/ctl-test-harness 0.1.49 → 0.1.51

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/harness.js CHANGED
@@ -14,6 +14,7 @@
14
14
  // program SRT listener, an HLS ladder), its config-schema port remap for a
15
15
  // built template, and whatever sidecar its output points at.
16
16
  import { netReachMode, STUDIO_INTERNAL_PORT, studioBaseFrom } from "../container-net.js";
17
+ import { ctlSupportsPublishDebugPorts } from "../launch.js";
17
18
  import { pollUntil } from "../poll.js";
18
19
  import { DemoGateFailure } from "./gates.js";
19
20
  import { DemoSession, defaultDemoDeps, demoSlug, expandHome, resolveIngestPort, } from "./run.js";
@@ -146,6 +147,18 @@ export class ProductHarness {
146
147
  launchArgv.push("--container-user", user);
147
148
  if (internalOnly)
148
149
  launchArgv.push("--internal-only");
150
+ // ctl binds a product's studio host port to 127.0.0.1. Every gate that
151
+ // fetches it names `s.host` — "the host every printed URL names" — so a
152
+ // non-loopback host is the harness's own statement that it will reach that
153
+ // port from elsewhere: a reused host-sibling daemon over the host-gateway
154
+ // alias, or an explicit --public-host. Derived from the host already
155
+ // computed rather than a second thing for a demo spec to remember to set,
156
+ // and probed because the CLI is `.strict()` and a released ctl predating
157
+ // the flag would abort the launch.
158
+ else if (s.host !== "localhost" && (await (deps.supportsPublishDebugPorts ?? ctlSupportsPublishDebugPorts)())) {
159
+ launchArgv.push("--publish-debug-ports");
160
+ deps.log(`studio host port re-opened on every interface for ${s.host} (--publish-debug-ports)`);
161
+ }
149
162
  if (!this.launchedIds.includes(instanceId))
150
163
  this.launchedIds.push(instanceId);
151
164
  // The whole list each time, not an append: idempotent, and readable by
package/demo/run.d.ts CHANGED
@@ -85,6 +85,9 @@ export interface DemoDeps {
85
85
  storeExists(storeDir: string): boolean;
86
86
  ensureNetwork(): ReturnType<typeof ensureRunnerOnNetwork>;
87
87
  supportsNoPublish(): Promise<boolean>;
88
+ /** Does the resolved ctl advertise `--publish-debug-ports`? Optional so an
89
+ * existing custom-deps consumer keeps working; absent = probe the CLI. */
90
+ supportsPublishDebugPorts?(): Promise<boolean>;
88
91
  containerUser(): string | undefined;
89
92
  /** Symlinks under an exported workdir whose target does not exist, as
90
93
  * `<link> -> <target>` lines. */
package/demo/run.js CHANGED
@@ -32,7 +32,7 @@ import { parseManifestSeed, repoOf } from "@norskvideo/ctl-sdk/manifest-seed";
32
32
  import { DOCKER_NETWORK_NAME, ensureRunnerOnNetwork, netReachMode } from "../container-net.js";
33
33
  import { cleanupDaemon, requireLicenseFile, runCli, startDaemon, } from "../daemon.js";
34
34
  import { hashSlot } from "../harness-config.js";
35
- import { ctlSupportsNoPublish, runnerContainerUser } from "../launch.js";
35
+ import { ctlSupportsNoPublish, ctlSupportsPublishDebugPorts, runnerContainerUser } from "../launch.js";
36
36
  import { pollUntil } from "../poll.js";
37
37
  import { nukePathAsRoot, reportForeignOwners } from "../root-nuke.js";
38
38
  import { startSrtSources } from "../source-pump.js";
@@ -300,6 +300,7 @@ export function defaultDemoDeps(cwd) {
300
300
  storeExists: (storeDir) => existsSync(storeDir),
301
301
  ensureNetwork: () => ensureRunnerOnNetwork(),
302
302
  supportsNoPublish: ctlSupportsNoPublish,
303
+ supportsPublishDebugPorts: ctlSupportsPublishDebugPorts,
303
304
  containerUser: runnerContainerUser,
304
305
  brokenSymlinks: findBrokenSymlinks,
305
306
  state: fileStateStore(cwd),
package/launch.d.ts CHANGED
@@ -14,6 +14,13 @@ export declare function withContainerUser<T>(opts: T & {
14
14
  };
15
15
  /** True when the resolved norsk-ctl CLI understands `--internal-only`. Memoised. */
16
16
  export declare function ctlSupportsNoPublish(): Promise<boolean>;
17
+ /** True when the resolved norsk-ctl CLI understands `--publish-debug-ports` —
18
+ * the flag that keeps a product's studio host port on every interface, which a
19
+ * harness reaching it from another host needs. Same probe for the same reason
20
+ * as `--internal-only` above: a released ctl that predates the flag would abort
21
+ * the launch under `.strict()`, and products pin a RELEASED ctl while picking
22
+ * the harness up separately, so the two versions genuinely diverge. */
23
+ export declare function ctlSupportsPublishDebugPorts(): Promise<boolean>;
17
24
  /** In `direct` reach mode, ask the daemon to bind no host ports (`internalOnly`)
18
25
  * so concurrent instances on a shared runner can't collide on the product's
19
26
  * host-port bands — but only when the resolved ctl supports the flag (see
package/launch.js CHANGED
@@ -42,8 +42,9 @@ export function withContainerUser(opts) {
42
42
  // catches up — no cross-repo release ordering to get right. Cached: one `--help`
43
43
  // spawn per process.
44
44
  const INTERNAL_ONLY_FLAG = "--internal-only";
45
- let internalOnlySupport;
46
- async function probeInternalOnlySupport() {
45
+ const PUBLISH_DEBUG_PORTS_FLAG = "--publish-debug-ports";
46
+ const flagSupport = new Map();
47
+ async function probeFlagSupport(flag) {
47
48
  try {
48
49
  const proc = Bun.spawn([...cliCommand, "instance", "launch-template", "--help"], {
49
50
  stdout: "pipe",
@@ -51,16 +52,34 @@ async function probeInternalOnlySupport() {
51
52
  });
52
53
  const [out, errText] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
53
54
  await proc.exited;
54
- return `${out}${errText}`.includes(INTERNAL_ONLY_FLAG);
55
+ return `${out}${errText}`.includes(flag);
55
56
  }
56
57
  catch {
57
58
  return false;
58
59
  }
59
60
  }
61
+ /** True when the resolved norsk-ctl CLI advertises `flag` in its launch help.
62
+ * Memoised per flag: one `--help` spawn each, however many callers ask. */
63
+ function ctlSupportsFlag(flag) {
64
+ const cached = flagSupport.get(flag);
65
+ if (cached)
66
+ return cached;
67
+ const probe = probeFlagSupport(flag);
68
+ flagSupport.set(flag, probe);
69
+ return probe;
70
+ }
60
71
  /** True when the resolved norsk-ctl CLI understands `--internal-only`. Memoised. */
61
72
  export function ctlSupportsNoPublish() {
62
- internalOnlySupport ??= probeInternalOnlySupport();
63
- return internalOnlySupport;
73
+ return ctlSupportsFlag(INTERNAL_ONLY_FLAG);
74
+ }
75
+ /** True when the resolved norsk-ctl CLI understands `--publish-debug-ports` —
76
+ * the flag that keeps a product's studio host port on every interface, which a
77
+ * harness reaching it from another host needs. Same probe for the same reason
78
+ * as `--internal-only` above: a released ctl that predates the flag would abort
79
+ * the launch under `.strict()`, and products pin a RELEASED ctl while picking
80
+ * the harness up separately, so the two versions genuinely diverge. */
81
+ export function ctlSupportsPublishDebugPorts() {
82
+ return ctlSupportsFlag(PUBLISH_DEBUG_PORTS_FLAG);
64
83
  }
65
84
  /** In `direct` reach mode, ask the daemon to bind no host ports (`internalOnly`)
66
85
  * so concurrent instances on a shared runner can't collide on the product's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-test-harness",
3
- "version": "0.1.49",
3
+ "version": "0.1.51",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/smoke.d.ts CHANGED
@@ -142,6 +142,9 @@ export interface SmokeDeps {
142
142
  storeExists(storeDir: string): boolean;
143
143
  ensureNetwork(): ReturnType<typeof ensureRunnerOnNetwork>;
144
144
  supportsNoPublish(): Promise<boolean>;
145
+ /** Does the resolved ctl advertise `--publish-debug-ports`? Optional so an
146
+ * existing custom-deps consumer keeps working; absent = probe the CLI. */
147
+ supportsPublishDebugPorts?(): Promise<boolean>;
145
148
  containerUser(): string | undefined;
146
149
  /** Diagnostic dump for a satisfied-phase timeout, taken while the instance
147
150
  * is still up. Optional so an existing custom-deps consumer keeps working;
package/smoke.js CHANGED
@@ -23,7 +23,7 @@ import { dumpInstanceContainerLogs, preserveEngineLogs } from "./container-logs.
23
23
  import { ensureRunnerOnNetwork, netReachMode, STUDIO_INTERNAL_PORT, studioBaseFrom, } from "./container-net.js";
24
24
  import { cleanupDaemon, requireLicenseFile, runCli, startDaemon, } from "./daemon.js";
25
25
  import { hashSlot } from "./harness-config.js";
26
- import { ctlSupportsNoPublish, runnerContainerUser } from "./launch.js";
26
+ import { ctlSupportsNoPublish, ctlSupportsPublishDebugPorts, runnerContainerUser } from "./launch.js";
27
27
  import { pollUntil } from "./poll.js";
28
28
  import { nukePathAsRoot, reportForeignOwners } from "./root-nuke.js";
29
29
  import { startSrtSources } from "./source-pump.js";
@@ -72,6 +72,7 @@ export const defaultSmokeDeps = {
72
72
  storeExists: (storeDir) => existsSync(storeDir),
73
73
  ensureNetwork: () => ensureRunnerOnNetwork(),
74
74
  supportsNoPublish: ctlSupportsNoPublish,
75
+ supportsPublishDebugPorts: ctlSupportsPublishDebugPorts,
75
76
  containerUser: runnerContainerUser,
76
77
  diagnose: (instanceId) => dumpInstanceContainerLogs(instanceId),
77
78
  fetchProductRoute: async ({ daemonPort, product, route, storeDir }) => {
@@ -328,6 +329,15 @@ export async function runProductSmoke(slug, spec, deps = defaultSmokeDeps) {
328
329
  launchArgv.push("--container-user", user);
329
330
  if (internalOnly)
330
331
  launchArgv.push("--internal-only");
332
+ // ctl binds a product's OPERATOR compose ports (STUDIO_HOST_PORT among
333
+ // them) to 127.0.0.1. `fetchStudio` dials NORSK_TEST_HOST in publish mode;
334
+ // when that is not loopback the test process is a container reaching a host
335
+ // sibling, which a loopback bind refuses. Derived from the host the
336
+ // fetchers already use rather than a second thing to configure.
337
+ else if ((process.env.NORSK_TEST_HOST ?? "localhost") !== "localhost" &&
338
+ (await (deps.supportsPublishDebugPorts ?? ctlSupportsPublishDebugPorts)())) {
339
+ launchArgv.push("--publish-debug-ports");
340
+ }
331
341
  launched = true;
332
342
  await cliOk(launchArgv);
333
343
  await pollUntil(async () => {