117 lines
3.9 KiB
TypeScript
117 lines
3.9 KiB
TypeScript
/**
|
|
* Command-line entry point for generating Effect HTTP clients or HttpApi
|
|
* definitions from an OpenAPI specification.
|
|
*
|
|
* The CLI reads a spec file, optionally applies JSON patches in order, selects
|
|
* the generator layer for the requested output format, reports generation
|
|
* warnings to stderr, and writes the generated source to stdout.
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
import * as Console from "effect/Console"
|
|
import * as Effect from "effect/Effect"
|
|
import type * as Schema from "effect/Schema"
|
|
import * as CliError from "effect/unstable/cli/CliError"
|
|
import * as Command from "effect/unstable/cli/Command"
|
|
import * as Flag from "effect/unstable/cli/Flag"
|
|
import type { OpenAPISpec } from "effect/unstable/httpapi/OpenApi"
|
|
import * as OpenApiGenerator from "./OpenApiGenerator.ts"
|
|
import * as OpenApiPatch from "./OpenApiPatch.ts"
|
|
|
|
const spec = Flag.fileParse("spec").pipe(
|
|
Flag.withAlias("s"),
|
|
Flag.withDescription("The OpenAPI spec file to generate output from")
|
|
)
|
|
|
|
const name = Flag.string("name").pipe(
|
|
Flag.withAlias("n"),
|
|
Flag.withDescription("The name of the generated output"),
|
|
Flag.withDefault("Client")
|
|
)
|
|
|
|
const format = Flag.choice("format", ["httpclient", "httpclient-type-only", "httpapi"] as const).pipe(
|
|
Flag.withAlias("f"),
|
|
Flag.withDescription(
|
|
"Output format to generate: httpclient | httpclient-type-only | httpapi (default: httpclient)"
|
|
),
|
|
Flag.withDefault("httpclient")
|
|
)
|
|
|
|
const patch = Flag.string("patch").pipe(
|
|
Flag.withAlias("p"),
|
|
Flag.withDescription(
|
|
"JSON patch to apply to OpenAPI spec before generation. " +
|
|
"Can be a file path (.json, .yaml, .yml) or inline JSON array. " +
|
|
"Multiple patches are applied in order."
|
|
),
|
|
Flag.between(0, Infinity)
|
|
)
|
|
|
|
const root = Command.make("openapigen", { spec, format, name, patch }).pipe(
|
|
Command.withHandler(Effect.fnUntraced(function*({ name, spec, format, patch }) {
|
|
let patchedSpec: Schema.Json = spec as Schema.Json
|
|
|
|
if (patch.length > 0) {
|
|
const parsedPatches = yield* Effect.forEach(
|
|
patch,
|
|
(input) =>
|
|
OpenApiPatch.parsePatchInput(input).pipe(
|
|
Effect.map((p) => ({ source: input, patch: p })),
|
|
Effect.mapError((error) => new CliError.UserError({ cause: error }))
|
|
)
|
|
)
|
|
patchedSpec = yield* OpenApiPatch.applyPatches(parsedPatches, patchedSpec).pipe(
|
|
Effect.mapError((error) => new CliError.UserError({ cause: error }))
|
|
)
|
|
}
|
|
|
|
const generator = yield* OpenApiGenerator.OpenApiGenerator
|
|
const warnings: Array<OpenApiGenerator.OpenApiGeneratorWarning> = []
|
|
const source = yield* generator.generate(patchedSpec as unknown as OpenAPISpec, {
|
|
name,
|
|
format,
|
|
onWarning: (warning) => {
|
|
warnings.push(warning)
|
|
}
|
|
})
|
|
yield* Effect.forEach(
|
|
warnings,
|
|
(warning) => Console.error(formatWarning(warning)),
|
|
{ discard: true }
|
|
)
|
|
return yield* Console.log(source)
|
|
})),
|
|
Command.provide(({ format }) =>
|
|
format === "httpclient-type-only"
|
|
? OpenApiGenerator.layerTransformerTs
|
|
: OpenApiGenerator.layerTransformerSchema
|
|
)
|
|
)
|
|
|
|
/**
|
|
* Runs the OpenAPI generator command-line program.
|
|
*
|
|
* **Details**
|
|
*
|
|
* The command reads an OpenAPI specification, optionally applies JSON patches,
|
|
* generates source code in the selected format, writes any generation warnings
|
|
* to stderr, and prints the generated source to stdout.
|
|
*
|
|
* @category running
|
|
* @since 4.0.0
|
|
*/
|
|
export const run: Effect.Effect<void, CliError.CliError, Command.Environment> = Command.run(root, {
|
|
version: "0.0.0"
|
|
})
|
|
|
|
const formatWarning = (warning: OpenApiGenerator.OpenApiGeneratorWarning): string => {
|
|
const context = [
|
|
warning.method?.toUpperCase(),
|
|
warning.path,
|
|
warning.operationId ? `(${warning.operationId})` : undefined
|
|
].filter((value): value is string => value !== undefined)
|
|
return context.length > 0
|
|
? `WARNING [${warning.code}] ${context.join(" ")}: ${warning.message}`
|
|
: `WARNING [${warning.code}] ${warning.message}`
|
|
}
|