/
Alex_Ural
/
opencode
Обзор
Документация
Войти
/
Alex_Ural
/
opencode
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
packages/cli/src/commands/handlers/api.ts
85 строк
3 KB
Dax Raad
feat(cli): add API request command
24 июн 2026, 06:35
24 июн 2026, 06:35
784eea3
Код
Авторство
О чём код?
import { EOL } from "node:os" import { Effect, Option } from "effect" import { Commands } from "../commands" import { Runtime } from "../../framework/runtime" import { Daemon } from "../../services/daemon" const methods = new Set(["delete", "get", "head", "options", "patch", "post", "put"]) type Operation = { operationId?: string } type OpenApi = { paths?: Record<string, Record<string, Operation>> } export default Runtime.handler( Commands.commands.api, Effect.fn("cli.api")(function* (input) { const daemon = yield* Daemon.Service const transport = yield* daemon.transport() const params = Option.getOrElse(input.param, () => ({})) const request = yield* resolveRequest(transport, input.request, params) const headers = new Headers(transport.headers) for (const header of input.header) { const index = header.indexOf(":") if (index < 1) return yield* Effect.fail(new Error(`Invalid header, expected name:value: ${header}`)) headers.set(header.slice(0, index).trim(), header.slice(index + 1).trim()) } const body = Option.getOrUndefined(input.data) if (body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json") const response = yield* Effect.tryPromise(() => fetch(new URL(request.path, transport.url), { method: request.method, headers, body, }), ) const output = yield* Effect.promise(() => response.text()) if (output) process.stdout.write(output + (output.endsWith(EOL) ? "" : EOL)) }), ) export function resolveOperation(spec: OpenApi, operationID: string, params: Record<string, string>) { for (const [path, operations] of Object.entries(spec.paths ?? {})) { for (const [method, operation] of Object.entries(operations)) { if (!methods.has(method) || operation.operationId !== operationID) continue return { method: method.toUpperCase(), path: interpolate(path, params) } } } throw new Error(`Operation not found: ${operationID}`) } export function rawRequest(input: readonly string[]) { if (input.length !== 2 || !methods.has(input[0].toLowerCase()) || !input[1].startsWith("/")) return return { method: input[0].toUpperCase(), path: input[1] } } function resolveRequest( transport: { url: string; headers: RequestInit["headers"] }, input: readonly string[], params: Record<string, string>, ) { const raw = rawRequest(input) if (raw) return Effect.succeed(raw) if (input.length !== 1) return Effect.fail(new Error("Expected an operation name or an HTTP method and path")) return Effect.tryPromise(async () => { const response = await fetch(new URL("/openapi.json", transport.url), { headers: transport.headers }) if (!response.ok) throw new Error(`Failed to load OpenAPI document: HTTP ${response.status}`) return resolveOperation((await response.json()) as OpenApi, input[0], params) }) } function interpolate(path: string, params: Record<string, string>) { const used = new Set<string>() const pathname = path.replaceAll(/\{([^}]+)\}/g, (_, name: string) => { const value = params[name] if (value === undefined) throw new Error(`Missing path parameter: ${name}`) used.add(name) return encodeURIComponent(value) }) const query = new URLSearchParams(Object.entries(params).filter(([name]) => !used.has(name))).toString() return query ? `${pathname}?${query}` : pathname }