@norskvideo/ctl-dev-kit 0.2.60 → 0.2.61
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/build/flake.sdk-app.nix +5 -1
- package/conventions/CLAUDE.core.md +6 -0
- package/conventions/bot-prs.yml +124 -13
- package/conventions/build-docs.yml +7 -3
- package/conventions/build-image.yml +25 -8
- package/conventions/check-drift.ts +2 -2
- package/conventions/integration.yml +32 -7
- package/conventions/publish-docs.yml +7 -3
- package/conventions/smoke.yml +22 -6
- package/conventions/sync-ctl-packages.yml +7 -6
- package/conventions/sync-dev-kit.yml +73 -73
- package/conventions/upgrade-latest.sdk-app.yml +112 -78
- package/conventions/upgrade-latest.yml +101 -93
- package/docs-theme/README.md +2 -2
- package/package.json +1 -1
|
@@ -9,22 +9,29 @@
|
|
|
9
9
|
# tracked package.json actually depends on them) move in lockstep via a global
|
|
10
10
|
# string-replace of old->new across every tracked file — which also updates the
|
|
11
11
|
# version-pin guard test, no drift. A repo with no studio npm deps needs no
|
|
12
|
-
# package.json `overrides` block at all. Also
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# package.json `overrides` block at all. Also moves flake.nix's norsk-ctl pin
|
|
13
|
+
# (ctlVersion + the per-platform hashes) to the `latest` channel. That pin is
|
|
14
|
+
# not dev-only: every CI tier runs the norsk-ctl it names, so a product's result
|
|
15
|
+
# belongs to a commit (ctl-pinned-in-repo.test.ts).
|
|
15
16
|
#
|
|
16
17
|
# Nightly cron + on demand. The bump runs the product's own gate (lint,
|
|
17
|
-
# typecheck, unit tests)
|
|
18
|
-
#
|
|
18
|
+
# typecheck, unit tests), then ALWAYS opens (or updates) a PR on
|
|
19
|
+
# upgrade-latest/nightly, as sync-ctl-packages does; it never pushes to main.
|
|
20
|
+
# bot-prs merges the PR once checks, smoke, integration and the guides gate are
|
|
21
|
+
# green on its head, so a bad Norsk nightly or norsk-ctl shows up as a red bot
|
|
22
|
+
# PR and main stays green.
|
|
19
23
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
24
|
+
# History, because each step was a fix. It first committed straight to main and
|
|
25
|
+
# let the push-triggered run be the verdict -- a RED `main` as the alarm, and
|
|
26
|
+
# nobody watches a product repo's own `main`: playout sat red ~12h, then funke,
|
|
27
|
+
# then probe, inside three days (fleet review 04c, class 8). Then it gated
|
|
28
|
+
# first and landed on green. That kept lint/typecheck/unit failures off main,
|
|
29
|
+
# but smoke, integration and the guides ran only after landing, so a bad
|
|
30
|
+
# upstream release still reddened main in every repo that took it
|
|
31
|
+
# (ci-workflows docs/dashboard/ci-fragility.md, problem 2 and proposal B).
|
|
32
|
+
# Pinned for the class by conventions/gate-before-main.test.ts. The branch is
|
|
33
|
+
# pushed with CI_DISPATCH_TOKEN (a PAT): a GITHUB_TOKEN push would fire none of
|
|
34
|
+
# the PR's workflows (Actions' recursion guard).
|
|
28
35
|
#
|
|
29
36
|
# Single-sourced in @norskvideo/ctl-dev-kit (conventions/upgrade-latest.yml) and
|
|
30
37
|
# copied verbatim into each product repo; the check:drift gate fails CI if a copy
|
|
@@ -116,6 +123,18 @@ jobs:
|
|
|
116
123
|
reg="https://registry.npmjs.org"
|
|
117
124
|
changed=0
|
|
118
125
|
|
|
126
|
+
# NORSK-CTL: the version flake.nix's ctlVersion names is the daemon every
|
|
127
|
+
# CI tier runs (ctl-pinned-in-repo.test.ts) as well as the one the dev
|
|
128
|
+
# shell launches, so it moves here like the engine does and the bump
|
|
129
|
+
# PR's tiers are what test it (ci-workflows docs/dashboard/
|
|
130
|
+
# ci-fragility.md, proposal A). Resolved up front, with everything
|
|
131
|
+
# else, so a refusal rewrites nothing.
|
|
132
|
+
ctl_base="https://s3.eu-west-1.amazonaws.com/norsk.video/norsk-ctl"
|
|
133
|
+
old_ctl="$( { grep -oE 'ctlVersion = "[^"]+"' flake.nix 2>/dev/null || true; } | head -1 | sed -E 's/.*"(.*)"/\1/')"
|
|
134
|
+
[ -n "$old_ctl" ] || { echo "::error::flake.nix pins no ctlVersion. CI runs the norsk-ctl it names, so add ctlVersion = \"<version>\"; to flake.nix."; exit 1; }
|
|
135
|
+
new_ctl="$(curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 "$ctl_base/latest")"
|
|
136
|
+
[ -n "$new_ctl" ] || { echo "::error::could not resolve norsk-ctl's latest channel"; exit 1; }
|
|
137
|
+
|
|
119
138
|
# Accumulate a COMPACT summary: only the components that actually moved
|
|
120
139
|
# get an entry, so a no-op nightly (old == new) posts nothing. Was: the
|
|
121
140
|
# full media + studio-lib + studio-image triple every night for every
|
|
@@ -264,18 +283,16 @@ jobs:
|
|
|
264
283
|
parts+=("studio-lib \`$new_studio_lib\`")
|
|
265
284
|
fi
|
|
266
285
|
|
|
267
|
-
# --- ctl
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
286
|
+
# --- norsk-ctl pin (flake.nix) ------------------------------------
|
|
287
|
+
# ctlVersion, plus the per-platform hash of each platform the flake
|
|
288
|
+
# fetches the daemon for. A Studio product's flake fetches all four; an
|
|
289
|
+
# sdk-app's fetches none and carries the version alone. Mirrors
|
|
271
290
|
# packages/dev-kit/build/refresh-ctl-pin.sh in the ctl monorepo.
|
|
272
|
-
|
|
273
|
-
old_ctl="$(grep -oE 'ctlVersion = "[^"]+"' flake.nix | sed -E 's/.*"(.*)"/\1/')"
|
|
274
|
-
new_ctl="$(curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 "$ctl_base/latest")"
|
|
275
|
-
if [ -n "$new_ctl" ] && [ "$old_ctl" != "$new_ctl" ]; then
|
|
291
|
+
if [ "$old_ctl" != "$new_ctl" ]; then
|
|
276
292
|
declare -A ctlplat=([x86_64-linux]=linux-x64 [aarch64-linux]=linux-arm64 [aarch64-darwin]=darwin-arm64 [x86_64-darwin]=darwin-x64)
|
|
277
293
|
for sys in "${!ctlplat[@]}"; do
|
|
278
294
|
p="${ctlplat[$sys]}"
|
|
295
|
+
grep -qF "plat = \"$p\";" flake.nix || continue
|
|
279
296
|
hex="$(curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 "$ctl_base/$new_ctl/norsk-ctl-$new_ctl-$p.sha256" | awk '{print $1}')"
|
|
280
297
|
[ -n "$hex" ] || { echo "no sha256 sidecar for $p"; exit 1; }
|
|
281
298
|
sri="$(nix hash convert --hash-algo sha256 --to sri "$hex")"
|
|
@@ -327,31 +344,51 @@ jobs:
|
|
|
327
344
|
bun run test:unit
|
|
328
345
|
'
|
|
329
346
|
|
|
330
|
-
|
|
331
|
-
|
|
347
|
+
# The bump's only way to main is this PR. bot-prs merges it once every tier
|
|
348
|
+
# that runs on a bot PR -- checks, smoke, integration and build-image's
|
|
349
|
+
# guides gate, wherever this repo has them -- is green on its head
|
|
350
|
+
# (ci-workflows docs/dashboard/ci-fragility.md, proposal B). Landing on a
|
|
351
|
+
# green gate kept lint, typecheck and unit failures off main, but the tiers
|
|
352
|
+
# that break on an upstream change ran only after landing, so a bad
|
|
353
|
+
# upstream release still turned main red in every repo that took it. The
|
|
354
|
+
# gate above still runs: its verdict heads the PR body, and a red one
|
|
355
|
+
# shows on the PR's own checks too. Never a draft: bot-prs reads red from
|
|
356
|
+
# the tiers, and a draft could never become ready again by PATCH.
|
|
357
|
+
- name: Open (or update) the bump PR
|
|
358
|
+
if: steps.bump.outputs.changed == '1'
|
|
332
359
|
env:
|
|
333
360
|
API_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
|
|
334
|
-
|
|
361
|
+
GATE: ${{ steps.gate.outcome }}
|
|
362
|
+
# Through env, never pasted into the script: the summary wraps each
|
|
363
|
+
# version in backticks, and inside a double-quoted shell string those
|
|
364
|
+
# ran as commands ("1.3.1-...: command not found"), dropping every
|
|
365
|
+
# version from the commit message and PR body.
|
|
335
366
|
SUMMARY: ${{ steps.bump.outputs.summary }}
|
|
336
367
|
run: |
|
|
337
368
|
set -euo pipefail
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
369
|
+
branch="upgrade-latest/nightly"
|
|
370
|
+
prefix="upgrade-latest/"
|
|
371
|
+
title="Bump Norsk nightly + ctl pins to latest channel"
|
|
372
|
+
repo="${{ github.repository }}"; owner="${repo%%/*}"
|
|
373
|
+
api="https://api.github.com/repos/$repo/pulls"
|
|
374
|
+
# An older PR of this bot's family is obsolete once this one is open:
|
|
375
|
+
# left open they piled up (nine were closed by hand on 2026-10-03).
|
|
376
|
+
# Best-effort: a failure here must not fail the bump.
|
|
342
377
|
close_superseded() {
|
|
343
|
-
|
|
378
|
+
local keep="$1" n
|
|
344
379
|
auth=(-H "Authorization: Bearer $API_TOKEN" -H "Accept: application/vnd.github+json")
|
|
345
|
-
for n in $(curl -sS "${auth[@]}" "$api
|
|
346
|
-
| jq -r --arg repo "$
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
380
|
+
for n in $(curl -sS "${auth[@]}" "$api?state=open&per_page=100" \
|
|
381
|
+
| jq -r --arg repo "$repo" --arg prefix "$prefix" --argjson keep "$keep" \
|
|
382
|
+
'.[] | select(.head.repo.full_name == $repo and (.head.ref | startswith($prefix)) and .number != $keep) | .number'); do
|
|
383
|
+
curl -sS -o /dev/null "${auth[@]}" -X POST "https://api.github.com/repos/$repo/issues/$n/comments" \
|
|
384
|
+
-d "$(jq -nc --arg b "Superseded by #$keep, a later run of this bot." '{body:$b}')"
|
|
385
|
+
curl -sS -o /dev/null "${auth[@]}" -X PATCH "$api/$n" -d '{"state":"closed"}'
|
|
350
386
|
echo "closed superseded PR #$n"
|
|
351
387
|
done
|
|
352
388
|
}
|
|
353
389
|
git config user.name "github-actions[bot]"
|
|
354
390
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
391
|
+
git checkout -B "$branch"
|
|
355
392
|
git add -A
|
|
356
393
|
git commit -m "chore: bump Norsk nightly + ctl pins to latest channel
|
|
357
394
|
|
|
@@ -359,81 +396,52 @@ jobs:
|
|
|
359
396
|
|
|
360
397
|
Coupled Norsk pins (npm overrides + manifest.seed.json image tags +
|
|
361
398
|
version-pin guard test) moved together via a global version-string
|
|
362
|
-
replace; lockfile regenerated.
|
|
363
|
-
platform hashes)
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
#
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
# a repo that is already complete, where --unshallow is refused.
|
|
379
|
-
git fetch --unshallow origin main 2>/dev/null || git fetch origin main
|
|
380
|
-
git rebase origin/main || { git rebase --abort; echo "rebase hit a real conflict; aborting"; exit 1; }
|
|
381
|
-
done
|
|
382
|
-
echo "push still failing after 5 rebase attempts"; exit 1
|
|
383
|
-
|
|
384
|
-
- name: Open a reconciliation PR (gate red)
|
|
385
|
-
if: steps.bump.outputs.changed == '1' && steps.gate.outcome == 'failure'
|
|
386
|
-
env:
|
|
387
|
-
API_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
|
|
388
|
-
# Through env, never pasted into the script: the summary wraps each
|
|
389
|
-
# version in backticks, and inside a double-quoted shell string those
|
|
390
|
-
# ran as commands ("1.3.1-...: command not found"), dropping every
|
|
391
|
-
# version from the commit message and PR body.
|
|
392
|
-
SUMMARY: ${{ steps.bump.outputs.summary }}
|
|
393
|
-
run: |
|
|
394
|
-
set -euo pipefail
|
|
395
|
-
branch="upgrade-latest/nightly"
|
|
396
|
-
git config user.name "github-actions[bot]"
|
|
397
|
-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
398
|
-
git checkout -b "$branch"
|
|
399
|
-
git add -A
|
|
400
|
-
git commit -m "chore: bump Norsk nightly + ctl pins to latest channel (needs reconciliation)
|
|
401
|
-
|
|
402
|
-
${SUMMARY}"
|
|
403
|
-
git push -f origin "$branch"
|
|
404
|
-
title="Bump Norsk nightly + ctl pins to latest channel"
|
|
399
|
+
replace; lockfile regenerated. flake.nix norsk-ctl pin (version + the
|
|
400
|
+
per-platform hashes), which CI runs, moved to the latest channel."
|
|
401
|
+
# One branch per bump, force-pushed from today's main -- but only when
|
|
402
|
+
# its tree changed. A red bump PR left standing is rebuilt by every run
|
|
403
|
+
# of this bot, and pushing the same tree again would re-run every slow
|
|
404
|
+
# tier on it for nothing.
|
|
405
|
+
if git fetch -q origin "$branch" 2>/dev/null && [ "$(git rev-parse 'HEAD^{tree}')" = "$(git rev-parse 'FETCH_HEAD^{tree}')" ]; then
|
|
406
|
+
echo "$branch already holds this bump; not re-pushing it"
|
|
407
|
+
else
|
|
408
|
+
git push -f origin "$branch"
|
|
409
|
+
fi
|
|
410
|
+
if [ "$GATE" = "success" ]; then
|
|
411
|
+
verdict="Gate green: lint, typecheck and unit tests pass on the bumped pins."
|
|
412
|
+
else
|
|
413
|
+
verdict="**Gate red**: lint, typecheck or unit tests fail on the bumped pins. That usually means the new Norsk nightly is bad, or genuinely changed something the product depends on. Fix it on this branch, or close this PR to skip the bump -- the next nightly run reopens it against whatever the channel holds then."
|
|
414
|
+
fi
|
|
405
415
|
body="$(printf '%s\n' \
|
|
406
|
-
"Automated nightly pin bump: the Norsk nightly channel moved and the coupled" \
|
|
407
|
-
"pins came with it -- but the product gate is **red**, so this bump needs a" \
|
|
408
|
-
"human before it reaches main." \
|
|
416
|
+
"Automated nightly pin bump: the Norsk nightly channel and norsk-ctl latest moved, and the coupled pins came with them." \
|
|
409
417
|
"" \
|
|
410
418
|
" ${SUMMARY}" \
|
|
411
419
|
"" \
|
|
412
|
-
"
|
|
413
|
-
"changed something the product depends on. Run it to see which:" \
|
|
414
|
-
"" \
|
|
415
|
-
" bun run lint && bun run typecheck && bun run test:unit" \
|
|
420
|
+
"$verdict" \
|
|
416
421
|
"" \
|
|
417
|
-
"
|
|
418
|
-
"
|
|
422
|
+
"bot-prs merges this once checks, smoke, integration and the guides gate" \
|
|
423
|
+
"(build-image) have all passed on its head, wherever this repo runs them." \
|
|
424
|
+
"Until then main does not move; a red tier keeps it open and reports it on" \
|
|
425
|
+
"the product dashboard.")"
|
|
419
426
|
# gh is not on the self-hosted runner PATH (it lives only inside
|
|
420
427
|
# `nix develop`, and this step runs outside it), so create/update the PR
|
|
421
428
|
# straight against the REST API with the same PAT that pushed the branch.
|
|
422
|
-
# The PAT (not GITHUB_TOKEN) is what lets the PR's own
|
|
423
|
-
# is the whole point: the bump is judged before it lands
|
|
424
|
-
repo="${{ github.repository }}"; owner="${repo%%/*}"
|
|
425
|
-
api="https://api.github.com/repos/$repo/pulls"
|
|
429
|
+
# The PAT (not GITHUB_TOKEN) is what lets the PR's own workflows run,
|
|
430
|
+
# which is the whole point: the bump is judged before it lands.
|
|
426
431
|
curl_api=(--fail-with-body -sS -H "Authorization: Bearer $API_TOKEN" -H "Accept: application/vnd.github+json")
|
|
427
432
|
existing="$(curl "${curl_api[@]}" "$api?head=$owner:$branch&state=open" | jq -r '.[0].number // empty')"
|
|
428
433
|
if [ -n "$existing" ]; then
|
|
429
434
|
curl "${curl_api[@]}" -X PATCH "$api/$existing" \
|
|
430
435
|
--data "$(jq -nc --arg t "$title" --arg b "$body" '{title:$t, body:$b}')" > /dev/null
|
|
431
|
-
|
|
436
|
+
number="$existing"
|
|
437
|
+
echo "updated PR #$number"
|
|
432
438
|
else
|
|
433
|
-
curl "${curl_api[@]}" -X POST "$api" \
|
|
439
|
+
number="$(curl "${curl_api[@]}" -X POST "$api" \
|
|
434
440
|
--data "$(jq -nc --arg t "$title" --arg b "$body" --arg h "$branch" \
|
|
435
|
-
'{title:$t, body:$b, head:$h, base:"main"}')" | jq -r
|
|
441
|
+
'{title:$t, body:$b, head:$h, base:"main"}')" | jq -r .number)"
|
|
442
|
+
echo "opened PR #$number"
|
|
436
443
|
fi
|
|
444
|
+
close_superseded "$number" || true
|
|
437
445
|
|
|
438
446
|
- name: Report "already fresh"
|
|
439
447
|
if: steps.bump.outputs.changed != '1'
|
package/docs-theme/README.md
CHANGED
|
@@ -85,8 +85,8 @@ a class attribute on every page of the site.
|
|
|
85
85
|
site: `hasDocsSite()` tests for `website/astro.config.mjs`, and
|
|
86
86
|
`docsThemeProblems()` routes this directory's `checkTheme` into the same report as
|
|
87
87
|
`biome.json` and the shared workflows. `conventions/sync-drift.ts` is the writer
|
|
88
|
-
half, so `sync-dev-kit.yml` rolls a theme change out to every product
|
|
89
|
-
|
|
88
|
+
half, so `sync-dev-kit.yml` rolls a theme change out to every product like any
|
|
89
|
+
other convention, through a bot PR that `bot-prs.yml` merges once its tiers pass. The site directory is `website/` across the fleet
|
|
90
90
|
(`DOCS_SITE_DIR`).
|
|
91
91
|
|
|
92
92
|
A site inside THIS monorepo still runs `sync.ts check` from its own test suite,
|