@continuous-excellence/ze-great-dashboard-aws 0.24.2 → 0.26.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/dist/cli.js CHANGED
@@ -83,6 +83,26 @@ var positionSchema = z2.union([
83
83
  ]);
84
84
  var panelDensities = ["auto", "comfortable", "compact"];
85
85
  var panelDensitySchema = z2.enum(panelDensities);
86
+ var httpValueJsonPathSchema = z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path");
87
+ var httpValueFactSchema = z2.object({
88
+ /** Stable address below the panel; labels and ordering may change without repointing it. */
89
+ id: z2.string().min(1),
90
+ label: z2.string().min(1),
91
+ url: z2.url(),
92
+ json_path: httpValueJsonPathSchema.optional()
93
+ });
94
+ var httpValuePanelIdentitySchema = z2.object({
95
+ id: z2.string().min(1),
96
+ type: z2.literal("http-value")
97
+ });
98
+ var httpValueScalarPanelSchema = httpValuePanelIdentitySchema.extend({
99
+ url: z2.url(),
100
+ json_path: httpValueJsonPathSchema.optional(),
101
+ link: z2.url().optional()
102
+ });
103
+ var httpValueGroupedPanelSchema = httpValuePanelIdentitySchema.extend({
104
+ facts: z2.array(httpValueFactSchema).min(1).max(4)
105
+ });
86
106
  var visibleRunningAnimations = [
87
107
  "radial",
88
108
  "runway",
@@ -125,7 +145,9 @@ var panelSchema = z2.looseObject({
125
145
  /** Source-agnostic endpoint used by the http-value signal. */
126
146
  url: z2.url().optional(),
127
147
  /** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
128
- json_path: z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path").optional()
148
+ json_path: httpValueJsonPathSchema.optional(),
149
+ /** Up to four independently fetched source-agnostic readings in one visual panel. */
150
+ facts: httpValueGroupedPanelSchema.shape.facts.optional()
129
151
  }).superRefine((panel, ctx) => {
130
152
  if ("display" in panel) {
131
153
  ctx.addIssue({
@@ -134,6 +156,32 @@ var panelSchema = z2.looseObject({
134
156
  message: "display was removed; use density"
135
157
  });
136
158
  }
159
+ if (panel.type !== "http-value") return;
160
+ if (panel.facts) {
161
+ if (panel.url !== void 0)
162
+ ctx.addIssue({
163
+ code: "custom",
164
+ path: ["url"],
165
+ message: "use facts URLs for grouped panels"
166
+ });
167
+ if (panel.json_path !== void 0)
168
+ ctx.addIssue({
169
+ code: "custom",
170
+ path: ["json_path"],
171
+ message: "use facts json_path values for grouped panels"
172
+ });
173
+ const seen = /* @__PURE__ */ new Map();
174
+ panel.facts.forEach((fact, index) => {
175
+ const first = seen.get(fact.id);
176
+ if (first === void 0) seen.set(fact.id, index);
177
+ else
178
+ ctx.addIssue({
179
+ code: "custom",
180
+ path: ["facts", index, "id"],
181
+ message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
182
+ });
183
+ });
184
+ }
137
185
  });
138
186
  var githubAppSchema = z2.object({
139
187
  app_id_env: z2.string().min(1),
@@ -303,6 +351,8 @@ import { z as z3 } from "zod";
303
351
  var clientEnvSchema = z3.object({
304
352
  /** Where the immutable client assets live. The one variable that repoints the whole client. */
305
353
  assetPath: z3.string().min(1),
354
+ /** Opaque SHA-256 fingerprint of the canonical asset path, for diagnostic correlation only. */
355
+ assetPathId: z3.string().regex(/^sha256:[a-f0-9]{64}$/),
306
356
  /** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
307
357
  proxyPath: z3.string().min(1),
308
358
  /** Which board this is, resolved server-side so the client parses no URLs. */
@@ -313,7 +363,7 @@ var clientEnvSchema = z3.object({
313
363
  clientId: z3.string().min(1).optional()
314
364
  }).optional()
315
365
  });
316
- var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true });
366
+ var clientIdentityResponseSchema = clientEnvSchema.pick({ assetPath: true, assetPathId: true }).extend({ serverVersion: z3.string().min(1) });
317
367
 
318
368
  // packages/shared/src/envelope.ts
319
369
  import { z as z4 } from "zod";
package/dist/index.js CHANGED
@@ -764,6 +764,26 @@ var positionSchema = z2.union([
764
764
  ]);
765
765
  var panelDensities = ["auto", "comfortable", "compact"];
766
766
  var panelDensitySchema = z2.enum(panelDensities);
767
+ var httpValueJsonPathSchema = z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path");
768
+ var httpValueFactSchema = z2.object({
769
+ /** Stable address below the panel; labels and ordering may change without repointing it. */
770
+ id: z2.string().min(1),
771
+ label: z2.string().min(1),
772
+ url: z2.url(),
773
+ json_path: httpValueJsonPathSchema.optional()
774
+ });
775
+ var httpValuePanelIdentitySchema = z2.object({
776
+ id: z2.string().min(1),
777
+ type: z2.literal("http-value")
778
+ });
779
+ var httpValueScalarPanelSchema = httpValuePanelIdentitySchema.extend({
780
+ url: z2.url(),
781
+ json_path: httpValueJsonPathSchema.optional(),
782
+ link: z2.url().optional()
783
+ });
784
+ var httpValueGroupedPanelSchema = httpValuePanelIdentitySchema.extend({
785
+ facts: z2.array(httpValueFactSchema).min(1).max(4)
786
+ });
767
787
  var visibleRunningAnimations = [
768
788
  "radial",
769
789
  "runway",
@@ -806,7 +826,9 @@ var panelSchema = z2.looseObject({
806
826
  /** Source-agnostic endpoint used by the http-value signal. */
807
827
  url: z2.url().optional(),
808
828
  /** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
809
- json_path: z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path").optional()
829
+ json_path: httpValueJsonPathSchema.optional(),
830
+ /** Up to four independently fetched source-agnostic readings in one visual panel. */
831
+ facts: httpValueGroupedPanelSchema.shape.facts.optional()
810
832
  }).superRefine((panel, ctx) => {
811
833
  if ("display" in panel) {
812
834
  ctx.addIssue({
@@ -815,6 +837,32 @@ var panelSchema = z2.looseObject({
815
837
  message: "display was removed; use density"
816
838
  });
817
839
  }
840
+ if (panel.type !== "http-value") return;
841
+ if (panel.facts) {
842
+ if (panel.url !== void 0)
843
+ ctx.addIssue({
844
+ code: "custom",
845
+ path: ["url"],
846
+ message: "use facts URLs for grouped panels"
847
+ });
848
+ if (panel.json_path !== void 0)
849
+ ctx.addIssue({
850
+ code: "custom",
851
+ path: ["json_path"],
852
+ message: "use facts json_path values for grouped panels"
853
+ });
854
+ const seen = /* @__PURE__ */ new Map();
855
+ panel.facts.forEach((fact, index) => {
856
+ const first = seen.get(fact.id);
857
+ if (first === void 0) seen.set(fact.id, index);
858
+ else
859
+ ctx.addIssue({
860
+ code: "custom",
861
+ path: ["facts", index, "id"],
862
+ message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
863
+ });
864
+ });
865
+ }
818
866
  });
819
867
  var githubAppSchema = z2.object({
820
868
  app_id_env: z2.string().min(1),
@@ -984,6 +1032,8 @@ import { z as z3 } from "zod";
984
1032
  var clientEnvSchema = z3.object({
985
1033
  /** Where the immutable client assets live. The one variable that repoints the whole client. */
986
1034
  assetPath: z3.string().min(1),
1035
+ /** Opaque SHA-256 fingerprint of the canonical asset path, for diagnostic correlation only. */
1036
+ assetPathId: z3.string().regex(/^sha256:[a-f0-9]{64}$/),
987
1037
  /** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
988
1038
  proxyPath: z3.string().min(1),
989
1039
  /** Which board this is, resolved server-side so the client parses no URLs. */
@@ -994,7 +1044,7 @@ var clientEnvSchema = z3.object({
994
1044
  clientId: z3.string().min(1).optional()
995
1045
  }).optional()
996
1046
  });
997
- var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true });
1047
+ var clientIdentityResponseSchema = clientEnvSchema.pick({ assetPath: true, assetPathId: true }).extend({ serverVersion: z3.string().min(1) });
998
1048
 
999
1049
  // packages/shared/src/envelope.ts
1000
1050
  import { z as z4 } from "zod";
package/dist/lambda.mjs CHANGED
@@ -25202,7 +25202,7 @@ var require_dist_cjs11 = __commonJS({
25202
25202
  var { setCredentialFeature: setCredentialFeature2 } = (init_client3(), __toCommonJS(client_exports2));
25203
25203
  var { CredentialsProviderError: CredentialsProviderError2, parseKnownFiles: parseKnownFiles2, getProfileName: getProfileName2 } = (init_config2(), __toCommonJS(config_exports));
25204
25204
  var { HttpRequest: HttpRequest2 } = (init_protocols(), __toCommonJS(protocols_exports));
25205
- var { createHash: createHash5, createPrivateKey, createPublicKey, sign: sign2 } = __require("node:crypto");
25205
+ var { createHash: createHash6, createPrivateKey, createPublicKey, sign: sign2 } = __require("node:crypto");
25206
25206
  var { promises } = __require("node:fs");
25207
25207
  var { homedir: homedir2 } = __require("node:os");
25208
25208
  var { dirname, join: join5 } = __require("node:path");
@@ -25371,7 +25371,7 @@ var require_dist_cjs11 = __commonJS({
25371
25371
  getTokenFilePath() {
25372
25372
  const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join5(homedir2(), ".aws", "login", "cache");
25373
25373
  const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
25374
- const loginSessionSha256 = createHash5("sha256").update(loginSessionBytes).digest("hex");
25374
+ const loginSessionSha256 = createHash6("sha256").update(loginSessionBytes).digest("hex");
25375
25375
  return join5(directory, `${loginSessionSha256}.json`);
25376
25376
  }
25377
25377
  derToRawSignature(derSignature) {
@@ -68320,6 +68320,26 @@ var positionSchema = external_exports.union([
68320
68320
  ]);
68321
68321
  var panelDensities = ["auto", "comfortable", "compact"];
68322
68322
  var panelDensitySchema = external_exports.enum(panelDensities);
68323
+ var httpValueJsonPathSchema = external_exports.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path");
68324
+ var httpValueFactSchema = external_exports.object({
68325
+ /** Stable address below the panel; labels and ordering may change without repointing it. */
68326
+ id: external_exports.string().min(1),
68327
+ label: external_exports.string().min(1),
68328
+ url: external_exports.url(),
68329
+ json_path: httpValueJsonPathSchema.optional()
68330
+ });
68331
+ var httpValuePanelIdentitySchema = external_exports.object({
68332
+ id: external_exports.string().min(1),
68333
+ type: external_exports.literal("http-value")
68334
+ });
68335
+ var httpValueScalarPanelSchema = httpValuePanelIdentitySchema.extend({
68336
+ url: external_exports.url(),
68337
+ json_path: httpValueJsonPathSchema.optional(),
68338
+ link: external_exports.url().optional()
68339
+ });
68340
+ var httpValueGroupedPanelSchema = httpValuePanelIdentitySchema.extend({
68341
+ facts: external_exports.array(httpValueFactSchema).min(1).max(4)
68342
+ });
68323
68343
  var visibleRunningAnimations = [
68324
68344
  "radial",
68325
68345
  "runway",
@@ -68362,7 +68382,9 @@ var panelSchema = external_exports.looseObject({
68362
68382
  /** Source-agnostic endpoint used by the http-value signal. */
68363
68383
  url: external_exports.url().optional(),
68364
68384
  /** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
68365
- json_path: external_exports.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path").optional()
68385
+ json_path: httpValueJsonPathSchema.optional(),
68386
+ /** Up to four independently fetched source-agnostic readings in one visual panel. */
68387
+ facts: httpValueGroupedPanelSchema.shape.facts.optional()
68366
68388
  }).superRefine((panel, ctx) => {
68367
68389
  if ("display" in panel) {
68368
68390
  ctx.addIssue({
@@ -68371,6 +68393,32 @@ var panelSchema = external_exports.looseObject({
68371
68393
  message: "display was removed; use density"
68372
68394
  });
68373
68395
  }
68396
+ if (panel.type !== "http-value") return;
68397
+ if (panel.facts) {
68398
+ if (panel.url !== void 0)
68399
+ ctx.addIssue({
68400
+ code: "custom",
68401
+ path: ["url"],
68402
+ message: "use facts URLs for grouped panels"
68403
+ });
68404
+ if (panel.json_path !== void 0)
68405
+ ctx.addIssue({
68406
+ code: "custom",
68407
+ path: ["json_path"],
68408
+ message: "use facts json_path values for grouped panels"
68409
+ });
68410
+ const seen = /* @__PURE__ */ new Map();
68411
+ panel.facts.forEach((fact, index) => {
68412
+ const first = seen.get(fact.id);
68413
+ if (first === void 0) seen.set(fact.id, index);
68414
+ else
68415
+ ctx.addIssue({
68416
+ code: "custom",
68417
+ path: ["facts", index, "id"],
68418
+ message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
68419
+ });
68420
+ });
68421
+ }
68374
68422
  });
68375
68423
  var githubAppSchema = external_exports.object({
68376
68424
  app_id_env: external_exports.string().min(1),
@@ -68552,6 +68600,8 @@ function boardSchemaModeline(url2) {
68552
68600
  var clientEnvSchema = external_exports.object({
68553
68601
  /** Where the immutable client assets live. The one variable that repoints the whole client. */
68554
68602
  assetPath: external_exports.string().min(1),
68603
+ /** Opaque SHA-256 fingerprint of the canonical asset path, for diagnostic correlation only. */
68604
+ assetPathId: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
68555
68605
  /** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
68556
68606
  proxyPath: external_exports.string().min(1),
68557
68607
  /** Which board this is, resolved server-side so the client parses no URLs. */
@@ -68562,7 +68612,7 @@ var clientEnvSchema = external_exports.object({
68562
68612
  clientId: external_exports.string().min(1).optional()
68563
68613
  }).optional()
68564
68614
  });
68565
- var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true });
68615
+ var clientIdentityResponseSchema = clientEnvSchema.pick({ assetPath: true, assetPathId: true }).extend({ serverVersion: external_exports.string().min(1) });
68566
68616
 
68567
68617
  // packages/shared/src/envelope.ts
68568
68618
  var errorKindSchema = external_exports.enum([
@@ -69803,23 +69853,17 @@ function failure(panelId, kind, error62, link, date6, failure2) {
69803
69853
  }
69804
69854
 
69805
69855
  // packages/server/src/adapters/http-value.ts
69806
- var httpValuePanelSchema = external_exports.object({
69807
- id: external_exports.string().min(1),
69808
- type: external_exports.literal("http-value"),
69809
- url: external_exports.url(),
69810
- json_path: external_exports.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional(),
69811
- link: external_exports.url().optional()
69812
- });
69813
69856
  function permittedHttpValueCalls(panel) {
69814
- const parsed = httpValuePanelSchema.parse(panel);
69815
- const url2 = new URL(parsed.url);
69816
- if (url2.protocol !== "http:" && url2.protocol !== "https:") {
69817
- throw new Error("http-value URLs must use http or https");
69818
- }
69819
- return [{ url: url2.toString(), headers: new Headers({ accept: "application/json, text/plain" }) }];
69857
+ const grouped = httpValueGroupedPanelSchema.safeParse(panel);
69858
+ const urls = grouped.success ? grouped.data.facts.map((fact) => fact.url) : [httpValueScalarPanelSchema.parse(panel).url];
69859
+ return urls.map(permittedCall);
69860
+ }
69861
+ function httpValueFact(panel, factId) {
69862
+ const grouped = httpValueGroupedPanelSchema.parse(panel);
69863
+ return grouped.facts.find((fact) => fact.id === factId);
69820
69864
  }
69821
69865
  async function fetchHttpValue(args) {
69822
- const panel = httpValuePanelSchema.parse(args.panel);
69866
+ const panel = httpValueScalarPanelSchema.parse(args.panel);
69823
69867
  const [call] = permittedHttpValueCalls(args.panel);
69824
69868
  if (!call) throw new Error("http-value adapter declared no permitted call");
69825
69869
  forwardValidators(args.requestHeaders, call.headers);
@@ -69868,6 +69912,21 @@ async function fetchHttpValue(args) {
69868
69912
  };
69869
69913
  }
69870
69914
  }
69915
+ async function fetchHttpValueFact(args) {
69916
+ const fact = httpValueFact(args.panel, args.factId);
69917
+ if (!fact) throw new Error(`http-value fact "${args.factId}" is not configured`);
69918
+ return fetchHttpValue({
69919
+ panel: { id: args.panel.id, type: "http-value", url: fact.url, json_path: fact.json_path },
69920
+ requestHeaders: args.requestHeaders,
69921
+ fetcher: args.fetcher
69922
+ });
69923
+ }
69924
+ function permittedCall(value) {
69925
+ const url2 = new URL(value);
69926
+ if (url2.protocol !== "http:" && url2.protocol !== "https:")
69927
+ throw new Error("http-value URLs must use http or https");
69928
+ return { url: url2.toString(), headers: new Headers({ accept: "application/json, text/plain" }) };
69929
+ }
69871
69930
  function parseValue(text) {
69872
69931
  const trimmed = text.trim();
69873
69932
  try {
@@ -72032,6 +72091,68 @@ var Hono2 = class extends Hono {
72032
72091
  // packages/server/src/app.ts
72033
72092
  var import_yaml = __toESM(require_dist(), 1);
72034
72093
 
72094
+ // packages/server/src/config.ts
72095
+ import { createHash as createHash5 } from "node:crypto";
72096
+ import { fileURLToPath } from "node:url";
72097
+ var DEFAULT_BOARD_CONFIG_URL = fileURLToPath(
72098
+ new URL("../../../boards/example.yaml", import.meta.url)
72099
+ );
72100
+ var configSchema = external_exports.object({
72101
+ /**
72102
+ * Where the built client lives. Points at a versioned CDN path in a deployment
72103
+ * (`https://assets.../dashboard/1.0.7`) or at the Vite dev server locally. Trailing slashes are
72104
+ * trimmed so `${assetPath}/index.html` never doubles up.
72105
+ */
72106
+ assetPath: external_exports.string({
72107
+ error: "ASSET_PATH is required \u2014 the server has no client to serve without it."
72108
+ }).min(1, "ASSET_PATH is required \u2014 the server has no client to serve without it.").transform((value) => value.replace(/\/+$/, "")),
72109
+ proxyPath: external_exports.string().min(1).default("/api"),
72110
+ /** A URL or a local file path. Local development uses a path and needs no credential. */
72111
+ boardConfigUrl: external_exports.string().min(1).default(DEFAULT_BOARD_CONFIG_URL),
72112
+ /** Optional for single-board files; startup selects the only board automatically. */
72113
+ board: external_exports.string().min(1).optional(),
72114
+ /** Optional ARN of the one Secrets Manager JSON credential map for this Lambda. */
72115
+ secretReference: external_exports.string().min(1).optional(),
72116
+ /** Immutable server image identifier, supplied by the image build and emitted only in startup logs. */
72117
+ serverRelease: external_exports.string().min(1).default("development"),
72118
+ port: external_exports.coerce.number().int().min(1).max(65535).default(3e3),
72119
+ host: external_exports.string().min(1).default("localhost"),
72120
+ /**
72121
+ * How long to keep retrying an unreachable template before giving up, in milliseconds.
72122
+ *
72123
+ * Zero — the default, and what every deployment uses — means a bad ASSET_PATH fails on the first
72124
+ * attempt, which is the behavior a misconfiguration deserves. `npm run dev` sets a few seconds
72125
+ * because it starts the server and the Vite dev server at the same moment, and without this the
72126
+ * local loop is a race the developer loses about half the time.
72127
+ */
72128
+ templateWaitMillis: external_exports.coerce.number().int().min(0).default(0)
72129
+ });
72130
+ function loadConfig2(env2 = process.env) {
72131
+ const result = configSchema.safeParse({
72132
+ assetPath: env2.ASSET_PATH,
72133
+ proxyPath: env2.PROXY_PATH,
72134
+ boardConfigUrl: env2.BOARD_CONFIG_URL,
72135
+ board: env2.BOARD,
72136
+ secretReference: env2.SECRET_REFERENCE,
72137
+ serverRelease: env2.SERVER_RELEASE,
72138
+ port: env2.PORT,
72139
+ host: env2.HOST,
72140
+ templateWaitMillis: env2.TEMPLATE_WAIT_MS
72141
+ });
72142
+ if (!result.success) {
72143
+ throw new Error(`Invalid server configuration:
72144
+ ${external_exports.prettifyError(result.error)}`);
72145
+ }
72146
+ return result.data;
72147
+ }
72148
+ function assetPathId(assetPath) {
72149
+ return `sha256:${createHash5("sha256").update(assetPath).digest("hex")}`;
72150
+ }
72151
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
72152
+ function isLocalHost(host) {
72153
+ return LOCAL_HOSTS.has(host);
72154
+ }
72155
+
72035
72156
  // packages/server/src/logger.ts
72036
72157
  var consoleLogger = {
72037
72158
  log(event) {
@@ -72049,6 +72170,29 @@ function destinationOrigin(value) {
72049
72170
  return void 0;
72050
72171
  }
72051
72172
  }
72173
+ function serverDiagnosticContext(serverVersion, configuredAssetPathId) {
72174
+ return { serverVersion, configuredAssetPathId };
72175
+ }
72176
+ function clientDiagnosticClaims(headers, context) {
72177
+ const version2 = headers.get("x-dashboard-client-version");
72178
+ const origin = headers.get("x-dashboard-client-origin");
72179
+ const assetPathId2 = headers.get("x-dashboard-client-asset-id");
72180
+ const clientOrigin = safeOrigin(origin);
72181
+ return {
72182
+ ...version2 && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(version2) ? { clientVersion: version2 } : {},
72183
+ ...clientOrigin ? { clientOrigin } : {},
72184
+ ...assetPathId2 && /^sha256:[a-f0-9]{64}$/.test(assetPathId2) ? { clientAssetPathMatchesConfigured: assetPathId2 === context.configuredAssetPathId } : {}
72185
+ };
72186
+ }
72187
+ function safeOrigin(value) {
72188
+ if (!value) return void 0;
72189
+ try {
72190
+ const origin = new URL(value).origin;
72191
+ return origin === "null" ? void 0 : origin;
72192
+ } catch {
72193
+ return void 0;
72194
+ }
72195
+ }
72052
72196
 
72053
72197
  // packages/server/src/template.ts
72054
72198
  var ASSET_PATH_SENTINEL = "/__ASSET_PATH__";
@@ -72134,6 +72278,10 @@ function createApp(deps) {
72134
72278
  const credentials = deps.credentials ?? environmentCredentials();
72135
72279
  const githubClient = createGithubClient(credentials);
72136
72280
  const logger2 = deps.logger ?? consoleLogger;
72281
+ const diagnosticContext = serverDiagnosticContext(
72282
+ config2.serverRelease,
72283
+ assetPathId(config2.assetPath)
72284
+ );
72137
72285
  const app = new Hono2();
72138
72286
  app.use("/api/*", async (c5, next) => {
72139
72287
  const id = requestId();
@@ -72145,8 +72293,10 @@ function createApp(deps) {
72145
72293
  const id = c5.get("dashboardRequestId");
72146
72294
  logger2.log({
72147
72295
  event: "server.unhandled_exception",
72296
+ serverVersion: diagnosticContext.serverVersion,
72148
72297
  ...id ? { requestId: id } : {},
72149
- operation: "route-handler"
72298
+ operation: "route-handler",
72299
+ ...clientDiagnosticClaims(c5.req.raw.headers, diagnosticContext)
72150
72300
  });
72151
72301
  return c5.json(
72152
72302
  { error: "The dashboard could not complete this request." },
@@ -72156,7 +72306,11 @@ function createApp(deps) {
72156
72306
  });
72157
72307
  app.get("/health", (c5) => c5.json({ status: "ok" }));
72158
72308
  app.get(`${config2.proxyPath}/client`, (c5) => {
72159
- const identity = { assetPath: config2.assetPath };
72309
+ const identity = {
72310
+ assetPath: config2.assetPath,
72311
+ assetPathId: diagnosticContext.configuredAssetPathId,
72312
+ serverVersion: diagnosticContext.serverVersion
72313
+ };
72160
72314
  return c5.json(identity, 200, { "cache-control": "no-store" });
72161
72315
  });
72162
72316
  app.get("/", (c5) => renderEntrypoint(c5.req.raw, selectedBoard));
@@ -72208,6 +72362,27 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
72208
72362
  };
72209
72363
  app.get("/api/boards/:board/rendered", (c5) => layoutDownload(c5, "rendered"));
72210
72364
  app.get("/api/boards/:board/authored", (c5) => layoutDownload(c5, "authored"));
72365
+ app.get("/api/panel/:board/:panelId/facts/:factId", async (c5) => {
72366
+ const boardName = c5.req.param("board");
72367
+ const panelId = c5.req.param("panelId");
72368
+ const factId = c5.req.param("factId");
72369
+ const panel = deps.boardConfig?.boards[boardName]?.panels.find(
72370
+ (candidate) => candidate.id === panelId
72371
+ );
72372
+ const fact = panel?.type === "http-value" && panel.facts ? httpValueFact(panel, factId) : void 0;
72373
+ if (!panel || !fact || !permitsPanelOperation(allowlist, boardName, panelId, "read"))
72374
+ return rejected(c5, { boardId: boardName, panelId, operation: "read" });
72375
+ return observation(
72376
+ c5,
72377
+ fetchHttpValueFact({
72378
+ panel,
72379
+ factId,
72380
+ requestHeaders: c5.req.raw.headers,
72381
+ fetcher: deps.fetcher ?? globalThis.fetch
72382
+ }),
72383
+ { boardId: boardName, panelId, operation: "read", destination: fact.url }
72384
+ );
72385
+ });
72211
72386
  app.get("/api/panel/:board/:panelId", async (c5) => {
72212
72387
  const boardName = c5.req.param("board");
72213
72388
  const panelId = c5.req.param("panelId");
@@ -72270,6 +72445,7 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
72270
72445
  if (panel.type === "pull-request-health")
72271
72446
  return rejected(c5, { boardId: boardName, panelId, operation: "read" });
72272
72447
  if (panel.type === "http-value") {
72448
+ if (panel.facts) return rejected(c5, { boardId: boardName, panelId, operation: "read" });
72273
72449
  const result = fetchHttpValue({
72274
72450
  panel,
72275
72451
  requestHeaders: c5.req.raw.headers,
@@ -72386,10 +72562,12 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
72386
72562
  function rejected(c5, context) {
72387
72563
  logger2.log({
72388
72564
  event: "api.operation_rejected",
72565
+ serverVersion: diagnosticContext.serverVersion,
72389
72566
  requestId: c5.get("dashboardRequestId"),
72390
72567
  boardId: context.boardId,
72391
72568
  panelId: context.panelId,
72392
- operation: context.operation
72569
+ operation: context.operation,
72570
+ ...clientDiagnosticClaims(c5.req.raw.headers, diagnosticContext)
72393
72571
  });
72394
72572
  return c5.notFound();
72395
72573
  }
@@ -72400,10 +72578,12 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
72400
72578
  if (adapted.envelope?.state === "error") {
72401
72579
  logger2.log({
72402
72580
  event: "panel.observation_failed",
72581
+ serverVersion: diagnosticContext.serverVersion,
72403
72582
  requestId: c5.get("dashboardRequestId"),
72404
72583
  boardId: context.boardId,
72405
72584
  panelId: context.panelId,
72406
72585
  operation: context.operation,
72586
+ ...clientDiagnosticClaims(c5.req.raw.headers, diagnosticContext),
72407
72587
  errorKind: adapted.envelope.error.kind,
72408
72588
  elapsedMs: Math.round(performance.now() - started),
72409
72589
  ...context.sourceName ? { sourceName: context.sourceName } : {},
@@ -72419,6 +72599,7 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
72419
72599
  const template = await templates.get(config2.assetPath);
72420
72600
  const env2 = {
72421
72601
  assetPath: config2.assetPath,
72602
+ assetPathId: diagnosticContext.configuredAssetPathId,
72422
72603
  proxyPath: config2.proxyPath,
72423
72604
  board
72424
72605
  };
@@ -72465,68 +72646,13 @@ async function fetchBoardConfig(url2, fetcher) {
72465
72646
  return response.text();
72466
72647
  }
72467
72648
 
72468
- // packages/server/src/config.ts
72469
- import { fileURLToPath } from "node:url";
72470
- var DEFAULT_BOARD_CONFIG_URL = fileURLToPath(
72471
- new URL("../../../boards/example.yaml", import.meta.url)
72472
- );
72473
- var configSchema = external_exports.object({
72474
- /**
72475
- * Where the built client lives. Points at a versioned CDN path in a deployment
72476
- * (`https://assets.../dashboard/1.0.7`) or at the Vite dev server locally. Trailing slashes are
72477
- * trimmed so `${assetPath}/index.html` never doubles up.
72478
- */
72479
- assetPath: external_exports.string({
72480
- error: "ASSET_PATH is required \u2014 the server has no client to serve without it."
72481
- }).min(1, "ASSET_PATH is required \u2014 the server has no client to serve without it.").transform((value) => value.replace(/\/+$/, "")),
72482
- proxyPath: external_exports.string().min(1).default("/api"),
72483
- /** A URL or a local file path. Local development uses a path and needs no credential. */
72484
- boardConfigUrl: external_exports.string().min(1).default(DEFAULT_BOARD_CONFIG_URL),
72485
- /** Optional for single-board files; startup selects the only board automatically. */
72486
- board: external_exports.string().min(1).optional(),
72487
- /** Optional ARN of the one Secrets Manager JSON credential map for this Lambda. */
72488
- secretReference: external_exports.string().min(1).optional(),
72489
- /** Immutable server image identifier, supplied by the image build and emitted only in startup logs. */
72490
- serverRelease: external_exports.string().min(1).default("development"),
72491
- port: external_exports.coerce.number().int().min(1).max(65535).default(3e3),
72492
- host: external_exports.string().min(1).default("localhost"),
72493
- /**
72494
- * How long to keep retrying an unreachable template before giving up, in milliseconds.
72495
- *
72496
- * Zero — the default, and what every deployment uses — means a bad ASSET_PATH fails on the first
72497
- * attempt, which is the behavior a misconfiguration deserves. `npm run dev` sets a few seconds
72498
- * because it starts the server and the Vite dev server at the same moment, and without this the
72499
- * local loop is a race the developer loses about half the time.
72500
- */
72501
- templateWaitMillis: external_exports.coerce.number().int().min(0).default(0)
72502
- });
72503
- function loadConfig2(env2 = process.env) {
72504
- const result = configSchema.safeParse({
72505
- assetPath: env2.ASSET_PATH,
72506
- proxyPath: env2.PROXY_PATH,
72507
- boardConfigUrl: env2.BOARD_CONFIG_URL,
72508
- board: env2.BOARD,
72509
- secretReference: env2.SECRET_REFERENCE,
72510
- serverRelease: env2.SERVER_RELEASE,
72511
- port: env2.PORT,
72512
- host: env2.HOST,
72513
- templateWaitMillis: env2.TEMPLATE_WAIT_MS
72514
- });
72515
- if (!result.success) {
72516
- throw new Error(`Invalid server configuration:
72517
- ${external_exports.prettifyError(result.error)}`);
72518
- }
72519
- return result.data;
72520
- }
72521
- var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
72522
- function isLocalHost(host) {
72523
- return LOCAL_HOSTS.has(host);
72524
- }
72525
-
72526
72649
  // packages/server/src/startup.ts
72527
72650
  async function startup(options = {}) {
72528
72651
  const logger2 = options.logger ?? consoleLogger;
72529
- logger2.log({ event: "server.starting" });
72652
+ logger2.log({
72653
+ event: "server.starting",
72654
+ serverVersion: process.env.SERVER_RELEASE ?? "development"
72655
+ });
72530
72656
  try {
72531
72657
  const config2 = loadConfig2();
72532
72658
  const fetcher = options.fetcher ?? globalThis.fetch;
@@ -72559,7 +72685,11 @@ async function startup(options = {}) {
72559
72685
  config: resolvedConfig
72560
72686
  };
72561
72687
  } catch (error62) {
72562
- logger2.log({ event: "server.startup_failed", category: startupFailureCategory(error62) });
72688
+ logger2.log({
72689
+ event: "server.startup_failed",
72690
+ serverVersion: process.env.SERVER_RELEASE ?? "development",
72691
+ category: startupFailureCategory(error62)
72692
+ });
72563
72693
  throw error62;
72564
72694
  }
72565
72695
  }
@@ -72598,7 +72728,12 @@ async function waitForTemplate(config2, fetcher) {
72598
72728
  var RETRY_INTERVAL_MILLIS = 250;
72599
72729
  function warnAboutMissingAuth(config2, logger2) {
72600
72730
  if (isLocalHost(config2.host)) return;
72601
- logger2.log({ event: "server.no_auth_warning", host: config2.host, port: config2.port });
72731
+ logger2.log({
72732
+ event: "server.no_auth_warning",
72733
+ serverVersion: config2.serverRelease,
72734
+ host: config2.host,
72735
+ port: config2.port
72736
+ });
72602
72737
  }
72603
72738
  function startupFailureCategory(error62) {
72604
72739
  const message = error62 instanceof Error ? error62.message : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@continuous-excellence/ze-great-dashboard-aws",
3
- "version": "0.24.2",
3
+ "version": "0.26.0",
4
4
  "type": "module",
5
5
  "description": "AWS server runtime and CloudFormation deployment tooling for Ze Great Dashboard.",
6
6
  "keywords": [