type RuntimeLaneDefaults = { defaultNodeId?: string; defaultGitopsRoot?: string; defaultBranch?: string; defaultGitopsBranch?: string; defaultCatalogPath?: string; defaultRuntimeEndpoint?: string; defaultWebEndpoint?: string; defaultV02RuntimeEndpoint?: string; defaultSourceRepo?: string; }; export type GitOpsRenderArgs = { lane: string; nodeId?: string; outDir: string; gitopsRoot?: string; sourceBranch: string; gitopsBranch: string; catalogPath: string; imageTagMode: string; runtimeEndpoint: string; webEndpoint: string; sourceRepo: string; [key: string]: unknown; }; export type RuntimeLaneConfig = { node?: string; sourceBranch?: string; gitopsBranch?: string; artifactCatalog?: string; runtimePath?: string; imageTagMode?: string; endpoint?: string; externalPostgres?: { enabled?: boolean; serviceName?: string; endpointAddress?: string; port?: number; }; publicEndpoints?: { frontend?: string; api?: string; [key: string]: string | undefined; }; observability?: { traceExplorerUrlTemplate?: string; }; sourceRepo?: string; }; export type NodeConfig = { gitopsRoot?: string; sourceRepo?: string; publicHost?: string; }; export type RuntimeLaneMirrorSpec = { lane: string; sourceBranch: string; gitopsBranch: string; artifactCatalog: string; runtimePath: string; }; export type DeployConfig = { nodes?: Record; lanes?: Record; }; const defaultNodeId = "node"; const defaultGitopsRoot = "deploy/gitops/node"; const defaultPublicHost = "74.48.78.17"; export function isRuntimeLane(lane: unknown): lane is string { return /^v[0-9]{2,}$/u.test(String(lane ?? "")); } export function runtimeLaneMinor(lane: string): number { invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`); return Number.parseInt(lane.slice(1), 10); } export function versionNameForRuntimeLane(lane: string): string { return `v0.${runtimeLaneMinor(lane)}`; } export function namespaceNameForRuntimeLane(lane: string): string { invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`); return `hwlab-${lane}`; } export function runtimePathForRuntimeLane(lane: string): string { invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`); return `runtime-${lane}`; } export function nodeConfig(deploy: DeployConfig, nodeId: string): NodeConfig { const config = deploy.nodes?.[nodeId]; return isObject(config) ? config : {}; } export function nodeIdForRuntimeLane(deploy: DeployConfig, lane: string, defaults: RuntimeLaneDefaults = {}): string { const laneConfig = runtimeLaneConfig(deploy, lane); return laneConfig.node ?? defaults.defaultNodeId ?? defaultNodeId; } export function gitopsRootForNode(deploy: DeployConfig, nodeId: string, defaults: RuntimeLaneDefaults = {}): string { const config = nodeConfig(deploy, nodeId); return normalizeRepoPath(config.gitopsRoot ?? defaults.defaultGitopsRoot ?? defaultGitopsRoot); } export function runtimeGitopsPathForRuntimeLane(deploy: DeployConfig, lane: string, defaults: RuntimeLaneDefaults = {}): string { const laneConfig = runtimeLaneConfig(deploy, lane); const runtimePath = normalizeRepoPath(laneConfig.runtimePath ?? runtimePathForRuntimeLane(lane)); if (runtimePath.startsWith("deploy/")) return runtimePath; return joinRepoPath(gitopsRootForNode(deploy, nodeIdForRuntimeLane(deploy, lane, defaults), defaults), runtimePath); } export function defaultPortForRuntimeLane(lane: string): number { return 17666 + runtimeLaneMinor(lane) * 1000; } export function publicHostLabel(host = defaultPublicHost): string { return host.replaceAll(".", "-"); } export function defaultEndpointForRuntimeLane(lane: string, options: { host?: string; v02Endpoint?: string } = {}): string { if (lane === "v02" && options.v02Endpoint) return options.v02Endpoint; return `https://hwlab-${lane}.${publicHostLabel(options.host ?? defaultPublicHost)}.nip.io`; } export function runtimeLaneConfig(deploy: DeployConfig, lane: string): RuntimeLaneConfig { invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`); const config = deploy.lanes?.[lane]; invariant(isObject(config), `deploy.lanes.${lane} is required`); return config; } export function applyRuntimeLaneDeployConfig(args: GitOpsRenderArgs, deploy: DeployConfig, defaults: RuntimeLaneDefaults = {}): GitOpsRenderArgs { if (!isRuntimeLane(args.lane)) return args; const result: GitOpsRenderArgs = { ...args }; const laneConfig = runtimeLaneConfig(deploy, args.lane); const configuredNodeId = nodeIdForRuntimeLane(deploy, args.lane, defaults); const rootNodeId = nodeIdFromGitopsRoot(result.gitopsRoot); const nodeId = result.nodeId && result.nodeId !== defaultNodeId ? result.nodeId : rootNodeId ?? configuredNodeId; const node = nodeConfig(deploy, nodeId); const versionName = versionNameForRuntimeLane(args.lane); const defaultEndpointOptions: { host?: string; v02Endpoint?: string } = {}; if (node.publicHost) defaultEndpointOptions.host = node.publicHost; if (defaults.defaultV02RuntimeEndpoint) defaultEndpointOptions.v02Endpoint = defaults.defaultV02RuntimeEndpoint; const defaultEndpoint = defaultEndpointForRuntimeLane(args.lane, defaultEndpointOptions); const configuredEndpoint = laneConfig.endpoint ?? defaultEndpoint; const configuredWebEndpoint = laneConfig.publicEndpoints?.frontend ?? configuredEndpoint; const configuredRuntimeEndpoint = laneConfig.publicEndpoints?.api ?? configuredEndpoint; const defaultBranch = defaults.defaultBranch ?? "node"; const defaultGitopsBranch = defaults.defaultGitopsBranch ?? "node-gitops"; const defaultCatalogPath = defaults.defaultCatalogPath ?? "deploy/artifact-catalog.dev.json"; const defaultRuntimeEndpoint = defaults.defaultRuntimeEndpoint ?? "http://74.48.78.17:17667"; const defaultWebEndpoint = defaults.defaultWebEndpoint ?? "http://74.48.78.17:17666"; const defaultSourceRepo = defaults.defaultSourceRepo ?? "git@github.com:pikasTech/HWLAB.git"; const defaultCatalog = `deploy/artifact-catalog.${args.lane}.json`; if (!result.nodeId || result.nodeId === defaultNodeId) result.nodeId = nodeId; if (!result.gitopsRoot || result.gitopsRoot === (defaults.defaultGitopsRoot ?? defaultGitopsRoot)) result.gitopsRoot = gitopsRootForNode(deploy, nodeId, defaults); if (result.sourceBranch === defaultBranch || result.sourceBranch === versionName) result.sourceBranch = laneConfig.sourceBranch ?? versionName; if (result.gitopsBranch === defaultGitopsBranch || result.gitopsBranch === `${versionName}-gitops`) result.gitopsBranch = laneConfig.gitopsBranch ?? `${versionName}-gitops`; if (result.catalogPath === defaultCatalogPath || result.catalogPath === defaultCatalog) result.catalogPath = laneConfig.artifactCatalog ?? defaultCatalog; if (result.imageTagMode === "full") result.imageTagMode = laneConfig.imageTagMode ?? result.imageTagMode; if (result.runtimeEndpoint === defaultRuntimeEndpoint || result.runtimeEndpoint === defaultEndpoint) result.runtimeEndpoint = configuredRuntimeEndpoint; if (result.webEndpoint === defaultWebEndpoint || result.webEndpoint === defaultEndpoint) result.webEndpoint = configuredWebEndpoint; if (result.sourceRepo === defaultSourceRepo) result.sourceRepo = laneConfig.sourceRepo ?? node.sourceRepo ?? result.sourceRepo; return result; } function nodeIdFromGitopsRoot(gitopsRoot: unknown): string | undefined { if (typeof gitopsRoot !== "string") return undefined; const normalized = normalizeRepoPath(gitopsRoot); const match = normalized.match(/(?:^|\/)deploy\/gitops\/node\/([^/]+)$/u); return match?.[1] ? match[1].toUpperCase() : undefined; } export function runtimeLaneMirrorSpecs(deploy: DeployConfig, defaults: RuntimeLaneDefaults = {}): RuntimeLaneMirrorSpec[] { return Object.entries(deploy.lanes ?? {}) .filter(([lane, config]) => isRuntimeLane(lane) && isObject(config)) .sort(([left], [right]) => runtimeLaneMinor(left) - runtimeLaneMinor(right)) .map(([lane, config]) => { const laneConfig = config as RuntimeLaneConfig; const versionName = versionNameForRuntimeLane(lane); return { lane, sourceBranch: laneConfig.sourceBranch ?? versionName, gitopsBranch: laneConfig.gitopsBranch ?? `${versionName}-gitops`, artifactCatalog: laneConfig.artifactCatalog ?? `deploy/artifact-catalog.${lane}.json`, runtimePath: runtimeGitopsPathForRuntimeLane(deploy, lane, defaults), }; }); } function normalizeRepoPath(value: string): string { return value.replaceAll("\\\\", "/").replace(/^\.\//u, "").replace(/\/+$/u, ""); } function joinRepoPath(left: string, right: string): string { return `${normalizeRepoPath(left)}/${normalizeRepoPath(right).replace(/^\/+/, "")}`; } function isObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function invariant(condition: unknown, message: string): asserts condition { if (!condition) throw new Error(message); }