@norskvideo/ctl-product-template-schema 0.1.54 → 0.1.56

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/index.d.ts CHANGED
@@ -4,6 +4,11 @@ export declare const ProductTemplateTargetSchema: z.ZodEnum<{
4
4
  }>;
5
5
  export type ProductTemplateTarget = z.infer<typeof ProductTemplateTargetSchema>;
6
6
  export declare const RESERVED_INSTANCE_ROUTE_SEGMENTS: readonly ["studio", "media", "visualiser", "uvis", "status", "metrics"];
7
+ /** How a template's runtimes appear on fleet dashboards (RFC 0006 §4.1). */
8
+ export declare const ProductTemplateDashboardSchema: z.ZodObject<{
9
+ kind: z.ZodString;
10
+ previews: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
+ }, z.core.$strip>;
7
12
  export declare const PRODUCT_TEMPLATE_ADVANCED_NETWORK_MODES: readonly ["docker", "hybrid"];
8
13
  export declare const ProductTemplateAdvancedSchema: z.ZodObject<{
9
14
  networkMode: z.ZodOptional<z.ZodObject<{
@@ -200,6 +205,14 @@ export declare const ProductTemplateManifestSchema: z.ZodObject<{
200
205
  websocket: z.ZodOptional<z.ZodBoolean>;
201
206
  auth: z.ZodOptional<z.ZodBoolean>;
202
207
  }, z.core.$strip>>>;
208
+ operations: z.ZodOptional<z.ZodArray<z.ZodObject<{
209
+ path: z.ZodString;
210
+ openapi: z.ZodString;
211
+ }, z.core.$strip>>>;
212
+ }, z.core.$strip>>;
213
+ dashboard: z.ZodOptional<z.ZodObject<{
214
+ kind: z.ZodString;
215
+ previews: z.ZodOptional<z.ZodArray<z.ZodString>>;
203
216
  }, z.core.$strip>>;
204
217
  allowWorkflowUploads: z.ZodOptional<z.ZodBoolean>;
205
218
  isStudio: z.ZodOptional<z.ZodBoolean>;
@@ -464,6 +477,14 @@ export declare const ProductTemplateMaterialsSchema: z.ZodObject<{
464
477
  websocket: z.ZodOptional<z.ZodBoolean>;
465
478
  auth: z.ZodOptional<z.ZodBoolean>;
466
479
  }, z.core.$strip>>>;
480
+ operations: z.ZodOptional<z.ZodArray<z.ZodObject<{
481
+ path: z.ZodString;
482
+ openapi: z.ZodString;
483
+ }, z.core.$strip>>>;
484
+ }, z.core.$strip>>;
485
+ dashboard: z.ZodOptional<z.ZodObject<{
486
+ kind: z.ZodString;
487
+ previews: z.ZodOptional<z.ZodArray<z.ZodString>>;
467
488
  }, z.core.$strip>>;
468
489
  allowWorkflowUploads: z.ZodOptional<z.ZodBoolean>;
469
490
  isStudio: z.ZodOptional<z.ZodBoolean>;
@@ -674,6 +695,10 @@ export declare const ProductTemplateMaterialsSchema: z.ZodObject<{
674
695
  path: z.ZodString;
675
696
  content: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>]>;
676
697
  }, z.core.$strip>>>;
698
+ openapi: z.ZodOptional<z.ZodArray<z.ZodObject<{
699
+ path: z.ZodString;
700
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>]>;
701
+ }, z.core.$strip>>>;
677
702
  workdirSeed: z.ZodDefault<z.ZodArray<z.ZodObject<{
678
703
  path: z.ZodString;
679
704
  content: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>]>;
package/index.js CHANGED
@@ -87,6 +87,36 @@ const ProductTemplateRouteSchema = z.object({
87
87
  description: "Whether the route sits behind the runner's oauth2 auth_request. Omit for the default `true`. Set false only for a surface that must be reachable unauthenticated, as the media route is.",
88
88
  }),
89
89
  });
90
+ // The operations a template serves, each described by an OpenAPI document it
91
+ // carries under openapi/ (RFC 0006 §4.1, norsk-ctl task 123). Keyed by the
92
+ // instance path the document describes, so it covers the runtime-screen
93
+ // catch-all as well as declared routes. An operation is reachable from a
94
+ // dashboard only when marked `x-norsk-dashboard: true`, and needs the
95
+ // platform rung its `x-norsk-role` names on the runtime.
96
+ const SAFE_SEGMENTS = (value) => !value.split("/").some((s) => s === ".." || s === ".");
97
+ const ProductTemplateOperationsSchema = z.object({
98
+ path: z
99
+ .string()
100
+ .regex(/^\/[A-Za-z0-9._~-]+(\/[A-Za-z0-9._~-]+)*$/, "an absolute path under the instance, no trailing slash")
101
+ .refine(SAFE_SEGMENTS, { message: "no . or .. segments" })
102
+ .meta({
103
+ description: "The path under /instance/<id>/ the document's paths are relative to, e.g. /live/api/probe-api.",
104
+ }),
105
+ openapi: z
106
+ .string()
107
+ .regex(/^openapi\/[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*\.(ya?ml|json)$/, "a YAML or JSON file under openapi/")
108
+ .refine(SAFE_SEGMENTS, { message: "no . or .. segments" })
109
+ .meta({ description: "The OpenAPI 3 document in the template tar, e.g. openapi/probe-api.yaml." }),
110
+ });
111
+ /** How a template's runtimes appear on fleet dashboards (RFC 0006 §4.1). */
112
+ export const ProductTemplateDashboardSchema = z.object({
113
+ kind: z.string().min(1).meta({
114
+ description: "The discovery kind a runtime of this template announces itself as, e.g. norsk-probe.monitor.",
115
+ }),
116
+ previews: z.array(z.string().min(1)).optional().meta({
117
+ description: "The roles of the MoQ previews it offers, e.g. main.",
118
+ }),
119
+ });
90
120
  const ProductTemplateProxySchema = z.object({
91
121
  expose: z.array(z.string()).default([]).meta({
92
122
  description: "Paths (or globs) the runner's nginx routes through to this instance. Anything not in the list returns 404 from the proxy. Glob-shaped strings (`/static/*`) are accepted as hints — current implementation treats the whole set as a signal to mount a catch-all on the instance root; per-path filtering can come later.",
@@ -94,6 +124,9 @@ const ProductTemplateProxySchema = z.object({
94
124
  routes: z.array(ProductTemplateRouteSchema).optional().meta({
95
125
  description: "Extra named services to route under the instance root, beside the runtime-screen catch-all. Additive and optional: a template that omits it routes exactly as before, so an older runner ignoring the field still launches the product.",
96
126
  }),
127
+ operations: z.array(ProductTemplateOperationsSchema).optional().meta({
128
+ description: "The operations this template's runtimes serve, each set described by an OpenAPI document in the tar; the ones marked x-norsk-dashboard are what fleet dashboards may call, each decided per user by the platform. Additive: a runner that predates it ignores it.",
129
+ }),
97
130
  });
98
131
  // product-template-schema cannot import @norsk-ctl/shared (dependency direction), so
99
132
  // this is a literal copy of the runner's network-mode enum — and the runner
@@ -207,7 +240,7 @@ export const ProductTemplateSideloadSchema = z
207
240
  export const ProductTemplateHostTlsSchema = z
208
241
  .object({
209
242
  services: z.array(z.string().min(1)).min(1).meta({
210
- description: "Compose services that get the host certificate. Each must be a service in the template's own compose.yml. The runner mounts the certificate and key read-only at /etc/norsk-host-tls (cert.pem, key.pem) and sets NORSK_HOST_TLS_DIR=/etc/norsk-host-tls and NORSK_HOST_TLS_TRUSTED=true|false (true when the certificate is a real, browser-trusted one; false when the host's is self-signed). A host with no certificate mounts nothing and sets neither variable; the launch still goes ahead.",
243
+ description: "Compose services that get the host certificate. Each must be a service in the template's own compose.yml. The runner mounts the certificate and key read-only at /etc/norsk-host-tls (cert.pem, key.pem) and sets NORSK_HOST_TLS_DIR=/etc/norsk-host-tls. A host with no certificate mounts nothing and sets no variable; the launch still goes ahead.",
211
244
  }),
212
245
  })
213
246
  .meta({ id: "ProductTemplateHostTls", outputId: "ProductTemplateHostTls" });
@@ -222,7 +255,7 @@ export const ProductTemplateHostTlsSchema = z
222
255
  export const ProductTemplateWorkdirSchema = z
223
256
  .object({
224
257
  services: z.array(z.string().min(1)).min(1).meta({
225
- description: "Compose services that get the instance working directory, read-write at /data -- the same directory, at the same path, that media and studio have -- with NORSK_WORKDIR=/data. Each must be a service in the template's own compose.yml. It survives relaunch, restart and runner upgrade; only `instance delete --purge` removes it.",
258
+ description: "Compose services that get the instance working directory, read-write at /data -- the same directory, at the same path, that media and studio have. Each must be a service in the template's own compose.yml. It survives relaunch, restart and runner upgrade; only `instance delete --purge` removes it.",
226
259
  }),
227
260
  })
228
261
  .meta({ id: "ProductTemplateWorkdir", outputId: "ProductTemplateWorkdir" });
@@ -368,6 +401,9 @@ export const ProductTemplateManifestSchema = z
368
401
  description: "Per-instance Debug menu toggles. Each of `studio`/`visualiser` defaults to shown; set false to hide a redundant link (e.g. Studio hides its own 'Open Studio').",
369
402
  }),
370
403
  proxy: ProductTemplateProxySchema.optional(),
404
+ dashboard: ProductTemplateDashboardSchema.optional().meta({
405
+ description: "How this template's runtimes appear on fleet dashboards: the kind they announce, and their previews. Absent = not on dashboards.",
406
+ }),
371
407
  allowWorkflowUploads: z.boolean().optional().meta({
372
408
  description: "Whether the operator may upload workflow documents for this product. They land in the working directory's studio-save-files/, where Studio lists them for the operator to open — so this is for Norsk Studio itself (the editor), not for appliances built on it: an appliance (probe, playout) generates its one workflow from its config screen and has nothing to select. Absent = false. Assets are governed separately by `supportsCustomAssets`.",
373
409
  }),
@@ -525,6 +561,9 @@ export const ProductTemplateMaterialsSchema = z
525
561
  .array(ProductTemplateFileSchema)
526
562
  .default([])
527
563
  .meta({ description: "Free-form product files — packed under assets/ in the tar" }),
564
+ openapi: z.array(ProductTemplateFileSchema).optional().meta({
565
+ description: "The OpenAPI documents proxy.operations names, packed under openapi/ in the tar. Optional so materials built before it still typecheck.",
566
+ }),
528
567
  workdirSeed: z.array(ProductTemplateFileSchema).default([]).meta({
529
568
  description: "Files copied recursively into the launch workdir at launch time. Packed under workdir-seed/ in the tar. Existing files in the launch workdir win on conflict (the seed only fills gaps), so re-launches and operator-edits-in-place are non-destructive. Lets a product template ship a 'starter pack' of demo workflows / dashboards / components that the runtime sees in /data on first launch.",
530
569
  }),
package/pack.js CHANGED
@@ -33,6 +33,9 @@ export function productTemplateTarEntries(materials) {
33
33
  for (const a of materials.assets) {
34
34
  entries.push({ name: `assets/${a.path}`, content: a.content });
35
35
  }
36
+ for (const o of materials.openapi ?? []) {
37
+ entries.push({ name: `openapi/${o.path}`, content: o.content });
38
+ }
36
39
  for (const s of materials.workdirSeed) {
37
40
  entries.push({ name: `workdir-seed/${s.path}`, content: s.content });
38
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-product-template-schema",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {