@continuous-excellence/ze-great-dashboard-aws 0.16.0 → 0.17.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/board-config.schema.json +121 -0
- package/client/assets/index-CSO187vo.js.map +1 -1
- package/client/board-config.schema.json +121 -0
- package/dist/cli.js +309 -41
- package/dist/index.js +309 -41
- package/dist/lambda.mjs +52 -9
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -728,33 +728,87 @@ import { parseDocument, Scalar } from "yaml";
|
|
|
728
728
|
import { createHash } from "node:crypto";
|
|
729
729
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
730
730
|
import { join, resolve } from "node:path";
|
|
731
|
-
|
|
731
|
+
|
|
732
|
+
// packages/shared/src/board-config.ts
|
|
732
733
|
import { z as z2 } from "zod";
|
|
733
734
|
|
|
734
|
-
// packages/
|
|
735
|
+
// packages/shared/src/duration.ts
|
|
735
736
|
import { z } from "zod";
|
|
736
|
-
var
|
|
737
|
-
var
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
737
|
+
var DURATION_PATTERN = /^(\d+)(ms|s|m|h)$/;
|
|
738
|
+
var UNIT_MILLIS = {
|
|
739
|
+
ms: 1,
|
|
740
|
+
s: 1e3,
|
|
741
|
+
m: 6e4,
|
|
742
|
+
h: 36e5
|
|
743
|
+
};
|
|
744
|
+
function parseDuration(value2) {
|
|
745
|
+
const match = DURATION_PATTERN.exec(value2);
|
|
746
|
+
if (!match) return null;
|
|
747
|
+
const [, rawAmount, rawUnit] = match;
|
|
748
|
+
if (rawAmount === void 0 || rawUnit === void 0) return null;
|
|
749
|
+
const amount = Number(rawAmount);
|
|
750
|
+
if (amount <= 0) return null;
|
|
751
|
+
return amount * UNIT_MILLIS[rawUnit];
|
|
752
|
+
}
|
|
753
|
+
var durationSchema = z.string().refine((value2) => parseDuration(value2) !== null, {
|
|
754
|
+
message: 'must be a positive duration like "30s", "5m", or "1h"'
|
|
755
|
+
}).brand();
|
|
756
|
+
|
|
757
|
+
// packages/shared/src/board-config.ts
|
|
758
|
+
var positionSchema = z2.union([
|
|
759
|
+
z2.object({
|
|
760
|
+
x: z2.number().int().min(0),
|
|
761
|
+
y: z2.number().int().min(0),
|
|
762
|
+
w: z2.number().int().min(1).max(12),
|
|
763
|
+
h: z2.number().int().min(1)
|
|
743
764
|
}),
|
|
744
|
-
|
|
765
|
+
z2.object({ x: z2.literal(0), y: z2.literal(0), w: z2.literal(0), h: z2.literal(0) })
|
|
745
766
|
]);
|
|
746
|
-
var
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
767
|
+
var panelDensities = ["auto", "comfortable", "compact"];
|
|
768
|
+
var panelDensitySchema = z2.enum(panelDensities);
|
|
769
|
+
var visibleRunningAnimations = [
|
|
770
|
+
"radial",
|
|
771
|
+
"runway",
|
|
772
|
+
"orbit",
|
|
773
|
+
"signal-field",
|
|
774
|
+
"telemetry-bloom",
|
|
775
|
+
"release-transit",
|
|
776
|
+
"status-weather",
|
|
777
|
+
"falling-shapes"
|
|
778
|
+
];
|
|
779
|
+
var runningAnimationSchema = z2.enum([...visibleRunningAnimations, "off"]);
|
|
780
|
+
var panelSchema = z2.looseObject({
|
|
781
|
+
/** Presentation-only wall label. `id` remains the stable proxy and allowlist address. */
|
|
782
|
+
label: z2.string().min(1).optional(),
|
|
783
|
+
id: z2.string().min(1),
|
|
784
|
+
type: z2.string().min(1),
|
|
785
|
+
source: z2.string().min(1).optional(),
|
|
786
|
+
pipeline: z2.union([z2.string(), z2.number()]).optional(),
|
|
787
|
+
/** Resolved public source branch, supplied by the board endpoint for client memory scoping. */
|
|
788
|
+
branch: z2.string().min(1).optional(),
|
|
789
|
+
/** Content-density bias; position still controls the panel's explicit grid placement. */
|
|
790
|
+
density: panelDensitySchema.optional(),
|
|
791
|
+
/** Advisory in v1 — a panel without a position renders in config order rather than not at all. */
|
|
753
792
|
position: positionSchema.optional(),
|
|
754
793
|
refresh: durationSchema.optional(),
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
794
|
+
/** Active pipeline runs are checked more often than normal panels. */
|
|
795
|
+
running_refresh: durationSchema.optional(),
|
|
796
|
+
/** Polling cadence around the estimated completion boundary. */
|
|
797
|
+
running_completion_refresh: durationSchema.optional(),
|
|
798
|
+
/** Maximum length of the tighter completion polling burst. */
|
|
799
|
+
running_completion_window: durationSchema.optional(),
|
|
800
|
+
/** Optional active-run treatment. Omitted selects a visible treatment at random. */
|
|
801
|
+
running_animation: runningAnimationSchema.optional(),
|
|
802
|
+
/** Local animation-demo cycle duration; ignored by other panel types. */
|
|
803
|
+
demo_run_duration: durationSchema.optional(),
|
|
804
|
+
/** Local animation-demo focused-review duration; ignored by other panel types. */
|
|
805
|
+
demo_review_duration: durationSchema.optional(),
|
|
806
|
+
/** A deliberate override only. Adapters derive links; hand-written ones drift. */
|
|
807
|
+
link: z2.url().optional(),
|
|
808
|
+
/** Source-agnostic endpoint used by the http-value signal. */
|
|
809
|
+
url: z2.url().optional(),
|
|
810
|
+
/** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */
|
|
811
|
+
json_path: z2.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/, "must be a simple JSON path").optional()
|
|
758
812
|
}).superRefine((panel, ctx) => {
|
|
759
813
|
if ("display" in panel) {
|
|
760
814
|
ctx.addIssue({
|
|
@@ -764,13 +818,218 @@ var panelSchema = z.looseObject({
|
|
|
764
818
|
});
|
|
765
819
|
}
|
|
766
820
|
});
|
|
767
|
-
var sourceSchema =
|
|
768
|
-
type:
|
|
769
|
-
|
|
821
|
+
var sourceSchema = z2.looseObject({
|
|
822
|
+
type: z2.string().min(1),
|
|
823
|
+
/** Credentials are never in the config — only the name of the env var holding one. */
|
|
824
|
+
token_env: z2.string().min(1).optional()
|
|
770
825
|
});
|
|
771
|
-
var boardSchema =
|
|
826
|
+
var boardSchema = z2.object({
|
|
772
827
|
refresh: durationSchema.optional(),
|
|
773
|
-
|
|
828
|
+
running_refresh: durationSchema.optional(),
|
|
829
|
+
running_completion_refresh: durationSchema.optional(),
|
|
830
|
+
running_completion_window: durationSchema.optional(),
|
|
831
|
+
panels: z2.array(panelSchema).min(1).superRefine((panels, ctx) => {
|
|
832
|
+
const seen = /* @__PURE__ */ new Map();
|
|
833
|
+
panels.forEach((panel, index) => {
|
|
834
|
+
const firstIndex = seen.get(panel.id);
|
|
835
|
+
if (firstIndex === void 0) {
|
|
836
|
+
seen.set(panel.id, index);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
ctx.addIssue({
|
|
840
|
+
code: "custom",
|
|
841
|
+
path: [index, "id"],
|
|
842
|
+
message: `duplicate panel id "${panel.id}" (already used by panel at index ${firstIndex})`
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
})
|
|
846
|
+
});
|
|
847
|
+
var authSchema = z2.object({
|
|
848
|
+
issuer: z2.url(),
|
|
849
|
+
/** Absent in gateway mode — the gateway does the flow, the proxy still validates. */
|
|
850
|
+
client_id: z2.string().min(1).optional(),
|
|
851
|
+
allow: z2.object({
|
|
852
|
+
groups: z2.array(z2.string().min(1)).optional(),
|
|
853
|
+
subjects: z2.array(z2.string().min(1)).optional()
|
|
854
|
+
}).optional()
|
|
855
|
+
});
|
|
856
|
+
var boardConfigSchema = z2.object({
|
|
857
|
+
sources: z2.record(z2.string().min(1), sourceSchema).default({}),
|
|
858
|
+
boards: z2.record(z2.string().min(1), boardSchema),
|
|
859
|
+
auth: authSchema.optional()
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// packages/shared/src/board-config-modeline.ts
|
|
863
|
+
var MODEL_LINE = /^# yaml-language-server:\s*\$schema=(\S+)\s*$/;
|
|
864
|
+
var SCHEMA_FILE = "board-config.schema.json";
|
|
865
|
+
function readBoardSchemaModeline(source) {
|
|
866
|
+
const line = source.split(/\r?\n/).find((candidate) => candidate.includes("yaml-language-server"));
|
|
867
|
+
if (!line) throw new Error(`Board configuration must include a ${SCHEMA_FILE} modeline`);
|
|
868
|
+
const match = MODEL_LINE.exec(line);
|
|
869
|
+
if (!match?.[1]) {
|
|
870
|
+
throw new Error(
|
|
871
|
+
`Malformed board schema modeline; expected "# yaml-language-server: $schema=<URL>/${SCHEMA_FILE}"`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
let url;
|
|
875
|
+
try {
|
|
876
|
+
url = new URL(match[1]);
|
|
877
|
+
} catch {
|
|
878
|
+
throw new Error(`Malformed board schema modeline; schema URL is not a valid URL: ${match[1]}`);
|
|
879
|
+
}
|
|
880
|
+
if (!["http:", "https:"].includes(url.protocol) || !url.pathname.endsWith(`/${SCHEMA_FILE}`)) {
|
|
881
|
+
throw new Error(`Malformed board schema modeline; URL must end in /${SCHEMA_FILE}: ${match[1]}`);
|
|
882
|
+
}
|
|
883
|
+
return match[1];
|
|
884
|
+
}
|
|
885
|
+
function boardSchemaModeline(url) {
|
|
886
|
+
return `# yaml-language-server: $schema=${url}`;
|
|
887
|
+
}
|
|
888
|
+
var boardSchemaFileName = SCHEMA_FILE;
|
|
889
|
+
|
|
890
|
+
// packages/shared/src/client-env.ts
|
|
891
|
+
import { z as z3 } from "zod";
|
|
892
|
+
var clientEnvSchema = z3.object({
|
|
893
|
+
/** Where the immutable client assets live. The one variable that repoints the whole client. */
|
|
894
|
+
assetPath: z3.string().min(1),
|
|
895
|
+
/** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */
|
|
896
|
+
proxyPath: z3.string().min(1),
|
|
897
|
+
/** Which board this is, resolved server-side so the client parses no URLs. */
|
|
898
|
+
board: z3.string().min(1),
|
|
899
|
+
/** The client version being served, so two published versions are visibly distinguishable. */
|
|
900
|
+
clientVersion: z3.string().min(1),
|
|
901
|
+
/** Omitted entirely when unauthenticated — presence is what turns auth on. */
|
|
902
|
+
auth: z3.object({
|
|
903
|
+
issuer: z3.string().min(1),
|
|
904
|
+
clientId: z3.string().min(1).optional()
|
|
905
|
+
}).optional()
|
|
906
|
+
});
|
|
907
|
+
var clientIdentitySchema = clientEnvSchema.pick({ assetPath: true, clientVersion: true });
|
|
908
|
+
|
|
909
|
+
// packages/shared/src/envelope.ts
|
|
910
|
+
import { z as z4 } from "zod";
|
|
911
|
+
var errorKindSchema = z4.enum([
|
|
912
|
+
"unreachable",
|
|
913
|
+
"unauthorized",
|
|
914
|
+
"not-found",
|
|
915
|
+
"no-runs",
|
|
916
|
+
"upstream-error"
|
|
917
|
+
]);
|
|
918
|
+
var envelopeErrorSchema = z4.object({
|
|
919
|
+
kind: errorKindSchema,
|
|
920
|
+
message: z4.string()
|
|
921
|
+
});
|
|
922
|
+
var envelopeSchema = z4.discriminatedUnion("state", [
|
|
923
|
+
z4.object({
|
|
924
|
+
panelId: z4.string().min(1),
|
|
925
|
+
state: z4.literal("ok"),
|
|
926
|
+
/** From the upstream response `Date`. A cached value must never be shown without its age. */
|
|
927
|
+
observedAt: z4.iso.datetime(),
|
|
928
|
+
/** Adapter-derived. The authority for this panel; the dashboard itself asserts nothing. */
|
|
929
|
+
link: z4.url().nullable(),
|
|
930
|
+
signal: z4.unknown()
|
|
931
|
+
}),
|
|
932
|
+
z4.object({
|
|
933
|
+
panelId: z4.string().min(1),
|
|
934
|
+
state: z4.literal("error"),
|
|
935
|
+
/**
|
|
936
|
+
* Populated even on error: knowing *when* a failure was observed is the difference between
|
|
937
|
+
* "broken just now" and "broken all morning", and a panel that can't reach its source is
|
|
938
|
+
* exactly when a viewer most wants to click through.
|
|
939
|
+
*/
|
|
940
|
+
observedAt: z4.iso.datetime(),
|
|
941
|
+
link: z4.url().nullable(),
|
|
942
|
+
error: envelopeErrorSchema
|
|
943
|
+
})
|
|
944
|
+
]);
|
|
945
|
+
var pipelineActivitySchema = z4.object({
|
|
946
|
+
kind: z4.enum(["job", "stage", "step"]),
|
|
947
|
+
name: z4.string().min(1),
|
|
948
|
+
parent: z4.string().min(1).optional()
|
|
949
|
+
});
|
|
950
|
+
var pipelineStatusSchema = z4.object({
|
|
951
|
+
type: z4.literal("pipeline-status"),
|
|
952
|
+
status: z4.enum(["passed", "failed", "running", "cancelled", "unknown"]),
|
|
953
|
+
/** The source's unmodified status/result vocabulary, for an honest display. */
|
|
954
|
+
rawStatus: z4.string(),
|
|
955
|
+
name: z4.string().min(1),
|
|
956
|
+
/** The branch represented by this status, when the upstream supplies or filters by one. */
|
|
957
|
+
branch: z4.string().min(1).optional(),
|
|
958
|
+
/** Elapsed execution time, supplied only after the upstream run has completed. */
|
|
959
|
+
durationMs: z4.number().int().nonnegative().optional(),
|
|
960
|
+
/** When an active run began, so the client can advance its elapsed display between polls. */
|
|
961
|
+
runStartedAt: z4.iso.datetime().optional(),
|
|
962
|
+
/** Stable source-run identity, when the adapter provides one. */
|
|
963
|
+
sourceRunId: z4.string().min(1).optional(),
|
|
964
|
+
/** Best-effort live work detail; adapters may describe a job, stage, or step. */
|
|
965
|
+
activity: pipelineActivitySchema.optional(),
|
|
966
|
+
/** Advisory duration from recent completed runs remembered by this browser. */
|
|
967
|
+
estimatedDurationMs: z4.number().int().positive().optional(),
|
|
968
|
+
/** When this workflow run was last updated by its source, distinct from our observation time. */
|
|
969
|
+
sourceUpdatedAt: z4.iso.datetime().optional()
|
|
970
|
+
});
|
|
971
|
+
var pullRequestHealthItemSchema = z4.object({
|
|
972
|
+
label: z4.string().min(1),
|
|
973
|
+
status: pipelineStatusSchema.shape.status,
|
|
974
|
+
detail: z4.string().min(1),
|
|
975
|
+
link: z4.url().nullable()
|
|
976
|
+
});
|
|
977
|
+
var pullRequestHealthSchema = z4.object({
|
|
978
|
+
type: z4.literal("pull-request-health"),
|
|
979
|
+
status: pipelineStatusSchema.shape.status,
|
|
980
|
+
summary: z4.string().min(1),
|
|
981
|
+
workflows: z4.array(pullRequestHealthItemSchema),
|
|
982
|
+
pullRequests: z4.array(pullRequestHealthItemSchema)
|
|
983
|
+
});
|
|
984
|
+
var httpValueSchema = z4.object({
|
|
985
|
+
type: z4.literal("http-value"),
|
|
986
|
+
value: z4.union([z4.string(), z4.number(), z4.boolean()])
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
// packages/aws/src/release.ts
|
|
990
|
+
import { parse, stringify } from "yaml";
|
|
991
|
+
import { z as z6 } from "zod";
|
|
992
|
+
|
|
993
|
+
// packages/aws/src/internal-board.ts
|
|
994
|
+
import { z as z5 } from "zod";
|
|
995
|
+
var durationSchema2 = z5.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value2) => Number.parseInt(value2, 10) > 0).brand();
|
|
996
|
+
var positionSchema2 = z5.union([
|
|
997
|
+
z5.object({
|
|
998
|
+
x: z5.number().int().min(0),
|
|
999
|
+
y: z5.number().int().min(0),
|
|
1000
|
+
w: z5.number().int().min(1).max(12),
|
|
1001
|
+
h: z5.number().int().min(1)
|
|
1002
|
+
}),
|
|
1003
|
+
z5.object({ x: z5.literal(0), y: z5.literal(0), w: z5.literal(0), h: z5.literal(0) })
|
|
1004
|
+
]);
|
|
1005
|
+
var panelSchema2 = z5.looseObject({
|
|
1006
|
+
// Presentation-only; ids still address proxy calls and the generated allowlist.
|
|
1007
|
+
label: z5.string().min(1).optional(),
|
|
1008
|
+
id: z5.string().min(1),
|
|
1009
|
+
type: z5.string().min(1),
|
|
1010
|
+
source: z5.string().min(1).optional(),
|
|
1011
|
+
density: z5.enum(["auto", "comfortable", "compact"]).optional(),
|
|
1012
|
+
position: positionSchema2.optional(),
|
|
1013
|
+
refresh: durationSchema2.optional(),
|
|
1014
|
+
link: z5.url().optional(),
|
|
1015
|
+
url: z5.url().optional(),
|
|
1016
|
+
json_path: z5.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/).optional()
|
|
1017
|
+
}).superRefine((panel, ctx) => {
|
|
1018
|
+
if ("display" in panel) {
|
|
1019
|
+
ctx.addIssue({
|
|
1020
|
+
code: "custom",
|
|
1021
|
+
path: ["display"],
|
|
1022
|
+
message: "display was removed; use density"
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
var sourceSchema2 = z5.looseObject({
|
|
1027
|
+
type: z5.string().min(1),
|
|
1028
|
+
token_env: z5.string().min(1).optional()
|
|
1029
|
+
});
|
|
1030
|
+
var boardSchema2 = z5.object({
|
|
1031
|
+
refresh: durationSchema2.optional(),
|
|
1032
|
+
panels: z5.array(panelSchema2).min(1).superRefine((panels, ctx) => {
|
|
774
1033
|
const seen = /* @__PURE__ */ new Map();
|
|
775
1034
|
panels.forEach((panel, index) => {
|
|
776
1035
|
const firstIndex = seen.get(panel.id);
|
|
@@ -784,15 +1043,15 @@ var boardSchema = z.object({
|
|
|
784
1043
|
});
|
|
785
1044
|
})
|
|
786
1045
|
});
|
|
787
|
-
var
|
|
788
|
-
sources:
|
|
789
|
-
boards:
|
|
790
|
-
auth:
|
|
791
|
-
issuer:
|
|
792
|
-
client_id:
|
|
793
|
-
allow:
|
|
794
|
-
groups:
|
|
795
|
-
subjects:
|
|
1046
|
+
var boardConfigSchema2 = z5.object({
|
|
1047
|
+
sources: z5.record(z5.string().min(1), sourceSchema2).default({}),
|
|
1048
|
+
boards: z5.record(z5.string().min(1), boardSchema2),
|
|
1049
|
+
auth: z5.object({
|
|
1050
|
+
issuer: z5.url(),
|
|
1051
|
+
client_id: z5.string().min(1).optional(),
|
|
1052
|
+
allow: z5.object({
|
|
1053
|
+
groups: z5.array(z5.string().min(1)).optional(),
|
|
1054
|
+
subjects: z5.array(z5.string().min(1)).optional()
|
|
796
1055
|
}).optional()
|
|
797
1056
|
}).optional()
|
|
798
1057
|
});
|
|
@@ -805,13 +1064,18 @@ function clientAssetUrl(version, domain = CANONICAL_ASSET_DOMAIN) {
|
|
|
805
1064
|
throw new Error(`Invalid dashboard version: ${version}`);
|
|
806
1065
|
return `${domain.replace(/\/+$/, "")}/dashboard/${version}`;
|
|
807
1066
|
}
|
|
808
|
-
async function validateBoardConfig(path) {
|
|
1067
|
+
async function validateBoardConfig(path, expectedSchemaUrl) {
|
|
809
1068
|
const source = await readFile(resolve(path), "utf8");
|
|
810
|
-
const
|
|
811
|
-
|
|
1069
|
+
const authoredSchemaUrl = readBoardSchemaModeline(source);
|
|
1070
|
+
const result = boardConfigSchema2.safeParse(parse(source));
|
|
1071
|
+
if (!result.success) {
|
|
1072
|
+
const stale = authoredSchemaUrl !== expectedSchemaUrl ? `
|
|
1073
|
+
Stale schema modeline: expected ${expectedSchemaUrl}` : "";
|
|
812
1074
|
throw new Error(`Invalid board configuration:
|
|
813
|
-
${
|
|
814
|
-
|
|
1075
|
+
${z6.prettifyError(result.error)}${stale}`);
|
|
1076
|
+
}
|
|
1077
|
+
const yaml = `${boardSchemaModeline(expectedSchemaUrl)}
|
|
1078
|
+
${stringify(result.data, { sortMapEntries: true })}`;
|
|
815
1079
|
return {
|
|
816
1080
|
yaml,
|
|
817
1081
|
sha256: sha256(yaml),
|
|
@@ -819,7 +1083,11 @@ ${z2.prettifyError(result.error)}`);
|
|
|
819
1083
|
};
|
|
820
1084
|
}
|
|
821
1085
|
async function assembleRelease(input) {
|
|
822
|
-
const
|
|
1086
|
+
const assetUrl = clientAssetUrl(input.version, input.assetDomain);
|
|
1087
|
+
const board = await validateBoardConfig(
|
|
1088
|
+
input.boardConfigPath,
|
|
1089
|
+
`${assetUrl}/${boardSchemaFileName}`
|
|
1090
|
+
);
|
|
823
1091
|
if (board.usesCredentials && !input.secretReference)
|
|
824
1092
|
throw new Error(
|
|
825
1093
|
"Board config uses token_env; SecretReference must name a Secrets Manager credential-map or Parameter Store SecureString ARN"
|
|
@@ -829,7 +1097,7 @@ async function assembleRelease(input) {
|
|
|
829
1097
|
await writeFile(join(outputDir, "board.yaml"), board.yaml);
|
|
830
1098
|
const metadata = {
|
|
831
1099
|
dashboardVersion: input.version,
|
|
832
|
-
clientAssetUrl:
|
|
1100
|
+
clientAssetUrl: assetUrl,
|
|
833
1101
|
serverRuntimeVersion: CORE_RUNTIME_VERSION,
|
|
834
1102
|
supportedProviders: input.providers ?? ["aws-lambda"],
|
|
835
1103
|
artifactChecksums: { "board.yaml": board.sha256 },
|
package/dist/lambda.mjs
CHANGED
|
@@ -64060,6 +64060,36 @@ var boardConfigSchema = external_exports.object({
|
|
|
64060
64060
|
auth: authSchema.optional()
|
|
64061
64061
|
});
|
|
64062
64062
|
|
|
64063
|
+
// packages/shared/src/board-config-modeline.ts
|
|
64064
|
+
var MODEL_LINE = /^# yaml-language-server:\s*\$schema=(\S+)\s*$/;
|
|
64065
|
+
var SCHEMA_FILE = "board-config.schema.json";
|
|
64066
|
+
function schemaUrlForAssetPath(assetPath) {
|
|
64067
|
+
return `${assetPath.replace(/\/+$/, "")}/${SCHEMA_FILE}`;
|
|
64068
|
+
}
|
|
64069
|
+
function readBoardSchemaModeline(source) {
|
|
64070
|
+
const line = source.split(/\r?\n/).find((candidate) => candidate.includes("yaml-language-server"));
|
|
64071
|
+
if (!line) throw new Error(`Board configuration must include a ${SCHEMA_FILE} modeline`);
|
|
64072
|
+
const match2 = MODEL_LINE.exec(line);
|
|
64073
|
+
if (!match2?.[1]) {
|
|
64074
|
+
throw new Error(
|
|
64075
|
+
`Malformed board schema modeline; expected "# yaml-language-server: $schema=<URL>/${SCHEMA_FILE}"`
|
|
64076
|
+
);
|
|
64077
|
+
}
|
|
64078
|
+
let url2;
|
|
64079
|
+
try {
|
|
64080
|
+
url2 = new URL(match2[1]);
|
|
64081
|
+
} catch {
|
|
64082
|
+
throw new Error(`Malformed board schema modeline; schema URL is not a valid URL: ${match2[1]}`);
|
|
64083
|
+
}
|
|
64084
|
+
if (!["http:", "https:"].includes(url2.protocol) || !url2.pathname.endsWith(`/${SCHEMA_FILE}`)) {
|
|
64085
|
+
throw new Error(`Malformed board schema modeline; URL must end in /${SCHEMA_FILE}: ${match2[1]}`);
|
|
64086
|
+
}
|
|
64087
|
+
return match2[1];
|
|
64088
|
+
}
|
|
64089
|
+
function boardSchemaModeline(url2) {
|
|
64090
|
+
return `# yaml-language-server: $schema=${url2}`;
|
|
64091
|
+
}
|
|
64092
|
+
|
|
64063
64093
|
// packages/shared/src/client-env.ts
|
|
64064
64094
|
var clientEnvSchema = external_exports.object({
|
|
64065
64095
|
/** Where the immutable client assets live. The one variable that repoints the whole client. */
|
|
@@ -67039,12 +67069,16 @@ function createApp(deps) {
|
|
|
67039
67069
|
...deps.boardConfig.auth ? { auth: deps.boardConfig.auth } : {}
|
|
67040
67070
|
};
|
|
67041
67071
|
const filenameBoard = safeDownloadFilename(boardName);
|
|
67042
|
-
return new Response(
|
|
67043
|
-
|
|
67044
|
-
|
|
67045
|
-
|
|
67072
|
+
return new Response(
|
|
67073
|
+
`${boardSchemaModeline(schemaUrlForAssetPath(config2.assetPath))}
|
|
67074
|
+
${(0, import_yaml.stringify)(outputConfig, { sortMapEntries: true })}`,
|
|
67075
|
+
{
|
|
67076
|
+
headers: {
|
|
67077
|
+
"content-type": "text/yaml; charset=utf-8",
|
|
67078
|
+
"content-disposition": `attachment; filename="${filenameBoard}-layout-${mode}.yaml"`
|
|
67079
|
+
}
|
|
67046
67080
|
}
|
|
67047
|
-
|
|
67081
|
+
);
|
|
67048
67082
|
};
|
|
67049
67083
|
app.get("/api/boards/:board/rendered", (c5) => layoutDownload(c5, "rendered"));
|
|
67050
67084
|
app.get("/api/boards/:board/authored", (c5) => layoutDownload(c5, "authored"));
|
|
@@ -67138,11 +67172,16 @@ function deriveVersionLabel(assetPath) {
|
|
|
67138
67172
|
// packages/server/src/board-config.ts
|
|
67139
67173
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
67140
67174
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
67141
|
-
async function loadBoardConfig(location, fetcher = globalThis.fetch) {
|
|
67175
|
+
async function loadBoardConfig(location, fetcher = globalThis.fetch, expectedSchemaUrl) {
|
|
67142
67176
|
const text = isUrl(location) ? await fetchBoardConfig(location, fetcher) : await readFile3(location, "utf-8");
|
|
67177
|
+
const authoredSchemaUrl = readBoardSchemaModeline(text);
|
|
67143
67178
|
const result = boardConfigSchema.safeParse((0, import_yaml2.parse)(text));
|
|
67144
|
-
if (!result.success)
|
|
67145
|
-
|
|
67179
|
+
if (!result.success) {
|
|
67180
|
+
const stale = expectedSchemaUrl && authoredSchemaUrl !== expectedSchemaUrl ? `
|
|
67181
|
+
Stale schema modeline: expected ${expectedSchemaUrl}` : "";
|
|
67182
|
+
throw new Error(`Invalid board configuration:
|
|
67183
|
+
${result.error.message}${stale}`);
|
|
67184
|
+
}
|
|
67146
67185
|
return result.data;
|
|
67147
67186
|
}
|
|
67148
67187
|
function isUrl(value) {
|
|
@@ -67218,7 +67257,11 @@ async function startup(options = {}) {
|
|
|
67218
67257
|
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
67219
67258
|
const [, boardConfig] = await Promise.all([
|
|
67220
67259
|
waitForTemplate(config2, fetcher),
|
|
67221
|
-
loadBoardConfig(
|
|
67260
|
+
loadBoardConfig(
|
|
67261
|
+
config2.boardConfigUrl,
|
|
67262
|
+
fetcher,
|
|
67263
|
+
config2.assetPath.includes("__ASSET_PATH__") ? void 0 : schemaUrlForAssetPath(config2.assetPath)
|
|
67264
|
+
)
|
|
67222
67265
|
]);
|
|
67223
67266
|
const board = selectBoard(config2.board, boardConfig);
|
|
67224
67267
|
const resolvedConfig = { ...config2, board };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@continuous-excellence/ze-great-dashboard-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AWS Lambda and CloudFormation adapter for Ze Great Dashboard.",
|
|
6
6
|
"keywords": [
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
31
|
"client",
|
|
32
|
+
"board-config.schema.json",
|
|
32
33
|
"bootstrap",
|
|
33
34
|
"template.yml",
|
|
34
35
|
"template-ecs.yml",
|