From a5e000915b85e2567e98773b18ac36f2ec01dae7 Mon Sep 17 00:00:00 2001 From: Rohin Bhargava Date: Tue, 10 Dec 2024 13:54:57 -0500 Subject: [PATCH] feat(cli): add new openapi-fdr command for direct openapi - fdr parsing (#5292) * added new openapi-fdr command * update import paths * address comments * remove erroneous async * remove fdr dep and install inlined parser package --------- Co-authored-by: fern-bot --- packages/cli/cli/package.json | 4 +- packages/cli/cli/src/cli.ts | 34 +++++--- ...eOpenApiToFdrApiDefinitionForWorkspaces.ts | 81 +++++++++++++++++++ pnpm-lock.yaml | 64 +++++++++++++++ 4 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 packages/cli/cli/src/commands/generate-openapi-fdr/generateOpenApiToFdrApiDefinitionForWorkspaces.ts diff --git a/packages/cli/cli/package.json b/packages/cli/cli/package.json index d262e217fa3..5b25a9f2d41 100644 --- a/packages/cli/cli/package.json +++ b/packages/cli/cli/package.json @@ -46,9 +46,10 @@ "@fern-api/configuration-loader": "workspace:*", "@fern-api/core": "workspace:*", "@fern-api/core-utils": "workspace:*", + "@fern-api/docs-parsers": "^0.0.10", "@fern-api/docs-preview": "workspace:*", "@fern-api/docs-resolver": "workspace:*", - "@fern-api/docs-validator": "workspace:*", + "@fern-api/docs-validator": "workspace:*", "@fern-api/fern-definition-formatter": "workspace:*", "@fern-api/fern-definition-schema": "workspace:*", "@fern-api/fern-definition-validator": "workspace:*", @@ -105,6 +106,7 @@ "js-yaml": "^4.1.0", "latest-version": "^9.0.0", "lodash-es": "^4.17.21", + "openapi-types": "^12.1.3", "ora": "^7.0.1", "organize-imports-cli": "^0.10.0", "prettier": "^2.7.1", diff --git a/packages/cli/cli/src/cli.ts b/packages/cli/cli/src/cli.ts index 7e2329b4371..a015859b267 100644 --- a/packages/cli/cli/src/cli.ts +++ b/packages/cli/cli/src/cli.ts @@ -47,6 +47,7 @@ import { generateJsonschemaForWorkspaces } from "./commands/jsonschema/generateJ import { generateDynamicIrForWorkspaces } from "./commands/generate-dynamic-ir/generateDynamicIrForWorkspaces"; import { writeDocsDefinitionForProject } from "./commands/write-docs-definition/writeDocsDefinitionForProject"; import { RUNTIME } from "@fern-typescript/fetcher"; +import { generateOpenApiToFdrApiDefinitionForWorkspaces } from "./commands/generate-openapi-fdr/generateOpenApiToFdrApiDefinitionForWorkspaces"; void runCli(); @@ -611,17 +612,32 @@ function addFdrCommand(cli: Argv, cliContext: CliContext) { string: true, default: new Array(), description: "Filter the FDR API definition for certain audiences" + }) + .option("v2", { + boolean: true, + description: "Use v2 format" }), async (argv) => { - await generateFdrApiDefinitionForWorkspaces({ - project: await loadProjectAndRegisterWorkspacesWithContext(cliContext, { - commandLineApiWorkspace: argv.api, - defaultToAllApiWorkspaces: false - }), - outputFilepath: resolve(cwd(), argv.pathToOutput), - cliContext, - audiences: argv.audience.length > 0 ? { type: "select", audiences: argv.audience } : { type: "all" } - }); + if (argv.v2) { + await generateOpenApiToFdrApiDefinitionForWorkspaces({ + project: await loadProjectAndRegisterWorkspacesWithContext(cliContext, { + commandLineApiWorkspace: argv.api, + defaultToAllApiWorkspaces: false + }), + outputFilepath: resolve(cwd(), argv.pathToOutput), + cliContext + }); + } else { + await generateFdrApiDefinitionForWorkspaces({ + project: await loadProjectAndRegisterWorkspacesWithContext(cliContext, { + commandLineApiWorkspace: argv.api, + defaultToAllApiWorkspaces: false + }), + outputFilepath: resolve(cwd(), argv.pathToOutput), + cliContext, + audiences: argv.audience.length > 0 ? { type: "select", audiences: argv.audience } : { type: "all" } + }); + } } ); } diff --git a/packages/cli/cli/src/commands/generate-openapi-fdr/generateOpenApiToFdrApiDefinitionForWorkspaces.ts b/packages/cli/cli/src/commands/generate-openapi-fdr/generateOpenApiToFdrApiDefinitionForWorkspaces.ts new file mode 100644 index 00000000000..e6ad2db29b9 --- /dev/null +++ b/packages/cli/cli/src/commands/generate-openapi-fdr/generateOpenApiToFdrApiDefinitionForWorkspaces.ts @@ -0,0 +1,81 @@ +import { AbsoluteFilePath, stringifyLargeObject } from "@fern-api/fs-utils"; +import { Project } from "@fern-api/project-loader"; +import { writeFile } from "fs/promises"; +import path from "path"; +import { CliContext } from "../../cli-context/CliContext"; +import { getAllOpenAPISpecs, LazyFernWorkspace, OSSWorkspace } from "@fern-api/lazy-fern-workspace"; +// TODO: clean up imports +import { OpenApiDocumentConverterNode } from "@fern-api/docs-parsers"; +import { ErrorCollector } from "@fern-api/docs-parsers"; +import fs from "fs"; +import yaml from "js-yaml"; +import { BaseOpenApiV3_1ConverterNodeContext } from "@fern-api/docs-parsers"; +import { OpenAPIV3_1 } from "openapi-types"; + +export async function generateOpenApiToFdrApiDefinitionForWorkspaces({ + project, + outputFilepath, + cliContext +}: { + project: Project; + outputFilepath: AbsoluteFilePath; + cliContext: CliContext; +}): Promise { + await Promise.all( + project.apiWorkspaces.map(async (workspace) => { + await cliContext.runTaskForWorkspace(workspace, async (context) => { + await cliContext.runTaskForWorkspace(workspace, async (context) => { + if (workspace instanceof LazyFernWorkspace) { + context.logger.info("Skipping, API is specified as a Fern Definition."); + return; + } else if (!(workspace instanceof OSSWorkspace)) { + return; + } + const openApiSpecs = await getAllOpenAPISpecs({ context, specs: workspace.specs }); + + if (openApiSpecs.length === 0) { + context.logger.error("No OpenAPI specs found in the workspace"); + return; + } + + if (openApiSpecs.length > 1) { + context.logger.error("Found multiple OpenAPI specs in the workspace."); + return; + } + + const openApi = openApiSpecs[0]; + + if (openApi != null) { + const input = yaml.load( + fs.readFileSync(openApi.absoluteFilepath, "utf8") + ) as OpenAPIV3_1.Document; + + const oasContext: BaseOpenApiV3_1ConverterNodeContext = { + document: input, + logger: context.logger, + errors: new ErrorCollector() + }; + + const openApiFdrJson = new OpenApiDocumentConverterNode({ + input, + context: oasContext, + accessPath: [], + pathId: workspace.workspaceName ?? "openapi parser" + }); + + const fdrApiDefinition = openApiFdrJson.convert(); + + const resolvedOutputFilePath = path.resolve(outputFilepath); + await writeFile( + resolvedOutputFilePath, + await stringifyLargeObject(fdrApiDefinition, { pretty: true }) + ); + context.logger.info(`Wrote FDR API definition to ${resolvedOutputFilePath}`); + } else { + context.logger.error("No OpenAPI spec found in the workspace"); + } + }); + }); + }) + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78dc0d32efc..591cc31bb7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3792,6 +3792,9 @@ importers: '@fern-api/core-utils': specifier: workspace:* version: link:../../commons/core-utils + '@fern-api/docs-parsers': + specifier: ^0.0.10 + version: 0.0.10(@types/node@18.7.18)(jsdom@20.0.3)(sass@1.72.0)(terser@5.31.5)(typescript@4.6.4) '@fern-api/docs-preview': specifier: workspace:* version: link:../docs-preview @@ -3969,6 +3972,9 @@ importers: lodash-es: specifier: ^4.17.21 version: 4.17.21 + openapi-types: + specifier: ^12.1.3 + version: 12.1.3 ora: specifier: ^7.0.1 version: 7.0.1 @@ -4156,6 +4162,8 @@ importers: specifier: ^2.1.4 version: 2.1.4(@types/node@18.7.18)(jsdom@20.0.3)(sass@1.72.0)(terser@5.31.5) + packages/cli/cli/dist/local: {} + packages/cli/configuration: dependencies: '@fern-api/core-utils': @@ -7977,15 +7985,27 @@ packages: '@exodus/schemasafe@1.0.0': resolution: {integrity: sha512-2cyupPIZI69HQxEAPllLXBjQp4njDKkOjYRCYxvMZe3/LY9pp9fBM3Tb1wiFAdP6Emo4v3OEbCLGj6u73Q5KLw==} + '@fern-api/core-utils@0.4.24-rc1': + resolution: {integrity: sha512-aYu4lQK2qZIKzTF9TeFrICTPJ/zGEZUEWQmZt6pJeHu+R6afrcCBNkUleWU1OpHlDbe+xXUUBOktRg0PM9Hywg==} + + '@fern-api/docs-parsers@0.0.10': + resolution: {integrity: sha512-TO+XxWDSEq2PIirkNTWdsIX5zTrQMjVg+sdTmdTIICpQVAboupC0bG6IfAU+D933VYoy4ajrkqZbCYQSUjMOhg==} + '@fern-api/fdr-sdk@0.126.1-444264056': resolution: {integrity: sha512-Xl1Ctteav1GOulX40756FLEoJAI7VCn6zgkdDb6RRSZhm9Z8fjaBRv/cdMo1saupqHNd62Hm5b8FdmGjwWFAPw==} + '@fern-api/logger@0.4.24-rc1': + resolution: {integrity: sha512-yh0E2F3K3IPnJZcE4dv+u8I51iKgTgv/reinKo4K5YmYEG1iLtw5vBEYMOPkQmsYFPAKIh++OMB/6TrsahMWew==} + '@fern-api/sdk@0.12.3': resolution: {integrity: sha512-sexxpQEHBmsAXW3UHo5x7NW8Z9TrHWYLGo/J7eFqHYSH8Sq9LA3vRlR0ni9h4FNlKnPDu9Y55L0BhsQVXYqPKA==} '@fern-api/template-resolver@0.7.5': resolution: {integrity: sha512-hmvSxuhyWMjn1DxJJS/R+CIaZcF5c+UBEyFqFOw+laoBtdDvNgEyCu7XP5cILVMABPHmt7kkIO5KbrsCS5S2kw==} + '@fern-api/ui-core-utils@0.0.0': + resolution: {integrity: sha512-8T3YLd+n8z5Vs+WNRIwH6PUW31ZC4/lkRD5G2+qyBcdePfOVYV3CHp3eiUrSSArOr0SJmzN/mQwPm3iAaey7nw==} + '@fern-api/ui-core-utils@0.126.1-444264056': resolution: {integrity: sha512-9L3Tgl83nGaw9Jug17ZXSkPL7ZTeOP3SukG/Fz2Y2Aa/GqEToCrexuGkTO090nwuv2zO9gi7CYDHvQOEl5IIMQ==} @@ -15059,6 +15079,39 @@ snapshots: '@exodus/schemasafe@1.0.0': {} + '@fern-api/core-utils@0.4.24-rc1': + dependencies: + lodash-es: 4.17.21 + strip-ansi: 7.1.0 + + '@fern-api/docs-parsers@0.0.10(@types/node@18.7.18)(jsdom@20.0.3)(sass@1.72.0)(terser@5.31.5)(typescript@4.6.4)': + dependencies: + '@fern-api/logger': 0.4.24-rc1 + '@fern-api/ui-core-utils': 0.0.0 + es-toolkit: 1.26.0 + openapi-types: 12.1.3 + ts-essentials: 10.0.1(typescript@4.6.4) + uuid: 9.0.1 + vitest: 2.1.4(@types/node@18.7.18)(jsdom@20.0.3)(sass@1.72.0)(terser@5.31.5) + whatwg-mimetype: 4.0.0 + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@types/node' + - '@vitest/browser' + - '@vitest/ui' + - happy-dom + - jsdom + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - typescript + '@fern-api/fdr-sdk@0.126.1-444264056(typescript@4.6.4)': dependencies: '@fern-api/ui-core-utils': 0.126.1-444264056 @@ -15079,6 +15132,11 @@ snapshots: - encoding - typescript + '@fern-api/logger@0.4.24-rc1': + dependencies: + '@fern-api/core-utils': 0.4.24-rc1 + chalk: 5.3.0 + '@fern-api/sdk@0.12.3': dependencies: '@fern-api/template-resolver': 0.7.5 @@ -15095,6 +15153,12 @@ snapshots: dependencies: prettier: 3.3.3 + '@fern-api/ui-core-utils@0.0.0': + dependencies: + strip-ansi: 7.1.0 + title: 3.5.3 + ua-parser-js: 1.0.37 + '@fern-api/ui-core-utils@0.126.1-444264056': dependencies: strip-ansi: 7.1.0