// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. help module for scripts/src/ci.ts. // SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets. // Moved mechanically from scripts/src/ci.ts:3273-3409 for #903. import { randomUUID } from "node:crypto"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, posix as posixPath } from "node:path"; import { blockedCatalogArtifactIds, catalogSummary, findCiCatalogArtifact, loadCiCatalog, supportedSourceBuildArtifactIds, type CiCatalogArtifact, type CiSourceBuildCatalogArtifact, type CiUpstreamImageCatalogArtifact } from "../ci-catalog"; import { runCommand } from "../command"; import { type UniDeskConfig, repoRoot, rootPath } from "../config"; import { ensureGithubSshIdentityForProvider, gitSshHttpConnectProxySource } from "../deploy-ssh-identity"; import { jobWithTail, listJobs, readJob, startJob } from "../jobs"; import { coreInternalFetch } from "../microservices"; import { artifactRegistryReadonlyResultFromCommand, buildArtifactRegistryReadonlyProbe, parseArtifactRegistryOptions, type ArtifactRegistryReadonlyProbe, } from "../artifact-registry"; import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard"; import { runSshCommandCapture } from "../ssh"; import { g14CiPipelineManifest, tektonPipelineReleaseUrl, tektonPipelineVersion, tektonTriggersInterceptorsUrl, tektonTriggersReleaseUrl, tektonTriggersVersion } from "./types"; export function catalogArtifactDescriptor(artifact: CiCatalogArtifact): Record { if (artifact.kind === "source-build") { return { serviceId: artifact.serviceId, kind: artifact.kind, status: artifact.status, producer: artifact.producer, source: artifact.source, image: artifact.image, ...(artifact.notes === undefined ? {} : { notes: artifact.notes }), ...(artifact.blockedReason === undefined ? {} : { blockedReason: artifact.blockedReason }), }; } return { serviceId: artifact.serviceId, kind: artifact.kind, status: artifact.status, producer: artifact.producer, upstream: artifact.upstream, blockedReason: artifact.blockedReason, ...(artifact.notes === undefined ? {} : { notes: artifact.notes }), }; } export function ciHelp(): Record { const catalog = loadCiCatalog(); return { command: "ci install|status|github-ssh-identity|run|publish-backend-core|publish-user-service|run-dev-e2e|logs|cleanup-runs|cleanup-failed-pods", description: "Manage native k3s Tekton CI on D601 or G14. CI may publish commit-pinned image artifacts, but it intentionally does not deploy CD.", examples: [ "bun scripts/cli.ts ci install", "bun scripts/cli.ts ci install --skip-prewarm", "bun scripts/cli.ts ci install --skip-prewarm --skip-tekton-install", "bun scripts/cli.ts ci install-status latest", "bun scripts/cli.ts ci install --wait --skip-prewarm --skip-tekton-install", "bun scripts/cli.ts ci plan --target D601", "bun scripts/cli.ts ci github-ssh-identity plan --target D518", "bun scripts/cli.ts ci github-ssh-identity ensure --target D518 --confirm", "bun scripts/cli.ts ci install --target G14", "bun scripts/cli.ts ci run --revision ", "bun scripts/cli.ts ci run --target G14 --revision ", "bun scripts/cli.ts ci publish-backend-core --commit ", "bun scripts/cli.ts ci publish-user-service --service baidu-netdisk --commit ", "bun scripts/cli.ts ci publish-user-service --service mdtodo --commit ", "bun scripts/cli.ts ci publish-user-service --service claudeqq --commit ", "bun scripts/cli.ts ci publish-user-service --service code-queue --commit ", "bun scripts/cli.ts ci publish-user-service --service decision-center --commit ", "bun scripts/cli.ts ci publish-user-service --service frontend --commit ", "bun scripts/cli.ts ci run-dev-e2e --wait-ms 600000", "bun scripts/cli.ts ci logs [--target G14] [--tail-lines 80]", "bun scripts/cli.ts ci cleanup-runs --target D601 --min-age-minutes 60 --limit 50 --dry-run", "bun scripts/cli.ts ci cleanup-runs --target D601 --min-age-minutes 60 --limit 50 --confirm", "bun scripts/cli.ts ci cleanup-failed-pods --target D601 --namespace unidesk-ci --min-age-minutes 60 --dry-run", "bun scripts/cli.ts ci cleanup-failed-pods --target D601 --namespace unidesk-ci --min-age-minutes 60 --confirm", ], tekton: { pipelineVersion: tektonPipelineVersion, triggersVersion: tektonTriggersVersion, sources: { pipeline: tektonPipelineReleaseUrl, triggers: tektonTriggersReleaseUrl, interceptors: tektonTriggersInterceptorsUrl, }, targets: { default: "D601", g14: { providerId: "G14", pipelineManifest: g14CiPipelineManifest, nodeSelector: "unidesk.ai/node-id=G14", }, }, }, install: { defaultMode: "async-job", waitFlag: "Use --wait only for explicit synchronous debugging; the default returns a .state/jobs job immediately with install-status follow-up.", statusCommand: "bun scripts/cli.ts ci install-status ", prewarmDefault: true, skipPrewarm: "Use --skip-prewarm only to refresh Tekton/CI manifests when runtime images are already present or prewarm is blocked by provider root/containerd permissions.", skipTektonInstall: "Use --skip-tekton-install with --skip-prewarm only when Tekton is already installed and only UniDesk CI manifests/triggers need refreshing.", }, backendCoreArtifact: { producer: "D601 CI", registry: "127.0.0.1:5000/unidesk/backend-core:", cdCommand: "bun scripts/cli.ts deploy apply --env prod --service backend-core --commit ", }, userServiceArtifact: { producer: "D601 CI", command: "bun scripts/cli.ts ci publish-user-service --service --commit ", transports: { auto: "uses direct-docker for repo-owned code-queue artifacts and Tekton for the remaining services", tekton: "D601 Tekton PipelineRun through backend-core/provider control plane", directDocker: "repo-owned Docker artifact publish without backend-core/database dispatch; no deploy apply, rollout, restart, or active-task mutation", }, supportedServices: supportedSourceBuildArtifactIds().filter((serviceId) => serviceId !== "backend-core"), blockedServices: blockedCatalogArtifactIds(), registry: "127.0.0.1:5000/unidesk/:", outputFields: ["serviceId", "sourceCommit", "sourceRepo", "dockerfile", "imageRef", "tag", "digest", "digestRef"], summaryContract: catalog.summaryContract, catalogSummary: catalogSummary(), catalog: catalog.artifacts.map(catalogArtifactDescriptor), boundary: "artifact producer only; no prod deploy and no production namespace mutation", frontendNext: [ "bun scripts/cli.ts deploy apply --env dev --service frontend --commit ", "bun scripts/cli.ts deploy apply --env prod --service frontend --commit ", ], }, runDevE2E: { defaultTriggerMode: "commit-pinned-ssh-launcher", desiredState: "origin/master:deploy.json#environments.dev", scriptSource: "origin/master:deploy.json#environments.dev.ci", }, logs: { defaultCapture: "ssh-stream", dispatchTaskFallback: "--dispatch-task", tailLines: "1..2000", reason: "stream capture avoids backend-core task-result compactJson truncating CI log text before the CLI can extract failure hints", }, cleanupRuns: { defaultMode: "dry-run", namespace: "unidesk-ci", selector: "status.phase in Succeeded,Failed", guard: "--confirm required for deletion; Running/Pending pods are never selected", }, cleanupFailedPods: { defaultNamespace: "unidesk-ci", defaultMinAgeMinutes: 60, defaultLimit: 50, scope: "Kubernetes pods with status.phase=Failed only; does not touch Running/Pending pods, workloads, PVCs, Secrets, or Tekton resources", defaultMode: "dry-run", }, }; } export function requireRunId(value: string): string { if (!/^[a-z0-9]([-a-z0-9]{0,46}[a-z0-9])?$/u.test(value)) { throw new Error("ci run-dev-e2e run id must be DNS-safe lowercase alnum/dash, max 48 chars"); } return value; }