@norskvideo/ctl-product-template-schema 0.1.29 → 0.1.31

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/logs.d.ts CHANGED
@@ -71,10 +71,15 @@ export declare function productTemplateLogsProblems(materials: {
71
71
  composeYaml: string;
72
72
  }): string[];
73
73
  /** Norsk Studio (the `studio` service every Studio-based product runs): its
74
- * winston logger writes one JSON object per line to debug.log under the
75
- * config's server.logs — /usr/src/app/logs in the image — while its stdout is
76
- * a coloured, multi-line copy for humans, so it is not shipped. (error.log
77
- * beside it repeats debug.log's errors, so it is not declared.) */
74
+ * winston loggers write one JSON object per line to debug.log under the
75
+ * config's server.logs — /usr/src/app/logs in the image — in a subdirectory
76
+ * per process: logs/server/debug.log (the editor/API) and
77
+ * logs/runner/debug.log (the workflow runner). Nothing writes the declared
78
+ * logs/debug.log itself; the declaration is there to mount logs/, and the
79
+ * node log agent tails one level below each mounted directory (norsk-mgr
80
+ * log-agent template 1.2.0). Its stdout is a coloured, multi-line copy for
81
+ * humans, so it is not shipped. (error.log beside each debug.log repeats its
82
+ * errors, so it is not declared.) */
78
83
  export declare const STUDIO_SERVICE_LOGS: ServiceLogs;
79
84
  /** The Norsk engine (the `media` service): it runs its own fluent-bit, which
80
85
  * already collects its logs and stdout/stderr (tags media-erlang,
package/logs.js CHANGED
@@ -92,10 +92,15 @@ export function productTemplateLogsProblems(materials) {
92
92
  ];
93
93
  }
94
94
  /** Norsk Studio (the `studio` service every Studio-based product runs): its
95
- * winston logger writes one JSON object per line to debug.log under the
96
- * config's server.logs — /usr/src/app/logs in the image — while its stdout is
97
- * a coloured, multi-line copy for humans, so it is not shipped. (error.log
98
- * beside it repeats debug.log's errors, so it is not declared.) */
95
+ * winston loggers write one JSON object per line to debug.log under the
96
+ * config's server.logs — /usr/src/app/logs in the image — in a subdirectory
97
+ * per process: logs/server/debug.log (the editor/API) and
98
+ * logs/runner/debug.log (the workflow runner). Nothing writes the declared
99
+ * logs/debug.log itself; the declaration is there to mount logs/, and the
100
+ * node log agent tails one level below each mounted directory (norsk-mgr
101
+ * log-agent template 1.2.0). Its stdout is a coloured, multi-line copy for
102
+ * humans, so it is not shipped. (error.log beside each debug.log repeats its
103
+ * errors, so it is not declared.) */
99
104
  export const STUDIO_SERVICE_LOGS = {
100
105
  stdout: "none",
101
106
  files: [{ path: "/usr/src/app/logs/debug.log", format: "json" }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-product-template-schema",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -30,6 +30,10 @@
30
30
  "./migration-socket": {
31
31
  "types": "./migration-socket.d.ts",
32
32
  "default": "./migration-socket.js"
33
+ },
34
+ "./product-channels": {
35
+ "types": "./product-channels.d.ts",
36
+ "default": "./product-channels.js"
33
37
  }
34
38
  },
35
39
  "main": "./index.js",
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ export declare const PRODUCT_CHANNELS_SCHEMA_VERSION = 1;
3
+ /** Public read URL of a product's channels object. */
4
+ export declare function productChannelsUrl(product: string): string;
5
+ /** Write target of a product's channels object. */
6
+ export declare function productChannelsS3Uri(product: string): string;
7
+ export declare const ProductChannelSchema: z.ZodEnum<{
8
+ nightly: "nightly";
9
+ rc: "rc";
10
+ latest: "latest";
11
+ }>;
12
+ export type ProductChannel = z.infer<typeof ProductChannelSchema>;
13
+ export declare const ProductChannelRowSchema: z.ZodObject<{
14
+ image: z.ZodString;
15
+ sha: z.ZodString;
16
+ publishedAt: z.ZodString;
17
+ runtimeImages: z.ZodDefault<z.ZodArray<z.ZodString>>;
18
+ gate: z.ZodObject<{
19
+ checks: z.ZodLiteral<"passed">;
20
+ guides: z.ZodUnion<readonly [z.ZodLiteral<"passed">, z.ZodObject<{
21
+ skipped: z.ZodString;
22
+ }, z.core.$loose>]>;
23
+ run: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$loose>;
25
+ }, z.core.$loose>;
26
+ export type ProductChannelRow = z.infer<typeof ProductChannelRowSchema>;
27
+ export declare const ProductChannelsSchema: z.ZodObject<{
28
+ schemaVersion: z.ZodLiteral<1>;
29
+ product: z.ZodString;
30
+ imageRef: z.ZodString;
31
+ updated: z.ZodString;
32
+ channels: z.ZodRecord<z.ZodEnum<{
33
+ nightly: "nightly";
34
+ rc: "rc";
35
+ latest: "latest";
36
+ }> & z.core.$partial, z.ZodObject<{
37
+ image: z.ZodString;
38
+ sha: z.ZodString;
39
+ publishedAt: z.ZodString;
40
+ runtimeImages: z.ZodDefault<z.ZodArray<z.ZodString>>;
41
+ gate: z.ZodObject<{
42
+ checks: z.ZodLiteral<"passed">;
43
+ guides: z.ZodUnion<readonly [z.ZodLiteral<"passed">, z.ZodObject<{
44
+ skipped: z.ZodString;
45
+ }, z.core.$loose>]>;
46
+ run: z.ZodOptional<z.ZodString>;
47
+ }, z.core.$loose>;
48
+ }, z.core.$loose>>;
49
+ }, z.core.$loose>;
50
+ export type ProductChannels = z.infer<typeof ProductChannelsSchema>;
51
+ export declare function parseProductChannels(raw: unknown): {
52
+ ok: true;
53
+ value: ProductChannels;
54
+ } | {
55
+ ok: false;
56
+ error: string;
57
+ };
58
+ /** `current` with `channel` set to `row`; every other channel is kept. */
59
+ export declare function withChannelRow(current: ProductChannels | null, opts: {
60
+ product: string;
61
+ imageRef: string;
62
+ channel: ProductChannel;
63
+ row: ProductChannelRow;
64
+ updated: string;
65
+ }): ProductChannels;
@@ -0,0 +1,75 @@
1
+ // Which product image each release channel offers, written by the product's
2
+ // gated publish and read by ctl when it registers the product.
3
+ //
4
+ // One object per product (`products/<name>/channels.json` on the norsk.video
5
+ // bucket), not rows in the shared channel-manifest.json: each product repo's
6
+ // role can then write only its own prefix, and six writers never race a
7
+ // read-modify-write on one object. Only a publish that passed the gate writes a
8
+ // row, so a row means "this image passed checks and the guides" -- the thing a
9
+ // hand-pinned deployment/products.json could never say, and went six weeks
10
+ // stale instead (task 57).
11
+ //
12
+ // Readers ignore fields they do not know: an older ctl reads objects written by
13
+ // newer dev-kits.
14
+ import { z } from "zod";
15
+ export const PRODUCT_CHANNELS_SCHEMA_VERSION = 1;
16
+ const BUCKET = "norsk.video";
17
+ const BUCKET_URL = `https://s3.eu-west-1.amazonaws.com/${BUCKET}`;
18
+ /** A product's licence name, e.g. `norsk-probe`. It names an S3 prefix, so it
19
+ * is held to a shape that cannot leave that prefix. */
20
+ const PRODUCT_NAME = /^[a-z0-9][a-z0-9-]*$/;
21
+ function productChannelsKey(product) {
22
+ if (!PRODUCT_NAME.test(product))
23
+ throw new Error(`not a product name: ${JSON.stringify(product)}`);
24
+ return `products/${product}/channels.json`;
25
+ }
26
+ /** Public read URL of a product's channels object. */
27
+ export function productChannelsUrl(product) {
28
+ return `${BUCKET_URL}/${productChannelsKey(product)}`;
29
+ }
30
+ /** Write target of a product's channels object. */
31
+ export function productChannelsS3Uri(product) {
32
+ return `s3://${BUCKET}/${productChannelsKey(product)}`;
33
+ }
34
+ export const ProductChannelSchema = z.enum(["nightly", "rc", "latest"]);
35
+ export const ProductChannelRowSchema = z.looseObject({
36
+ /** The immutable dated tag, `<repo>:<YYYY-MM-DD>-<sha8>` -- never a channel tag. */
37
+ image: z.string().min(1),
38
+ /** The product commit the image was built from. */
39
+ sha: z.string().min(7),
40
+ publishedAt: z.string().min(1),
41
+ /** The Norsk and Studio images the product's templates launch (its
42
+ * `norsk-ctl.runtime-images` label). */
43
+ runtimeImages: z.array(z.string()).default([]),
44
+ gate: z.looseObject({
45
+ checks: z.literal("passed"),
46
+ /** `passed`, or skipped by the product's explicit opt-out, with its reason. */
47
+ guides: z.union([z.literal("passed"), z.looseObject({ skipped: z.string().min(1) })]),
48
+ /** The run that gated and published this image. */
49
+ run: z.string().optional(),
50
+ }),
51
+ });
52
+ export const ProductChannelsSchema = z.looseObject({
53
+ schemaVersion: z.literal(PRODUCT_CHANNELS_SCHEMA_VERSION),
54
+ product: z.string().regex(PRODUCT_NAME),
55
+ imageRef: z.string().min(1),
56
+ updated: z.string().min(1),
57
+ channels: z.partialRecord(ProductChannelSchema, ProductChannelRowSchema),
58
+ });
59
+ export function parseProductChannels(raw) {
60
+ const r = ProductChannelsSchema.safeParse(raw);
61
+ return r.success ? { ok: true, value: r.data } : { ok: false, error: z.prettifyError(r.error) };
62
+ }
63
+ /** `current` with `channel` set to `row`; every other channel is kept. */
64
+ export function withChannelRow(current, opts) {
65
+ if (current && (current.product !== opts.product || current.imageRef !== opts.imageRef)) {
66
+ throw new Error(`channels object belongs to ${current.product} (${current.imageRef}), not ${opts.product} (${opts.imageRef})`);
67
+ }
68
+ return {
69
+ schemaVersion: PRODUCT_CHANNELS_SCHEMA_VERSION,
70
+ product: opts.product,
71
+ imageRef: opts.imageRef,
72
+ updated: opts.updated,
73
+ channels: { ...(current?.channels ?? {}), [opts.channel]: opts.row },
74
+ };
75
+ }