@norskvideo/ctl-test-harness 0.1.52 → 0.1.54
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/daemon.d.ts +14 -0
- package/daemon.js +35 -18
- package/package.json +8 -1
- package/source-pump.d.ts +9 -4
- package/source-pump.js +4 -0
package/daemon.d.ts
CHANGED
|
@@ -42,6 +42,20 @@ export interface StartDaemonOptions {
|
|
|
42
42
|
seedConfig?: boolean;
|
|
43
43
|
readyTimeoutMs?: number;
|
|
44
44
|
}
|
|
45
|
+
/** Root the instance working directory under the store dir (itself under
|
|
46
|
+
* TEST_TMP_BASE) rather than the daemon default ~/norsk-runtime. The launcher
|
|
47
|
+
* bind-mounts the workdir as /data and then mounts the stored template's
|
|
48
|
+
* workflow.yml (which lives under the store dir) *inside* it — Docker Desktop
|
|
49
|
+
* rejects a nested bind mount whose inner source resolves to a different host
|
|
50
|
+
* share-root than the outer, so both mounts must share the TEST_TMP_BASE root.
|
|
51
|
+
* With NORSK_CTL_STORE_DIR set, config.yaml is read from <storeDir>/config.yaml.
|
|
52
|
+
* networkMode is the one required config.yaml field — omitting it makes the
|
|
53
|
+
* whole file fail to parse (ConfigParseError), silently dropping the workdir
|
|
54
|
+
* override. Seed both.
|
|
55
|
+
*
|
|
56
|
+
* A suite that writes its own config.yaml (to pick a networkMode, a proxy
|
|
57
|
+
* port) still gets the working directory added, unless it states one. */
|
|
58
|
+
export declare function seedDaemonConfig(storeDir: string): void;
|
|
45
59
|
export declare function startDaemon(storeDir: string, options?: StartDaemonOptions): {
|
|
46
60
|
daemon: DaemonProcess;
|
|
47
61
|
ready: Promise<void>;
|
package/daemon.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// works under bun AND under Playwright's Node workers — so the doc-guide tier
|
|
9
9
|
// consumes these same bodies instead of keeping a third fork.
|
|
10
10
|
import { spawn, spawnSync } from "node:child_process";
|
|
11
|
-
import { existsSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { createServer } from "node:net";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { cliCommand } from "./cli-command.js";
|
|
@@ -118,30 +118,47 @@ function daemonHandle(proc) {
|
|
|
118
118
|
exited,
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
+
/** Root the instance working directory under the store dir (itself under
|
|
122
|
+
* TEST_TMP_BASE) rather than the daemon default ~/norsk-runtime. The launcher
|
|
123
|
+
* bind-mounts the workdir as /data and then mounts the stored template's
|
|
124
|
+
* workflow.yml (which lives under the store dir) *inside* it — Docker Desktop
|
|
125
|
+
* rejects a nested bind mount whose inner source resolves to a different host
|
|
126
|
+
* share-root than the outer, so both mounts must share the TEST_TMP_BASE root.
|
|
127
|
+
* With NORSK_CTL_STORE_DIR set, config.yaml is read from <storeDir>/config.yaml.
|
|
128
|
+
* networkMode is the one required config.yaml field — omitting it makes the
|
|
129
|
+
* whole file fail to parse (ConfigParseError), silently dropping the workdir
|
|
130
|
+
* override. Seed both.
|
|
131
|
+
*
|
|
132
|
+
* A suite that writes its own config.yaml (to pick a networkMode, a proxy
|
|
133
|
+
* port) still gets the working directory added, unless it states one. */
|
|
134
|
+
export function seedDaemonConfig(storeDir) {
|
|
135
|
+
const configPath = join(storeDir, "config.yaml");
|
|
136
|
+
const workdirLine = `defaultWorkingDirectory: ${JSON.stringify(join(storeDir, "norsk-runtime"))}\n`;
|
|
137
|
+
if (!existsSync(configPath)) {
|
|
138
|
+
writeFileSync(configPath, `networkMode: docker\n${workdirLine}`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const existing = readFileSync(configPath, "utf-8");
|
|
142
|
+
if (/^defaultWorkingDirectory\s*:/m.test(existing))
|
|
143
|
+
return;
|
|
144
|
+
const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
145
|
+
writeFileSync(configPath, `${existing}${separator}${workdirLine}`);
|
|
146
|
+
}
|
|
121
147
|
export function startDaemon(storeDir, options) {
|
|
122
148
|
const port = options?.port ?? 8333;
|
|
123
149
|
killProcessOnPort(port);
|
|
124
|
-
if (options?.seedConfig !== false)
|
|
125
|
-
|
|
126
|
-
// TEST_TMP_BASE) rather than the daemon default ~/norsk-runtime. The launcher
|
|
127
|
-
// bind-mounts the workdir as /data and then mounts the stored template's
|
|
128
|
-
// workflow.yml (which lives under the store dir) *inside* it — Docker Desktop
|
|
129
|
-
// rejects a nested bind mount whose inner source resolves to a different host
|
|
130
|
-
// share-root than the outer, so both mounts must share the TEST_TMP_BASE root.
|
|
131
|
-
// With NORSK_CTL_STORE_DIR set, config.yaml is read from <storeDir>/config.yaml.
|
|
132
|
-
// networkMode is the one required config.yaml field — omitting it makes the
|
|
133
|
-
// whole file fail to parse (ConfigParseError), silently dropping the workdir
|
|
134
|
-
// override. Seed both.
|
|
135
|
-
const configPath = join(storeDir, "config.yaml");
|
|
136
|
-
if (!existsSync(configPath)) {
|
|
137
|
-
writeFileSync(configPath, `networkMode: docker\ndefaultWorkingDirectory: ${JSON.stringify(join(storeDir, "norsk-runtime"))}\n`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
150
|
+
if (options?.seedConfig !== false)
|
|
151
|
+
seedDaemonConfig(storeDir);
|
|
140
152
|
const [cmd, ...cmdArgs] = cliCommand;
|
|
153
|
+
// A spawned daemon must boot GUARDED: exercising the real ingress secret is
|
|
154
|
+
// the point of the tiers that spawn one. The unit tier's marker would
|
|
155
|
+
// otherwise be inherited straight through this env spread and quietly open
|
|
156
|
+
// it (RFC 0003 D10b).
|
|
157
|
+
const { NORSK_CTL_UNGUARDED_TEST_DAEMON: _unguarded, ...inheritedEnv } = process.env;
|
|
141
158
|
const proc = spawn(cmd, [...cmdArgs, "serve"], {
|
|
142
159
|
stdio: "inherit",
|
|
143
160
|
detached: options?.detached === true,
|
|
144
|
-
env: { ...
|
|
161
|
+
env: { ...inheritedEnv, NORSK_CTL_STORE_DIR: storeDir, NORSK_CTL_PORT: String(port), ...options?.env },
|
|
145
162
|
});
|
|
146
163
|
const spawnFailed = new Promise((_, reject) => {
|
|
147
164
|
proc.once("error", (e) => reject(new Error(cliUnavailableMessage(cliCommand, e.message))));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-test-harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -74,6 +74,13 @@
|
|
|
74
74
|
"@norskvideo/norsk-studio": "*",
|
|
75
75
|
"@norskvideo/norsk-studio-built-ins": "*"
|
|
76
76
|
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@types/bun": "latest",
|
|
79
|
+
"@types/node": "^22.10.0",
|
|
80
|
+
"@norskvideo/norsk-studio": "1.29.0-2026-09-10-b21d9013",
|
|
81
|
+
"@norskvideo/norsk-studio-built-ins": "1.29.0-2026-09-10-b21d9013",
|
|
82
|
+
"@norskvideo/norsk-sdk": "1.3.0-2026-09-17-3b73a790"
|
|
83
|
+
},
|
|
77
84
|
"peerDependenciesMeta": {
|
|
78
85
|
"@norskvideo/norsk-studio": {
|
|
79
86
|
"optional": true
|
package/source-pump.d.ts
CHANGED
|
@@ -36,10 +36,15 @@ export interface StartCameraSourceOptions {
|
|
|
36
36
|
* (kept even when the fallback is used, so the handle is stable). Required
|
|
37
37
|
* when the same asset must run multiple times within an instance. */
|
|
38
38
|
name?: string;
|
|
39
|
-
/** Sent as `x-norsk-proxy
|
|
40
|
-
* proxy
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
/** Sent as `x-norsk-proxy`. **Effectively required.** The daemon writes it
|
|
40
|
+
* to `<stateDir>/proxy-secret` (`<storeDir>/proxy-secret` under
|
|
41
|
+
* NORSK_CTL_STORE_DIR); read it from there and pass it.
|
|
42
|
+
*
|
|
43
|
+
* BREAKING for callers that omitted it (RFC 0003 D10b, ctl >= this
|
|
44
|
+
* release): the daemon used to admit any loopback caller without a secret
|
|
45
|
+
* while TLS was off, and that bypass is gone — a loopback caller is a host
|
|
46
|
+
* admin, and a host admin can read the secret file. A harness or guide that
|
|
47
|
+
* relied on it now fails with `POST /api/sources failed (307)`. */
|
|
43
48
|
proxySecret?: string;
|
|
44
49
|
timeoutMs?: number;
|
|
45
50
|
/** Test seams. */
|
package/source-pump.js
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
// `name` overrides the source identity (container name + management key) so
|
|
11
11
|
// multiple sources on the same asset can co-exist within one instance. See
|
|
12
12
|
// `startSrtSources` for the multi-port fan-out helper that uses it.
|
|
13
|
+
//
|
|
14
|
+
// PASS `proxySecret`. RFC 0003 D10b removed the daemon's loopback bypass, so
|
|
15
|
+
// a call without it now 307s where it used to be admitted. See the field's
|
|
16
|
+
// docblock for where to read the secret from.
|
|
13
17
|
import { existsSync } from "node:fs";
|
|
14
18
|
import { basename, extname } from "node:path";
|
|
15
19
|
import { pollUntil } from "./poll.js";
|