Merge pull request #2886 from pikasTech/fix/hwlab-first-materialization-scope
Pipelines as Code CI / platform-infra-gitea-nc01- Success

修复 HWLAB 首次物化发布范围审阅
This commit is contained in:
Lyon
2026-07-23 08:40:07 +08:00
committed by GitHub
3 changed files with 76 additions and 2 deletions
@@ -426,6 +426,9 @@ test("L2/L3 release exposes plan first and trigger only sends the PaC webhook",
expect(planAction).toContain("imageBuildCount");
expect(planAction).toContain("envReuse");
expect(planAction).toContain("requestedScope");
expect(planAction).toContain("domain?.artifactCatalog?.status === 'missing'");
expect(planAction).toContain("mode: firstMaterialization ? 'first-materialization' : 'changed-paths'");
expect(planAction).toContain("[...new Set([...buildServices, ...rolloutServices, ...affectedServices])].sort()");
expect(planAction).toContain("service.envChangedPaths");
expect(planAction).toContain("service.codeChangedPaths");
expect(planAction).toContain("(service.reason || []).includes('gitops-render-input-changed')");
@@ -628,6 +631,41 @@ test("PaC compact release plan keeps bounded registry probe results", () => {
expect(JSON.stringify(compact)).not.toContain("sha256:aaaaaaaa");
});
test("PaC compact release plan summarizes env reuse without duplicate service lists", () => {
const compact = compactManualReleasePlan({
envReuse: {
status: "planned",
groups: [{
id: "shared-runtime",
buildService: "api",
buildRequired: true,
services: ["api", "worker", "web"],
buildServices: ["api"],
consumerServices: ["worker", "web"],
imageRepository: "registry.invalid/private/shared-runtime",
}],
reusedServices: ["unchanged-service"],
reusedServiceCount: 1,
buildSkippedServices: ["worker", "web", "unchanged-service"],
},
});
expect(compact.envReuse).toEqual({
status: "planned",
groups: [{
id: "shared-runtime",
buildService: "api",
buildRequired: true,
serviceCount: 3,
buildServiceCount: 1,
consumerServiceCount: 2,
}],
reusedServiceCount: 1,
buildSkippedServiceCount: 3,
});
expect(JSON.stringify(compact)).not.toContain("registry.invalid");
expect(JSON.stringify(compact)).not.toContain("unchanged-service");
});
test("PaC release manifest renders YAML-owned watcher startup backlog probes", () => {
const pac = readPacConfig({ consumerId: "hwlab-nc01-v03", selectDefault: true });
const source = readFileSync(resolve(import.meta.dir, "fixtures/pac/release-leading-comment-only.yaml"), "utf8");
@@ -611,7 +611,7 @@ const configuredImages = JSON.parse(process.env.UNIDESK_PAC_MANUAL_IMAGE_REPOSIT
const affectedServices = domain?.affectedServices || (effectiveChangedPaths.length > 0 ? [process.env.UNIDESK_PAC_MANUAL_RUNTIME_SERVICE].filter(Boolean) : []);
const rolloutServices = domain?.rolloutServices || affectedServices;
const buildServices = domain?.buildServices || (effectiveChangedPaths.length > 0 ? configuredImages : []);
const requestedServices = domain
const changedRequestedServices = domain
? (domain.services || [])
.filter((service) => (service.changedPaths || []).length > 0
|| (service.envChangedPaths || []).length > 0
@@ -621,6 +621,10 @@ const requestedServices = domain
|| (service.reason || []).includes('gitops-render-input-changed'))
.map((service) => service.serviceId || service.id)
: (effectiveChangedPaths.length > 0 ? [process.env.UNIDESK_PAC_MANUAL_RUNTIME_SERVICE].filter(Boolean) : []);
const firstMaterialization = domain?.artifactCatalog?.status === 'missing';
const requestedServices = firstMaterialization
? [...new Set([...buildServices, ...rolloutServices, ...affectedServices])].sort()
: changedRequestedServices;
const expandedBuildServices = domain
? buildServices.filter((serviceId) => !requestedServices.includes(serviceId))
: [];
@@ -685,6 +689,7 @@ const plan = {
: null,
},
requestedScope: {
mode: firstMaterialization ? 'first-materialization' : 'changed-paths',
affectedServiceCount: requestedServices.length,
affectedServices: requestedServices,
},
@@ -728,7 +728,7 @@ export function compactManualReleasePlan(plan: Record<string, any>): Record<stri
},
rangeBaseline: plan.rangeBaseline ?? null,
requestedScope: plan.requestedScope ?? null,
envReuse: plan.envReuse ?? null,
envReuse: compactManualReleaseEnvReuse(plan.envReuse),
imageBuildCount: plan.imageBuildCount ?? null,
buildServices: plan.buildServices ?? [],
rolloutServiceCount: plan.rolloutServiceCount ?? null,
@@ -740,6 +740,37 @@ export function compactManualReleasePlan(plan: Record<string, any>): Record<stri
};
}
function compactManualReleaseEnvReuse(value: unknown): Record<string, unknown> | null {
if (value === null || typeof value !== "object") return null;
const envReuse = value as Record<string, unknown>;
const groups = Array.isArray(envReuse.groups)
? envReuse.groups.map((item) => {
const group = item !== null && typeof item === "object" ? item as Record<string, unknown> : {};
const services = Array.isArray(group.services) ? group.services : [];
const buildServices = Array.isArray(group.buildServices) ? group.buildServices : [];
const consumerServices = Array.isArray(group.consumerServices) ? group.consumerServices : [];
return {
id: typeof group.id === "string" ? group.id : null,
buildService: typeof group.buildService === "string" ? group.buildService : null,
buildRequired: group.buildRequired === true,
serviceCount: services.length,
buildServiceCount: buildServices.length,
consumerServiceCount: consumerServices.length,
};
}).filter((item) => item.id !== null)
: [];
const reusedServices = Array.isArray(envReuse.reusedServices) ? envReuse.reusedServices : [];
const buildSkippedServices = Array.isArray(envReuse.buildSkippedServices) ? envReuse.buildSkippedServices : [];
return {
status: typeof envReuse.status === "string" ? envReuse.status : null,
groups,
reusedServiceCount: typeof envReuse.reusedServiceCount === "number"
? envReuse.reusedServiceCount
: reusedServices.length,
buildSkippedServiceCount: buildSkippedServices.length,
};
}
function compactRegistryProbe(value: unknown): Record<string, unknown> | null {
if (value === null || typeof value !== "object") return null;
const probe = value as Record<string, unknown>;