@continuous-excellence/ze-great-dashboard-aws 0.27.8 → 0.27.10

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
@@ -2,18 +2,18 @@
2
2
 
3
3
  // packages/aws/src/cli.ts
4
4
  import { execFile as execFile2 } from "node:child_process";
5
- import { readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
5
+ import { readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
6
6
  import { promisify as promisify2 } from "node:util";
7
- import { parse as parse3 } from "yaml";
7
+ import { parse as parse2 } from "yaml";
8
8
 
9
9
  // packages/aws/src/doctor.ts
10
10
  import { execFile } from "node:child_process";
11
- import { readFile as readFile5 } from "node:fs/promises";
11
+ import { readFile as readFile6 } from "node:fs/promises";
12
12
  import { promisify } from "node:util";
13
- import { parse as parse2 } from "yaml";
13
+ import { parse } from "yaml";
14
14
 
15
15
  // packages/aws/src/bootstrap.ts
16
- import { readFile as readFile2 } from "node:fs/promises";
16
+ import { readFile as readFile3 } from "node:fs/promises";
17
17
  import { fileURLToPath } from "node:url";
18
18
 
19
19
  // packages/aws/src/compute-mode.ts
@@ -43,9 +43,15 @@ function computeMode(config) {
43
43
 
44
44
  // packages/aws/src/release.ts
45
45
  import { createHash } from "node:crypto";
46
- import { mkdir, readFile, writeFile } from "node:fs/promises";
46
+ import { mkdir, writeFile } from "node:fs/promises";
47
47
  import { join, resolve } from "node:path";
48
48
 
49
+ // packages/server/src/allowlist.ts
50
+ import { ZodError } from "zod";
51
+
52
+ // packages/server/src/adapters/azure-devops.ts
53
+ import { readFile } from "node:fs/promises";
54
+
49
55
  // packages/shared/src/board-config.ts
50
56
  import { z as z2 } from "zod";
51
57
 
@@ -68,7 +74,8 @@ function parseDuration(value2) {
68
74
  return amount * UNIT_MILLIS[rawUnit];
69
75
  }
70
76
  var durationSchema = z.string().refine((value2) => parseDuration(value2) !== null, {
71
- message: 'must be a positive duration like "30s", "5m", or "1h"'
77
+ message: 'must be a positive duration like "30s", "5m", or "1h"',
78
+ params: { constraint: 'Use a positive duration like "30s", "5m", or "1h".' }
72
79
  }).brand();
73
80
 
74
81
  // packages/shared/src/board-config.ts
@@ -154,7 +161,8 @@ var panelSchema = z2.looseObject({
154
161
  ctx.addIssue({
155
162
  code: "custom",
156
163
  path: ["display"],
157
- message: "display was removed; use density"
164
+ message: "display was removed; use density",
165
+ params: { constraint: "display was removed; use density" }
158
166
  });
159
167
  }
160
168
  if (panel.type !== "http-value") return;
@@ -163,13 +171,15 @@ var panelSchema = z2.looseObject({
163
171
  ctx.addIssue({
164
172
  code: "custom",
165
173
  path: ["url"],
166
- message: "use facts URLs for grouped panels"
174
+ message: "use facts URLs for grouped panels",
175
+ params: { constraint: "use facts URLs for grouped panels" }
167
176
  });
168
177
  if (panel.json_path !== void 0)
169
178
  ctx.addIssue({
170
179
  code: "custom",
171
180
  path: ["json_path"],
172
- message: "use facts json_path values for grouped panels"
181
+ message: "use facts json_path values for grouped panels",
182
+ params: { constraint: "use facts json_path values for grouped panels" }
173
183
  });
174
184
  const seen = /* @__PURE__ */ new Map();
175
185
  panel.facts.forEach((fact, index) => {
@@ -179,7 +189,8 @@ var panelSchema = z2.looseObject({
179
189
  ctx.addIssue({
180
190
  code: "custom",
181
191
  path: ["facts", index, "id"],
182
- message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
192
+ message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`,
193
+ params: { constraint: "Use a unique fact id within this panel." }
183
194
  });
184
195
  });
185
196
  }
@@ -196,7 +207,11 @@ var sourceAuthenticationSchema = {
196
207
  };
197
208
  function exactlyOneGithubAuthenticationMode(source, ctx) {
198
209
  if (source.token_env && source.github_app)
199
- ctx.addIssue({ code: "custom", message: "configure token_env or github_app, not both" });
210
+ ctx.addIssue({
211
+ code: "custom",
212
+ message: "configure token_env or github_app, not both",
213
+ params: { constraint: "configure token_env or github_app, not both" }
214
+ });
200
215
  }
201
216
  var githubActionsSourceSchema = z2.looseObject({
202
217
  type: z2.literal("github-actions"),
@@ -220,24 +235,44 @@ var azureDevOpsSourceSchema = z2.looseObject({
220
235
  if (source.token_env && source.entra_token_file_env) {
221
236
  ctx.addIssue({
222
237
  code: "custom",
223
- message: "configure token_env or entra_token_file_env, not both"
238
+ message: "configure token_env or entra_token_file_env, not both",
239
+ params: { constraint: "configure token_env or entra_token_file_env, not both" }
224
240
  });
225
241
  }
226
242
  if (!source.token_env && !source.entra_token_file_env) {
227
243
  ctx.addIssue({
228
244
  code: "custom",
229
- message: "configure token_env or entra_token_file_env"
245
+ message: "configure token_env or entra_token_file_env",
246
+ params: { constraint: "configure token_env or entra_token_file_env" }
230
247
  });
231
248
  }
232
249
  });
233
250
  var gitlabInstanceUrlSchema = z2.url().superRefine((value2, ctx) => {
234
251
  const url = new URL(value2);
235
252
  if (url.protocol !== "https:")
236
- ctx.addIssue({ code: "custom", message: "must be an HTTPS GitLab instance URL" });
253
+ ctx.addIssue({
254
+ code: "custom",
255
+ message: "must be an HTTPS GitLab instance URL",
256
+ params: { constraint: "must be an HTTPS GitLab instance URL" }
257
+ });
237
258
  if (url.username || url.password)
238
- ctx.addIssue({ code: "custom", message: "must not include credentials" });
239
- if (url.search) ctx.addIssue({ code: "custom", message: "must not include a query string" });
240
- if (url.hash) ctx.addIssue({ code: "custom", message: "must not include a fragment" });
259
+ ctx.addIssue({
260
+ code: "custom",
261
+ message: "must not include credentials",
262
+ params: { constraint: "must not include credentials" }
263
+ });
264
+ if (url.search)
265
+ ctx.addIssue({
266
+ code: "custom",
267
+ message: "must not include a query string",
268
+ params: { constraint: "must not include a query string" }
269
+ });
270
+ if (url.hash)
271
+ ctx.addIssue({
272
+ code: "custom",
273
+ message: "must not include a fragment",
274
+ params: { constraint: "must not include a fragment" }
275
+ });
241
276
  });
242
277
  var gitlabCiSourceSchema = z2.looseObject({
243
278
  type: z2.literal("gitlab-ci"),
@@ -251,6 +286,17 @@ var gitlabCiSourceSchema = z2.looseObject({
251
286
  url: gitlabInstanceUrlSchema.optional().default("https://gitlab.com")
252
287
  });
253
288
  var sourceSchema = z2.looseObject({ type: z2.string().min(1), ...sourceAuthenticationSchema }).superRefine(exactlyOneGithubAuthenticationMode);
289
+ function credentialEnvironmentNames(source) {
290
+ const githubApp = githubAppSchema.safeParse(source.github_app);
291
+ return [
292
+ ...source.token_env ? [source.token_env] : [],
293
+ ...githubApp.success ? [
294
+ githubApp.data.app_id_env,
295
+ githubApp.data.private_key_env,
296
+ githubApp.data.installation_id_env
297
+ ] : []
298
+ ];
299
+ }
254
300
  var boardSchema = z2.object({
255
301
  refresh: durationSchema.optional(),
256
302
  running_refresh: durationSchema.optional(),
@@ -267,7 +313,8 @@ var boardSchema = z2.object({
267
313
  ctx.addIssue({
268
314
  code: "custom",
269
315
  path: [index, "id"],
270
- message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`
316
+ message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`,
317
+ params: { constraint: "Use a unique panel id within this board." }
271
318
  });
272
319
  });
273
320
  })
@@ -287,15 +334,14 @@ var boardConfigSchema = z2.object({
287
334
  auth: authSchema.optional()
288
335
  }).superRefine((config, ctx) => {
289
336
  for (const [sourceName, source] of Object.entries(config.sources)) {
290
- const sourceSchema3 = source.type === "azure-devops" ? azureDevOpsSourceSchema : source.type === "gitlab-ci" ? gitlabCiSourceSchema : void 0;
291
- if (!sourceSchema3) continue;
292
- const parsedSource = sourceSchema3.safeParse(source);
337
+ const sourceSchema2 = source.type === "azure-devops" ? azureDevOpsSourceSchema : source.type === "gitlab-ci" ? gitlabCiSourceSchema : void 0;
338
+ if (!sourceSchema2) continue;
339
+ const parsedSource = sourceSchema2.safeParse(source);
293
340
  if (!parsedSource.success) {
294
341
  for (const issue of parsedSource.error.issues) {
295
342
  ctx.addIssue({
296
- code: "custom",
297
- path: ["sources", sourceName, ...issue.path],
298
- message: issue.message
343
+ ...issue,
344
+ path: ["sources", sourceName, ...issue.path]
299
345
  });
300
346
  }
301
347
  }
@@ -308,7 +354,10 @@ var boardConfigSchema = z2.object({
308
354
  ctx.addIssue({
309
355
  code: "custom",
310
356
  path: ["boards", boardName, "panels", panelIndex, "pipeline"],
311
- message: "Azure DevOps pipeline-status panels require a positive numeric pipeline definition id"
357
+ message: "Azure DevOps pipeline-status panels require a positive numeric pipeline definition id",
358
+ params: {
359
+ constraint: "Provide a positive numeric Azure DevOps pipeline definition id."
360
+ }
312
361
  });
313
362
  }
314
363
  });
@@ -471,95 +520,376 @@ var httpValueSchema = z4.object({
471
520
  value: z4.union([z4.string(), z4.number(), z4.boolean()])
472
521
  });
473
522
 
474
- // packages/aws/src/release.ts
475
- import { parse, stringify } from "yaml";
476
- import { z as z6 } from "zod";
477
-
478
- // packages/aws/src/internal-board.ts
523
+ // packages/server/src/adapters/azure-devops.ts
479
524
  import { z as z5 } from "zod";
480
- var durationSchema2 = z5.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value2) => Number.parseInt(value2, 10) > 0).brand();
481
- var positionSchema2 = z5.union([
482
- z5.object({
483
- x: z5.number().int().min(0),
484
- y: z5.number().int().min(0),
485
- w: z5.number().int().min(1).max(12),
486
- h: z5.number().int().min(1)
487
- }),
488
- z5.object({ x: z5.literal(0), y: z5.literal(0), w: z5.literal(0), h: z5.literal(0) })
489
- ]);
490
- var panelSchema2 = z5.looseObject({
491
- // Presentation-only; ids still address proxy calls and the generated allowlist.
492
- label: z5.string().min(1).optional(),
525
+ var pipelinePanelSchema = z5.object({
493
526
  id: z5.string().min(1),
494
- type: z5.string().min(1),
495
- source: z5.string().min(1).optional(),
496
- density: z5.enum(["auto", "comfortable", "compact"]).optional(),
497
- position: positionSchema2.optional(),
498
- refresh: durationSchema2.optional(),
499
- link: z5.url().optional(),
500
- url: z5.url().optional(),
501
- json_path: z5.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional()
502
- }).superRefine((panel, ctx) => {
503
- if ("display" in panel) {
504
- ctx.addIssue({
505
- code: "custom",
506
- path: ["display"],
507
- message: "display was removed; use density"
508
- });
509
- }
527
+ type: z5.literal("pipeline-status"),
528
+ source: z5.string().min(1),
529
+ pipeline: z5.number().int().positive()
510
530
  });
511
- var sourceSchema2 = z5.looseObject({
512
- type: z5.string().min(1),
513
- token_env: z5.string().min(1).optional(),
514
- github_app: z5.object({
515
- app_id_env: z5.string().min(1),
516
- private_key_env: z5.string().min(1),
517
- installation_id_env: z5.string().min(1)
518
- }).optional()
519
- }).superRefine((source, ctx) => {
520
- if (source.token_env && source.github_app)
521
- ctx.addIssue({ code: "custom", message: "configure token_env or github_app, not both" });
531
+ var buildSchema = z5.object({
532
+ id: z5.number().int().positive(),
533
+ buildNumber: z5.string().min(1),
534
+ status: z5.string().nullable().optional(),
535
+ result: z5.string().nullable().optional(),
536
+ sourceBranch: z5.string().min(1).nullable().optional(),
537
+ startTime: z5.string().nullable().optional(),
538
+ finishTime: z5.string().nullable().optional(),
539
+ lastChangedDate: z5.string().nullable().optional()
522
540
  });
523
- var boardSchema2 = z5.object({
524
- refresh: durationSchema2.optional(),
525
- panels: z5.array(panelSchema2).min(1).superRefine((panels, ctx) => {
526
- const seen = /* @__PURE__ */ new Map();
527
- panels.forEach((panel, index) => {
528
- const firstIndex = seen.get(panel.id);
529
- if (firstIndex === void 0) seen.set(panel.id, index);
530
- else
531
- ctx.addIssue({
532
- code: "custom",
533
- path: [index, "id"],
534
- message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`
535
- });
536
- });
541
+ var buildsSchema = z5.object({ value: z5.array(buildSchema) });
542
+ var timelineSchema = z5.object({
543
+ records: z5.array(
544
+ z5.object({
545
+ id: z5.string().min(1),
546
+ parentId: z5.string().min(1).nullable().optional(),
547
+ type: z5.string().min(1),
548
+ name: z5.string().min(1),
549
+ state: z5.string().nullable().optional()
550
+ })
551
+ )
552
+ });
553
+ function permittedAzureDevOpsCalls(panel, source) {
554
+ const { panel: parsedPanel, source: parsedSource } = z5.object({ panel: pipelinePanelSchema, source: azureDevOpsSourceSchema }).parse({ panel, source });
555
+ return [buildsCall(parsedPanel.pipeline, parsedSource)];
556
+ }
557
+ var delegatedTokenFileSchema = z5.object({
558
+ accessToken: z5.string().min(1),
559
+ expiresAt: z5.string().min(1)
560
+ });
561
+ function buildsCall(definitionId, source) {
562
+ const url = apiUrl(source, "build/builds");
563
+ url.searchParams.set("definitions", String(definitionId));
564
+ if (source.branch) url.searchParams.set("branchName", adoBranch(source.branch));
565
+ url.searchParams.set("queryOrder", "queueTimeDescending");
566
+ url.searchParams.set("$top", "1");
567
+ return { url: url.toString(), headers: new Headers({ accept: "application/json" }) };
568
+ }
569
+ function apiUrl(source, suffix) {
570
+ const url = new URL(
571
+ `https://dev.azure.com/${encodeURIComponent(source.organization)}/${encodeURIComponent(source.project)}/_apis/${suffix}`
572
+ );
573
+ url.searchParams.set("api-version", "7.1");
574
+ return url;
575
+ }
576
+ function adoBranch(branch) {
577
+ return branch.startsWith("refs/") ? branch : `refs/heads/${branch}`;
578
+ }
579
+
580
+ // packages/server/src/adapters/github-actions.ts
581
+ import { z as z6 } from "zod";
582
+ var pipelinePanelSchema2 = z6.object({
583
+ id: z6.string().min(1),
584
+ type: z6.literal("pipeline-status"),
585
+ source: z6.string().min(1),
586
+ /** GitHub accepts a workflow filename or numeric workflow id in this URL segment. */
587
+ pipeline: z6.string().min(1)
588
+ });
589
+ var pullRequestHealthPanelSchema = z6.object({
590
+ id: z6.string().min(1),
591
+ type: z6.literal("pull-request-health"),
592
+ source: z6.string().min(1),
593
+ base_branch: z6.string().min(1),
594
+ update_workflows: z6.array(
595
+ z6.object({
596
+ workflow: z6.string().min(1),
597
+ branch_prefixes: z6.array(z6.string().min(1)).min(1)
598
+ })
599
+ ).min(1),
600
+ build_workflow: z6.string().min(1)
601
+ });
602
+ var runSchema = z6.object({
603
+ id: z6.number().int().positive().optional(),
604
+ status: z6.string(),
605
+ conclusion: z6.string().nullable(),
606
+ name: z6.string().min(1),
607
+ html_url: z6.url(),
608
+ /** The branch that produced this run; absent for some event types. */
609
+ head_branch: z6.string().min(1).nullable().optional(),
610
+ // Timestamp quality should only remove timing advice, never make an otherwise useful run unreadable.
611
+ run_started_at: z6.string().nullable().optional(),
612
+ updated_at: z6.string().optional()
613
+ });
614
+ var runsSchema = z6.object({ workflow_runs: z6.array(runSchema) });
615
+ var jobsSchema = z6.object({
616
+ jobs: z6.array(
617
+ z6.object({
618
+ name: z6.string().min(1),
619
+ status: z6.string(),
620
+ steps: z6.array(z6.object({ name: z6.string().min(1), status: z6.string() })).optional()
621
+ })
622
+ )
623
+ });
624
+ var pullRequestsSchema = z6.array(
625
+ z6.object({
626
+ number: z6.number().int().positive(),
627
+ html_url: z6.url(),
628
+ head: z6.object({ ref: z6.string().min(1) }),
629
+ base: z6.object({ ref: z6.string().min(1) })
537
630
  })
631
+ );
632
+ function permittedGithubActionsCalls(panel, source, _credentials) {
633
+ const { panel: parsedPanel, source: parsedSource } = z6.object({ panel: pipelinePanelSchema2, source: githubActionsSourceSchema }).parse({ panel, source });
634
+ const url = new URL(
635
+ `https://api.github.com/repos/${parsedSource.repo}/actions/workflows/${encodeURIComponent(parsedPanel.pipeline)}/runs`
636
+ );
637
+ if (parsedSource.branch) url.searchParams.set("branch", parsedSource.branch);
638
+ url.searchParams.set("per_page", "1");
639
+ return [{ url: url.toString(), headers: new Headers() }];
640
+ }
641
+ function pullRequestHealthCapabilities(panel) {
642
+ const { panel: parsed } = z6.object({ panel: pullRequestHealthPanelSchema }).parse({ panel });
643
+ return {
644
+ panel: parsed,
645
+ configuredUpdateWorkflow(workflow) {
646
+ return parsed.update_workflows.find((candidate) => candidate.workflow === workflow);
647
+ },
648
+ permitsBuildBranch(branch) {
649
+ return parsed.update_workflows.some(
650
+ ({ branch_prefixes }) => branch_prefixes.some((prefix) => branch.startsWith(prefix))
651
+ );
652
+ }
653
+ };
654
+ }
655
+
656
+ // packages/server/src/adapters/gitlab-ci.ts
657
+ import { z as z7 } from "zod";
658
+ var pipelinePanelSchema3 = z7.object({
659
+ id: z7.string().min(1),
660
+ type: z7.literal("pipeline-status"),
661
+ source: z7.string().min(1)
538
662
  });
539
- var boardConfigSchema2 = z5.object({
540
- sources: z5.record(z5.string().min(1), sourceSchema2).default({}),
541
- boards: z5.record(z5.string().min(1), boardSchema2),
542
- auth: z5.object({
543
- issuer: z5.url(),
544
- client_id: z5.string().min(1).optional(),
545
- allow: z5.object({
546
- groups: z5.array(z5.string().min(1)).optional(),
547
- subjects: z5.array(z5.string().min(1)).optional()
548
- }).optional()
549
- }).optional()
663
+ var pipelineSchema = z7.object({
664
+ id: z7.number().int().positive(),
665
+ name: z7.string().min(1).nullable().optional(),
666
+ status: z7.string(),
667
+ ref: z7.string().min(1).nullable().optional(),
668
+ created_at: z7.string().nullable().optional(),
669
+ updated_at: z7.string().nullable().optional(),
670
+ web_url: z7.url()
550
671
  });
551
- function credentialEnvironmentNames2(source) {
552
- return [
553
- ...source.token_env ? [source.token_env] : [],
554
- ...source.github_app ? [
555
- source.github_app.app_id_env,
556
- source.github_app.private_key_env,
557
- source.github_app.installation_id_env
558
- ] : []
559
- ];
672
+ function permittedGitlabCiCalls(panel, source) {
673
+ const { source: parsedSource } = z7.object({ panel: pipelinePanelSchema3, source: gitlabCiSourceSchema }).parse({ panel, source });
674
+ return [pipelinesCall(parsedSource)];
675
+ }
676
+ function pipelinesCall(source) {
677
+ const url = new URL(source.url);
678
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/api/v4/projects/${encodeURIComponent(source.project)}/pipelines`;
679
+ url.searchParams.set("per_page", "1");
680
+ if (source.branch) url.searchParams.set("ref", source.branch);
681
+ return { url: url.toString(), headers: new Headers({ accept: "application/json" }) };
682
+ }
683
+
684
+ // packages/server/src/adapters/http-value.ts
685
+ import { z as z8 } from "zod";
686
+ function permittedHttpValueCalls(panel) {
687
+ const grouped = httpValueGroupedPanelSchema.safeParse(panel);
688
+ const urls = grouped.success ? grouped.data.facts.map((fact) => fact.url) : [z8.object({ panel: httpValueScalarPanelSchema }).parse({ panel }).panel.url];
689
+ return urls.map(permittedCall);
690
+ }
691
+ function permittedCall(value2) {
692
+ const url = new URL(value2);
693
+ if (url.protocol !== "http:" && url.protocol !== "https:")
694
+ throw new Error("http-value URLs must use http or https");
695
+ return { url: url.toString(), headers: new Headers({ accept: "application/json, text/plain" }) };
696
+ }
697
+
698
+ // packages/server/src/configuration-error.ts
699
+ var ConfigurationError = class extends Error {
700
+ constructor(message2, diagnostics) {
701
+ super(message2);
702
+ this.diagnostics = diagnostics;
703
+ }
704
+ diagnostics;
705
+ };
706
+ function configurationLocation(path) {
707
+ return path.reduce(
708
+ (location, part) => typeof part === "number" ? `${location}[${part}]` : `${location}${location ? "." : ""}${String(part)}`,
709
+ ""
710
+ ) || "configuration";
711
+ }
712
+ function boardConfigurationLocation(config, path) {
713
+ const [record, key, ...rest] = path;
714
+ if ((record === "boards" || record === "sources") && key !== void 0) {
715
+ const entries = config && typeof config === "object" ? Reflect.get(config, record) : void 0;
716
+ const index = entries && typeof entries === "object" ? Object.keys(entries).indexOf(String(key)) : -1;
717
+ return configurationLocation([record, index, ...rest]);
718
+ }
719
+ return configurationLocation(path);
720
+ }
721
+ function schemaDiagnostics(error, locate, kind = "schema") {
722
+ return error.issues.map((issue) => ({
723
+ kind,
724
+ location: locate(issue.path),
725
+ constraint: correctiveConstraint(issue)
726
+ }));
727
+ }
728
+ function correctiveConstraint(issue) {
729
+ switch (issue.code) {
730
+ case "invalid_type":
731
+ return `Provide a value of type ${issue.expected}.`;
732
+ case "too_big":
733
+ return `Use ${issue.inclusive ? "at most" : "fewer than"} ${issue.maximum} ${issue.origin === "array" ? "entries" : issue.origin === "string" ? "characters" : "as the value"}.`;
734
+ case "too_small":
735
+ return `Use ${issue.inclusive ? "at least" : "more than"} ${issue.minimum} ${issue.origin === "array" ? "entries" : issue.origin === "string" ? "characters" : "as the value"}.`;
736
+ // Format messages are fixed by our schemas (or Zod); unlike custom issues they contain no authored values.
737
+ case "invalid_format":
738
+ return issue.message;
739
+ case "invalid_value":
740
+ return "Use one of the values allowed by the board schema.";
741
+ case "custom":
742
+ return typeof issue.params?.constraint === "string" ? issue.params.constraint : "Correct this field according to the board schema.";
743
+ default:
744
+ return "Provide a value matching the board schema for this field.";
745
+ }
746
+ }
747
+
748
+ // packages/server/src/allowlist.ts
749
+ function deriveValidatedAllowlist(config) {
750
+ return derive(config, true);
751
+ }
752
+ function derive(config, rejectUnsupported) {
753
+ const allowed = /* @__PURE__ */ new Map();
754
+ for (const [boardName, board] of Object.entries(config.boards)) {
755
+ for (const panel of board.panels) {
756
+ const source = panel.source ? config.sources[panel.source] : void 0;
757
+ try {
758
+ const capability = panelCapability(panel, source);
759
+ if (capability.kind === "unsupported") {
760
+ if (rejectUnsupported) throw new Error("no bounded operations");
761
+ continue;
762
+ }
763
+ if (capability.kind === "server")
764
+ allowed.set(`${boardName}/${panel.id}`, new Set(capability.operations));
765
+ } catch (error) {
766
+ if (rejectUnsupported) {
767
+ const location = `boards[${Object.keys(config.boards).indexOf(boardName)}].panels[${board.panels.indexOf(panel)}]`;
768
+ throw new ConfigurationError(
769
+ unsupportedPanelOperation(boardName, panel, source, error).message,
770
+ error instanceof ZodError ? schemaDiagnostics(
771
+ error,
772
+ ([subject, ...path]) => {
773
+ const root = subject === "source" ? ["sources", Object.keys(config.sources).indexOf(panel.source ?? "")] : [
774
+ "boards",
775
+ Object.keys(config.boards).indexOf(boardName),
776
+ "panels",
777
+ board.panels.indexOf(panel)
778
+ ];
779
+ return configurationLocation([...root, ...path]);
780
+ },
781
+ "panel-admission"
782
+ ) : [
783
+ {
784
+ kind: "panel-admission",
785
+ location,
786
+ constraint: "Use a supported panel/source combination with bounded HTTP(S) operations."
787
+ }
788
+ ]
789
+ );
790
+ }
791
+ throw error;
792
+ }
793
+ }
794
+ }
795
+ return allowed;
796
+ }
797
+ function panelCapability(panel, source) {
798
+ if (panel.type === "pipeline-status" && source?.type === "github-actions") {
799
+ permittedGithubActionsCalls(panel, source);
800
+ return { kind: "server", operations: ["read"] };
801
+ }
802
+ if (panel.type === "pipeline-status" && source?.type === "azure-devops") {
803
+ permittedAzureDevOpsCalls(panel, source);
804
+ return { kind: "server", operations: ["read"] };
805
+ }
806
+ if (panel.type === "pipeline-status" && source?.type === "gitlab-ci") {
807
+ permittedGitlabCiCalls(panel, source);
808
+ return { kind: "server", operations: ["read"] };
809
+ }
810
+ if (panel.type === "http-value") {
811
+ permittedHttpValueCalls(panel);
812
+ return { kind: "server", operations: ["read"] };
813
+ }
814
+ if (panel.type === "pull-request-health" && source?.type === "github-actions") {
815
+ pullRequestHealthCapabilities(panel);
816
+ return {
817
+ kind: "server",
818
+ operations: ["pull-requests", "update-workflow", "pull-request-build"]
819
+ };
820
+ }
821
+ if (panel.type === "pipeline-animation-demo") return { kind: "client-only" };
822
+ return { kind: "unsupported" };
823
+ }
824
+ function unsupportedPanelOperation(boardName, panel, source, error) {
825
+ const sourceName = panel.source ?? "(none)";
826
+ const sourceType = source?.type ?? "(none)";
827
+ const detail = error instanceof Error && error.message !== "no bounded operations" ? ` ${error.message}` : "";
828
+ return new Error(
829
+ `Unsupported configured panel operation: board "${boardName}", panel "${panel.id}", source "${sourceName}" (${sourceType}), signal "${panel.type}".${detail} Configure a supported panel/source combination and all adapter-required fields.`
830
+ );
831
+ }
832
+
833
+ // packages/server/src/board-config.ts
834
+ import { readFile as readFile2 } from "node:fs/promises";
835
+ import { parse as parseYaml, YAMLParseError } from "yaml";
836
+ async function loadBoardConfig(location, fetcher = globalThis.fetch, expectedSchemaUrl) {
837
+ const text = isUrl(location) ? await fetchBoardConfig(location, fetcher) : await readFile2(location, "utf-8");
838
+ let authoredSchemaUrl;
839
+ try {
840
+ authoredSchemaUrl = readBoardSchemaModeline(text, expectedSchemaUrl);
841
+ } catch (error) {
842
+ throw new ConfigurationError(error instanceof Error ? error.message : "Invalid modeline", [
843
+ {
844
+ kind: "modeline",
845
+ location: "line 1",
846
+ constraint: "Add a first-line yaml-language-server schema modeline ending in /board-config.schema.json."
847
+ }
848
+ ]);
849
+ }
850
+ let parsed;
851
+ try {
852
+ parsed = parseYaml(text);
853
+ } catch (error) {
854
+ const position = error instanceof YAMLParseError ? error.linePos?.[0] : void 0;
855
+ const location2 = position ? `line ${position.line}, column ${position.col}` : "configuration";
856
+ throw new ConfigurationError(
857
+ `Invalid board configuration YAML at ${location2}: correct YAML syntax.`,
858
+ [
859
+ {
860
+ kind: "yaml",
861
+ location: location2,
862
+ constraint: "Correct YAML syntax and indentation."
863
+ }
864
+ ]
865
+ );
866
+ }
867
+ const result = boardConfigSchema.safeParse(parsed);
868
+ if (!result.success) {
869
+ const stale = expectedSchemaUrl && authoredSchemaUrl !== expectedSchemaUrl ? `
870
+ Stale schema modeline: expected ${expectedSchemaUrl}` : "";
871
+ throw new ConfigurationError(
872
+ `Invalid board configuration:
873
+ ${result.error.message}${stale}`,
874
+ schemaDiagnostics(result.error, (path) => boardConfigurationLocation(parsed, path))
875
+ );
876
+ }
877
+ return result.data;
878
+ }
879
+ function isUrl(value2) {
880
+ return /^https?:\/\//i.test(value2);
881
+ }
882
+ async function fetchBoardConfig(url, fetcher) {
883
+ const response = await fetcher(url);
884
+ if (!response.ok)
885
+ throw new Error(
886
+ `Unable to fetch board configuration from ${url}: ${response.status} ${response.statusText}`
887
+ );
888
+ return response.text();
560
889
  }
561
890
 
562
891
  // packages/aws/src/release.ts
892
+ import { stringify } from "yaml";
563
893
  var CORE_RUNTIME_VERSION = "1.0.0";
564
894
  var CANONICAL_ASSET_DOMAIN = "https://public-assets.zegreatrob.com";
565
895
  function legacyAssetPath(version, domain = CANONICAL_ASSET_DOMAIN) {
@@ -582,22 +912,27 @@ function normalizeAssetPath(value2) {
582
912
  return assetPath;
583
913
  }
584
914
  async function validateBoardConfig(path, expectedSchemaUrl) {
585
- const source = await readFile(resolve(path), "utf8");
586
- const authoredSchemaUrl = readBoardSchemaModeline(source, expectedSchemaUrl);
587
- const result = boardConfigSchema2.safeParse(parse(source));
588
- if (!result.success) {
589
- const stale = authoredSchemaUrl !== expectedSchemaUrl ? `
590
- Stale schema modeline: expected ${expectedSchemaUrl}` : "";
591
- throw new Error(`Invalid board configuration:
592
- ${z6.prettifyError(result.error)}${stale}`);
915
+ const config = await loadBoardConfig(resolve(path), void 0, expectedSchemaUrl).catch(
916
+ (error) => {
917
+ throw new Error(
918
+ `${path}: ${error instanceof Error ? error.message : "Invalid board configuration"}`
919
+ );
920
+ }
921
+ );
922
+ try {
923
+ deriveValidatedAllowlist(config);
924
+ } catch (error) {
925
+ throw new Error(
926
+ `${path}: ${error instanceof Error ? error.message : "Invalid panel configuration"}`
927
+ );
593
928
  }
594
929
  const yaml = `${boardSchemaModeline(expectedSchemaUrl)}
595
- ${stringify(result.data, { sortMapEntries: true })}`;
930
+ ${stringify(config, { sortMapEntries: true })}`;
596
931
  return {
597
932
  yaml,
598
933
  sha256: sha256(yaml),
599
- usesCredentials: Object.values(result.data.sources).some(
600
- (source2) => credentialEnvironmentNames2(source2).length > 0
934
+ usesCredentials: Object.values(config.sources).some(
935
+ (source) => credentialEnvironmentNames(source).length > 0
601
936
  )
602
937
  };
603
938
  }
@@ -750,11 +1085,11 @@ async function bootstrapTemplatePath(kind, mode = "lambda") {
750
1085
  return fileURLToPath(new URL(templates[kind][mode], import.meta.url));
751
1086
  }
752
1087
  async function bootstrapTemplate(kind, mode = "lambda") {
753
- return readFile2(await bootstrapTemplatePath(kind, mode), "utf8");
1088
+ return readFile3(await bootstrapTemplatePath(kind, mode), "utf8");
754
1089
  }
755
1090
  async function installedBootstrapDesiredState(config = {}) {
756
1091
  const packageManifest = JSON.parse(
757
- await readFile2(new URL("../package.json", import.meta.url), "utf8")
1092
+ await readFile3(new URL("../package.json", import.meta.url), "utf8")
758
1093
  );
759
1094
  return desiredStateFromTemplates(
760
1095
  packageVersion(packageManifest),
@@ -824,7 +1159,7 @@ function inspectTemplate(kind, path, template) {
824
1159
  }
825
1160
  async function bootstrapPlan(config) {
826
1161
  const packageManifest = JSON.parse(
827
- await readFile2(new URL("../package.json", import.meta.url), "utf8")
1162
+ await readFile3(new URL("../package.json", import.meta.url), "utf8")
828
1163
  );
829
1164
  const packageTemplates = await installedTemplateInspections(computeMode(config));
830
1165
  const installedDesiredState = desiredStateFromTemplates(
@@ -997,7 +1332,7 @@ function requiredBootstrapParameters(kind, values) {
997
1332
  }
998
1333
 
999
1334
  // packages/aws/src/index.ts
1000
- import { cp, mkdir as mkdir2, readFile as readFile4, rm, writeFile as writeFile3 } from "node:fs/promises";
1335
+ import { cp, readFile as readFile5, rm, writeFile as writeFile3 } from "node:fs/promises";
1001
1336
  import { join as join2, resolve as resolve2 } from "node:path";
1002
1337
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1003
1338
 
@@ -2039,7 +2374,7 @@ async function checkBootstrap(config, options, dependencies) {
2039
2374
  }
2040
2375
 
2041
2376
  // packages/aws/src/guided.ts
2042
- import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
2377
+ import { readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
2043
2378
 
2044
2379
  // packages/aws/src/handoff.ts
2045
2380
  var githubSubjectKeys = [
@@ -2555,7 +2890,7 @@ async function scaffoldBootstrapManifest(input) {
2555
2890
  async function upgradeBootstrapManifest(path) {
2556
2891
  let parsed;
2557
2892
  try {
2558
- parsed = JSON.parse(await readFile3(path, "utf8"));
2893
+ parsed = JSON.parse(await readFile4(path, "utf8"));
2559
2894
  } catch (error) {
2560
2895
  throw new Error(
2561
2896
  `Unable to read bootstrap manifest ${path}: ${error instanceof Error ? error.message : String(error)}`
@@ -2851,11 +3186,8 @@ function deploymentTemplate(template, values) {
2851
3186
  }
2852
3187
  async function packageLambda(options) {
2853
3188
  const outputDir = resolve2(options.outputDir);
2854
- await mkdir2(outputDir, { recursive: true });
2855
3189
  const runtimeDir = join2(outputDir, "lambda");
2856
- await mkdir2(runtimeDir, { recursive: true });
2857
3190
  const lambdaSource = fileURLToPath2(new URL("../dist/lambda.mjs", import.meta.url));
2858
- await cp(lambdaSource, join2(runtimeDir, "index.mjs"));
2859
3191
  const release = await assembleRelease({
2860
3192
  boardConfigPath: options.boardConfigPath,
2861
3193
  outputDir: runtimeDir,
@@ -2865,12 +3197,13 @@ async function packageLambda(options) {
2865
3197
  assetDomain: options.assetDomain,
2866
3198
  secretReference: options.secretReference
2867
3199
  });
3200
+ await cp(lambdaSource, join2(runtimeDir, "index.mjs"));
2868
3201
  const runtimeMetadata = {
2869
3202
  ...release.metadata,
2870
3203
  computeMode: "lambda",
2871
3204
  artifactChecksums: {
2872
3205
  ...release.metadata.artifactChecksums,
2873
- "index.mjs": sha256(await readFile4(join2(runtimeDir, "index.mjs")))
3206
+ "index.mjs": sha256(await readFile5(join2(runtimeDir, "index.mjs")))
2874
3207
  }
2875
3208
  };
2876
3209
  await writeFile3(join2(runtimeDir, "release.json"), `${JSON.stringify(runtimeMetadata, null, 2)}
@@ -2885,7 +3218,7 @@ async function packageLambda(options) {
2885
3218
  archiveFiles.map(async (name) => [
2886
3219
  name,
2887
3220
  [
2888
- strToU8(await readFile4(join2(runtimeDir, name), "utf8")),
3221
+ strToU8(await readFile5(join2(runtimeDir, name), "utf8")),
2889
3222
  { mtime: new Date(1980, 0, 1, 0, 0, 0), level: 9 }
2890
3223
  ]
2891
3224
  ])
@@ -2893,7 +3226,7 @@ async function packageLambda(options) {
2893
3226
  );
2894
3227
  await writeFile3(lambdaPath, zipSync(archive));
2895
3228
  await rm(runtimeDir, { recursive: true, force: true });
2896
- const lambdaChecksum = sha256(await readFile4(lambdaPath));
3229
+ const lambdaChecksum = sha256(await readFile5(lambdaPath));
2897
3230
  const deploymentChecksum = sha256(JSON.stringify(runtimeMetadata));
2898
3231
  const packagedRelease = {
2899
3232
  ...runtimeMetadata,
@@ -2914,7 +3247,6 @@ async function packageLambda(options) {
2914
3247
  }
2915
3248
  async function packageEcs(options) {
2916
3249
  const outputDir = resolve2(options.outputDir);
2917
- await mkdir2(outputDir, { recursive: true });
2918
3250
  const imageReference = requireImageDigest(options.imageReference ?? "");
2919
3251
  const release = await assembleRelease({
2920
3252
  boardConfigPath: options.boardConfigPath,
@@ -2943,13 +3275,13 @@ async function packageEcs(options) {
2943
3275
  );
2944
3276
  await writeFile3(
2945
3277
  join2(outputDir, "SHA256SUMS"),
2946
- `${sha256(await readFile4(join2(outputDir, "release.json")))} release.json
3278
+ `${sha256(await readFile5(join2(outputDir, "release.json")))} release.json
2947
3279
  `
2948
3280
  );
2949
3281
  return packagedRelease;
2950
3282
  }
2951
3283
  async function cloudFormationTemplate(mode = "lambda") {
2952
- return readFile4(
3284
+ return readFile5(
2953
3285
  fileURLToPath2(
2954
3286
  new URL(mode === "ecs" ? "../template-ecs.yml" : "../template.yml", import.meta.url)
2955
3287
  ),
@@ -2991,8 +3323,8 @@ async function templateContract(parameters) {
2991
3323
  )?.[0];
2992
3324
  const metadataBlock = template.match(/^Metadata:\n[\s\S]*?(?=^Parameters:\s*$)/m)?.[0];
2993
3325
  if (!parametersBlock) throw new Error("template has no Parameters block");
2994
- const parsedParameters = parse2(parametersBlock);
2995
- const parsedMetadata = metadataBlock ? parse2(metadataBlock) : {};
3326
+ const parsedParameters = parse(parametersBlock);
3327
+ const parsedMetadata = metadataBlock ? parse(metadataBlock) : {};
2996
3328
  const definitions = parsedParameters.Parameters ?? {};
2997
3329
  const managedValue = parsedMetadata.Metadata?.PackageManagedParameters ?? [];
2998
3330
  if (!Array.isArray(managedValue) || !managedValue.every((key) => typeof key === "string"))
@@ -3054,7 +3386,7 @@ async function runDoctor(options, dependencies = actualDependencies) {
3054
3386
  let parameterData;
3055
3387
  await check("Parameters/template", async () => {
3056
3388
  const parameters = readParameterValues(
3057
- JSON.parse(await readFile5(options.parametersPath, "utf8"))
3389
+ JSON.parse(await readFile6(options.parametersPath, "utf8"))
3058
3390
  );
3059
3391
  parameterData = await templateContract(parameters);
3060
3392
  return `${options.parametersPath} is compatible (${computeMode({ mode: parameterData.values.ComputeMode })})`;
@@ -3094,7 +3426,7 @@ async function runDoctor(options, dependencies = actualDependencies) {
3094
3426
  const template = await bootstrapTemplate("github-oidc");
3095
3427
  const expectedRevision = bootstrapTemplateRevision(template);
3096
3428
  await check("Bootstrap template", async () => {
3097
- const raw = JSON.parse(await readFile5(stackPath, "utf8"));
3429
+ const raw = JSON.parse(await readFile6(stackPath, "utf8"));
3098
3430
  const stack = raw && typeof raw === "object" && "Stacks" in raw ? raw.Stacks?.[0] : raw;
3099
3431
  if (!stack || typeof stack !== "object")
3100
3432
  throw new Error("captured stack JSON contains no stack");
@@ -3132,7 +3464,7 @@ var option = (name, fallback) => {
3132
3464
  };
3133
3465
  async function installedPackageVersion() {
3134
3466
  const packageManifest = JSON.parse(
3135
- await readFile6(new URL("../package.json", import.meta.url), "utf8")
3467
+ await readFile7(new URL("../package.json", import.meta.url), "utf8")
3136
3468
  );
3137
3469
  return typeof packageManifest.version === "string" ? packageManifest.version : "";
3138
3470
  }
@@ -3146,7 +3478,7 @@ function parameter(key, value2) {
3146
3478
  }
3147
3479
  async function existingParameters(path, allowMissing = false) {
3148
3480
  try {
3149
- const parsed = JSON.parse(await readFile6(path, "utf8"));
3481
+ const parsed = JSON.parse(await readFile7(path, "utf8"));
3150
3482
  if (!Array.isArray(parsed)) throw new Error(`${path} must contain a JSON parameter array`);
3151
3483
  const values = parsed.map((value2) => {
3152
3484
  if (!value2 || typeof value2 !== "object" || typeof value2.ParameterKey !== "string" || typeof value2.ParameterValue !== "string")
@@ -3170,7 +3502,7 @@ function bootstrapKind() {
3170
3502
  throw new Error("--kind must be core or github-oidc");
3171
3503
  }
3172
3504
  async function readBootstrapConfig(path) {
3173
- const parsed = JSON.parse(await readFile6(path, "utf8"));
3505
+ const parsed = JSON.parse(await readFile7(path, "utf8"));
3174
3506
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
3175
3507
  throw new Error("--config must contain a JSON object");
3176
3508
  return parsed;
@@ -3251,10 +3583,10 @@ async function templateParameters(mode = "lambda") {
3251
3583
  const template = await cloudFormationTemplate(mode);
3252
3584
  const block = template.match(/^Parameters:\n[\s\S]*?(?=^[A-Za-z][A-Za-z0-9]*:\s*$)/m)?.[0];
3253
3585
  if (!block) throw new Error("Unable to read Parameters from the CloudFormation template");
3254
- const parsed = parse3(block);
3586
+ const parsed = parse2(block);
3255
3587
  if (!parsed.Parameters) throw new Error("CloudFormation template has no Parameters");
3256
3588
  const metadataBlock = template.match(/^Metadata:\n[\s\S]*?(?=^Parameters:\s*$)/m)?.[0];
3257
- const metadata = metadataBlock ? parse3(metadataBlock).Metadata ?? {} : {};
3589
+ const metadata = metadataBlock ? parse2(metadataBlock).Metadata ?? {} : {};
3258
3590
  if (metadata.PackageManagedParameters !== void 0 && (!Array.isArray(metadata.PackageManagedParameters) || !metadata.PackageManagedParameters.every((key) => typeof key === "string")))
3259
3591
  throw new Error("CloudFormation template has invalid PackageManagedParameters metadata");
3260
3592
  const packageManaged = new Set(metadata.PackageManagedParameters ?? []);
@@ -3428,7 +3760,7 @@ try {
3428
3760
  if (action === "init") {
3429
3761
  const output = requiredOption("--output");
3430
3762
  try {
3431
- await readFile6(output, "utf8");
3763
+ await readFile7(output, "utf8");
3432
3764
  throw new Error(`Refusing to overwrite existing manifest: ${output}`);
3433
3765
  } catch (error) {
3434
3766
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
@@ -3486,9 +3818,9 @@ Review and commit this manifest before deployment.`
3486
3818
  config,
3487
3819
  configPath,
3488
3820
  workDir: option("--work-dir"),
3489
- coreStack: coreStackPath ? JSON.parse(await readFile6(coreStackPath, "utf8")) : void 0,
3821
+ coreStack: coreStackPath ? JSON.parse(await readFile7(coreStackPath, "utf8")) : void 0,
3490
3822
  coreStackPath,
3491
- githubOidcStack: githubStackPath ? JSON.parse(await readFile6(githubStackPath, "utf8")) : void 0,
3823
+ githubOidcStack: githubStackPath ? JSON.parse(await readFile7(githubStackPath, "utf8")) : void 0,
3492
3824
  githubOidcStackPath: githubStackPath,
3493
3825
  runner
3494
3826
  });
@@ -3510,9 +3842,9 @@ Review and commit this manifest before deployment.`
3510
3842
  config,
3511
3843
  configPath,
3512
3844
  workDir: option("--work-dir"),
3513
- coreStack: coreStackPath ? JSON.parse(await readFile6(coreStackPath, "utf8")) : void 0,
3845
+ coreStack: coreStackPath ? JSON.parse(await readFile7(coreStackPath, "utf8")) : void 0,
3514
3846
  coreStackPath,
3515
- githubOidcStack: githubStackPath ? JSON.parse(await readFile6(githubStackPath, "utf8")) : void 0,
3847
+ githubOidcStack: githubStackPath ? JSON.parse(await readFile7(githubStackPath, "utf8")) : void 0,
3516
3848
  githubOidcStackPath: githubStackPath,
3517
3849
  runner
3518
3850
  });
@@ -3527,8 +3859,8 @@ ${formatBootstrapRemediationText(handoff.remediation).join("\n")}`
3527
3859
  const githubStackPath = requiredOption("--github-oidc-stack-json");
3528
3860
  const verified = await verifyBootstrap({
3529
3861
  config,
3530
- coreStack: JSON.parse(await readFile6(coreStackPath, "utf8")),
3531
- githubOidcStack: JSON.parse(await readFile6(githubStackPath, "utf8"))
3862
+ coreStack: JSON.parse(await readFile7(coreStackPath, "utf8")),
3863
+ githubOidcStack: JSON.parse(await readFile7(githubStackPath, "utf8"))
3532
3864
  });
3533
3865
  printBootstrap(
3534
3866
  verified,
@@ -3612,7 +3944,7 @@ ${plan.notes.join("\n")}`);
3612
3944
  if (kind === "github-oidc" && coreStackPath) {
3613
3945
  coreOutputs = coreBootstrapOutputs(
3614
3946
  deployedBootstrapStack(
3615
- JSON.parse(await readFile6(coreStackPath, "utf8")),
3947
+ JSON.parse(await readFile7(coreStackPath, "utf8")),
3616
3948
  bootstrapContractVersion(await bootstrapTemplate("core", mode))
3617
3949
  )
3618
3950
  );
@@ -3625,7 +3957,7 @@ ${plan.notes.join("\n")}`);
3625
3957
  const deployedStackPath = option("--deployed-stack-json");
3626
3958
  if (deployedStackPath) {
3627
3959
  const stack = deployedBootstrapStack(
3628
- JSON.parse(await readFile6(deployedStackPath, "utf8")),
3960
+ JSON.parse(await readFile7(deployedStackPath, "utf8")),
3629
3961
  bootstrapContractVersion(await bootstrapTemplate(kind, mode))
3630
3962
  );
3631
3963
  if (kind === "github-oidc" && !(stack.Parameters ?? []).some(