@norskvideo/ctl-dev-kit 0.2.4 → 0.2.6

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.
@@ -19,10 +19,10 @@
19
19
  // The invariant: the resolved graph carries exactly ONE zod 4 version. Nested
20
20
  // zod 3.x (a dev-only tool npm/bun isolates) is tolerated and only noted. Any
21
21
  // second distinct 4.x — the split — fails, and the fix is a single-version pin
22
- // in the root package.json `overrides`, matching the zod your @norskvideo/ctl-*
23
- // packages resolve (4.4.3 today). Do NOT pin products DOWN to satisfy norsk-ctl:
24
- // its 4.4.3 exists for an MCP-SDK typecheck that products do not perform — pin
25
- // UP to the version the rest of the tree wants and let ctl's copy dedupe onto it.
22
+ // in the root package.json `overrides`, matching the exact zod the @norskvideo/ctl-*
23
+ // packages depend on (create-product emits it as PRODUCT_ZOD). That is 4.4.3:
24
+ // @modelcontextprotocol/sdk does not work with 4.5 whatever its range says, so
25
+ // do not pin products up past it.
26
26
 
27
27
  import { existsSync, readFileSync } from "node:fs";
28
28
  import { join } from "node:path";
@@ -89,8 +89,20 @@ export function checkZodSingleton(repoRoot: string): ZodSingletonResult {
89
89
  } else {
90
90
  const lock = parseBunLock(readFileSync(lockPath, "utf8"));
91
91
  const versions = new Map<string, string[]>(); // version -> the lock keys carrying it
92
+ const ctlZod = new Map<string, string[]>(); // exact zod a ctl-* package declares -> those packages
92
93
  for (const [key, entry] of Object.entries(lock.packages ?? {})) {
93
94
  const id = Array.isArray(entry) ? String(entry[0] ?? "") : "";
95
+ if (id.startsWith("@norskvideo/ctl-")) {
96
+ const meta = (Array.isArray(entry) ? entry[2] : undefined) as
97
+ | { dependencies?: Record<string, string> }
98
+ | undefined;
99
+ const declared = meta?.dependencies?.zod;
100
+ if (declared !== undefined && /^\d/.test(declared)) {
101
+ const pkgs = ctlZod.get(declared) ?? [];
102
+ pkgs.push(id.slice(0, id.lastIndexOf("@")));
103
+ ctlZod.set(declared, pkgs);
104
+ }
105
+ }
94
106
  if (!id.startsWith("zod@")) continue;
95
107
  const version = id.slice("zod@".length);
96
108
  const keys = versions.get(version) ?? [];
@@ -108,11 +120,22 @@ export function checkZodSingleton(repoRoot: string): ZodSingletonResult {
108
120
  errors.push(`bun.lock: unexpected zod ${v} (${keys.join(", ")}) — only zod 4 is permitted`);
109
121
  }
110
122
 
123
+ // The ctl-* packages pin zod exactly (4.4.3: @modelcontextprotocol/sdk does not
124
+ // work with 4.5 whatever its range says). A product's one zod must be that one.
125
+ const required = ctlZod.size === 1 ? [...ctlZod.keys()][0] : undefined;
126
+ if (ctlZod.size > 1) {
127
+ const detail = [...ctlZod].map(([v, pkgs]) => ` zod ${v}: ${pkgs.join(", ")}`).join("\n");
128
+ errors.push(
129
+ `bun.lock: the @norskvideo/ctl-* packages disagree on their zod —\n${detail}\n Align their versions.`,
130
+ );
131
+ }
132
+
111
133
  if (v4.length === 0) {
112
134
  errors.push("bun.lock: no zod 4 resolved — expected exactly one");
113
135
  } else if (v4.length > 1) {
114
136
  const detail = v4.map(([v, keys]) => ` zod@${v}: ${keys.join(", ")}`).join("\n");
115
- const suggested = v4.at(-1)?.[0];
137
+ const suggested =
138
+ required ?? v4.find(([, keys]) => keys.some((k) => k.startsWith("@norskvideo/ctl-")))?.[0] ?? v4.at(-1)?.[0];
116
139
  errors.push(
117
140
  `bun.lock: the zod graph is SPLIT across ${v4.length} versions —\n${detail}\n` +
118
141
  ` A schema crossing this boundary (e.g. ManifestSchema from @norskvideo/ctl-sdk\n` +
@@ -120,6 +143,12 @@ export function checkZodSingleton(repoRoot: string): ZodSingletonResult {
120
143
  ` package.json overrides — e.g. "overrides": { "zod": "${suggested}" } — matching\n` +
121
144
  ` the zod your @norskvideo/ctl-* packages resolve, then re-run bun install.`,
122
145
  );
146
+ } else if (required !== undefined && v4[0]?.[0] !== required) {
147
+ errors.push(
148
+ `bun.lock: zod resolves to ${v4[0]?.[0]}, but the @norskvideo/ctl-* packages pin ${required}.\n` +
149
+ ` Set "overrides": { "zod": "${required}" } in the root package.json and declare\n` +
150
+ ` zod ${required} in each workspace, then re-run bun install.`,
151
+ );
123
152
  }
124
153
  }
125
154
 
@@ -126,6 +126,14 @@ const DEFAULT_MEDIA_IMAGE = `norskvideo/norsk:${devKitPin(devKitPackageJson, "@n
126
126
  const DEFAULT_STUDIO_IMAGE = "norskvideo/norsk-studio:1.29.0-2026-09-10-dcf40e7c";
127
127
  const DEFAULT_STUDIO_LIB = devKitPin(devKitPackageJson, "@norskvideo/norsk-studio");
128
128
 
129
+ /** The one zod 4 a generated product resolves: pinned in its root `overrides`
130
+ * and declared by every workspace that uses zod. Derived from the dev-kit's own
131
+ * pin, the same exact version the published ctl-* packages depend on, so the
132
+ * product's tree dedupes onto it instead of splitting beside a newer zod and
133
+ * failing conventions/zod-singleton.ts on day one. It is held at 4.4.3 because
134
+ * @modelcontextprotocol/sdk does not work with 4.5 despite its declared range. */
135
+ export const PRODUCT_ZOD = devKitPin(devKitPackageJson, "zod");
136
+
129
137
  const bareTag = (ref: string) => ref.slice(ref.lastIndexOf(":") + 1);
130
138
 
131
139
  export function resolveFeatures(opts: CreateProductOptions): Set<ProductFeature> {
@@ -12,7 +12,7 @@
12
12
  // (ours, and it emits the schema key verbatim rather than PascalCasing it);
13
13
  // copy-yamls is funke's, the only one carrying the external-globals plugin
14
14
  // that keeps react out of the browser bundle.
15
- import type { GeneratedFile, ShapeContext } from "../create-product.ts";
15
+ import { type GeneratedFile, PRODUCT_ZOD, type ShapeContext } from "../create-product.ts";
16
16
  import { VIEWS_INFO_IMPORTS, VIEWS_INFO_RUNTIME } from "./views.ts";
17
17
 
18
18
  // The identifier the placeholder registers under, referenced from three
@@ -45,7 +45,7 @@ function packageJson(ctx: ShapeContext): string {
45
45
  dependencies: {
46
46
  "@norskvideo/ctl-oas-to-ts": "^0.1.0",
47
47
  react: "^19.0.0",
48
- zod: "^4.3.6",
48
+ zod: PRODUCT_ZOD,
49
49
  },
50
50
  devDependencies: {
51
51
  "@norskvideo/norsk-sdk": ctx.pins.mediaLib,
@@ -13,7 +13,13 @@
13
13
  // from the schema package, addComponent/stubbedLibrary with a THROWING
14
14
  // validate(), INVARIANTS + parity + byte-snapshot + studio-load wired into
15
15
  // test:unit, and an image smoke tier.
16
- import { type GeneratedFile, type ShapeContext, type ShapeModule, studioPackage } from "./create-product.ts";
16
+ import {
17
+ type GeneratedFile,
18
+ PRODUCT_ZOD,
19
+ type ShapeContext,
20
+ type ShapeModule,
21
+ studioPackage,
22
+ } from "./create-product.ts";
17
23
  import { DOCS_SITE_GITIGNORE, DOCS_SITE_SCRIPTS, docsSiteFiles } from "./docs-site.ts";
18
24
  import {
19
25
  COMPONENTS_GITIGNORE,
@@ -361,6 +367,7 @@ function rootPackageJson(ctx: ShapeContext): string {
361
367
  ...optionalStudioPins(ctx),
362
368
  "@norskvideo/norsk-studio-builder": studioLib,
363
369
  "@norskvideo/norsk-studio-built-ins": studioLib,
370
+ zod: PRODUCT_ZOD,
364
371
  },
365
372
  },
366
373
  null,
@@ -430,7 +437,7 @@ function sharedPackageJson(ctx: ShapeContext): string {
430
437
  "@norskvideo/norsk-studio-builder": studioLib,
431
438
  "@norskvideo/norsk-studio-built-ins": studioLib,
432
439
  yaml: "^2.8.0",
433
- zod: "^4.3.6",
440
+ zod: PRODUCT_ZOD,
434
441
  },
435
442
  devDependencies: {
436
443
  "@norskvideo/ctl-foundation": "^0.1.0",
@@ -1319,7 +1326,7 @@ function backendPackageJson(ctx: ShapeContext): string {
1319
1326
  "@norskvideo/norsk-studio-builder": ctx.pins.studioLib,
1320
1327
  cors: "^2.8.5",
1321
1328
  express: "^5.0.1",
1322
- zod: "^4.3.6",
1329
+ zod: PRODUCT_ZOD,
1323
1330
  },
1324
1331
  devDependencies: {
1325
1332
  "@types/bun": "latest",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./create-product": "./create-product/create-product.ts",
@@ -33,6 +33,26 @@
33
33
  "peerDependencies": {
34
34
  "@playwright/test": "*"
35
35
  },
36
+ "devDependencies": {
37
+ "@norskvideo/ctl-oas-to-ts": "^0.1.0",
38
+ "@norskvideo/norsk-sdk": "1.3.0-2026-09-17-3b73a790",
39
+ "@norskvideo/norsk-studio": "1.29.0-2026-09-10-b21d9013",
40
+ "@norskvideo/norsk-studio-builder": "1.29.0-2026-09-10-b21d9013",
41
+ "@norskvideo/norsk-studio-built-ins": "1.29.0-2026-09-10-b21d9013",
42
+ "@playwright/test": "^1.59.0",
43
+ "@types/bun": "latest",
44
+ "@types/cors": "^2.8.17",
45
+ "@types/express": "5",
46
+ "@types/node": "^22.10.0",
47
+ "@types/react": "^19.0.10",
48
+ "@types/react-dom": "^19.0.3",
49
+ "cors": "^2.8.5",
50
+ "express": "5",
51
+ "react": "^19.0.0",
52
+ "react-dom": "^19.0.0",
53
+ "yaml": "^2.8.0",
54
+ "zod": "4.4.3"
55
+ },
36
56
  "peerDependenciesMeta": {
37
57
  "@playwright/test": {
38
58
  "optional": true