@norskvideo/ctl-dev-kit 0.1.40 → 0.1.42

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.
@@ -102,10 +102,31 @@ jobs:
102
102
  [ -n "$new_studio_lib" ] && [ "$new_studio_lib" != "null" ] || { echo "could not resolve norsk-studio npm nightly"; exit 1; }
103
103
  studio_line="${new_studio_lib%%-*}" # e.g. 1.28.0 — keep the image on this line
104
104
  old_studio_img="$(jq -r '.latest.studio | split(":")[1]' manifest.seed.json)"
105
- new_studio_img="$(curl -fsSL "https://hub.docker.com/v2/repositories/norskvideo/norsk-studio/tags?page_size=100&ordering=last_updated" \
106
- | jq -r '.results[].name' \
105
+ # FOLLOW THE NIGHTLY CHANNEL — do not pick the newest tag NAME on the
106
+ # line. Newest-name selects on publish recency, so any build landing a
107
+ # <line>-<date>-<sha> tag wins, including one off a feature branch. Not
108
+ # hypothetical: a norsk-studio `reasoning` build took both the nightly
109
+ # tag and the newest-name slot on 2026-08-25, this job pinned it at
110
+ # 04:29, and every signed probe workflow was refused at launch that
111
+ # morning because the fix on main was not in it. norsk-studio now
112
+ # refuses to publish `nightly` from anything but main, so the channel is
113
+ # the trustworthy signal — and this follows it rather than guessing.
114
+ #
115
+ # Resolve nightly's digest, then name the version-specific tag carrying
116
+ # the same digest. The moving tag is not pinnable itself: it moves under
117
+ # you, and a launch has to record the exact image it ran.
118
+ hub="https://hub.docker.com/v2/repositories/norskvideo/norsk-studio"
119
+ nightly_digest="$(curl -fsSL "$hub/tags/nightly" | jq -r '.digest // empty')"
120
+ [ -n "$nightly_digest" ] || { echo "could not resolve the norsk-studio nightly tag's digest"; exit 1; }
121
+ new_studio_img="$(curl -fsSL "$hub/tags?page_size=100&ordering=last_updated" \
122
+ | jq -r --arg d "$nightly_digest" '.results[] | select(.digest == $d) | .name' \
107
123
  | grep -E "^${studio_line}-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9a-f]+$" | head -1 || true)"
108
- [ -n "$new_studio_img" ] || { echo "no published norsk-studio image on the $studio_line line — npm/docker version lines have diverged"; exit 1; }
124
+ [ -n "$new_studio_img" ] || {
125
+ echo "norsk-studio nightly ($nightly_digest) has no version-specific tag on the $studio_line line."
126
+ echo "Either the npm and docker version lines have diverged, or nightly has moved to a"
127
+ echo "different line than the npm nightly ($new_studio_lib). Not guessing — fix the channel."
128
+ exit 1
129
+ }
109
130
  echo "studio lib: ${old_studio_lib:-<no npm pins>} -> $new_studio_lib"
110
131
  echo "studio image: $old_studio_img -> $new_studio_img"
111
132
  if [ "$old_studio_img" != "$new_studio_img" ]; then
@@ -696,6 +696,7 @@ function productTemplateTs(ctx: ShapeContext): string {
696
696
  // @norskvideo/ctl-product-template-schema/pack — this repo carries no copy of
697
697
  // the wire format.
698
698
  import {
699
+ ProductTemplateAdvancedSchema,
699
700
  type ProductTemplateManifest,
700
701
  type ProductTemplateMaterials,
701
702
  type ProductTemplateParameter,
@@ -712,8 +713,26 @@ export type BuildOpts = {
712
713
  // buildStudioLibrary(). Required — the production composer is the builder.
713
714
  library?: ComponentLibrary;
714
715
  generatedAt?: Date;
716
+ // Create-time custom defaults from the build form's disclosure, merged
717
+ // per-knob over STANDARD_ADVANCED_OVERRIDES into manifest.advanced.
718
+ advanced?: ProductTemplateManifest["advanced"];
715
719
  };
716
720
 
721
+ // The build form rides its Custom defaults as a reserved $advancedDefaults
722
+ // sibling of the config keys; the config schema is strict, so split it off
723
+ // before the parse. Absent key = plain config passthrough.
724
+ export function splitAdvancedDefaults(
725
+ body: unknown,
726
+ ):
727
+ | { ok: true; config: unknown; advanced?: ProductTemplateManifest["advanced"] }
728
+ | { ok: false; issues: unknown[] } {
729
+ if (typeof body !== "object" || body === null || !("$advancedDefaults" in body)) return { ok: true, config: body };
730
+ const { $advancedDefaults: raw, ...config } = body as Record<string, unknown>;
731
+ const parsed = ProductTemplateAdvancedSchema.safeParse(raw);
732
+ if (!parsed.success) return { ok: false, issues: parsed.error.issues };
733
+ return { ok: true, config, advanced: parsed.data };
734
+ }
735
+
717
736
  const STUDIO_DEFAULT_HOST_PORT = 18000;
718
737
 
719
738
  export function buildProductTemplateMaterials(config: ProductConfig, opts: BuildOpts = {}): ProductTemplateMaterials {
@@ -734,7 +753,7 @@ export function buildProductTemplateMaterials(config: ProductConfig, opts: Build
734
753
  // proxy. Narrow as the product grows a surface of its own.
735
754
  expose: ["/", "/static/*", "/api/*", "/live/api/*", "/ws", "/ws/*"],
736
755
  },
737
- advanced: { ...STANDARD_ADVANCED_OVERRIDES },
756
+ advanced: { ...STANDARD_ADVANCED_OVERRIDES, ...opts.advanced },
738
757
  };
739
758
 
740
759
  return {
@@ -1066,7 +1085,7 @@ export const openapiRouter = createOpenapiRouter(buildOpenApiYaml);
1066
1085
  }
1067
1086
 
1068
1087
  function routesProductTemplateTs(ctx: ShapeContext): string {
1069
- return `import { buildProductTemplateMaterials } from "${ctx.scope}/shared/product-template";
1088
+ return `import { type BuildOpts, buildProductTemplateMaterials, splitAdvancedDefaults } from "${ctx.scope}/shared/product-template";
1070
1089
  import { ProductConfigSchema } from "${ctx.scope}/shared/schemas";
1071
1090
  import { packProductTemplate } from "@norskvideo/ctl-product-template-schema/pack";
1072
1091
  import type { ComponentLibrary } from "@norskvideo/norsk-studio-builder";
@@ -1083,14 +1102,19 @@ const STARTER_CONFIGS: Record<string, unknown> = {
1083
1102
  export function makeProductTemplateRouter(library: ComponentLibrary): Router {
1084
1103
  const router = Router();
1085
1104
 
1086
- const sendTemplate = (res: Parameters<Parameters<Router["get"]>[1]>[1], raw: unknown, filename: string) => {
1105
+ const sendTemplate = (
1106
+ res: Parameters<Parameters<Router["get"]>[1]>[1],
1107
+ raw: unknown,
1108
+ filename: string,
1109
+ advanced?: BuildOpts["advanced"],
1110
+ ) => {
1087
1111
  const parsed = ProductConfigSchema.safeParse(raw);
1088
1112
  if (!parsed.success) {
1089
1113
  res.status(400).json({ error: "invalid config", issues: parsed.error.issues });
1090
1114
  return;
1091
1115
  }
1092
1116
  try {
1093
- const materials = buildProductTemplateMaterials(parsed.data, { library });
1117
+ const materials = buildProductTemplateMaterials(parsed.data, { library, advanced });
1094
1118
  const tar = packProductTemplate(materials);
1095
1119
  res
1096
1120
  .status(200)
@@ -1105,9 +1129,15 @@ export function makeProductTemplateRouter(library: ComponentLibrary): Router {
1105
1129
  }
1106
1130
  };
1107
1131
 
1108
- // POST /api/product-template — build a tar from an arbitrary ProductConfig.
1132
+ // POST /api/product-template — build a tar from an arbitrary ProductConfig,
1133
+ // optionally carrying create-time custom defaults as $advancedDefaults.
1109
1134
  router.post("/product-template", (req, res) => {
1110
- sendTemplate(res, req.body, "${ctx.name}.tar");
1135
+ const split = splitAdvancedDefaults(req.body);
1136
+ if (!split.ok) {
1137
+ res.status(400).json({ error: "invalid $advancedDefaults", issues: split.issues });
1138
+ return;
1139
+ }
1140
+ sendTemplate(res, split.config, "${ctx.name}.tar", split.advanced);
1111
1141
  });
1112
1142
 
1113
1143
  // GET /api/product-template/<slug> — the runner GETs these at product-add
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",