@continuous-excellence/ze-great-dashboard-aws 0.18.3 → 0.19.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/lambda.mjs CHANGED
@@ -3124,7 +3124,7 @@ var require_timestamp = __commonJS({
3124
3124
  resolve: (str) => parseSexagesimal(str, false),
3125
3125
  stringify: stringifySexagesimal
3126
3126
  };
3127
- var timestamp2 = {
3127
+ var timestamp3 = {
3128
3128
  identify: (value) => value instanceof Date,
3129
3129
  default: true,
3130
3130
  tag: "tag:yaml.org,2002:timestamp",
@@ -3133,7 +3133,7 @@ var require_timestamp = __commonJS({
3133
3133
  // assumed to be 00:00:00Z (start of day, UTC).
3134
3134
  test: RegExp("^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})(?:(?:t|T|[ \\t]+)([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?)?$"),
3135
3135
  resolve(str) {
3136
- const match2 = str.match(timestamp2.test);
3136
+ const match2 = str.match(timestamp3.test);
3137
3137
  if (!match2)
3138
3138
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
3139
3139
  const [, year2, month, day, hour, minute, second] = match2.map(Number);
@@ -3152,7 +3152,7 @@ var require_timestamp = __commonJS({
3152
3152
  };
3153
3153
  exports.floatTime = floatTime;
3154
3154
  exports.intTime = intTime;
3155
- exports.timestamp = timestamp2;
3155
+ exports.timestamp = timestamp3;
3156
3156
  }
3157
3157
  });
3158
3158
 
@@ -3172,7 +3172,7 @@ var require_schema3 = __commonJS({
3172
3172
  var omap = require_omap();
3173
3173
  var pairs = require_pairs();
3174
3174
  var set2 = require_set();
3175
- var timestamp2 = require_timestamp();
3175
+ var timestamp3 = require_timestamp();
3176
3176
  var schema = [
3177
3177
  map4.map,
3178
3178
  seq.seq,
@@ -3192,9 +3192,9 @@ var require_schema3 = __commonJS({
3192
3192
  omap.omap,
3193
3193
  pairs.pairs,
3194
3194
  set2.set,
3195
- timestamp2.intTime,
3196
- timestamp2.floatTime,
3197
- timestamp2.timestamp
3195
+ timestamp3.intTime,
3196
+ timestamp3.floatTime,
3197
+ timestamp3.timestamp
3198
3198
  ];
3199
3199
  exports.schema = schema;
3200
3200
  }
@@ -3219,7 +3219,7 @@ var require_tags = __commonJS({
3219
3219
  var pairs = require_pairs();
3220
3220
  var schema$2 = require_schema3();
3221
3221
  var set2 = require_set();
3222
- var timestamp2 = require_timestamp();
3222
+ var timestamp3 = require_timestamp();
3223
3223
  var schemas = /* @__PURE__ */ new Map([
3224
3224
  ["core", schema.schema],
3225
3225
  ["failsafe", [map4.map, seq.seq, string4.string]],
@@ -3233,11 +3233,11 @@ var require_tags = __commonJS({
3233
3233
  float: float.float,
3234
3234
  floatExp: float.floatExp,
3235
3235
  floatNaN: float.floatNaN,
3236
- floatTime: timestamp2.floatTime,
3236
+ floatTime: timestamp3.floatTime,
3237
3237
  int: int2.int,
3238
3238
  intHex: int2.intHex,
3239
3239
  intOct: int2.intOct,
3240
- intTime: timestamp2.intTime,
3240
+ intTime: timestamp3.intTime,
3241
3241
  map: map4.map,
3242
3242
  merge: merge3.merge,
3243
3243
  null: _null4.nullTag,
@@ -3245,7 +3245,7 @@ var require_tags = __commonJS({
3245
3245
  pairs: pairs.pairs,
3246
3246
  seq: seq.seq,
3247
3247
  set: set2.set,
3248
- timestamp: timestamp2.timestamp
3248
+ timestamp: timestamp3.timestamp
3249
3249
  };
3250
3250
  var coreKnownTags = {
3251
3251
  "tag:yaml.org,2002:binary": binary.binary,
@@ -3253,7 +3253,7 @@ var require_tags = __commonJS({
3253
3253
  "tag:yaml.org,2002:omap": omap.omap,
3254
3254
  "tag:yaml.org,2002:pairs": pairs.pairs,
3255
3255
  "tag:yaml.org,2002:set": set2.set,
3256
- "tag:yaml.org,2002:timestamp": timestamp2.timestamp
3256
+ "tag:yaml.org,2002:timestamp": timestamp3.timestamp
3257
3257
  };
3258
3258
  function getTags(customTags, schemaName, addMergeTag) {
3259
3259
  const schemaTags = schemas.get(schemaName);
@@ -10643,12 +10643,12 @@ var init_schema_date_utils = __esm({
10643
10643
  [, month, day, hour, minute, second, fraction, year2] = matches;
10644
10644
  }
10645
10645
  if (year2 && second) {
10646
- const timestamp2 = Date.UTC(Number(year2), months.indexOf(month), Number(day), Number(hour), Number(minute), Number(second), fraction ? Math.round(parseFloat(`0.${fraction}`) * 1e3) : 0);
10646
+ const timestamp3 = Date.UTC(Number(year2), months.indexOf(month), Number(day), Number(hour), Number(minute), Number(second), fraction ? Math.round(parseFloat(`0.${fraction}`) * 1e3) : 0);
10647
10647
  range(day, 1, 31);
10648
10648
  range(hour, 0, 23);
10649
10649
  range(minute, 0, 59);
10650
10650
  range(second, 0, 60);
10651
- const date6 = new Date(timestamp2);
10651
+ const date6 = new Date(timestamp3);
10652
10652
  date6.setUTCFullYear(Number(year2));
10653
10653
  return date6;
10654
10654
  }
@@ -17638,14 +17638,14 @@ var init_DefaultRateLimiter = __esm({
17638
17638
  this.availableTokens = this.availableTokens - amount;
17639
17639
  }
17640
17640
  refillTokenBucket() {
17641
- const timestamp2 = this.getCurrentTimeInSeconds();
17641
+ const timestamp3 = this.getCurrentTimeInSeconds();
17642
17642
  if (!this.lastTimestamp) {
17643
- this.lastTimestamp = timestamp2;
17643
+ this.lastTimestamp = timestamp3;
17644
17644
  return;
17645
17645
  }
17646
- const fillAmount = (timestamp2 - this.lastTimestamp) * this.fillRate;
17646
+ const fillAmount = (timestamp3 - this.lastTimestamp) * this.fillRate;
17647
17647
  this.availableTokens = Math.min(this.maxCapacity, this.availableTokens + fillAmount);
17648
- this.lastTimestamp = timestamp2;
17648
+ this.lastTimestamp = timestamp3;
17649
17649
  }
17650
17650
  calculateTimeWindow() {
17651
17651
  this.timeWindow = this.getPrecise(Math.pow(this.lastMaxRate * (1 - this.beta) / this.scaleConstant, 1 / 3));
@@ -17653,8 +17653,8 @@ var init_DefaultRateLimiter = __esm({
17653
17653
  cubicThrottle(rateToUse) {
17654
17654
  return this.getPrecise(rateToUse * this.beta);
17655
17655
  }
17656
- cubicSuccess(timestamp2) {
17657
- return this.getPrecise(this.scaleConstant * Math.pow(timestamp2 - this.lastThrottleTime - this.timeWindow, 3) + this.lastMaxRate);
17656
+ cubicSuccess(timestamp3) {
17657
+ return this.getPrecise(this.scaleConstant * Math.pow(timestamp3 - this.lastThrottleTime - this.timeWindow, 3) + this.lastMaxRate);
17658
17658
  }
17659
17659
  enableTokenBucket() {
17660
17660
  this.enabled = true;
@@ -49137,8 +49137,8 @@ var handle = (app, { isContentTypeBinary } = { isContentTypeBinary: void 0 }) =>
49137
49137
  requestContext = getRequestContext(event);
49138
49138
  } catch (error52) {
49139
49139
  console.error("Error processing request:", error52);
49140
- const errorResponse2 = error52 instanceof TypeError ? new Response("Invalid request", { status: 400 }) : new Response("Internal Server Error", { status: 500 });
49141
- return processor.createResult(event, errorResponse2, { isContentTypeBinary });
49140
+ const errorResponse3 = error52 instanceof TypeError ? new Response("Invalid request", { status: 400 }) : new Response("Internal Server Error", { status: 500 });
49141
+ return processor.createResult(event, errorResponse3, { isContentTypeBinary });
49142
49142
  }
49143
49143
  const res = await app.fetch(req, {
49144
49144
  event,
@@ -64040,14 +64040,25 @@ var githubActionsSourceSchema = external_exports.looseObject({
64040
64040
  branch: external_exports.string().min(1).optional(),
64041
64041
  ...sourceAuthenticationSchema
64042
64042
  }).superRefine(exactlyOneGithubAuthenticationMode);
64043
+ var azureDevOpsSourceSchema = external_exports.looseObject({
64044
+ type: external_exports.literal("azure-devops"),
64045
+ /** Azure DevOps organization name, used as one URL path segment. */
64046
+ organization: external_exports.string().regex(/^[^/\s]+$/, "must be an organization name"),
64047
+ project: external_exports.string().min(1),
64048
+ /** The branch whose build health the dashboard represents. */
64049
+ branch: external_exports.string().min(1).optional(),
64050
+ /** Azure DevOps Build API access is authenticated with a read-scoped PAT. */
64051
+ token_env: external_exports.string().min(1)
64052
+ });
64043
64053
  var sourceSchema = external_exports.looseObject({ type: external_exports.string().min(1), ...sourceAuthenticationSchema }).superRefine(exactlyOneGithubAuthenticationMode);
64044
64054
  function credentialEnvironmentNames(source) {
64055
+ const githubApp = githubAppSchema.safeParse(source.github_app);
64045
64056
  return [
64046
64057
  ...source.token_env ? [source.token_env] : [],
64047
- ...source.github_app ? [
64048
- source.github_app.app_id_env,
64049
- source.github_app.private_key_env,
64050
- source.github_app.installation_id_env
64058
+ ...githubApp.success ? [
64059
+ githubApp.data.app_id_env,
64060
+ githubApp.data.private_key_env,
64061
+ githubApp.data.installation_id_env
64051
64062
  ] : []
64052
64063
  ];
64053
64064
  }
@@ -64085,6 +64096,33 @@ var boardConfigSchema = external_exports.object({
64085
64096
  sources: external_exports.record(external_exports.string().min(1), sourceSchema).default({}),
64086
64097
  boards: external_exports.record(external_exports.string().min(1), boardSchema),
64087
64098
  auth: authSchema.optional()
64099
+ }).superRefine((config2, ctx) => {
64100
+ for (const [sourceName, source] of Object.entries(config2.sources)) {
64101
+ if (source.type !== "azure-devops") continue;
64102
+ const parsedSource = azureDevOpsSourceSchema.safeParse(source);
64103
+ if (!parsedSource.success) {
64104
+ for (const issue2 of parsedSource.error.issues) {
64105
+ ctx.addIssue({
64106
+ code: "custom",
64107
+ path: ["sources", sourceName, ...issue2.path],
64108
+ message: issue2.message
64109
+ });
64110
+ }
64111
+ }
64112
+ }
64113
+ for (const [boardName, board] of Object.entries(config2.boards)) {
64114
+ board.panels.forEach((panel, panelIndex) => {
64115
+ const source = panel.source ? config2.sources[panel.source] : void 0;
64116
+ if (panel.type !== "pipeline-status" || source?.type !== "azure-devops") return;
64117
+ if (typeof panel.pipeline !== "number" || !Number.isInteger(panel.pipeline) || panel.pipeline <= 0) {
64118
+ ctx.addIssue({
64119
+ code: "custom",
64120
+ path: ["boards", boardName, "panels", panelIndex, "pipeline"],
64121
+ message: "Azure DevOps pipeline-status panels require a positive numeric pipeline definition id"
64122
+ });
64123
+ }
64124
+ });
64125
+ }
64088
64126
  });
64089
64127
 
64090
64128
  // packages/shared/src/board-config-modeline.ts
@@ -64129,15 +64167,13 @@ var clientEnvSchema = external_exports.object({
64129
64167
  proxyPath: external_exports.string().min(1),
64130
64168
  /** Which board this is, resolved server-side so the client parses no URLs. */
64131
64169
  board: external_exports.string().min(1),
64132
- /** The client version being served, so two published versions are visibly distinguishable. */
64133
- clientVersion: external_exports.string().min(1),
64134
64170
  /** Omitted entirely when unauthenticated — presence is what turns auth on. */
64135
64171
  auth: external_exports.object({
64136
64172
  issuer: external_exports.string().min(1),
64137
64173
  clientId: external_exports.string().min(1).optional()
64138
64174
  }).optional()
64139
64175
  });
64140
- var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true, clientVersion: true });
64176
+ var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true });
64141
64177
 
64142
64178
  // packages/shared/src/envelope.ts
64143
64179
  var errorKindSchema = external_exports.enum([
@@ -66400,6 +66436,275 @@ var Hono2 = class extends Hono {
66400
66436
  // packages/server/src/app.ts
66401
66437
  var import_yaml = __toESM(require_dist(), 1);
66402
66438
 
66439
+ // packages/server/src/upstream.ts
66440
+ function forwardValidators(requestHeaders, upstreamHeaders) {
66441
+ for (const name of ["if-none-match", "if-modified-since"]) {
66442
+ const value = requestHeaders.get(name);
66443
+ if (value) upstreamHeaders.set(name, value);
66444
+ }
66445
+ }
66446
+ function observedAt(upstreamDate) {
66447
+ const parsed = upstreamDate ? new Date(upstreamDate) : void 0;
66448
+ return parsed && !Number.isNaN(parsed.valueOf()) ? parsed.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
66449
+ }
66450
+ function upstreamErrorEnvelope(args) {
66451
+ return {
66452
+ panelId: args.panelId,
66453
+ state: "error",
66454
+ observedAt: observedAt(args.upstreamDate),
66455
+ link: args.link,
66456
+ error: {
66457
+ kind: args.kind,
66458
+ message: args.error instanceof Error ? args.error.message : String(args.error)
66459
+ }
66460
+ };
66461
+ }
66462
+ function upstreamErrorResponse(envelope) {
66463
+ return new Response(JSON.stringify(envelope), { status: 200 });
66464
+ }
66465
+ function upstreamErrorKind(status) {
66466
+ if (status === 401 || status === 403) return "unauthorized";
66467
+ if (status === 404) return "not-found";
66468
+ return "upstream-error";
66469
+ }
66470
+ async function adapterRouteResponse(result) {
66471
+ const headers = passthroughHeaders(result.response.headers);
66472
+ if (result.response.status === 304) return new Response(null, { status: 304, headers });
66473
+ const envelope = result.envelope ?? JSON.parse(await result.response.text());
66474
+ headers.set("content-type", "application/json; charset=utf-8");
66475
+ return new Response(JSON.stringify(envelope), { status: 200, headers });
66476
+ }
66477
+ function passthroughHeaders(upstream) {
66478
+ const headers = new Headers();
66479
+ for (const name of ["cache-control", "etag", "last-modified", "date", "vary"]) {
66480
+ const value = upstream.get(name);
66481
+ if (value) headers.set(name, value);
66482
+ }
66483
+ return headers;
66484
+ }
66485
+
66486
+ // packages/server/src/adapters/azure-devops.ts
66487
+ var pipelinePanelSchema = external_exports.object({
66488
+ id: external_exports.string().min(1),
66489
+ type: external_exports.literal("pipeline-status"),
66490
+ source: external_exports.string().min(1),
66491
+ pipeline: external_exports.number().int().positive()
66492
+ });
66493
+ var buildSchema = external_exports.object({
66494
+ id: external_exports.number().int().positive(),
66495
+ buildNumber: external_exports.string().min(1),
66496
+ status: external_exports.string().nullable().optional(),
66497
+ result: external_exports.string().nullable().optional(),
66498
+ sourceBranch: external_exports.string().min(1).nullable().optional(),
66499
+ startTime: external_exports.string().nullable().optional(),
66500
+ finishTime: external_exports.string().nullable().optional(),
66501
+ lastChangedDate: external_exports.string().nullable().optional()
66502
+ });
66503
+ var buildsSchema = external_exports.object({ value: external_exports.array(buildSchema) });
66504
+ var timelineSchema = external_exports.object({
66505
+ records: external_exports.array(
66506
+ external_exports.object({
66507
+ id: external_exports.string().min(1),
66508
+ parentId: external_exports.string().min(1).nullable().optional(),
66509
+ type: external_exports.string().min(1),
66510
+ name: external_exports.string().min(1),
66511
+ state: external_exports.string().nullable().optional()
66512
+ })
66513
+ )
66514
+ });
66515
+ function permittedAzureDevOpsCalls(panel, source) {
66516
+ const parsedPanel = pipelinePanelSchema.parse(panel);
66517
+ const parsedSource = azureDevOpsSourceSchema.parse(source);
66518
+ return [buildsCall(parsedPanel.pipeline, parsedSource)];
66519
+ }
66520
+ async function fetchAzureDevOpsPipeline(args) {
66521
+ const panel = pipelinePanelSchema.parse(args.panel);
66522
+ const source = azureDevOpsSourceSchema.parse(args.source);
66523
+ const token = args.credentials.get(source.token_env);
66524
+ if (!token) {
66525
+ return {
66526
+ response: errorResponse(
66527
+ errorEnvelope(
66528
+ panel.id,
66529
+ "unauthorized",
66530
+ `Credential ${source.token_env} is not available.`,
66531
+ sourceLink(panel.pipeline, source)
66532
+ )
66533
+ )
66534
+ };
66535
+ }
66536
+ const call = buildsCall(panel.pipeline, source);
66537
+ copyValidators(args.requestHeaders, call.headers);
66538
+ call.headers.set("authorization", `Basic ${btoa(`:${token}`)}`);
66539
+ let upstream;
66540
+ try {
66541
+ upstream = await args.fetcher(call.url, { headers: call.headers });
66542
+ } catch (error52) {
66543
+ return {
66544
+ response: errorResponse(
66545
+ errorEnvelope(panel.id, "unreachable", error52, sourceLink(panel.pipeline, source))
66546
+ )
66547
+ };
66548
+ }
66549
+ if (upstream.status === 304) return { response: upstream };
66550
+ if (!upstream.ok) {
66551
+ return {
66552
+ response: errorResponse(
66553
+ errorEnvelope(
66554
+ panel.id,
66555
+ errorKind(upstream.status),
66556
+ `${upstream.status} ${upstream.statusText}`,
66557
+ sourceLink(panel.pipeline, source),
66558
+ observedAt2(upstream.headers.get("date"))
66559
+ )
66560
+ )
66561
+ };
66562
+ }
66563
+ try {
66564
+ const build = buildsSchema.parse(await upstream.json()).value[0];
66565
+ if (!build) {
66566
+ return {
66567
+ response: errorResponse(
66568
+ errorEnvelope(
66569
+ panel.id,
66570
+ "no-runs",
66571
+ source.branch ? `No builds found for branch "${source.branch}". Check the source's branch setting.` : "No builds found.",
66572
+ sourceLink(panel.pipeline, source),
66573
+ observedAt2(upstream.headers.get("date"))
66574
+ )
66575
+ )
66576
+ };
66577
+ }
66578
+ const signal = {
66579
+ type: "pipeline-status",
66580
+ status: normalizeStatus(build.status, build.result),
66581
+ rawStatus: build.result ?? build.status ?? "unknown",
66582
+ name: build.buildNumber,
66583
+ ...build.sourceBranch ?? source.branch ? { branch: build.sourceBranch ?? source.branch } : {},
66584
+ ...timestamp(build.startTime) ? { runStartedAt: timestamp(build.startTime) } : {},
66585
+ ...timestamp(build.lastChangedDate) ? { sourceUpdatedAt: timestamp(build.lastChangedDate) } : {},
66586
+ sourceRunId: String(build.id),
66587
+ ...completedBuildDuration(build)
66588
+ };
66589
+ if (signal.status === "running") {
66590
+ const activity = await activeAzureDevOpsActivity({
66591
+ buildId: build.id,
66592
+ source,
66593
+ fetcher: args.fetcher,
66594
+ authorization: call.headers.get("authorization") ?? ""
66595
+ });
66596
+ if (activity) signal.activity = activity;
66597
+ }
66598
+ return {
66599
+ envelope: {
66600
+ panelId: panel.id,
66601
+ state: "ok",
66602
+ observedAt: observedAt2(upstream.headers.get("date")),
66603
+ link: buildLink(build.id, source),
66604
+ signal
66605
+ },
66606
+ response: upstream
66607
+ };
66608
+ } catch (error52) {
66609
+ return {
66610
+ response: errorResponse(
66611
+ errorEnvelope(
66612
+ panel.id,
66613
+ "upstream-error",
66614
+ error52,
66615
+ sourceLink(panel.pipeline, source),
66616
+ observedAt2(upstream.headers.get("date"))
66617
+ )
66618
+ )
66619
+ };
66620
+ }
66621
+ }
66622
+ function buildsCall(definitionId, source) {
66623
+ const url2 = apiUrl(source, "build/builds");
66624
+ url2.searchParams.set("definitions", String(definitionId));
66625
+ if (source.branch) url2.searchParams.set("branchName", adoBranch(source.branch));
66626
+ url2.searchParams.set("queryOrder", "queueTimeDescending");
66627
+ url2.searchParams.set("$top", "1");
66628
+ return { url: url2.toString(), headers: new Headers({ accept: "application/json" }) };
66629
+ }
66630
+ function timelineCall(buildId, source) {
66631
+ return {
66632
+ url: apiUrl(source, `build/builds/${buildId}/timeline`).toString(),
66633
+ headers: new Headers({ accept: "application/json" })
66634
+ };
66635
+ }
66636
+ function apiUrl(source, suffix) {
66637
+ const url2 = new URL(
66638
+ `https://dev.azure.com/${encodeURIComponent(source.organization)}/${encodeURIComponent(source.project)}/_apis/${suffix}`
66639
+ );
66640
+ url2.searchParams.set("api-version", "7.1");
66641
+ return url2;
66642
+ }
66643
+ function adoBranch(branch) {
66644
+ return branch.startsWith("refs/") ? branch : `refs/heads/${branch}`;
66645
+ }
66646
+ async function activeAzureDevOpsActivity(args) {
66647
+ const call = timelineCall(args.buildId, args.source);
66648
+ call.headers.set("authorization", args.authorization);
66649
+ try {
66650
+ const response = await args.fetcher(call.url, { headers: call.headers });
66651
+ if (!response.ok) return void 0;
66652
+ const records = timelineSchema.parse(await response.json()).records;
66653
+ const active = records.find((record2) => record2.state === "inProgress");
66654
+ if (!active) return void 0;
66655
+ const parent = active.parentId ? records.find((record2) => record2.id === active.parentId) : void 0;
66656
+ const kind = active.type.toLowerCase();
66657
+ return {
66658
+ kind: kind === "stage" ? "stage" : kind === "job" ? "job" : "step",
66659
+ name: active.name,
66660
+ ...parent ? { parent: parent.name } : {}
66661
+ };
66662
+ } catch {
66663
+ return void 0;
66664
+ }
66665
+ }
66666
+ function normalizeStatus(status, result) {
66667
+ if (status === "notStarted" || status === "postponed" || status === "inProgress" || status === "cancelling")
66668
+ return "running";
66669
+ if (status !== "completed") return "unknown";
66670
+ if (result === "succeeded") return "passed";
66671
+ if (result === "failed") return "failed";
66672
+ if (result === "canceled") return "cancelled";
66673
+ return "unknown";
66674
+ }
66675
+ function completedBuildDuration(build) {
66676
+ const startedAt = timestamp(build.startTime);
66677
+ const finishedAt = timestamp(build.finishTime);
66678
+ if (!startedAt || !finishedAt) return {};
66679
+ const durationMs = new Date(finishedAt).valueOf() - new Date(startedAt).valueOf();
66680
+ return Number.isFinite(durationMs) && durationMs >= 0 ? { durationMs } : {};
66681
+ }
66682
+ function timestamp(value) {
66683
+ if (!value || !external_exports.iso.datetime().safeParse(value).success) return void 0;
66684
+ return Number.isFinite(new Date(value).valueOf()) ? value : void 0;
66685
+ }
66686
+ function copyValidators(requestHeaders, upstreamHeaders) {
66687
+ forwardValidators(requestHeaders, upstreamHeaders);
66688
+ }
66689
+ function buildLink(buildId, source) {
66690
+ return `https://dev.azure.com/${encodeURIComponent(source.organization)}/${encodeURIComponent(source.project)}/_build/results?buildId=${buildId}`;
66691
+ }
66692
+ function sourceLink(definitionId, source) {
66693
+ return `https://dev.azure.com/${encodeURIComponent(source.organization)}/${encodeURIComponent(source.project)}/_build?definitionId=${definitionId}`;
66694
+ }
66695
+ function errorResponse(envelope) {
66696
+ return upstreamErrorResponse(envelope);
66697
+ }
66698
+ function errorEnvelope(panelId, kind, error52, link, date6) {
66699
+ return upstreamErrorEnvelope({ panelId, kind, error: error52, link, upstreamDate: date6 });
66700
+ }
66701
+ function errorKind(status) {
66702
+ return upstreamErrorKind(status);
66703
+ }
66704
+ function observedAt2(value) {
66705
+ return observedAt(value);
66706
+ }
66707
+
66403
66708
  // packages/server/src/credentials.ts
66404
66709
  var import_client_secrets_manager = __toESM(require_dist_cjs16(), 1);
66405
66710
  var import_client_ssm = __toESM(require_dist_cjs17(), 1);
@@ -66509,10 +66814,7 @@ function createGithubClient(credentials, now = Date.now) {
66509
66814
  }
66510
66815
  function githubHeaders(requestHeaders) {
66511
66816
  const headers = new Headers({ accept: "application/vnd.github+json" });
66512
- for (const name of ["if-none-match", "if-modified-since"]) {
66513
- const value = requestHeaders.get(name);
66514
- if (value) headers.set(name, value);
66515
- }
66817
+ forwardValidators(requestHeaders, headers);
66516
66818
  return headers;
66517
66819
  }
66518
66820
  async function exchangeInstallationToken(args) {
@@ -66565,7 +66867,7 @@ function base64url3(value) {
66565
66867
  }
66566
66868
 
66567
66869
  // packages/server/src/adapters/github-actions.ts
66568
- var pipelinePanelSchema = external_exports.object({
66870
+ var pipelinePanelSchema2 = external_exports.object({
66569
66871
  id: external_exports.string().min(1),
66570
66872
  type: external_exports.literal("pipeline-status"),
66571
66873
  source: external_exports.string().min(1),
@@ -66616,7 +66918,7 @@ var pullRequestsSchema = external_exports.array(
66616
66918
  })
66617
66919
  );
66618
66920
  function permittedGithubActionsCalls(panel, source, _credentials) {
66619
- const parsedPanel = pipelinePanelSchema.parse(panel);
66921
+ const parsedPanel = pipelinePanelSchema2.parse(panel);
66620
66922
  const parsedSource = githubActionsSourceSchema.parse(source);
66621
66923
  const url2 = new URL(
66622
66924
  `https://api.github.com/repos/${parsedSource.repo}/actions/workflows/${encodeURIComponent(parsedPanel.pipeline)}/runs`
@@ -66706,7 +67008,7 @@ async function fetchGithubActionsPullRequestHealth(args) {
66706
67008
  return {
66707
67009
  response: new Response(
66708
67010
  JSON.stringify(
66709
- errorEnvelope(
67011
+ errorEnvelope2(
66710
67012
  panel.id,
66711
67013
  error52 instanceof GithubAuthenticationError ? "unauthorized" : "upstream-error",
66712
67014
  error52,
@@ -66767,7 +67069,7 @@ function githubRunsCall(source, workflow, branch, event, perPage = 1) {
66767
67069
  function healthItem(label, run, detail, link) {
66768
67070
  return {
66769
67071
  label,
66770
- status: run ? normalizeStatus(run.status, run.conclusion) : "unknown",
67072
+ status: run ? normalizeStatus2(run.status, run.conclusion) : "unknown",
66771
67073
  detail: run ? `${detail} \xB7 ${run.conclusion ?? run.status}` : `${detail} \xB7 No run found`,
66772
67074
  link: link ?? run?.html_url ?? null
66773
67075
  };
@@ -66803,11 +67105,11 @@ async function fetchGithubActionsPipeline(args) {
66803
67105
  return {
66804
67106
  response: new Response(
66805
67107
  JSON.stringify(
66806
- errorEnvelope(
67108
+ errorEnvelope2(
66807
67109
  args.panel.id,
66808
67110
  error52 instanceof GithubAuthenticationError ? "unauthorized" : "unreachable",
66809
67111
  error52,
66810
- sourceLink(args.panel, args.source)
67112
+ sourceLink2(args.panel, args.source)
66811
67113
  )
66812
67114
  ),
66813
67115
  { status: 200 }
@@ -66819,12 +67121,12 @@ async function fetchGithubActionsPipeline(args) {
66819
67121
  return {
66820
67122
  response: new Response(
66821
67123
  JSON.stringify(
66822
- errorEnvelope(
67124
+ errorEnvelope2(
66823
67125
  args.panel.id,
66824
- errorKind(upstream.status),
67126
+ errorKind2(upstream.status),
66825
67127
  `${upstream.status} ${upstream.statusText}`,
66826
- sourceLink(args.panel, args.source),
66827
- observedAt(upstream.headers.get("date"))
67128
+ sourceLink2(args.panel, args.source),
67129
+ observedAt3(upstream.headers.get("date"))
66828
67130
  )
66829
67131
  ),
66830
67132
  { status: 200 }
@@ -66838,12 +67140,12 @@ async function fetchGithubActionsPipeline(args) {
66838
67140
  return {
66839
67141
  response: new Response(
66840
67142
  JSON.stringify(
66841
- errorEnvelope(
67143
+ errorEnvelope2(
66842
67144
  args.panel.id,
66843
67145
  "no-runs",
66844
67146
  parsedSource.branch ? `No workflow runs found for branch "${parsedSource.branch}". Check the source's branch setting.` : "No workflow runs found.",
66845
- sourceLink(args.panel, args.source),
66846
- observedAt(upstream.headers.get("date"))
67147
+ sourceLink2(args.panel, args.source),
67148
+ observedAt3(upstream.headers.get("date"))
66847
67149
  )
66848
67150
  ),
66849
67151
  { status: 200 }
@@ -66852,12 +67154,12 @@ async function fetchGithubActionsPipeline(args) {
66852
67154
  }
66853
67155
  const signal = {
66854
67156
  type: "pipeline-status",
66855
- status: normalizeStatus(run.status, run.conclusion),
67157
+ status: normalizeStatus2(run.status, run.conclusion),
66856
67158
  rawStatus: run.conclusion ?? run.status,
66857
67159
  name: run.name,
66858
67160
  ...run.head_branch ?? parsedSource.branch ? { branch: run.head_branch ?? parsedSource.branch } : {},
66859
- ...timestamp(run.updated_at) ? { sourceUpdatedAt: timestamp(run.updated_at) } : {},
66860
- ...timestamp(run.run_started_at) ? { runStartedAt: timestamp(run.run_started_at) } : {},
67161
+ ...timestamp2(run.updated_at) ? { sourceUpdatedAt: timestamp2(run.updated_at) } : {},
67162
+ ...timestamp2(run.run_started_at) ? { runStartedAt: timestamp2(run.run_started_at) } : {},
66861
67163
  ...run.id !== void 0 ? { sourceRunId: String(run.id) } : {},
66862
67164
  ...run.status === "completed" ? completedRunDuration(run) : {}
66863
67165
  };
@@ -66874,7 +67176,7 @@ async function fetchGithubActionsPipeline(args) {
66874
67176
  const envelope = {
66875
67177
  panelId: args.panel.id,
66876
67178
  state: "ok",
66877
- observedAt: observedAt(upstream.headers.get("date")),
67179
+ observedAt: observedAt3(upstream.headers.get("date")),
66878
67180
  link: run.html_url,
66879
67181
  signal
66880
67182
  };
@@ -66883,12 +67185,12 @@ async function fetchGithubActionsPipeline(args) {
66883
67185
  return {
66884
67186
  response: new Response(
66885
67187
  JSON.stringify(
66886
- errorEnvelope(
67188
+ errorEnvelope2(
66887
67189
  args.panel.id,
66888
67190
  "upstream-error",
66889
67191
  error52,
66890
- sourceLink(args.panel, args.source),
66891
- observedAt(upstream.headers.get("date"))
67192
+ sourceLink2(args.panel, args.source),
67193
+ observedAt3(upstream.headers.get("date"))
66892
67194
  )
66893
67195
  ),
66894
67196
  { status: 200 }
@@ -66927,45 +67229,36 @@ function githubJobsCall(source, runId) {
66927
67229
  return { url: url2.toString(), headers: new Headers() };
66928
67230
  }
66929
67231
  function completedRunDuration(run) {
66930
- const startedAt = timestamp(run.run_started_at);
66931
- const updatedAt = timestamp(run.updated_at);
67232
+ const startedAt = timestamp2(run.run_started_at);
67233
+ const updatedAt = timestamp2(run.updated_at);
66932
67234
  if (!startedAt || !updatedAt) return {};
66933
67235
  const durationMs = new Date(updatedAt).valueOf() - new Date(startedAt).valueOf();
66934
67236
  return Number.isFinite(durationMs) && durationMs >= 0 ? { durationMs } : {};
66935
67237
  }
66936
- function timestamp(value) {
67238
+ function timestamp2(value) {
66937
67239
  if (!value || !external_exports.iso.datetime().safeParse(value).success) return void 0;
66938
67240
  return Number.isFinite(new Date(value).valueOf()) ? value : void 0;
66939
67241
  }
66940
- function normalizeStatus(status, conclusion) {
67242
+ function normalizeStatus2(status, conclusion) {
66941
67243
  if (status !== "completed") return "running";
66942
67244
  if (conclusion === "success") return "passed";
66943
67245
  if (conclusion === "failure" || conclusion === "timed_out") return "failed";
66944
67246
  if (conclusion === "cancelled") return "cancelled";
66945
67247
  return "unknown";
66946
67248
  }
66947
- function errorKind(status) {
66948
- if (status === 401 || status === 403) return "unauthorized";
66949
- if (status === 404) return "not-found";
66950
- return "upstream-error";
67249
+ function errorKind2(status) {
67250
+ return upstreamErrorKind(status);
66951
67251
  }
66952
- function errorEnvelope(panelId, kind, error52, link, observedAt3 = (/* @__PURE__ */ new Date()).toISOString()) {
66953
- return {
66954
- panelId,
66955
- state: "error",
66956
- observedAt: observedAt3,
66957
- link,
66958
- error: { kind, message: error52 instanceof Error ? error52.message : String(error52) }
66959
- };
67252
+ function errorEnvelope2(panelId, kind, error52, link, observedAt4 = (/* @__PURE__ */ new Date()).toISOString()) {
67253
+ return upstreamErrorEnvelope({ panelId, kind, error: error52, link, upstreamDate: observedAt4 });
66960
67254
  }
66961
- function sourceLink(panel, source) {
66962
- const parsedPanel = pipelinePanelSchema.parse(panel);
67255
+ function sourceLink2(panel, source) {
67256
+ const parsedPanel = pipelinePanelSchema2.parse(panel);
66963
67257
  const parsedSource = githubActionsSourceSchema.parse(source);
66964
67258
  return `https://github.com/${parsedSource.repo}/actions/workflows/${encodeURIComponent(parsedPanel.pipeline)}`;
66965
67259
  }
66966
- function observedAt(value) {
66967
- const parsed = value ? new Date(value) : void 0;
66968
- return parsed && !Number.isNaN(parsed.valueOf()) ? parsed.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
67260
+ function observedAt3(value) {
67261
+ return observedAt(value);
66969
67262
  }
66970
67263
 
66971
67264
  // packages/server/src/adapters/http-value.ts
@@ -66988,22 +67281,21 @@ async function fetchHttpValue(args) {
66988
67281
  const panel = httpValuePanelSchema.parse(args.panel);
66989
67282
  const [call] = permittedHttpValueCalls(args.panel);
66990
67283
  if (!call) throw new Error("http-value adapter declared no permitted call");
66991
- for (const name of ["if-none-match", "if-modified-since"]) {
66992
- const value = args.requestHeaders.get(name);
66993
- if (value) call.headers.set(name, value);
66994
- }
67284
+ forwardValidators(args.requestHeaders, call.headers);
66995
67285
  let upstream;
66996
67286
  try {
66997
67287
  upstream = await args.fetcher(call.url, { headers: call.headers });
66998
67288
  } catch (error52) {
66999
- return { response: errorResponse(errorEnvelope2(panel.id, "unreachable", error52, panel)) };
67289
+ return { response: errorResponse2("unreachable", error52, panel) };
67000
67290
  }
67001
67291
  if (upstream.status === 304) return { response: upstream };
67002
67292
  if (!upstream.ok) {
67003
- const kind = upstream.status === 401 || upstream.status === 403 ? "unauthorized" : upstream.status === 404 ? "not-found" : "upstream-error";
67004
67293
  return {
67005
- response: errorResponse(
67006
- errorEnvelope2(panel.id, kind, `${upstream.status} ${upstream.statusText}`, panel, upstream)
67294
+ response: errorResponse2(
67295
+ upstreamErrorKind(upstream.status),
67296
+ `${upstream.status} ${upstream.statusText}`,
67297
+ panel,
67298
+ upstream
67007
67299
  )
67008
67300
  };
67009
67301
  }
@@ -67015,7 +67307,7 @@ async function fetchHttpValue(args) {
67015
67307
  envelope: {
67016
67308
  panelId: panel.id,
67017
67309
  state: "ok",
67018
- observedAt: observedAt2(upstream.headers.get("date")),
67310
+ observedAt: observedAt(upstream.headers.get("date")),
67019
67311
  link: panel.link ?? panel.url,
67020
67312
  signal
67021
67313
  },
@@ -67023,7 +67315,7 @@ async function fetchHttpValue(args) {
67023
67315
  };
67024
67316
  } catch (error52) {
67025
67317
  return {
67026
- response: errorResponse(errorEnvelope2(panel.id, "upstream-error", error52, panel, upstream))
67318
+ response: errorResponse2("upstream-error", error52, panel, upstream)
67027
67319
  };
67028
67320
  }
67029
67321
  }
@@ -67050,21 +67342,16 @@ function extractJsonPath(value, path) {
67050
67342
  }
67051
67343
  return current;
67052
67344
  }
67053
- function errorResponse(envelope) {
67054
- return new Response(JSON.stringify(envelope), { status: 200 });
67055
- }
67056
- function errorEnvelope2(panelId, kind, error52, panel, response) {
67057
- return {
67058
- panelId,
67059
- state: "error",
67060
- observedAt: observedAt2(response?.headers.get("date") ?? null),
67061
- link: panel.link ?? panel.url,
67062
- error: { kind, message: error52 instanceof Error ? error52.message : String(error52) }
67063
- };
67064
- }
67065
- function observedAt2(value) {
67066
- const parsed = value ? new Date(value) : void 0;
67067
- return parsed && !Number.isNaN(parsed.valueOf()) ? parsed.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
67345
+ function errorResponse2(kind, error52, panel, response) {
67346
+ return upstreamErrorResponse(
67347
+ upstreamErrorEnvelope({
67348
+ panelId: panel.id,
67349
+ kind,
67350
+ error: error52,
67351
+ link: panel.link ?? panel.url,
67352
+ upstreamDate: response?.headers.get("date")
67353
+ })
67354
+ );
67068
67355
  }
67069
67356
 
67070
67357
  // packages/server/src/allowlist.ts
@@ -67083,6 +67370,9 @@ function callsFor(panel, source) {
67083
67370
  if (panel.type === "pipeline-status" && source?.type === "github-actions") {
67084
67371
  return permittedGithubActionsCalls(panel, source);
67085
67372
  }
67373
+ if (panel.type === "pipeline-status" && source?.type === "azure-devops") {
67374
+ return permittedAzureDevOpsCalls(panel, source);
67375
+ }
67086
67376
  if (panel.type === "pull-request-health" && source?.type === "github-actions") {
67087
67377
  return permittedGithubActionsPullRequestHealthCalls(panel, source);
67088
67378
  }
@@ -67169,7 +67459,6 @@ function serializeForScriptTag(value) {
67169
67459
  function createApp(deps) {
67170
67460
  const { config: config2 } = deps;
67171
67461
  const templates = new TemplateCache(deps.fetcher ?? globalThis.fetch);
67172
- const clientVersion = deps.clientVersion ?? deriveVersionLabel(config2.assetPath);
67173
67462
  const selectedBoard = config2.board ?? Object.keys(deps.boardConfig?.boards ?? {})[0] ?? "ze-great-team";
67174
67463
  const allowlist = deps.boardConfig ? deriveAllowlist(deps.boardConfig) : /* @__PURE__ */ new Map();
67175
67464
  const credentials = deps.credentials ?? environmentCredentials();
@@ -67178,7 +67467,7 @@ function createApp(deps) {
67178
67467
  app.get("/health", (c5) => c5.json({ status: "ok" }));
67179
67468
  app.use("/api/*", async (_c5, next) => next());
67180
67469
  app.get(`${config2.proxyPath}/client`, (c5) => {
67181
- const identity = { assetPath: config2.assetPath, clientVersion };
67470
+ const identity = { assetPath: config2.assetPath };
67182
67471
  return c5.json(identity, 200, { "cache-control": "no-store" });
67183
67472
  });
67184
67473
  app.get("/", (c5) => renderEntrypoint(c5.req.raw, selectedBoard));
@@ -67246,11 +67535,17 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67246
67535
  fetcher: deps.fetcher ?? globalThis.fetch,
67247
67536
  githubClient
67248
67537
  });
67249
- const headers = passthroughHeaders(result.response.headers);
67250
- if (result.response.status === 304) return new Response(null, { status: 304, headers });
67251
- const envelope = result.envelope ?? JSON.parse(await result.response.text());
67252
- headers.set("content-type", "application/json; charset=utf-8");
67253
- return new Response(JSON.stringify(envelope), { status: 200, headers });
67538
+ return adapterRouteResponse(result);
67539
+ }
67540
+ if (panel.type === "pipeline-status" && source?.type === "azure-devops" && source) {
67541
+ const result = await fetchAzureDevOpsPipeline({
67542
+ panel,
67543
+ source,
67544
+ requestHeaders: c5.req.raw.headers,
67545
+ fetcher: deps.fetcher ?? globalThis.fetch,
67546
+ credentials
67547
+ });
67548
+ return adapterRouteResponse(result);
67254
67549
  }
67255
67550
  if (panel.type === "pull-request-health" && source?.type === "github-actions" && source) {
67256
67551
  const result = await fetchGithubActionsPullRequestHealth({
@@ -67260,10 +67555,7 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67260
67555
  fetcher: deps.fetcher ?? globalThis.fetch,
67261
67556
  githubClient
67262
67557
  });
67263
- const headers = passthroughHeaders(result.response.headers);
67264
- const envelope = result.envelope ?? JSON.parse(await result.response.text());
67265
- headers.set("content-type", "application/json; charset=utf-8");
67266
- return new Response(JSON.stringify(envelope), { status: 200, headers });
67558
+ return adapterRouteResponse(result);
67267
67559
  }
67268
67560
  if (panel.type === "http-value") {
67269
67561
  const result = await fetchHttpValue({
@@ -67271,11 +67563,7 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67271
67563
  requestHeaders: c5.req.raw.headers,
67272
67564
  fetcher: deps.fetcher ?? globalThis.fetch
67273
67565
  });
67274
- const headers = passthroughHeaders(result.response.headers);
67275
- if (result.response.status === 304) return new Response(null, { status: 304, headers });
67276
- const envelope = result.envelope ?? JSON.parse(await result.response.text());
67277
- headers.set("content-type", "application/json; charset=utf-8");
67278
- return new Response(JSON.stringify(envelope), { status: 200, headers });
67566
+ return adapterRouteResponse(result);
67279
67567
  }
67280
67568
  return c5.notFound();
67281
67569
  });
@@ -67284,8 +67572,7 @@ ${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
67284
67572
  const env2 = {
67285
67573
  assetPath: config2.assetPath,
67286
67574
  proxyPath: config2.proxyPath,
67287
- board,
67288
- clientVersion
67575
+ board
67289
67576
  };
67290
67577
  return new Response(renderIndexHtml(template, env2), {
67291
67578
  headers: {
@@ -67302,20 +67589,6 @@ function safeDownloadFilename(value) {
67302
67589
  const sanitized = value.replace(/[\\/\r\n"%*:|<>?]/g, "_").trim();
67303
67590
  return sanitized || "board";
67304
67591
  }
67305
- function passthroughHeaders(upstream) {
67306
- const headers = new Headers();
67307
- for (const name of ["cache-control", "etag", "last-modified", "date", "vary"]) {
67308
- const value = upstream.get(name);
67309
- if (value) headers.set(name, value);
67310
- }
67311
- return headers;
67312
- }
67313
- function deriveVersionLabel(assetPath) {
67314
- const segments = assetPath.split("/").filter((segment) => segment !== "");
67315
- const last = segments.at(-1);
67316
- if (last === void 0) return "unknown";
67317
- return last === ASSET_PATH_SENTINEL.replace("/", "") ? "dev" : last;
67318
- }
67319
67592
 
67320
67593
  // packages/server/src/board-config.ts
67321
67594
  import { readFile as readFile3 } from "node:fs/promises";