import { constants as fsConstants } from "node:fs"; import { access } from "node:fs/promises"; import path from "node:path"; export const BUILDABLE_IMPLEMENTATION_STATES = Object.freeze([ "repo-entrypoint", "static-web-wrapper", "repo-bundle" ]); const buildableImplementationStates = new Set(BUILDABLE_IMPLEMENTATION_STATES); async function pathExists(repoRoot, relativePath) { try { await access(path.join(repoRoot, relativePath), fsConstants.R_OK); return true; } catch { return false; } } export function publishEnabledForService(service) { return service.sourceState !== "intentionally-disabled" && buildableImplementationStates.has(service.implementationState); } export function notPublishedReasonForService(service) { if (publishEnabledForService(service)) { return "publish_not_run"; } if (service.sourceState === "intentionally-disabled") { return "disabled_intentionally"; } if (service.implementationState === "missing-runtime-entrypoint") { return "disabled_missing_runtime_entrypoint"; } if (service.implementationState === "library-only") { return "disabled_missing_executable_entrypoint"; } return `disabled_${service.implementationState}`; } export function servicePublishPolicy(service) { const publishEnabled = publishEnabledForService(service); return { publishEnabled, artifactRequired: publishEnabled, artifactScope: publishEnabled ? "required" : "disabled", notPublishedReason: publishEnabled ? "publish_not_run" : notPublishedReasonForService(service) }; } export function withServicePublishPolicy(service) { return { ...service, ...servicePublishPolicy(service) }; } function serviceSourceState(catalog, serviceId) { return catalog?.services?.find((service) => service.serviceId === serviceId)?.sourceState ?? "source-present"; } export async function resolveDevArtifactService(repoRoot, serviceId, catalog = null) { const sourceState = serviceSourceState(catalog, serviceId); const cmdPath = `cmd/${serviceId}/main.mjs`; if (await pathExists(repoRoot, cmdPath)) { return withServicePublishPolicy({ serviceId, runtimeKind: "node-command", entrypoint: cmdPath, implementationState: "repo-entrypoint", sourceState }); } if (serviceId === "hwlab-cloud-web") { return withServicePublishPolicy({ serviceId, runtimeKind: "cloud-web", entrypoint: "web/hwlab-cloud-web/index.html", implementationState: "static-web-wrapper", sourceState }); } if (serviceId === "hwlab-cli") { const hasBin = await pathExists(repoRoot, "tools/hwlab-cli/bin/hwlab-cli.mjs"); return withServicePublishPolicy({ serviceId, runtimeKind: "cli", entrypoint: hasBin ? "tools/hwlab-cli/bin/hwlab-cli.mjs" : "tools/hwlab-cli/lib/cli.mjs", implementationState: hasBin ? "repo-entrypoint" : "library-only", sourceState }); } if (serviceId === "hwlab-agent-skills") { return withServicePublishPolicy({ serviceId, runtimeKind: "skills-bundle", entrypoint: "skills/hwlab-agent-runtime/SKILL.md", implementationState: "repo-bundle", sourceState }); } return withServicePublishPolicy({ serviceId, runtimeKind: "health-placeholder", entrypoint: null, implementationState: "missing-runtime-entrypoint", sourceState }); } export async function resolveDevArtifactServices(repoRoot, serviceIds, frozenServiceIds, catalog = null) { const unknown = serviceIds.filter((serviceId) => !frozenServiceIds.includes(serviceId)); if (unknown.length > 0) { throw new Error(`unknown service IDs: ${unknown.join(", ")}`); } const services = []; for (const serviceId of serviceIds) { services.push(await resolveDevArtifactService(repoRoot, serviceId, catalog)); } return services; } export function serviceInventoryFromServices(services) { const requiredServiceIds = services .filter((service) => service.artifactRequired) .map((service) => service.serviceId); const disabledServiceIds = services .filter((service) => !service.artifactRequired) .map((service) => service.serviceId); return { version: "v2", serviceCount: services.length, requiredServiceCount: requiredServiceIds.length, disabledServiceCount: disabledServiceIds.length, requiredServiceIds, disabledServiceIds, services: services.map((service) => ({ serviceId: service.serviceId, publishEnabled: service.publishEnabled, artifactRequired: service.artifactRequired, artifactScope: service.artifactScope, runtimeKind: service.runtimeKind, implementationState: service.implementationState, sourceState: service.sourceState, entrypoint: service.entrypoint, disabledReason: service.artifactRequired ? null : service.notPublishedReason })) }; }