@norskvideo/ctl-dev-kit 0.2.8 → 0.2.10

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.
@@ -18,7 +18,7 @@
18
18
  // (conventions/* + build/*), resolved package-relative so they work both
19
19
  // workspace-symlinked and installed from the published tarball.
20
20
  import { execFileSync } from "node:child_process";
21
- import { existsSync, readFileSync, statSync } from "node:fs";
21
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
22
22
  import { join } from "node:path";
23
23
  import { parseManifestSeed } from "@norskvideo/ctl-sdk/manifest-seed";
24
24
  import { checkTheme } from "../docs-theme/sync.ts";
@@ -340,6 +340,49 @@ export function demoProblems(repoRoot: string, canonicalShim: string): string[]
340
340
  return problems;
341
341
  }
342
342
 
343
+ // Product logs (norsk-mgr docs/internal/design/product-logs.md): every compose
344
+ // service a product template declares must say how it logs, or the node's log
345
+ // agent ships nothing (or garbage) for it. The schema's
346
+ // productTemplateLogsProblems says what is wrong with one template; what this
347
+ // gate pins is that the repo asks it. A repo that packs a product template
348
+ // must have a test that calls it — the packer itself cannot refuse, since
349
+ // products also pack at runtime and a missed entry must not become a 500.
350
+ const PACKS_TEMPLATE = /\bpackProductTemplate\b/;
351
+ const CHECKS_LOGS = /\bproductTemplateLogsProblems\b/;
352
+ const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "lib", "coverage", ".wireit"]);
353
+
354
+ function sourceFiles(dir: string, out: string[] = []): string[] {
355
+ let names: string[];
356
+ try {
357
+ names = readdirSync(dir);
358
+ } catch {
359
+ return out;
360
+ }
361
+ for (const name of names) {
362
+ if (SKIP_DIRS.has(name)) continue;
363
+ const path = join(dir, name);
364
+ const st = statSync(path);
365
+ if (st.isDirectory()) sourceFiles(path, out);
366
+ else if (/\.(ts|tsx)$/.test(name)) out.push(path);
367
+ }
368
+ return out;
369
+ }
370
+
371
+ export function productLogsProblems(repoRoot: string): string[] {
372
+ const files = sourceFiles(repoRoot);
373
+ const read = (f: string) => readFileSync(f, "utf8");
374
+ if (!files.some((f) => PACKS_TEMPLATE.test(read(f)))) return [];
375
+ const tested = files.some((f) => /\.test\.tsx?$/.test(f) && CHECKS_LOGS.test(read(f)));
376
+ return tested
377
+ ? []
378
+ : [
379
+ "this repo packs a product template but no test calls productTemplateLogsProblems " +
380
+ "(@norskvideo/ctl-product-template-schema). Add one asserting it returns [] for every template " +
381
+ "the product builds, and give the manifest a `logs:` entry per compose service " +
382
+ "(norsk-mgr docs/internal/design/product-logs.md).",
383
+ ];
384
+ }
385
+
343
386
  // A file that must be a verbatim, byte-for-byte copy of its dev-kit canonical.
344
387
  function verbatimProblem(
345
388
  repoRoot: string,
@@ -737,6 +780,7 @@ export function checkDrift(repoRoot: string, canonical: CanonicalBytes, opts: Ch
737
780
  for (const p of docsThemeProblems(repoRoot, opts.themeCanonical)) push(p);
738
781
 
739
782
  for (const p of demoProblems(repoRoot, canonical.demoShim)) push(p);
783
+ for (const p of productLogsProblems(repoRoot)) push(p);
740
784
 
741
785
  return { ok: problems.length === 0, problems };
742
786
  }
@@ -283,6 +283,18 @@ jobs:
283
283
  NORSK_CTL_BINARY: ${{ runner.temp }}/norsk-ctl
284
284
  NORSK_LICENSE_FILE: ${{ runner.temp }}/norsk-license.json
285
285
  NORSK_LAUNCHED_MANIFEST: ${{ runner.temp }}/norsk-launched-instances
286
+ # Where the harness copies each instance's engine logs and — since
287
+ # ctl-test-harness 0.1.60 — its CONTAINER logs, before teardown removes
288
+ # the containers. The studio container's stdout is the only place
289
+ # "Refusing to run workflow" and the runner's own startup appear, and
290
+ # every gate failure that pointed at `docker logs <instance>-studio-1`
291
+ # was pointing at something already destroyed.
292
+ #
293
+ # Keyed on run id + attempt because these runners are SHARED between
294
+ # repos: a bare $RUNNER_TEMP/norsk-logs is one directory nothing clears,
295
+ # and the first artifact built that way carried ten of another product's
296
+ # instances alongside its own three. Same shape the test-temp base uses.
297
+ NORSK_CI_LOG_DIR: ${{ runner.temp }}/norsk-logs-${{ github.run_id }}-${{ github.run_attempt }}
286
298
  run: |
287
299
  set -euo pipefail
288
300
  suite() {
@@ -321,6 +333,7 @@ jobs:
321
333
  path: |
322
334
  ${{ runner.temp }}/integration.log
323
335
  test-results/
336
+ ${{ runner.temp }}/norsk-logs-${{ github.run_id }}-${{ github.run_attempt }}
324
337
  retention-days: 7
325
338
  if-no-files-found: warn
326
339
 
@@ -265,10 +265,18 @@ jobs:
265
265
  NORSK_LICENSE_FILE: ${{ runner.temp }}/norsk-license.json
266
266
  # Where the harness copies each instance's full engine logs
267
267
  # (/var/log/norsk -> debug.json, host-mounted under the store) before
268
- # teardown nukes them. Uploaded below. `docker logs` only sees the
268
+ # teardown nukes them, plus — since ctl-test-harness 0.1.60 — each
269
+ # instance's CONTAINER logs, which is the only copy of the studio
270
+ # container's stdout. Uploaded below. `docker logs` only sees the
269
271
  # notice+ stdout subset and a decode-error flood can drown its tail;
270
272
  # this is the complete structured log at every level.
271
- NORSK_CI_LOG_DIR: ${{ runner.temp }}/norsk-logs
273
+ #
274
+ # Keyed on run id + attempt because these runners are SHARED between
275
+ # repos: a bare $RUNNER_TEMP/norsk-logs is one directory nothing clears,
276
+ # so a job uploaded whatever earlier jobs had left in it (observed: one
277
+ # product's artifact carrying ten of another's instances). Same shape the
278
+ # test-temp base uses, for the same reason.
279
+ NORSK_CI_LOG_DIR: ${{ runner.temp }}/norsk-logs-${{ github.run_id }}-${{ github.run_attempt }}
272
280
  run: |
273
281
  # Outer pipefail so the step reports the journey's exit, not tee's --
274
282
  # otherwise a failed smoke run over the `| tee` below scores green.
@@ -315,7 +323,7 @@ jobs:
315
323
  uses: actions/upload-artifact@v4
316
324
  with:
317
325
  name: norsk-engine-logs-attempt-${{ github.run_attempt }}
318
- path: ${{ runner.temp }}/norsk-logs
326
+ path: ${{ runner.temp }}/norsk-logs-${{ github.run_id }}-${{ github.run_attempt }}
319
327
  retention-days: 7
320
328
  if-no-files-found: ignore
321
329
 
@@ -1104,6 +1104,7 @@ import {
1104
1104
  type ProductTemplateMaterials,
1105
1105
  type ProductTemplateParameter,
1106
1106
  STANDARD_ADVANCED_OVERRIDES,
1107
+ STANDARD_LOGS,
1107
1108
  } from "@norskvideo/ctl-product-template-schema";
1108
1109
  import type { ComponentLibrary } from "@norskvideo/norsk-studio-builder";
1109
1110
  import { stringify as yamlStringify } from "yaml";
@@ -1155,6 +1156,10 @@ ${runtimeScreen} proxy: {
1155
1156
  expose: [${proxyExpose}],
1156
1157
  },
1157
1158
  advanced: { ...STANDARD_ADVANCED_OVERRIDES, ...opts.advanced },
1159
+ // How each compose service logs, for the node's log agent. A service
1160
+ // added to renderCompose needs its own entry here — the drift gate
1161
+ // requires a test (product-template.test.ts) that says so.
1162
+ logs: STANDARD_LOGS,
1158
1163
  };
1159
1164
 
1160
1165
  return {
@@ -1256,6 +1261,7 @@ import {
1256
1261
  ParametersFileSchema,
1257
1262
  ProductTemplateManifestSchema,
1258
1263
  ProductTemplateMaterialsSchema,
1264
+ productTemplateLogsProblems,
1259
1265
  } from "@norskvideo/ctl-product-template-schema";
1260
1266
  import { packProductTemplate } from "@norskvideo/ctl-product-template-schema/pack";
1261
1267
  import type { ComponentLibrary } from "@norskvideo/norsk-studio-builder";
@@ -1277,6 +1283,11 @@ describe("product-template materials", () => {
1277
1283
  expect(ProductTemplateMaterialsSchema.safeParse(materials).success).toBe(true);
1278
1284
  });
1279
1285
 
1286
+ it("declare how every compose service logs", () => {
1287
+ const materials = buildProductTemplateMaterials(ProductConfigSchema.parse(example), { library });
1288
+ expect(productTemplateLogsProblems(materials)).toEqual([]);
1289
+ });
1290
+
1280
1291
  it("pack to a tar whose entries round-trip through the schema package", () => {
1281
1292
  const materials = buildProductTemplateMaterials(ProductConfigSchema.parse(example), { library });
1282
1293
  const tar = packProductTemplate(materials);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-dev-kit",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./create-product": "./create-product/create-product.ts",