Files
pikasTech-unidesk/scripts/src/hwlab-native-development.ts
T
2026-07-21 10:09:14 +02:00

350 lines
21 KiB
TypeScript

import { resolve } from "node:path";
import { runCommand } from "./command";
import { resolveConfigRef } from "./ops/config-refs";
import { readEnvSourceFile, requiredEnvValue } from "./secrets";
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeNativeDevelopmentSpec } from "./hwlab-node-lanes";
type Application = "workbench" | "hwpod" | "caserun";
type Service = "api" | "worker" | "web";
type Action = "start" | "stop" | "restart" | "status" | "logs";
export function hwlabNativeDevelopmentHelp(): Record<string, unknown> {
return {
ok: true,
command: "hwlab nodes native-development workbench",
usage: [
"bun scripts/cli.ts hwlab nodes native-development workbench status --node <node> --lane <lane>",
"bun scripts/cli.ts hwlab nodes native-development workbench api|worker|web start|stop|restart|status|logs --node <node> --lane <lane>",
"bun scripts/cli.ts hwlab nodes native-development workbench cli <health|session|turn|events ...> --node <node> --lane <lane>",
"bun scripts/cli.ts hwlab nodes native-development hwpod status --node <node> --lane <lane>",
"bun scripts/cli.ts hwlab nodes native-development hwpod api|worker|web start|stop|restart|status|logs --node <node> --lane <lane>",
"bun scripts/cli.ts hwlab nodes native-development caserun api|web start|stop|restart|status|logs --node <node> --lane <lane>",
],
contract: "L1 API/Web 用户入口只输出 owning YAML 解析出的固定 HTTPS 域名;IP、port 和 bind/probe 地址不作为用户入口。",
configPath: hwlabRuntimeLaneConfigPath(),
};
}
export function runHwlabNativeDevelopmentCommand(args: string[]): Record<string, unknown> {
if (args.length === 0 || args.includes("--help") || args.includes("-h")) return hwlabNativeDevelopmentHelp();
const [applicationRaw, serviceRaw, actionRaw] = args;
const application = parseApplication(applicationRaw);
const node = requiredOption(args, "--node");
const lane = requiredOption(args, "--lane");
if (!isHwlabRuntimeLane(lane)) throw new Error(`--lane must be declared in ${hwlabRuntimeLaneConfigPath()}`);
const spec = hwlabRuntimeLaneSpecForNode(lane, node);
const native = spec.nativeDevelopment?.[application];
if (native === undefined) throw new Error(`${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.${application} is required`);
const env = application === "workbench"
? workbenchEnvironment(spec, native as HwlabRuntimeNativeDevelopmentSpec["workbench"])
: application === "hwpod"
? hwlabHwpodNativeDevelopmentEnvironment(native as NonNullable<HwlabRuntimeNativeDevelopmentSpec["hwpod"]>)
: hwlabCaseRunNativeDevelopmentEnvironment(native as NonNullable<HwlabRuntimeNativeDevelopmentSpec["caserun"]>);
if (application === "workbench" && serviceRaw === "cli") {
const commandArgs = withoutOptions(args.slice(2), ["--node", "--lane"]);
if (commandArgs.length === 0) throw new Error("native-development workbench cli requires a Workbench command");
const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", "workbench", ...commandArgs];
const result = runCommand(command, spec.workspace, {
timeoutMs: cliTimeoutMs(commandArgs),
env: { ...process.env, ...env, WORKBENCH_API_URL: env.WORKBENCH_NATIVE_API_URL },
});
return {
ok: result.exitCode === 0,
command: "hwlab nodes native-development workbench cli",
node,
lane,
transport: commandArgs.includes("--over-api") ? "native-api" : "local-dispatcher",
configSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.workbench`,
workbenchResult: parsePayload(result.stdout),
...(result.exitCode === 0 ? {} : { error: { code: "hwlab_native_cli_failed", exitCode: result.exitCode, stderr: result.stderr.trim().slice(-2000) } }),
};
}
if (application === "hwpod" && serviceRaw === "cli") {
const commandArgs = withoutOptions(args.slice(2), ["--node", "--lane"]);
if (commandArgs.length === 0) throw new Error("native-development hwpod cli requires a HWPOD command");
const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", "hwpod", ...commandArgs];
const result = runCommand(command, spec.workspace, {
timeoutMs: cliTimeoutMs(commandArgs),
env: { ...process.env, ...env, HWLAB_RUNTIME_API_URL: env.HWPOD_NATIVE_API_URL },
});
return {
ok: result.exitCode === 0,
command: "hwlab nodes native-development hwpod cli",
node,
lane,
transport: commandArgs.includes("--over-api") ? "native-api" : "local-function",
configSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.hwpod`,
hwpodResult: parsePayload(result.stdout),
...(result.exitCode === 0 ? {} : { error: { code: "hwlab_native_cli_failed", exitCode: result.exitCode, stderr: result.stderr.trim().slice(-2000) } }),
};
}
if (application === "workbench" && serviceRaw === "status") {
const remaining = withoutOptions(args.slice(2), ["--node", "--lane"]);
if (remaining.length > 0) throw new Error(`native-development workbench status does not accept positional arguments: ${remaining.join(" ")}`);
const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", "workbench", "service", "status"];
const result = runCommand(command, spec.workspace, { timeoutMs: 15_000, env: { ...process.env, ...env } });
const payload = parsePayload(result.stdout);
return {
ok: result.exitCode === 0 && payload.ok !== false,
command: "hwlab nodes native-development workbench status",
node,
lane,
action: "status",
configSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.workbench`,
userEndpoint: { url: `${native.publicExposure.publicBaseUrl}/workbench` },
serviceResult: payload,
...(result.exitCode === 0 ? {} : { error: { code: "hwlab_native_cli_failed", exitCode: result.exitCode, stderr: result.stderr.trim().slice(-2000) } }),
};
}
if (application === "hwpod" && serviceRaw === "status") {
const remaining = withoutOptions(args.slice(2), ["--node", "--lane"]);
if (remaining.length > 0) throw new Error(`native-development hwpod status does not accept positional arguments: ${remaining.join(" ")}`);
const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", "hwpod", "service", "status"];
const result = runCommand(command, spec.workspace, { timeoutMs: 30_000, env: { ...process.env, ...env } });
const payload = parsePayload(result.stdout);
return {
ok: result.exitCode === 0 && payload.ok !== false,
command: "hwlab nodes native-development hwpod status",
node,
lane,
action: "status",
configSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.hwpod`,
userEndpoint: { url: `${native.publicExposure.publicBaseUrl}${(native as NonNullable<HwlabRuntimeNativeDevelopmentSpec["hwpod"]>).web.accessProfile.startPath}` },
serviceResult: payload,
...(result.exitCode === 0 ? {} : { error: { code: "hwlab_native_cli_failed", exitCode: result.exitCode, stderr: result.stderr.trim().slice(-2000) } }),
};
}
const service = parseService(serviceRaw, application);
const action = parseAction(actionRaw);
const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", application, "service", service, action];
const result = runCommand(command, spec.workspace, { timeoutMs: 15_000, env: { ...process.env, ...env } });
const payload = parsePayload(result.stdout);
const publicEndpoint = service === "api"
? { url: native.publicExposure.publicBaseUrl }
: service === "web"
? { url: application === "hwpod" ? `${native.publicExposure.publicBaseUrl}${(native as NonNullable<HwlabRuntimeNativeDevelopmentSpec["hwpod"]>).web.accessProfile.startPath}` : `${native.publicExposure.publicBaseUrl}/${application === "workbench" ? "workbench" : "caserun"}` }
: null;
return {
ok: result.exitCode === 0 && payload.ok !== false,
command: `hwlab nodes native-development ${application} ${service} ${action}`,
node,
lane,
service,
action,
configSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.${application}`,
publicExposureSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.${application}.publicExposure`,
userEndpoint: publicEndpoint,
serviceResult: payload,
...(result.exitCode === 0 ? {} : { error: { code: "hwlab_native_service_failed", exitCode: result.exitCode, stderr: result.stderr.trim().slice(-2000) } }),
};
}
function workbenchEnvironment(spec: HwlabRuntimeLaneSpec, native: HwlabRuntimeNativeDevelopmentSpec["workbench"]): NodeJS.ProcessEnv {
const sourceCommitResult = runCommand(["git", "rev-parse", "HEAD"], spec.workspace, { timeoutMs: 5_000 });
if (sourceCommitResult.exitCode !== 0 || !/^[0-9a-f]{40}$/iu.test(sourceCommitResult.stdout.trim())) throw new Error(`cannot resolve current HWLAB source commit from ${spec.workspace}`);
return hwlabNativeDevelopmentEnvironment(native, spec, sourceCommitResult.stdout.trim());
}
export function hwlabNativeDevelopmentEnvironment(native: HwlabRuntimeNativeDevelopmentSpec["workbench"], spec: HwlabRuntimeLaneSpec, sourceCommit: string): NodeJS.ProcessEnv {
const codeAgent = spec.codeAgentRuntime;
if (codeAgent === undefined) throw new Error(`${hwlabRuntimeLaneConfigPath()}#lanes.${spec.lane}.targets.${spec.nodeId}.codeAgentRuntime is required`);
const kafkaBridge = codeAgent.kafkaEventBridge;
if (kafkaBridge === undefined) throw new Error(`${hwlabRuntimeLaneConfigPath()}#lanes.${spec.lane}.targets.${spec.nodeId}.${native.kafka.configRef} is required`);
if (native.kafka.hwlabEventConsumerGroupId === kafkaBridge.hwlabEventConsumerGroupId) throw new Error("native Workbench Kafka consumer group must be distinct from the Cloud API live SSE consumer group");
if (!/^[0-9a-f]{40}$/iu.test(sourceCommit)) throw new Error("sourceCommit must be the current HWLAB workspace 40-hex git SHA");
const providerSecretNames = codeAgent.providerSecretNames;
if (providerSecretNames === undefined) throw new Error(`${hwlabRuntimeLaneConfigPath()}#lanes.${spec.lane}.targets.${spec.nodeId}.codeAgentRuntime.providerSecretNames is required`);
const defaultProviderKey = codeAgent.defaultProviderProfile.toLowerCase().replace(/[^a-z0-9]+/gu, "-");
if (!providerSecretNames[defaultProviderKey]) throw new Error(`codeAgentRuntime.providerSecretNames.${defaultProviderKey} is required for defaultProviderProfile=${codeAgent.defaultProviderProfile}`);
const authorizationSource = readEnvSourceFile({
root: "/root/.unidesk/.state/secrets",
sourceRef: codeAgent.apiKeySourceRef,
});
const agentRunApiKey = requiredEnvValue(authorizationSource.values, codeAgent.apiKeySourceKey, codeAgent.apiKeySourceRef);
const sessionIndex = native.kafka.refreshReplay.sessionIndex;
return {
WORKBENCH_MODE: native.mode,
WORKBENCH_TEMPORAL_ADDRESS: native.temporal.address,
WORKBENCH_TEMPORAL_NAMESPACE: native.temporal.namespace,
WORKBENCH_TEMPORAL_TASK_QUEUE: native.temporal.taskQueue,
WORKBENCH_ACTIVITY_START_TO_CLOSE_TIMEOUT_MS: String(native.temporal.activity.startToCloseTimeoutMs),
WORKBENCH_ACTIVITY_RETRY_INITIAL_INTERVAL_MS: String(native.temporal.activity.retryInitialIntervalMs),
WORKBENCH_ACTIVITY_RETRY_MAXIMUM_INTERVAL_MS: String(native.temporal.activity.retryMaximumIntervalMs),
WORKBENCH_ACTIVITY_RETRY_MAXIMUM_ATTEMPTS: String(native.temporal.activity.retryMaximumAttempts),
AGENTRUN_MGR_URL: native.agentRun.managerUrl,
AGENTRUN_API_KEY: agentRunApiKey,
HWLAB_CODE_AGENT_ADAPTER: codeAgent.adapter,
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_PRIVATE_URL: "1",
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: spec.nodeId,
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: codeAgent.defaultProviderProfile,
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: codeAgent.runnerNamespace,
HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: codeAgent.secretNamespace,
...providerSecretNameEnvironment(providerSecretNames),
HWLAB_CODE_AGENT_AGENTRUN_GITHUB_TOOL_SECRET_NAME: codeAgent.toolSecretNames.githubPr,
HWLAB_CODE_AGENT_AGENTRUN_UNIDESK_SSH_TOOL_SECRET_NAME: codeAgent.toolSecretNames.unideskSsh,
HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: spec.gitReadUrl,
HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: sourceCommit,
HWLAB_RUNTIME_API_URL: native.agentRun.runtimeApiUrl,
HWLAB_RUNTIME_WEB_URL: native.agentRun.runtimeWebUrl,
HWLAB_RUNTIME_NAMESPACE: spec.runtimeNamespace,
HWLAB_RUNTIME_LANE: spec.lane,
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1",
HWLAB_KAFKA_DIRECT_PUBLISH_ENABLED: "false",
HWLAB_WORKBENCH_LIVE_KAFKA_SSE_ENABLED: "true",
HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_ENABLED: "true",
HWLAB_KAFKA_TRANSACTIONAL_PROJECTOR_ENABLED: "false",
HWLAB_KAFKA_PROJECTION_OUTBOX_RELAY_ENABLED: "false",
HWLAB_WORKBENCH_PROJECTION_REALTIME_ENABLED: "false",
HWLAB_KAFKA_BOOTSTRAP_SERVERS: native.kafka.bootstrapServers,
HWLAB_KAFKA_DNS_SERVERS: native.kafka.dnsServers.join(","),
HWLAB_KAFKA_DNS_SEARCH_DOMAINS: native.kafka.dnsSearchDomains.join(","),
HWLAB_KAFKA_AGENTRUN_EVENT_TOPIC: kafkaBridge.agentRunEventTopic,
HWLAB_KAFKA_EVENT_TOPIC: kafkaBridge.hwlabEventTopic,
HWLAB_KAFKA_CLIENT_ID: native.kafka.clientId,
HWLAB_KAFKA_HWLAB_EVENT_GROUP_ID: native.kafka.hwlabEventConsumerGroupId,
HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_GROUP_PREFIX: native.kafka.refreshReplay.groupIdPrefix,
HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_TIMEOUT_MS: String(native.kafka.refreshReplay.timeoutMs),
HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_SCAN_LIMIT: String(native.kafka.refreshReplay.scanLimit),
HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_MATCHED_EVENT_LIMIT: String(native.kafka.refreshReplay.matchedEventLimit),
HWLAB_WORKBENCH_KAFKA_REFRESH_REPLAY_LIVE_BUFFER_LIMIT: String(native.kafka.refreshReplay.liveBufferLimit),
...(sessionIndex === undefined ? {} : {
HWLAB_WORKBENCH_KAFKA_REFRESH_INDEX_ENABLED: String(sessionIndex.enabled),
HWLAB_WORKBENCH_KAFKA_REFRESH_INDEX_MAX_EVENTS: String(sessionIndex.maxEvents),
HWLAB_WORKBENCH_KAFKA_REFRESH_INDEX_MAX_EVENTS_PER_SESSION: String(sessionIndex.maxEventsPerSession),
HWLAB_WORKBENCH_KAFKA_REFRESH_INDEX_BOOTSTRAP_TIMEOUT_MS: String(sessionIndex.bootstrapTimeoutMs),
HWLAB_WORKBENCH_KAFKA_REFRESH_INDEX_REBUILD_INTERVAL_MS: String(sessionIndex.rebuildIntervalMs),
HWLAB_WORKBENCH_KAFKA_REFRESH_INDEX_REBUILD_COOLDOWN_MS: String(sessionIndex.rebuildCooldownMs),
}),
WORKBENCH_NATIVE_SERVICE_STATE_DIR: native.stateDir,
WORKBENCH_NATIVE_STATE_FILE: resolve(spec.workspace, native.stateFile),
WORKBENCH_API_BIND_HOST: native.api.bindHost,
WORKBENCH_API_PROBE_HOST: native.api.probeHost,
WORKBENCH_PUBLIC_BASE_URL: native.publicExposure.publicBaseUrl,
WORKBENCH_API_PORT: String(native.api.port),
WORKBENCH_API_IDLE_TIMEOUT_SECONDS: String(native.api.idleTimeoutSeconds),
WORKBENCH_WORKER_BIND_HOST: native.worker.bindHost,
WORKBENCH_WORKER_PROBE_HOST: native.worker.probeHost,
WORKBENCH_WORKER_HEALTH_PORT: String(native.worker.healthPort),
WORKBENCH_WEB_BIND_HOST: native.web.bindHost,
WORKBENCH_WEB_PROBE_HOST: native.web.probeHost,
WORKBENCH_WEB_PORT: String(native.web.port),
WORKBENCH_NATIVE_API_URL: `http://${native.api.probeHost}:${native.api.port}`,
WORKBENCH_WEB_RUNTIME_CONFIG: JSON.stringify(native.web.runtimeConfig),
};
}
function providerSecretNameEnvironment(providerSecretNames: Readonly<Record<string, string>>): NodeJS.ProcessEnv {
return Object.fromEntries(Object.entries(providerSecretNames).map(([profile, secretName]) => {
const profileKey = profile.toUpperCase().replace(/[^A-Z0-9]+/gu, "_");
return [`HWLAB_CODE_AGENT_AGENTRUN_${profileKey}_SECRET_NAME`, secretName];
}));
}
function parsePayload(stdout: string): Record<string, unknown> {
const parsed = JSON.parse(stdout) as unknown;
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("HWLAB Workbench CLI returned invalid JSON");
return parsed as Record<string, unknown>;
}
function parseApplication(value: string | undefined): Application {
if (value === "workbench" || value === "hwpod" || value === "caserun") return value;
throw new Error("native-development application must be workbench, hwpod, or caserun");
}
function parseService(value: string | undefined, application: Application): Service {
if (value === "api" || value === "web" || (application !== "caserun" && value === "worker")) return value;
throw new Error(`native-development ${application} service must be ${application === "caserun" ? "api or web" : "api, worker, or web"}`);
}
function parseAction(value: string | undefined): Action {
if (value === "start" || value === "stop" || value === "restart" || value === "status" || value === "logs") return value;
throw new Error("native-development workbench action must be start, stop, restart, status, or logs");
}
function requiredOption(args: string[], option: string): string {
const index = args.indexOf(option);
const value = index < 0 ? undefined : args[index + 1];
if (!value || value.startsWith("--")) throw new Error(`${option} requires a value`);
return value;
}
function withoutOptions(args: string[], options: string[]): string[] {
const result: string[] = [];
for (let index = 0; index < args.length; index += 1) {
if (options.includes(args[index])) {
index += 1;
continue;
}
result.push(args[index]);
}
return result;
}
function cliTimeoutMs(args: string[]): number {
const index = args.indexOf("--timeout-ms");
const requested = index >= 0 ? Number.parseInt(args[index + 1] ?? "", 10) : 0;
return Number.isSafeInteger(requested) && requested > 0 ? requested + 5_000 : 30_000;
}
function hwlabCaseRunNativeDevelopmentEnvironment(native: NonNullable<HwlabRuntimeNativeDevelopmentSpec["caserun"]>): NodeJS.ProcessEnv {
return {
CASERUN_NATIVE_SERVICE_STATE_DIR: native.stateDir,
CASERUN_PUBLIC_BASE_URL: native.publicExposure.publicBaseUrl,
CASERUN_PROBE_HOST: native.api.probeHost,
CASERUN_API_BIND_HOST: native.api.bindHost,
CASERUN_API_PORT: String(native.api.port),
CASERUN_WEB_BIND_HOST: native.web.bindHost,
CASERUN_WEB_PORT: String(native.web.port),
};
}
export function hwlabHwpodNativeDevelopmentEnvironment(native: NonNullable<HwlabRuntimeNativeDevelopmentSpec["hwpod"]>): NodeJS.ProcessEnv {
const nodeAuthSource = readEnvSourceFile({
root: "/root/.unidesk/.state/secrets",
sourceRef: native.nodeAuth.sourceRef,
});
const nodeToken = requiredEnvValue(nodeAuthSource.values, native.nodeAuth.sourceKey, native.nodeAuth.sourceRef);
const databaseSource = readEnvSourceFile({
root: "/root/.unidesk/.state/secrets",
sourceRef: native.specRegistry.database.sourceRef,
});
const databaseUrl = requiredEnvValue(databaseSource.values, native.specRegistry.database.sourceKey, native.specRegistry.database.sourceRef);
const builtInSpecs = native.specRegistry.builtInConfigRefs.map((configRef) => {
const resolved = resolveConfigRef(configRef, "nativeDevelopment.hwpod.specRegistry.builtInConfigRefs");
return { configRef, sha256: resolved.sha256, document: resolved.value };
});
return {
HWPOD_NATIVE_SERVICE_STATE_DIR: native.stateDir,
HWPOD_HEALTH_TIMEOUT_MS: String(native.healthTimeoutMs),
HWPOD_PUBLIC_BASE_URL: native.publicExposure.publicBaseUrl,
HWPOD_TEMPORAL_ADDRESS: native.temporal.address,
HWPOD_TEMPORAL_NAMESPACE: native.temporal.namespace,
HWPOD_TEMPORAL_TASK_QUEUE: native.temporal.taskQueue,
HWPOD_ACTIVITY_START_TO_CLOSE_TIMEOUT_MS: String(native.temporal.activity.startToCloseTimeoutMs),
HWPOD_ACTIVITY_RETRY_INITIAL_INTERVAL_MS: String(native.temporal.activity.retryInitialIntervalMs),
HWPOD_ACTIVITY_RETRY_MAXIMUM_INTERVAL_MS: String(native.temporal.activity.retryMaximumIntervalMs),
HWPOD_ACTIVITY_RETRY_MAXIMUM_ATTEMPTS: String(native.temporal.activity.retryMaximumAttempts),
HWPOD_NODE_WS_TOKEN: nodeToken,
HWPOD_SPEC_DATABASE_URL: databaseUrl,
HWPOD_SPEC_DATABASE_SCHEMA: native.specRegistry.database.schema,
HWPOD_SPEC_DATABASE_TABLE: native.specRegistry.database.table,
HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS: String(native.specRegistry.database.connectionTimeoutMs),
HWPOD_BUILTIN_SPEC_DOCUMENTS_JSON: JSON.stringify(builtInSpecs),
HWPOD_API_BIND_HOST: native.api.bindHost,
HWPOD_API_PROBE_HOST: native.api.probeHost,
HWPOD_API_PORT: String(native.api.port),
HWPOD_WORKER_BIND_HOST: native.worker.bindHost,
HWPOD_WORKER_PROBE_HOST: native.worker.probeHost,
HWPOD_WORKER_HEALTH_PORT: String(native.worker.healthPort),
HWPOD_WEB_BIND_HOST: native.web.bindHost,
HWPOD_WEB_PROBE_HOST: native.web.probeHost,
HWPOD_WEB_PORT: String(native.web.port),
HWPOD_WEB_RUNTIME_CONFIG: JSON.stringify(native.web.runtimeConfig),
HWPOD_WEB_ACCESS_PROFILE_JSON: JSON.stringify(native.web.accessProfile),
HWPOD_NATIVE_API_URL: `http://${native.api.probeHost}:${native.api.port}`,
HWPOD_NODE_OPS_API_URL: `http://${native.api.probeHost}:${native.api.port}`,
};
}