#!/usr/bin/env node import assert from "node:assert/strict"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { readStructuredFile } from "./src/structured-config.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const namespace = "hwlab-dev"; async function readConfig(relativePath) { return readStructuredFile(repoRoot, relativePath); } function listItems(document) { return document?.kind === "List" ? document.items ?? [] : [document]; } function serviceIdsFromDeploy(deploy) { return (deploy.services ?? []).map((service) => service.serviceId).filter(Boolean); } function assertUnique(label, values) { assert.equal(new Set(values).size, values.length, `${label} must be unique`); } function assertDeployManifestConsistency(deploy) { assert.equal(deploy.environment, "dev", "deploy/deploy.yaml environment"); assert.equal(deploy.namespace, namespace, "deploy/deploy.yaml namespace"); assert.equal(deploy.profiles?.prod?.enabled, false, "deploy/deploy.yaml prod disabled"); const serviceIds = serviceIdsFromDeploy(deploy); assert.ok(serviceIds.length > 0, "deploy/deploy.yaml services must not be empty"); assertUnique("deploy/deploy.yaml serviceId", serviceIds); const byId = new Set(serviceIds); for (const mapping of deploy.k3s?.serviceMappings ?? []) { assert.ok(byId.has(mapping.serviceId), `deploy/deploy.yaml k3s serviceMapping ${mapping.name} references a declared service`); assert.equal(mapping.namespace, namespace, `${mapping.name} namespace`); } const v02 = deploy.lanes?.v02; if (v02) { const envReuseServices = Array.isArray(v02.envReuseServices) ? v02.envReuseServices.filter(Boolean) : []; assert.ok(envReuseServices.length > 0, "deploy.lanes.v02.envReuseServices must not be empty"); assertUnique("deploy.lanes.v02.envReuseServices", envReuseServices); for (const serviceId of envReuseServices) { assert.ok(byId.has(serviceId), `deploy.lanes.v02.envReuseServices ${serviceId} must be declared in deploy.services`); assert.ok(v02.serviceDeclarations?.[serviceId], `deploy.lanes.v02.serviceDeclarations.${serviceId} is required`); assert.ok(v02.bootScripts?.[serviceId], `deploy.lanes.v02.bootScripts.${serviceId} is required`); } } } function assertK8sServiceMappings(deploy, services) { const serviceDocs = listItems(services).filter((item) => item?.kind === "Service"); const serviceNames = new Set(serviceDocs.map((item) => item.metadata?.name).filter(Boolean)); for (const mapping of deploy.k3s?.serviceMappings ?? []) { assert.ok(serviceNames.has(mapping.name), `deploy/k8s/base/services.yaml missing Service ${mapping.name}`); const service = serviceDocs.find((item) => item.metadata?.name === mapping.name); const port = service?.spec?.ports?.find((entry) => entry.name === "http") ?? service?.spec?.ports?.[0]; assert.equal(port?.port, mapping.port, `${mapping.name} service port`); assert.equal(port?.targetPort, mapping.targetPort, `${mapping.name} targetPort`); } } function assertRuntimeServiceWorkloads(deploy, workloads) { const workloadNames = new Set(listItems(workloads).map((item) => item?.metadata?.name).filter(Boolean)); for (const service of deploy.services ?? []) { if (service.externalImage || service.kind === "external") continue; assert.ok(workloadNames.has(service.serviceId), `deploy/k8s/base/workloads.yaml missing workload ${service.serviceId}`); } } export async function readDevM3CardinalityInputs() { const [deploy, workloads, services, topology] = await Promise.all([ readConfig("deploy/deploy.yaml"), readConfig("deploy/k8s/base/workloads.yaml"), readConfig("deploy/k8s/base/services.yaml"), readConfig("fixtures/mvp/m3-hardware-loop/topology.json").catch(() => null) ]); return { deploy, workloads, services, topology }; } export function validateDevM3Cardinality({ deploy, workloads, services }) { assertDeployManifestConsistency(deploy); assertK8sServiceMappings(deploy, services); assertRuntimeServiceWorkloads(deploy, workloads); } export async function main() { validateDevM3Cardinality(await readDevM3CardinalityInputs()); console.log("validated DEV deploy manifest service mapping consistency"); } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { try { await main(); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; } }