diff --git a/src/_template.ts b/src/_template.ts index fe383b1..4079620 100644 --- a/src/_template.ts +++ b/src/_template.ts @@ -97,7 +97,7 @@ export class ApiClient { } } - // apigen:modules + // apigen:clientMembers } // apigen:types diff --git a/src/config.ts b/src/config.ts index b7414c3..808f99b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,6 +10,7 @@ export type Config = { source: string output: string | null name: string + namespacing: boolean parseDates: boolean inlineEnums: boolean resolveName?: (ctx: Context, op: OpConfig, proposal: OpName) => OpName | undefined @@ -24,6 +25,7 @@ export const initCtx = (config?: Partial): Context => { output: "", name: "ApiClient", doc: { openapi: "3.1.0" }, + namespacing: true, parseDates: false, inlineEnums: false, headers: {}, @@ -54,6 +56,11 @@ export const getCliConfig = () => { description: "API class name to export", default: "ApiClient", }, + noNamespacing: { + type: Boolean, + description: "disable namespacing of generated methods based on the first tag", + default: false, + }, parseDates: { type: Boolean, description: "Parse dates as Date objects", @@ -78,6 +85,7 @@ export const getCliConfig = () => { source: argv._.source, output: argv._.output ?? null, name: argv.flags.name, + namespacing: !argv.flags.noNamespacing, parseDates: argv.flags.parseDates, inlineEnums: argv.flags.inlineEnums, headers: parseHeaders(argv.flags.header), diff --git a/src/generator.ts b/src/generator.ts index 32f99d0..63317c5 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -25,7 +25,9 @@ const normalizeOpName = (val: string) => { } export const getOpName = (ctx: Context, op: OpConfig) => { - let ns = normalizeOpName(filterEmpty(op.tags ?? [])[0] ?? "general") + let ns + if (ctx.namespacing) ns = normalizeOpName(filterEmpty(op.tags ?? [])[0] ?? "general") + else ns = "" let fn = op.operationId ?? null // if not opId, try to make it from path @@ -78,7 +80,7 @@ export const prepareUrl = (url: string, rename: Record) => { ) } -const prepareOp = (ctx: Context, cfg: OpConfig, opName: string) => { +const prepareOp = (ctx: Context, cfg: OpConfig, opName: string): [ts.Identifier, ts.Expression] => { cfg.parameters = cfg.parameters ?? [] const reqSchema = getReqSchema(ctx, cfg) @@ -126,7 +128,7 @@ const prepareOp = (ctx: Context, cfg: OpConfig, opName: string) => { : undefined, ]) - return f.createPropertyAssignment( + return [ f.createIdentifier(normalizeIdentifier(opName)), f.createArrowFunction( undefined, @@ -148,21 +150,23 @@ const prepareOp = (ctx: Context, cfg: OpConfig, opName: string) => { ), ]), ), - ) + ] } -const prepareNs = (ctx: Context, name: string, handlers: ts.PropertyAssignment[]) => { +const prepareNs = (ctx: Context, name: string, handlers: [ts.Identifier, ts.Expression][]) => { return f.createPropertyDeclaration( undefined, normalizeIdentifier(name), undefined, undefined, - f.createObjectLiteralExpression(handlers), + f.createObjectLiteralExpression( + handlers.map(([name, expr]) => f.createPropertyAssignment(name, expr)), + ), ) } const prepareRoutes = async (ctx: Context) => { - const routes: Record = {} + const routes: Record = {} for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) { ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}` @@ -223,12 +227,18 @@ const prepareTypes = async (ctx: Context) => { export const generateAst = async (ctx: Context) => { const types = await prepareTypes(ctx) const routes = await prepareRoutes(ctx) - const modules: ts.PropertyDeclaration[] = [] - for (const [k, v] of Object.entries(routes)) { - modules.push(prepareNs(ctx, k, v)) + const clientMembers: ts.Node[] = [] + if (ctx.namespacing) { + for (const [k, v] of Object.entries(routes)) { + clientMembers.push(prepareNs(ctx, k, v)) + } + } else { + for (const [name, expr] of routes[""]) { + clientMembers.push(f.createPropertyDeclaration(undefined, name, undefined, undefined, expr)) + } } - return { modules, types } + return { clientMembers, types } } export const loadSchema = async ({ diff --git a/src/main.ts b/src/main.ts index bfdf13d..9b4fe60 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ import fs from "fs/promises" import { dirname, join } from "path" -import ts from "typescript" import { fileURLToPath } from "url" import { Config, initCtx } from "./config" import { generateAst, loadSchema } from "./generator" @@ -9,7 +8,7 @@ import { formatCode, printCode } from "./printer" export const apigen = async (config: Partial & Pick) => { const doc = await loadSchema({ url: config.source, headers: config.headers }) const ctx = initCtx({ ...config, doc }) - const { modules, types } = await generateAst(ctx) + const { clientMembers, types } = await generateAst(ctx) const filepath = join(dirname(fileURLToPath(import.meta.url)), "_template.ts") const file = await fs.readFile(filepath, "utf-8") @@ -27,8 +26,7 @@ export const apigen = async (config: Partial & Pick { + const printer = ts.createPrinter() + const sourceFile = ts.createSourceFile( + "temp.ts", + "", + ts.ScriptTarget.Latest, + false, + ts.ScriptKind.TS, + ) -const addNewLines = (nodes: T[]) => { - const result: T[] = [] - for (const node of nodes) { - result.push(node) - result.push(f.createIdentifier("\n") as unknown as T) - } - return result -} - -export const printCode = (nodes: ts.Statement[]) => { - return ts - .createPrinter() - .printFile( - f.createSourceFile( - addNewLines(nodes), - f.createToken(ts.SyntaxKind.EndOfFileToken), - ts.NodeFlags.None, - ), - ) + return nodes + .map((node) => printer.printNode(ts.EmitHint.Unspecified, node, sourceFile)) + .join("\n\n") .replaceAll("}, ", "},\n\n") } diff --git a/test/type-gen.test.ts b/test/type-gen.test.ts index c69a2e2..bd95d68 100644 --- a/test/type-gen.test.ts +++ b/test/type-gen.test.ts @@ -1,5 +1,4 @@ import { Oas3Schema } from "@redocly/openapi-core" -import ts from "typescript" import { test } from "uvu" import { equal } from "uvu/assert" import { initCtx } from "../src/config" @@ -16,7 +15,7 @@ test("type inline", async () => { const t = (l: OAS3, r: string, cfg?: Cfg) => { const ctx = initCtx({ ...cfg }) const res = makeType(ctx, l) - const txt = printCode([res as unknown as ts.Statement]) + const txt = printCode([res]) .replace(/"(\w+)"(\??):/g, "$1$2:") .replaceAll("\n", " ") .replace(/ +/g, " ") @@ -147,7 +146,7 @@ test("type alias", async () => { const t = (l: Oas3Schema & { name?: string }, r: string, cfg?: Cfg) => { const ctx = initCtx({ ...cfg }) const res = makeTypeAlias(ctx, l.name ?? "t", l) - const txt = printCode([res as unknown as ts.Statement]) + const txt = printCode([res]) .replace(";", "") .replace("export ", "") .replaceAll("\n", " ") diff --git a/test/url-gen.test.ts b/test/url-gen.test.ts index f0da1c5..36b629f 100644 --- a/test/url-gen.test.ts +++ b/test/url-gen.test.ts @@ -1,5 +1,4 @@ import { trim } from "lodash-es" -import ts from "typescript" import { test } from "uvu" import { equal } from "uvu/assert" import { prepareUrl } from "../src/generator" @@ -7,7 +6,7 @@ import { printCode } from "../src/printer" test("url template", async () => { const t = async (url: string, replacements: Record) => { - const code = printCode([prepareUrl(url, replacements) as unknown as ts.Statement]) + const code = printCode([prepareUrl(url, replacements)]) return trim(trim(code.trim(), ";"), '`"') }