refactor: migrate v02 cloud api to TypeScript

This commit is contained in:
Codex
2026-05-29 00:33:01 +08:00
parent 44851d8eb3
commit a0869c5afa
79 changed files with 10696 additions and 10065 deletions
+33 -6
View File
@@ -825,18 +825,43 @@ function shortRevision(value) {
return source.length >= 7 ? source.slice(0, 7) : source || "unknown";
}
function runNodeEntrypoint(file) {
const child = spawn(process.execPath, [file], {
function runEntrypoint(command, args) {
const child = spawn(command, args, {
cwd: "/app",
env: process.env,
stdio: "inherit"
});
child.on("error", (error) => {
process.stderr.write(JSON.stringify({
serviceId,
status: "failed",
error: "entrypoint_spawn_failed",
command,
reason: error.message
}) + "\n");
process.exit(127);
});
child.on("exit", (code, signal) => {
if (signal) process.kill(process.pid, signal);
else process.exit(code ?? 0);
});
}
function runNodeEntrypoint(file) {
runEntrypoint(process.execPath, [file]);
}
function runBunEntrypoint(file) {
const candidates = [
process.env.HWLAB_BUN_COMMAND,
"/app/node_modules/.bin/bun",
"/usr/local/bin/bun",
"bun"
].filter(Boolean);
const command = candidates.find((candidate) => !candidate.includes("/") || existsSync(candidate)) || "bun";
runEntrypoint(command, [file]);
}
function cookieValue(request, name) {
const raw = request.headers.cookie || "";
for (const part of raw.split(";")) {
@@ -1128,6 +1153,8 @@ function serveHealthOnly() {
if (runtimeKind === "node-command" && entrypoint) {
runNodeEntrypoint(entrypoint);
} else if (runtimeKind === "bun-command" && entrypoint) {
runBunEntrypoint(entrypoint);
} else if (runtimeKind === "cloud-web") {
await serveCloudWeb();
} else {
@@ -1138,11 +1165,12 @@ if (runtimeKind === "node-command" && entrypoint) {
}
function dockerfile(baseImage, port, labels = []) {
const dependencyTimingScript = `started_at=$(node -e "console.log(Date.now())"); status=succeeded; if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && codex_version="$(node -p "require('./node_modules/@openai/codex/package.json').version")" && case "$(uname -m)" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-x64@npm:@openai/codex@\${codex_version}-linux-x64" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-arm64@npm:@openai/codex@\${codex_version}-linux-arm64" ;; esac || status=failed; finished_at=$(node -e "console.log(Date.now())"); node -e "console.log(JSON.stringify({event:'g14-cicd-build-step-timing',stage:'dependency-install',status:process.argv[1],durationMs:Number(process.argv[3])-Number(process.argv[2]),source:'dockerfile-run'}))" "$status" "$started_at" "$finished_at"; test "$status" = succeeded`;
const dependencyTimingScript = `started_at=$(node -e "console.log(Date.now())"); status=succeeded; if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then npm install --no-save --omit=dev --include=optional --ignore-scripts bun@1.3.13; fi && codex_version="$(node -p "require('./node_modules/@openai/codex/package.json').version")" && case "$(uname -m)" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-x64@npm:@openai/codex@\${codex_version}-linux-x64" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-arm64@npm:@openai/codex@\${codex_version}-linux-arm64" ;; esac || status=failed; finished_at=$(node -e "console.log(Date.now())"); node -e "console.log(JSON.stringify({event:'g14-cicd-build-step-timing',stage:'dependency-install',status:process.argv[1],durationMs:Number(process.argv[3])-Number(process.argv[2]),source:'dockerfile-run'}))" "$status" "$started_at" "$finished_at"; test "$status" = succeeded`;
return [
`FROM ${baseImage}`,
"WORKDIR /app",
...labels.map(([name, value]) => `LABEL ${name}=${dockerfileQuote(value)}`),
"ARG HWLAB_ARTIFACT_KIND",
"ENV CODEX_HOME=/codex-home",
"ENV HWLAB_CODE_AGENT_CODEX_HOME=/codex-home",
"ENV HWLAB_CODE_AGENT_WORKSPACE=/workspace/hwlab",
@@ -1167,7 +1195,6 @@ function dockerfile(baseImage, port, labels = []) {
`RUN node -e "const fs=require('node:fs'); fs.writeFileSync('/usr/local/bin/hwlab-dev-artifact-runtime.mjs', Buffer.from('${runtimeScriptBase64()}', 'base64')); fs.chmodSync('/usr/local/bin/hwlab-dev-artifact-runtime.mjs', 0o755);"`,
"ARG HWLAB_ENVIRONMENT",
"ARG HWLAB_SERVICE_ID",
"ARG HWLAB_ARTIFACT_KIND",
"ARG HWLAB_SERVICE_ENTRYPOINT",
"ARG HWLAB_COMMIT_ID",
"ARG HWLAB_REVISION",
@@ -1304,8 +1331,8 @@ async function preflight({ args, services, catalog, deployManifest, baseImagePre
blocker({
type: "runtime_blocker",
scope: service.serviceId,
summary: `${service.serviceId} has no cmd/${service.serviceId}/main.mjs runtime entrypoint; a health-only placeholder image was not built as a real implementation.`,
next: `Add cmd/${service.serviceId}/main.mjs or a dedicated Dockerfile for ${service.serviceId}.`
summary: `${service.serviceId} has no runtime entrypoint; a health-only placeholder image was not built as a real implementation.`,
next: `Add cmd/${service.serviceId}/main.mjs, cmd/${service.serviceId}/main.ts, or a dedicated Dockerfile for ${service.serviceId}.`
})
);
}
+2 -2
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import assert from "node:assert/strict";
import { createServer } from "node:net";
import { createCloudApiServer } from "../internal/cloud/server.mjs";
import { createCloudApiServer } from "../internal/cloud/server.ts";
import { ENVIRONMENT_DEV } from "../internal/protocol/index.mjs";
const previousEnv = {
+3 -3
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import assert from "node:assert/strict";
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import http from "node:http";
@@ -9,12 +9,12 @@ import path from "node:path";
import {
handleCodeAgentChat,
validateCodeAgentChatSchema
} from "../internal/cloud/code-agent-chat.mjs";
} from "../internal/cloud/code-agent-chat.ts";
import {
codexAppServerArgs,
codexAppServerProviderBaseUrl,
createCodexStdioSessionManager
} from "../internal/cloud/codex-stdio-session.mjs";
} from "../internal/cloud/codex-stdio-session.ts";
import {
classifyCodexRunnerCapability,
classifyCodeAgentChatReadiness,
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import {
runDeployContractPlanCli,
writeDeployContractPlanError
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import {
runDeployDesiredStatePlanCli,
writeDeployDesiredStatePlanError
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import {
formatDevRuntimeMigrationFailure,
runDevRuntimeMigrationCli
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import {
formatDevRuntimeProvisioningFailure,
runDevRuntimeProvisioningCli
+2 -2
View File
@@ -67,7 +67,7 @@ test("component model uses built-in service paths", () => {
"skills/",
"tools/"
]);
assert.deepEqual(matchingPaths(["cmd/hwlab-cloud-api/main.mjs"], models[0].componentPaths), ["cmd/hwlab-cloud-api/main.mjs"]);
assert.deepEqual(matchingPaths(["cmd/hwlab-cloud-api/main.ts"], models[0].componentPaths), ["cmd/hwlab-cloud-api/main.ts"]);
});
test("planner remains compatible with deploy.k3s.serviceMappings shape", async () => {
@@ -103,7 +103,7 @@ async function createFixtureRepo(options = {}) {
}
await writeFile(path.join(repo, "package.json"), JSON.stringify({ type: "module" }, null, 2));
await writeFile(path.join(repo, "package-lock.json"), JSON.stringify({ lockfileVersion: 3 }, null, 2));
await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.mjs"), "console.log('api');\n");
await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.ts"), "console.log('api');\n");
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.mjs"), "console.log('bridge');\n");
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.test.mjs"), "console.log('test');\n");
await writeFile(path.join(repo, "web/hwlab-cloud-web/index.html"), "<html></html>\n");
+16 -3
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import { spawn } from "node:child_process";
import { createServer } from "node:net";
import { existsSync } from "node:fs";
import http from "node:http";
import https from "node:https";
import path from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import assert from "node:assert/strict";
@@ -24,9 +26,10 @@ const commonEnv = {
HWLAB_CLOUD_RUNTIME_DURABLE: "false"
};
const children = [];
const bunCommand = resolveBunCommand();
try {
const cloud = startNode("cmd/hwlab-cloud-api/main.mjs", {
const cloud = startNode("cmd/hwlab-cloud-api/main.ts", {
...commonEnv,
HWLAB_CLOUD_API_HOST: "127.0.0.1",
HWLAB_CLOUD_API_PORT: String(cloudPort),
@@ -134,7 +137,8 @@ try {
}
function startNode(entrypoint, env) {
const child = spawn(process.execPath, [entrypoint], {
const command = entrypoint.endsWith(".ts") ? bunCommand : process.execPath;
const child = spawn(command, [entrypoint], {
cwd,
env,
stdio: ["ignore", "pipe", "pipe"]
@@ -153,6 +157,15 @@ function startNode(entrypoint, env) {
return child;
}
function resolveBunCommand() {
const candidates = [
process.env.HWLAB_BUN_COMMAND,
process.env.HOME ? path.join(process.env.HOME, ".bun/bin/bun") : null,
"bun"
].filter(Boolean);
return candidates.find((candidate) => !candidate.includes("/") || existsSync(candidate)) || "bun";
}
async function waitForGatewaySession(apiBaseUrl, gatewaySessionId) {
const deadline = Date.now() + 8000;
let last;
+16 -4
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import net from "node:net";
import os from "node:os";
@@ -8,11 +9,12 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { DEV_ENDPOINT, ENVIRONMENT_DEV } from "../internal/protocol/index.mjs";
import { validateCodeAgentChatSchema } from "../internal/cloud/code-agent-chat.mjs";
import { validateCodeAgentChatSchema } from "../internal/cloud/code-agent-chat.ts";
import { runCli } from "../tools/hwlab-cli/lib/cli.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const children = [];
const bunCommand = resolveBunCommand();
const CODEX_STDIO_FEASIBILITY_BLOCKERS = new Set([
"codex_cli_binary_missing",
"codex_stdio_supervisor_disabled",
@@ -46,7 +48,8 @@ function freePort() {
}
function startNode(relativeScript, env = {}) {
const child = spawn(process.execPath, [relativeScript], {
const command = relativeScript.endsWith(".ts") ? bunCommand : process.execPath;
const child = spawn(command, [relativeScript], {
cwd: repoRoot,
env: {
...process.env,
@@ -73,6 +76,15 @@ function startNode(relativeScript, env = {}) {
return record;
}
function resolveBunCommand() {
const candidates = [
process.env.HWLAB_BUN_COMMAND,
process.env.HOME ? path.join(process.env.HOME, ".bun/bin/bun") : null,
"bun"
].filter(Boolean);
return candidates.find((candidate) => !candidate.includes("/") || existsSync(candidate)) || "bun";
}
function runNodeJson(relativeScript, args = [], env = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [relativeScript, ...args], {
@@ -184,7 +196,7 @@ function rpcEnvelope({ id, method, params = {} }) {
async function smokeCloudApi() {
const port = await freePort();
const service = startNode("cmd/hwlab-cloud-api/main.mjs", {
const service = startNode("cmd/hwlab-cloud-api/main.ts", {
HWLAB_CLOUD_API_HOST: "127.0.0.1",
HWLAB_CLOUD_API_PORT: String(port),
HWLAB_CLOUD_DB_URL: "",
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env node
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import os from "node:os";
import path from "node:path";
const args = process.argv.slice(2);
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
process.stdout.write(JSON.stringify({
script: "run-bun",
usage: "node scripts/run-bun.mjs <bun-args...>",
env: "HWLAB_BUN_COMMAND",
resolvedCommand: resolveBunCommand()
}, null, 2) + "\n");
process.exit(0);
}
const command = resolveBunCommand();
if (!command) {
process.stderr.write(JSON.stringify({
script: "run-bun",
status: "failed",
error: "bun_command_not_found",
checked: bunCandidates()
}, null, 2) + "\n");
process.exit(127);
}
const child = spawn(command, args, {
cwd: process.cwd(),
env: process.env,
stdio: "inherit"
});
child.on("error", (error) => {
process.stderr.write(JSON.stringify({
script: "run-bun",
status: "failed",
error: "bun_spawn_failed",
command,
reason: error.message
}, null, 2) + "\n");
process.exit(127);
});
child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
}
process.exit(code ?? 0);
});
function resolveBunCommand() {
for (const candidate of bunCandidates()) {
if (!candidate) continue;
if (!candidate.includes(path.sep)) return candidate;
if (existsSync(candidate)) return candidate;
}
return null;
}
function bunCandidates() {
return [
process.env.HWLAB_BUN_COMMAND,
path.join(process.cwd(), "node_modules", ".bin", process.platform === "win32" ? "bun.exe" : "bun"),
path.join(os.homedir(), ".bun", "bin", process.platform === "win32" ? "bun.exe" : "bun"),
"/root/.bun/bin/bun",
"/usr/local/bin/bun",
"bun"
].filter(Boolean);
}
@@ -917,7 +917,7 @@ function summarizeCloudWebBuildFreshness(distFreshness, artifactCloudWeb) {
async function inspectCodeAgentTimeoutReadiness(repoRoot) {
const [appSource, cloudApiEntrySource] = await Promise.all([
readFile(path.join(repoRoot, "web/hwlab-cloud-web/app.mjs"), "utf8"),
readFile(path.join(repoRoot, "cmd/hwlab-cloud-api/main.mjs"), "utf8")
readFile(path.join(repoRoot, "cmd/hwlab-cloud-api/main.ts"), "utf8")
]);
const frontendTimeoutMs = numericSourceConstant(appSource, "DEFAULT_CODE_AGENT_TIMEOUT_MS");
const backendTimeoutMs = parseTimeoutFallbackMs(cloudApiEntrySource, "HWLAB_CODE_AGENT_TIMEOUT_MS");
@@ -953,7 +953,7 @@ function parseTimeoutFallbackMs(source, envName) {
async function inspectM3IoSourceReadiness(repoRoot) {
const files = await listTrackedFiles(repoRoot);
const candidateFiles = files.filter((file) =>
/\.(?:mjs|js|json|md|html|css|yaml|yml)$/u.test(file) &&
/\.(?:ts|mjs|js|json|md|html|css|yaml|yml)$/u.test(file) &&
file !== "scripts/src/artifact-runtime-readiness-guard.mjs" &&
file !== "scripts/artifact-runtime-readiness-guard.test.mjs"
);
@@ -971,7 +971,7 @@ async function inspectM3IoSourceReadiness(repoRoot) {
const present = matches.length > 0;
const sourceReady = contractFiles.length > 0 &&
matches.some((file) => file === "web/hwlab-cloud-web/app.mjs") &&
matches.some((file) => file === "internal/cloud/m3-io-control.mjs" || file === "scripts/src/m3-io-control-e2e.mjs");
matches.some((file) => file === "internal/cloud/m3-io-control.ts" || file === "scripts/src/m3-io-control-e2e.mjs");
return {
status: present ? (sourceReady ? "pass" : "blocked") : "absent",
present,
+6 -6
View File
@@ -1,11 +1,11 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.ts";
import {
DEV_CODE_AGENT_PROVIDER_CONTRACT,
codeAgentSecretRefPlaceholder
} from "../../internal/cloud/code-agent-contract.mjs";
} from "../../internal/cloud/code-agent-contract.ts";
import {
HWLAB_M3_IO_API_BASE_URL_ENV,
HWLAB_M3_IO_DEV_SERVICE_BASE_URL
@@ -361,10 +361,10 @@ function validateCloudApiDbSource(ctx, env) {
"cloud API DEV DB SSL mode"
);
expectEqual(ctx, env.HWLAB_CLOUD_DB_CONTRACT, "dev-redacted-presence-only", "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_CONTRACT", "cloud API DB redacted contract marker");
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.source, "secret-url-host", "internal/cloud/db-contract.mjs.endpointAuthority.source", "cloud API DB runtime authority source");
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.env, "HWLAB_CLOUD_DB_URL", "internal/cloud/db-contract.mjs.endpointAuthority.env", "cloud API DB runtime authority env");
expectEqual(ctx, alias.requiredForReadiness, false, "internal/cloud/db-contract.mjs.dns.requiredForReadiness", "cloud API DB DNS alias readiness requirement");
expectEqual(ctx, alias.usedForProbe, false, "internal/cloud/db-contract.mjs.dns.usedForProbe", "cloud API DB DNS alias probe source");
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.source, "secret-url-host", "internal/cloud/db-contract.ts.endpointAuthority.source", "cloud API DB runtime authority source");
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.env, "HWLAB_CLOUD_DB_URL", "internal/cloud/db-contract.ts.endpointAuthority.env", "cloud API DB runtime authority env");
expectEqual(ctx, alias.requiredForReadiness, false, "internal/cloud/db-contract.ts.dns.requiredForReadiness", "cloud API DB DNS alias readiness requirement");
expectEqual(ctx, alias.usedForProbe, false, "internal/cloud/db-contract.ts.dns.usedForProbe", "cloud API DB DNS alias probe source");
for (const name of [
"HWLAB_CLOUD_DB_SERVICE_NAME",
"HWLAB_CLOUD_DB_SERVICE_NAMESPACE",
+2 -2
View File
@@ -7,8 +7,8 @@ import { promisify } from "node:util";
import {
DEV_CODE_AGENT_PROVIDER_CONTRACT,
inspectCodeAgentProviderManifestRefs
} from "../../internal/cloud/code-agent-contract.mjs";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
} from "../../internal/cloud/code-agent-contract.ts";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.ts";
import { tempReportPath } from "./report-paths.mjs";
const execFileAsync = promisify(execFile);
+13
View File
@@ -63,6 +63,19 @@ function serviceSourceState(catalog, serviceId) {
export async function resolveDevArtifactService(repoRoot, serviceId, catalog = null) {
const sourceState = serviceSourceState(catalog, serviceId);
if (serviceId === "hwlab-cloud-api") {
const tsCmdPath = `cmd/${serviceId}/main.ts`;
if (await pathExists(repoRoot, tsCmdPath)) {
return withServicePublishPolicy({
serviceId,
runtimeKind: "bun-command",
entrypoint: tsCmdPath,
implementationState: "repo-entrypoint",
sourceState
});
}
}
const cmdPath = `cmd/${serviceId}/main.mjs`;
if (await pathExists(repoRoot, cmdPath)) {
return withServicePublishPolicy({
@@ -1640,7 +1640,7 @@ function readStaticFiles() {
runtime: readText("web/hwlab-cloud-web/runtime.mjs"),
help: readText("web/hwlab-cloud-web/help.md"),
artifactPublisher: readText("scripts/artifact-publish.mjs"),
cloudApiServer: readText("internal/cloud/server.mjs"),
cloudApiServer: readText("internal/cloud/server.ts"),
devicePodData: readText("internal/device-pod/fake-data.mjs"),
devicePodService: readText("cmd/hwlab-device-pod/main.mjs"),
protocol: readText("internal/protocol/index.mjs"),
+3 -3
View File
@@ -8,11 +8,11 @@ import { fileURLToPath } from "node:url";
import {
DEV_DB_ENV_CONTRACT,
summarizeDbContract
} from "../../internal/cloud/db-contract.mjs";
} from "../../internal/cloud/db-contract.ts";
import {
DEV_CODE_AGENT_PROVIDER_CONTRACT,
codeAgentSecretRefPlaceholder
} from "../../internal/cloud/code-agent-contract.mjs";
} from "../../internal/cloud/code-agent-contract.ts";
import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
@@ -187,7 +187,7 @@ async function createDevGateReport(edgeHealth) {
"node scripts/m1-contract-smoke.mjs"
],
evidence: [
"npm run check includes internal/cloud/server.test.mjs and validates /health plus /health/live."
"npm run check includes internal/cloud/server-health.test.ts and validates /health plus /health/live."
],
summary: "Local cloud-api health checks pass and include service, commit, and image evidence fields."
},
+2 -2
View File
@@ -9,11 +9,11 @@ import {
DEV_DB_ENV_CONTRACT,
buildDbHealthContract,
summarizeDbContract
} from "../../internal/cloud/db-contract.mjs";
} from "../../internal/cloud/db-contract.ts";
import {
DEV_CODE_AGENT_PROVIDER_CONTRACT,
codeAgentSecretRefPlaceholder
} from "../../internal/cloud/code-agent-contract.mjs";
} from "../../internal/cloud/code-agent-contract.ts";
import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";
import { probeRegistryCapabilities } from "./registry-capabilities.mjs";
+2 -2
View File
@@ -13,8 +13,8 @@ import {
RUNTIME_DURABLE_ADAPTER_MISSING,
RUNTIME_DURABILITY_REQUIRED_EVIDENCE,
RUNTIME_STORE_KIND_POSTGRES
} from "../../internal/db/runtime-store.mjs";
import { CLOUD_CORE_MIGRATION_ID } from "../../internal/db/schema.mjs";
} from "../../internal/db/runtime-store.ts";
import { CLOUD_CORE_MIGRATION_ID } from "../../internal/db/schema.ts";
import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
+2 -2
View File
@@ -7,8 +7,8 @@ const defaultKubeconfig = "/etc/rancher/k3s/k3s.yaml";
const defaultNamespace = "hwlab-dev";
const defaultDeployment = "hwlab-cloud-api";
const defaultConfigMap = "hwlab-cloud-api-code-agent-hotfix";
const defaultMountPath = "/app/internal/cloud/code-agent-chat.mjs";
const defaultSubPath = "code-agent-chat.mjs";
const defaultMountPath = "/app/internal/cloud/code-agent-chat.ts";
const defaultSubPath = "code-agent-chat.ts";
const defaultMarker = "runtime-hotfix-pc-gateway-shell";
const defaultAnnotationKey = "hwlab.pikastech.local/pc-gateway-shell-hotfix";
const defaultPodLabelSelector = "app.kubernetes.io/name=hwlab-cloud-api";
@@ -87,7 +87,7 @@ function fixtureResult(args, options = {}) {
if (options.noHotfix) {
return { ok: false, exitCode: 1, stdout: "", stderr: "Error from server (NotFound): configmaps \"hwlab-cloud-api-code-agent-hotfix\" not found" };
}
return okJson({ data: { "code-agent-chat.mjs": "// runtime-hotfix-pc-gateway-shell" } });
return okJson({ data: { "code-agent-chat.ts": "// runtime-hotfix-pc-gateway-shell" } });
}
if (text.includes("get deployment")) {
return okJson(deploymentFixture({ noHotfix: options.noHotfix }));
@@ -127,7 +127,7 @@ function deploymentFixture({ noHotfix = false } = {}) {
name: "code-agent-hotfix",
configMap: {
name: "hwlab-cloud-api-code-agent-hotfix",
items: [{ key: "code-agent-chat.mjs", path: "code-agent-chat.mjs" }]
items: [{ key: "code-agent-chat.ts", path: "code-agent-chat.ts" }]
}
}],
containers: [{
@@ -135,8 +135,8 @@ function deploymentFixture({ noHotfix = false } = {}) {
image: "127.0.0.1:5000/hwlab/hwlab-cloud-api:dev",
volumeMounts: noHotfix ? [] : [{
name: "code-agent-hotfix",
mountPath: "/app/internal/cloud/code-agent-chat.mjs",
subPath: "code-agent-chat.mjs",
mountPath: "/app/internal/cloud/code-agent-chat.ts",
subPath: "code-agent-chat.ts",
readOnly: true
}]
}]
+3 -3
View File
@@ -9,7 +9,7 @@ import {
CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE,
CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE_COLUMNS,
CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS
} from "../../internal/db/schema.mjs";
} from "../../internal/db/schema.ts";
import {
PostgresCloudRuntimeStore,
RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED,
@@ -19,8 +19,8 @@ import {
RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED,
buildPostgresPoolConfig
} from "../../internal/db/runtime-store.mjs";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
} from "../../internal/db/runtime-store.ts";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.ts";
import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
+5 -4
View File
@@ -12,10 +12,11 @@ import {
CLOUD_CORE_MIGRATION_ID,
CLOUD_RUNTIME_DURABLE_ADAPTER_SCHEMA_VERSION,
CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS
} from "../../internal/db/schema.mjs";
} from "../../internal/db/schema.ts";
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../..");
const bunCommand = process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun");
test("source check is non-secret and does not attempt live DB access", async () => {
const report = await buildDevRuntimeMigrationReport(parseArgs(["--check"]), {
@@ -41,8 +42,8 @@ test("source check is non-secret and does not attempt live DB access", async ()
test("cloud-api image migration entrypoint exposes the same non-secret source check", async () => {
const { stdout } = await execFileAsync(
process.execPath,
["cmd/hwlab-cloud-api/migrate.mjs", "--check"],
bunCommand,
["cmd/hwlab-cloud-api/migrate.ts", "--check"],
{
cwd: repoRoot,
env: {
@@ -67,7 +68,7 @@ test("cloud-api image migration entrypoint exposes the same non-secret source ch
test("cloud-api image migration entrypoint redacts invalid DSN-shaped argv failures", async () => {
const secretArg = "postgresql://hwlab:secret-password@db.example.test:5432/hwlab?sslmode=require";
await assert.rejects(
execFileAsync(process.execPath, ["cmd/hwlab-cloud-api/migrate.mjs", secretArg], { cwd: repoRoot }),
execFileAsync(bunCommand, ["cmd/hwlab-cloud-api/migrate.ts", secretArg], { cwd: repoRoot }),
(error) => {
const report = JSON.parse(error.stdout);
assert.equal(report.conclusion.status, "blocked");
+2 -2
View File
@@ -3,7 +3,7 @@ import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.ts";
import {
PostgresCloudRuntimeStore,
RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED,
@@ -13,7 +13,7 @@ import {
RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED,
buildPostgresPoolConfig
} from "../../internal/db/runtime-store.mjs";
} from "../../internal/db/runtime-store.ts";
import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
@@ -11,6 +11,7 @@ import {
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../..");
const bunCommand = process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun");
test("source check parses target role/database without leaking DB URL material", async () => {
const report = await buildDevRuntimeProvisioningReport(parseArgs(["--check"]), {
@@ -33,8 +34,8 @@ test("source check parses target role/database without leaking DB URL material",
test("cloud-api image provisioning entrypoint exposes non-secret source check", async () => {
const { stdout } = await execFileAsync(
process.execPath,
["cmd/hwlab-cloud-api/provision.mjs", "--check"],
bunCommand,
["cmd/hwlab-cloud-api/provision.ts", "--check"],
{
cwd: repoRoot,
env: {
+2
View File
@@ -353,6 +353,7 @@ function reuseCandidate(catalogRecord, affected) {
}
function runtimeKindForService(serviceId) {
if (serviceId === "hwlab-cloud-api") return "bun-command";
if (serviceId === "hwlab-cloud-web") return "cloud-web";
if (serviceId === "hwlab-cli") return "cli";
if (serviceId === "hwlab-agent-skills") return "skills-bundle";
@@ -360,6 +361,7 @@ function runtimeKindForService(serviceId) {
}
function entrypointForService(serviceId) {
if (serviceId === "hwlab-cloud-api") return "cmd/hwlab-cloud-api/main.ts";
if (serviceId === "hwlab-cloud-web") return "web/hwlab-cloud-web/index.html";
if (serviceId === "hwlab-cli") return "tools/hwlab-cli/bin/hwlab-cli.mjs";
if (serviceId === "hwlab-agent-skills") return "skills/hwlab-agent-runtime/SKILL.md";
+4 -4
View File
@@ -8,7 +8,7 @@ import {
M3_IO_CONTROL_CONTRACT_VERSION,
M3_IO_CONTROL_ROUTE,
describeM3IoControl
} from "../../internal/cloud/m3-io-control.mjs";
} from "../../internal/cloud/m3-io-control.ts";
export const M3_IO_E2E_CONTRACT_VERSION = "m3-io-control-e2e-v1";
export const defaultFrontendUrl = "http://74.48.78.17:16666/";
@@ -225,9 +225,9 @@ export async function buildM3IoControlSourceReport(options = {}) {
export async function runM3IoControlSourceChecks({ repoRoot: root = repoRoot } = {}) {
const [serverSource, jsonRpcSource, m3Source, appSource, htmlSource, artifactPublisherSource, cloudWebRouteSource] = await Promise.all([
readRepoText(root, "internal/cloud/server.mjs"),
readRepoText(root, "internal/cloud/json-rpc.mjs"),
readRepoText(root, "internal/cloud/m3-io-control.mjs"),
readRepoText(root, "internal/cloud/server.ts"),
readRepoText(root, "internal/cloud/json-rpc.ts"),
readRepoText(root, "internal/cloud/m3-io-control.ts"),
readRepoText(root, "web/hwlab-cloud-web/app.mjs"),
readRepoText(root, "web/hwlab-cloud-web/index.html"),
readRepoText(root, "scripts/artifact-publish.mjs"),
+1 -1
View File
@@ -14,7 +14,7 @@ import {
import {
M3_IO_CHAIN,
M3_IO_CONTROL_ROUTE
} from "../../internal/cloud/m3-io-control.mjs";
} from "../../internal/cloud/m3-io-control.ts";
import {
buildM3IoControlSourceReport,
runM3IoControlLiveReport
+3 -3
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import assert from "node:assert/strict";
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
@@ -6,11 +6,11 @@ import { fileURLToPath } from "node:url";
import {
DEV_DB_ENV_CONTRACT,
parseDbUrlContract
} from "../internal/cloud/db-contract.mjs";
} from "../internal/cloud/db-contract.ts";
import {
DEV_CODE_AGENT_PROVIDER_CONTRACT,
codeAgentSecretRefPlaceholder
} from "../internal/cloud/code-agent-contract.mjs";
} from "../internal/cloud/code-agent-contract.ts";
import {
HWLAB_M3_IO_API_BASE_URL_ENV,
HWLAB_M3_IO_DEV_SERVICE_BASE_URL
+12 -12
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env node
#!/usr/bin/env bun
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DEV_DB_ENV_CONTRACT } from "../internal/cloud/db-contract.mjs";
import { DEV_DB_ENV_CONTRACT } from "../internal/cloud/db-contract.ts";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -215,12 +215,12 @@ function assertDurableRuntimeRunbook(value) {
"Moved:",
"Still blocked:",
"No full M3, M4, or M5 acceptance is allowed while runtime durability is blocked",
"node cmd/hwlab-cloud-api/provision.mjs --check",
"node cmd/hwlab-cloud-api/provision.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"node cmd/hwlab-cloud-api/provision.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"node cmd/hwlab-cloud-api/migrate.mjs --check",
"node cmd/hwlab-cloud-api/migrate.mjs --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"node cmd/hwlab-cloud-api/migrate.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"bun cmd/hwlab-cloud-api/provision.ts --check",
"bun cmd/hwlab-cloud-api/provision.ts --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"bun cmd/hwlab-cloud-api/provision.ts --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"bun cmd/hwlab-cloud-api/migrate.ts --check",
"bun cmd/hwlab-cloud-api/migrate.ts --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"bun cmd/hwlab-cloud-api/migrate.ts --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"DEV CD runtime migration Job uses",
"node scripts/dev-runtime-provisioning.mjs --check",
"node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
@@ -247,11 +247,11 @@ function assertCloudApiImageRuntimeEntrypoints({ packageJson, artifactPublisher,
assertIncludes(artifactPublisher, expected, `${label} artifact publisher`);
}
for (const expected of [
"cmd/hwlab-cloud-api/provision.mjs",
"cmd/hwlab-cloud-api/migrate.mjs"
"cmd/hwlab-cloud-api/provision.ts",
"cmd/hwlab-cloud-api/migrate.ts"
]) {
assertIncludes(packageJson.scripts?.check, `node --check ${expected}`, `${label} package check`);
assertIncludes(packageJson.scripts?.check, `node ${expected} --check`, `${label} package runtime check`);
assertIncludes(packageJson.scripts?.check, `bun build ${expected} --target=bun --packages=external`, `${label} package check`);
assertIncludes(packageJson.scripts?.check, `bun ${expected} --check`, `${label} package runtime check`);
}
for (const expected of ["scripts/g14-artifact-publish.mjs", "runtime-dev", "runtime-prod"]) {
assertIncludes(gitopsRenderSource, expected, `${label} G14 GitOps render source`);