Files
pikasTech-HWLAB/internal/dev-entrypoint/artifact-runtime.mjs
T
2026-05-30 11:09:56 +08:00

181 lines
6.6 KiB
JavaScript

import { spawn } from "node:child_process";
import { createServer } from "node:http";
import { chmodSync, existsSync, lstatSync, mkdirSync, symlinkSync } from "node:fs";
import path from "node:path";
import { serveCloudWeb } from "/app/internal/dev-entrypoint/cloud-web-runtime.mjs";
const serviceId = process.env.HWLAB_SERVICE_ID || "hwlab-unknown";
const environment = process.env.HWLAB_ENVIRONMENT || "dev";
const runtimeKind = process.env.HWLAB_ARTIFACT_KIND || "health-placeholder";
const entrypoint = process.env.HWLAB_SERVICE_ENTRYPOINT || "";
const port = Number.parseInt(process.env.PORT || process.env.HWLAB_PORT || "8080", 10);
function ensureCodeAgentRuntimeBase() {
if (serviceId !== "hwlab-cloud-api") return;
const workspace = process.env.HWLAB_CODE_AGENT_CODEX_WORKSPACE || process.env.HWLAB_CODE_AGENT_WORKSPACE || "/workspace/hwlab";
const codexHome = process.env.CODEX_HOME || process.env.HWLAB_CODE_AGENT_CODEX_HOME || "/codex-home";
process.env.HWLAB_CODE_AGENT_WORKSPACE = process.env.HWLAB_CODE_AGENT_WORKSPACE || workspace;
process.env.HWLAB_CODE_AGENT_CODEX_WORKSPACE = process.env.HWLAB_CODE_AGENT_CODEX_WORKSPACE || workspace;
process.env.HWLAB_CODE_AGENT_CODEX_SANDBOX = process.env.HWLAB_CODE_AGENT_CODEX_SANDBOX || "danger-full-access";
process.env.HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED = process.env.HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED || "1";
process.env.HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR = process.env.HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR || "repo-owned";
process.env.HWLAB_CODE_AGENT_PROVIDER = process.env.HWLAB_CODE_AGENT_PROVIDER || "codex-stdio";
process.env.CODEX_HOME = codexHome;
process.env.HWLAB_CODE_AGENT_CODEX_COMMAND = process.env.HWLAB_CODE_AGENT_CODEX_COMMAND || "/app/node_modules/.bin/codex";
process.env.HWLAB_CODE_AGENT_SKILLS_DIRS = process.env.HWLAB_CODE_AGENT_SKILLS_DIRS || "/app/skills";
mkdirSync(path.dirname(workspace), { recursive: true });
if (!existsSync(workspace)) {
if (workspace === "/workspace/hwlab" && existsSync("/app")) {
symlinkSync("/app", workspace, "dir");
} else {
mkdirSync(workspace, { recursive: true });
}
}
mkdirSync(codexHome, { recursive: true });
for (const target of [workspace, codexHome]) {
try {
const info = lstatSync(target);
chmodSync(info.isSymbolicLink() ? path.resolve(target) : target, 0o777);
} catch {
// Runtime readiness reports the concrete blocker without exposing secret values.
}
}
}
ensureCodeAgentRuntimeBase();
function sendJson(response, statusCode, body) {
const payload = JSON.stringify(body, null, 2);
response.writeHead(statusCode, {
"content-type": "application/json; charset=utf-8",
"content-length": Buffer.byteLength(payload)
});
response.end(payload);
}
function healthPayload() {
const commitId = process.env.HWLAB_COMMIT_ID || process.env.HWLAB_GIT_SHA || "unknown";
const imageReference = process.env.HWLAB_IMAGE || "unknown";
const imageTag = imageTagFromReference(imageReference) || shortRevision(commitId);
const buildCreatedAt = normalizeIsoTimestamp(
process.env.HWLAB_BUILD_CREATED_AT ||
process.env.HWLAB_IMAGE_CREATED_AT ||
process.env.SOURCE_DATE_EPOCH ||
process.env.BUILD_DATE
);
const buildSource = process.env.HWLAB_BUILD_SOURCE || "runtime-env";
return {
serviceId,
environment,
status: "ok",
artifactKind: runtimeKind,
revision: process.env.HWLAB_REVISION || commitId,
commit: {
id: commitId,
source: buildSource
},
image: {
reference: imageReference,
tag: imageTag,
digest: process.env.HWLAB_IMAGE_DIGEST || "unknown"
},
build: {
createdAt: buildCreatedAt,
source: buildSource,
provenance: process.env.HWLAB_BUILD_PROVENANCE || null,
metadataSource: buildCreatedAt ? "runtime-env:HWLAB_BUILD_CREATED_AT" : "unavailable",
unavailableReason: buildCreatedAt ? null : "HWLAB_BUILD_CREATED_AT missing from runtime environment"
}
};
}
function normalizeIsoTimestamp(value) {
const source = String(value || "").trim();
if (!source) return null;
if (/^\d+$/u.test(source)) {
const seconds = Number.parseInt(source, 10);
if (Number.isSafeInteger(seconds)) {
const date = new Date(seconds * 1000);
if (Number.isFinite(date.getTime())) return date.toISOString();
}
}
const date = new Date(source);
return Number.isFinite(date.getTime()) ? date.toISOString() : null;
}
function imageTagFromReference(imageReference) {
const source = String(imageReference || "").trim();
const slashIndex = source.lastIndexOf("/");
const colonIndex = source.lastIndexOf(":");
if (colonIndex <= slashIndex) return null;
return source.slice(colonIndex + 1) || null;
}
function shortRevision(value) {
const source = String(value || "").trim();
return source.length >= 7 ? source.slice(0, 7) : source || "unknown";
}
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 serveHealthOnly() {
const server = createServer((request, response) => {
const url = new URL(request.url || "/", "http://hwlab-artifact.local");
if (url.pathname === "/health/live" || url.pathname === "/health") {
sendJson(response, 200, healthPayload());
return;
}
sendJson(response, 404, { error: "not_found", serviceId, path: url.pathname });
});
server.listen(port, "0.0.0.0", () => {
process.stdout.write(JSON.stringify({ serviceId, environment, status: "listening", port }) + "\n");
});
}
if (runtimeKind === "node-command" && entrypoint) {
runNodeEntrypoint(entrypoint);
} else if (runtimeKind === "bun-command" && entrypoint) {
runBunEntrypoint(entrypoint);
} else if (runtimeKind === "cloud-web") {
await serveCloudWeb({ port, serviceId, healthPayload, sendJson });
} else {
serveHealthOnly();
}