@continuous-excellence/ze-great-dashboard-aws 0.21.0 → 0.22.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.
Files changed (2) hide show
  1. package/dist/lambda.mjs +271 -70
  2. package/package.json +1 -1
package/dist/lambda.mjs CHANGED
@@ -66462,6 +66462,20 @@ var Hono2 = class extends Hono {
66462
66462
  var import_yaml = __toESM(require_dist(), 1);
66463
66463
 
66464
66464
  // packages/server/src/upstream.ts
66465
+ function publicErrorMessage(kind) {
66466
+ switch (kind) {
66467
+ case "unreachable":
66468
+ return "The source could not be reached. Check its address and network connectivity.";
66469
+ case "unauthorized":
66470
+ return "The dashboard is not authorized to read this source. Check the configured access.";
66471
+ case "not-found":
66472
+ return "The configured source was not found. Check the board configuration.";
66473
+ case "no-runs":
66474
+ return "No matching workflow runs were found. Check the configured workflow and branch.";
66475
+ case "upstream-error":
66476
+ return "The source returned an unreadable response. Check the source service and configuration.";
66477
+ }
66478
+ }
66465
66479
  function forwardValidators(requestHeaders, upstreamHeaders) {
66466
66480
  for (const name of ["if-none-match", "if-modified-since"]) {
66467
66481
  const value = requestHeaders.get(name);
@@ -66477,13 +66491,26 @@ function upstreamErrorEnvelope(args) {
66477
66491
  panelId: args.panelId,
66478
66492
  state: "error",
66479
66493
  observedAt: observedAt(args.upstreamDate),
66480
- link: args.link,
66494
+ link: publicLink(args.link),
66481
66495
  error: {
66482
66496
  kind: args.kind,
66483
- message: args.error instanceof Error ? args.error.message : String(args.error)
66497
+ // Raw failures regularly contain URLs, query values, response fragments, or credentials.
66498
+ // The specific evidence stays server-side; the browser gets this fixed actionable vocabulary.
66499
+ message: publicErrorMessage(args.kind)
66484
66500
  }
66485
66501
  };
66486
66502
  }
66503
+ function publicLink(value) {
66504
+ if (!value) return null;
66505
+ try {
66506
+ const url2 = new URL(value);
66507
+ url2.search = "";
66508
+ url2.hash = "";
66509
+ return url2.toString();
66510
+ } catch {
66511
+ return null;
66512
+ }
66513
+ }
66487
66514
  function upstreamErrorResponse(envelope) {
66488
66515
  return new Response(JSON.stringify(envelope), { status: 200 });
66489
66516
  }
@@ -66494,10 +66521,15 @@ function upstreamErrorKind(status) {
66494
66521
  }
66495
66522
  async function adapterRouteResponse(result) {
66496
66523
  const headers = passthroughHeaders(result.response.headers);
66497
- if (result.response.status === 304) return new Response(null, { status: 304, headers });
66524
+ if (result.response.status === 304)
66525
+ return { response: new Response(null, { status: 304, headers }) };
66498
66526
  const envelope = result.envelope ?? JSON.parse(await result.response.text());
66499
66527
  headers.set("content-type", "application/json; charset=utf-8");
66500
- return new Response(JSON.stringify(envelope), { status: 200, headers });
66528
+ return { response: new Response(JSON.stringify(envelope), { status: 200, headers }), envelope };
66529
+ }
66530
+ function safeNetworkCode(error52) {
66531
+ const code = error52 && typeof error52 === "object" ? error52.code : void 0;
66532
+ return code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "ETIMEDOUT" || code === "EAI_AGAIN" ? code : void 0;
66501
66533
  }
66502
66534
  function passthroughHeaders(upstream) {
66503
66535
  const headers = new Headers();
@@ -66555,7 +66587,8 @@ async function fetchAzureDevOpsPipeline(args) {
66555
66587
  `Credential ${source.token_env} is not available.`,
66556
66588
  sourceLink(panel.pipeline, source)
66557
66589
  )
66558
- )
66590
+ ),
66591
+ failure: { kind: "unauthorized" }
66559
66592
  };
66560
66593
  }
66561
66594
  const call = buildsCall(panel.pipeline, source);
@@ -66568,7 +66601,11 @@ async function fetchAzureDevOpsPipeline(args) {
66568
66601
  return {
66569
66602
  response: errorResponse(
66570
66603
  errorEnvelope(panel.id, "unreachable", error52, sourceLink(panel.pipeline, source))
66571
- )
66604
+ ),
66605
+ failure: {
66606
+ kind: "unreachable",
66607
+ ...safeNetworkCode(error52) ? { networkCode: safeNetworkCode(error52) } : {}
66608
+ }
66572
66609
  };
66573
66610
  }
66574
66611
  if (upstream.status === 304) return { response: upstream };
@@ -66582,7 +66619,8 @@ async function fetchAzureDevOpsPipeline(args) {
66582
66619
  sourceLink(panel.pipeline, source),
66583
66620
  observedAt2(upstream.headers.get("date"))
66584
66621
  )
66585
- )
66622
+ ),
66623
+ failure: { kind: errorKind(upstream.status), upstreamStatus: upstream.status }
66586
66624
  };
66587
66625
  }
66588
66626
  try {
@@ -66597,7 +66635,8 @@ async function fetchAzureDevOpsPipeline(args) {
66597
66635
  sourceLink(panel.pipeline, source),
66598
66636
  observedAt2(upstream.headers.get("date"))
66599
66637
  )
66600
- )
66638
+ ),
66639
+ failure: { kind: "no-runs", upstreamStatus: upstream.status }
66601
66640
  };
66602
66641
  }
66603
66642
  const signal = {
@@ -66640,7 +66679,8 @@ async function fetchAzureDevOpsPipeline(args) {
66640
66679
  sourceLink(panel.pipeline, source),
66641
66680
  observedAt2(upstream.headers.get("date"))
66642
66681
  )
66643
- )
66682
+ ),
66683
+ failure: { kind: "upstream-error", upstreamStatus: upstream.status }
66644
66684
  };
66645
66685
  }
66646
66686
  }
@@ -67068,7 +67108,11 @@ async function githubObservation(args, source, call, normalize) {
67068
67108
  )
67069
67109
  ),
67070
67110
  { status: 200 }
67071
- )
67111
+ ),
67112
+ failure: {
67113
+ kind: error52 instanceof GithubAuthenticationError ? "unauthorized" : "unreachable",
67114
+ ...safeNetworkCode(error52) ? { networkCode: safeNetworkCode(error52) } : {}
67115
+ }
67072
67116
  };
67073
67117
  }
67074
67118
  if (upstream.status === 304) return { response: upstream };
@@ -67085,7 +67129,8 @@ async function githubObservation(args, source, call, normalize) {
67085
67129
  )
67086
67130
  ),
67087
67131
  { status: 200 }
67088
- )
67132
+ ),
67133
+ failure: { kind: errorKind2(upstream.status), upstreamStatus: upstream.status }
67089
67134
  };
67090
67135
  try {
67091
67136
  return {
@@ -67105,7 +67150,8 @@ async function githubObservation(args, source, call, normalize) {
67105
67150
  )
67106
67151
  ),
67107
67152
  { status: 200 }
67108
- )
67153
+ ),
67154
+ failure: { kind: "upstream-error", upstreamStatus: upstream.status }
67109
67155
  };
67110
67156
  }
67111
67157
  }
@@ -67161,7 +67207,11 @@ async function fetchGithubActionsPipeline(args) {
67161
67207
  )
67162
67208
  ),
67163
67209
  { status: 200 }
67164
- )
67210
+ ),
67211
+ failure: {
67212
+ kind: error52 instanceof GithubAuthenticationError ? "unauthorized" : "unreachable",
67213
+ ...safeNetworkCode(error52) ? { networkCode: safeNetworkCode(error52) } : {}
67214
+ }
67165
67215
  };
67166
67216
  }
67167
67217
  if (upstream.status === 304) return { response: upstream };
@@ -67178,7 +67228,8 @@ async function fetchGithubActionsPipeline(args) {
67178
67228
  )
67179
67229
  ),
67180
67230
  { status: 200 }
67181
- )
67231
+ ),
67232
+ failure: { kind: errorKind2(upstream.status), upstreamStatus: upstream.status }
67182
67233
  };
67183
67234
  }
67184
67235
  try {
@@ -67197,7 +67248,8 @@ async function fetchGithubActionsPipeline(args) {
67197
67248
  )
67198
67249
  ),
67199
67250
  { status: 200 }
67200
- )
67251
+ ),
67252
+ failure: { kind: "no-runs", upstreamStatus: upstream.status }
67201
67253
  };
67202
67254
  }
67203
67255
  const signal = {
@@ -67242,7 +67294,8 @@ async function fetchGithubActionsPipeline(args) {
67242
67294
  )
67243
67295
  ),
67244
67296
  { status: 200 }
67245
- )
67297
+ ),
67298
+ failure: { kind: "upstream-error", upstreamStatus: upstream.status }
67246
67299
  };
67247
67300
  }
67248
67301
  }
@@ -67334,7 +67387,13 @@ async function fetchHttpValue(args) {
67334
67387
  try {
67335
67388
  upstream = await args.fetcher(call.url, { headers: call.headers });
67336
67389
  } catch (error52) {
67337
- return { response: errorResponse2("unreachable", error52, panel) };
67390
+ return {
67391
+ response: errorResponse2("unreachable", error52, panel),
67392
+ failure: {
67393
+ kind: "unreachable",
67394
+ ...safeNetworkCode(error52) ? { networkCode: safeNetworkCode(error52) } : {}
67395
+ }
67396
+ };
67338
67397
  }
67339
67398
  if (upstream.status === 304) return { response: upstream };
67340
67399
  if (!upstream.ok) {
@@ -67344,7 +67403,8 @@ async function fetchHttpValue(args) {
67344
67403
  `${upstream.status} ${upstream.statusText}`,
67345
67404
  panel,
67346
67405
  upstream
67347
- )
67406
+ ),
67407
+ failure: { kind: upstreamErrorKind(upstream.status), upstreamStatus: upstream.status }
67348
67408
  };
67349
67409
  }
67350
67410
  try {
@@ -67356,14 +67416,15 @@ async function fetchHttpValue(args) {
67356
67416
  panelId: panel.id,
67357
67417
  state: "ok",
67358
67418
  observedAt: observedAt(upstream.headers.get("date")),
67359
- link: panel.link ?? panel.url,
67419
+ link: publicLink(panel.link ?? panel.url),
67360
67420
  signal
67361
67421
  },
67362
67422
  response: upstream
67363
67423
  };
67364
67424
  } catch (error52) {
67365
67425
  return {
67366
- response: errorResponse2("upstream-error", error52, panel, upstream)
67426
+ response: errorResponse2("upstream-error", error52, panel, upstream),
67427
+ failure: { kind: "upstream-error", upstreamStatus: upstream.status }
67367
67428
  };
67368
67429
  }
67369
67430
  }
@@ -67434,6 +67495,24 @@ function callsFor(panel, source) {
67434
67495
  return void 0;
67435
67496
  }
67436
67497
 
67498
+ // packages/server/src/logger.ts
67499
+ var consoleLogger = {
67500
+ log(event) {
67501
+ console.log(JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...event }));
67502
+ }
67503
+ };
67504
+ function requestId() {
67505
+ return crypto.randomUUID();
67506
+ }
67507
+ function destinationOrigin(value) {
67508
+ if (!value) return void 0;
67509
+ try {
67510
+ return new URL(value).origin;
67511
+ } catch {
67512
+ return void 0;
67513
+ }
67514
+ }
67515
+
67437
67516
  // packages/server/src/template.ts
67438
67517
  var ASSET_PATH_SENTINEL = "/__ASSET_PATH__";
67439
67518
  var TemplateFetchError = class extends Error {
@@ -67517,9 +67596,28 @@ function createApp(deps) {
67517
67596
  const allowlist = deps.boardConfig ? deriveAllowlist(deps.boardConfig) : /* @__PURE__ */ new Map();
67518
67597
  const credentials = deps.credentials ?? environmentCredentials();
67519
67598
  const githubClient = createGithubClient(credentials);
67599
+ const logger2 = deps.logger ?? consoleLogger;
67520
67600
  const app = new Hono2();
67601
+ app.use("/api/*", async (c5, next) => {
67602
+ const id = requestId();
67603
+ c5.set("dashboardRequestId", id);
67604
+ await next();
67605
+ c5.header("x-dashboard-request-id", id);
67606
+ });
67607
+ app.onError((_error, c5) => {
67608
+ const id = c5.get("dashboardRequestId");
67609
+ logger2.log({
67610
+ event: "server.unhandled_exception",
67611
+ ...id ? { requestId: id } : {},
67612
+ operation: "route-handler"
67613
+ });
67614
+ return c5.json(
67615
+ { error: "The dashboard could not complete this request." },
67616
+ 500,
67617
+ id ? { "x-dashboard-request-id": id } : void 0
67618
+ );
67619
+ });
67521
67620
  app.get("/health", (c5) => c5.json({ status: "ok" }));
67522
- app.use("/api/*", async (_c5, next) => next());
67523
67621
  app.get(`${config2.proxyPath}/client`, (c5) => {
67524
67622
  const identity = { assetPath: config2.assetPath };
67525
67623
  return c5.json(identity, 200, { "cache-control": "no-store" });
@@ -67580,48 +67678,82 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67580
67678
  const panel = board?.panels.find((candidate) => candidate.id === panelId);
67581
67679
  const source = panel?.source ? deps.boardConfig?.sources[panel.source] : void 0;
67582
67680
  if (!panel || panel.type !== "http-value" && !source || !permitsPanelOperation(allowlist, boardName, panelId, "read"))
67583
- return c5.notFound();
67681
+ return rejected(c5, { boardId: boardName, panelId, operation: "read" });
67584
67682
  if (panel.type === "pipeline-status" && source?.type === "github-actions" && source) {
67585
- const result = await fetchGithubActionsPipeline({
67683
+ const result = fetchGithubActionsPipeline({
67586
67684
  panel,
67587
67685
  source,
67588
67686
  requestHeaders: c5.req.raw.headers,
67589
67687
  fetcher: deps.fetcher ?? globalThis.fetch,
67590
67688
  githubClient
67591
67689
  });
67592
- return adapterRouteResponse(result);
67690
+ return observation(c5, result, {
67691
+ boardId: boardName,
67692
+ panelId,
67693
+ operation: "read",
67694
+ sourceName: panel.source,
67695
+ sourceType: source.type,
67696
+ destination: "https://api.github.com"
67697
+ });
67593
67698
  }
67594
67699
  if (panel.type === "pipeline-status" && source?.type === "azure-devops" && source) {
67595
- const result = await fetchAzureDevOpsPipeline({
67700
+ const result = fetchAzureDevOpsPipeline({
67596
67701
  panel,
67597
67702
  source,
67598
67703
  requestHeaders: c5.req.raw.headers,
67599
67704
  fetcher: deps.fetcher ?? globalThis.fetch,
67600
67705
  credentials
67601
67706
  });
67602
- return adapterRouteResponse(result);
67707
+ return observation(c5, result, {
67708
+ boardId: boardName,
67709
+ panelId,
67710
+ operation: "read",
67711
+ sourceName: panel.source,
67712
+ sourceType: source.type,
67713
+ destination: "https://dev.azure.com"
67714
+ });
67603
67715
  }
67604
- if (panel.type === "pull-request-health") return c5.notFound();
67716
+ if (panel.type === "pull-request-health")
67717
+ return rejected(c5, { boardId: boardName, panelId, operation: "read" });
67605
67718
  if (panel.type === "http-value") {
67606
- const result = await fetchHttpValue({
67719
+ const result = fetchHttpValue({
67607
67720
  panel,
67608
67721
  requestHeaders: c5.req.raw.headers,
67609
67722
  fetcher: deps.fetcher ?? globalThis.fetch
67610
67723
  });
67611
- return adapterRouteResponse(result);
67724
+ return observation(c5, result, {
67725
+ boardId: boardName,
67726
+ panelId,
67727
+ operation: "read",
67728
+ destination: panel.url
67729
+ });
67612
67730
  }
67613
67731
  return c5.notFound();
67614
67732
  });
67615
67733
  app.get("/api/panel/:board/:panelId/pull-requests", async (c5) => {
67616
67734
  const resolved = pullRequestPanel(c5.req.param("board"), c5.req.param("panelId"), "pull-requests");
67617
- if (!resolved) return c5.notFound();
67618
- return adapterRouteResponse(
67619
- await fetchGithubActionsPullRequestCandidates({
67735
+ if (!resolved)
67736
+ return rejected(c5, {
67737
+ boardId: c5.req.param("board"),
67738
+ panelId: c5.req.param("panelId"),
67739
+ operation: "pull-requests"
67740
+ });
67741
+ return observation(
67742
+ c5,
67743
+ fetchGithubActionsPullRequestCandidates({
67620
67744
  ...resolved,
67621
67745
  requestHeaders: c5.req.raw.headers,
67622
67746
  fetcher: deps.fetcher ?? globalThis.fetch,
67623
67747
  githubClient
67624
- })
67748
+ }),
67749
+ {
67750
+ boardId: c5.req.param("board"),
67751
+ panelId: c5.req.param("panelId"),
67752
+ operation: "pull-requests",
67753
+ sourceName: resolved.panel.source,
67754
+ sourceType: resolved.source.type,
67755
+ destination: "https://api.github.com"
67756
+ }
67625
67757
  );
67626
67758
  });
67627
67759
  app.get("/api/panel/:board/:panelId/update-workflow/:workflow", async (c5) => {
@@ -67631,15 +67763,29 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67631
67763
  "update-workflow"
67632
67764
  );
67633
67765
  const workflow = c5.req.param("workflow");
67634
- if (!resolved?.capabilities.configuredUpdateWorkflow(workflow)) return c5.notFound();
67635
- return adapterRouteResponse(
67636
- await fetchGithubActionsUpdateWorkflow({
67766
+ if (!resolved?.capabilities.configuredUpdateWorkflow(workflow))
67767
+ return rejected(c5, {
67768
+ boardId: c5.req.param("board"),
67769
+ panelId: c5.req.param("panelId"),
67770
+ operation: "update-workflow"
67771
+ });
67772
+ return observation(
67773
+ c5,
67774
+ fetchGithubActionsUpdateWorkflow({
67637
67775
  ...resolved,
67638
67776
  workflow,
67639
67777
  requestHeaders: c5.req.raw.headers,
67640
67778
  fetcher: deps.fetcher ?? globalThis.fetch,
67641
67779
  githubClient
67642
- })
67780
+ }),
67781
+ {
67782
+ boardId: c5.req.param("board"),
67783
+ panelId: c5.req.param("panelId"),
67784
+ operation: "update-workflow",
67785
+ sourceName: resolved.panel.source,
67786
+ sourceType: resolved.source.type,
67787
+ destination: "https://api.github.com"
67788
+ }
67643
67789
  );
67644
67790
  });
67645
67791
  app.get("/api/panel/:board/:panelId/pull-request-build", async (c5) => {
@@ -67650,15 +67796,28 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67650
67796
  );
67651
67797
  const branch = c5.req.query("branch");
67652
67798
  if (!resolved || !branch || !resolved.capabilities.permitsBuildBranch(branch))
67653
- return c5.notFound();
67654
- return adapterRouteResponse(
67655
- await fetchGithubActionsPullRequestBuild({
67799
+ return rejected(c5, {
67800
+ boardId: c5.req.param("board"),
67801
+ panelId: c5.req.param("panelId"),
67802
+ operation: "pull-request-build"
67803
+ });
67804
+ return observation(
67805
+ c5,
67806
+ fetchGithubActionsPullRequestBuild({
67656
67807
  ...resolved,
67657
67808
  branch,
67658
67809
  requestHeaders: c5.req.raw.headers,
67659
67810
  fetcher: deps.fetcher ?? globalThis.fetch,
67660
67811
  githubClient
67661
- })
67812
+ }),
67813
+ {
67814
+ boardId: c5.req.param("board"),
67815
+ panelId: c5.req.param("panelId"),
67816
+ operation: "pull-request-build",
67817
+ sourceName: resolved.panel.source,
67818
+ sourceType: resolved.source.type,
67819
+ destination: "https://api.github.com"
67820
+ }
67662
67821
  );
67663
67822
  });
67664
67823
  function pullRequestPanel(boardName, panelId, operation2) {
@@ -67670,6 +67829,38 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67670
67829
  if (!capabilities) return void 0;
67671
67830
  return panel?.type === "pull-request-health" && source?.type === "github-actions" && permitsPanelOperation(allowlist, boardName, panelId, operation2) ? { panel, source, capabilities } : void 0;
67672
67831
  }
67832
+ function rejected(c5, context) {
67833
+ logger2.log({
67834
+ event: "api.operation_rejected",
67835
+ requestId: c5.get("dashboardRequestId"),
67836
+ boardId: context.boardId,
67837
+ panelId: context.panelId,
67838
+ operation: context.operation
67839
+ });
67840
+ return c5.notFound();
67841
+ }
67842
+ async function observation(c5, result, context) {
67843
+ const started = performance.now();
67844
+ const resolved = await result;
67845
+ const adapted = await adapterRouteResponse(resolved);
67846
+ if (adapted.envelope?.state === "error") {
67847
+ logger2.log({
67848
+ event: "panel.observation_failed",
67849
+ requestId: c5.get("dashboardRequestId"),
67850
+ boardId: context.boardId,
67851
+ panelId: context.panelId,
67852
+ operation: context.operation,
67853
+ errorKind: adapted.envelope.error.kind,
67854
+ elapsedMs: Math.round(performance.now() - started),
67855
+ ...context.sourceName ? { sourceName: context.sourceName } : {},
67856
+ ...context.sourceType ? { sourceType: context.sourceType } : {},
67857
+ ...destinationOrigin(context.destination) ? { destinationOrigin: destinationOrigin(context.destination) } : {},
67858
+ ...resolved.failure?.upstreamStatus ? { upstreamStatus: resolved.failure.upstreamStatus } : {},
67859
+ ...resolved.failure?.networkCode ? { networkCode: resolved.failure.networkCode } : {}
67860
+ });
67861
+ }
67862
+ return adapted.response;
67863
+ }
67673
67864
  async function renderEntrypoint(_request, board) {
67674
67865
  const template = await templates.get(config2.assetPath);
67675
67866
  const env2 = {
@@ -67777,28 +67968,35 @@ function isLocalHost(host) {
67777
67968
 
67778
67969
  // packages/server/src/startup.ts
67779
67970
  async function startup(options = {}) {
67780
- const config2 = loadConfig2();
67781
- const fetcher = options.fetcher ?? globalThis.fetch;
67782
- const [, boardConfig] = await Promise.all([
67783
- waitForTemplate(config2, fetcher),
67784
- loadBoardConfig(
67785
- config2.boardConfigUrl,
67786
- fetcher,
67787
- config2.assetPath.includes("__ASSET_PATH__") ? void 0 : schemaUrlForAssetPath(config2.assetPath)
67788
- )
67789
- ]);
67790
- const board = selectBoard(config2.board, boardConfig);
67791
- const resolvedConfig = { ...config2, board };
67792
- const credentialNames = Object.values(boardConfig.sources).flatMap(credentialEnvironmentNames);
67793
- const credentials = await createCredentialResolver({
67794
- secretReference: config2.secretReference,
67795
- credentialNames
67796
- });
67797
- warnAboutMissingAuth(resolvedConfig);
67798
- return {
67799
- app: createApp({ config: resolvedConfig, fetcher, boardConfig, credentials }),
67800
- config: resolvedConfig
67801
- };
67971
+ const logger2 = options.logger ?? consoleLogger;
67972
+ logger2.log({ event: "server.starting" });
67973
+ try {
67974
+ const config2 = loadConfig2();
67975
+ const fetcher = options.fetcher ?? globalThis.fetch;
67976
+ const [, boardConfig] = await Promise.all([
67977
+ waitForTemplate(config2, fetcher),
67978
+ loadBoardConfig(
67979
+ config2.boardConfigUrl,
67980
+ fetcher,
67981
+ config2.assetPath.includes("__ASSET_PATH__") ? void 0 : schemaUrlForAssetPath(config2.assetPath)
67982
+ )
67983
+ ]);
67984
+ const board = selectBoard(config2.board, boardConfig);
67985
+ const resolvedConfig = { ...config2, board };
67986
+ const credentialNames = Object.values(boardConfig.sources).flatMap(credentialEnvironmentNames);
67987
+ const credentials = await createCredentialResolver({
67988
+ secretReference: config2.secretReference,
67989
+ credentialNames
67990
+ });
67991
+ warnAboutMissingAuth(resolvedConfig, logger2);
67992
+ return {
67993
+ app: createApp({ config: resolvedConfig, fetcher, boardConfig, credentials, logger: logger2 }),
67994
+ config: resolvedConfig
67995
+ };
67996
+ } catch (error52) {
67997
+ logger2.log({ event: "server.startup_failed", category: startupFailureCategory(error52) });
67998
+ throw error52;
67999
+ }
67802
68000
  }
67803
68001
  function selectBoard(requested, config2) {
67804
68002
  if (requested) {
@@ -67833,14 +68031,17 @@ async function waitForTemplate(config2, fetcher) {
67833
68031
  }
67834
68032
  }
67835
68033
  var RETRY_INTERVAL_MILLIS = 250;
67836
- function warnAboutMissingAuth(config2) {
68034
+ function warnAboutMissingAuth(config2, logger2) {
67837
68035
  if (isLocalHost(config2.host)) return;
67838
- console.warn(
67839
- `
67840
- \u26A0\uFE0F No auth configured. This instance is accessible to anyone who can reach it.
67841
- Bound to ${config2.host}:${config2.port}, which is not localhost.
67842
- `
67843
- );
68036
+ logger2.log({ event: "server.no_auth_warning", host: config2.host, port: config2.port });
68037
+ }
68038
+ function startupFailureCategory(error52) {
68039
+ const message = error52 instanceof Error ? error52.message : "";
68040
+ if (message.includes("server configuration")) return "configuration";
68041
+ if (message.includes("ASSET_PATH") || message.includes("<head>")) return "template";
68042
+ if (message.includes("board") || message.includes("Board")) return "board-config";
68043
+ if (message.includes("credential") || message.includes("secret")) return "credentials";
68044
+ return "unknown";
67844
68045
  }
67845
68046
 
67846
68047
  // packages/server/src/lambda.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@continuous-excellence/ze-great-dashboard-aws",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "type": "module",
5
5
  "description": "AWS server runtime and CloudFormation deployment tooling for Ze Great Dashboard.",
6
6
  "keywords": [