diff --git a/.eslintignore b/.eslintignore index 082fa5d6..0e3ae983 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,6 +6,6 @@ dist tmp jest.config.js website -playground/__gen__ +playground test/codegen/generators/*/output mcp-server \ No newline at end of file diff --git a/src/browser/generate.ts b/src/browser/generate.ts index 27c68b7c..870b9f61 100644 --- a/src/browser/generate.ts +++ b/src/browser/generate.ts @@ -3,8 +3,6 @@ * Uses the shared rendering pipeline for in-memory code generation. * No filesystem I/O - returns generated files as data. */ -import {parse as parseYaml} from 'yaml'; -import {parse, dereference} from '@readme/openapi-parser'; import {OpenAPIV2, OpenAPIV3, OpenAPIV3_1} from 'openapi-types'; // Use the shim's interface for browser compatibility, cast to any when passing to generators // that expect @asyncapi/parser.AsyncAPIDocumentInterface @@ -12,6 +10,7 @@ import {AsyncAPIDocumentInterface} from './shims/asyncapi-parser'; import type {AsyncAPIDocumentInterface as NodeAsyncAPIDocumentInterface} from '@asyncapi/parser'; import {CodegenError} from '../codegen/errors'; import {loadAsyncapiFromMemoryBrowser} from './parser'; +import {loadOpenapiFromMemory} from '../codegen/inputs/openapi'; import { loadJsonSchemaFromMemory, JsonSchemaDocument @@ -99,7 +98,9 @@ export async function generate( case 'openapi': try { - openapiDocument = await parseOpenAPIFromMemory(input.spec); + // Shared with the Node in-memory loader so the browser playground + // gets the same normalization (incl. reflectComponentSchemaNames). + openapiDocument = await loadOpenapiFromMemory(input.spec); } catch (error) { errors.push( `Failed to parse OpenAPI spec: ${error instanceof Error ? error.message : String(error)}` @@ -110,10 +111,7 @@ export async function generate( case 'jsonschema': try { - const parsed = parseSpecString(input.spec); - jsonSchemaDocument = loadJsonSchemaFromMemory( - parsed as JsonSchemaDocument - ); + jsonSchemaDocument = loadJsonSchemaFromMemory(input.spec); } catch (error) { errors.push( `Failed to parse JSON Schema: ${error instanceof Error ? error.message : String(error)}` @@ -160,28 +158,3 @@ export async function generate( return {files, errors}; } - -/** - * Parse an OpenAPI spec from a string. - */ -async function parseOpenAPIFromMemory( - specString: string -): Promise { - const document = parseSpecString(specString); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const parsedDocument = await parse(document as any); - return await dereference(parsedDocument); -} - -/** - * Parse a spec string (YAML or JSON) into an object. - */ -function parseSpecString(specString: string): unknown { - // Try JSON first - try { - return JSON.parse(specString); - } catch { - // Fall back to YAML - return parseYaml(specString); - } -} diff --git a/src/codegen/configurations.ts b/src/codegen/configurations.ts index b1d3a03f..e9894093 100644 --- a/src/codegen/configurations.ts +++ b/src/codegen/configurations.ts @@ -25,9 +25,9 @@ import { includeTypeScriptClientDependencies } from './generators/typescript/client'; import path from 'path'; -import {loadAsyncapi} from './inputs/asyncapi'; -import {loadOpenapi} from './inputs/openapi'; -import {loadJsonSchema} from './inputs/jsonschema'; +import {loadAsyncapi, loadAsyncapiFromMemory} from './inputs/asyncapi'; +import {loadOpenapi, loadOpenapiFromMemory} from './inputs/openapi'; +import {loadJsonSchema, loadJsonSchemaFromMemory} from './inputs/jsonschema'; import { defaultTypeScriptHeadersOptions, defaultTypeScriptTypesOptions @@ -315,3 +315,40 @@ export async function realizeGeneratorContext( return context; } + +/** + * In-memory counterpart of {@link realizeGeneratorContext}. Given a config + * object and the raw specification string, it produces a fully-loaded + * `RunGeneratorContext` using the same per-input loaders as the file flow — + * so callers that hold a spec in memory (the platform's `generateInMemory`, + * preview, the playground) generate identical output to `codegen generate`. + * This is the single seam for in-memory generation: parsing/normalization + * lives here, never in downstream consumers. + */ +export async function realizeInMemoryGeneratorContext({ + configuration, + specificationDocument +}: { + configuration: TheCodegenConfiguration; + specificationDocument: string; +}): Promise { + const config = realizeConfiguration(configuration); + const context: RunGeneratorContext = { + configuration: config, + // Root-level virtual paths (matching the browser in-memory path) so + // generated output paths resolve identically regardless of caller cwd. + documentPath: '/virtual-spec', + configFilePath: '/virtual-config.mjs' + }; + if (config.inputType === 'asyncapi') { + context.asyncapiDocument = + await loadAsyncapiFromMemory(specificationDocument); + } else if (config.inputType === 'openapi') { + context.openapiDocument = + await loadOpenapiFromMemory(specificationDocument); + } else if (config.inputType === 'jsonschema') { + context.jsonSchemaDocument = + loadJsonSchemaFromMemory(specificationDocument); + } + return context; +} diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 5f28cd6f..32244476 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -1,6 +1,6 @@ -export {loadAsyncapi} from './inputs/asyncapi'; -export {loadOpenapi} from './inputs/openapi'; -export {loadJsonSchema} from './inputs/jsonschema'; +export {loadAsyncapi, loadAsyncapiFromMemory} from './inputs/asyncapi'; +export {loadOpenapi, loadOpenapiFromMemory} from './inputs/openapi'; +export {loadJsonSchema, loadJsonSchemaFromMemory} from './inputs/jsonschema'; export { defaultTypeScriptChannelsGenerator, @@ -52,7 +52,8 @@ export { realizeConfiguration, loadAndRealizeConfigFile, loadConfigFile, - realizeGeneratorContext + realizeGeneratorContext, + realizeInMemoryGeneratorContext } from './configurations'; export {renderGenerator, determineRenderGraph, renderGraph} from './renderer'; diff --git a/src/codegen/inputs/jsonschema/parser.ts b/src/codegen/inputs/jsonschema/parser.ts index 274b08fc..2f9e50d1 100644 --- a/src/codegen/inputs/jsonschema/parser.ts +++ b/src/codegen/inputs/jsonschema/parser.ts @@ -110,14 +110,18 @@ function parseDocument( * Load JSON Schema document from memory. */ export function loadJsonSchemaFromMemory( - document: JsonSchemaDocument, + document: string | JsonSchemaDocument, documentPath?: string ): JsonSchemaDocument { const path = documentPath || 'memory'; Logger.verbose(`Loading JSON Schema document from ${path}`); - validateJsonSchemaDocument(document, path); - return document; + const parsed = + typeof document === 'string' + ? (parseDocument(document, path, null) as JsonSchemaDocument) + : document; + validateJsonSchemaDocument(parsed, path); + return parsed; } /** diff --git a/src/codegen/inputs/openapi/index.ts b/src/codegen/inputs/openapi/index.ts index 3ef9d05b..5fb8453e 100644 --- a/src/codegen/inputs/openapi/index.ts +++ b/src/codegen/inputs/openapi/index.ts @@ -1,2 +1,2 @@ -export {loadOpenapi, loadOpenapiDocument} from './parser'; +export {loadOpenapi, loadOpenapiDocument, loadOpenapiFromMemory} from './parser'; export {processOpenAPIPayloads} from './generators/payloads'; diff --git a/src/codegen/inputs/openapi/parser.ts b/src/codegen/inputs/openapi/parser.ts index 1adf0631..f6ec8860 100644 --- a/src/codegen/inputs/openapi/parser.ts +++ b/src/codegen/inputs/openapi/parser.ts @@ -95,6 +95,26 @@ export async function loadOpenapiDocument( } } +/** + * Load and normalize an OpenAPI document already held in memory (as a raw + * JSON/YAML string). Runs the exact same normalization as the local-file path + * of {@link loadOpenapiDocument} — dereference followed by + * {@link reflectComponentSchemaNames} — so in-memory callers (the platform's + * `generateInMemory`, preview, and the playground) produce byte-identical + * output to `codegen generate` on a file. + */ +export async function loadOpenapiFromMemory( + specString: string +): Promise { + const document = parseDocumentContent(specString, 'memory', null); + const parsedDocument = await parse(document); + const {dereference} = await import('@readme/openapi-parser'); + const dereferenced = await dereference(parsedDocument); + reflectComponentSchemaNames(dereferenced); + Logger.debug(`OpenAPI document loaded and dereferenced from memory`); + return dereferenced; +} + function parseDocumentContent( content: string, documentPath: string, diff --git a/src/index.ts b/src/index.ts index 789b3b4c..3bae4c92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,10 @@ export { loadAndRealizeConfigFile, loadConfigFile, realizeGeneratorContext, + realizeInMemoryGeneratorContext, + loadOpenapiFromMemory, + loadAsyncapiFromMemory, + loadJsonSchemaFromMemory, ChannelFunctionTypes } from './codegen';