feat(sentinel): recover cicd child json dumps

This commit is contained in:
Codex
2026-07-02 20:15:52 +00:00
parent f8be6ce609
commit a92f9fb06d
4 changed files with 2276 additions and 2075 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { describe, expect, test } from "bun:test";
import { resolveSentinelChildJson } from "./hwlab-node-web-sentinel-cicd-shared";
describe("sentinel CI/CD child JSON recovery", () => {
test("recovers remote probe JSON from trans truncation dump", () => {
const dir = join(tmpdir(), `unidesk-sentinel-cicd-${Date.now()}-${process.pid}`);
mkdirSync(dir, { recursive: true });
const dumpPath = join(dir, "stdout.json");
writeFileSync(dumpPath, JSON.stringify({
ok: true,
present: true,
digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
valuesRedacted: true,
}));
const summary = {
stdout: {
stream: "stdout",
truncated: true,
dumpPath,
valuesRedacted: true,
},
valuesRedacted: true,
};
const resolved = resolveSentinelChildJson({
stdout: "tail fragment that is not json",
stderr: `UNIDESK_SSH_TRUNCATION_SUMMARY ${JSON.stringify(summary)}\n`,
exitCode: 0,
timedOut: false,
}, "web-probe-sentinel-test-probe");
expect(resolved.parsed?.ok).toBe(true);
expect(resolved.parsed?.present).toBe(true);
expect(resolved.diagnostics.stdoutKind).toBe("ssh-truncation-summary");
expect(resolved.diagnostics.dumpPath).toBe(dumpPath);
expect(resolved.diagnostics.source).toBe("dump");
});
});
@@ -0,0 +1,986 @@
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-07-01-p16-cicd-source-snapshot.
// Responsibility: shared types, config helpers and text render utilities for web-probe sentinel CI/CD.
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { resolveCliChildJsonCommandResult } from "./cli-child-json-recovery";
import type { CommandResult } from "./command";
import { rootPath } from "./config";
import type { HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
import type { RenderedCliResult } from "./output";
export type WebProbeSentinelConfigAction = "plan" | "status";
export type WebProbeSentinelImageAction = "status" | "build";
export type WebProbeSentinelControlPlaneAction = "plan" | "apply" | "status" | "trigger-current";
export type WebProbeSentinelPublishAction = "publish-current";
export type WebProbeSentinelMaintenanceAction = "status" | "start" | "stop";
export type WebProbeSentinelDashboardAction = "verify" | "screenshot" | "trigger";
export type WebProbeSentinelReportView = "summary" | "turn-summary" | "findings" | "trace-frame" | "auth-session-switch-summary";
export type WebProbeSentinelOptions =
| {
readonly kind: "config";
readonly action: WebProbeSentinelConfigAction;
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly dryRun: boolean;
}
| {
readonly kind: "image";
readonly action: WebProbeSentinelImageAction;
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly dryRun: boolean;
readonly confirm: boolean;
readonly wait: boolean;
readonly timeoutSeconds: number;
}
| {
readonly kind: "control-plane";
readonly action: WebProbeSentinelControlPlaneAction;
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly dryRun: boolean;
readonly confirm: boolean;
readonly wait: boolean;
readonly timeoutSeconds: number;
readonly rerun: boolean;
}
| {
readonly kind: "publish";
readonly action: WebProbeSentinelPublishAction;
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly dryRun: boolean;
readonly confirm: boolean;
readonly wait: boolean;
readonly timeoutSeconds: number;
readonly rerun: boolean;
}
| {
readonly kind: "maintenance";
readonly action: WebProbeSentinelMaintenanceAction;
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly dryRun: boolean;
readonly confirm: boolean;
readonly wait: boolean;
readonly timeoutSeconds: number;
readonly releaseId: string | null;
readonly reason: string | null;
readonly quickVerify: boolean;
}
| {
readonly kind: "validate";
readonly action: "validate";
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly dryRun: boolean;
readonly confirm: boolean;
readonly wait: boolean;
readonly timeoutSeconds: number;
readonly quickVerify: boolean;
}
| {
readonly kind: "report";
readonly action: "report";
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly view: WebProbeSentinelReportView;
readonly runId: string | null;
readonly latest: boolean;
readonly traceId: string | null;
readonly sampleSeq: number | null;
readonly raw: boolean;
readonly full: boolean;
readonly timeoutSeconds: number;
}
| {
readonly kind: "dashboard";
readonly action: WebProbeSentinelDashboardAction;
readonly node: string;
readonly lane: string;
readonly sentinelId: string | null;
readonly viewport: string;
readonly localDir: string;
readonly name: string | null;
readonly runId: string | null;
readonly timeoutMs: number;
readonly waitTimeoutMs: number;
readonly timeoutSeconds: number;
readonly commandTimeoutSeconds: number;
readonly fullPage: boolean;
readonly raw: boolean;
};
export interface SentinelCicdState {
readonly spec: HwlabRuntimeLaneSpec;
readonly sentinelId: string;
readonly configRefs: Record<string, string>;
readonly configReady: boolean;
readonly runtime: Record<string, unknown>;
readonly cicd: Record<string, unknown>;
readonly scenarios: unknown;
readonly publicExposure: Record<string, unknown>;
readonly secrets: Record<string, unknown>;
readonly controlPlaneTarget: Record<string, unknown>;
readonly controlPlaneNode: Record<string, unknown>;
readonly sourceHead: SourceHead;
readonly image: SentinelImagePlan;
readonly manifests: readonly Record<string, unknown>[];
readonly manifestSha256: string;
readonly valuesRedacted: true;
}
export interface SourceHead {
readonly ok: boolean;
readonly repository: string;
readonly branch: string;
readonly commit: string | null;
readonly stageRef: string | null;
readonly mirrorCommit: string | null;
readonly sourceAuthority: "git-mirror-cache" | "git-mirror-snapshot";
readonly latestDrift: boolean;
readonly result: CompactCommandResult;
}
export interface SentinelImagePlan {
readonly repository: string;
readonly tag: string;
readonly ref: string;
readonly digestRef: string | null;
readonly baseImage: string;
readonly buildContext: string;
readonly entrypoint: string;
readonly dockerfileSha256: string;
readonly dockerfilePreview: string;
readonly monitorWeb: Record<string, unknown>;
}
export interface SentinelObservedStatus {
readonly sourceMirror: Record<string, unknown>;
readonly registry: Record<string, unknown>;
readonly gitMirror: Record<string, unknown>;
readonly gitops: Record<string, unknown>;
readonly argo: Record<string, unknown>;
readonly runtime: Record<string, unknown>;
readonly cadence: Record<string, unknown>;
readonly wait?: Record<string, unknown>;
}
export interface SentinelObservedExpectation {
readonly gitopsRevision: string | null;
readonly runtimeImage: string | null;
}
export interface SentinelRemoteJobResult {
readonly ok: boolean;
readonly phase: string;
readonly resourceKind?: "Job" | "PipelineRun";
readonly jobName: string;
readonly payload: Record<string, unknown>;
readonly polls?: number;
readonly elapsedMs?: number;
readonly create?: Record<string, unknown>;
readonly probe?: Record<string, unknown>;
readonly diagnostics?: Record<string, unknown>;
readonly valuesRedacted: true;
}
export interface CompactCommandResult {
readonly exitCode: number | null;
readonly timedOut: boolean;
readonly stdoutBytes: number;
readonly stderrBytes: number;
readonly stdoutPreview: string;
readonly stderrPreview: string;
}
export interface ChildCliResult {
readonly ok: boolean;
readonly parsed: Record<string, unknown> | null;
readonly result: CompactCommandResult & { stdoutTail: string; stderrTail: string };
}
export function sentinelSourceSnapshotStageRefPrefix(cicd: Record<string, unknown>): string {
const branch = stringAt(cicd, "source.branch");
const repository = stringAt(cicd, "source.repository");
const prefix = stringAt(cicd, "sourceSnapshot.stageRefPrefix")
.replaceAll("{branch}", branch)
.replaceAll("{repository}", repository)
.replace(/\/+$/u, "");
if (!prefix.startsWith("refs/")) throw new Error("sourceSnapshot.stageRefPrefix must resolve to a git ref prefix");
return prefix;
}
export function sentinelSourceSnapshotRef(cicd: Record<string, unknown>, commit: string): string {
return `${sentinelSourceSnapshotStageRefPrefix(cicd)}/${commit}`;
}
export function monitorWebCicdPlan(spec: HwlabRuntimeLaneSpec, cicd: Record<string, unknown>): Record<string, unknown> {
return {
stack: stringAtNullable(cicd, "monitorWeb.frontendStack") ?? "vue3-vendored-browser-build",
runtimeMode: stringAtNullable(cicd, "monitorWeb.runtimeMode") ?? "runner-served-bridge",
assetRoot: stringAtNullable(cicd, "monitorWeb.assetRoot") ?? "scripts/assets/web-probe-sentinel-monitor-web",
verifyCommand: "bun scripts/verify-web-probe-sentinel-monitor-web.ts",
gitMirrorReadUrl: stringAt(cicd, "source.gitMirrorReadUrl"),
sourceMode: stringAt(cicd, "builder.sourceMode"),
envReuseMode: stringAtNullable(cicd, "monitorWeb.envReuse.mode") ?? "k8s-buildkit-and-ci-node-deps",
envReuseNodeDepsPath: stringAtNullable(cicd, "monitorWeb.envReuse.nodeDepsPath") ?? "/opt/hwlab-ci-node-deps/node_modules",
verifyPhase: stringAtNullable(cicd, "monitorWeb.imageBuild.verifyPhase") ?? "pre-image-build",
imageBuildBuilder: spec.buildkit?.sidecarImage ?? null,
imageBuildPackageMode: stringAtNullable(cicd, "monitorWeb.imageBuild.packageMode") ?? "copy-only-containerfile",
imageBuildNetworkMode: monitorWebImageBuildNetworkMode(cicd),
imageBuildProxySource: stringAtNullable(cicd, "monitorWeb.imageBuild.proxySource") ?? "node.networkProfile.imageBuildProxy",
imageBuildContextIgnore: stringAtNullable(cicd, "monitorWeb.imageBuild.contextIgnore") ?? "generated",
imageBuildState: monitorWebBuildkitStatePlan(cicd),
ciBudgetSeconds: numberAtNullable(cicd, "monitorWeb.ciBudget.maxSeconds") ?? numberAt(cicd, "confirmWait.maxSeconds"),
valuesRedacted: true,
};
}
export function monitorWebImageBuildNetworkMode(cicd: Record<string, unknown>): "default" | "host" {
const value = stringAtNullable(cicd, "monitorWeb.imageBuild.networkMode") ?? "host";
if (value !== "default" && value !== "host") throw new Error(`monitorWeb.imageBuild.networkMode must be default or host, got ${value}`);
return value;
}
export function monitorWebBuildkitStatePlan(cicd: Record<string, unknown>): Record<string, unknown> {
const state = recordTarget(valueAtPath(cicd, "monitorWeb.imageBuild.buildkitState"), "monitorWeb.imageBuild.buildkitState");
const mode = stringAt(state, "mode");
if (mode === "hostPath") {
return {
mode,
path: stringAt(state, "path"),
type: stringAt(state, "type"),
valuesRedacted: true,
};
}
if (mode === "persistentVolumeClaim") {
return {
mode,
claimName: stringAt(state, "claimName"),
valuesRedacted: true,
};
}
if (mode === "emptyDir") {
return {
mode,
sizeLimit: stringAt(state, "sizeLimit"),
valuesRedacted: true,
};
}
throw new Error(`monitorWeb.imageBuild.buildkitState.mode must be hostPath, persistentVolumeClaim or emptyDir, got ${mode}`);
}
export function secretSourcePaths(sourceRef: string): string[] {
if (sourceRef.startsWith(".env/")) return ownerFileSourcePaths(sourceRef);
const paths = [join(repoRoot, ".state", "secrets", sourceRef)];
const marker = "/.worktree/";
const index = repoRoot.indexOf(marker);
if (index >= 0) paths.push(join(repoRoot.slice(0, index), ".state", "secrets", sourceRef));
return [...new Set(paths)];
}
export function ownerFileSourcePaths(sourceRef: string): string[] {
if (sourceRef.includes("..") || sourceRef.includes("\0")) return [];
const marker = "/.worktree/";
const index = repoRoot.indexOf(marker);
const roots = index >= 0 ? [repoRoot.slice(0, index), repoRoot] : [repoRoot];
return [...new Set(roots.map((root) => join(root, sourceRef)))];
}
export function parseEnvFile(textValue: string): Record<string, string> {
const values: Record<string, string> = {};
for (const rawLine of textValue.split(/\r?\n/u)) {
const line = rawLine.trim();
if (line.length === 0 || line.startsWith("#")) continue;
const index = line.indexOf("=");
if (index <= 0) continue;
const key = line.slice(0, index).trim();
let value = line.slice(index + 1).trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
values[key] = value;
}
return values;
}
export function stringAtNullable(value: unknown, path: string): string | null {
const found = valueAtPath(value, path);
return typeof found === "string" && found.length > 0 ? found : null;
}
export function numberAtNullable(value: unknown, path: string): number | null {
const found = valueAtPath(value, path);
return typeof found === "number" && Number.isFinite(found) ? found : null;
}
export function booleanAtNullable(value: unknown, path: string): boolean | null {
const found = valueAtPath(value, path);
return typeof found === "boolean" ? found : null;
}
export function displayPath(pathValue: string): string {
if (pathValue.startsWith(`${repoRoot}/`)) return pathValue.slice(repoRoot.length + 1);
const marker = "/.worktree/";
const index = repoRoot.indexOf(marker);
if (index >= 0) {
const mainRoot = repoRoot.slice(0, index);
if (pathValue.startsWith(`${mainRoot}/`)) return pathValue.slice(mainRoot.length + 1);
}
return pathValue;
}
export function sentinelPipelineRunName(state: SentinelCicdState, rerun = false): string {
const commit = state.sourceHead.commit ?? "source";
const base = `hwlab-web-probe-sentinel-${safeKubernetesSegment(state.sentinelId, 24)}-${commit.slice(0, 12)}`;
if (!rerun) return base;
const suffix = `-r${Date.now().toString(36)}`;
return `${base.slice(0, Math.max(1, 63 - suffix.length)).replace(/-+$/u, "")}${suffix}`;
}
export function sentinelCliSuffix(state: SentinelCicdState): string {
return ` --sentinel ${state.sentinelId}`;
}
export function safeJobSegment(value: string): string {
return value.replace(/[^A-Za-z0-9_]+/gu, "_").replace(/^_+|_+$/gu, "").slice(0, 48) || "sentinel";
}
export function safeKubernetesSegment(value: string, maxLength: number): string {
const normalized = value.toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/^-+|-+$/gu, "");
return (normalized || "sentinel").slice(0, Math.max(1, maxLength)).replace(/-+$/u, "") || "sentinel";
}
export function renderPublishResult(publish: Record<string, unknown>): string {
const payload = record(publish.payload);
const diagnostics = record(publish.diagnostics);
const diagnosticEnvReuse = record(diagnostics.envReuse);
const envReuse = Object.keys(record(payload.envReuse)).length > 0 ? record(payload.envReuse) : diagnosticEnvReuse;
const imageBuild = record(payload.imageBuild);
const imageBuildProxy = record(imageBuild.proxy);
const payloadStageTimings = record(payload.stageTimings);
const diagnosticStageTimings = record(diagnostics.stageTimings);
const timings = Object.keys(payloadStageTimings).length > 0 ? payloadStageTimings : diagnosticStageTimings;
const commands = record(diagnostics.commands);
const proxySummary = [imageBuildProxy.httpProxyPresent, imageBuildProxy.httpsProxyPresent, imageBuildProxy.allProxyPresent].some((item) => item === true) ? "present" : "none";
const runColumn = diagnostics.resourceKind === "PipelineRun" || publish.resourceKind === "PipelineRun" ? "PIPELINERUN" : "JOB";
const lines = [
"PUBLISH",
table(["OK", "PHASE", runColumn, "ELAPSED", "POD", "CURRENT", "DIGEST", "GITOPS"], [[
publish.ok,
publish.phase,
publish.jobName,
publish.elapsedMs ?? "-",
diagnostics.pod ?? "-",
diagnostics.currentPhase ?? "-",
short(payload.digestRef),
short(payload.gitopsCommit),
]]),
];
if (Object.keys(envReuse).length > 0) {
lines.push(
"",
"PUBLISH_ENV_REUSE",
table(["MODE", "NODE_DEPS", "PRESENT", "ENTRIES", "LINKED", "DEPENDENCY"], [[
envReuse.mode,
envReuse.nodeDepsPath,
envReuse.nodeDepsPresent,
envReuse.nodeDepsEntries,
envReuse.linkedNodeDeps ?? "-",
envReuse.dependencyReuse,
]]),
);
}
if (Object.keys(imageBuild).length > 0 || Object.keys(timings).length > 0) {
lines.push(
"",
"PUBLISH_BUILD",
table(["BUILDER", "PACKAGE", "NETWORK", "PROXY", "IGNORE", "CACHE", "CACHE_LINES", "STEP_LINES", "SOURCE_MS", "VERIFY_MS", "IMAGE_MS", "GITOPS_MS", "TOTAL_MS"], [[
imageBuild.builder ?? "-",
imageBuild.packageMode ?? "-",
imageBuild.networkMode ?? "-",
proxySummary,
imageBuild.contextIgnoreEntries ?? "-",
imageBuild.layerCache ?? "-",
imageBuild.cacheHitLines ?? "-",
imageBuild.stepLines ?? "-",
timings.sourceFetchMs ?? "-",
timings.monitorWebVerifyMs ?? "-",
timings.imageBuildMs ?? "-",
timings.gitopsMs ?? "-",
timings.totalMs ?? payload.elapsedMs ?? "-",
]]),
);
}
if (Object.keys(diagnostics).length > 0) {
lines.push(
"",
"PUBLISH_DIAGNOSTICS",
table(["TASKRUN", "POD_PHASE", "ACTIVE", "CONDITION", "COMPLETED", "RECENT_LOG"], [[
diagnostics.taskRun ?? "-",
diagnostics.podPhase ?? "-",
diagnostics.active ?? "-",
diagnostics.conditionReason ?? diagnostics.conditionStatus ?? "-",
Array.isArray(diagnostics.completedStages) ? diagnostics.completedStages.join(",") : "-",
diagnostics.recentLogSummary ?? "-",
]]),
);
}
if (publish.ok !== true && Object.keys(commands).length > 0) {
lines.push(
"",
"PUBLISH_DRILLDOWN",
` status: ${commands.cliStatus ?? "-"}`,
` logs: ${commands.logs ?? "-"}`,
` describe: ${commands.describe ?? "-"}`,
` publish-current: ${commands.publishCurrent ?? "-"}`,
` git-mirror: ${commands.gitMirrorStatus ?? "-"}`,
` sync: ${commands.gitMirrorSync ?? "-"}`,
` flush: ${commands.gitMirrorFlush ?? "-"}`,
` apply: ${commands.controlPlaneApply ?? "-"}`,
);
}
return lines.join("\n");
}
export function renderPublishCurrentResult(result: Record<string, unknown>): string {
const source = record(result.source);
const image = record(result.image);
const controlPlane = record(result.controlPlane);
const publish = record(controlPlane.publish);
const publishPayload = record(publish.payload);
const observed = record(controlPlane.observed);
const gitops = record(observed.gitops);
const argo = record(observed.argo);
const runtime = record(observed.runtime);
const runtimeDeployment = record(record(runtime.probe).deployment);
const health = record(result.health);
const healthBody = record(record(health.health).bodyJson);
const timings = record(result.timings);
const budget = record(result.budget);
const stageBudgets = record(result.stageBudgets);
const validationPlan = record(result.validationPlan);
const blocker = record(result.blocker);
const recoveryNext = record(controlPlane.recoveryNext);
const next = record(result.next);
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
const slowStages = Array.isArray(result.slowStages) ? result.slowStages.map(record) : [];
const lines = [
String(result.command),
"",
table(["NODE", "LANE", "SENTINEL", "STATUS", "MODE", "BUDGET_S", "ELAPSED_S"], [[
result.node,
result.lane,
result.sentinelId,
result.ok === true ? "ok" : "blocked",
result.mode,
budget.maxSeconds ?? "-",
finiteNumberOrNull(result.elapsedMs) === null ? "-" : Math.round((finiteNumberOrNull(result.elapsedMs) ?? 0) / 1000),
]]),
"",
table(["SOURCE", "COMMIT", "AUTHORITY", "STAGE_REF", "IMAGE_REF", "DIGEST", "PIPELINERUN"], [[
`${source.repository ?? "-"}@${source.branch ?? "-"}`,
short(source.commit),
source.sourceAuthority ?? "-",
short(source.stageRef),
image.ref ?? "-",
short(publishPayload.digestRef ?? record(record(observed.registry).probe).digest),
result.pipelineRun ?? publish.jobName ?? "-",
]]),
"",
table(["GITOPS_REV", "ARGO_REV", "ARGO", "RUNTIME_IMAGE", "RUNTIME_READY", "HEALTH"], [[
short(gitops.revision),
short(argo.revision),
`${argo.syncStatus ?? "-"}/${argo.healthStatus ?? "-"}`,
short(runtimeDeployment.image),
`${runtimeDeployment.readyReplicas ?? "-"}/${runtimeDeployment.desiredReplicas ?? "-"}`,
health.ok === true ? "pass" : health.skipped === true ? `skipped:${text(health.reason)}` : Object.keys(health).length === 0 ? "planned" : "blocked",
]]),
"",
table(["SOURCE_SYNC_MS", "SOURCE_FETCH_MS", "VERIFY_MS", "IMAGE_MS", "GITOPS_MS", "ARGO_RUNTIME_MS", "VALIDATION_MS", "TOTAL_MS"], [[
timings.sourceSyncMs ?? "-",
timings.sourceFetchMs ?? "-",
timings.monitorWebVerifyMs ?? "-",
timings.imageBuildMs ?? "-",
timings.gitopsMs ?? "-",
timings.argoRuntimeMs ?? "-",
timings.healthValidationMs ?? "-",
timings.totalMs ?? "-",
]]),
"",
table(["BUDGET_SOURCE", "SOURCE_SYNC", "SOURCE_FETCH", "VERIFY", "IMAGE", "GITOPS", "ARGO_RUNTIME", "VALIDATION"], [[
"YAML publishCurrent",
stageBudgets.sourceSyncSeconds ?? "-",
stageBudgets.sourceFetchSeconds ?? "-",
stageBudgets.monitorWebVerifySeconds ?? "-",
stageBudgets.imageBuildSeconds ?? "-",
stageBudgets.gitopsSeconds ?? "-",
stageBudgets.argoRuntimeSeconds ?? "-",
stageBudgets.dashboardVerifySeconds ?? "-",
]]),
];
if (Object.keys(publish).length > 0) {
const payloadImageBuild = record(publishPayload.imageBuild);
const payloadEnvReuse = record(publishPayload.envReuse);
lines.push(
"",
table(["ENV_REUSE", "NODE_DEPS", "BUILD_PACKAGE", "BUILD_NETWORK", "CACHE", "CACHE_LINES"], [[
payloadEnvReuse.dependencyReuse ?? "-",
payloadEnvReuse.nodeDepsPresent ?? "-",
payloadImageBuild.packageMode ?? "-",
payloadImageBuild.networkMode ?? "-",
payloadImageBuild.layerCache ?? "-",
payloadImageBuild.cacheHitLines ?? "-",
]]),
);
}
lines.push(
"",
Object.keys(health).length === 0
? "HEALTH_VALIDATION\n-"
: table(["ENDPOINT", "HTTP", "OK", "STATUS", "PUBLIC_URL", "INTERNAL_URL"], [[
health.endpoint ?? validationPlan.endpoint ?? "-",
health.httpStatus ?? "-",
healthBody.ok ?? "-",
healthBody.status ?? "-",
health.publicUrl ?? "-",
health.internalUrl ?? "-",
]]),
"",
slowStages.length === 0 ? "SLOW_STAGES\n-" : [
"SLOW_STAGES",
table(["STAGE", "ELAPSED_MS", "BUDGET_S", "SUGGESTION"], slowStages.map((stage) => [stage.stage, stage.elapsedMs, stage.budgetSeconds, stage.suggestion])),
].join("\n"),
"",
warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((item) => `- ${text(item)}`)].join("\n"),
"",
Object.keys(blocker).length === 0 ? "BLOCKER\n-" : ["BLOCKER", table(["CODE", "REASON"], [[blocker.code, blocker.reason]])].join("\n"),
"",
Object.keys(recoveryNext).length === 0 ? "RECOVERY_NEXT\n-" : [
"RECOVERY_NEXT",
table(["REASON", "PIPELINERUN", "DIGEST", "GITOPS"], [[recoveryNext.reason, recoveryNext.pipelineRun ?? "-", short(recoveryNext.digestRef), short(recoveryNext.gitopsCommit)]]),
` publish-current: ${recoveryNext.publishCurrent ?? "-"}`,
` status: ${recoveryNext.nextStatus ?? "-"}`,
` git-mirror: ${recoveryNext.gitMirrorStatus ?? "-"}`,
` sync: ${recoveryNext.gitMirrorSync ?? "-"}`,
` flush: ${recoveryNext.gitMirrorFlush ?? "-"}`,
` apply: ${recoveryNext.controlPlaneApply ?? "-"}`,
].join("\n"),
"",
"NEXT",
` publish-current: ${next.publishCurrent ?? "-"}`,
` status: ${next.controlPlaneStatus ?? "-"}`,
` post-deploy-dashboard: ${next.dashboardVerify ?? "-"}`,
` git-mirror: ${next.gitMirrorStatus ?? "-"}`,
` sync: ${next.gitMirrorSync ?? "-"}`,
` flush: ${next.gitMirrorFlush ?? "-"}`,
"",
"DISCLOSURE",
` end-to-end and stage budgets are read from ${Object.keys(validationPlan).length > 0 ? "publishCurrent YAML and runtime.healthPath" : "YAML-required publishCurrent fields"}.`,
" CI/CD validation only checks the configured health endpoint; web-probe, Playwright and browser dashboard checks are post-deploy evidence, not this gate.",
" image build uses Tekton PipelineRun and BuildKit; this command does not require Docker daemon/socket/build.",
);
return lines.join("\n");
}
export function renderImageResult(result: Record<string, unknown>): string {
const source = record(result.source);
const sourceMirror = record(result.sourceMirror);
const sourceMirrorSync = record(result.sourceMirrorSync);
const image = record(result.image);
const monitorWeb = record(image.monitorWeb);
const registry = record(result.registry);
const publish = record(result.publish);
const blocker = record(result.blocker);
const next = record(result.next);
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
return [
String(result.command),
"",
table(["NODE", "LANE", "STATUS", "MODE", "MUTATION"], [[result.node, result.lane, result.ok === true ? "ok" : "blocked", result.mode, result.mutation]]),
"",
table(["SOURCE_REPO", "BRANCH", "COMMIT", "AUTHORITY", "STAGE_REF", "MIRROR"], [[source.repository, source.branch, short(source.commit), source.sourceAuthority ?? "-", short(source.stageRef), short(source.mirrorCommit)]]),
"",
Object.keys(sourceMirror).length === 0 ? "SOURCE_MIRROR\n-" : table(["OK", "MODE", "COMMIT", "EXPECTED", "READ_URL"], [[sourceMirror.ok, record(sourceMirror.probe).mode, short(record(sourceMirror.probe).commit), short(record(sourceMirror.probe).expectedCommit), record(sourceMirror.probe).readUrl ?? "-"]]),
"",
table(["IMAGE", "BASE", "ENTRYPOINT", "DOCKERFILE"], [[image.ref, image.baseImage, image.entrypoint, short(image.dockerfileSha256)]]),
"",
Object.keys(monitorWeb).length === 0 ? "MONITOR_WEB\n-" : table(["STACK", "MODE", "ASSETS", "VERIFY", "ENV_REUSE", "IMAGE_BUILDER", "BUILD_PKG", "BUILD_NET", "BUILD_STATE", "CTX_IGNORE"], [[monitorWeb.stack, monitorWeb.runtimeMode, monitorWeb.assetRoot, monitorWeb.verifyCommand, `${monitorWeb.envReuseMode}:${monitorWeb.envReuseNodeDepsPath}`, monitorWeb.imageBuildBuilder ?? "-", monitorWeb.imageBuildPackageMode, monitorWeb.imageBuildNetworkMode, `${record(monitorWeb.imageBuildState).mode ?? "-"}:${record(monitorWeb.imageBuildState).path ?? record(monitorWeb.imageBuildState).claimName ?? record(monitorWeb.imageBuildState).sizeLimit ?? "-"}`, monitorWeb.imageBuildContextIgnore]]),
"",
Object.keys(registry).length === 0 ? "REGISTRY\n-" : table(["PROBED", "PRESENT", "DIGEST"], [[record(registry.probe).url ?? "-", record(registry.probe).present ?? "-", short(record(registry.probe).digest)]]),
"",
Object.keys(sourceMirrorSync).length === 0 ? "SOURCE_MIRROR_SYNC\n-" : table(["OK", "PHASE", "JOB", "COMMIT", "STAGE_REF", "ELAPSED"], [[sourceMirrorSync.ok, sourceMirrorSync.phase, sourceMirrorSync.jobName, short(record(sourceMirrorSync.payload).mirrorCommit), short(record(sourceMirrorSync.payload).stageRef), sourceMirrorSync.elapsedMs ?? "-"]]),
"",
Object.keys(publish).length === 0 ? "PUBLISH\n-" : renderPublishResult(publish),
"",
warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((item) => `- ${text(item)}`)].join("\n"),
"",
Object.keys(blocker).length === 0 ? "BLOCKER\n-" : ["BLOCKER", table(["CODE", "REASON"], [[blocker.code, blocker.reason]])].join("\n"),
"",
"NEXT",
` status: ${next.status ?? "-"}`,
` dry-run: ${next.dryRun ?? "-"}`,
` confirm: ${next.confirm ?? "-"}`,
` trigger: ${next.controlPlaneTrigger ?? "-"}`,
` control-plane: ${next.controlPlanePlan ?? "-"}`,
"",
"DISCLOSURE",
" valuesRedacted=true; image status shows refs, hashes and object names only.",
].join("\n");
}
export function renderControlPlaneResult(result: Record<string, unknown>): string {
const source = record(result.source);
const image = record(result.image);
const gitops = record(result.gitops);
const argo = record(result.argo);
const validation = record(result.validation);
const observability = record(result.observability);
const observed = record(result.observed);
const sourceMirrorSync = record(result.sourceMirrorSync);
const publish = record(result.publish);
const flush = record(result.flush);
const runtimeSecretsApply = record(result.runtimeSecretsApply);
const publicExposureApply = record(result.publicExposureApply);
const publicExposureCaddy = record(publicExposureApply.caddy);
const argoApply = record(result.argoApply);
const blocker = record(result.blocker);
const statusDiagnosis = record(result.statusDiagnosis);
const targetValidation = record(result.targetValidation);
const targetValidationBusiness = record(targetValidation.businessStatus);
const recoveryNext = record(result.recoveryNext);
const next = record(result.next);
const warnings = Array.isArray(result.warnings) ? result.warnings : [];
return [
String(result.command),
"",
table(["NODE", "LANE", "STATUS", "MODE", "PIPELINERUN"], [[result.node, result.lane, result.ok === true ? "ok" : "blocked", result.mode, result.pipelineRun]]),
"",
table(["SOURCE", "COMMIT", "AUTHORITY", "STAGE_REF", "IMAGE", "MANIFEST"], [[`${source.repository}@${source.branch}`, short(source.commit), source.sourceAuthority ?? "-", short(source.stageRef), image.ref, short(gitops.manifestSha256)]]),
"",
table(["GITOPS_PATH", "ARGO_APP", "TARGET_REV", "OBJECTS"], [[gitops.path, argo.applicationName, gitops.targetRevision, gitops.manifestObjects]]),
"",
table(["SCENARIO", "MAX_SECONDS", "CI_WAIT", "QVERIFY", "SECOND_PATH"], [[validation.scenarioId, validation.maxSeconds, validation.controlPlaneWaitMaxSeconds ?? "-", validation.quickVerifyMode ?? "-", validation.automaticSecondPath]]),
"",
Object.keys(observability).length === 0 ? "OTEL\n-" : table(["ENABLED", "ENDPOINT", "SERVICE", "COVERAGE"], [[observability.enabled, observability.endpointConfigured, observability.serviceName, observability.coverage]]),
"",
renderObservedStatus(observed),
"",
Object.keys(statusDiagnosis).length === 0 ? "STATUS_DIAGNOSIS\n-" : [
"STATUS_DIAGNOSIS",
table(["CODE", "PHASE", "PIPELINERUN", "SOURCE", "REGISTRY", "GIT_MIRROR", "GITOPS", "ARGO", "RUNTIME"], [[
statusDiagnosis.code,
statusDiagnosis.phase,
statusDiagnosis.pipelineRun,
statusDiagnosis.sourceMirror,
statusDiagnosis.registry,
statusDiagnosis.gitMirror,
statusDiagnosis.gitops,
statusDiagnosis.argo,
statusDiagnosis.runtime,
]]),
].join("\n"),
"",
Object.keys(sourceMirrorSync).length === 0 ? "SOURCE_MIRROR_SYNC\n-" : table(["OK", "PHASE", "JOB", "COMMIT", "STAGE_REF", "ELAPSED"], [[sourceMirrorSync.ok, sourceMirrorSync.phase, sourceMirrorSync.jobName, short(record(sourceMirrorSync.payload).mirrorCommit), short(record(sourceMirrorSync.payload).stageRef), sourceMirrorSync.elapsedMs ?? "-"]]),
"",
Object.keys(targetValidation).length === 0 ? "TARGET_VALIDATION\n-" : table(["OK", "STATUS", "BUSINESS", "ERROR_TITLE", "SCENARIO", "RUN", "OBSERVER", "REPORT", "FINDINGS", "ARTIFACTS"], [[
targetValidation.ok,
targetValidation.status,
targetValidationBusiness.status ?? "-",
targetValidation.errorTitleZh ?? targetValidation.failureTitleZh ?? targetValidationBusiness.errorTitleZh ?? "-",
targetValidation.scenarioId,
targetValidation.runId,
targetValidation.observerId,
short(targetValidation.reportJsonSha256),
targetValidation.findingCount,
targetValidation.artifactCount,
]]),
"",
Object.keys(publish).length === 0 ? "PUBLISH\n-" : renderPublishResult(publish),
"",
Object.keys(flush).length === 0
? "FLUSH\n-"
: flush.mode === "async-job"
? table(["OK", "MODE", "JOB", "STATUS"], [[flush.ok, flush.mode, record(flush.job).id, record(flush.next).status]])
: table(["OK", "EXIT", "TIMED_OUT", "PREVIEW"], [[flush.ok, record(flush.result).exitCode, record(flush.result).timedOut, record(flush.result).stdoutPreview]]),
"",
Object.keys(runtimeSecretsApply).length === 0 ? "RUNTIME_SECRETS\n-" : table(["OK", "SECRETS", "KEYS", "SKIPPED"], [[runtimeSecretsApply.ok, runtimeSecretsApply.secretCount ?? "-", runtimeSecretsApply.keyCount ?? "-", runtimeSecretsApply.skippedKeyCount ?? "-"]]),
"",
Object.keys(publicExposureApply).length === 0 ? "PUBLIC_EXPOSURE_APPLY\n-" : table(["OK", "SECRET", "CADDY", "HOST", "ROUTE_HTTP"], [[publicExposureApply.ok, record(publicExposureApply.secret).ok, record(publicExposureApply.caddy).ok, publicExposureApply.hostname, record(publicExposureApply.caddy).routeProbeHttpStatus ?? "-"]]),
"",
Object.keys(publicExposureCaddy).length === 0 || publicExposureCaddy.ok === true
? "CADDY_APPLY_DETAIL\n-"
: table(["PY", "VALIDATE", "RELOAD", "PROBE", "HTTP", "BLOCK", "ACTIVE", "ERROR", "STDOUT", "STDERR"], [[publicExposureCaddy.pythonExitCode, publicExposureCaddy.validateExitCode, publicExposureCaddy.reloadExitCode, publicExposureCaddy.routeProbeExitCode, publicExposureCaddy.routeProbeHttpStatus, publicExposureCaddy.afterBlockPresent, publicExposureCaddy.active, short(publicExposureCaddy.errorPreview), short(record(publicExposureCaddy.result).stdoutPreview), short(record(publicExposureCaddy.result).stderrPreview)]]),
"",
Object.keys(argoApply).length === 0 ? "ARGO_APPLY\n-" : table(["OK", "EXIT", "PREVIEW"], [[argoApply.ok, record(argoApply.result).exitCode, record(argoApply.result).stdoutPreview]]),
"",
warnings.length === 0 ? "WARNINGS\n-" : ["WARNINGS", ...warnings.map((item) => `- ${text(item)}`)].join("\n"),
"",
Object.keys(blocker).length === 0 ? "BLOCKER\n-" : ["BLOCKER", table(["CODE", "REASON"], [[blocker.code, blocker.reason]])].join("\n"),
"",
Object.keys(recoveryNext).length === 0 ? "RECOVERY_NEXT\n-" : [
"RECOVERY_NEXT",
table(["REASON", "PIPELINERUN", "DIGEST", "GITOPS"], [[recoveryNext.reason, recoveryNext.pipelineRun ?? "-", short(recoveryNext.digestRef), short(recoveryNext.gitopsCommit)]]),
` publish-current: ${recoveryNext.publishCurrent ?? "-"}`,
` status: ${recoveryNext.nextStatus ?? "-"}`,
` git-mirror: ${recoveryNext.gitMirrorStatus ?? "-"}`,
` sync: ${recoveryNext.gitMirrorSync ?? "-"}`,
` flush: ${recoveryNext.gitMirrorFlush ?? "-"}`,
` apply: ${recoveryNext.controlPlaneApply ?? "-"}`,
].join("\n"),
"",
"NEXT",
` plan: ${next.plan ?? "-"}`,
` status: ${next.status ?? "-"}`,
` image: ${next.image ?? "-"}`,
` trigger-current: ${next.triggerCurrent ?? "-"}`,
` apply: ${next.apply ?? "-"}`,
` validate: ${next.validate ?? "-"}`,
` quick-verify: ${next.quickVerify ?? "-"}`,
` git-mirror: ${next.gitMirrorStatus ?? "-"}`,
` sync: ${next.gitMirrorSync ?? "-"}`,
` flush: ${next.gitMirrorFlush ?? "-"}`,
"",
"DISCLOSURE",
" default view is a bounded CI/CD summary; full manifest content is represented by object counts and sha256.",
" sentinel unavailable policy is structured-failure; no automatic second execution path is rendered.",
].join("\n");
}
export function renderObservedStatus(observed: Record<string, unknown>): string {
const rows = [
observedStatusRow("source", observed.sourceMirror),
observedStatusRow("registry", observed.registry),
observedStatusRow("git-mirror", observed.gitMirror),
observedStatusRow("gitops", observed.gitops),
observedStatusRow("argo", observed.argo),
observedStatusRow("runtime", observed.runtime),
observedStatusRow("cadence", observed.cadence),
].filter((row) => row !== null);
if (rows.length === 0) return "OBSERVED\n-";
return table(["CHECK", "OK", "DETAIL", "EXIT", "TIMED_OUT", "PREVIEW"], rows);
}
export function observedStatusRow(name: string, value: unknown): unknown[] | null {
const item = record(value);
if (Object.keys(item).length === 0) return null;
const result = record(item.result);
return [name, item.ok, observedDetail(name, item), result.exitCode, result.timedOut, result.stdoutPreview];
}
export function observedDetail(name: string, item: Record<string, unknown>): string {
if (name === "source") return `${record(item.probe).mode ?? "mirror"} ${short(record(item.probe).commit)}/${short(record(item.probe).expectedCommit)}`;
if (name === "registry") return `${record(item.probe).present === true ? "present" : "missing"} ${short(record(item.probe).digest)}`;
if (name === "git-mirror" && item.skipped === true) return `${item.reason ?? "skipped"}`;
if (name === "gitops") return `${short(item.revision)} image=${short(item.image)}`;
if (name === "argo") {
const diagnostics = record(item.diagnostics);
const problems = Array.isArray(diagnostics.problemResources) ? diagnostics.problemResources : [];
const first = problems.find((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry)) as Record<string, unknown> | undefined;
const problemText = Number(diagnostics.problemResourceCount ?? 0) > 0
? ` degraded=${diagnostics.problemResourceCount}:${first?.kind ?? "-"} ${first?.namespace ?? "-"}/${first?.name ?? "-"} ${first?.healthStatus ?? first?.status ?? "-"}`
: "";
return `${item.syncStatus ?? "-"} ${item.healthStatus ?? "-"} ${short(item.revision)}/${short(item.expectedRevision)}${problemText}`;
}
if (name === "runtime") {
const probe = record(item.probe);
const deployment = record(probe.deployment);
return `ready=${deployment.readyReplicas ?? "-"} image=${short(deployment.image)}/${short(deployment.expectedImage)}`;
}
if (name === "cadence") {
if (item.skipped === true) return `${item.reason ?? "skipped"}`;
const probe = record(item.probe);
return `${probe.code ?? "ok"} schedule=${probe.schedule ?? "-"}/${probe.expectedSchedule ?? "-"} last=${probe.lastScheduleTime ?? "-"} jobs=${probe.jobCount ?? "-"}`;
}
return "-";
}
export function renderAsyncJobResult(result: Record<string, unknown>): string {
const job = record(result.job);
const next = record(result.next);
return [
String(result.command),
"",
table(["NODE", "LANE", "MODE", "MUTATION", "JOB"], [[result.node, result.lane, result.mode, result.mutation, job.id]]),
"",
table(["STATUS", "NAME", "CREATED"], [[job.status, job.name, job.createdAt]]),
"",
"NEXT",
` status: ${next.status ?? "-"}`,
` wait: ${next.wait ?? "-"}`,
"",
"DISCLOSURE",
" confirmed operation is delegated to UniDesk job status to keep interactive calls bounded.",
].join("\n");
}
export function rendered(ok: boolean, command: string, text: string): RenderedCliResult {
return { ok, command, renderedText: `${text.trimEnd()}\n`, contentType: "text/plain" };
}
export function readConfigFile(file: string): unknown {
if (file.startsWith("/") || file.includes("..") || !file.startsWith("config/")) throw new Error(`unsafe configRef file: ${file}`);
const abs = rootPath(file);
if (!existsSync(abs)) throw new Error(`${file} does not exist`);
return Bun.YAML.parse(readFileSync(abs, "utf8")) as unknown;
}
export function configRefFile(ref: string): string {
const [file, path, extra] = ref.split("#");
if (extra !== undefined || file === undefined || path === undefined || file.length === 0 || path.length === 0) throw new Error(`invalid configRef: ${ref}`);
return file;
}
export function valueAtPath(value: unknown, path: string): unknown {
let current: unknown = value;
for (const segment of path.split(".")) {
const match = /^([A-Za-z0-9_-]+)?(?:\[(\d+)\])?$/u.exec(segment);
if (match === null) return undefined;
if (match[1] !== undefined) {
if (!isRecord(current)) return undefined;
current = current[match[1]];
}
if (match[2] !== undefined) {
if (!Array.isArray(current)) return undefined;
current = current[Number(match[2])];
}
}
return current;
}
export function stringAt(value: unknown, path: string): string {
const found = valueAtPath(value, path);
if (typeof found !== "string" || found.length === 0) throw new Error(`${path} must be a non-empty string`);
return found;
}
export function nonEmptyString(value: unknown): string | null {
return typeof value === "string" && value.length > 0 ? value : null;
}
export function stringField(value: Record<string, unknown>, path: string): string {
return stringAt(value, path);
}
export function stringTarget(value: unknown, label: string): string {
if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must resolve to a non-empty string`);
return value;
}
export function numberAt(value: unknown, path: string): number {
const found = valueAtPath(value, path);
if (typeof found !== "number" || !Number.isFinite(found)) throw new Error(`${path} must be a number`);
return found;
}
export function booleanAt(value: unknown, path: string): boolean {
const found = valueAtPath(value, path);
if (typeof found !== "boolean") throw new Error(`${path} must be a boolean`);
return found;
}
export function finiteNumberOrNull(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
export function arrayAt(value: unknown, path: string): unknown[] {
const found = valueAtPath(value, path);
if (!Array.isArray(found)) throw new Error(`${path} must be an array`);
return found;
}
export function arrayAtNullable(value: unknown, path: string): Record<string, unknown>[] {
const found = valueAtPath(value, path);
return Array.isArray(found) ? found.map(record) : [];
}
export function recordTarget(value: unknown, label: string): Record<string, unknown> {
if (!isRecord(value)) throw new Error(`${label} must resolve to an object`);
return value;
}
export function record(value: unknown): Record<string, unknown> {
return isRecord(value) ? value : {};
}
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function manifestObjectSummary(items: readonly Record<string, unknown>[]): readonly Record<string, unknown>[] {
return items.map((item) => ({
kind: item.kind ?? null,
name: record(item.metadata).name ?? null,
namespace: record(item.metadata).namespace ?? null,
}));
}
export function compactCommand(result: CommandResult): CompactCommandResult {
return {
exitCode: result.exitCode,
timedOut: result.timedOut,
stdoutBytes: Buffer.byteLength(result.stdout),
stderrBytes: Buffer.byteLength(result.stderr),
stdoutPreview: result.stdout.trim().slice(0, 500),
stderrPreview: result.stderr.trim().slice(0, 500),
};
}
export function resolveSentinelChildJson(result: Pick<CommandResult, "stdout" | "stderr" | "exitCode" | "timedOut">, requestedStdoutType: string): { parsed: Record<string, unknown> | null; diagnostics: Record<string, unknown> } {
const resolved = resolveCliChildJsonCommandResult({
result,
requestedStdoutType,
acceptParsed: (value) => Object.keys(value).length > 0,
});
return { parsed: resolved.parsed, diagnostics: resolved.diagnostics };
}
export function parseJsonObject(text: string): Record<string, unknown> | null {
const trimmed = text.trim();
if (trimmed.length === 0) return null;
try {
const parsed = JSON.parse(trimmed) as unknown;
return isRecord(parsed) ? parsed : null;
} catch {
return null;
}
}
export function table(headers: string[], rows: unknown[][]): string {
const normalized = [headers, ...rows.map((row) => row.map(text))];
const widths = headers.map((_, index) => Math.max(...normalized.map((row) => text(row[index] ?? "").length)));
return normalized.map((row) => row.map((cell, index) => text(cell).padEnd(widths[index])).join(" ").trimEnd()).join("\n");
}
export function text(value: unknown): string {
if (value === undefined || value === null || value === "") return "-";
if (typeof value === "boolean") return value ? "true" : "false";
return String(value).replace(/\s+/gu, " ").trim();
}
export function short(value: unknown): string {
const raw = text(value);
if (raw === "-") return raw;
if (/^sha256:[0-9a-f]{64}$/iu.test(raw)) return `${raw.slice(0, 19)}...`;
if (/^[0-9a-f]{40}$/iu.test(raw)) return raw.slice(0, 12);
return raw.length > 42 ? `${raw.slice(0, 39)}...` : raw;
}
export function sha256(textValue: string): string {
return `sha256:${createHash("sha256").update(textValue).digest("hex")}`;
}
export function shellQuote(value: string): string {
return `'${value.replace(/'/gu, "'\\''")}'`;
}
export function tomlEscape(value: string): string {
return value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
}
File diff suppressed because it is too large Load Diff