@burdenoff/microfe-devportal 2026.1026.1 → 2026.1026.2
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/devportal/components/NodeSmokePanel.d.ts +34 -0
- package/dist/devportal/components/NodeSmokePanel.d.ts.map +1 -0
- package/dist/devportal/components/NodeSmokePanel.js +131 -0
- package/dist/devportal/components/NodeSmokePanel.js.map +1 -0
- package/dist/devportal/components/PackageValidationPanel.d.ts.map +1 -1
- package/dist/devportal/components/PackageValidationPanel.js +46 -36
- package/dist/devportal/components/PackageValidationPanel.js.map +1 -1
- package/dist/devportal/components/index.d.ts +1 -0
- package/dist/devportal/components/index.d.ts.map +1 -1
- package/dist/devportal/components/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
export type SmokeOperationStatus = "PASS" | "FAIL" | "SKIP";
|
|
4
|
+
export interface SmokeOperation {
|
|
5
|
+
name: string;
|
|
6
|
+
status: SmokeOperationStatus;
|
|
7
|
+
detail: string | null;
|
|
8
|
+
error: string | null;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
}
|
|
11
|
+
export type NodeSmokeState = {
|
|
12
|
+
kind: "none";
|
|
13
|
+
} | {
|
|
14
|
+
kind: "running";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "passed" | "failed" | "skipped";
|
|
17
|
+
operations: SmokeOperation[];
|
|
18
|
+
passed: number;
|
|
19
|
+
failed: number;
|
|
20
|
+
skipped: number;
|
|
21
|
+
error: string | null;
|
|
22
|
+
finishedAt: string | null;
|
|
23
|
+
};
|
|
24
|
+
/** Parse the stored record defensively — metadata is untyped JSON. */
|
|
25
|
+
export declare function readNodeSmokeState(metadata: unknown): NodeSmokeState;
|
|
26
|
+
interface NodeSmokePanelProps {
|
|
27
|
+
metadata: unknown;
|
|
28
|
+
/** Start expanded (the admin review card does; the versions table does not). */
|
|
29
|
+
defaultExpanded?: boolean;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const NodeSmokePanel: FC<NodeSmokePanelProps>;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=NodeSmokePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSmokePanel.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/NodeSmokePanel.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAahC,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,oBAAoB,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IACE,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtC,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AA6BN,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,cAAc,CAmBpE;AA+BD,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAwGlD,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { useTr as e } from "../utils/i18n.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import { useState as n } from "react";
|
|
4
|
+
import { CheckCircle2 as r, ChevronDown as i, ChevronUp as a, Loader2 as o, MinusCircle as s, XCircle as c } from "lucide-react";
|
|
5
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
6
|
+
//#region src/devportal/components/NodeSmokePanel.tsx
|
|
7
|
+
function d(e) {
|
|
8
|
+
return typeof e == "object" && !!e && !Array.isArray(e);
|
|
9
|
+
}
|
|
10
|
+
function f(e) {
|
|
11
|
+
return Array.isArray(e) ? e.filter(d).map((e) => ({
|
|
12
|
+
name: typeof e.name == "string" ? e.name : "",
|
|
13
|
+
status: e.status === "PASS" || e.status === "SKIP" ? e.status : "FAIL",
|
|
14
|
+
detail: typeof e.detail == "string" ? e.detail : null,
|
|
15
|
+
error: typeof e.error == "string" ? e.error : null,
|
|
16
|
+
durationMs: typeof e.durationMs == "number" ? e.durationMs : 0
|
|
17
|
+
})) : [];
|
|
18
|
+
}
|
|
19
|
+
var p = (e) => typeof e == "number" ? e : 0, m = {
|
|
20
|
+
PASSED: "passed",
|
|
21
|
+
FAILED: "failed",
|
|
22
|
+
SKIPPED: "skipped"
|
|
23
|
+
};
|
|
24
|
+
function h(e) {
|
|
25
|
+
if (!d(e)) return { kind: "none" };
|
|
26
|
+
let t = e.packageSmoke;
|
|
27
|
+
if (!d(t) || typeof t.status != "string") return { kind: "none" };
|
|
28
|
+
if (t.status === "RUNNING") return { kind: "running" };
|
|
29
|
+
let n = m[t.status];
|
|
30
|
+
return n ? {
|
|
31
|
+
kind: n,
|
|
32
|
+
operations: f(t.operations),
|
|
33
|
+
passed: p(t.passed),
|
|
34
|
+
failed: p(t.failed),
|
|
35
|
+
skipped: p(t.skipped),
|
|
36
|
+
error: typeof t.error == "string" ? t.error : null,
|
|
37
|
+
finishedAt: typeof t.finishedAt == "string" ? t.finishedAt : null
|
|
38
|
+
} : { kind: "none" };
|
|
39
|
+
}
|
|
40
|
+
var g = {
|
|
41
|
+
PASS: "text-status-success-text",
|
|
42
|
+
FAIL: "text-status-error-text",
|
|
43
|
+
SKIP: "text-text-secondary"
|
|
44
|
+
}, _ = ({ operations: e }) => /* @__PURE__ */ l("ul", {
|
|
45
|
+
className: "space-y-1",
|
|
46
|
+
children: e.map((e, n) => /* @__PURE__ */ u("li", {
|
|
47
|
+
className: "text-xs leading-snug",
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ l("code", {
|
|
50
|
+
className: "mr-1 rounded bg-bg-sunken px-1 py-0.5 font-mono text-text-primary",
|
|
51
|
+
children: e.name
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ l("span", {
|
|
54
|
+
className: t("font-medium", g[e.status]),
|
|
55
|
+
children: e.status
|
|
56
|
+
}),
|
|
57
|
+
(e.error ?? e.detail) && /* @__PURE__ */ l("span", {
|
|
58
|
+
className: "ml-1 text-text-secondary",
|
|
59
|
+
children: e.error ?? e.detail
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ u("span", {
|
|
62
|
+
className: "ml-1 text-text-tertiary",
|
|
63
|
+
children: [e.durationMs, " ms"]
|
|
64
|
+
})
|
|
65
|
+
]
|
|
66
|
+
}, `${e.name}-${n}`))
|
|
67
|
+
}), v = ({ metadata: d, defaultExpanded: f = !1, className: p }) => {
|
|
68
|
+
let m = e(), g = h(d), [v, y] = n(f);
|
|
69
|
+
if (g.kind === "none") return null;
|
|
70
|
+
if (g.kind === "running") return /* @__PURE__ */ u("p", {
|
|
71
|
+
className: t("inline-flex items-center gap-1 text-xs text-status-info-text", p),
|
|
72
|
+
"data-testid": "node-smoke",
|
|
73
|
+
"data-state": "running",
|
|
74
|
+
children: [/* @__PURE__ */ l(o, {
|
|
75
|
+
size: 12,
|
|
76
|
+
className: "animate-spin"
|
|
77
|
+
}), m("devportal.nodeSmoke.running", "Testing operations…")]
|
|
78
|
+
});
|
|
79
|
+
let b = {
|
|
80
|
+
passed: m("devportal.nodeSmoke.passed", "Operations tested: {{passed}} passed, {{skipped}} skipped", {
|
|
81
|
+
passed: g.passed,
|
|
82
|
+
skipped: g.skipped
|
|
83
|
+
}),
|
|
84
|
+
failed: m("devportal.nodeSmoke.failed", "Operation tests failed: {{failed}} of {{total}}", {
|
|
85
|
+
failed: g.failed,
|
|
86
|
+
total: g.operations.length
|
|
87
|
+
}),
|
|
88
|
+
skipped: m("devportal.nodeSmoke.skipped", "Operation tests not run in this environment")
|
|
89
|
+
}, x = {
|
|
90
|
+
passed: /* @__PURE__ */ l(r, { size: 12 }),
|
|
91
|
+
failed: /* @__PURE__ */ l(c, { size: 12 }),
|
|
92
|
+
skipped: /* @__PURE__ */ l(s, { size: 12 })
|
|
93
|
+
}, S = {
|
|
94
|
+
passed: "text-status-success-text",
|
|
95
|
+
failed: "text-status-error-text",
|
|
96
|
+
skipped: "text-text-secondary"
|
|
97
|
+
}, C = g.operations.length > 0 || g.error !== null;
|
|
98
|
+
return /* @__PURE__ */ u("div", {
|
|
99
|
+
className: t("space-y-1", p),
|
|
100
|
+
"data-testid": "node-smoke",
|
|
101
|
+
"data-state": g.kind,
|
|
102
|
+
children: [/* @__PURE__ */ u("button", {
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: () => C && y((e) => !e),
|
|
105
|
+
className: t("inline-flex items-center gap-1 text-xs font-medium", S[g.kind], C ? "hover:underline" : "cursor-default"),
|
|
106
|
+
"aria-expanded": C ? v : void 0,
|
|
107
|
+
children: [
|
|
108
|
+
x[g.kind],
|
|
109
|
+
b[g.kind],
|
|
110
|
+
C && l(v ? a : i, { size: 12 })
|
|
111
|
+
]
|
|
112
|
+
}), v && C && /* @__PURE__ */ u("div", {
|
|
113
|
+
className: "max-w-md space-y-2 rounded-md border border-border-seam bg-bg-surface p-2",
|
|
114
|
+
children: [
|
|
115
|
+
g.error && /* @__PURE__ */ l("p", {
|
|
116
|
+
className: t("text-xs", g.kind === "skipped" ? "text-text-secondary" : "text-status-error-text"),
|
|
117
|
+
children: g.error
|
|
118
|
+
}),
|
|
119
|
+
g.operations.length > 0 && /* @__PURE__ */ l(_, { operations: g.operations }),
|
|
120
|
+
g.finishedAt && /* @__PURE__ */ l("p", {
|
|
121
|
+
className: "text-[11px] text-text-secondary",
|
|
122
|
+
children: new Date(g.finishedAt).toLocaleString()
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
})]
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
export { v as NodeSmokePanel, h as readNodeSmokeState };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=NodeSmokePanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSmokePanel.js","names":[],"sources":["../../../src/devportal/components/NodeSmokePanel.tsx"],"sourcesContent":["/**\n * NodeSmokePanel — shows the operation smoke run (BOFF-9656) that\n * global-devportal-svc records on `ApplicationVersion.metadata.packageSmoke`.\n *\n * Once a custom node's sandbox image is built, the service starts it in a\n * sandbox and calls every manifest operation with an empty input, the same\n * check `fluidgrids-node smoke` runs locally. A version whose run failed, or\n * is still running, cannot be submitted or approved; this panel is how the\n * developer sees which operation broke and how the reviewer sees the result.\n */\nimport { useState } from \"react\";\nimport type { FC } from \"react\";\nimport {\n CheckCircle2,\n ChevronDown,\n ChevronUp,\n Loader2,\n MinusCircle,\n XCircle,\n} from \"lucide-react\";\n\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\n\nexport type SmokeOperationStatus = \"PASS\" | \"FAIL\" | \"SKIP\";\n\nexport interface SmokeOperation {\n name: string;\n status: SmokeOperationStatus;\n detail: string | null;\n error: string | null;\n durationMs: number;\n}\n\nexport type NodeSmokeState =\n | { kind: \"none\" }\n | { kind: \"running\" }\n | {\n kind: \"passed\" | \"failed\" | \"skipped\";\n operations: SmokeOperation[];\n passed: number;\n failed: number;\n skipped: number;\n error: string | null;\n finishedAt: string | null;\n };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readOperations(value: unknown): SmokeOperation[] {\n if (!Array.isArray(value)) return [];\n return value.filter(isRecord).map((entry) => ({\n name: typeof entry.name === \"string\" ? entry.name : \"\",\n status:\n entry.status === \"PASS\" || entry.status === \"SKIP\"\n ? entry.status\n : \"FAIL\",\n detail: typeof entry.detail === \"string\" ? entry.detail : null,\n error: typeof entry.error === \"string\" ? entry.error : null,\n durationMs: typeof entry.durationMs === \"number\" ? entry.durationMs : 0,\n }));\n}\n\nconst count = (value: unknown): number =>\n typeof value === \"number\" ? value : 0;\n\nconst SETTLED_KINDS: Record<string, \"passed\" | \"failed\" | \"skipped\"> = {\n PASSED: \"passed\",\n FAILED: \"failed\",\n SKIPPED: \"skipped\",\n};\n\n/** Parse the stored record defensively — metadata is untyped JSON. */\nexport function readNodeSmokeState(metadata: unknown): NodeSmokeState {\n if (!isRecord(metadata)) return { kind: \"none\" };\n const record = metadata.packageSmoke;\n if (!isRecord(record) || typeof record.status !== \"string\") {\n return { kind: \"none\" };\n }\n if (record.status === \"RUNNING\") return { kind: \"running\" };\n const kind = SETTLED_KINDS[record.status];\n if (!kind) return { kind: \"none\" };\n return {\n kind,\n operations: readOperations(record.operations),\n passed: count(record.passed),\n failed: count(record.failed),\n skipped: count(record.skipped),\n error: typeof record.error === \"string\" ? record.error : null,\n finishedAt:\n typeof record.finishedAt === \"string\" ? record.finishedAt : null,\n };\n}\n\nconst STATUS_CLASS: Record<SmokeOperationStatus, string> = {\n PASS: \"text-status-success-text\",\n FAIL: \"text-status-error-text\",\n SKIP: \"text-text-secondary\",\n};\n\nconst OperationList: FC<{ operations: SmokeOperation[] }> = ({\n operations,\n}) => (\n <ul className=\"space-y-1\">\n {operations.map((op, index) => (\n <li key={`${op.name}-${index}`} className=\"text-xs leading-snug\">\n <code className=\"mr-1 rounded bg-bg-sunken px-1 py-0.5 font-mono text-text-primary\">\n {op.name}\n </code>\n <span className={cn(\"font-medium\", STATUS_CLASS[op.status])}>\n {op.status}\n </span>\n {(op.error ?? op.detail) && (\n <span className=\"ml-1 text-text-secondary\">\n {op.error ?? op.detail}\n </span>\n )}\n <span className=\"ml-1 text-text-tertiary\">{op.durationMs} ms</span>\n </li>\n ))}\n </ul>\n);\n\ninterface NodeSmokePanelProps {\n metadata: unknown;\n /** Start expanded (the admin review card does; the versions table does not). */\n defaultExpanded?: boolean;\n className?: string;\n}\n\nexport const NodeSmokePanel: FC<NodeSmokePanelProps> = ({\n metadata,\n defaultExpanded = false,\n className,\n}) => {\n const tr = useTr();\n const state = readNodeSmokeState(metadata);\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n // Nothing recorded: the build has not finished, or the version predates\n // operation smoke tests. The build status already says which.\n if (state.kind === \"none\") return null;\n\n if (state.kind === \"running\") {\n return (\n <p\n className={cn(\n \"inline-flex items-center gap-1 text-xs text-status-info-text\",\n className,\n )}\n data-testid=\"node-smoke\"\n data-state=\"running\"\n >\n <Loader2 size={12} className=\"animate-spin\" />\n {tr(\"devportal.nodeSmoke.running\", \"Testing operations…\")}\n </p>\n );\n }\n\n const summaries = {\n passed: tr(\n \"devportal.nodeSmoke.passed\",\n \"Operations tested: {{passed}} passed, {{skipped}} skipped\",\n { passed: state.passed, skipped: state.skipped },\n ),\n failed: tr(\n \"devportal.nodeSmoke.failed\",\n \"Operation tests failed: {{failed}} of {{total}}\",\n { failed: state.failed, total: state.operations.length },\n ),\n skipped: tr(\n \"devportal.nodeSmoke.skipped\",\n \"Operation tests not run in this environment\",\n ),\n };\n const icons = {\n passed: <CheckCircle2 size={12} />,\n failed: <XCircle size={12} />,\n skipped: <MinusCircle size={12} />,\n };\n const tone = {\n passed: \"text-status-success-text\",\n failed: \"text-status-error-text\",\n skipped: \"text-text-secondary\",\n };\n const hasDetails = state.operations.length > 0 || state.error !== null;\n\n return (\n <div\n className={cn(\"space-y-1\", className)}\n data-testid=\"node-smoke\"\n data-state={state.kind}\n >\n <button\n type=\"button\"\n onClick={() => hasDetails && setExpanded((value) => !value)}\n className={cn(\n \"inline-flex items-center gap-1 text-xs font-medium\",\n tone[state.kind],\n hasDetails ? \"hover:underline\" : \"cursor-default\",\n )}\n aria-expanded={hasDetails ? expanded : undefined}\n >\n {icons[state.kind]}\n {summaries[state.kind]}\n {hasDetails &&\n (expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />)}\n </button>\n {expanded && hasDetails && (\n <div className=\"max-w-md space-y-2 rounded-md border border-border-seam bg-bg-surface p-2\">\n {state.error && (\n <p\n className={cn(\n \"text-xs\",\n state.kind === \"skipped\"\n ? \"text-text-secondary\"\n : \"text-status-error-text\",\n )}\n >\n {state.error}\n </p>\n )}\n {state.operations.length > 0 && (\n <OperationList operations={state.operations} />\n )}\n {state.finishedAt && (\n <p className=\"text-[11px] text-text-secondary\">\n {new Date(state.finishedAt).toLocaleString()}\n </p>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageValidationPanel.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/PackageValidationPanel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PackageValidationPanel.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/PackageValidationPanel.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAchC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IACE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAgBN,sEAAsE;AACtE,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,OAAO,GAChB,sBAAsB,CAgBxB;AA4BD,UAAU,2BAA2B;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,sBAAsB,EAAE,EAAE,CAAC,2BAA2B,CASlE,CAAC"}
|
|
@@ -1,92 +1,102 @@
|
|
|
1
1
|
import { useTr as e } from "../utils/i18n.js";
|
|
2
2
|
import { cn as t } from "../utils/cn.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { NodeSmokePanel as n } from "./NodeSmokePanel.js";
|
|
4
|
+
import { useState as r } from "react";
|
|
5
|
+
import { AlertTriangle as i, CheckCircle2 as a, ChevronDown as o, ChevronUp as s, Loader2 as c, XCircle as l } from "lucide-react";
|
|
6
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
6
7
|
//#region src/devportal/components/PackageValidationPanel.tsx
|
|
7
|
-
function
|
|
8
|
+
function f(e) {
|
|
8
9
|
return typeof e == "object" && !!e && !Array.isArray(e);
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
-
return Array.isArray(e) ? e.filter(
|
|
11
|
+
function p(e) {
|
|
12
|
+
return Array.isArray(e) ? e.filter(f).map((e) => ({
|
|
12
13
|
severity: e.severity === "warning" ? "warning" : "error",
|
|
13
14
|
code: typeof e.code == "string" ? e.code : "",
|
|
14
15
|
path: typeof e.path == "string" ? e.path : "",
|
|
15
16
|
message: typeof e.message == "string" ? e.message : ""
|
|
16
17
|
})) : [];
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
-
if (!
|
|
19
|
+
function m(e) {
|
|
20
|
+
if (!f(e)) return { kind: "none" };
|
|
20
21
|
let t = e.packageValidation;
|
|
21
|
-
return
|
|
22
|
+
return f(t) ? t.pending === !0 ? { kind: "pending" } : typeof t.valid == "boolean" ? {
|
|
22
23
|
kind: t.valid ? "passed" : "failed",
|
|
23
|
-
errors:
|
|
24
|
-
warnings:
|
|
24
|
+
errors: p(t.errors),
|
|
25
|
+
warnings: p(t.warnings),
|
|
25
26
|
validatedAt: typeof t.validatedAt == "string" ? t.validatedAt : null,
|
|
26
27
|
runtime: typeof t.runtime == "string" ? t.runtime : null,
|
|
27
28
|
operationCount: typeof t.operationCount == "number" ? t.operationCount : 0
|
|
28
29
|
} : { kind: "none" } : { kind: "none" };
|
|
29
30
|
}
|
|
30
|
-
var
|
|
31
|
+
var h = ({ issues: e }) => /* @__PURE__ */ u("ul", {
|
|
31
32
|
className: "space-y-1",
|
|
32
|
-
children: e.map((e, t) => /* @__PURE__ */
|
|
33
|
+
children: e.map((e, t) => /* @__PURE__ */ d("li", {
|
|
33
34
|
className: "text-xs leading-snug",
|
|
34
|
-
children: [e.path && /* @__PURE__ */
|
|
35
|
+
children: [e.path && /* @__PURE__ */ u("code", {
|
|
35
36
|
className: "mr-1 rounded bg-bg-sunken px-1 py-0.5 font-mono text-text-primary",
|
|
36
37
|
children: e.path
|
|
37
|
-
}), /* @__PURE__ */
|
|
38
|
+
}), /* @__PURE__ */ u("span", {
|
|
38
39
|
className: e.severity === "error" ? "text-status-error-text" : "text-status-warning-text",
|
|
39
40
|
children: e.message
|
|
40
41
|
})]
|
|
41
42
|
}, `${e.code}-${e.path}-${t}`))
|
|
42
|
-
}),
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
}), g = ({ metadata: e, defaultExpanded: r = !1, className: i }) => /* @__PURE__ */ d("div", {
|
|
44
|
+
className: t("space-y-1", i),
|
|
45
|
+
children: [/* @__PURE__ */ u(_, {
|
|
46
|
+
metadata: e,
|
|
47
|
+
defaultExpanded: r
|
|
48
|
+
}), /* @__PURE__ */ u(n, {
|
|
49
|
+
metadata: e,
|
|
50
|
+
defaultExpanded: r
|
|
51
|
+
})]
|
|
52
|
+
}), _ = ({ metadata: n, defaultExpanded: f = !1, className: p }) => {
|
|
53
|
+
let g = e(), _ = m(n), [v, y] = r(f);
|
|
54
|
+
if (_.kind === "none") return /* @__PURE__ */ u("p", {
|
|
55
|
+
className: t("text-xs text-text-secondary", p),
|
|
46
56
|
"data-testid": "package-validation",
|
|
47
57
|
"data-state": "none",
|
|
48
58
|
children: g("devportal.packageValidation.none", "Package not validated (uploaded before validation existed)")
|
|
49
59
|
});
|
|
50
|
-
if (_.kind === "pending") return /* @__PURE__ */
|
|
51
|
-
className: t("inline-flex items-center gap-1 text-xs text-status-info-text",
|
|
60
|
+
if (_.kind === "pending") return /* @__PURE__ */ d("p", {
|
|
61
|
+
className: t("inline-flex items-center gap-1 text-xs text-status-info-text", p),
|
|
52
62
|
"data-testid": "package-validation",
|
|
53
63
|
"data-state": "pending",
|
|
54
|
-
children: [/* @__PURE__ */
|
|
64
|
+
children: [/* @__PURE__ */ u(c, {
|
|
55
65
|
size: 12,
|
|
56
66
|
className: "animate-spin"
|
|
57
67
|
}), g("devportal.packageValidation.pending", "Validating package…")]
|
|
58
68
|
});
|
|
59
69
|
let b = _.kind === "failed", x = _.errors.length > 0 || _.warnings.length > 0, S = b ? g("devportal.packageValidation.failed", "Validation failed: {{count}} error(s)", { count: _.errors.length }) : g("devportal.packageValidation.passed", "Package validated");
|
|
60
|
-
return /* @__PURE__ */
|
|
61
|
-
className: t("space-y-1",
|
|
70
|
+
return /* @__PURE__ */ d("div", {
|
|
71
|
+
className: t("space-y-1", p),
|
|
62
72
|
"data-testid": "package-validation",
|
|
63
73
|
"data-state": _.kind,
|
|
64
|
-
children: [/* @__PURE__ */
|
|
74
|
+
children: [/* @__PURE__ */ d("button", {
|
|
65
75
|
type: "button",
|
|
66
76
|
onClick: () => x && y((e) => !e),
|
|
67
77
|
className: t("inline-flex items-center gap-1 text-xs font-medium", b ? "text-status-error-text" : "text-status-success-text", x ? "hover:underline" : "cursor-default"),
|
|
68
78
|
"aria-expanded": x ? v : void 0,
|
|
69
79
|
children: [
|
|
70
|
-
|
|
80
|
+
u(b ? l : a, { size: 12 }),
|
|
71
81
|
S,
|
|
72
|
-
_.warnings.length > 0 && /* @__PURE__ */
|
|
82
|
+
_.warnings.length > 0 && /* @__PURE__ */ d("span", {
|
|
73
83
|
className: "inline-flex items-center gap-0.5 text-status-warning-text",
|
|
74
|
-
children: [/* @__PURE__ */
|
|
84
|
+
children: [/* @__PURE__ */ u(i, { size: 12 }), g("devportal.packageValidation.warnings", "{{count}} warning(s)", { count: _.warnings.length })]
|
|
75
85
|
}),
|
|
76
|
-
x &&
|
|
86
|
+
x && u(v ? s : o, { size: 12 })
|
|
77
87
|
]
|
|
78
|
-
}), v && x && /* @__PURE__ */
|
|
88
|
+
}), v && x && /* @__PURE__ */ d("div", {
|
|
79
89
|
className: "max-w-md space-y-2 rounded-md border border-border-seam bg-bg-surface p-2",
|
|
80
90
|
children: [
|
|
81
|
-
_.errors.length > 0 && /* @__PURE__ */
|
|
91
|
+
_.errors.length > 0 && /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
|
|
82
92
|
className: "mb-1 text-xs font-semibold text-status-error-text",
|
|
83
93
|
children: g("devportal.packageValidation.errorsTitle", "Errors")
|
|
84
|
-
}), /* @__PURE__ */
|
|
85
|
-
_.warnings.length > 0 && /* @__PURE__ */
|
|
94
|
+
}), /* @__PURE__ */ u(h, { issues: _.errors })] }),
|
|
95
|
+
_.warnings.length > 0 && /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
|
|
86
96
|
className: "mb-1 text-xs font-semibold text-status-warning-text",
|
|
87
97
|
children: g("devportal.packageValidation.warningsTitle", "Needs reviewer attention")
|
|
88
|
-
}), /* @__PURE__ */
|
|
89
|
-
/* @__PURE__ */
|
|
98
|
+
}), /* @__PURE__ */ u(h, { issues: _.warnings })] }),
|
|
99
|
+
/* @__PURE__ */ u("p", {
|
|
90
100
|
className: "text-[11px] text-text-secondary",
|
|
91
101
|
children: [
|
|
92
102
|
_.runtime && g("devportal.packageValidation.runtime", "Runtime: {{runtime}}", { runtime: _.runtime }),
|
|
@@ -99,6 +109,6 @@ var m = ({ issues: e }) => /* @__PURE__ */ l("ul", {
|
|
|
99
109
|
});
|
|
100
110
|
};
|
|
101
111
|
//#endregion
|
|
102
|
-
export {
|
|
112
|
+
export { g as PackageValidationPanel, m as readPackageValidationState };
|
|
103
113
|
|
|
104
114
|
//# sourceMappingURL=PackageValidationPanel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PackageValidationPanel.js","names":[],"sources":["../../../src/devportal/components/PackageValidationPanel.tsx"],"sourcesContent":["/**\n * PackageValidationPanel — shows the custom-node package validation report\n * (BOFF-4315) stored on `ApplicationVersion.metadata.packageValidation` by\n * global-devportal-svc.\n *\n * The service validates every uploaded NODE package before building it, and\n * refuses to submit or approve a version whose package failed. This panel is\n * how the developer learns why, and how the reviewing admin sees warnings\n * (dynamic code, unusual field types) that need a human decision.\n */\nimport { useState } from \"react\";\nimport type { FC } from \"react\";\nimport {\n AlertTriangle,\n CheckCircle2,\n ChevronDown,\n ChevronUp,\n Loader2,\n XCircle,\n} from \"lucide-react\";\n\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\n\nexport interface PackageValidationIssue {\n severity: \"error\" | \"warning\";\n code: string;\n path: string;\n message: string;\n}\n\nexport type PackageValidationState =\n | { kind: \"none\" }\n | { kind: \"pending\" }\n | {\n kind: \"passed\" | \"failed\";\n errors: PackageValidationIssue[];\n warnings: PackageValidationIssue[];\n validatedAt: string | null;\n runtime: string | null;\n operationCount: number;\n };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readIssues(value: unknown): PackageValidationIssue[] {\n if (!Array.isArray(value)) return [];\n return value.filter(isRecord).map((entry) => ({\n severity: entry.severity === \"warning\" ? \"warning\" : \"error\",\n code: typeof entry.code === \"string\" ? entry.code : \"\",\n path: typeof entry.path === \"string\" ? entry.path : \"\",\n message: typeof entry.message === \"string\" ? entry.message : \"\",\n }));\n}\n\n/** Parse the stored report defensively — metadata is untyped JSON. */\nexport function readPackageValidationState(\n metadata: unknown,\n): PackageValidationState {\n if (!isRecord(metadata)) return { kind: \"none\" };\n const report = metadata.packageValidation;\n if (!isRecord(report)) return { kind: \"none\" };\n if (report.pending === true) return { kind: \"pending\" };\n if (typeof report.valid !== \"boolean\") return { kind: \"none\" };\n return {\n kind: report.valid ? \"passed\" : \"failed\",\n errors: readIssues(report.errors),\n warnings: readIssues(report.warnings),\n validatedAt:\n typeof report.validatedAt === \"string\" ? report.validatedAt : null,\n runtime: typeof report.runtime === \"string\" ? report.runtime : null,\n operationCount:\n typeof report.operationCount === \"number\" ? report.operationCount : 0,\n };\n}\n\nconst IssueList: FC<{ issues: PackageValidationIssue[] }> = ({ issues }) => (\n <ul className=\"space-y-1\">\n {issues.map((issue, index) => (\n <li\n key={`${issue.code}-${issue.path}-${index}`}\n className=\"text-xs leading-snug\"\n >\n {issue.path && (\n <code className=\"mr-1 rounded bg-bg-sunken px-1 py-0.5 font-mono text-text-primary\">\n {issue.path}\n </code>\n )}\n <span\n className={\n issue.severity === \"error\"\n ? \"text-status-error-text\"\n : \"text-status-warning-text\"\n }\n >\n {issue.message}\n </span>\n </li>\n ))}\n </ul>\n);\n\ninterface PackageValidationPanelProps {\n metadata: unknown;\n /** Start expanded (the admin review card does; the versions table does not). */\n defaultExpanded?: boolean;\n className?: string;\n}\n\nexport const PackageValidationPanel: FC<PackageValidationPanelProps> = ({\n metadata,\n defaultExpanded = false,\n className,\n}) => {\n const tr = useTr();\n const state = readPackageValidationState(metadata);\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n if (state.kind === \"none\") {\n return (\n <p\n className={cn(\"text-xs text-text-secondary\", className)}\n data-testid=\"package-validation\"\n data-state=\"none\"\n >\n {tr(\n \"devportal.packageValidation.none\",\n \"Package not validated (uploaded before validation existed)\",\n )}\n </p>\n );\n }\n\n if (state.kind === \"pending\") {\n return (\n <p\n className={cn(\n \"inline-flex items-center gap-1 text-xs text-status-info-text\",\n className,\n )}\n data-testid=\"package-validation\"\n data-state=\"pending\"\n >\n <Loader2 size={12} className=\"animate-spin\" />\n {tr(\"devportal.packageValidation.pending\", \"Validating package…\")}\n </p>\n );\n }\n\n const failed = state.kind === \"failed\";\n const hasDetails = state.errors.length > 0 || state.warnings.length > 0;\n const summary = failed\n ? tr(\n \"devportal.packageValidation.failed\",\n \"Validation failed: {{count}} error(s)\",\n { count: state.errors.length },\n )\n : tr(\"devportal.packageValidation.passed\", \"Package validated\");\n\n return (\n <div\n className={cn(\"space-y-1\", className)}\n data-testid=\"package-validation\"\n data-state={state.kind}\n >\n <button\n type=\"button\"\n onClick={() => hasDetails && setExpanded((value) => !value)}\n className={cn(\n \"inline-flex items-center gap-1 text-xs font-medium\",\n failed ? \"text-status-error-text\" : \"text-status-success-text\",\n hasDetails ? \"hover:underline\" : \"cursor-default\",\n )}\n aria-expanded={hasDetails ? expanded : undefined}\n >\n {failed ? <XCircle size={12} /> : <CheckCircle2 size={12} />}\n {summary}\n {state.warnings.length > 0 && (\n <span className=\"inline-flex items-center gap-0.5 text-status-warning-text\">\n <AlertTriangle size={12} />\n {tr(\n \"devportal.packageValidation.warnings\",\n \"{{count}} warning(s)\",\n { count: state.warnings.length },\n )}\n </span>\n )}\n {hasDetails &&\n (expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />)}\n </button>\n {expanded && hasDetails && (\n <div className=\"max-w-md space-y-2 rounded-md border border-border-seam bg-bg-surface p-2\">\n {state.errors.length > 0 && (\n <div>\n <p className=\"mb-1 text-xs font-semibold text-status-error-text\">\n {tr(\"devportal.packageValidation.errorsTitle\", \"Errors\")}\n </p>\n <IssueList issues={state.errors} />\n </div>\n )}\n {state.warnings.length > 0 && (\n <div>\n <p className=\"mb-1 text-xs font-semibold text-status-warning-text\">\n {tr(\n \"devportal.packageValidation.warningsTitle\",\n \"Needs reviewer attention\",\n )}\n </p>\n <IssueList issues={state.warnings} />\n </div>\n )}\n <p className=\"text-[11px] text-text-secondary\">\n {[\n state.runtime &&\n tr(\n \"devportal.packageValidation.runtime\",\n \"Runtime: {{runtime}}\",\n {\n runtime: state.runtime,\n },\n ),\n tr(\n \"devportal.packageValidation.operations\",\n \"{{count}} operation(s)\",\n { count: state.operationCount },\n ),\n state.validatedAt && new Date(state.validatedAt).toLocaleString(),\n ]\n .filter(Boolean)\n .join(\" · \")}\n </p>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PackageValidationPanel.js","names":[],"sources":["../../../src/devportal/components/PackageValidationPanel.tsx"],"sourcesContent":["/**\n * PackageValidationPanel — shows the custom-node package validation report\n * (BOFF-4315) stored on `ApplicationVersion.metadata.packageValidation` by\n * global-devportal-svc.\n *\n * The service validates every uploaded NODE package before building it, and\n * refuses to submit or approve a version whose package failed. This panel is\n * how the developer learns why, and how the reviewing admin sees warnings\n * (dynamic code, unusual field types) that need a human decision.\n *\n * Below the report it shows the operation smoke run on the built node\n * (BOFF-9656, `NodeSmokePanel`), so the versions page and the admin review\n * card both show every pre-review check in one place.\n */\nimport { useState } from \"react\";\nimport type { FC } from \"react\";\nimport {\n AlertTriangle,\n CheckCircle2,\n ChevronDown,\n ChevronUp,\n Loader2,\n XCircle,\n} from \"lucide-react\";\n\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\nimport { NodeSmokePanel } from \"./NodeSmokePanel\";\n\nexport interface PackageValidationIssue {\n severity: \"error\" | \"warning\";\n code: string;\n path: string;\n message: string;\n}\n\nexport type PackageValidationState =\n | { kind: \"none\" }\n | { kind: \"pending\" }\n | {\n kind: \"passed\" | \"failed\";\n errors: PackageValidationIssue[];\n warnings: PackageValidationIssue[];\n validatedAt: string | null;\n runtime: string | null;\n operationCount: number;\n };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readIssues(value: unknown): PackageValidationIssue[] {\n if (!Array.isArray(value)) return [];\n return value.filter(isRecord).map((entry) => ({\n severity: entry.severity === \"warning\" ? \"warning\" : \"error\",\n code: typeof entry.code === \"string\" ? entry.code : \"\",\n path: typeof entry.path === \"string\" ? entry.path : \"\",\n message: typeof entry.message === \"string\" ? entry.message : \"\",\n }));\n}\n\n/** Parse the stored report defensively — metadata is untyped JSON. */\nexport function readPackageValidationState(\n metadata: unknown,\n): PackageValidationState {\n if (!isRecord(metadata)) return { kind: \"none\" };\n const report = metadata.packageValidation;\n if (!isRecord(report)) return { kind: \"none\" };\n if (report.pending === true) return { kind: \"pending\" };\n if (typeof report.valid !== \"boolean\") return { kind: \"none\" };\n return {\n kind: report.valid ? \"passed\" : \"failed\",\n errors: readIssues(report.errors),\n warnings: readIssues(report.warnings),\n validatedAt:\n typeof report.validatedAt === \"string\" ? report.validatedAt : null,\n runtime: typeof report.runtime === \"string\" ? report.runtime : null,\n operationCount:\n typeof report.operationCount === \"number\" ? report.operationCount : 0,\n };\n}\n\nconst IssueList: FC<{ issues: PackageValidationIssue[] }> = ({ issues }) => (\n <ul className=\"space-y-1\">\n {issues.map((issue, index) => (\n <li\n key={`${issue.code}-${issue.path}-${index}`}\n className=\"text-xs leading-snug\"\n >\n {issue.path && (\n <code className=\"mr-1 rounded bg-bg-sunken px-1 py-0.5 font-mono text-text-primary\">\n {issue.path}\n </code>\n )}\n <span\n className={\n issue.severity === \"error\"\n ? \"text-status-error-text\"\n : \"text-status-warning-text\"\n }\n >\n {issue.message}\n </span>\n </li>\n ))}\n </ul>\n);\n\ninterface PackageValidationPanelProps {\n metadata: unknown;\n /** Start expanded (the admin review card does; the versions table does not). */\n defaultExpanded?: boolean;\n className?: string;\n}\n\nexport const PackageValidationPanel: FC<PackageValidationPanelProps> = ({\n metadata,\n defaultExpanded = false,\n className,\n}) => (\n <div className={cn(\"space-y-1\", className)}>\n <ValidationReport metadata={metadata} defaultExpanded={defaultExpanded} />\n <NodeSmokePanel metadata={metadata} defaultExpanded={defaultExpanded} />\n </div>\n);\n\nconst ValidationReport: FC<PackageValidationPanelProps> = ({\n metadata,\n defaultExpanded = false,\n className,\n}) => {\n const tr = useTr();\n const state = readPackageValidationState(metadata);\n const [expanded, setExpanded] = useState(defaultExpanded);\n\n if (state.kind === \"none\") {\n return (\n <p\n className={cn(\"text-xs text-text-secondary\", className)}\n data-testid=\"package-validation\"\n data-state=\"none\"\n >\n {tr(\n \"devportal.packageValidation.none\",\n \"Package not validated (uploaded before validation existed)\",\n )}\n </p>\n );\n }\n\n if (state.kind === \"pending\") {\n return (\n <p\n className={cn(\n \"inline-flex items-center gap-1 text-xs text-status-info-text\",\n className,\n )}\n data-testid=\"package-validation\"\n data-state=\"pending\"\n >\n <Loader2 size={12} className=\"animate-spin\" />\n {tr(\"devportal.packageValidation.pending\", \"Validating package…\")}\n </p>\n );\n }\n\n const failed = state.kind === \"failed\";\n const hasDetails = state.errors.length > 0 || state.warnings.length > 0;\n const summary = failed\n ? tr(\n \"devportal.packageValidation.failed\",\n \"Validation failed: {{count}} error(s)\",\n { count: state.errors.length },\n )\n : tr(\"devportal.packageValidation.passed\", \"Package validated\");\n\n return (\n <div\n className={cn(\"space-y-1\", className)}\n data-testid=\"package-validation\"\n data-state={state.kind}\n >\n <button\n type=\"button\"\n onClick={() => hasDetails && setExpanded((value) => !value)}\n className={cn(\n \"inline-flex items-center gap-1 text-xs font-medium\",\n failed ? \"text-status-error-text\" : \"text-status-success-text\",\n hasDetails ? \"hover:underline\" : \"cursor-default\",\n )}\n aria-expanded={hasDetails ? expanded : undefined}\n >\n {failed ? <XCircle size={12} /> : <CheckCircle2 size={12} />}\n {summary}\n {state.warnings.length > 0 && (\n <span className=\"inline-flex items-center gap-0.5 text-status-warning-text\">\n <AlertTriangle size={12} />\n {tr(\n \"devportal.packageValidation.warnings\",\n \"{{count}} warning(s)\",\n { count: state.warnings.length },\n )}\n </span>\n )}\n {hasDetails &&\n (expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />)}\n </button>\n {expanded && hasDetails && (\n <div className=\"max-w-md space-y-2 rounded-md border border-border-seam bg-bg-surface p-2\">\n {state.errors.length > 0 && (\n <div>\n <p className=\"mb-1 text-xs font-semibold text-status-error-text\">\n {tr(\"devportal.packageValidation.errorsTitle\", \"Errors\")}\n </p>\n <IssueList issues={state.errors} />\n </div>\n )}\n {state.warnings.length > 0 && (\n <div>\n <p className=\"mb-1 text-xs font-semibold text-status-warning-text\">\n {tr(\n \"devportal.packageValidation.warningsTitle\",\n \"Needs reviewer attention\",\n )}\n </p>\n <IssueList issues={state.warnings} />\n </div>\n )}\n <p className=\"text-[11px] text-text-secondary\">\n {[\n state.runtime &&\n tr(\n \"devportal.packageValidation.runtime\",\n \"Runtime: {{runtime}}\",\n {\n runtime: state.runtime,\n },\n ),\n tr(\n \"devportal.packageValidation.operations\",\n \"{{count}} operation(s)\",\n { count: state.operationCount },\n ),\n state.validatedAt && new Date(state.validatedAt).toLocaleString(),\n ]\n .filter(Boolean)\n .join(\" · \")}\n </p>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC"}
|