fix(cli): make group help side-effect free
This commit is contained in:
@@ -111,6 +111,27 @@ test("hwlab-cli client auth status exposes local session state and next commands
|
||||
assert.equal(JSON.stringify(mismatch.payload).includes("session-old"), false);
|
||||
});
|
||||
|
||||
test("hwlab-cli client group help is visible and does not issue HTTP requests", async () => {
|
||||
let fetchCount = 0;
|
||||
const fetchImpl = async () => {
|
||||
fetchCount += 1;
|
||||
return new Response(JSON.stringify({ ok: false }), { status: 500 });
|
||||
};
|
||||
|
||||
const auth = await runHwlabCli(["client", "auth", "--help", "--base-url", "http://web.test"], { fetchImpl });
|
||||
assert.equal(auth.exitCode, 0);
|
||||
assert.equal(auth.payload.action, "client.auth.help");
|
||||
assert.equal(auth.payload.serviceRuntime, false);
|
||||
assert.ok(auth.payload.commands.some((command: string) => command.startsWith("login ")));
|
||||
|
||||
const agent = await runHwlabCli(["client", "agent", "--help", "--base-url", "http://web.test"], { fetchImpl });
|
||||
assert.equal(agent.exitCode, 0);
|
||||
assert.equal(agent.payload.action, "client.agent.help");
|
||||
assert.equal(agent.payload.serviceRuntime, false);
|
||||
assert.ok(agent.payload.commands.some((command: string) => command.startsWith("send ")));
|
||||
assert.equal(fetchCount, 0);
|
||||
});
|
||||
|
||||
test("hwlab-cli client device-pods events compacts event stream by default", async () => {
|
||||
const result = await runHwlabCli(["client", "device-pods", "events", "pod-1", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], {
|
||||
fetchImpl: async () => new Response(JSON.stringify({
|
||||
|
||||
@@ -62,6 +62,10 @@ async function clientCommand(context: any) {
|
||||
const group = context.rest[0] || "help";
|
||||
const next = { ...context, rest: context.rest.slice(1) };
|
||||
if (["help", "--help", "-h"].includes(group)) return help();
|
||||
if (context.parsed.help === true) {
|
||||
const groupHelp = clientSubcommandHelp(group);
|
||||
if (groupHelp) return groupHelp;
|
||||
}
|
||||
if (group === "auth") return authCommand(next);
|
||||
if (group === "device-pods" || group === "device-pod") return devicePodsCommand(next);
|
||||
if (group === "runtime") return runtimeCommand(next);
|
||||
@@ -74,6 +78,19 @@ async function clientCommand(context: any) {
|
||||
throw cliError("unsupported_client_command", `unsupported client command: ${group}`, { group });
|
||||
}
|
||||
|
||||
function clientSubcommandHelp(group: string) {
|
||||
if (group === "auth") return authHelp();
|
||||
if (group === "device-pods" || group === "device-pod") return devicePodsHelp();
|
||||
if (group === "runtime") return runtimeHelp();
|
||||
if (group === "gateway") return gatewayHelp();
|
||||
if (group === "agent") return agentHelp();
|
||||
if (["harness", "harness-ops", "harness-opt"].includes(group)) return harnessHelp();
|
||||
if (group === "workbench") return workbenchHelp();
|
||||
if (group === "rpc") return rpcHelp();
|
||||
if (group === "request") return requestHelp();
|
||||
return null;
|
||||
}
|
||||
|
||||
function help() {
|
||||
return ok("help", {
|
||||
version: VERSION,
|
||||
@@ -116,6 +133,7 @@ function help() {
|
||||
|
||||
async function authCommand(context: any) {
|
||||
const subcommand = context.rest[0] || "session";
|
||||
if (wantsHelp(context)) return authHelp();
|
||||
if (subcommand === "login") return authLogin(context);
|
||||
if (subcommand === "status") return authStatus(context);
|
||||
if (subcommand === "profiles") return authProfiles(context);
|
||||
@@ -132,6 +150,20 @@ async function authCommand(context: any) {
|
||||
throw cliError("unsupported_auth_command", `unsupported auth command: ${subcommand}`, { subcommand });
|
||||
}
|
||||
|
||||
function authHelp() {
|
||||
return ok("client.auth.help", {
|
||||
serviceRuntime: false,
|
||||
imagePublished: false,
|
||||
commands: [
|
||||
"login --base-url URL --username USER --password-env HWLAB_PASSWORD [--profile NAME]",
|
||||
"status [--base-url URL] [--profile NAME]",
|
||||
"session [--base-url URL] [--profile NAME]",
|
||||
"profiles [--base-url URL]",
|
||||
"logout [--base-url URL] [--profile NAME]"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async function authStatus(context: any) {
|
||||
const localSession = sessionStateSummary(await readSessionState({ parsed: context.parsed, env: context.env, cwd: context.cwd ?? process.cwd() }));
|
||||
return ok("client.auth.status", {
|
||||
@@ -190,6 +222,7 @@ async function authLogin(context: any) {
|
||||
|
||||
async function devicePodsCommand(context: any) {
|
||||
const subcommand = context.rest[0] || "list";
|
||||
if (wantsHelp(context)) return devicePodsHelp();
|
||||
if (subcommand === "list") {
|
||||
const response = await requestJson({ ...context, method: "GET", path: "/v1/device-pods" });
|
||||
return responsePayload("client.device-pods.list", response, context, { route: route("GET", "/v1/device-pods"), body: responseBodyForCli(response.body, context.parsed) });
|
||||
@@ -210,8 +243,22 @@ async function devicePodsCommand(context: any) {
|
||||
throw cliError("unsupported_device_pods_command", `unsupported device-pods command: ${subcommand}`, { subcommand });
|
||||
}
|
||||
|
||||
function devicePodsHelp() {
|
||||
return ok("client.device-pods.help", {
|
||||
serviceRuntime: false,
|
||||
imagePublished: false,
|
||||
commands: [
|
||||
"list [--base-url URL]",
|
||||
"status POD_ID [--base-url URL]",
|
||||
"events POD_ID [--limit N]",
|
||||
"probe POD_ID [--max-bytes N]"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async function runtimeCommand(context: any) {
|
||||
const subcommand = context.rest[0] || "routes";
|
||||
if (wantsHelp(context)) return runtimeHelp();
|
||||
if (subcommand !== "routes" && subcommand !== "pods") {
|
||||
throw cliError("unsupported_runtime_command", `unsupported runtime command: ${subcommand}`, { subcommand });
|
||||
}
|
||||
@@ -237,6 +284,17 @@ async function runtimeCommand(context: any) {
|
||||
});
|
||||
}
|
||||
|
||||
function runtimeHelp() {
|
||||
return ok("client.runtime.help", {
|
||||
serviceRuntime: false,
|
||||
imagePublished: false,
|
||||
commands: [
|
||||
"routes [--service-id SERVICE]",
|
||||
"pods [--service-id SERVICE]"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function liveBuildServices(body: any) {
|
||||
const builds = Array.isArray(body?.builds) ? body.builds : [];
|
||||
if (builds.length > 0) return builds;
|
||||
@@ -523,6 +581,7 @@ function gatewayScenarioPass({ scenario, response, dispatch, stdout, stderr, gat
|
||||
|
||||
async function agentCommand(context: any) {
|
||||
const subcommand = context.rest[0] || "send";
|
||||
if (wantsHelp(context)) return agentHelp();
|
||||
if (subcommand === "send") return agentSend(context);
|
||||
if (subcommand === "result") {
|
||||
const traceId = requiredTraceId(context.rest[1] ?? context.parsed.traceId);
|
||||
@@ -550,6 +609,20 @@ async function agentCommand(context: any) {
|
||||
throw cliError("unsupported_agent_command", `unsupported agent command: ${subcommand}`, { subcommand });
|
||||
}
|
||||
|
||||
function agentHelp() {
|
||||
return ok("client.agent.help", {
|
||||
serviceRuntime: false,
|
||||
imagePublished: false,
|
||||
commands: [
|
||||
"send --message TEXT|--message-file PATH [--from-trace TRACE] [--conversation-id ID] [--session-id ID] [--thread-id ID] [--retry-of TRACE] [--no-wait]",
|
||||
"result TRACE_ID [--full]",
|
||||
"trace TRACE_ID [--render web] [--limit N] [--full]",
|
||||
"inspect --trace-id TRACE_ID|--conversation-id ID|--session-id ID|--thread-id ID",
|
||||
"cancel TRACE_ID"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function agentInspectPath(context: any) {
|
||||
const params = new URLSearchParams();
|
||||
const traceId = text(context.rest[1] ?? context.parsed.traceId);
|
||||
@@ -892,6 +965,7 @@ async function pollAgentResult(context: any, traceId: string, acceptedBody: any)
|
||||
|
||||
async function workbenchCommand(context: any) {
|
||||
const subcommand = context.rest[0] || "summary";
|
||||
if (wantsHelp(context)) return workbenchHelp();
|
||||
if (subcommand === "restore" || subcommand === "status") return workbenchRestoreCommand(context, subcommand);
|
||||
if (subcommand === "watch") return workbenchWatchCommand(context);
|
||||
if (subcommand === "reset") return workbenchResetCommand(context);
|
||||
@@ -921,6 +995,20 @@ async function workbenchCommand(context: any) {
|
||||
return ok("client.workbench.summary", { status: failed ? "degraded" : "succeeded", baseUrl: baseUrl(context.parsed, context.env), devicePodId: podId, probes }, failed ? "degraded" : "succeeded");
|
||||
}
|
||||
|
||||
function workbenchHelp() {
|
||||
return ok("client.workbench.help", {
|
||||
serviceRuntime: false,
|
||||
imagePublished: false,
|
||||
commands: [
|
||||
"summary [--pod-id POD_ID]",
|
||||
"restore [--profile NAME]",
|
||||
"status [--profile NAME]",
|
||||
"watch [--after-revision REVISION]",
|
||||
"reset --confirm"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async function workbenchRestoreCommand(context: any, subcommand: string) {
|
||||
const workspace = await restoreWorkbenchWorkspace(context, { quiet: false });
|
||||
return ok(`client.workbench.${subcommand}`, {
|
||||
@@ -954,6 +1042,7 @@ async function workbenchResetCommand(context: any) {
|
||||
}
|
||||
|
||||
async function requestCommand(context: any) {
|
||||
if (wantsHelp(context)) return requestHelp();
|
||||
const method = requiredText(context.rest[0], "method").toUpperCase();
|
||||
const pathName = normalizeRequestPath(requiredText(context.rest[1], "path"));
|
||||
const body = await requestBodyValue(context);
|
||||
@@ -976,7 +1065,20 @@ async function requestCommand(context: any) {
|
||||
});
|
||||
}
|
||||
|
||||
function requestHelp() {
|
||||
return ok("client.request.help", {
|
||||
serviceRuntime: false,
|
||||
imagePublished: false,
|
||||
commands: [
|
||||
"GET /path [--full]",
|
||||
"POST /path --body-json '{...}' [--full]",
|
||||
"METHOD /path --body-file FILE"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async function rpcCommand(context: any) {
|
||||
if (wantsHelp(context)) return rpcHelp();
|
||||
const method = requiredText(context.rest[0] ?? context.parsed.method, "method");
|
||||
const params = await rpcParamsValue(context);
|
||||
const traceId = text(context.parsed.traceId) || makeId("trc");
|
||||
@@ -1006,6 +1108,23 @@ async function rpcCommand(context: any) {
|
||||
});
|
||||
}
|
||||
|
||||
function rpcHelp() {
|
||||
return ok("client.rpc.help", {
|
||||
serviceRuntime: false,
|
||||
imagePublished: false,
|
||||
commands: [
|
||||
"METHOD [--params-json '{...}']",
|
||||
"METHOD --params-file FILE",
|
||||
"METHOD --full"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function wantsHelp(context: any) {
|
||||
const token = text(context.rest?.[0]);
|
||||
return context.parsed.help === true || token === "help" || token === "--help" || token === "-h";
|
||||
}
|
||||
|
||||
async function rpcParamsValue(context: any) {
|
||||
const { parsed } = context;
|
||||
const raw = typeof parsed.paramsJson === "string"
|
||||
|
||||
Reference in New Issue
Block a user