@norskvideo/ctl-dev-kit 0.2.35 → 0.2.37

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.
@@ -30,6 +30,11 @@ on:
30
30
  schedule:
31
31
  - cron: "30 1 * * *" # 01:30 UTC — ahead of ctl's nightly docs build
32
32
  workflow_dispatch:
33
+ inputs:
34
+ publish:
35
+ description: "Publish the regenerated manual. Off runs every guide tier and reports the result without touching the docs-latest release (a branch dry run)."
36
+ type: boolean
37
+ default: true
33
38
 
34
39
  permissions:
35
40
  contents: write # move the docs-latest release + clobber its asset
@@ -226,6 +231,7 @@ jobs:
226
231
  # gh is on PATH inside the nix shell; `upload --clobber` replaces the
227
232
  # same-named asset in place, so the release stays latest-only.
228
233
  - name: Publish manual.html to the moving docs-latest release
234
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }}
229
235
  env:
230
236
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
231
237
  run: |
@@ -274,6 +280,7 @@ jobs:
274
280
  # exports map, so this works whether ctl-dev-kit is the published package or
275
281
  # a workspace link (scripts/set-ctl-source.ts local mode).
276
282
  - name: Publish the versioned docs bundle and move the pointer
283
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }}
277
284
  env:
278
285
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
279
286
  # How many bundles to keep on the release, the current one included.
@@ -308,6 +315,7 @@ jobs:
308
315
  # key through ci-workflows' registry, so a new product needs no new mapping
309
316
  # here. Not wrapped in nix -- plain curl, and the quoting stays readable.
310
317
  - name: Tell norsk-ctl a new bundle is published
318
+ if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }}
311
319
  env:
312
320
  DISPATCH_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
313
321
  run: |
@@ -337,3 +345,103 @@ jobs:
337
345
  echo "::warning::removing containers this job left running:$s"
338
346
  docker rm -f $s
339
347
  fi
348
+
349
+ # Report the result to the aggregated product CI dashboard (id3as/ci-workflows)
350
+ # as the `docs` cell. Red as well as green: the guides failed every night for a
351
+ # month while the board showed nothing, because this workflow never reported.
352
+ # A dry run (publish off) stays off the board -- it may be a branch.
353
+ notify:
354
+ needs: [publish]
355
+ if: ${{ !cancelled() && (github.event_name != 'workflow_dispatch' || inputs.publish) }}
356
+ runs-on: x64
357
+ steps:
358
+ # Runs BEFORE checkout, and does two things: points the harness's temp base
359
+ # out of the workspace for every later step in this job, and clears whatever
360
+ # the old repo-local default left behind on this runner. The body carries the
361
+ # why. It fails the job only when legacy litter survives -- which would take
362
+ # the checkout down a step later anyway, but report nothing useful.
363
+ - name: Route test-temp out of the workspace (pre-checkout)
364
+ run: |
365
+ set -uo pipefail
366
+ # These runners are docker-OUTSIDE-of-docker, so a bind-mount source the
367
+ # HOST daemon has not seen before is created by dockerd, as ROOT, on the
368
+ # host -- even when the launch passes --container-user (reproduced
369
+ # 2026-09-04). The harness's default base is repo-local for an OrbStack
370
+ # inotify constraint that binds macOS dev and nothing here, so every run
371
+ # seeded the CHECKOUT with root-owned dirs a non-root `git clean -ffdx`
372
+ # cannot remove. That took commentary's checks AND integration red on
373
+ # 2026-09-03. Use the sanctioned override, per-run and outside the
374
+ # checkout -- as 8ff17a8d did in June for one debug workflow, and stopped.
375
+ base="$RUNNER_TEMP/nctt-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
376
+ mkdir -p "$base"
377
+ echo "NORSK_CTL_TEST_TMP=$base" >> "$GITHUB_ENV"
378
+ # Yesterday's bases hold dockerd-created root dirs, so a non-root rm
379
+ # EACCESes. They cannot wedge a checkout, but they do fill the disk.
380
+ for old in $(find "$RUNNER_TEMP" -maxdepth 1 -name 'nctt-*' -mtime +0 -printf '%f\n' 2>/dev/null || true); do
381
+ docker run --rm --user 0:0 -v "$RUNNER_TEMP":/t alpine sh -c 'rm -rf -- "/t/$1"' sh "$old" >/dev/null 2>&1 || true
382
+ done
383
+ # LEGACY: a runner that ran the old default still carries a workspace
384
+ # test-temp, and a checkout with clean:true dies on it. Reap the holders
385
+ # first -- rm cannot unlink a live mountpoint even as root (EBUSY), and a
386
+ # RESTARTING leaked container re-creates the path as root seconds after
387
+ # any clean. Match on mount SOURCE, not a label: the holders are untracked
388
+ # by construction, because a killed run labels nothing.
389
+ tt="$GITHUB_WORKSPACE/test-temp"
390
+ [ -d "$tt" ] || exit 0
391
+ stuck=""
392
+ for c in $(docker ps -aq 2>/dev/null || true); do
393
+ if docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null | grep -q "^$tt/"; then
394
+ stuck="$stuck $c"
395
+ fi
396
+ done
397
+ if [ -n "$stuck" ]; then
398
+ echo "reaping containers holding mounts under $tt:$stuck"
399
+ docker rm -f $stuck || true
400
+ fi
401
+ # The DIRECTORY, not just its contents: an empty but root-owned base
402
+ # still EACCESes a later mkdtemp. Mounting the PARENT is what lets a root
403
+ # container unlink the leaf.
404
+ docker run --rm --user 0:0 -v "$GITHUB_WORKSPACE":/w alpine \
405
+ sh -c 'rm -rf /w/test-temp' || rm -rf "$tt" || true
406
+ # Fail HERE if it survived. The checkout fails on it either way, but
407
+ # reports only an EACCES rmdir with no clue what was holding the path.
408
+ if [ -e "$tt" ]; then
409
+ echo "::error::$tt survived the pre-checkout clean"
410
+ ls -lan "$tt" || true
411
+ exit 1
412
+ fi
413
+
414
+ - uses: actions/checkout@v5
415
+ with:
416
+ clean: true
417
+ - id: meta
418
+ run: |
419
+ if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then
420
+ echo "state=failure" >> "$GITHUB_OUTPUT"
421
+ else
422
+ echo "state=success" >> "$GITHUB_OUTPUT"
423
+ fi
424
+ - uses: ./.github/actions/ci-status-dispatch
425
+ with:
426
+ token: ${{ secrets.CI_DISPATCH_TOKEN }}
427
+ product: __PRODUCT__
428
+ pipeline: docs
429
+ status: ${{ steps.meta.outputs.state }}
430
+ - name: Reap the containers this job launched
431
+ # Whatever the tests did. A teardown cut short by a test timeout leaves
432
+ # its instance running on the shared Docker daemon (see JOB_REAP_STEP in
433
+ # conventions/workflow-shell.ts); everything this job launched mounts its
434
+ # own test-temp, so that is what goes.
435
+ if: always()
436
+ run: |
437
+ base="${NORSK_CTL_TEST_TMP:-}"
438
+ tt="$GITHUB_WORKSPACE/test-temp"
439
+ s=""
440
+ for c in $(docker ps -aq); do
441
+ src="$(docker inspect -f '{{range .Mounts}}{{println .Source}}{{end}}' "$c" 2>/dev/null)" || continue
442
+ if { [ -n "$base" ] && grep -q "^$base/" <<<"$src"; } || grep -q "^$tt/" <<<"$src"; then s="$s $c"; fi
443
+ done
444
+ if [ -n "$s" ]; then
445
+ echo "::warning::removing containers this job left running:$s"
446
+ docker rm -f $s
447
+ fi
@@ -88,12 +88,22 @@ export async function startInstanceProxy(opts) {
88
88
  const headers = new Headers(req.headers);
89
89
  headers.delete("host");
90
90
  const body = req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer();
91
- const upstream = await fetch(`${httpUpstream}${path}${u.search}`, {
92
- method: req.method,
93
- headers,
94
- body,
95
- redirect: "manual",
96
- });
91
+ // Studio being briefly unreachable is ordinary while a guide polls it; a
92
+ // throw here escapes as an unhandled error and fails every guide in the run.
93
+ let upstream;
94
+ try {
95
+ upstream = await fetch(`${httpUpstream}${path}${u.search}`, {
96
+ method: req.method,
97
+ headers,
98
+ body,
99
+ redirect: "manual",
100
+ });
101
+ }
102
+ catch (e) {
103
+ return new Response(`instance proxy: upstream unreachable: ${e instanceof Error ? e.message : String(e)}`, {
104
+ status: 502,
105
+ });
106
+ }
97
107
  const outHeaders = new Headers(upstream.headers);
98
108
  for (const h of STRIP_RESPONSE_HEADERS)
99
109
  outHeaders.delete(h);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./create-product": "./create-product/create-product.ts",