feat: add hwlab cli and web skeleton
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { DEV_ENDPOINT } from "../../../internal/protocol/index.mjs";
|
||||
|
||||
const DEFAULT_PROJECTS = [
|
||||
{
|
||||
projectId: "proj_mvp-l6",
|
||||
name: "HWLAB L6 MVP",
|
||||
status: "active",
|
||||
environment: "dev",
|
||||
updatedAt: "2026-05-21T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
projectId: "proj_cloud-web",
|
||||
name: "Cloud Web Skeleton",
|
||||
status: "active",
|
||||
environment: "dev",
|
||||
updatedAt: "2026-05-20T12:00:00.000Z"
|
||||
}
|
||||
];
|
||||
|
||||
function loadRuntimeFixture(cwd) {
|
||||
const fixturePath = path.resolve(cwd, "fixtures/mvp/runtime.json");
|
||||
if (!fs.existsSync(fixturePath)) {
|
||||
return null;
|
||||
}
|
||||
const raw = fs.readFileSync(fixturePath, "utf8");
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
function resolveRuntime(cwd) {
|
||||
const fixture = loadRuntimeFixture(cwd);
|
||||
if (fixture) {
|
||||
return fixture;
|
||||
}
|
||||
return {
|
||||
endpoints: { dev: DEV_ENDPOINT },
|
||||
projects: DEFAULT_PROJECTS,
|
||||
mvpSteps: [
|
||||
"browser/CLI/gateway",
|
||||
"master hwlab-edge-proxy",
|
||||
"frp",
|
||||
"D601 hwlab-dev/hwlab-router",
|
||||
"cloud-web/cloud-api",
|
||||
"hardware trusted closed loop",
|
||||
"agent automation closed loop",
|
||||
"evidence record",
|
||||
"worker cleanup"
|
||||
],
|
||||
loops: [
|
||||
{ name: "hardware trusted closed loop" },
|
||||
{ name: "agent automation closed loop" }
|
||||
],
|
||||
evidence: [],
|
||||
cleanup: []
|
||||
};
|
||||
}
|
||||
|
||||
function formatJson(value) {
|
||||
return JSON.stringify(value, null, 2);
|
||||
}
|
||||
|
||||
function writeLine(stream, line = "") {
|
||||
stream.write(`${line}\n`);
|
||||
}
|
||||
|
||||
function writeProjectList(runtime, stdout) {
|
||||
writeLine(stdout, "hwlab-cli project list");
|
||||
writeLine(stdout, `endpoint: ${runtime.endpoints?.dev ?? DEV_ENDPOINT}`);
|
||||
for (const project of runtime.projects ?? []) {
|
||||
writeLine(
|
||||
stdout,
|
||||
`${project.projectId} | ${project.name} | ${project.status} | ${project.environment} | ${project.updatedAt}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function writeHealth(runtime, stdout) {
|
||||
writeLine(stdout, "hwlab-cli health");
|
||||
writeLine(stdout, "status: ok");
|
||||
writeLine(stdout, `environment: dev`);
|
||||
writeLine(stdout, `endpoint: ${runtime.endpoints?.dev ?? DEV_ENDPOINT}`);
|
||||
writeLine(stdout, `projects: ${(runtime.projects ?? []).length}`);
|
||||
}
|
||||
|
||||
function writeDryRun(runtime, stdout) {
|
||||
writeLine(stdout, "hwlab-cli test e2e --env dev --mvp --dry-run");
|
||||
writeLine(stdout, `endpoint: ${runtime.endpoints?.dev ?? DEV_ENDPOINT}`);
|
||||
writeLine(stdout, "mvp route:");
|
||||
writeLine(
|
||||
stdout,
|
||||
` ${runtime.serviceRoute?.join(" -> ") ?? "browser/CLI/gateway -> master hwlab-edge-proxy -> frp -> D601 hwlab-dev/hwlab-router -> cloud-web/cloud-api"}`
|
||||
);
|
||||
for (const step of runtime.mvpSteps ?? []) {
|
||||
writeLine(stdout, `- ${step}`);
|
||||
}
|
||||
writeLine(stdout, "closed loops:");
|
||||
for (const loop of runtime.loops ?? []) {
|
||||
writeLine(stdout, `- ${loop.name}${loop.description ? `: ${loop.description}` : ""}`);
|
||||
}
|
||||
writeLine(stdout, "evidence record:");
|
||||
for (const evidence of runtime.evidence ?? []) {
|
||||
writeLine(
|
||||
stdout,
|
||||
`- ${evidence.evidenceId} | ${evidence.kind} | ${evidence.serviceId} | ${evidence.uri}`
|
||||
);
|
||||
}
|
||||
writeLine(stdout, "worker cleanup:");
|
||||
for (const step of runtime.cleanup ?? []) {
|
||||
writeLine(stdout, `- ${step}`);
|
||||
}
|
||||
writeLine(stdout, "dry-run only: no DEV/PROD changes were made");
|
||||
}
|
||||
|
||||
function writeHelp(stdout) {
|
||||
writeLine(stdout, "hwlab-cli");
|
||||
writeLine(stdout, "commands:");
|
||||
writeLine(stdout, " health");
|
||||
writeLine(stdout, " project list");
|
||||
writeLine(stdout, " test e2e --env dev --mvp --dry-run");
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const flags = new Set();
|
||||
const options = new Map();
|
||||
const rest = [];
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
if (arg.startsWith("--")) {
|
||||
flags.add(arg);
|
||||
const next = args[index + 1];
|
||||
if (next && !next.startsWith("--")) {
|
||||
options.set(arg, next);
|
||||
index += 1;
|
||||
}
|
||||
} else {
|
||||
rest.push(arg);
|
||||
}
|
||||
}
|
||||
return { flags, options, rest };
|
||||
}
|
||||
|
||||
export async function runCli(argv, io) {
|
||||
const stdout = io.stdout ?? process.stdout;
|
||||
const stderr = io.stderr ?? process.stderr;
|
||||
const runtime = resolveRuntime(io.cwd ?? process.cwd());
|
||||
const [command = "help", subcommand, ...rest] = argv;
|
||||
const { flags, options } = parseArgs(rest);
|
||||
|
||||
if (command === "health") {
|
||||
writeHealth(runtime, stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command === "project" && subcommand === "list") {
|
||||
writeProjectList(runtime, stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command === "test" && subcommand === "e2e") {
|
||||
const env = options.get("--env") ?? null;
|
||||
const isDryRun = flags.has("--dry-run");
|
||||
const isMvp = flags.has("--mvp");
|
||||
if (env !== "dev") {
|
||||
writeLine(stderr, "only --env dev is supported in the skeleton");
|
||||
return 1;
|
||||
}
|
||||
if (!isMvp || !isDryRun) {
|
||||
writeLine(stderr, "use --mvp --dry-run for the skeleton");
|
||||
return 1;
|
||||
}
|
||||
writeDryRun(runtime, stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command === "project" && !subcommand) {
|
||||
writeLine(stderr, "usage: hwlab-cli project list");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (command === "test" && !subcommand) {
|
||||
writeLine(stderr, "usage: hwlab-cli test e2e --env dev --mvp --dry-run");
|
||||
return 1;
|
||||
}
|
||||
|
||||
writeHelp(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function formatRuntime(runtime) {
|
||||
return formatJson(runtime);
|
||||
}
|
||||
Reference in New Issue
Block a user