@norskvideo/ctl-product-template-schema 0.1.57 → 0.1.59
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 +8 -0
- package/index.js +67 -0
- package/package.json +6 -1
package/index.d.ts
CHANGED
|
@@ -209,6 +209,10 @@ export declare const ProductTemplateManifestSchema: z.ZodObject<{
|
|
|
209
209
|
path: z.ZodString;
|
|
210
210
|
openapi: z.ZodString;
|
|
211
211
|
}, z.core.$strip>>>;
|
|
212
|
+
openPaths: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
213
|
+
path: z.ZodString;
|
|
214
|
+
prefix: z.ZodOptional<z.ZodBoolean>;
|
|
215
|
+
}, z.core.$strip>>>;
|
|
212
216
|
}, z.core.$strip>>;
|
|
213
217
|
dashboard: z.ZodOptional<z.ZodObject<{
|
|
214
218
|
kind: z.ZodString;
|
|
@@ -481,6 +485,10 @@ export declare const ProductTemplateMaterialsSchema: z.ZodObject<{
|
|
|
481
485
|
path: z.ZodString;
|
|
482
486
|
openapi: z.ZodString;
|
|
483
487
|
}, z.core.$strip>>>;
|
|
488
|
+
openPaths: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
489
|
+
path: z.ZodString;
|
|
490
|
+
prefix: z.ZodOptional<z.ZodBoolean>;
|
|
491
|
+
}, z.core.$strip>>>;
|
|
484
492
|
}, z.core.$strip>>;
|
|
485
493
|
dashboard: z.ZodOptional<z.ZodObject<{
|
|
486
494
|
kind: z.ZodString;
|
package/index.js
CHANGED
|
@@ -117,6 +117,64 @@ export const ProductTemplateDashboardSchema = z.object({
|
|
|
117
117
|
description: "The roles of the MoQ previews it offers, e.g. main.",
|
|
118
118
|
}),
|
|
119
119
|
});
|
|
120
|
+
// One Studio path a guest reaches without the operator sign-in (RFC 0010): a
|
|
121
|
+
// join page, its assets, the scoped routes it calls. The path carries its own
|
|
122
|
+
// authority (a signed join token), so the sign-in protects nothing there and
|
|
123
|
+
// only stops the guest.
|
|
124
|
+
//
|
|
125
|
+
// Studio's editor API (uploads included) and MCP are never open, and a prefix
|
|
126
|
+
// may not reach into /live/, which holds every component's API and every live
|
|
127
|
+
// socket. Exact paths under /live/ are how a product opens one route and not
|
|
128
|
+
// its neighbours (`join` but not `join-link`).
|
|
129
|
+
//
|
|
130
|
+
// A `{name}` segment stands for any one segment, for guests named at runtime
|
|
131
|
+
// (`commentators/{commentator}/join`). It never stands for the first segment,
|
|
132
|
+
// which decides /api, /mcp and /live, nor for the component under /live/: the
|
|
133
|
+
// component is what vouches for its own routes, so "this route on every
|
|
134
|
+
// component" would open routes no component offered. A prefix is literal.
|
|
135
|
+
const OPEN_PATH = /^\/[A-Za-z0-9._~-]+(\/([A-Za-z0-9._~-]+|\{[A-Za-z][A-Za-z0-9_]*\}))*\/?$/;
|
|
136
|
+
const PLACEHOLDER = /^\{.*\}$/;
|
|
137
|
+
const NEVER_OPEN = ["/api", "/mcp"];
|
|
138
|
+
const under = (path, root) => path === root || path.startsWith(`${root}/`);
|
|
139
|
+
/** The component a /live path addresses: `/live/<c>/ws`, `/live/api/<c>/...`. */
|
|
140
|
+
const liveComponentSegment = (path) => {
|
|
141
|
+
const [, first, second, third] = path.split("/");
|
|
142
|
+
if (first !== "live")
|
|
143
|
+
return undefined;
|
|
144
|
+
return second === "api" ? third : second;
|
|
145
|
+
};
|
|
146
|
+
const ProductTemplateOpenPathSchema = z
|
|
147
|
+
.object({
|
|
148
|
+
path: z
|
|
149
|
+
.string()
|
|
150
|
+
.regex(OPEN_PATH, "an absolute path under the Studio route: letters, digits and . _ ~ - between slashes, or a whole-segment {name} after the first")
|
|
151
|
+
.refine(SAFE_SEGMENTS, { message: "no . or .. segments" })
|
|
152
|
+
.refine((p) => !NEVER_OPEN.some((root) => under(p, root)), {
|
|
153
|
+
message: `Studio's ${NEVER_OPEN.join(" and ")} are never open`,
|
|
154
|
+
})
|
|
155
|
+
.meta({
|
|
156
|
+
description: "Path relative to the instance's Studio route: /dashboard/x is served at /instance/<id>/studio/dashboard/x. Never under /api or /mcp. A whole segment written {name} matches any one segment, except the first and the component under /live/.",
|
|
157
|
+
}),
|
|
158
|
+
prefix: z.boolean().optional().meta({
|
|
159
|
+
description: "Open everything below `path`, which must then end in `/`. For static assets; a prefix may not reach into /live/. Omit for an exact path.",
|
|
160
|
+
}),
|
|
161
|
+
})
|
|
162
|
+
.refine((o) => !o.prefix || o.path.endsWith("/"), {
|
|
163
|
+
message: "a prefix ends in /",
|
|
164
|
+
path: ["path"],
|
|
165
|
+
})
|
|
166
|
+
.refine((o) => !o.prefix || !under(o.path.replace(/\/$/, ""), "/live"), {
|
|
167
|
+
message: "a prefix may not reach into /live/: open each route there by its exact path",
|
|
168
|
+
path: ["path"],
|
|
169
|
+
})
|
|
170
|
+
.refine((o) => !o.prefix || !o.path.includes("{"), {
|
|
171
|
+
message: "a prefix is literal: placeholders are for exact paths",
|
|
172
|
+
path: ["path"],
|
|
173
|
+
})
|
|
174
|
+
.refine((o) => !PLACEHOLDER.test(liveComponentSegment(o.path) ?? ""), {
|
|
175
|
+
message: "a placeholder may not stand for the component under /live/: name the component",
|
|
176
|
+
path: ["path"],
|
|
177
|
+
});
|
|
120
178
|
const ProductTemplateProxySchema = z.object({
|
|
121
179
|
expose: z.array(z.string()).default([]).meta({
|
|
122
180
|
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.",
|
|
@@ -127,6 +185,15 @@ const ProductTemplateProxySchema = z.object({
|
|
|
127
185
|
operations: z.array(ProductTemplateOperationsSchema).optional().meta({
|
|
128
186
|
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
187
|
}),
|
|
188
|
+
openPaths: z
|
|
189
|
+
.array(ProductTemplateOpenPathSchema)
|
|
190
|
+
.refine((paths) => new Set(paths.map((p) => `${p.prefix ? "prefix" : "exact"} ${p.path}`)).size === paths.length, {
|
|
191
|
+
message: "each open path once: nginx refuses a duplicate location, and that fails the reload for every instance",
|
|
192
|
+
})
|
|
193
|
+
.optional()
|
|
194
|
+
.meta({
|
|
195
|
+
description: "Studio paths served without the operator sign-in (RFC 0010): a guest page, its assets and the routes it calls, each of which carries its own authority. Additive: a runner that predates it leaves them signed in.",
|
|
196
|
+
}),
|
|
130
197
|
});
|
|
131
198
|
// product-template-schema cannot import @norsk-ctl/shared (dependency direction), so
|
|
132
199
|
// this is a literal copy of the runner's network-mode enum — and the runner
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-product-template-schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/id3as/norsk-ctl.git",
|
|
8
|
+
"directory": "packages/product-template-schema"
|
|
9
|
+
},
|
|
5
10
|
"exports": {
|
|
6
11
|
".": {
|
|
7
12
|
"types": "./index.d.ts",
|