feat: add code agent provider profiles
This commit is contained in:
+171
-32
@@ -15,12 +15,19 @@ const defaultBranch = process.env.HWLAB_G14_BRANCH || "G14";
|
||||
const defaultGitopsBranch = process.env.HWLAB_G14_GITOPS_BRANCH || "G14-gitops";
|
||||
const defaultRuntimeEndpoint = process.env.HWLAB_G14_RUNTIME_ENDPOINT || "http://74.48.78.17:17667";
|
||||
const defaultWebEndpoint = process.env.HWLAB_G14_WEB_ENDPOINT || "http://74.48.78.17:17666";
|
||||
const defaultProdRuntimeEndpoint = process.env.HWLAB_G14_PROD_RUNTIME_ENDPOINT || "http://74.48.78.17:18667";
|
||||
const defaultProdWebEndpoint = process.env.HWLAB_G14_PROD_WEB_ENDPOINT || "http://74.48.78.17:18666";
|
||||
const defaultProxyUrl = process.env.HWLAB_G14_PROXY_URL || "http://127.0.0.1:10808";
|
||||
const defaultAllProxyUrl = process.env.HWLAB_G14_ALL_PROXY_URL || "socks5h://127.0.0.1:10808";
|
||||
const defaultNoProxy = process.env.HWLAB_G14_NO_PROXY || "127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc";
|
||||
const ciJsonRunnerImage = process.env.HWLAB_G14_CI_JSON_IMAGE || "mcr.microsoft.com/playwright@sha256:b0ab6f3cb99aa7803adbc14d9027ec1785fc6e433b97e134e0f8fe61683b6b53";
|
||||
const ciToolsRunnerImage = process.env.HWLAB_G14_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-v1";
|
||||
const defaultDevBaseImage = process.env.HWLAB_G14_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim";
|
||||
const deepSeekProxyImage = process.env.HWLAB_G14_DEEPSEEK_PROXY_IMAGE || "ghcr.io/berriai/litellm:main-latest";
|
||||
const deepSeekLiteLlmModel = process.env.HWLAB_G14_DEEPSEEK_LITELLM_MODEL || "deepseek/deepseek-chat";
|
||||
const deepSeekProfileModel = process.env.HWLAB_G14_DEEPSEEK_PROFILE_MODEL || "deepseek-chat";
|
||||
const codexApiProfileModel = process.env.HWLAB_G14_CODEX_API_PROFILE_MODEL || "gpt-5.5";
|
||||
const codexApiProfileBaseUrl = process.env.HWLAB_G14_CODEX_API_PROFILE_BASE_URL || "http://172.26.26.227:17680/v1/responses";
|
||||
const sourceCommitPattern = /^[a-f0-9]{7,40}$/u;
|
||||
|
||||
function proxyEnv() {
|
||||
@@ -46,6 +53,8 @@ function parseArgs(argv) {
|
||||
sourceRepo: defaultSourceRepo,
|
||||
runtimeEndpoint: defaultRuntimeEndpoint,
|
||||
webEndpoint: defaultWebEndpoint,
|
||||
prodRuntimeEndpoint: defaultProdRuntimeEndpoint,
|
||||
prodWebEndpoint: defaultProdWebEndpoint,
|
||||
check: false,
|
||||
write: true,
|
||||
help: false
|
||||
@@ -60,6 +69,8 @@ function parseArgs(argv) {
|
||||
else if (arg === "--source-repo") args.sourceRepo = readOption(argv, ++index, arg);
|
||||
else if (arg === "--runtime-endpoint") args.runtimeEndpoint = readOption(argv, ++index, arg);
|
||||
else if (arg === "--web-endpoint") args.webEndpoint = readOption(argv, ++index, arg);
|
||||
else if (arg === "--prod-runtime-endpoint") args.prodRuntimeEndpoint = readOption(argv, ++index, arg);
|
||||
else if (arg === "--prod-web-endpoint") args.prodWebEndpoint = readOption(argv, ++index, arg);
|
||||
else if (arg === "--check") {
|
||||
args.check = true;
|
||||
args.write = false;
|
||||
@@ -91,6 +102,8 @@ function usage() {
|
||||
` --registry-prefix PREFIX default: ${defaultRegistryPrefix}`,
|
||||
` --runtime-endpoint URL default: ${defaultRuntimeEndpoint}`,
|
||||
` --web-endpoint URL default: ${defaultWebEndpoint}`,
|
||||
` --prod-runtime-endpoint URL default: ${defaultProdRuntimeEndpoint}`,
|
||||
` --prod-web-endpoint URL default: ${defaultProdWebEndpoint}`,
|
||||
" --check verify generated files are current without writing"
|
||||
].join("\n");
|
||||
}
|
||||
@@ -181,24 +194,53 @@ function imageFor(registryPrefix, serviceId, shortCommit) {
|
||||
return `${registryPrefix}/${serviceId}:${shortCommit}`;
|
||||
}
|
||||
|
||||
function transformWorkloads({ workloads, deploy, source, registryPrefix, runtimeEndpoint, webEndpoint }) {
|
||||
function namespaceNameForProfile(profile) {
|
||||
return profile === "prod" ? "hwlab-prod" : "hwlab-dev";
|
||||
}
|
||||
|
||||
function runtimePathForProfile(profile) {
|
||||
return profile === "prod" ? "runtime-prod" : "runtime-dev";
|
||||
}
|
||||
|
||||
function runtimeLabelForProfile(profile) {
|
||||
return profile === "prod" ? "prod" : "dev";
|
||||
}
|
||||
|
||||
function profileEndpoint(args, profile) {
|
||||
return profile === "prod"
|
||||
? { runtimeEndpoint: args.prodRuntimeEndpoint, webEndpoint: args.prodWebEndpoint }
|
||||
: { runtimeEndpoint: args.runtimeEndpoint, webEndpoint: args.webEndpoint };
|
||||
}
|
||||
|
||||
function deepSeekBaseUrl(namespace) {
|
||||
return `http://hwlab-deepseek-proxy.${namespace}.svc.cluster.local:4000/v1/responses`;
|
||||
}
|
||||
|
||||
function namespaceScopedHost(value, namespace) {
|
||||
if (typeof value !== "string") return value;
|
||||
return value.replace(/\.hwlab-dev\.svc\.cluster\.local/gu, `.${namespace}.svc.cluster.local`);
|
||||
}
|
||||
|
||||
function transformWorkloads({ workloads, deploy, source, registryPrefix, runtimeEndpoint, webEndpoint, profile = "dev" }) {
|
||||
const result = cloneJson(workloads);
|
||||
const deployServices = new Map((deploy.services ?? []).map((service) => [service.serviceId, service]));
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const profileLabel = runtimeLabelForProfile(profile);
|
||||
const stableRuntimeLabels = {
|
||||
"app.kubernetes.io/part-of": "hwlab",
|
||||
"hwlab.pikastech.local/environment": "dev",
|
||||
"hwlab.pikastech.local/profile": "dev"
|
||||
"hwlab.pikastech.local/environment": profileLabel,
|
||||
"hwlab.pikastech.local/profile": profileLabel
|
||||
};
|
||||
for (const item of asItems(result, "workloads")) {
|
||||
item.metadata.namespace = deploy.namespace;
|
||||
item.metadata.namespace = namespace;
|
||||
label(item.metadata, {
|
||||
...stableRuntimeLabels,
|
||||
"hwlab.pikastech.local/gitops-target": "g14",
|
||||
"hwlab.pikastech.local/source-commit": source.full,
|
||||
"unidesk.ai/g14-staging": "true"
|
||||
"unidesk.ai/g14-staging": profile === "dev" ? "true" : "false"
|
||||
});
|
||||
annotate(item.metadata, {
|
||||
"hwlab.pikastech.local/gitops-note": "G14 GitOps target only; production traffic remains outside this target.",
|
||||
"hwlab.pikastech.local/gitops-note": profile === "prod" ? "G14 PROD GitOps target." : "G14 DEV GitOps target.",
|
||||
"hwlab.pikastech.local/source-commit": source.full
|
||||
});
|
||||
if (item.kind === "Job") {
|
||||
@@ -234,15 +276,30 @@ function transformWorkloads({ workloads, deploy, source, registryPrefix, runtime
|
||||
container.image = image;
|
||||
container.imagePullPolicy = "IfNotPresent";
|
||||
container.env ??= [];
|
||||
for (const entry of container.env) {
|
||||
if (Object.hasOwn(entry, "value")) entry.value = namespaceScopedHost(entry.value, namespace);
|
||||
}
|
||||
upsertEnv(container.env, "HWLAB_ENVIRONMENT", profileLabel);
|
||||
upsertEnv(container.env, "HWLAB_COMMIT_ID", source.full);
|
||||
upsertEnv(container.env, "HWLAB_IMAGE", image);
|
||||
upsertEnv(container.env, "HWLAB_IMAGE_TAG", source.short);
|
||||
upsertEnv(container.env, "HWLAB_GITOPS_TARGET", "g14");
|
||||
upsertEnv(container.env, "HWLAB_GITOPS_PROFILE", profileLabel);
|
||||
upsertEnv(container.env, "HWLAB_GITOPS_SOURCE_COMMIT", source.full);
|
||||
if (serviceId === "hwlab-cloud-api" || serviceId === "hwlab-edge-proxy") {
|
||||
upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", runtimeEndpoint);
|
||||
}
|
||||
if (serviceId === "hwlab-cloud-api") {
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE", "deepseek");
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_MODEL", deepSeekProfileModel);
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL", deepSeekBaseUrl(namespace));
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_MODEL", codexApiProfileModel);
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_BASE_URL", codexApiProfileBaseUrl);
|
||||
upsertEnv(container.env, "NO_PROXY", `${namespace}.svc.cluster.local,.svc,.cluster.local,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.26.26.227,172.26.0.0/16`);
|
||||
upsertEnv(container.env, "no_proxy", `${namespace}.svc.cluster.local,.svc,.cluster.local,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,172.26.26.227,172.26.0.0/16`);
|
||||
}
|
||||
if (serviceId === "hwlab-cloud-web") {
|
||||
upsertEnv(container.env, "HWLAB_API_BASE_URL", `http://hwlab-cloud-api.${namespace}.svc.cluster.local:6667`);
|
||||
upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", webEndpoint);
|
||||
}
|
||||
if (serviceId === "hwlab-cli") {
|
||||
@@ -259,8 +316,14 @@ function transformWorkloads({ workloads, deploy, source, registryPrefix, runtime
|
||||
return result;
|
||||
}
|
||||
|
||||
function transformHealthContract(value, namespace, labels, annotations, runtimeEndpoint, webEndpoint) {
|
||||
function transformHealthContract(value, namespace, labels, annotations, runtimeEndpoint, webEndpoint, profile = "dev") {
|
||||
const result = transformListNamespace(value, namespace, labels, annotations);
|
||||
if (result.metadata?.name === "hwlab-dev-health-contract") {
|
||||
result.metadata.name = `hwlab-${profile}-health-contract`;
|
||||
}
|
||||
if (result.metadata?.labels && Object.hasOwn(result.metadata.labels, "app.kubernetes.io/name")) {
|
||||
result.metadata.labels["app.kubernetes.io/name"] = `hwlab-${profile}-health-contract`;
|
||||
}
|
||||
if (result.data && typeof result.data === "object") {
|
||||
if (Object.hasOwn(result.data, "endpoint")) result.data.endpoint = runtimeEndpoint;
|
||||
if (Object.hasOwn(result.data, "cloud-web")) {
|
||||
@@ -1194,26 +1257,31 @@ function argoProject() {
|
||||
spec: {
|
||||
description: "HWLAB G14 GitOps project; D601 remains outside this project.",
|
||||
sourceRepos: [defaultSourceRepo],
|
||||
destinations: [{ server: "https://kubernetes.default.svc", namespace: "hwlab-dev" }],
|
||||
destinations: [
|
||||
{ server: "https://kubernetes.default.svc", namespace: "hwlab-dev" },
|
||||
{ server: "https://kubernetes.default.svc", namespace: "hwlab-prod" }
|
||||
],
|
||||
clusterResourceWhitelist: [{ group: "", kind: "Namespace" }],
|
||||
namespaceResourceWhitelist: [{ group: "*", kind: "*" }]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function argoApplication(args) {
|
||||
function argoApplication(args, profile = "dev") {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const runtimePath = runtimePathForProfile(profile);
|
||||
return {
|
||||
apiVersion: "argoproj.io/v1alpha1",
|
||||
kind: "Application",
|
||||
metadata: {
|
||||
name: "hwlab-g14-dev",
|
||||
name: `hwlab-g14-${profile}`,
|
||||
namespace: "argocd",
|
||||
labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": "g14" }
|
||||
},
|
||||
spec: {
|
||||
project: "hwlab-g14",
|
||||
source: { repoURL: args.sourceRepo, targetRevision: args.gitopsBranch, path: "deploy/gitops/g14/runtime" },
|
||||
destination: { server: "https://kubernetes.default.svc", namespace: "hwlab-dev" },
|
||||
source: { repoURL: args.sourceRepo, targetRevision: args.gitopsBranch, path: `deploy/gitops/g14/${runtimePath}` },
|
||||
destination: { server: "https://kubernetes.default.svc", namespace },
|
||||
syncPolicy: { automated: { prune: false, selfHeal: true }, syncOptions: ["CreateNamespace=true", "ApplyOutOfSyncOnly=true"] }
|
||||
}
|
||||
};
|
||||
@@ -1289,12 +1357,71 @@ remotePort = 17667
|
||||
};
|
||||
}
|
||||
|
||||
function runtimeKustomization({ source }) {
|
||||
function deepSeekProxyManifest({ profile = "dev" } = {}) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const labels = {
|
||||
"app.kubernetes.io/name": "hwlab-deepseek-proxy",
|
||||
"app.kubernetes.io/part-of": "hwlab",
|
||||
"hwlab.pikastech.local/environment": runtimeLabelForProfile(profile),
|
||||
"hwlab.pikastech.local/gitops-target": "g14",
|
||||
"hwlab.pikastech.local/service-id": "hwlab-deepseek-proxy"
|
||||
};
|
||||
return {
|
||||
apiVersion: "v1",
|
||||
kind: "List",
|
||||
items: [
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "ConfigMap",
|
||||
metadata: { name: "hwlab-deepseek-proxy-config", namespace, labels },
|
||||
data: {
|
||||
"config.yaml": `model_list:\n - model_name: ${deepSeekProfileModel}\n litellm_params:\n model: ${deepSeekLiteLlmModel}\n api_key: os.environ/DEEPSEEK_API_KEY\n api_base: https://api.deepseek.com\nlitellm_settings:\n drop_params: true\n set_verbose: false\n`
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deployment",
|
||||
metadata: { name: "hwlab-deepseek-proxy", namespace, labels },
|
||||
spec: {
|
||||
replicas: 1,
|
||||
selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" } },
|
||||
template: {
|
||||
metadata: { labels },
|
||||
spec: {
|
||||
containers: [{
|
||||
name: "litellm",
|
||||
image: deepSeekProxyImage,
|
||||
imagePullPolicy: "IfNotPresent",
|
||||
args: ["--config", "/etc/litellm/config.yaml", "--host", "0.0.0.0", "--port", "4000"],
|
||||
env: [{ name: "DEEPSEEK_API_KEY", valueFrom: { secretKeyRef: { name: "hwlab-code-agent-provider", key: "openai-api-key", optional: true } } }],
|
||||
ports: [{ name: "http", containerPort: 4000 }],
|
||||
readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 },
|
||||
livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 },
|
||||
volumeMounts: [{ name: "config", mountPath: "/etc/litellm", readOnly: true }]
|
||||
}],
|
||||
volumes: [{ name: "config", configMap: { name: "hwlab-deepseek-proxy-config" } }]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "Service",
|
||||
metadata: { name: "hwlab-deepseek-proxy", namespace, labels },
|
||||
spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }] }
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function runtimeKustomization({ profile = "dev" } = {}) {
|
||||
const resources = ["namespace.yaml", "code-agent-codex-config.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"];
|
||||
if (profile === "dev") resources.push("g14-frpc.yaml");
|
||||
return {
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1",
|
||||
kind: "Kustomization",
|
||||
namespace: "hwlab-dev",
|
||||
resources: ["namespace.yaml", "code-agent-codex-config.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "g14-frpc.yaml"]
|
||||
namespace: namespaceNameForProfile(profile),
|
||||
resources
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1317,7 +1444,7 @@ This directory is generated from \`CI.json\`, \`deploy/deploy.json\`, and \`depl
|
||||
}
|
||||
|
||||
async function plannedFiles(args) {
|
||||
const [ci, deploy, namespace, config, services, health, workloads] = await Promise.all([
|
||||
const [ci, deploy, namespaceTemplate, config, services, health, workloads] = await Promise.all([
|
||||
readJson("CI.json"),
|
||||
readJson("deploy/deploy.json"),
|
||||
readJson("deploy/k8s/base/namespace.yaml"),
|
||||
@@ -1327,12 +1454,8 @@ async function plannedFiles(args) {
|
||||
readJson("deploy/k8s/base/workloads.yaml")
|
||||
]);
|
||||
const source = await resolveSourceRevision(args.sourceRevision);
|
||||
const labels = { "hwlab.pikastech.local/gitops-target": "g14", "hwlab.pikastech.local/source-commit": source.full };
|
||||
const baseLabels = { "hwlab.pikastech.local/gitops-target": "g14", "hwlab.pikastech.local/source-commit": source.full };
|
||||
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" };
|
||||
const runtimeNamespace = cloneJson(namespace);
|
||||
runtimeNamespace.metadata.name = deploy.namespace;
|
||||
label(runtimeNamespace.metadata, labels);
|
||||
annotate(runtimeNamespace.metadata, annotations);
|
||||
const files = new Map();
|
||||
const putJson = (relative, value) => files.set(path.join(args.outDir, relative), jsonManifest(value));
|
||||
const putText = (relative, value) => files.set(path.join(args.outDir, relative), textFile(value));
|
||||
@@ -1345,11 +1468,14 @@ async function plannedFiles(args) {
|
||||
gitopsBranch: args.gitopsBranch,
|
||||
sourceRepo: args.sourceRepo,
|
||||
registryPrefix: args.registryPrefix,
|
||||
runtimePath: "deploy/gitops/g14/runtime",
|
||||
publicEndpoints: { web: args.webEndpoint, runtime: args.runtimeEndpoint },
|
||||
runtimePaths: ["deploy/gitops/g14/runtime-dev", "deploy/gitops/g14/runtime-prod"],
|
||||
publicEndpoints: {
|
||||
dev: { web: args.webEndpoint, runtime: args.runtimeEndpoint },
|
||||
prod: { web: args.prodWebEndpoint, runtime: args.prodRuntimeEndpoint }
|
||||
},
|
||||
frpDeployment: "hwlab-dev/hwlab-g14-frpc",
|
||||
tektonPipeline: "hwlab-ci/hwlab-g14-ci-image-publish",
|
||||
argoApplication: "argocd/hwlab-g14-dev"
|
||||
argoApplications: ["argocd/hwlab-g14-dev", "argocd/hwlab-g14-prod"]
|
||||
});
|
||||
putText("README.md", readme({ source, args }));
|
||||
putJson("registry/registry.yaml", registryManifest());
|
||||
@@ -1359,14 +1485,27 @@ async function plannedFiles(args) {
|
||||
putJson("tekton/control-plane-reconciler.yaml", tektonControlPlaneReconcilerCronJob(args));
|
||||
putJson("tekton/pipelinerun.sample.yaml", tektonPipelineRunTemplate({ source, args }));
|
||||
putJson("argocd/project.yaml", argoProject());
|
||||
putJson("argocd/application.yaml", argoApplication(args));
|
||||
putJson("runtime/kustomization.yaml", runtimeKustomization({ source }));
|
||||
putJson("runtime/namespace.yaml", runtimeNamespace);
|
||||
putJson("runtime/code-agent-codex-config.yaml", transformListNamespace(config, deploy.namespace, labels, annotations));
|
||||
putJson("runtime/services.yaml", transformListNamespace(services, deploy.namespace, labels, annotations));
|
||||
putJson("runtime/health-contract.yaml", transformHealthContract(health, deploy.namespace, labels, annotations, args.runtimeEndpoint, args.webEndpoint));
|
||||
putJson("runtime/workloads.yaml", transformWorkloads({ workloads, deploy, source, registryPrefix: args.registryPrefix, runtimeEndpoint: args.runtimeEndpoint, webEndpoint: args.webEndpoint }));
|
||||
putJson("runtime/g14-frpc.yaml", g14FrpcManifest());
|
||||
putJson("argocd/application-dev.yaml", argoApplication(args, "dev"));
|
||||
putJson("argocd/application-prod.yaml", argoApplication(args, "prod"));
|
||||
|
||||
for (const profile of ["dev", "prod"]) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const profileLabels = { ...baseLabels, "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile) };
|
||||
const runtimeNamespace = cloneJson(namespaceTemplate);
|
||||
runtimeNamespace.metadata.name = namespace;
|
||||
label(runtimeNamespace.metadata, profileLabels);
|
||||
annotate(runtimeNamespace.metadata, annotations);
|
||||
const runtimePath = runtimePathForProfile(profile);
|
||||
const endpoints = profileEndpoint(args, profile);
|
||||
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile }));
|
||||
putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace);
|
||||
putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations));
|
||||
putJson(`${runtimePath}/services.yaml`, transformListNamespace(services, namespace, profileLabels, annotations));
|
||||
putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile));
|
||||
putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, source, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile }));
|
||||
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile }));
|
||||
if (profile === "dev") putJson(`${runtimePath}/g14-frpc.yaml`, g14FrpcManifest());
|
||||
}
|
||||
return { files, source };
|
||||
}
|
||||
|
||||
|
||||
@@ -1264,6 +1264,57 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) {
|
||||
safety: layoutSafety()
|
||||
});
|
||||
} catch (error) {
|
||||
if (isBrowserExecutionEnvironmentError(error)) {
|
||||
const summary = `Workbench layout smoke skipped because the browser could not run: ${error.message}`;
|
||||
return sanitizeLayoutReport({
|
||||
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
|
||||
$id: reportModeId("layout"),
|
||||
reportVersion: "v1",
|
||||
status: "skip",
|
||||
issue: "pikasTech/HWLAB#273",
|
||||
taskId: "dev-cloud-workbench-layout",
|
||||
commitId: observeSourceIdentity().reportCommitId,
|
||||
acceptanceLevel: "dev_cloud_workbench_layout",
|
||||
devOnly: true,
|
||||
prodDisabled: true,
|
||||
reportLifecycle: layoutReportLifecycle(useLiveUrl, "blocked"),
|
||||
task: "DC-DCSN-P0-2026-003",
|
||||
mode: "layout-browser",
|
||||
sourceMode: layoutSourceMode,
|
||||
url,
|
||||
generatedAt: new Date().toISOString(),
|
||||
sourceContract: layoutSourceContract(),
|
||||
validationCommands: layoutValidationCommands(),
|
||||
localSmoke: layoutLocalSmoke("skip"),
|
||||
dryRun: layoutDryRun(),
|
||||
devPreconditions: layoutDevPreconditions(useLiveUrl, "blocked"),
|
||||
evidenceLevel: useLiveUrl ? "BLOCKED" : "SOURCE",
|
||||
devLive: false,
|
||||
summary,
|
||||
viewports: layoutViewports.map(({ id, width, height }) => ({ id, width, height })),
|
||||
checks: [
|
||||
{
|
||||
id: "layout-browser-runtime",
|
||||
status: "skip",
|
||||
summary,
|
||||
evidence: ["browser launch failed", "use G14 k3s/CI Playwright base image for browser smoke"]
|
||||
}
|
||||
],
|
||||
blockers: [
|
||||
{
|
||||
type: "environment_blocker",
|
||||
scope: "layout-browser-runtime",
|
||||
status: "open",
|
||||
summary
|
||||
}
|
||||
],
|
||||
artifacts: {
|
||||
screenshotDir: artifactRoot,
|
||||
reportPath: args.reportPath ?? null
|
||||
},
|
||||
safety: layoutSafety()
|
||||
});
|
||||
}
|
||||
return sanitizeLayoutReport({
|
||||
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
|
||||
$id: reportModeId("layout"),
|
||||
@@ -9206,6 +9257,11 @@ function escapeRegExp(value) {
|
||||
return String(value).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
||||
}
|
||||
|
||||
function isBrowserExecutionEnvironmentError(error) {
|
||||
const message = String(error?.message ?? error ?? "");
|
||||
return /Executable doesn't exist|Please run the following command to download new browsers|does not support chromium|Host system is missing dependencies|browserType\.launch|Failed to launch|No usable sandbox|Missing X server|Target page, context or browser has been closed/iu.test(message);
|
||||
}
|
||||
|
||||
export function printSmokeHelp() {
|
||||
return {
|
||||
status: "usage",
|
||||
|
||||
Reference in New Issue
Block a user