@continuous-excellence/ze-great-dashboard-aws 0.16.0 → 0.17.0
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/board-config.schema.json +121 -0
- package/client/assets/index-CSO187vo.js.map +1 -1
- package/client/board-config.schema.json +121 -0
- package/dist/cli.js +309 -41
- package/dist/index.js +309 -41
- package/dist/lambda.mjs +52 -9
- package/package.json +2 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "board-config.schema.json",
|
|
4
|
+
"title": "Ze Great Dashboard board configuration",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["boards"],
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"sources": { "type": "object", "additionalProperties": { "$ref": "#/$defs/source" } },
|
|
10
|
+
"boards": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"minProperties": 1,
|
|
13
|
+
"additionalProperties": { "$ref": "#/$defs/board" }
|
|
14
|
+
},
|
|
15
|
+
"auth": { "$ref": "#/$defs/auth" }
|
|
16
|
+
},
|
|
17
|
+
"$defs": {
|
|
18
|
+
"duration": { "type": "string", "pattern": "^[1-9][0-9]*(?:ms|s|m|h)$" },
|
|
19
|
+
"position": {
|
|
20
|
+
"oneOf": [
|
|
21
|
+
{
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["x", "y", "w", "h"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"x": { "type": "integer", "minimum": 0 },
|
|
26
|
+
"y": { "type": "integer", "minimum": 0 },
|
|
27
|
+
"w": { "type": "integer", "minimum": 1, "maximum": 12 },
|
|
28
|
+
"h": { "type": "integer", "minimum": 1 }
|
|
29
|
+
},
|
|
30
|
+
"additionalProperties": true
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "object",
|
|
34
|
+
"required": ["x", "y", "w", "h"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"x": { "const": 0 },
|
|
37
|
+
"y": { "const": 0 },
|
|
38
|
+
"w": { "const": 0 },
|
|
39
|
+
"h": { "const": 0 }
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": true
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"panel": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"required": ["id", "type"],
|
|
48
|
+
"additionalProperties": true,
|
|
49
|
+
"properties": {
|
|
50
|
+
"id": { "type": "string", "minLength": 1 },
|
|
51
|
+
"type": { "type": "string", "minLength": 1 },
|
|
52
|
+
"label": { "type": "string", "minLength": 1 },
|
|
53
|
+
"source": { "type": "string", "minLength": 1 },
|
|
54
|
+
"pipeline": { "oneOf": [{ "type": "string" }, { "type": "number" }] },
|
|
55
|
+
"density": { "enum": ["auto", "comfortable", "compact"] },
|
|
56
|
+
"position": { "$ref": "#/$defs/position" },
|
|
57
|
+
"refresh": { "$ref": "#/$defs/duration" },
|
|
58
|
+
"running_refresh": { "$ref": "#/$defs/duration" },
|
|
59
|
+
"running_completion_refresh": { "$ref": "#/$defs/duration" },
|
|
60
|
+
"running_completion_window": { "$ref": "#/$defs/duration" },
|
|
61
|
+
"running_animation": {
|
|
62
|
+
"enum": [
|
|
63
|
+
"radial",
|
|
64
|
+
"runway",
|
|
65
|
+
"orbit",
|
|
66
|
+
"signal-field",
|
|
67
|
+
"telemetry-bloom",
|
|
68
|
+
"release-transit",
|
|
69
|
+
"status-weather",
|
|
70
|
+
"falling-shapes",
|
|
71
|
+
"off"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"demo_run_duration": { "$ref": "#/$defs/duration" },
|
|
75
|
+
"demo_review_duration": { "$ref": "#/$defs/duration" },
|
|
76
|
+
"link": { "type": "string", "format": "uri" },
|
|
77
|
+
"url": { "type": "string", "format": "uri" },
|
|
78
|
+
"json_path": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"pattern": "^\\$(?:\\.[A-Za-z_][A-Za-z0-9_]*|\\[\\d+\\])*$"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"board": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"required": ["panels"],
|
|
87
|
+
"additionalProperties": true,
|
|
88
|
+
"properties": {
|
|
89
|
+
"refresh": { "$ref": "#/$defs/duration" },
|
|
90
|
+
"running_refresh": { "$ref": "#/$defs/duration" },
|
|
91
|
+
"running_completion_refresh": { "$ref": "#/$defs/duration" },
|
|
92
|
+
"running_completion_window": { "$ref": "#/$defs/duration" },
|
|
93
|
+
"panels": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/panel" } }
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"source": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"required": ["type"],
|
|
99
|
+
"additionalProperties": true,
|
|
100
|
+
"properties": {
|
|
101
|
+
"type": { "type": "string", "minLength": 1 },
|
|
102
|
+
"token_env": { "type": "string", "minLength": 1 }
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"auth": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"required": ["issuer"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"issuer": { "type": "string", "format": "uri" },
|
|
110
|
+
"client_id": { "type": "string", "minLength": 1 },
|
|
111
|
+
"allow": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"properties": {
|
|
114
|
+
"groups": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
115
|
+
"subjects": { "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -46,33 +46,87 @@ function computeMode(config) {
|
|
|
46
46
|
import { createHash } from "node:crypto";
|
|
47
47
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
48
48
|
import { join, resolve } from "node:path";
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
// packages/shared/src/board-config.ts
|
|
50
51
|
import { z as z2 } from "zod";
|
|
51
52
|
|
|
52
|
-
// packages/
|
|
53
|
+
// packages/shared/src/duration.ts
|
|
53
54
|
import { z } from "zod";
|
|
54
|
-
var
|
|
55
|
-
var
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
var DURATION_PATTERN = /^(\d+)(ms|s|m|h)$/;
|
|
56
|
+
var UNIT_MILLIS = {
|
|
57
|
+
ms: 1,
|
|
58
|
+
s: 1e3,
|
|
59
|
+
m: 6e4,
|
|
60
|
+
h: 36e5
|
|
61
|
+
};
|
|
62
|
+
function parseDuration(value2) {
|
|
63
|
+
const match = DURATION_PATTERN.exec(value2);
|
|
64
|
+
if (!match) return null;
|
|
65
|
+
const [, rawAmount, rawUnit] = match;
|
|
66
|
+
if (rawAmount === void 0 || rawUnit === void 0) return null;
|
|
67
|
+
const amount = Number(rawAmount);
|
|
68
|
+
if (amount <= 0) return null;
|
|
69
|
+
return amount * UNIT_MILLIS[rawUnit];
|
|
70
|
+
}
|
|
71
|
+
var durationSchema = z.string().refine((value2) => parseDuration(value2) !== null, {
|
|
72
|
+
message: 'must be a positive duration like "30s", "5m", or "1h"'
|
|
73
|
+
}).brand();
|
|
74
|
+
|
|
75
|
+
// packages/shared/src/board-config.ts
|
|
76
|
+
var positionSchema = z2.union([
|
|
77
|
+
z2.object({
|
|
78
|
+
x: z2.number().int().min(0),
|
|
79
|
+
y: z2.number().int().min(0),
|
|
80
|
+
w: z2.number().int().min(1).max(12),
|
|
81
|
+
h: z2.number().int().min(1)
|
|
61
82
|
}),
|
|
62
|
-
|
|
83
|
+
z2.object({ x: z2.literal(0), y: z2.literal(0), w: z2.literal(0), h: z2.literal(0) })
|
|
63
84
|
]);
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
var panelDensities = ["auto", "comfortable", "compact"];
|
|
86
|
+
var panelDensitySchema = z2.enum(panelDensities);
|
|
87
|
+
var visibleRunningAnimations = [
|
|
88
|
+
"radial",
|
|
89
|
+
"runway",
|
|
90
|
+
"orbit",
|
|
91
|
+
"signal-field",
|
|
92
|
+
"telemetry-bloom",
|
|
93
|
+
"release-transit",
|
|
94
|
+
"status-weather",
|
|
95
|
+
"falling-shapes"
|
|
96
|
+
];
|
|
97
|
+
var runningAnimationSchema = z2.enum([...visibleRunningAnimations, "off"]);
|
|
98
|
+
var panelSchema = z2.looseObject({
|
|
99
|
+
/** Presentation-only wall label. `id` remains the stable proxy and allowlist address. */
|
|
100
|
+
label: z2.string().min(1).optional(),
|
|
101
|
+
id: z2.string().min(1),
|
|
102
|
+
type: z2.string().min(1),
|
|
103
|
+
source: z2.string().min(1).optional(),
|
|
104
|
+
pipeline: z2.union([z2.string(), z2.number()]).optional(),
|
|
105
|
+
/** Resolved public source branch, supplied by the board endpoint for client memory scoping. */
|
|
106
|
+
branch: z2.string().min(1).optional(),
|
|
107
|
+
/** Content-density bias; position still controls the panel's explicit grid placement. */
|
|
108
|
+
density: panelDensitySchema.optional(),
|
|
109
|
+
/** Advisory in v1 — a panel without a position renders in config order rather than not at all. */
|
|
71
110
|
position: positionSchema.optional(),
|
|
72
111
|
refresh: durationSchema.optional(),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
/** Active pipeline runs are checked more often than normal panels. */
|
|
113
|
+
running_refresh: durationSchema.optional(),
|
|
114
|
+
/** Polling cadence around the estimated completion boundary. */
|
|
115
|
+
running_completion_refresh: durationSchema.optional(),
|
|
116
|
+
/** Maximum length of the tighter completion polling burst. */
|
|
117
|
+
running_completion_window: durationSchema.optional(),
|
|
118
|
+
/** Optional active-run treatment. Omitted selects a visible treatment at random. */
|
|
119
|
+
running_animation: runningAnimationSchema.optional(),
|
|
120
|
+
/** Local animation-demo cycle duration; ignored by other panel types. */
|
|
121
|
+
demo_run_duration: durationSchema.optional(),
|
|
122
|
+
/** Local animation-demo focused-review duration; ignored by other panel types. */
|
|
123
|
+
demo_review_duration: durationSchema.optional(),
|
|
124
|
+
/** A deliberate override only. Adapters derive links; hand-written ones drift. */
|
|
125
|
+
link: z2.url().optional(),
|
|
126
|
+
/** Source-agnostic endpoint used by the http-value signal. */
|
|
127
|
+
url: z2.url().optional(),
|
|
128
|
+
/** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
|
|
129
|
+
json_path: z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path").optional()
|
|
76
130
|
}).superRefine((panel, ctx) => {
|
|
77
131
|
if ("display" in panel) {
|
|
78
132
|
ctx.addIssue({
|
|
@@ -82,13 +136,218 @@ var panelSchema = z.looseObject({
|
|
|
82
136
|
});
|
|
83
137
|
}
|
|
84
138
|
});
|
|
85
|
-
var sourceSchema =
|
|
86
|
-
type:
|
|
87
|
-
|
|
139
|
+
var sourceSchema = z2.looseObject({
|
|
140
|
+
type: z2.string().min(1),
|
|
141
|
+
/** Credentials are never in the config — only the name of the env var holding one. */
|
|
142
|
+
token_env: z2.string().min(1).optional()
|
|
88
143
|
});
|
|
89
|
-
var boardSchema =
|
|
144
|
+
var boardSchema = z2.object({
|
|
90
145
|
refresh: durationSchema.optional(),
|
|
91
|
-
|
|
146
|
+
running_refresh: durationSchema.optional(),
|
|
147
|
+
running_completion_refresh: durationSchema.optional(),
|
|
148
|
+
running_completion_window: durationSchema.optional(),
|
|
149
|
+
panels: z2.array(panelSchema).min(1).superRefine((panels, ctx) => {
|
|
150
|
+
const seen = /* @__PURE__ */ new Map();
|
|
151
|
+
panels.forEach((panel, index) => {
|
|
152
|
+
const firstIndex = seen.get(panel.id);
|
|
153
|
+
if (firstIndex === void 0) {
|
|
154
|
+
seen.set(panel.id, index);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
ctx.addIssue({
|
|
158
|
+
code: "custom",
|
|
159
|
+
path: [index, "id"],
|
|
160
|
+
message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
var authSchema = z2.object({
|
|
166
|
+
issuer: z2.url(),
|
|
167
|
+
/** Absent in gateway mode — the gateway does the flow, the proxy still validates. */
|
|
168
|
+
client_id: z2.string().min(1).optional(),
|
|
169
|
+
allow: z2.object({
|
|
170
|
+
groups: z2.array(z2.string().min(1)).optional(),
|
|
171
|
+
subjects: z2.array(z2.string().min(1)).optional()
|
|
172
|
+
}).optional()
|
|
173
|
+
});
|
|
174
|
+
var boardConfigSchema = z2.object({
|
|
175
|
+
sources: z2.record(z2.string().min(1), sourceSchema).default({}),
|
|
176
|
+
boards: z2.record(z2.string().min(1), boardSchema),
|
|
177
|
+
auth: authSchema.optional()
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// packages/shared/src/board-config-modeline.ts
|
|
181
|
+
var MODEL_LINE = /^# yaml-language-server:\s*\$schema=(\S+)\s*$/;
|
|
182
|
+
var SCHEMA_FILE = "board-config.schema.json";
|
|
183
|
+
function readBoardSchemaModeline(source) {
|
|
184
|
+
const line = source.split(/\r?\n/).find((candidate) => candidate.includes("yaml-language-server"));
|
|
185
|
+
if (!line) throw new Error(`Board configuration must include a ${SCHEMA_FILE} modeline`);
|
|
186
|
+
const match = MODEL_LINE.exec(line);
|
|
187
|
+
if (!match?.[1]) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Malformed board schema modeline; expected "# yaml-language-server: $schema=<URL>/${SCHEMA_FILE}"`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
let url;
|
|
193
|
+
try {
|
|
194
|
+
url = new URL(match[1]);
|
|
195
|
+
} catch {
|
|
196
|
+
throw new Error(`Malformed board schema modeline; schema URL is not a valid URL: ${match[1]}`);
|
|
197
|
+
}
|
|
198
|
+
if (!["http:", "https:"].includes(url.protocol) || !url.pathname.endsWith(`/${SCHEMA_FILE}`)) {
|
|
199
|
+
throw new Error(`Malformed board schema modeline; URL must end in /${SCHEMA_FILE}: ${match[1]}`);
|
|
200
|
+
}
|
|
201
|
+
return match[1];
|
|
202
|
+
}
|
|
203
|
+
function boardSchemaModeline(url) {
|
|
204
|
+
return `# yaml-language-server: $schema=${url}`;
|
|
205
|
+
}
|
|
206
|
+
var boardSchemaFileName = SCHEMA_FILE;
|
|
207
|
+
|
|
208
|
+
// packages/shared/src/client-env.ts
|
|
209
|
+
import { z as z3 } from "zod";
|
|
210
|
+
var clientEnvSchema = z3.object({
|
|
211
|
+
/** Where the immutable client assets live. The one variable that repoints the whole client. */
|
|
212
|
+
assetPath: z3.string().min(1),
|
|
213
|
+
/** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
|
|
214
|
+
proxyPath: z3.string().min(1),
|
|
215
|
+
/** Which board this is, resolved server-side so the client parses no URLs. */
|
|
216
|
+
board: z3.string().min(1),
|
|
217
|
+
/** The client version being served, so two published versions are visibly distinguishable. */
|
|
218
|
+
clientVersion: z3.string().min(1),
|
|
219
|
+
/** Omitted entirely when unauthenticated — presence is what turns auth on. */
|
|
220
|
+
auth: z3.object({
|
|
221
|
+
issuer: z3.string().min(1),
|
|
222
|
+
clientId: z3.string().min(1).optional()
|
|
223
|
+
}).optional()
|
|
224
|
+
});
|
|
225
|
+
var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true, clientVersion: true });
|
|
226
|
+
|
|
227
|
+
// packages/shared/src/envelope.ts
|
|
228
|
+
import { z as z4 } from "zod";
|
|
229
|
+
var errorKindSchema = z4.enum([
|
|
230
|
+
"unreachable",
|
|
231
|
+
"unauthorized",
|
|
232
|
+
"not-found",
|
|
233
|
+
"no-runs",
|
|
234
|
+
"upstream-error"
|
|
235
|
+
]);
|
|
236
|
+
var envelopeErrorSchema = z4.object({
|
|
237
|
+
kind: errorKindSchema,
|
|
238
|
+
message: z4.string()
|
|
239
|
+
});
|
|
240
|
+
var envelopeSchema = z4.discriminatedUnion("state", [
|
|
241
|
+
z4.object({
|
|
242
|
+
panelId: z4.string().min(1),
|
|
243
|
+
state: z4.literal("ok"),
|
|
244
|
+
/** From the upstream response `Date`. A cached value must never be shown without its age. */
|
|
245
|
+
observedAt: z4.iso.datetime(),
|
|
246
|
+
/** Adapter-derived. The authority for this panel; the dashboard itself asserts nothing. */
|
|
247
|
+
link: z4.url().nullable(),
|
|
248
|
+
signal: z4.unknown()
|
|
249
|
+
}),
|
|
250
|
+
z4.object({
|
|
251
|
+
panelId: z4.string().min(1),
|
|
252
|
+
state: z4.literal("error"),
|
|
253
|
+
/**
|
|
254
|
+
* Populated even on error: knowing *when* a failure was observed is the difference between
|
|
255
|
+
* "broken just now" and "broken all morning", and a panel that can't reach its source is
|
|
256
|
+
* exactly when a viewer most wants to click through.
|
|
257
|
+
*/
|
|
258
|
+
observedAt: z4.iso.datetime(),
|
|
259
|
+
link: z4.url().nullable(),
|
|
260
|
+
error: envelopeErrorSchema
|
|
261
|
+
})
|
|
262
|
+
]);
|
|
263
|
+
var pipelineActivitySchema = z4.object({
|
|
264
|
+
kind: z4.enum(["job", "stage", "step"]),
|
|
265
|
+
name: z4.string().min(1),
|
|
266
|
+
parent: z4.string().min(1).optional()
|
|
267
|
+
});
|
|
268
|
+
var pipelineStatusSchema = z4.object({
|
|
269
|
+
type: z4.literal("pipeline-status"),
|
|
270
|
+
status: z4.enum(["passed", "failed", "running", "cancelled", "unknown"]),
|
|
271
|
+
/** The source's unmodified status/result vocabulary, for an honest display. */
|
|
272
|
+
rawStatus: z4.string(),
|
|
273
|
+
name: z4.string().min(1),
|
|
274
|
+
/** The branch represented by this status, when the upstream supplies or filters by one. */
|
|
275
|
+
branch: z4.string().min(1).optional(),
|
|
276
|
+
/** Elapsed execution time, supplied only after the upstream run has completed. */
|
|
277
|
+
durationMs: z4.number().int().nonnegative().optional(),
|
|
278
|
+
/** When an active run began, so the client can advance its elapsed display between polls. */
|
|
279
|
+
runStartedAt: z4.iso.datetime().optional(),
|
|
280
|
+
/** Stable source-run identity, when the adapter provides one. */
|
|
281
|
+
sourceRunId: z4.string().min(1).optional(),
|
|
282
|
+
/** Best-effort live work detail; adapters may describe a job, stage, or step. */
|
|
283
|
+
activity: pipelineActivitySchema.optional(),
|
|
284
|
+
/** Advisory duration from recent completed runs remembered by this browser. */
|
|
285
|
+
estimatedDurationMs: z4.number().int().positive().optional(),
|
|
286
|
+
/** When this workflow run was last updated by its source, distinct from our observation time. */
|
|
287
|
+
sourceUpdatedAt: z4.iso.datetime().optional()
|
|
288
|
+
});
|
|
289
|
+
var pullRequestHealthItemSchema = z4.object({
|
|
290
|
+
label: z4.string().min(1),
|
|
291
|
+
status: pipelineStatusSchema.shape.status,
|
|
292
|
+
detail: z4.string().min(1),
|
|
293
|
+
link: z4.url().nullable()
|
|
294
|
+
});
|
|
295
|
+
var pullRequestHealthSchema = z4.object({
|
|
296
|
+
type: z4.literal("pull-request-health"),
|
|
297
|
+
status: pipelineStatusSchema.shape.status,
|
|
298
|
+
summary: z4.string().min(1),
|
|
299
|
+
workflows: z4.array(pullRequestHealthItemSchema),
|
|
300
|
+
pullRequests: z4.array(pullRequestHealthItemSchema)
|
|
301
|
+
});
|
|
302
|
+
var httpValueSchema = z4.object({
|
|
303
|
+
type: z4.literal("http-value"),
|
|
304
|
+
value: z4.union([z4.string(), z4.number(), z4.boolean()])
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// packages/aws/src/release.ts
|
|
308
|
+
import { parse, stringify } from "yaml";
|
|
309
|
+
import { z as z6 } from "zod";
|
|
310
|
+
|
|
311
|
+
// packages/aws/src/internal-board.ts
|
|
312
|
+
import { z as z5 } from "zod";
|
|
313
|
+
var durationSchema2 = z5.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value2) => Number.parseInt(value2, 10) > 0).brand();
|
|
314
|
+
var positionSchema2 = z5.union([
|
|
315
|
+
z5.object({
|
|
316
|
+
x: z5.number().int().min(0),
|
|
317
|
+
y: z5.number().int().min(0),
|
|
318
|
+
w: z5.number().int().min(1).max(12),
|
|
319
|
+
h: z5.number().int().min(1)
|
|
320
|
+
}),
|
|
321
|
+
z5.object({ x: z5.literal(0), y: z5.literal(0), w: z5.literal(0), h: z5.literal(0) })
|
|
322
|
+
]);
|
|
323
|
+
var panelSchema2 = z5.looseObject({
|
|
324
|
+
// Presentation-only; ids still address proxy calls and the generated allowlist.
|
|
325
|
+
label: z5.string().min(1).optional(),
|
|
326
|
+
id: z5.string().min(1),
|
|
327
|
+
type: z5.string().min(1),
|
|
328
|
+
source: z5.string().min(1).optional(),
|
|
329
|
+
density: z5.enum(["auto", "comfortable", "compact"]).optional(),
|
|
330
|
+
position: positionSchema2.optional(),
|
|
331
|
+
refresh: durationSchema2.optional(),
|
|
332
|
+
link: z5.url().optional(),
|
|
333
|
+
url: z5.url().optional(),
|
|
334
|
+
json_path: z5.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional()
|
|
335
|
+
}).superRefine((panel, ctx) => {
|
|
336
|
+
if ("display" in panel) {
|
|
337
|
+
ctx.addIssue({
|
|
338
|
+
code: "custom",
|
|
339
|
+
path: ["display"],
|
|
340
|
+
message: "display was removed; use density"
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
var sourceSchema2 = z5.looseObject({
|
|
345
|
+
type: z5.string().min(1),
|
|
346
|
+
token_env: z5.string().min(1).optional()
|
|
347
|
+
});
|
|
348
|
+
var boardSchema2 = z5.object({
|
|
349
|
+
refresh: durationSchema2.optional(),
|
|
350
|
+
panels: z5.array(panelSchema2).min(1).superRefine((panels, ctx) => {
|
|
92
351
|
const seen = /* @__PURE__ */ new Map();
|
|
93
352
|
panels.forEach((panel, index) => {
|
|
94
353
|
const firstIndex = seen.get(panel.id);
|
|
@@ -102,15 +361,15 @@ var boardSchema = z.object({
|
|
|
102
361
|
});
|
|
103
362
|
})
|
|
104
363
|
});
|
|
105
|
-
var
|
|
106
|
-
sources:
|
|
107
|
-
boards:
|
|
108
|
-
auth:
|
|
109
|
-
issuer:
|
|
110
|
-
client_id:
|
|
111
|
-
allow:
|
|
112
|
-
groups:
|
|
113
|
-
subjects:
|
|
364
|
+
var boardConfigSchema2 = z5.object({
|
|
365
|
+
sources: z5.record(z5.string().min(1), sourceSchema2).default({}),
|
|
366
|
+
boards: z5.record(z5.string().min(1), boardSchema2),
|
|
367
|
+
auth: z5.object({
|
|
368
|
+
issuer: z5.url(),
|
|
369
|
+
client_id: z5.string().min(1).optional(),
|
|
370
|
+
allow: z5.object({
|
|
371
|
+
groups: z5.array(z5.string().min(1)).optional(),
|
|
372
|
+
subjects: z5.array(z5.string().min(1)).optional()
|
|
114
373
|
}).optional()
|
|
115
374
|
}).optional()
|
|
116
375
|
});
|
|
@@ -123,13 +382,18 @@ function clientAssetUrl(version, domain = CANONICAL_ASSET_DOMAIN) {
|
|
|
123
382
|
throw new Error(`Invalid dashboard version: ${version}`);
|
|
124
383
|
return `${domain.replace(/\/+$/, "")}/dashboard/${version}`;
|
|
125
384
|
}
|
|
126
|
-
async function validateBoardConfig(path) {
|
|
385
|
+
async function validateBoardConfig(path, expectedSchemaUrl) {
|
|
127
386
|
const source = await readFile(resolve(path), "utf8");
|
|
128
|
-
const
|
|
129
|
-
|
|
387
|
+
const authoredSchemaUrl = readBoardSchemaModeline(source);
|
|
388
|
+
const result = boardConfigSchema2.safeParse(parse(source));
|
|
389
|
+
if (!result.success) {
|
|
390
|
+
const stale = authoredSchemaUrl !== expectedSchemaUrl ? `
|
|
391
|
+
Stale schema modeline: expected ${expectedSchemaUrl}` : "";
|
|
130
392
|
throw new Error(`Invalid board configuration:
|
|
131
|
-
${
|
|
132
|
-
|
|
393
|
+
${z6.prettifyError(result.error)}${stale}`);
|
|
394
|
+
}
|
|
395
|
+
const yaml = `${boardSchemaModeline(expectedSchemaUrl)}
|
|
396
|
+
${stringify(result.data, { sortMapEntries: true })}`;
|
|
133
397
|
return {
|
|
134
398
|
yaml,
|
|
135
399
|
sha256: sha256(yaml),
|
|
@@ -137,7 +401,11 @@ ${z2.prettifyError(result.error)}`);
|
|
|
137
401
|
};
|
|
138
402
|
}
|
|
139
403
|
async function assembleRelease(input) {
|
|
140
|
-
const
|
|
404
|
+
const assetUrl = clientAssetUrl(input.version, input.assetDomain);
|
|
405
|
+
const board = await validateBoardConfig(
|
|
406
|
+
input.boardConfigPath,
|
|
407
|
+
`${assetUrl}/${boardSchemaFileName}`
|
|
408
|
+
);
|
|
141
409
|
if (board.usesCredentials && !input.secretReference)
|
|
142
410
|
throw new Error(
|
|
143
411
|
"Board config uses token_env; SecretReference must name a Secrets Manager credential-map or Parameter Store SecureString ARN"
|
|
@@ -147,7 +415,7 @@ async function assembleRelease(input) {
|
|
|
147
415
|
await writeFile(join(outputDir, "board.yaml"), board.yaml);
|
|
148
416
|
const metadata = {
|
|
149
417
|
dashboardVersion: input.version,
|
|
150
|
-
clientAssetUrl:
|
|
418
|
+
clientAssetUrl: assetUrl,
|
|
151
419
|
serverRuntimeVersion: CORE_RUNTIME_VERSION,
|
|
152
420
|
supportedProviders: input.providers ?? ["aws-lambda"],
|
|
153
421
|
artifactChecksums: { "board.yaml": board.sha256 },
|