@norskvideo/ctl-dev-kit 0.1.7 → 0.1.9

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.
@@ -18,9 +18,16 @@
18
18
  #
19
19
  # Everything else is auto-detected off PRODUCT_DIR, so a product opts in just by
20
20
  # having the directory/script — no per-product flag to keep in sync:
21
+ # frontend/dist the frontend workspace's build output
22
+ # dashboards/ the dashboard workspace's build output
23
+ # components/lib the components workspace's build output
21
24
  # native/ native addon(s) (probe's signer) -> beside bundle
22
25
  # assets/ product-template assets (funke's PNGs)
23
26
  # scripts/runtime-images.ts -> emits the norsk-ctl.runtime-images label
27
+ #
28
+ # Only backend/dist is required. A product with no frontend/dashboard/components
29
+ # workspace (reuters) stages those empty and builds the same as one that has all
30
+ # three (funke/probe/commentary).
24
31
  set -euo pipefail
25
32
 
26
33
  : "${PRODUCT_DIR:?build-bundle-image.sh: PRODUCT_DIR must be set by the wrapper}"
@@ -43,11 +50,26 @@ stage="$(mktemp -d)"
43
50
  trap 'rm -rf "${stage}"' EXIT
44
51
 
45
52
  echo "==> staging artifacts"
46
- mkdir -p "${stage}/backend" "${stage}/frontend" "${stage}/components" "${stage}/native" "${stage}/assets"
53
+ mkdir -p "${stage}/backend" "${stage}/frontend/dist" "${stage}/components/lib" \
54
+ "${stage}/dashboards" "${stage}/native" "${stage}/assets"
55
+ # The backend bundle is the one non-negotiable payload — every bundle product has
56
+ # one, so a missing dist is a broken build, not an absent workspace.
47
57
  cp -R "${PRODUCT_DIR}/backend/dist" "${stage}/backend/dist"
48
- cp -R "${PRODUCT_DIR}/frontend/dist" "${stage}/frontend/dist"
49
- cp -R "${PRODUCT_DIR}/dashboards" "${stage}/dashboards"
50
- cp -R "${PRODUCT_DIR}/components/lib" "${stage}/components/lib"
58
+
59
+ # Frontend / dashboards / components are produced by the workspaces of the same
60
+ # name, so a product that has no such workspace never produces them (reuters:
61
+ # backend-only). Staged empty in that case, exactly as native/ and assets/ below
62
+ # — Dockerfile.bundle COPYs all six unconditionally, so the path must exist even
63
+ # when there is nothing to put in it.
64
+ if [ -d "${PRODUCT_DIR}/frontend/dist" ]; then
65
+ cp -R "${PRODUCT_DIR}/frontend/dist/." "${stage}/frontend/dist/"
66
+ fi
67
+ if [ -d "${PRODUCT_DIR}/dashboards" ]; then
68
+ cp -R "${PRODUCT_DIR}/dashboards/." "${stage}/dashboards/"
69
+ fi
70
+ if [ -d "${PRODUCT_DIR}/components/lib" ]; then
71
+ cp -R "${PRODUCT_DIR}/components/lib/." "${stage}/components/lib/"
72
+ fi
51
73
 
52
74
  # Native addon(s): staged beside the bundle. Strip the build host's RUNPATH (nix
53
75
  # store / repo-local paths) so the shipped .node carries no build-host paths; its
@@ -0,0 +1,140 @@
1
+ # Product integration testing on the DooD runners
2
+
3
+ Guidelines for writing (and debugging) a split product's slow-tier integration
4
+ suite against the org's self-hosted CI. These are hard-won: the failures below
5
+ _look_ like license, networking, or engine bugs but are almost always
6
+ **runner/harness setup** — mounts, ports, hosts, image provenance. Chasing them
7
+ as product bugs wastes time.
8
+
9
+ The reference implementations are the `norsk-ctl-product-playout` and
10
+ `norsk-ctl-product-commentary` harnesses; the shared helpers live in
11
+ `@norskvideo/ctl-test-harness`.
12
+
13
+ ## Runner topology (docker-outside-of-docker)
14
+
15
+ The CI runner is itself a **container**. The daemon (`norsk-ctl`) and the product
16
+ backend are spawned **in-process inside that container**. Studio + the Norsk
17
+ media engine launch as **host siblings** (the runner shares the host docker
18
+ socket), with their ports **published on the host**.
19
+
20
+ Consequence — two address classes the harness must not confuse:
21
+
22
+ | Reaching… | Address |
23
+ | ------------------------------------ | ----------------------------------------- |
24
+ | the daemon, the product backend | `localhost:<port>` (in-process) |
25
+ | studio / media host-published ports | `${NORSK_TEST_HOST}:<port>` |
26
+ | a launched container by compose name | `<instance>-<service>-1` **on norsk-net** |
27
+
28
+ `NORSK_TEST_HOST` is `host.docker.internal` in CI and unset (→ `localhost`)
29
+ locally. Set it in the workflow env. The shared `studio-state` fetchers
30
+ (`fetchComponents`, `fetchComponentState`, …) already honour it; **any local
31
+ support code that hits a studio/media port must route through a `TEST_HOST`
32
+ helper** (`process.env.NORSK_TEST_HOST ?? "localhost"`), never a hardcoded
33
+ `localhost`. Daemon/backend fetches stay on `localhost`.
34
+
35
+ ## Host-shared working directory (or studio EACCESes on boot)
36
+
37
+ Studio bind-mounts its instance working directory as `${workdir}:/data` and runs
38
+ as a **non-root** auto-detected user. If the workdir is the daemon default
39
+ (`~/norsk-runtime`, container-only), the sibling mount materialises **root-owned**
40
+ and studio dies at boot with `EACCES: mkdir '/data/moq-certs'` — the workflow
41
+ never starts, the SRT graph never builds, and every downstream test times out
42
+ looking like a networking/license fault.
43
+
44
+ Fix: point the daemon at a **host-shared** workdir under the test store dir.
45
+ Pre-write `config.yaml` with `defaultWorkingDirectory` under `makeStoreDir(...)`
46
+ (which roots in the gitignored, host-shared `test-temp/`):
47
+
48
+ ```ts
49
+ writeFileSync(
50
+ join(storeDir, "config.yaml"),
51
+ `networkMode: docker\ndefaultWorkingDirectory: ${JSON.stringify(join(storeDir, "norsk-runtime"))}\n`,
52
+ );
53
+ ```
54
+
55
+ Docker still leaves **root-owned** read-only bind-mount target dirs inside that
56
+ workdir, which the non-root runner can't `rm`. So `teardown()` must nuke the
57
+ store dir from a throwaway **root** container, or `afterAll` (and the runner's
58
+ own checkout cleanup) blocks:
59
+
60
+ ```ts
61
+ spawnSync("docker", ["run", "--rm", "--user", "0:0", "-v", `${dirname(storeDir)}:/base`,
62
+ "--entrypoint", "sh", STUDIO_IMAGE, "-c", `rm -rf /base/${basename(storeDir)}`]);
63
+ ```
64
+
65
+ ## Reaching served media / HLS output
66
+
67
+ Studio reports playback URLs (HLS multivariant, WHIP endpoints) with the host it
68
+ was **configured with** — loopback locally, which the in-container test can't
69
+ reach. Two cases:
70
+
71
+ - **Proxied harness** — the URL is on the daemon's `publicHost` (default
72
+ `:443`). Route the fetch through `applyTestHost(url)` (swaps a loopback host
73
+ for `NORSK_TEST_HOST`). `assertMultivariantHasRenditions` does this by default.
74
+ - **Proxy-less harness** — the advertised `/instance/<id>/media/...` route is
75
+ served **only by the daemon's nginx proxy**; with no proxy, nothing answers on
76
+ `:443`. Reach the media container **directly on norsk-net** instead:
77
+ `mediaDirectUrl(url)` rewrites it to `http://<id>-media-1:8080/<native-path>`
78
+ (the same bypass `whip-driver` uses; the media route is unauthenticated). Pass
79
+ it via `assertMultivariantHasRenditions`'s `resolveFetchUrl` hook, and **join
80
+ the runner to norsk-net** in setup so the compose service name resolves:
81
+
82
+ ```ts
83
+ spawnSync("docker", ["network", "create", "norsk-net"]); // idempotent
84
+ spawnSync("docker", ["network", "connect", "norsk-net", hostname()]);
85
+ ```
86
+
87
+ ## Capture servers (studio pushes → the runner)
88
+
89
+ When a test stands up an HLS/SCTE-35 capture server that **studio pushes to**,
90
+ studio (a host sibling on norsk-net) can't reach the runner via the host LAN IP.
91
+ Join norsk-net and hand studio the runner's **norsk-net IP** (`docker inspect`
92
+ self), falling back to the host LAN IP only when `NORSK_TEST_HOST` is unset. See
93
+ `manifest-capture.ts`'s `captureHost()`.
94
+
95
+ ## Build the product image in the integration job
96
+
97
+ A `kind: dev` product still launches an instance whose compose references the
98
+ product's **image** (e.g. the runtime-screen `funke-backend` service on
99
+ `<product>:dev`). The `build-image` job builds it on a _different_ runner, so the
100
+ integration job must build it too — otherwise `docker compose up` tries to
101
+ **pull** the local-only tag and dies with `pull access denied` before any test
102
+ runs. Add `bun run build:image` inside the `nix develop .#build` block (docker is
103
+ reachable there); it also builds the dist the dev backend serves `/configure`
104
+ from.
105
+
106
+ ## Stale node_modules on `clean: false` runners
107
+
108
+ The runners check out `clean: false` (to preserve `test-temp/`), so
109
+ `node_modules` **persists between runs**, and `bun install --frozen-lockfile`
110
+ does **not** reliably relink a workspace-hoisted package whose version moved. A
111
+ stale `@norskvideo/ctl-test-harness@0.1.2` once survived a bump to `0.1.3`, so a
112
+ newly-added export was `SyntaxError: export 'X' not found` and the suite crashed
113
+ in <1s. The bun **global cache is integrity-checked** (it can't serve a wrong
114
+ version); only the persisted `node_modules` dir goes stale. Guard the install:
115
+
116
+ ```yaml
117
+ rm -rf node_modules/@norskvideo/ctl-test-harness
118
+ bun install --frozen-lockfile
119
+ ```
120
+
121
+ When a product pins the shared harness in **both** `package.json` **and**
122
+ `tests/package.json`, bump **both** — a lone root bump can leave the tests
123
+ workspace resolving the old version.
124
+
125
+ ## GPU
126
+
127
+ Instances launch with `--hardware nvidia` and encode on **nvenc** on the runners
128
+ (the nvenc log-grep + no-software-fallback assertions pass). Note the
129
+ `/usr/bin/nvidia-smi` **binary** may be absent in the media container even when
130
+ nvenc works — assert on the encoder logs, not on `nvidia-smi` exit status.
131
+
132
+ ## License
133
+
134
+ The org secret `NORSK_LICENSE_V2` + a modern engine accept the license (a green
135
+ `probe` run proves it). V2 licenses need a **post-2026-07-17** engine, and SDK
136
+ clients that create media nodes (e.g. a WHIP driver) must answer the license
137
+ **handshake** via `createLicenseChallengeResponder` or the engine aborts node
138
+ creation with `10 ABORTED: License handshake required`. The `license is marked
139
+ NON-PRODUCTION` warning is expected in CI, not an error. The media engine's
140
+ `hugepages / rte_eal_init failed` line is **benign noise** — do not chase it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json"