@norskvideo/ctl-dev-kit 0.1.17 → 0.1.19

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.nix CHANGED
@@ -20,7 +20,7 @@
20
20
  description = "norsk-ctl product build + dev shell";
21
21
 
22
22
  inputs = {
23
- nixpkgs.url = "github:NixOS/nixpkgs/d233902339c02a9c334e7e593de68855ad26c4cb"; # nixpkgs-unstable 2026-05-15, bun 1.3.13
23
+ nixpkgs.url = "github:NixOS/nixpkgs/d233902339c02a9c334e7e593de68855ad26c4cb"; # nixpkgs-unstable 2026-05-15, bun 1.3.13, chromium 148.0.7778.167
24
24
  # Biome must match the version pinned in package.json (@biomejs/biome 2.5.5)
25
25
  # exactly: 2.4.x can't parse this repo's biome.json (`rules.preset` is a
26
26
  # 2.5 key) and any other 2.5.x emits a version-mismatch info that breaks the
@@ -92,22 +92,49 @@
92
92
  pkgs.git
93
93
  pkgs.ffmpeg # integration suites spawn a local ffmpeg (e.g. SRT egress capture)
94
94
  ];
95
+
96
+ # Playwright doc-guide browser. nix-pinned headless Chromium needs a
97
+ # real X display on a truly headless box (page.fill silently no-ops on
98
+ # plain HTML inputs; some pages crash the renderer). Ubuntu's apt Xvfb
99
+ # is too old for Chromium 148's input dispatch — the nix xorg-server
100
+ # works. A product's scripts/with-display.sh wraps playwright in
101
+ # xvfb-run iff DISPLAY is unset.
102
+ linuxBrowser = pkgs.lib.optionals pkgs.stdenv.isLinux [
103
+ pkgs.chromium
104
+ pkgs.xvfb-run
105
+ pkgs.xorg.xorgserver
106
+ ];
107
+ # Headless Chromium finds no system fonts on NixOS, so text renders at
108
+ # zero advance width — invisible, and every toBeVisible() on text times
109
+ # out. Point fontconfig at a minimal bundled set so glyphs have real
110
+ # metrics.
111
+ fontsConf = pkgs.makeFontsConf {
112
+ fontDirectories = [ pkgs.dejavu_fonts pkgs.liberation_ttf ];
113
+ };
114
+ browserHook = pkgs.lib.optionalString pkgs.stdenv.isLinux ''
115
+ export BROWSER_FOR_TESTING=${pkgs.chromium}/bin/chromium
116
+ export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
117
+ export FONTCONFIG_FILE=${fontsConf}
118
+ '';
95
119
  in {
96
120
  # Everyday dev shell — build tools + the pinned norsk-ctl daemon.
97
121
  # `nix develop` and `nix develop .#dev` are equivalent.
98
122
  default = pkgs.mkShell {
99
123
  BIOME_BINARY = "${biome}/bin/biome";
100
- buildInputs = buildTools ++ [ (mkCtl system) ];
124
+ buildInputs = buildTools ++ linuxBrowser ++ [ (mkCtl system) ];
125
+ shellHook = browserHook;
101
126
  };
102
127
  dev = pkgs.mkShell {
103
128
  BIOME_BINARY = "${biome}/bin/biome";
104
- buildInputs = buildTools ++ [ (mkCtl system) ];
129
+ buildInputs = buildTools ++ linuxBrowser ++ [ (mkCtl system) ];
130
+ shellHook = browserHook;
105
131
  };
106
132
  # Lean shell for CI (build-image / integration) — no ctl fetch, so
107
133
  # image builds never depend on the ctl binary channel.
108
134
  build = pkgs.mkShell {
109
135
  BIOME_BINARY = "${biome}/bin/biome";
110
- buildInputs = buildTools;
136
+ buildInputs = buildTools ++ linuxBrowser;
137
+ shellHook = browserHook;
111
138
  };
112
139
  });
113
140
  };
@@ -25,8 +25,14 @@ export interface DriftReport {
25
25
  problems: string[];
26
26
  }
27
27
 
28
- const BEGIN = "<!-- BEGIN ctl-shared-conventions v1 -->";
29
- const END = "<!-- END ctl-shared-conventions v1 -->";
28
+ export const BEGIN = "<!-- BEGIN ctl-shared-conventions v1 -->";
29
+ export const END = "<!-- END ctl-shared-conventions v1 -->";
30
+
31
+ // The .gitignore shared block is delimited by these markers (see gitignore.core),
32
+ // so the sync writer can locate-and-replace it deterministically. The gate itself
33
+ // checks the whole marker-delimited block as a verbatim substring.
34
+ export const GITIGNORE_BEGIN = "# BEGIN ctl-shared-gitignore v1";
35
+ export const GITIGNORE_END = "# END ctl-shared-gitignore v1";
30
36
 
31
37
  const RESYNC = "Edit the dev-kit source and re-sync — never hand-edit the copy.";
32
38
 
@@ -45,8 +51,8 @@ function firstDiffLine(actual: string, expected: string): string {
45
51
 
46
52
  // The one legitimately per-repo line in the shared workflows: the dashboard key
47
53
  // the CI result is dispatched under. Everything else is verbatim-shared.
48
- const PRODUCT_LINE = /^(\s*product:\s*).*$/gm;
49
- const PRODUCT_SENTINEL = "__PRODUCT__";
54
+ export const PRODUCT_LINE = /^(\s*product:\s*).*$/gm;
55
+ export const PRODUCT_SENTINEL = "__PRODUCT__";
50
56
 
51
57
  function workflowProblem(relPath: string, canonicalRef: string, actual: string, canonical: string): string | undefined {
52
58
  // Compare structure with the product value masked so any real key passes, then
@@ -66,8 +72,8 @@ function workflowProblem(relPath: string, canonicalRef: string, actual: string,
66
72
  // this pin only feeds the dev flake; it floats per repo and over time and is NOT
67
73
  // a shared convention. Mask it so the gate compares flake STRUCTURE, not the pin
68
74
  // — same idea as masking upgrade-latest's product: line.
69
- const CTL_VERSION_LINE = /ctlVersion = "[^"]*";/;
70
- const CTL_HASH_LINE = /hash = "sha256-[^"]*";/g;
75
+ export const CTL_VERSION_LINE = /ctlVersion = "[^"]*";/;
76
+ export const CTL_HASH_LINE = /hash = "sha256-[^"]*";/g;
71
77
 
72
78
  function flakeProblem(actual: string, canonical: string): string | undefined {
73
79
  const mask = (s: string) =>
@@ -82,10 +88,10 @@ function flakeProblem(actual: string, canonical: string): string | undefined {
82
88
  // inside a sanctioned marker block at the head of `excludes`. Everything outside
83
89
  // the block is byte-compared; inside, only well-formed `"entry",` lines are
84
90
  // allowed so the block can't smuggle arbitrary config past the byte check.
85
- const DPRINT_BEGIN = "// BEGIN repo-specific excludes";
86
- const DPRINT_END = "// END repo-specific excludes";
91
+ export const DPRINT_BEGIN = "// BEGIN repo-specific excludes";
92
+ export const DPRINT_END = "// END repo-specific excludes";
87
93
 
88
- function splitAtMarkers(s: string): { prefix: string; body: string[]; suffix: string } | undefined {
94
+ export function splitAtMarkers(s: string): { prefix: string; body: string[]; suffix: string } | undefined {
89
95
  const lines = s.split("\n");
90
96
  const begin = lines.findIndex((l) => l.trim() === DPRINT_BEGIN);
91
97
  const end = lines.findIndex((l) => l.trim() === DPRINT_END);
@@ -7,22 +7,21 @@
7
7
  # need re-syncing. This workflow closes the first gap and surfaces the second.
8
8
  #
9
9
  # It bumps the @norskvideo/ctl-dev-kit pin to the newest published version,
10
- # reinstalls, and runs this product's own gate (check:drift + lint + typecheck +
11
- # unit tests) against the new bytes:
10
+ # reinstalls, re-syncs this product's forced copies from the new canonical
11
+ # (sync-drift), then runs the gate (check:drift + lint + typecheck + unit tests):
12
12
  #
13
- # - gate green -> a clean freshness bump; commit straight to main, like
14
- # upgrade-latest.yml. The common case: a dev-kit release that doesn't touch
15
- # this product's copies (or only verbatim ones already reconciled).
16
- # - gate red -> the new dev-kit needs a human: a drifted copy to re-sync, or
17
- # a convention change that breaks this product. Push a branch and open a PR;
18
- # check:drift's own output names each drifted file and the first diffing line.
19
- # Re-sync on the branch until green, then merge.
13
+ # - gate green -> the bump plus any mechanical re-sync is correct; commit it all
14
+ # straight to main, hands-free. This is the common case.
15
+ # - gate red -> the new dev-kit genuinely does not fit this product: a
16
+ # convention that fails lint/typecheck/tests, or a copy sync-drift could not
17
+ # mechanically converge. Push a branch and open a PR for a human -- real
18
+ # engineering to adopt the convention, not a copy-paste.
20
19
  #
21
- # The re-sync is deliberately NOT automated. check:drift already emits the exact
22
- # fix, the masked / marker-block copies (the flake ctl pin, the CLAUDE core, the
23
- # dprint and gitignore blocks) need judgement, and a bot that blindly rewrote
24
- # them could commit a subtly-wrong splice. Detection plus a ready branch is the
25
- # leverage; the fix stays human.
20
+ # sync-drift re-derives each copy from the canonical (never a second opinion that
21
+ # can disagree), preserving the per-repo bits the gate masks -- the product: keys,
22
+ # the flake ctl pin, the dprint/gitignore/CLAUDE repo content. The gate runs AFTER
23
+ # it, so a bad rewrite fails check:drift and a breaking convention fails the build,
24
+ # rather than landing silently. Structural per-repo files it never touches.
26
25
  #
27
26
  # Single-sourced in @norskvideo/ctl-dev-kit (conventions/sync-dev-kit.yml) and
28
27
  # copied verbatim into each product repo; the check:drift gate fails CI if a copy
@@ -81,7 +80,7 @@ jobs:
81
80
  echo "old=$old_bare" >> "$GITHUB_OUTPUT"
82
81
  echo "new=$new" >> "$GITHUB_OUTPUT"
83
82
 
84
- - name: Reinstall and run the product gate against the new dev-kit
83
+ - name: Reinstall, re-sync the copies from canonical, run the product gate
85
84
  id: gate
86
85
  if: steps.sync.outputs.changed == '1'
87
86
  continue-on-error: true
@@ -89,6 +88,7 @@ jobs:
89
88
  nix develop .#build --command bash -c '
90
89
  set -euo pipefail
91
90
  bun install
91
+ bun run node_modules/@norskvideo/ctl-dev-kit/conventions/sync-drift.ts
92
92
  bun run check:drift
93
93
  bun run lint
94
94
  bun run typecheck
@@ -104,9 +104,10 @@ jobs:
104
104
  git add -A
105
105
  git commit -m "chore: sync dev-kit ${{ steps.sync.outputs.old }} -> ${{ steps.sync.outputs.new }}
106
106
 
107
- @norskvideo/ctl-dev-kit pin bumped to the newest published version;
108
- check:drift + lint + typecheck + unit tests green against the new bytes.
109
- Lockfile regenerated."
107
+ @norskvideo/ctl-dev-kit pin bumped to the newest published version and the
108
+ forced copies re-synced from the new canonical (sync-drift); check:drift +
109
+ lint + typecheck + unit tests green against the new bytes. Lockfile
110
+ regenerated."
110
111
  # A GITHUB_TOKEN push does not retrigger checks (Actions' recursion
111
112
  # guard) -- fine, the gate above already ran them. Rebase-and-retry so a
112
113
  # commit landing mid-run does not red the sync; a real conflict aborts.
@@ -134,18 +135,17 @@ jobs:
134
135
  title="Sync dev-kit ${{ steps.sync.outputs.old }} -> ${{ steps.sync.outputs.new }}"
135
136
  body="$(printf '%s\n' \
136
137
  "Automated dev-kit sync: the @norskvideo/ctl-dev-kit pin moved to the newest" \
137
- "published version, but this product's gate is **red** against the new bytes," \
138
- "so the sync needs a human." \
138
+ "published version and sync-drift re-synced the forced copies from the new" \
139
+ "canonical -- but the gate is **red**, so this bump needs a human." \
139
140
  "" \
140
- "Run the gate locally to see what to fix:" \
141
+ "The mechanical re-sync is already on this branch. A red gate here means the" \
142
+ "new dev-kit genuinely does not fit the product yet -- run the gate to see which:" \
141
143
  "" \
142
144
  " bun install" \
143
- " bun run check:drift # names each drifted copy and the first diffing line" \
144
- " bun run lint && bun run typecheck && bun run test:unit" \
145
+ " bun run check:drift # a copy sync-drift could not mechanically converge" \
146
+ " bun run lint && bun run typecheck && bun run test:unit # a convention that breaks real code" \
145
147
  "" \
146
- "Re-sync the named copies from node_modules/@norskvideo/ctl-dev-kit (never" \
147
- "hand-edit against memory), or resolve the convention change this pin" \
148
- "introduced, pushing to this branch until green -- then merge.")"
148
+ "Resolve whichever is failing, pushing to this branch until green -- then merge.")"
149
149
  if existing="$(gh pr list --head "$branch" --state open --json number -q '.[0].number')" && [ -n "$existing" ]; then
150
150
  gh pr edit "$existing" --title "$title" --body "$body"
151
151
  else
@@ -0,0 +1,190 @@
1
+ // The writer half of the drift gate. check-drift.ts proves a product's forced
2
+ // copies match this dev-kit's canonical; sync-drift.ts *makes* them match, by
3
+ // re-deriving each copy from the same canonical bytes. It is the mechanical
4
+ // inverse of the checker and shares its constants (imported below) so the two
5
+ // cannot disagree about what "in sync" means.
6
+ //
7
+ // It only touches copies whose convergent form is mechanical — there is no
8
+ // judgement anywhere:
9
+ // - verbatim (biome.json, tsconfig.base.json) -> overwrite with canonical
10
+ // - masked workflows (checks / upgrade / sync) -> canonical, repo's product: key kept
11
+ // - flake.nix -> canonical, repo's floating ctl pin kept
12
+ // - marker-block (CLAUDE core, dprint, .gitignore) -> replace the shared block, keep the rest
13
+ // Structural per-repo files (root tsconfig.json, deployment/build-image.sh,
14
+ // manifest.seed.json) have NO canonical bytes to apply — the gate checks their
15
+ // shape, not their content — so they are left untouched.
16
+ //
17
+ // The safety net is that the gate still runs AFTER sync: a copy this writer
18
+ // cannot mechanically converge (e.g. a workflow with no product key to preserve)
19
+ // is left for check-drift to flag, and a convention that genuinely breaks the
20
+ // product surfaces as a red lint/typecheck/test — not as a silent bad copy.
21
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import {
24
+ BEGIN,
25
+ type CanonicalBytes,
26
+ CTL_HASH_LINE,
27
+ CTL_VERSION_LINE,
28
+ END,
29
+ GITIGNORE_BEGIN,
30
+ GITIGNORE_END,
31
+ PRODUCT_LINE,
32
+ PRODUCT_SENTINEL,
33
+ splitAtMarkers,
34
+ } from "./check-drift.ts";
35
+
36
+ export interface SyncReport {
37
+ written: string[];
38
+ skipped: string[];
39
+ }
40
+
41
+ function writeIfChanged(path: string, content: string, rel: string, written: string[]): void {
42
+ const current = existsSync(path) ? readFileSync(path, "utf8") : undefined;
43
+ if (current !== content) {
44
+ writeFileSync(path, content);
45
+ written.push(rel);
46
+ }
47
+ }
48
+
49
+ // checks.yml / upgrade-latest.yml / sync-dev-kit.yml: canonical byte-for-byte
50
+ // except the one `product:` dispatch key, which the gate masks. Re-emit the
51
+ // canonical with the repo's own key filled back in. A workflow with no key to
52
+ // preserve (absent, or still the sentinel) is left for the gate to flag.
53
+ function syncWorkflow(repoRoot: string, rel: string, canonical: string, r: SyncReport): void {
54
+ const path = join(repoRoot, rel);
55
+ if (!existsSync(path)) {
56
+ r.skipped.push(`${rel} (absent)`);
57
+ return;
58
+ }
59
+ const key = readFileSync(path, "utf8").match(/^\s*product:\s*(\S+)/m)?.[1];
60
+ if (!key || key === PRODUCT_SENTINEL) {
61
+ r.skipped.push(`${rel} (no product: key to preserve)`);
62
+ return;
63
+ }
64
+ writeIfChanged(path, canonical.replace(PRODUCT_LINE, `$1${key}`), rel, r.written);
65
+ }
66
+
67
+ // flake.nix is verbatim except the dev-only ctl pin (ctlVersion + the four
68
+ // per-platform hashes), which floats per repo. Re-emit canonical STRUCTURE with
69
+ // the repo's pin restored in order — so a structural edit (e.g. a stray comment)
70
+ // is replaced while the pin is kept. Structure is identical across repos, so the
71
+ // Nth hash line in canonical maps to the Nth in the repo.
72
+ function syncFlake(repoRoot: string, canonical: string, r: SyncReport): void {
73
+ const path = join(repoRoot, "flake.nix");
74
+ if (!existsSync(path)) {
75
+ r.skipped.push("flake.nix (absent)");
76
+ return;
77
+ }
78
+ const existing = readFileSync(path, "utf8");
79
+ const version = existing.match(CTL_VERSION_LINE)?.[0];
80
+ const hashes = existing.match(CTL_HASH_LINE) ?? [];
81
+ let out = version ? canonical.replace(CTL_VERSION_LINE, version) : canonical;
82
+ let i = 0;
83
+ out = out.replace(CTL_HASH_LINE, (m) => hashes[i++] ?? m);
84
+ writeIfChanged(path, out, "flake.nix", r.written);
85
+ }
86
+
87
+ // Replace the region between two markers with the canonical block, preserving
88
+ // everything outside it. `canonicalBlock` starts at the BEGIN marker and ends at
89
+ // the END marker.
90
+ function replaceMarkerBlock(existing: string, begin: string, end: string, canonicalBlock: string): string | undefined {
91
+ const b = existing.indexOf(begin);
92
+ const e = existing.indexOf(end);
93
+ if (b === -1 || e === -1 || e < b) return undefined;
94
+ const after = existing.slice(e + end.length);
95
+ return existing.slice(0, b) + canonicalBlock.trimEnd() + (after.startsWith("\n") ? after : `\n${after}`);
96
+ }
97
+
98
+ // CLAUDE.md: the fenced conventions core lives between the markers; the repo's
99
+ // own intro/outro prose is outside and kept.
100
+ function syncClaude(repoRoot: string, core: string, r: SyncReport): void {
101
+ const path = join(repoRoot, "CLAUDE.md");
102
+ if (!existsSync(path)) {
103
+ r.skipped.push("CLAUDE.md (absent)");
104
+ return;
105
+ }
106
+ const out = replaceMarkerBlock(readFileSync(path, "utf8"), BEGIN, END, core);
107
+ if (out === undefined) {
108
+ r.skipped.push("CLAUDE.md (no marker block)");
109
+ return;
110
+ }
111
+ writeIfChanged(path, out, "CLAUDE.md", r.written);
112
+ }
113
+
114
+ // .gitignore: the shared block is marker-delimited; repo-specific ignores live
115
+ // outside it. Replace the block, or prepend it if the repo has none yet (the
116
+ // one-time migration case).
117
+ function syncGitignore(repoRoot: string, canonicalCore: string, r: SyncReport): void {
118
+ const path = join(repoRoot, ".gitignore");
119
+ const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
120
+ const replaced = replaceMarkerBlock(existing, GITIGNORE_BEGIN, GITIGNORE_END, canonicalCore);
121
+ const out = replaced ?? `${canonicalCore.trimEnd()}\n${existing}`;
122
+ writeIfChanged(path, out, ".gitignore", r.written);
123
+ }
124
+
125
+ // dprint.json: canonical outside the repo-specific-excludes markers, the repo's
126
+ // own exclude entries kept between them.
127
+ function syncDprint(repoRoot: string, canonical: string, r: SyncReport): void {
128
+ const path = join(repoRoot, "dprint.json");
129
+ if (!existsSync(path)) {
130
+ r.skipped.push("dprint.json (absent)");
131
+ return;
132
+ }
133
+ const c = splitAtMarkers(canonical);
134
+ if (!c) throw new Error("canonical dprint.base.jsonc is missing its own marker block");
135
+ const a = splitAtMarkers(readFileSync(path, "utf8"));
136
+ if (!a) {
137
+ r.skipped.push("dprint.json (no marker block)");
138
+ return;
139
+ }
140
+ // splitAtMarkers keeps the file's trailing newline as an empty final line in
141
+ // suffix, so the join alone reproduces it — appending another would drift.
142
+ const out = [c.prefix, ...a.body, c.suffix].join("\n");
143
+ writeIfChanged(path, out, "dprint.json", r.written);
144
+ }
145
+
146
+ export function syncDrift(repoRoot: string, canonical: CanonicalBytes): SyncReport {
147
+ const r: SyncReport = { written: [], skipped: [] };
148
+
149
+ writeIfChanged(join(repoRoot, "biome.json"), canonical.biome, "biome.json", r.written);
150
+ writeIfChanged(join(repoRoot, "tsconfig.base.json"), canonical.tsconfigBase, "tsconfig.base.json", r.written);
151
+
152
+ syncWorkflow(repoRoot, ".github/workflows/checks.yml", canonical.checks, r);
153
+ syncWorkflow(repoRoot, ".github/workflows/upgrade-latest.yml", canonical.upgradeLatest, r);
154
+ syncWorkflow(repoRoot, ".github/workflows/sync-dev-kit.yml", canonical.syncDevKit, r);
155
+
156
+ syncFlake(repoRoot, canonical.flake, r);
157
+ syncClaude(repoRoot, canonical.core, r);
158
+ syncGitignore(repoRoot, canonical.gitignoreCore, r);
159
+ syncDprint(repoRoot, canonical.dprint, r);
160
+
161
+ return r;
162
+ }
163
+
164
+ if (import.meta.main) {
165
+ const repoRoot = process.argv[2] ?? process.cwd();
166
+ const dir = import.meta.dir;
167
+ const canonical: CanonicalBytes = {
168
+ core: readFileSync(join(dir, "CLAUDE.core.md"), "utf8"),
169
+ flake: readFileSync(join(dir, "..", "build", "flake.nix"), "utf8"),
170
+ upgradeLatest: readFileSync(join(dir, "upgrade-latest.yml"), "utf8"),
171
+ syncDevKit: readFileSync(join(dir, "sync-dev-kit.yml"), "utf8"),
172
+ checks: readFileSync(join(dir, "checks.yml"), "utf8"),
173
+ biome: readFileSync(join(dir, "biome.base.json"), "utf8"),
174
+ tsconfigBase: readFileSync(join(dir, "tsconfig.base.json"), "utf8"),
175
+ dprint: readFileSync(join(dir, "dprint.base.jsonc"), "utf8"),
176
+ buildImageBootstrap: readFileSync(join(dir, "..", "build", "build-image.bootstrap.sh"), "utf8"),
177
+ gitignoreCore: readFileSync(join(dir, "gitignore.core"), "utf8"),
178
+ };
179
+ const report = syncDrift(repoRoot, canonical);
180
+ if (report.written.length === 0) {
181
+ console.log("sync-drift: all shared-convention copies already match @norskvideo/ctl-dev-kit.");
182
+ } else {
183
+ console.log(`sync-drift: rewrote ${report.written.length} copy(ies) from @norskvideo/ctl-dev-kit:`);
184
+ for (const w of report.written) console.log(` - ${w}`);
185
+ }
186
+ if (report.skipped.length > 0) {
187
+ console.log("left for check-drift / a human:");
188
+ for (const s of report.skipped) console.log(` - ${s}`);
189
+ }
190
+ }
@@ -111,6 +111,7 @@ function rootPackageJson(ctx: ShapeContext): string {
111
111
  "test:unit": "bun test shared/src/ backend/src/ tests/unit/",
112
112
  "test:image": "bun test tests/image/",
113
113
  "check:drift": "bun run node_modules/@norskvideo/ctl-dev-kit/conventions/check-drift.ts",
114
+ "sync:drift": "bun run node_modules/@norskvideo/ctl-dev-kit/conventions/sync-drift.ts",
114
115
  typecheck: "bunx tsc --noEmit -p shared && bunx tsc --noEmit -p backend && bunx tsc --noEmit -p .",
115
116
  build: "bun run lint && bun run typecheck && bun run build:no-lint",
116
117
  "build:no-lint": "bun run build:shared && bun run build:backend",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",