// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. options module for scripts/src/artifact-registry.ts. // SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets. // Moved mechanically from scripts/src/artifact-registry.ts:881-978 for #903. import { createHash } from "node:crypto"; import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { runCommand, type CommandResult } from "../command"; import { readConfig, type UniDeskConfig, repoRoot, rootPath } from "../config"; import { startJob } from "../jobs"; import { compareDeployJsonExecutorMirrors, deployJsonCommitImage, deployJsonDriftResult, deployJsonSourceOfTruth, hasDeployJsonExecutorContract, k3sManifestExecutorMirror, parseDeployJsonServiceContractBase64, readDeployJsonServiceContractFromFile, type DeployJsonExecutorMirror, type DeployJsonServiceContract, } from "../deploy-json-contract"; import { d601K3sGuardShellLines } from "../d601-k3s-guard"; import { composeRuntimeEnvValue } from "../runtime-env"; import type { ArtifactDeployEnvironment, ArtifactRegistryOptions } from "./types"; import { artifactRegistryOptionsFromTarget } from "./types"; import { resolveArtifactRegistryTarget } from "../ops/targets"; export function isHelpArg(value: string | undefined): boolean { return value === undefined || value === "help" || value === "--help" || value === "-h"; } export function requireValue(args: string[], index: number, option: string): string { const value = args[index + 1]; if (value === undefined || value.length === 0) throw new Error(`${option} requires a non-empty value`); return value; } export function positiveInt(value: string, option: string): number { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${option} must be a positive integer`); return parsed; } export function absolutePath(value: string, option: string): string { if (!value.startsWith("/")) throw new Error(`${option} must be an absolute path`); if (value.includes("\n") || value.includes("\0")) throw new Error(`${option} must not contain control characters`); return value.replace(/\/+$/u, ""); } export function commitValue(value: string, option: string): string { const normalized = value.toLowerCase(); if (!/^[0-9a-f]{40}$/u.test(normalized)) throw new Error(`${option} must be a full 40-character commit SHA`); return normalized; } export function environmentValue(value: string, option: string): ArtifactDeployEnvironment { if (value === "prod" || value === "dev") return value; throw new Error(`${option} must be one of: prod, dev`); } export function parseArtifactRegistryOptions(args: string[]): ArtifactRegistryOptions { let targetSelection: string | null = null; let environment: ArtifactDeployEnvironment | null = null; let host: string | null = null; let port: number | null = null; let image: string | null = null; let baseDir: string | null = null; let storageDir: string | null = null; let timeoutMs: number | null = null; let dryRun = false; let runNow = false; let commit: string | null = null; let targetImage: string | null = null; let serviceId: string | null = null; let sourceRepo: string | null = null; let sourceRepoExplicit = false; let deployRef: string | null = null; let deployJsonService: ArtifactRegistryOptions["deployJsonService"] = null; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--dry-run") { dryRun = true; } else if (arg === "--run-now") { runNow = true; } else if (arg === "--env" || arg === "--environment") { environment = environmentValue(requireValue(args, index, arg), arg); index += 1; } else if (arg === "--target" || arg === "--provider-id") { targetSelection = requireValue(args, index, arg); index += 1; } else if (arg === "--host") { host = requireValue(args, index, arg); index += 1; } else if (arg === "--port") { port = positiveInt(requireValue(args, index, arg), arg); index += 1; } else if (arg === "--image") { image = requireValue(args, index, arg); index += 1; } else if (arg === "--base-dir") { baseDir = absolutePath(requireValue(args, index, arg), arg); index += 1; } else if (arg === "--storage-dir") { storageDir = absolutePath(requireValue(args, index, arg), arg); index += 1; } else if (arg === "--timeout-ms") { timeoutMs = positiveInt(requireValue(args, index, arg), arg); index += 1; } else if (arg === "--commit") { commit = commitValue(requireValue(args, index, arg), arg); index += 1; } else if (arg === "--target-image") { targetImage = requireValue(args, index, arg); index += 1; } else if (arg === "--service" || arg === "--service-id") { serviceId = requireValue(args, index, arg); index += 1; } else if (arg === "--source-repo") { sourceRepo = requireValue(args, index, arg); sourceRepoExplicit = true; index += 1; } else if (arg === "--deploy-ref") { deployRef = requireValue(args, index, arg); index += 1; } else if (arg === "--deploy-json-service") { deployJsonService = parseDeployJsonServiceContractBase64(requireValue(args, index, arg)); index += 1; } else { throw new Error(`unknown artifact-registry option: ${arg}`); } } const options = artifactRegistryOptionsFromTarget(resolveArtifactRegistryTarget(targetSelection)); options.environment = environment; options.host = host ?? options.host; options.port = port ?? options.port; options.image = image ?? options.image; options.baseDir = baseDir ?? options.baseDir; options.storageDir = storageDir ?? options.storageDir; options.timeoutMs = timeoutMs ?? options.timeoutMs; options.dryRun = dryRun; options.runNow = runNow; options.commit = commit; options.targetImage = targetImage; options.serviceId = serviceId; options.sourceRepo = sourceRepo ?? options.sourceRepo; options.sourceRepoExplicit = sourceRepoExplicit; options.deployRef = deployRef; options.deployJsonService = deployJsonService; if (options.host !== "127.0.0.1") throw new Error("--host is first-stage restricted to 127.0.0.1"); if (options.port !== 5000) throw new Error("--port is first-stage restricted to 5000"); return options; }