



























@@ -5,9 +5,13 @@
55import fs from "node:fs";
66import path from "node:path";
77import { fileURLToPath } from "node:url";
8+import { laneResources, laneWeight } from "./lib/docker-e2e-plan.mjs";
9+import { allReleasePathLanes, mainLanes, tailLanes } from "./lib/docker-e2e-scenarios.mjs";
810911const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
1012const errors = [];
13+const packageJson = JSON.parse(readText("package.json"));
14+const packageScripts = new Set(Object.keys(packageJson.scripts ?? {}));
11151216function readText(relativePath) {
1317return fs.readFileSync(path.join(ROOT_DIR, relativePath), "utf8");
@@ -43,9 +47,67 @@ if (/^\s*(?:COPY|ADD)\s+\.\s+\/app(?:\s|$)/imu.test(dockerfile)) {
4347errors.push("scripts/e2e/Dockerfile: do not copy the source checkout into /app");
4448}
454950+function validateUniqueLanes(label, lanes) {
51+const seen = new Set();
52+for (const lane of lanes) {
53+if (seen.has(lane.name)) {
54+errors.push(`${label}: duplicate Docker E2E lane '${lane.name}'`);
55+}
56+seen.add(lane.name);
57+}
58+}
59+60+function validateLane(label, lane) {
61+if (!lane.name || typeof lane.name !== "string") {
62+errors.push(`${label}: Docker E2E lane is missing a string name`);
63+}
64+if (!lane.command || typeof lane.command !== "string") {
65+errors.push(`${label}: Docker E2E lane '${lane.name}' is missing a string command`);
66+return;
67+}
68+if (lane.e2eImageKind && lane.e2eImageKind !== "bare" && lane.e2eImageKind !== "functional") {
69+errors.push(
70+`${label}: Docker E2E lane '${lane.name}' has invalid image kind '${lane.e2eImageKind}'`,
71+);
72+}
73+if (lane.live && lane.e2eImageKind) {
74+errors.push(`${label}: live Docker E2E lane '${lane.name}' must not require a package image`);
75+}
76+if (!lane.live && !lane.e2eImageKind) {
77+errors.push(`${label}: package Docker E2E lane '${lane.name}' must declare an e2e image kind`);
78+}
79+if (laneWeight(lane) < 1) {
80+errors.push(`${label}: Docker E2E lane '${lane.name}' must have positive weight`);
81+}
82+if (!laneResources(lane).includes("docker")) {
83+errors.push(`${label}: Docker E2E lane '${lane.name}' must include the docker resource`);
84+}
85+86+for (const match of lane.command.matchAll(/\bpnpm\s+([^\s]+)/gu)) {
87+const script = match[1];
88+if (!packageScripts.has(script)) {
89+errors.push(
90+`${label}: Docker E2E lane '${lane.name}' references missing package script '${script}'`,
91+);
92+}
93+}
94+}
95+96+const releasePathLanes = allReleasePathLanes({ includeOpenWebUI: true });
97+for (const [label, lanes] of [
98+["release-path", releasePathLanes],
99+["main", mainLanes],
100+["tail", tailLanes],
101+]) {
102+validateUniqueLanes(label, lanes);
103+for (const lane of lanes) {
104+validateLane(label, lane);
105+}
106+}
107+46108if (errors.length > 0) {
47109console.error(errors.join("\n"));
48110process.exit(1);
49111}
5011251-console.log("Docker E2E package boundary guard passed.");
113+console.log("Docker E2E package boundary/catalog guard passed.");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。