@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.
@@ -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
- import { parse, stringify } from "yaml";
49
+
50
+ // packages/shared/src/board-config.ts
50
51
  import { z as z2 } from "zod";
51
52
 
52
- // packages/aws/src/internal-board.ts
53
+ // packages/shared/src/duration.ts
53
54
  import { z } from "zod";
54
- var durationSchema = z.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value2) => Number.parseInt(value2, 10) > 0).brand();
55
- var positionSchema = z.union([
56
- z.object({
57
- x: z.number().int().min(0),
58
- y: z.number().int().min(0),
59
- w: z.number().int().min(1).max(12),
60
- h: z.number().int().min(1)
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
- z.object({ x: z.literal(0), y: z.literal(0), w: z.literal(0), h: z.literal(0) })
83
+ z2.object({ x: z2.literal(0), y: z2.literal(0), w: z2.literal(0), h: z2.literal(0) })
63
84
  ]);
64
- var panelSchema = z.looseObject({
65
- // Presentation-only; ids still address proxy calls and the generated allowlist.
66
- label: z.string().min(1).optional(),
67
- id: z.string().min(1),
68
- type: z.string().min(1),
69
- source: z.string().min(1).optional(),
70
- density: z.enum(["auto", "comfortable", "compact"]).optional(),
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
- link: z.url().optional(),
74
- url: z.url().optional(),
75
- json_path: z.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional()
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 = z.looseObject({
86
- type: z.string().min(1),
87
- token_env: z.string().min(1).optional()
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 = z.object({
144
+ var boardSchema = z2.object({
90
145
  refresh: durationSchema.optional(),
91
- panels: z.array(panelSchema).min(1).superRefine((panels, ctx) => {
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 boardConfigSchema = z.object({
106
- sources: z.record(z.string().min(1), sourceSchema).default({}),
107
- boards: z.record(z.string().min(1), boardSchema),
108
- auth: z.object({
109
- issuer: z.url(),
110
- client_id: z.string().min(1).optional(),
111
- allow: z.object({
112
- groups: z.array(z.string().min(1)).optional(),
113
- subjects: z.array(z.string().min(1)).optional()
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 result = boardConfigSchema.safeParse(parse(source));
129
- if (!result.success)
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
- ${z2.prettifyError(result.error)}`);
132
- const yaml = stringify(result.data, { sortMapEntries: true });
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 board = await validateBoardConfig(input.boardConfigPath);
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: clientAssetUrl(input.version, input.assetDomain),
418
+ clientAssetUrl: assetUrl,
151
419
  serverRuntimeVersion: CORE_RUNTIME_VERSION,
152
420
  supportedProviders: input.providers ?? ["aws-lambda"],
153
421
  artifactChecksums: { "board.yaml": board.sha256 },