fix: 将 production release 归并回 v0.3 单线历史

This commit is contained in:
root
2026-07-18 12:46:15 +02:00
19 changed files with 1761 additions and 121 deletions
+43 -18
View File
@@ -24,6 +24,7 @@ import {
generatedPath,
gitopsPathForProfile,
imageTagForSource,
isNamedRuntimeLane,
isRuntimeLane,
jsonManifest,
label,
@@ -79,7 +80,7 @@ import {
} from "./src/gitops-render/tekton-manifests.mjs";
import { CLOUD_CORE_MIGRATIONS } from "../internal/db/schema.ts";
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev", externalPostgres = false, workbenchRedis = false, runtimeConfigMaps = false, externalSecrets = false } = {}) {
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev", externalPostgres = false, workbenchRedis = false, runtimeConfigMaps = false, externalSecrets = false, deploy = null } = {}) {
const resources = ["namespace.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"];
if (!isRuntimeLane(profile)) resources.splice(1, 0, "code-agent-codex-config.yaml");
if (runtimeConfigMaps) resources.splice(3, 0, "configmaps.yaml");
@@ -95,12 +96,12 @@ function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile ==
return {
apiVersion: "kustomize.config.k8s.io/v1beta1",
kind: "Kustomization",
namespace: namespaceNameForProfile(profile),
namespace: namespaceNameForProfile(profile, deploy),
resources
};
}
function readme({ source, args }) {
function readme({ source, args, deploy }) {
if (args.lane === "v02") {
return `# HWLAB v0.2 GitOps CI/CD
@@ -117,6 +118,21 @@ This directory is generated by \`scripts/gitops-render.mjs --lane v02\` from sou
- node DEV/PROD boundary: this lane does not modify archived DEV/PROD branches, \`hwlab-dev\`, \`hwlab-prod\`, 17666/17667 or 18666/18667.
- CronJob policy: v0.2 does not generate \`hwlab-v02-branch-poller\` or \`hwlab-v02-control-plane-reconciler\`; obsolete live CronJobs should be deleted by the UniDesk control-plane apply command.
- Gate policy: old DEV/D601/main gates do not enter this lane; new checks stay limited to the fixed branch, namespace, catalog, runtime path and Argo boundaries.
`;
}
if (isNamedRuntimeLane(args.lane)) {
const namespace = namespaceNameForProfile(args.lane, deploy);
const runtimePath = gitopsPathForProfile(args, args.lane, deploy);
return `# HWLAB ${args.lane} GitOps CI/CD
This directory is generated by \`scripts/gitops-render.mjs --lane ${args.lane}\` from the owning runtime lane declaration in \`deploy/deploy.yaml\`.
- Source: \`${args.sourceRepo}\` branch \`${args.sourceBranch}\` at \`${source.full}\`.
- GitOps: generated desired state is promoted to \`${args.gitWriteUrl}\` branch \`${args.gitopsBranch}\`.
- Artifact contract: runtime images come from \`${args.catalogPath}\`.
- Runtime: Argo CD consumes \`${args.gitReadUrl}:${args.gitopsBranch}:${runtimePath}\` and deploys only to namespace \`${namespace}\`.
- Public endpoints: web \`${args.webEndpoint}\`; API \`${args.runtimeEndpoint}\`.
- Authority: branch, catalog, runtime path, namespace, public services and endpoints are read from \`deploy/deploy.yaml\`; missing named-lane fields fail closed.
`;
}
return `# HWLAB node GitOps CI/CD
@@ -161,7 +177,7 @@ async function plannedFiles(args) {
let artifactCatalog = await readJsonIfPresent(args.catalogPath, null);
if (!artifactCatalog && isRuntimeLane(args.lane)) artifactCatalog = artifactCatalogSkeleton({ args, deploy, source });
if (!artifactCatalog) artifactCatalog = await readJson(args.catalogPath);
const settings = ciLaneSettings(args);
const settings = ciLaneSettings(args, deploy);
const profiles = laneRuntimeProfiles(args);
const migrationSources = isRuntimeLane(args.lane)
? await Promise.all(CLOUD_CORE_MIGRATIONS.map(async (migration) => ({
@@ -196,9 +212,9 @@ async function plannedFiles(args) {
gitReadUrl: args.gitReadUrl,
gitWriteUrl: args.gitWriteUrl,
registryPrefix: args.registryPrefix,
runtimePaths: profiles.map((profile) => gitopsPathForProfile(args, profile)),
runtimePaths: profiles.map((profile) => gitopsPathForProfile(args, profile, deploy)),
publicEndpoints,
frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile)}/${isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc"}`])),
frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile, deploy)}/${isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc"}`])),
tektonPipeline: `hwlab-ci/${settings.pipelineName}`,
argoApplications: profiles.map((profile) => `argocd/${argoApplicationName(profile)}`)
};
@@ -210,22 +226,22 @@ async function plannedFiles(args) {
};
}
putJson("source.json", sourceDescriptor);
putText("README.md", readme({ source, args }));
putText("README.md", readme({ source, args, deploy }));
putJson("registry/registry.yaml", registryManifest());
if (isRuntimeLane(args.lane)) putJson("devops-infra/git-mirror.yaml", devopsInfraGitMirrorManifest(args, deploy));
putJson(`${settings.tektonDir}/rbac.yaml`, tektonRbac(args));
putJson(`${settings.tektonDir}/rbac.yaml`, tektonRbac(args, deploy));
putJson(`${settings.tektonDir}/tasks.yaml`, { apiVersion: "v1", kind: "List", items: tektonTasks(args, deploy) });
putJson(`${settings.tektonDir}/pipeline.yaml`, tektonPipeline(args, deploy));
if (!isRuntimeLane(args.lane)) {
putJson(`${settings.tektonDir}/poller.yaml`, tektonPollerCronJob(args, deploy));
putJson(`${settings.tektonDir}/control-plane-reconciler.yaml`, tektonControlPlaneReconcilerCronJob(args));
}
putJson(`${settings.tektonDir}/pipelinerun.sample.yaml`, tektonPipelineRunTemplate({ source, args }));
putJson("argocd/project.yaml", argoProject(args));
for (const profile of profiles) putJson(`argocd/application-${profile}.yaml`, argoApplication(args, profile));
putJson(`${settings.tektonDir}/pipelinerun.sample.yaml`, tektonPipelineRunTemplate({ source, args, deploy }));
putJson("argocd/project.yaml", argoProject(args, deploy));
for (const profile of profiles) putJson(`argocd/application-${profile}.yaml`, argoApplication(args, profile, deploy));
for (const profile of profiles) {
const namespace = namespaceNameForProfile(profile);
const namespace = namespaceNameForProfile(profile, deploy);
const includeDeviceAgent = profile === "dev";
const externalPostgres = externalPostgresConfigForLane(deploy, profile, args);
const workbenchRedis = workbenchRuntimeRedisConfigForProfile(deploy, profile);
@@ -236,9 +252,9 @@ async function plannedFiles(args) {
runtimeNamespace.metadata.name = namespace;
label(runtimeNamespace.metadata, profileLabels);
annotate(runtimeNamespace.metadata, annotations);
const runtimePath = runtimePathForProfile(profile);
const runtimePath = runtimePathForProfile(profile, deploy);
const endpoints = profileEndpoint(args, profile);
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent, externalPostgres: Boolean(externalPostgres), workbenchRedis: Boolean(workbenchRedis), runtimeConfigMaps: runtimeConfigMaps.length > 0, externalSecrets: Boolean(runtimeSecretPlane) }));
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent, externalPostgres: Boolean(externalPostgres), workbenchRedis: Boolean(workbenchRedis), runtimeConfigMaps: runtimeConfigMaps.length > 0, externalSecrets: Boolean(runtimeSecretPlane), deploy }));
putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace);
if (!isRuntimeLane(profile)) putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations));
putJson(`${runtimePath}/services.yaml`, transformServices({ services, namespace, labels: profileLabels, annotations, profile, deploy, nodeId: args.nodeId }));
@@ -247,10 +263,19 @@ async function plannedFiles(args) {
if (runtimeSecretPlane) putJson(`${runtimePath}/external-secrets.yaml`, runtimeSecretPlaneManifest({ config: runtimeSecretPlane, namespace, labels: profileLabels, annotations }));
putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, catalog: artifactCatalog, source, sourceBranch: args.sourceBranch, gitReadUrl: args.gitReadUrl, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile, nodeId: args.nodeId, useDeployImages: args.useDeployImages, metricsSidecarSha256 }));
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, sourceBranch: args.sourceBranch, sourceRepo: args.sourceRepo, gitReadUrl: args.gitReadUrl, deploy, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages, metricsSidecarSha256 }));
if (isRuntimeLane(profile) && externalPostgres) putJson(`${runtimePath}/external-postgres.yaml`, externalPostgresManifest({ profile, config: externalPostgres, source }));
if (isRuntimeLane(profile) && !externalPostgres) putJson(`${runtimePath}/postgres.yaml`, v02PostgresManifest({ profile, migrationSources, source, image: runtimePostgresImageForProfile(deploy, profile) }));
if (workbenchRedis) putJson(`${runtimePath}/workbench-redis.yaml`, workbenchRuntimeRedisManifest({ profile, config: workbenchRedis, source }));
if (isRuntimeLane(profile)) putJson(`${runtimePath}/openfga.yaml`, v02OpenFgaManifest({ profile, source }));
if (isRuntimeLane(profile) && externalPostgres) putJson(`${runtimePath}/external-postgres.yaml`, externalPostgresManifest({
profile,
config: externalPostgres,
source,
deploy,
migrationSources,
catalog: artifactCatalog,
registryPrefix: args.registryPrefix,
useDeployImages: args.useDeployImages
}));
if (isRuntimeLane(profile) && !externalPostgres) putJson(`${runtimePath}/postgres.yaml`, v02PostgresManifest({ profile, migrationSources, source, image: runtimePostgresImageForProfile(deploy, profile), deploy }));
if (workbenchRedis) putJson(`${runtimePath}/workbench-redis.yaml`, workbenchRuntimeRedisManifest({ profile, config: workbenchRedis, source, deploy }));
if (isRuntimeLane(profile)) putJson(`${runtimePath}/openfga.yaml`, v02OpenFgaManifest({ profile, source, deploy }));
if (isRuntimeLane(profile)) putJson(`${runtimePath}/observability.yaml`, observabilityManifest({ deploy, profile, namespace, labels: profileLabels, annotations, metricsSidecarScript }));
if (isRuntimeLane(profile)) putJson(`${runtimePath}/opencode.yaml`, opencodeServerManifest({ profile, source, deploy, catalog: artifactCatalog, registryPrefix: args.registryPrefix, useDeployImages: args.useDeployImages, sourceBranch: args.sourceBranch, gitReadUrl: args.gitReadUrl, sourceRepo: args.sourceRepo }));
if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages }));