@continuous-excellence/ze-great-dashboard-aws 0.27.9 → 0.27.11

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,31 +43,21 @@ 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
 
52
58
  // packages/shared/src/duration.ts
53
59
  import { z } from "zod";
54
- var DURATION_PATTERN = /^(\d+)(ms|s|m|h)$/;
55
- var UNIT_MILLIS = {
56
- ms: 1,
57
- s: 1e3,
58
- m: 6e4,
59
- h: 36e5
60
- };
61
- function parseDuration(value2) {
62
- const match = DURATION_PATTERN.exec(value2);
63
- if (!match) return null;
64
- const [, rawAmount, rawUnit] = match;
65
- if (rawAmount === void 0 || rawUnit === void 0) return null;
66
- const amount = Number(rawAmount);
67
- if (amount <= 0) return null;
68
- return amount * UNIT_MILLIS[rawUnit];
69
- }
70
- var durationSchema = z.string().refine((value2) => parseDuration(value2) !== null, {
60
+ var durationSchema = z.string().regex(/^[1-9][0-9]*(?:ms|s|m|h)$/, {
71
61
  message: 'must be a positive duration like "30s", "5m", or "1h"'
72
62
  }).brand();
73
63
 
@@ -154,7 +144,8 @@ var panelSchema = z2.looseObject({
154
144
  ctx.addIssue({
155
145
  code: "custom",
156
146
  path: ["display"],
157
- message: "display was removed; use density"
147
+ message: "display was removed; use density",
148
+ params: { constraint: "display was removed; use density" }
158
149
  });
159
150
  }
160
151
  if (panel.type !== "http-value") return;
@@ -163,13 +154,15 @@ var panelSchema = z2.looseObject({
163
154
  ctx.addIssue({
164
155
  code: "custom",
165
156
  path: ["url"],
166
- message: "use facts URLs for grouped panels"
157
+ message: "use facts URLs for grouped panels",
158
+ params: { constraint: "use facts URLs for grouped panels" }
167
159
  });
168
160
  if (panel.json_path !== void 0)
169
161
  ctx.addIssue({
170
162
  code: "custom",
171
163
  path: ["json_path"],
172
- message: "use facts json_path values for grouped panels"
164
+ message: "use facts json_path values for grouped panels",
165
+ params: { constraint: "use facts json_path values for grouped panels" }
173
166
  });
174
167
  const seen = /* @__PURE__ */ new Map();
175
168
  panel.facts.forEach((fact, index) => {
@@ -179,7 +172,8 @@ var panelSchema = z2.looseObject({
179
172
  ctx.addIssue({
180
173
  code: "custom",
181
174
  path: ["facts", index, "id"],
182
- message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`
175
+ message: `duplicate fact id "${fact.id}" (already used by fact at index ${first})`,
176
+ params: { constraint: "Use a unique fact id within this panel." }
183
177
  });
184
178
  });
185
179
  }
@@ -196,7 +190,11 @@ var sourceAuthenticationSchema = {
196
190
  };
197
191
  function exactlyOneGithubAuthenticationMode(source, ctx) {
198
192
  if (source.token_env && source.github_app)
199
- ctx.addIssue({ code: "custom", message: "configure token_env or github_app, not both" });
193
+ ctx.addIssue({
194
+ code: "custom",
195
+ message: "configure token_env or github_app, not both",
196
+ params: { constraint: "configure token_env or github_app, not both" }
197
+ });
200
198
  }
201
199
  var githubActionsSourceSchema = z2.looseObject({
202
200
  type: z2.literal("github-actions"),
@@ -220,24 +218,44 @@ var azureDevOpsSourceSchema = z2.looseObject({
220
218
  if (source.token_env && source.entra_token_file_env) {
221
219
  ctx.addIssue({
222
220
  code: "custom",
223
- message: "configure token_env or entra_token_file_env, not both"
221
+ message: "configure token_env or entra_token_file_env, not both",
222
+ params: { constraint: "configure token_env or entra_token_file_env, not both" }
224
223
  });
225
224
  }
226
225
  if (!source.token_env && !source.entra_token_file_env) {
227
226
  ctx.addIssue({
228
227
  code: "custom",
229
- message: "configure token_env or entra_token_file_env"
228
+ message: "configure token_env or entra_token_file_env",
229
+ params: { constraint: "configure token_env or entra_token_file_env" }
230
230
  });
231
231
  }
232
232
  });
233
233
  var gitlabInstanceUrlSchema = z2.url().superRefine((value2, ctx) => {
234
234
  const url = new URL(value2);
235
235
  if (url.protocol !== "https:")
236
- ctx.addIssue({ code: "custom", message: "must be an HTTPS GitLab instance URL" });
236
+ ctx.addIssue({
237
+ code: "custom",
238
+ message: "must be an HTTPS GitLab instance URL",
239
+ params: { constraint: "must be an HTTPS GitLab instance URL" }
240
+ });
237
241
  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" });
242
+ ctx.addIssue({
243
+ code: "custom",
244
+ message: "must not include credentials",
245
+ params: { constraint: "must not include credentials" }
246
+ });
247
+ if (url.search)
248
+ ctx.addIssue({
249
+ code: "custom",
250
+ message: "must not include a query string",
251
+ params: { constraint: "must not include a query string" }
252
+ });
253
+ if (url.hash)
254
+ ctx.addIssue({
255
+ code: "custom",
256
+ message: "must not include a fragment",
257
+ params: { constraint: "must not include a fragment" }
258
+ });
241
259
  });
242
260
  var gitlabCiSourceSchema = z2.looseObject({
243
261
  type: z2.literal("gitlab-ci"),
@@ -251,6 +269,17 @@ var gitlabCiSourceSchema = z2.looseObject({
251
269
  url: gitlabInstanceUrlSchema.optional().default("https://gitlab.com")
252
270
  });
253
271
  var sourceSchema = z2.looseObject({ type: z2.string().min(1), ...sourceAuthenticationSchema }).superRefine(exactlyOneGithubAuthenticationMode);
272
+ function credentialEnvironmentNames(source) {
273
+ const githubApp = githubAppSchema.safeParse(source.github_app);
274
+ return [
275
+ ...source.token_env ? [source.token_env] : [],
276
+ ...githubApp.success ? [
277
+ githubApp.data.app_id_env,
278
+ githubApp.data.private_key_env,
279
+ githubApp.data.installation_id_env
280
+ ] : []
281
+ ];
282
+ }
254
283
  var boardSchema = z2.object({
255
284
  refresh: durationSchema.optional(),
256
285
  running_refresh: durationSchema.optional(),
@@ -267,7 +296,8 @@ var boardSchema = z2.object({
267
296
  ctx.addIssue({
268
297
  code: "custom",
269
298
  path: [index, "id"],
270
- message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`
299
+ message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`,
300
+ params: { constraint: "Use a unique panel id within this board." }
271
301
  });
272
302
  });
273
303
  })
@@ -287,15 +317,14 @@ var boardConfigSchema = z2.object({
287
317
  auth: authSchema.optional()
288
318
  }).superRefine((config, ctx) => {
289
319
  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);
320
+ const sourceSchema2 = source.type === "azure-devops" ? azureDevOpsSourceSchema : source.type === "gitlab-ci" ? gitlabCiSourceSchema : void 0;
321
+ if (!sourceSchema2) continue;
322
+ const parsedSource = sourceSchema2.safeParse(source);
293
323
  if (!parsedSource.success) {
294
324
  for (const issue of parsedSource.error.issues) {
295
325
  ctx.addIssue({
296
- code: "custom",
297
- path: ["sources", sourceName, ...issue.path],
298
- message: issue.message
326
+ ...issue,
327
+ path: ["sources", sourceName, ...issue.path]
299
328
  });
300
329
  }
301
330
  }
@@ -308,7 +337,10 @@ var boardConfigSchema = z2.object({
308
337
  ctx.addIssue({
309
338
  code: "custom",
310
339
  path: ["boards", boardName, "panels", panelIndex, "pipeline"],
311
- message: "Azure DevOps pipeline-status panels require a positive numeric pipeline definition id"
340
+ message: "Azure DevOps pipeline-status panels require a positive numeric pipeline definition id",
341
+ params: {
342
+ constraint: "Provide a positive numeric Azure DevOps pipeline definition id."
343
+ }
312
344
  });
313
345
  }
314
346
  });
@@ -471,95 +503,376 @@ var httpValueSchema = z4.object({
471
503
  value: z4.union([z4.string(), z4.number(), z4.boolean()])
472
504
  });
473
505
 
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
506
+ // packages/server/src/adapters/azure-devops.ts
479
507
  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(),
508
+ var pipelinePanelSchema = z5.object({
493
509
  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
- }
510
+ type: z5.literal("pipeline-status"),
511
+ source: z5.string().min(1),
512
+ pipeline: z5.number().int().positive()
510
513
  });
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" });
514
+ var buildSchema = z5.object({
515
+ id: z5.number().int().positive(),
516
+ buildNumber: z5.string().min(1),
517
+ status: z5.string().nullable().optional(),
518
+ result: z5.string().nullable().optional(),
519
+ sourceBranch: z5.string().min(1).nullable().optional(),
520
+ startTime: z5.string().nullable().optional(),
521
+ finishTime: z5.string().nullable().optional(),
522
+ lastChangedDate: z5.string().nullable().optional()
522
523
  });
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
- });
524
+ var buildsSchema = z5.object({ value: z5.array(buildSchema) });
525
+ var timelineSchema = z5.object({
526
+ records: z5.array(
527
+ z5.object({
528
+ id: z5.string().min(1),
529
+ parentId: z5.string().min(1).nullable().optional(),
530
+ type: z5.string().min(1),
531
+ name: z5.string().min(1),
532
+ state: z5.string().nullable().optional()
533
+ })
534
+ )
535
+ });
536
+ function permittedAzureDevOpsCalls(panel, source) {
537
+ const { panel: parsedPanel, source: parsedSource } = z5.object({ panel: pipelinePanelSchema, source: azureDevOpsSourceSchema }).parse({ panel, source });
538
+ return [buildsCall(parsedPanel.pipeline, parsedSource)];
539
+ }
540
+ var delegatedTokenFileSchema = z5.object({
541
+ accessToken: z5.string().min(1),
542
+ expiresAt: z5.string().min(1)
543
+ });
544
+ function buildsCall(definitionId, source) {
545
+ const url = apiUrl(source, "build/builds");
546
+ url.searchParams.set("definitions", String(definitionId));
547
+ if (source.branch) url.searchParams.set("branchName", adoBranch(source.branch));
548
+ url.searchParams.set("queryOrder", "queueTimeDescending");
549
+ url.searchParams.set("$top", "1");
550
+ return { url: url.toString(), headers: new Headers({ accept: "application/json" }) };
551
+ }
552
+ function apiUrl(source, suffix) {
553
+ const url = new URL(
554
+ `https://dev.azure.com/${encodeURIComponent(source.organization)}/${encodeURIComponent(source.project)}/_apis/${suffix}`
555
+ );
556
+ url.searchParams.set("api-version", "7.1");
557
+ return url;
558
+ }
559
+ function adoBranch(branch) {
560
+ return branch.startsWith("refs/") ? branch : `refs/heads/${branch}`;
561
+ }
562
+
563
+ // packages/server/src/adapters/github-actions.ts
564
+ import { z as z6 } from "zod";
565
+ var pipelinePanelSchema2 = z6.object({
566
+ id: z6.string().min(1),
567
+ type: z6.literal("pipeline-status"),
568
+ source: z6.string().min(1),
569
+ /** GitHub accepts a workflow filename or numeric workflow id in this URL segment. */
570
+ pipeline: z6.string().min(1)
571
+ });
572
+ var pullRequestHealthPanelSchema = z6.object({
573
+ id: z6.string().min(1),
574
+ type: z6.literal("pull-request-health"),
575
+ source: z6.string().min(1),
576
+ base_branch: z6.string().min(1),
577
+ update_workflows: z6.array(
578
+ z6.object({
579
+ workflow: z6.string().min(1),
580
+ branch_prefixes: z6.array(z6.string().min(1)).min(1)
581
+ })
582
+ ).min(1),
583
+ build_workflow: z6.string().min(1)
584
+ });
585
+ var runSchema = z6.object({
586
+ id: z6.number().int().positive().optional(),
587
+ status: z6.string(),
588
+ conclusion: z6.string().nullable(),
589
+ name: z6.string().min(1),
590
+ html_url: z6.url(),
591
+ /** The branch that produced this run; absent for some event types. */
592
+ head_branch: z6.string().min(1).nullable().optional(),
593
+ // Timestamp quality should only remove timing advice, never make an otherwise useful run unreadable.
594
+ run_started_at: z6.string().nullable().optional(),
595
+ updated_at: z6.string().optional()
596
+ });
597
+ var runsSchema = z6.object({ workflow_runs: z6.array(runSchema) });
598
+ var jobsSchema = z6.object({
599
+ jobs: z6.array(
600
+ z6.object({
601
+ name: z6.string().min(1),
602
+ status: z6.string(),
603
+ steps: z6.array(z6.object({ name: z6.string().min(1), status: z6.string() })).optional()
604
+ })
605
+ )
606
+ });
607
+ var pullRequestsSchema = z6.array(
608
+ z6.object({
609
+ number: z6.number().int().positive(),
610
+ html_url: z6.url(),
611
+ head: z6.object({ ref: z6.string().min(1) }),
612
+ base: z6.object({ ref: z6.string().min(1) })
537
613
  })
614
+ );
615
+ function permittedGithubActionsCalls(panel, source, _credentials) {
616
+ const { panel: parsedPanel, source: parsedSource } = z6.object({ panel: pipelinePanelSchema2, source: githubActionsSourceSchema }).parse({ panel, source });
617
+ const url = new URL(
618
+ `https://api.github.com/repos/${parsedSource.repo}/actions/workflows/${encodeURIComponent(parsedPanel.pipeline)}/runs`
619
+ );
620
+ if (parsedSource.branch) url.searchParams.set("branch", parsedSource.branch);
621
+ url.searchParams.set("per_page", "1");
622
+ return [{ url: url.toString(), headers: new Headers() }];
623
+ }
624
+ function pullRequestHealthCapabilities(panel) {
625
+ const { panel: parsed } = z6.object({ panel: pullRequestHealthPanelSchema }).parse({ panel });
626
+ return {
627
+ panel: parsed,
628
+ configuredUpdateWorkflow(workflow) {
629
+ return parsed.update_workflows.find((candidate) => candidate.workflow === workflow);
630
+ },
631
+ permitsBuildBranch(branch) {
632
+ return parsed.update_workflows.some(
633
+ ({ branch_prefixes }) => branch_prefixes.some((prefix) => branch.startsWith(prefix))
634
+ );
635
+ }
636
+ };
637
+ }
638
+
639
+ // packages/server/src/adapters/gitlab-ci.ts
640
+ import { z as z7 } from "zod";
641
+ var pipelinePanelSchema3 = z7.object({
642
+ id: z7.string().min(1),
643
+ type: z7.literal("pipeline-status"),
644
+ source: z7.string().min(1)
538
645
  });
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()
646
+ var pipelineSchema = z7.object({
647
+ id: z7.number().int().positive(),
648
+ name: z7.string().min(1).nullable().optional(),
649
+ status: z7.string(),
650
+ ref: z7.string().min(1).nullable().optional(),
651
+ created_at: z7.string().nullable().optional(),
652
+ updated_at: z7.string().nullable().optional(),
653
+ web_url: z7.url()
550
654
  });
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
- ];
655
+ function permittedGitlabCiCalls(panel, source) {
656
+ const { source: parsedSource } = z7.object({ panel: pipelinePanelSchema3, source: gitlabCiSourceSchema }).parse({ panel, source });
657
+ return [pipelinesCall(parsedSource)];
658
+ }
659
+ function pipelinesCall(source) {
660
+ const url = new URL(source.url);
661
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/api/v4/projects/${encodeURIComponent(source.project)}/pipelines`;
662
+ url.searchParams.set("per_page", "1");
663
+ if (source.branch) url.searchParams.set("ref", source.branch);
664
+ return { url: url.toString(), headers: new Headers({ accept: "application/json" }) };
665
+ }
666
+
667
+ // packages/server/src/adapters/http-value.ts
668
+ import { z as z8 } from "zod";
669
+ function permittedHttpValueCalls(panel) {
670
+ const grouped = httpValueGroupedPanelSchema.safeParse(panel);
671
+ const urls = grouped.success ? grouped.data.facts.map((fact) => fact.url) : [z8.object({ panel: httpValueScalarPanelSchema }).parse({ panel }).panel.url];
672
+ return urls.map(permittedCall);
673
+ }
674
+ function permittedCall(value2) {
675
+ const url = new URL(value2);
676
+ if (url.protocol !== "http:" && url.protocol !== "https:")
677
+ throw new Error("http-value URLs must use http or https");
678
+ return { url: url.toString(), headers: new Headers({ accept: "application/json, text/plain" }) };
679
+ }
680
+
681
+ // packages/server/src/configuration-error.ts
682
+ var ConfigurationError = class extends Error {
683
+ constructor(message2, diagnostics) {
684
+ super(message2);
685
+ this.diagnostics = diagnostics;
686
+ }
687
+ diagnostics;
688
+ };
689
+ function configurationLocation(path) {
690
+ return path.reduce(
691
+ (location, part) => typeof part === "number" ? `${location}[${part}]` : `${location}${location ? "." : ""}${String(part)}`,
692
+ ""
693
+ ) || "configuration";
694
+ }
695
+ function boardConfigurationLocation(config, path) {
696
+ const [record, key, ...rest] = path;
697
+ if ((record === "boards" || record === "sources") && key !== void 0) {
698
+ const entries = config && typeof config === "object" ? Reflect.get(config, record) : void 0;
699
+ const index = entries && typeof entries === "object" ? Object.keys(entries).indexOf(String(key)) : -1;
700
+ return configurationLocation([record, index, ...rest]);
701
+ }
702
+ return configurationLocation(path);
703
+ }
704
+ function schemaDiagnostics(error, locate, kind = "schema") {
705
+ return error.issues.map((issue) => ({
706
+ kind,
707
+ location: locate(issue.path),
708
+ constraint: correctiveConstraint(issue)
709
+ }));
710
+ }
711
+ function correctiveConstraint(issue) {
712
+ switch (issue.code) {
713
+ case "invalid_type":
714
+ return `Provide a value of type ${issue.expected}.`;
715
+ case "too_big":
716
+ return `Use ${issue.inclusive ? "at most" : "fewer than"} ${issue.maximum} ${issue.origin === "array" ? "entries" : issue.origin === "string" ? "characters" : "as the value"}.`;
717
+ case "too_small":
718
+ return `Use ${issue.inclusive ? "at least" : "more than"} ${issue.minimum} ${issue.origin === "array" ? "entries" : issue.origin === "string" ? "characters" : "as the value"}.`;
719
+ // Format messages are fixed by our schemas (or Zod); unlike custom issues they contain no authored values.
720
+ case "invalid_format":
721
+ return issue.message;
722
+ case "invalid_value":
723
+ return "Use one of the values allowed by the board schema.";
724
+ case "custom":
725
+ return typeof issue.params?.constraint === "string" ? issue.params.constraint : "Correct this field according to the board schema.";
726
+ default:
727
+ return "Provide a value matching the board schema for this field.";
728
+ }
729
+ }
730
+
731
+ // packages/server/src/allowlist.ts
732
+ function deriveValidatedAllowlist(config) {
733
+ return derive(config, true);
734
+ }
735
+ function derive(config, rejectUnsupported) {
736
+ const allowed = /* @__PURE__ */ new Map();
737
+ for (const [boardName, board] of Object.entries(config.boards)) {
738
+ for (const panel of board.panels) {
739
+ const source = panel.source ? config.sources[panel.source] : void 0;
740
+ try {
741
+ const capability = panelCapability(panel, source);
742
+ if (capability.kind === "unsupported") {
743
+ if (rejectUnsupported) throw new Error("no bounded operations");
744
+ continue;
745
+ }
746
+ if (capability.kind === "server")
747
+ allowed.set(`${boardName}/${panel.id}`, new Set(capability.operations));
748
+ } catch (error) {
749
+ if (rejectUnsupported) {
750
+ const location = `boards[${Object.keys(config.boards).indexOf(boardName)}].panels[${board.panels.indexOf(panel)}]`;
751
+ throw new ConfigurationError(
752
+ unsupportedPanelOperation(boardName, panel, source, error).message,
753
+ error instanceof ZodError ? schemaDiagnostics(
754
+ error,
755
+ ([subject, ...path]) => {
756
+ const root = subject === "source" ? ["sources", Object.keys(config.sources).indexOf(panel.source ?? "")] : [
757
+ "boards",
758
+ Object.keys(config.boards).indexOf(boardName),
759
+ "panels",
760
+ board.panels.indexOf(panel)
761
+ ];
762
+ return configurationLocation([...root, ...path]);
763
+ },
764
+ "panel-admission"
765
+ ) : [
766
+ {
767
+ kind: "panel-admission",
768
+ location,
769
+ constraint: "Use a supported panel/source combination with bounded HTTP(S) operations."
770
+ }
771
+ ]
772
+ );
773
+ }
774
+ throw error;
775
+ }
776
+ }
777
+ }
778
+ return allowed;
779
+ }
780
+ function panelCapability(panel, source) {
781
+ if (panel.type === "pipeline-status" && source?.type === "github-actions") {
782
+ permittedGithubActionsCalls(panel, source);
783
+ return { kind: "server", operations: ["read"] };
784
+ }
785
+ if (panel.type === "pipeline-status" && source?.type === "azure-devops") {
786
+ permittedAzureDevOpsCalls(panel, source);
787
+ return { kind: "server", operations: ["read"] };
788
+ }
789
+ if (panel.type === "pipeline-status" && source?.type === "gitlab-ci") {
790
+ permittedGitlabCiCalls(panel, source);
791
+ return { kind: "server", operations: ["read"] };
792
+ }
793
+ if (panel.type === "http-value") {
794
+ permittedHttpValueCalls(panel);
795
+ return { kind: "server", operations: ["read"] };
796
+ }
797
+ if (panel.type === "pull-request-health" && source?.type === "github-actions") {
798
+ pullRequestHealthCapabilities(panel);
799
+ return {
800
+ kind: "server",
801
+ operations: ["pull-requests", "update-workflow", "pull-request-build"]
802
+ };
803
+ }
804
+ if (panel.type === "pipeline-animation-demo") return { kind: "client-only" };
805
+ return { kind: "unsupported" };
806
+ }
807
+ function unsupportedPanelOperation(boardName, panel, source, error) {
808
+ const sourceName = panel.source ?? "(none)";
809
+ const sourceType = source?.type ?? "(none)";
810
+ const detail = error instanceof Error && error.message !== "no bounded operations" ? ` ${error.message}` : "";
811
+ return new Error(
812
+ `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.`
813
+ );
814
+ }
815
+
816
+ // packages/server/src/board-config.ts
817
+ import { readFile as readFile2 } from "node:fs/promises";
818
+ import { parse as parseYaml, YAMLParseError } from "yaml";
819
+ async function loadBoardConfig(location, fetcher = globalThis.fetch, expectedSchemaUrl) {
820
+ const text = isUrl(location) ? await fetchBoardConfig(location, fetcher) : await readFile2(location, "utf-8");
821
+ let authoredSchemaUrl;
822
+ try {
823
+ authoredSchemaUrl = readBoardSchemaModeline(text, expectedSchemaUrl);
824
+ } catch (error) {
825
+ throw new ConfigurationError(error instanceof Error ? error.message : "Invalid modeline", [
826
+ {
827
+ kind: "modeline",
828
+ location: "line 1",
829
+ constraint: "Add a first-line yaml-language-server schema modeline ending in /board-config.schema.json."
830
+ }
831
+ ]);
832
+ }
833
+ let parsed;
834
+ try {
835
+ parsed = parseYaml(text);
836
+ } catch (error) {
837
+ const position = error instanceof YAMLParseError ? error.linePos?.[0] : void 0;
838
+ const location2 = position ? `line ${position.line}, column ${position.col}` : "configuration";
839
+ throw new ConfigurationError(
840
+ `Invalid board configuration YAML at ${location2}: correct YAML syntax.`,
841
+ [
842
+ {
843
+ kind: "yaml",
844
+ location: location2,
845
+ constraint: "Correct YAML syntax and indentation."
846
+ }
847
+ ]
848
+ );
849
+ }
850
+ const result = boardConfigSchema.safeParse(parsed);
851
+ if (!result.success) {
852
+ const stale = expectedSchemaUrl && authoredSchemaUrl !== expectedSchemaUrl ? `
853
+ Stale schema modeline: expected ${expectedSchemaUrl}` : "";
854
+ throw new ConfigurationError(
855
+ `Invalid board configuration:
856
+ ${result.error.message}${stale}`,
857
+ schemaDiagnostics(result.error, (path) => boardConfigurationLocation(parsed, path))
858
+ );
859
+ }
860
+ return result.data;
861
+ }
862
+ function isUrl(value2) {
863
+ return /^https?:\/\//i.test(value2);
864
+ }
865
+ async function fetchBoardConfig(url, fetcher) {
866
+ const response = await fetcher(url);
867
+ if (!response.ok)
868
+ throw new Error(
869
+ `Unable to fetch board configuration from ${url}: ${response.status} ${response.statusText}`
870
+ );
871
+ return response.text();
560
872
  }
561
873
 
562
874
  // packages/aws/src/release.ts
875
+ import { stringify } from "yaml";
563
876
  var CORE_RUNTIME_VERSION = "1.0.0";
564
877
  var CANONICAL_ASSET_DOMAIN = "https://public-assets.zegreatrob.com";
565
878
  function legacyAssetPath(version, domain = CANONICAL_ASSET_DOMAIN) {
@@ -582,22 +895,27 @@ function normalizeAssetPath(value2) {
582
895
  return assetPath;
583
896
  }
584
897
  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}`);
898
+ const config = await loadBoardConfig(resolve(path), void 0, expectedSchemaUrl).catch(
899
+ (error) => {
900
+ throw new Error(
901
+ `${path}: ${error instanceof Error ? error.message : "Invalid board configuration"}`
902
+ );
903
+ }
904
+ );
905
+ try {
906
+ deriveValidatedAllowlist(config);
907
+ } catch (error) {
908
+ throw new Error(
909
+ `${path}: ${error instanceof Error ? error.message : "Invalid panel configuration"}`
910
+ );
593
911
  }
594
912
  const yaml = `${boardSchemaModeline(expectedSchemaUrl)}
595
- ${stringify(result.data, { sortMapEntries: true })}`;
913
+ ${stringify(config, { sortMapEntries: true })}`;
596
914
  return {
597
915
  yaml,
598
916
  sha256: sha256(yaml),
599
- usesCredentials: Object.values(result.data.sources).some(
600
- (source2) => credentialEnvironmentNames2(source2).length > 0
917
+ usesCredentials: Object.values(config.sources).some(
918
+ (source) => credentialEnvironmentNames(source).length > 0
601
919
  )
602
920
  };
603
921
  }
@@ -750,11 +1068,11 @@ async function bootstrapTemplatePath(kind, mode = "lambda") {
750
1068
  return fileURLToPath(new URL(templates[kind][mode], import.meta.url));
751
1069
  }
752
1070
  async function bootstrapTemplate(kind, mode = "lambda") {
753
- return readFile2(await bootstrapTemplatePath(kind, mode), "utf8");
1071
+ return readFile3(await bootstrapTemplatePath(kind, mode), "utf8");
754
1072
  }
755
1073
  async function installedBootstrapDesiredState(config = {}) {
756
1074
  const packageManifest = JSON.parse(
757
- await readFile2(new URL("../package.json", import.meta.url), "utf8")
1075
+ await readFile3(new URL("../package.json", import.meta.url), "utf8")
758
1076
  );
759
1077
  return desiredStateFromTemplates(
760
1078
  packageVersion(packageManifest),
@@ -824,7 +1142,7 @@ function inspectTemplate(kind, path, template) {
824
1142
  }
825
1143
  async function bootstrapPlan(config) {
826
1144
  const packageManifest = JSON.parse(
827
- await readFile2(new URL("../package.json", import.meta.url), "utf8")
1145
+ await readFile3(new URL("../package.json", import.meta.url), "utf8")
828
1146
  );
829
1147
  const packageTemplates = await installedTemplateInspections(computeMode(config));
830
1148
  const installedDesiredState = desiredStateFromTemplates(
@@ -997,7 +1315,7 @@ function requiredBootstrapParameters(kind, values) {
997
1315
  }
998
1316
 
999
1317
  // packages/aws/src/index.ts
1000
- import { cp, mkdir as mkdir2, readFile as readFile4, rm, writeFile as writeFile3 } from "node:fs/promises";
1318
+ import { cp, readFile as readFile5, rm, writeFile as writeFile3 } from "node:fs/promises";
1001
1319
  import { join as join2, resolve as resolve2 } from "node:path";
1002
1320
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1003
1321
 
@@ -2039,7 +2357,7 @@ async function checkBootstrap(config, options, dependencies) {
2039
2357
  }
2040
2358
 
2041
2359
  // packages/aws/src/guided.ts
2042
- import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
2360
+ import { readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
2043
2361
 
2044
2362
  // packages/aws/src/handoff.ts
2045
2363
  var githubSubjectKeys = [
@@ -2555,7 +2873,7 @@ async function scaffoldBootstrapManifest(input) {
2555
2873
  async function upgradeBootstrapManifest(path) {
2556
2874
  let parsed;
2557
2875
  try {
2558
- parsed = JSON.parse(await readFile3(path, "utf8"));
2876
+ parsed = JSON.parse(await readFile4(path, "utf8"));
2559
2877
  } catch (error) {
2560
2878
  throw new Error(
2561
2879
  `Unable to read bootstrap manifest ${path}: ${error instanceof Error ? error.message : String(error)}`
@@ -2851,11 +3169,8 @@ function deploymentTemplate(template, values) {
2851
3169
  }
2852
3170
  async function packageLambda(options) {
2853
3171
  const outputDir = resolve2(options.outputDir);
2854
- await mkdir2(outputDir, { recursive: true });
2855
3172
  const runtimeDir = join2(outputDir, "lambda");
2856
- await mkdir2(runtimeDir, { recursive: true });
2857
3173
  const lambdaSource = fileURLToPath2(new URL("../dist/lambda.mjs", import.meta.url));
2858
- await cp(lambdaSource, join2(runtimeDir, "index.mjs"));
2859
3174
  const release = await assembleRelease({
2860
3175
  boardConfigPath: options.boardConfigPath,
2861
3176
  outputDir: runtimeDir,
@@ -2865,12 +3180,13 @@ async function packageLambda(options) {
2865
3180
  assetDomain: options.assetDomain,
2866
3181
  secretReference: options.secretReference
2867
3182
  });
3183
+ await cp(lambdaSource, join2(runtimeDir, "index.mjs"));
2868
3184
  const runtimeMetadata = {
2869
3185
  ...release.metadata,
2870
3186
  computeMode: "lambda",
2871
3187
  artifactChecksums: {
2872
3188
  ...release.metadata.artifactChecksums,
2873
- "index.mjs": sha256(await readFile4(join2(runtimeDir, "index.mjs")))
3189
+ "index.mjs": sha256(await readFile5(join2(runtimeDir, "index.mjs")))
2874
3190
  }
2875
3191
  };
2876
3192
  await writeFile3(join2(runtimeDir, "release.json"), `${JSON.stringify(runtimeMetadata, null, 2)}
@@ -2885,7 +3201,7 @@ async function packageLambda(options) {
2885
3201
  archiveFiles.map(async (name) => [
2886
3202
  name,
2887
3203
  [
2888
- strToU8(await readFile4(join2(runtimeDir, name), "utf8")),
3204
+ strToU8(await readFile5(join2(runtimeDir, name), "utf8")),
2889
3205
  { mtime: new Date(1980, 0, 1, 0, 0, 0), level: 9 }
2890
3206
  ]
2891
3207
  ])
@@ -2893,7 +3209,7 @@ async function packageLambda(options) {
2893
3209
  );
2894
3210
  await writeFile3(lambdaPath, zipSync(archive));
2895
3211
  await rm(runtimeDir, { recursive: true, force: true });
2896
- const lambdaChecksum = sha256(await readFile4(lambdaPath));
3212
+ const lambdaChecksum = sha256(await readFile5(lambdaPath));
2897
3213
  const deploymentChecksum = sha256(JSON.stringify(runtimeMetadata));
2898
3214
  const packagedRelease = {
2899
3215
  ...runtimeMetadata,
@@ -2914,7 +3230,6 @@ async function packageLambda(options) {
2914
3230
  }
2915
3231
  async function packageEcs(options) {
2916
3232
  const outputDir = resolve2(options.outputDir);
2917
- await mkdir2(outputDir, { recursive: true });
2918
3233
  const imageReference = requireImageDigest(options.imageReference ?? "");
2919
3234
  const release = await assembleRelease({
2920
3235
  boardConfigPath: options.boardConfigPath,
@@ -2943,13 +3258,13 @@ async function packageEcs(options) {
2943
3258
  );
2944
3259
  await writeFile3(
2945
3260
  join2(outputDir, "SHA256SUMS"),
2946
- `${sha256(await readFile4(join2(outputDir, "release.json")))} release.json
3261
+ `${sha256(await readFile5(join2(outputDir, "release.json")))} release.json
2947
3262
  `
2948
3263
  );
2949
3264
  return packagedRelease;
2950
3265
  }
2951
3266
  async function cloudFormationTemplate(mode = "lambda") {
2952
- return readFile4(
3267
+ return readFile5(
2953
3268
  fileURLToPath2(
2954
3269
  new URL(mode === "ecs" ? "../template-ecs.yml" : "../template.yml", import.meta.url)
2955
3270
  ),
@@ -2991,8 +3306,8 @@ async function templateContract(parameters) {
2991
3306
  )?.[0];
2992
3307
  const metadataBlock = template.match(/^Metadata:\n[\s\S]*?(?=^Parameters:\s*$)/m)?.[0];
2993
3308
  if (!parametersBlock) throw new Error("template has no Parameters block");
2994
- const parsedParameters = parse2(parametersBlock);
2995
- const parsedMetadata = metadataBlock ? parse2(metadataBlock) : {};
3309
+ const parsedParameters = parse(parametersBlock);
3310
+ const parsedMetadata = metadataBlock ? parse(metadataBlock) : {};
2996
3311
  const definitions = parsedParameters.Parameters ?? {};
2997
3312
  const managedValue = parsedMetadata.Metadata?.PackageManagedParameters ?? [];
2998
3313
  if (!Array.isArray(managedValue) || !managedValue.every((key) => typeof key === "string"))
@@ -3054,7 +3369,7 @@ async function runDoctor(options, dependencies = actualDependencies) {
3054
3369
  let parameterData;
3055
3370
  await check("Parameters/template", async () => {
3056
3371
  const parameters = readParameterValues(
3057
- JSON.parse(await readFile5(options.parametersPath, "utf8"))
3372
+ JSON.parse(await readFile6(options.parametersPath, "utf8"))
3058
3373
  );
3059
3374
  parameterData = await templateContract(parameters);
3060
3375
  return `${options.parametersPath} is compatible (${computeMode({ mode: parameterData.values.ComputeMode })})`;
@@ -3094,7 +3409,7 @@ async function runDoctor(options, dependencies = actualDependencies) {
3094
3409
  const template = await bootstrapTemplate("github-oidc");
3095
3410
  const expectedRevision = bootstrapTemplateRevision(template);
3096
3411
  await check("Bootstrap template", async () => {
3097
- const raw = JSON.parse(await readFile5(stackPath, "utf8"));
3412
+ const raw = JSON.parse(await readFile6(stackPath, "utf8"));
3098
3413
  const stack = raw && typeof raw === "object" && "Stacks" in raw ? raw.Stacks?.[0] : raw;
3099
3414
  if (!stack || typeof stack !== "object")
3100
3415
  throw new Error("captured stack JSON contains no stack");
@@ -3132,7 +3447,7 @@ var option = (name, fallback) => {
3132
3447
  };
3133
3448
  async function installedPackageVersion() {
3134
3449
  const packageManifest = JSON.parse(
3135
- await readFile6(new URL("../package.json", import.meta.url), "utf8")
3450
+ await readFile7(new URL("../package.json", import.meta.url), "utf8")
3136
3451
  );
3137
3452
  return typeof packageManifest.version === "string" ? packageManifest.version : "";
3138
3453
  }
@@ -3146,7 +3461,7 @@ function parameter(key, value2) {
3146
3461
  }
3147
3462
  async function existingParameters(path, allowMissing = false) {
3148
3463
  try {
3149
- const parsed = JSON.parse(await readFile6(path, "utf8"));
3464
+ const parsed = JSON.parse(await readFile7(path, "utf8"));
3150
3465
  if (!Array.isArray(parsed)) throw new Error(`${path} must contain a JSON parameter array`);
3151
3466
  const values = parsed.map((value2) => {
3152
3467
  if (!value2 || typeof value2 !== "object" || typeof value2.ParameterKey !== "string" || typeof value2.ParameterValue !== "string")
@@ -3170,7 +3485,7 @@ function bootstrapKind() {
3170
3485
  throw new Error("--kind must be core or github-oidc");
3171
3486
  }
3172
3487
  async function readBootstrapConfig(path) {
3173
- const parsed = JSON.parse(await readFile6(path, "utf8"));
3488
+ const parsed = JSON.parse(await readFile7(path, "utf8"));
3174
3489
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
3175
3490
  throw new Error("--config must contain a JSON object");
3176
3491
  return parsed;
@@ -3251,10 +3566,10 @@ async function templateParameters(mode = "lambda") {
3251
3566
  const template = await cloudFormationTemplate(mode);
3252
3567
  const block = template.match(/^Parameters:\n[\s\S]*?(?=^[A-Za-z][A-Za-z0-9]*:\s*$)/m)?.[0];
3253
3568
  if (!block) throw new Error("Unable to read Parameters from the CloudFormation template");
3254
- const parsed = parse3(block);
3569
+ const parsed = parse2(block);
3255
3570
  if (!parsed.Parameters) throw new Error("CloudFormation template has no Parameters");
3256
3571
  const metadataBlock = template.match(/^Metadata:\n[\s\S]*?(?=^Parameters:\s*$)/m)?.[0];
3257
- const metadata = metadataBlock ? parse3(metadataBlock).Metadata ?? {} : {};
3572
+ const metadata = metadataBlock ? parse2(metadataBlock).Metadata ?? {} : {};
3258
3573
  if (metadata.PackageManagedParameters !== void 0 && (!Array.isArray(metadata.PackageManagedParameters) || !metadata.PackageManagedParameters.every((key) => typeof key === "string")))
3259
3574
  throw new Error("CloudFormation template has invalid PackageManagedParameters metadata");
3260
3575
  const packageManaged = new Set(metadata.PackageManagedParameters ?? []);
@@ -3428,7 +3743,7 @@ try {
3428
3743
  if (action === "init") {
3429
3744
  const output = requiredOption("--output");
3430
3745
  try {
3431
- await readFile6(output, "utf8");
3746
+ await readFile7(output, "utf8");
3432
3747
  throw new Error(`Refusing to overwrite existing manifest: ${output}`);
3433
3748
  } catch (error) {
3434
3749
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
@@ -3486,9 +3801,9 @@ Review and commit this manifest before deployment.`
3486
3801
  config,
3487
3802
  configPath,
3488
3803
  workDir: option("--work-dir"),
3489
- coreStack: coreStackPath ? JSON.parse(await readFile6(coreStackPath, "utf8")) : void 0,
3804
+ coreStack: coreStackPath ? JSON.parse(await readFile7(coreStackPath, "utf8")) : void 0,
3490
3805
  coreStackPath,
3491
- githubOidcStack: githubStackPath ? JSON.parse(await readFile6(githubStackPath, "utf8")) : void 0,
3806
+ githubOidcStack: githubStackPath ? JSON.parse(await readFile7(githubStackPath, "utf8")) : void 0,
3492
3807
  githubOidcStackPath: githubStackPath,
3493
3808
  runner
3494
3809
  });
@@ -3510,9 +3825,9 @@ Review and commit this manifest before deployment.`
3510
3825
  config,
3511
3826
  configPath,
3512
3827
  workDir: option("--work-dir"),
3513
- coreStack: coreStackPath ? JSON.parse(await readFile6(coreStackPath, "utf8")) : void 0,
3828
+ coreStack: coreStackPath ? JSON.parse(await readFile7(coreStackPath, "utf8")) : void 0,
3514
3829
  coreStackPath,
3515
- githubOidcStack: githubStackPath ? JSON.parse(await readFile6(githubStackPath, "utf8")) : void 0,
3830
+ githubOidcStack: githubStackPath ? JSON.parse(await readFile7(githubStackPath, "utf8")) : void 0,
3516
3831
  githubOidcStackPath: githubStackPath,
3517
3832
  runner
3518
3833
  });
@@ -3527,8 +3842,8 @@ ${formatBootstrapRemediationText(handoff.remediation).join("\n")}`
3527
3842
  const githubStackPath = requiredOption("--github-oidc-stack-json");
3528
3843
  const verified = await verifyBootstrap({
3529
3844
  config,
3530
- coreStack: JSON.parse(await readFile6(coreStackPath, "utf8")),
3531
- githubOidcStack: JSON.parse(await readFile6(githubStackPath, "utf8"))
3845
+ coreStack: JSON.parse(await readFile7(coreStackPath, "utf8")),
3846
+ githubOidcStack: JSON.parse(await readFile7(githubStackPath, "utf8"))
3532
3847
  });
3533
3848
  printBootstrap(
3534
3849
  verified,
@@ -3612,7 +3927,7 @@ ${plan.notes.join("\n")}`);
3612
3927
  if (kind === "github-oidc" && coreStackPath) {
3613
3928
  coreOutputs = coreBootstrapOutputs(
3614
3929
  deployedBootstrapStack(
3615
- JSON.parse(await readFile6(coreStackPath, "utf8")),
3930
+ JSON.parse(await readFile7(coreStackPath, "utf8")),
3616
3931
  bootstrapContractVersion(await bootstrapTemplate("core", mode))
3617
3932
  )
3618
3933
  );
@@ -3625,7 +3940,7 @@ ${plan.notes.join("\n")}`);
3625
3940
  const deployedStackPath = option("--deployed-stack-json");
3626
3941
  if (deployedStackPath) {
3627
3942
  const stack = deployedBootstrapStack(
3628
- JSON.parse(await readFile6(deployedStackPath, "utf8")),
3943
+ JSON.parse(await readFile7(deployedStackPath, "utf8")),
3629
3944
  bootstrapContractVersion(await bootstrapTemplate(kind, mode))
3630
3945
  );
3631
3946
  if (kind === "github-oidc" && !(stack.Parameters ?? []).some(