Adds a CLI command generation pipeline with a runner to execute generated commands, run/artifact API routes, and a TestRunnerPanel UI for monitoring execution — complete with tests covering the generator, runner, run routes, and the panel component. Fusion-Task-Id: FN-3765
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import type { GenerateCliInput, GeneratedCliArtifact } from "./types.js";
|
|
|
|
function buildScript(draftJson: string): string {
|
|
return `#!/usr/bin/env node
|
|
const draft = ${draftJson};
|
|
|
|
function parseArgs(argv) {
|
|
const parsed = { endpoint: "", params: {} };
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const part = argv[i];
|
|
if (part === "--endpoint") {
|
|
parsed.endpoint = String(argv[i + 1] ?? "");
|
|
i += 1;
|
|
continue;
|
|
}
|
|
if (part.startsWith("--")) {
|
|
const key = part.slice(2);
|
|
const next = argv[i + 1];
|
|
if (next === undefined || String(next).startsWith("--")) {
|
|
parsed.params[key] = true;
|
|
} else {
|
|
parsed.params[key] = String(next);
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function endpointUrl(baseUrl, path) {
|
|
return new URL(path, baseUrl).toString();
|
|
}
|
|
|
|
(async () => {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const endpoint = draft.endpoints.find((item) => item.id === args.endpoint);
|
|
if (!endpoint) {
|
|
process.stderr.write(\`Unknown endpoint: \${args.endpoint}\\n\`);
|
|
process.exit(2);
|
|
}
|
|
|
|
const method = endpoint.method || "GET";
|
|
const url = endpointUrl(draft.baseUrl, endpoint.path || "/");
|
|
const headers = { "content-type": "application/json" };
|
|
|
|
if (draft.credential?.kind === "apiKey") {
|
|
const envValue = process.env[draft.credential.envVar] ?? process.env[\`CLIPP_CRED_\${String(draft.credential.envVar).toUpperCase()}\`];
|
|
if (envValue) headers[draft.credential.header] = envValue;
|
|
}
|
|
if (draft.credential?.kind === "bearerToken") {
|
|
const token = process.env[draft.credential.envVar] ?? process.env[\`CLIPP_CRED_\${String(draft.credential.envVar).toUpperCase()}\`];
|
|
if (token) headers.authorization = \`Bearer \${token}\`;
|
|
}
|
|
if (draft.credential?.kind === "basicAuth") {
|
|
const username = process.env[draft.credential.usernameEnvVar] ?? process.env[\`CLIPP_CRED_\${String(draft.credential.usernameEnvVar).toUpperCase()}\`];
|
|
const password = process.env[draft.credential.passwordEnvVar] ?? process.env[\`CLIPP_CRED_\${String(draft.credential.passwordEnvVar).toUpperCase()}\`];
|
|
if (username || password) {
|
|
headers.authorization = "Basic " + Buffer.from(String(username ?? "") + ":" + String(password ?? "")).toString("base64");
|
|
}
|
|
}
|
|
|
|
const body = method === "GET" || method === "DELETE" ? undefined : JSON.stringify(args.params);
|
|
const response = await fetch(url, { method, headers, body });
|
|
const text = await response.text();
|
|
if (!response.ok) {
|
|
process.stderr.write(text || \`HTTP \${response.status}\\n\`);
|
|
process.exit(1);
|
|
}
|
|
process.stdout.write(text);
|
|
})().catch((error) => {
|
|
process.stderr.write(String(error?.message ?? error));
|
|
process.exit(1);
|
|
});
|
|
`;
|
|
}
|
|
|
|
export async function generateCli({ draft, outDir }: GenerateCliInput): Promise<GeneratedCliArtifact> {
|
|
await mkdir(outDir, { recursive: true });
|
|
const binPath = join(outDir, `${draft.slug}.mjs`);
|
|
await writeFile(binPath, buildScript(JSON.stringify(draft)), "utf8");
|
|
|
|
if (process.platform !== "win32") {
|
|
await chmod(binPath, 0o755);
|
|
}
|
|
|
|
return {
|
|
draftId: draft.id,
|
|
slug: draft.slug,
|
|
binPath,
|
|
entrypoint: "node",
|
|
generatedAt: new Date().toISOString(),
|
|
};
|
|
}
|