@continuous-excellence/ze-great-dashboard-aws 0.18.4 → 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/cli.js CHANGED
@@ -157,6 +157,16 @@ var githubActionsSourceSchema = z2.looseObject({
157
157
  branch: z2.string().min(1).optional(),
158
158
  ...sourceAuthenticationSchema
159
159
  }).superRefine(exactlyOneGithubAuthenticationMode);
160
+ var azureDevOpsSourceSchema = z2.looseObject({
161
+ type: z2.literal("azure-devops"),
162
+ /** Azure DevOps organization name, used as one URL path segment. */
163
+ organization: z2.string().regex(/^[^/\s]+$/, "must be an organization name"),
164
+ project: z2.string().min(1),
165
+ /** The branch whose build health the dashboard represents. */
166
+ branch: z2.string().min(1).optional(),
167
+ /** Azure DevOps Build API access is authenticated with a read-scoped PAT. */
168
+ token_env: z2.string().min(1)
169
+ });
160
170
  var sourceSchema = z2.looseObject({ type: z2.string().min(1), ...sourceAuthenticationSchema }).superRefine(exactlyOneGithubAuthenticationMode);
161
171
  var boardSchema = z2.object({
162
172
  refresh: durationSchema.optional(),
@@ -192,6 +202,33 @@ var boardConfigSchema = z2.object({
192
202
  sources: z2.record(z2.string().min(1), sourceSchema).default({}),
193
203
  boards: z2.record(z2.string().min(1), boardSchema),
194
204
  auth: authSchema.optional()
205
+ }).superRefine((config, ctx) => {
206
+ for (const [sourceName, source] of Object.entries(config.sources)) {
207
+ if (source.type !== "azure-devops") continue;
208
+ const parsedSource = azureDevOpsSourceSchema.safeParse(source);
209
+ if (!parsedSource.success) {
210
+ for (const issue of parsedSource.error.issues) {
211
+ ctx.addIssue({
212
+ code: "custom",
213
+ path: ["sources", sourceName, ...issue.path],
214
+ message: issue.message
215
+ });
216
+ }
217
+ }
218
+ }
219
+ for (const [boardName, board] of Object.entries(config.boards)) {
220
+ board.panels.forEach((panel, panelIndex) => {
221
+ const source = panel.source ? config.sources[panel.source] : void 0;
222
+ if (panel.type !== "pipeline-status" || source?.type !== "azure-devops") return;
223
+ if (typeof panel.pipeline !== "number" || !Number.isInteger(panel.pipeline) || panel.pipeline <= 0) {
224
+ ctx.addIssue({
225
+ code: "custom",
226
+ path: ["boards", boardName, "panels", panelIndex, "pipeline"],
227
+ message: "Azure DevOps pipeline-status panels require a positive numeric pipeline definition id"
228
+ });
229
+ }
230
+ });
231
+ }
195
232
  });
196
233
 
197
234
  // packages/shared/src/board-config-modeline.ts
@@ -235,15 +272,13 @@ var clientEnvSchema = z3.object({
235
272
  proxyPath: z3.string().min(1),
236
273
  /** Which board this is, resolved server-side so the client parses no URLs. */
237
274
  board: z3.string().min(1),
238
- /** The client version being served, so two published versions are visibly distinguishable. */
239
- clientVersion: z3.string().min(1),
240
275
  /** Omitted entirely when unauthenticated — presence is what turns auth on. */
241
276
  auth: z3.object({
242
277
  issuer: z3.string().min(1),
243
278
  clientId: z3.string().min(1).optional()
244
279
  }).optional()
245
280
  });
246
- var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true, clientVersion: true });
281
+ var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true });
247
282
 
248
283
  // packages/shared/src/envelope.ts
249
284
  import { z as z4 } from "zod";
@@ -416,11 +451,25 @@ function credentialEnvironmentNames2(source) {
416
451
  // packages/aws/src/release.ts
417
452
  var CORE_RUNTIME_VERSION = "1.0.0";
418
453
  var CANONICAL_ASSET_DOMAIN = "https://public-assets.zegreatrob.com";
419
- function clientAssetUrl(version, domain = CANONICAL_ASSET_DOMAIN) {
454
+ function legacyAssetPath(version, domain = CANONICAL_ASSET_DOMAIN) {
420
455
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version))
421
456
  throw new Error(`Invalid dashboard version: ${version}`);
422
457
  return `${domain.replace(/\/+$/, "")}/dashboard/${version}`;
423
458
  }
459
+ function normalizeAssetPath(value2) {
460
+ const assetPath = value2.replace(/\/+$/, "");
461
+ let url;
462
+ try {
463
+ url = new URL(assetPath);
464
+ } catch {
465
+ throw new Error("--asset-path must be an absolute HTTP(S) URL");
466
+ }
467
+ if (!["http:", "https:"].includes(url.protocol) || !url.hostname || url.username || url.password || url.search || url.hash)
468
+ throw new Error(
469
+ "--asset-path must be an absolute HTTP(S) URL without credentials, query, or fragment"
470
+ );
471
+ return assetPath;
472
+ }
424
473
  async function validateBoardConfig(path, expectedSchemaUrl) {
425
474
  const source = await readFile(resolve(path), "utf8");
426
475
  const authoredSchemaUrl = readBoardSchemaModeline(source, expectedSchemaUrl);
@@ -442,10 +491,14 @@ ${stringify(result.data, { sortMapEntries: true })}`;
442
491
  };
443
492
  }
444
493
  async function assembleRelease(input) {
445
- const assetUrl = clientAssetUrl(input.version, input.assetDomain);
494
+ if (input.assetPath && input.assetDomain)
495
+ throw new Error("--asset-path and --asset-domain cannot be used together");
496
+ const assetPath = normalizeAssetPath(
497
+ input.assetPath ?? legacyAssetPath(input.version, input.assetDomain)
498
+ );
446
499
  const board = await validateBoardConfig(
447
500
  input.boardConfigPath,
448
- `${assetUrl}/${boardSchemaFileName}`
501
+ `${assetPath}/${boardSchemaFileName}`
449
502
  );
450
503
  if (board.usesCredentials && !input.secretReference)
451
504
  throw new Error(
@@ -456,7 +509,7 @@ async function assembleRelease(input) {
456
509
  await writeFile(join(outputDir, "board.yaml"), board.yaml);
457
510
  const metadata = {
458
511
  dashboardVersion: input.version,
459
- clientAssetUrl: assetUrl,
512
+ assetPath,
460
513
  serverRuntimeVersion: CORE_RUNTIME_VERSION,
461
514
  supportedProviders: input.providers ?? ["aws-lambda"],
462
515
  artifactChecksums: { "board.yaml": board.sha256 },
@@ -2700,6 +2753,7 @@ async function packageLambda(options) {
2700
2753
  outputDir: runtimeDir,
2701
2754
  version: options.version,
2702
2755
  providers: ["aws-lambda"],
2756
+ assetPath: options.assetPath,
2703
2757
  assetDomain: options.assetDomain,
2704
2758
  secretReference: options.secretReference
2705
2759
  });
@@ -2745,7 +2799,7 @@ async function packageLambda(options) {
2745
2799
  deploymentTemplate(await cloudFormationTemplate("lambda"), {
2746
2800
  ComputeMode: "lambda",
2747
2801
  LambdaArtifactKey: packagedRelease.artifactKey,
2748
- DashboardVersion: packagedRelease.dashboardVersion
2802
+ AssetPath: packagedRelease.assetPath
2749
2803
  })
2750
2804
  );
2751
2805
  return packagedRelease;
@@ -2759,6 +2813,7 @@ async function packageEcs(options) {
2759
2813
  outputDir,
2760
2814
  version: options.version,
2761
2815
  providers: ["aws-ecs-fargate"],
2816
+ assetPath: options.assetPath,
2762
2817
  assetDomain: options.assetDomain,
2763
2818
  secretReference: options.secretReference
2764
2819
  });
@@ -2775,7 +2830,7 @@ async function packageEcs(options) {
2775
2830
  deploymentTemplate(await cloudFormationTemplate("ecs"), {
2776
2831
  ComputeMode: "ecs",
2777
2832
  Image: imageReference,
2778
- DashboardVersion: packagedRelease.dashboardVersion
2833
+ AssetPath: packagedRelease.assetPath
2779
2834
  })
2780
2835
  );
2781
2836
  await writeFile3(
@@ -2944,10 +2999,8 @@ async function runDoctor(options, dependencies = actualDependencies) {
2944
2999
  return `${bucket} exists in ${bucketRegion}`;
2945
3000
  });
2946
3001
  await check("Hosted client", async () => {
2947
- const baseUrl = parameterData?.definitions.AssetBaseUrl?.Default;
2948
- if (typeof baseUrl !== "string") throw new Error("template has no AssetBaseUrl default");
2949
3002
  if (!dependencies.packageVersion) throw new Error("installed package version is unavailable");
2950
- const url = `${baseUrl.replace(/\/+$/, "")}/dashboard/${dependencies.packageVersion}/index.html`;
3003
+ const url = `https://public-assets.zegreatrob.com/dashboard/${dependencies.packageVersion}/index.html`;
2951
3004
  const response = await dependencies.fetch(url);
2952
3005
  if (!response.ok) throw new Error(`${url} returned HTTP ${response.status}`);
2953
3006
  return `${url} returned HTTP ${response.status}`;
@@ -3583,6 +3636,8 @@ ${shellCommand(awsCommand)}`,
3583
3636
  if (!boardConfig) throw new Error("--board-config is required");
3584
3637
  if (!version)
3585
3638
  throw new Error("Unable to determine the package version; pass --version explicitly");
3639
+ if (option("--asset-domain"))
3640
+ console.warn("--asset-domain is deprecated; use --asset-path with the complete asset URL");
3586
3641
  const outputDir = option("--output", "aws-dashboard-release") ?? "aws-dashboard-release";
3587
3642
  const parametersPath = option("--parameters", "aws-dashboard-parameters.json") ?? "aws-dashboard-parameters.json";
3588
3643
  const rawParameters = await existingParameters(parametersPath);
@@ -3597,12 +3652,14 @@ ${shellCommand(awsCommand)}`,
3597
3652
  boardConfigPath: boardConfig,
3598
3653
  outputDir,
3599
3654
  version,
3655
+ assetPath: option("--asset-path"),
3600
3656
  assetDomain: option("--asset-domain"),
3601
3657
  secretReference
3602
3658
  }) : await packageEcs({
3603
3659
  boardConfigPath: boardConfig,
3604
3660
  outputDir,
3605
3661
  version,
3662
+ assetPath: option("--asset-path"),
3606
3663
  assetDomain: option("--asset-domain"),
3607
3664
  secretReference,
3608
3665
  imageReference: option("--image")
@@ -3611,7 +3668,7 @@ ${shellCommand(awsCommand)}`,
3611
3668
  ComputeMode: mode,
3612
3669
  LambdaArtifactKey: metadata.artifactKey,
3613
3670
  Image: metadata.image ?? "",
3614
- DashboardVersion: metadata.dashboardVersion
3671
+ AssetPath: metadata.assetPath
3615
3672
  });
3616
3673
  if (mode === "lambda" && !rawParameters.some(({ ParameterKey }) => ParameterKey === "ComputeMode"))
3617
3674
  resolvedParameters.splice(
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { type BootstrapAffectedStack, type BootstrapRecoveryCommand, type Bootst
7
7
  export type ReleaseMetadata = {
8
8
  computeMode: ComputeMode;
9
9
  dashboardVersion: string;
10
- clientAssetUrl: string;
10
+ assetPath: string;
11
11
  serverRuntimeVersion: string;
12
12
  supportedProviders: string[];
13
13
  artifactChecksums: Record<string, string>;
@@ -23,6 +23,8 @@ export type LambdaPackageOptions = {
23
23
  boardConfigPath: string;
24
24
  outputDir: string;
25
25
  version: string;
26
+ assetPath?: string;
27
+ /** @deprecated Use assetPath. */
26
28
  assetDomain?: string;
27
29
  /** ARN of the consumer-owned JSON credential-map secret, never a secret value. */
28
30
  secretReference?: string;
package/dist/index.js CHANGED
@@ -839,6 +839,16 @@ var githubActionsSourceSchema = z2.looseObject({
839
839
  branch: z2.string().min(1).optional(),
840
840
  ...sourceAuthenticationSchema
841
841
  }).superRefine(exactlyOneGithubAuthenticationMode);
842
+ var azureDevOpsSourceSchema = z2.looseObject({
843
+ type: z2.literal("azure-devops"),
844
+ /** Azure DevOps organization name, used as one URL path segment. */
845
+ organization: z2.string().regex(/^[^/\s]+$/, "must be an organization name"),
846
+ project: z2.string().min(1),
847
+ /** The branch whose build health the dashboard represents. */
848
+ branch: z2.string().min(1).optional(),
849
+ /** Azure DevOps Build API access is authenticated with a read-scoped PAT. */
850
+ token_env: z2.string().min(1)
851
+ });
842
852
  var sourceSchema = z2.looseObject({ type: z2.string().min(1), ...sourceAuthenticationSchema }).superRefine(exactlyOneGithubAuthenticationMode);
843
853
  var boardSchema = z2.object({
844
854
  refresh: durationSchema.optional(),
@@ -874,6 +884,33 @@ var boardConfigSchema = z2.object({
874
884
  sources: z2.record(z2.string().min(1), sourceSchema).default({}),
875
885
  boards: z2.record(z2.string().min(1), boardSchema),
876
886
  auth: authSchema.optional()
887
+ }).superRefine((config, ctx) => {
888
+ for (const [sourceName, source] of Object.entries(config.sources)) {
889
+ if (source.type !== "azure-devops") continue;
890
+ const parsedSource = azureDevOpsSourceSchema.safeParse(source);
891
+ if (!parsedSource.success) {
892
+ for (const issue of parsedSource.error.issues) {
893
+ ctx.addIssue({
894
+ code: "custom",
895
+ path: ["sources", sourceName, ...issue.path],
896
+ message: issue.message
897
+ });
898
+ }
899
+ }
900
+ }
901
+ for (const [boardName, board] of Object.entries(config.boards)) {
902
+ board.panels.forEach((panel, panelIndex) => {
903
+ const source = panel.source ? config.sources[panel.source] : void 0;
904
+ if (panel.type !== "pipeline-status" || source?.type !== "azure-devops") return;
905
+ if (typeof panel.pipeline !== "number" || !Number.isInteger(panel.pipeline) || panel.pipeline <= 0) {
906
+ ctx.addIssue({
907
+ code: "custom",
908
+ path: ["boards", boardName, "panels", panelIndex, "pipeline"],
909
+ message: "Azure DevOps pipeline-status panels require a positive numeric pipeline definition id"
910
+ });
911
+ }
912
+ });
913
+ }
877
914
  });
878
915
 
879
916
  // packages/shared/src/board-config-modeline.ts
@@ -917,15 +954,13 @@ var clientEnvSchema = z3.object({
917
954
  proxyPath: z3.string().min(1),
918
955
  /** Which board this is, resolved server-side so the client parses no URLs. */
919
956
  board: z3.string().min(1),
920
- /** The client version being served, so two published versions are visibly distinguishable. */
921
- clientVersion: z3.string().min(1),
922
957
  /** Omitted entirely when unauthenticated — presence is what turns auth on. */
923
958
  auth: z3.object({
924
959
  issuer: z3.string().min(1),
925
960
  clientId: z3.string().min(1).optional()
926
961
  }).optional()
927
962
  });
928
- var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true, clientVersion: true });
963
+ var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true });
929
964
 
930
965
  // packages/shared/src/envelope.ts
931
966
  import { z as z4 } from "zod";
@@ -1098,11 +1133,25 @@ function credentialEnvironmentNames2(source) {
1098
1133
  // packages/aws/src/release.ts
1099
1134
  var CORE_RUNTIME_VERSION = "1.0.0";
1100
1135
  var CANONICAL_ASSET_DOMAIN = "https://public-assets.zegreatrob.com";
1101
- function clientAssetUrl(version, domain = CANONICAL_ASSET_DOMAIN) {
1136
+ function legacyAssetPath(version, domain = CANONICAL_ASSET_DOMAIN) {
1102
1137
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version))
1103
1138
  throw new Error(`Invalid dashboard version: ${version}`);
1104
1139
  return `${domain.replace(/\/+$/, "")}/dashboard/${version}`;
1105
1140
  }
1141
+ function normalizeAssetPath(value2) {
1142
+ const assetPath = value2.replace(/\/+$/, "");
1143
+ let url;
1144
+ try {
1145
+ url = new URL(assetPath);
1146
+ } catch {
1147
+ throw new Error("--asset-path must be an absolute HTTP(S) URL");
1148
+ }
1149
+ if (!["http:", "https:"].includes(url.protocol) || !url.hostname || url.username || url.password || url.search || url.hash)
1150
+ throw new Error(
1151
+ "--asset-path must be an absolute HTTP(S) URL without credentials, query, or fragment"
1152
+ );
1153
+ return assetPath;
1154
+ }
1106
1155
  async function validateBoardConfig(path, expectedSchemaUrl) {
1107
1156
  const source = await readFile(resolve(path), "utf8");
1108
1157
  const authoredSchemaUrl = readBoardSchemaModeline(source, expectedSchemaUrl);
@@ -1124,10 +1173,14 @@ ${stringify(result.data, { sortMapEntries: true })}`;
1124
1173
  };
1125
1174
  }
1126
1175
  async function assembleRelease(input) {
1127
- const assetUrl = clientAssetUrl(input.version, input.assetDomain);
1176
+ if (input.assetPath && input.assetDomain)
1177
+ throw new Error("--asset-path and --asset-domain cannot be used together");
1178
+ const assetPath = normalizeAssetPath(
1179
+ input.assetPath ?? legacyAssetPath(input.version, input.assetDomain)
1180
+ );
1128
1181
  const board = await validateBoardConfig(
1129
1182
  input.boardConfigPath,
1130
- `${assetUrl}/${boardSchemaFileName}`
1183
+ `${assetPath}/${boardSchemaFileName}`
1131
1184
  );
1132
1185
  if (board.usesCredentials && !input.secretReference)
1133
1186
  throw new Error(
@@ -1138,7 +1191,7 @@ async function assembleRelease(input) {
1138
1191
  await writeFile(join(outputDir, "board.yaml"), board.yaml);
1139
1192
  const metadata = {
1140
1193
  dashboardVersion: input.version,
1141
- clientAssetUrl: assetUrl,
1194
+ assetPath,
1142
1195
  serverRuntimeVersion: CORE_RUNTIME_VERSION,
1143
1196
  supportedProviders: input.providers ?? ["aws-lambda"],
1144
1197
  artifactChecksums: { "board.yaml": board.sha256 },
@@ -2688,6 +2741,7 @@ async function packageLambda(options) {
2688
2741
  outputDir: runtimeDir,
2689
2742
  version: options.version,
2690
2743
  providers: ["aws-lambda"],
2744
+ assetPath: options.assetPath,
2691
2745
  assetDomain: options.assetDomain,
2692
2746
  secretReference: options.secretReference
2693
2747
  });
@@ -2733,7 +2787,7 @@ async function packageLambda(options) {
2733
2787
  deploymentTemplate(await cloudFormationTemplate("lambda"), {
2734
2788
  ComputeMode: "lambda",
2735
2789
  LambdaArtifactKey: packagedRelease.artifactKey,
2736
- DashboardVersion: packagedRelease.dashboardVersion
2790
+ AssetPath: packagedRelease.assetPath
2737
2791
  })
2738
2792
  );
2739
2793
  return packagedRelease;
@@ -2747,6 +2801,7 @@ async function packageEcs(options) {
2747
2801
  outputDir,
2748
2802
  version: options.version,
2749
2803
  providers: ["aws-ecs-fargate"],
2804
+ assetPath: options.assetPath,
2750
2805
  assetDomain: options.assetDomain,
2751
2806
  secretReference: options.secretReference
2752
2807
  });
@@ -2763,7 +2818,7 @@ async function packageEcs(options) {
2763
2818
  deploymentTemplate(await cloudFormationTemplate("ecs"), {
2764
2819
  ComputeMode: "ecs",
2765
2820
  Image: imageReference,
2766
- DashboardVersion: packagedRelease.dashboardVersion
2821
+ AssetPath: packagedRelease.assetPath
2767
2822
  })
2768
2823
  );
2769
2824
  await writeFile3(