#!/usr/bin/env node import * as NodeRuntime from "@effect/platform-node/NodeRuntime";import * as NodeServices from "@effect/platform-node/NodeServices";import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonSchemaGenerator";import { getUrlDiagnostics } from "@t3tools/shared/urlDiagnostics";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Logger from "effect/Logger";import * as Path from "effect/Path";import * as Schema from "effect/Schema";import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse,} from "effect/unstable/http";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; const UPSTREAM_REF = "b39f943a634a6e7ba86c3d6e8cf6d5f35e612566";const USER_AGENT = "effect-codex-app-server-generator";const GITHUB_API_BASE = "https://api.github.com/repos/openai/codex/contents/codex-rs/app-server-protocol"; const GithubContentEntries = Schema.Array( Schema.Struct({ name: Schema.String, path: Schema.String, download_url: Schema.NullOr(Schema.String), type: Schema.String, }),);type GithubContentEntry = (typeof GithubContentEntries.Type)[number]; const JsonSchemaDocument = Schema.StructWithRest( Schema.Struct({ definitions: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json)), }), [Schema.Record(Schema.String, Schema.Json)],);const decodeGithubContentEntries = Schema.decodeEffect(Schema.fromJsonString(GithubContentEntries));const decodeJsonSchemaDocument = Schema.decodeEffect(Schema.fromJsonString(JsonSchemaDocument)); interface GeneratedPaths { readonly generatedDir: string; readonly schemaOutputPath: string; readonly metaOutputPath: string; readonly namespacesOutputPath: string;} interface MethodEntry { readonly method: string; readonly paramsType?: string;} interface JsonSchemaFile { readonly namespace?: string; readonly exportName: string; readonly fileName: string; readonly downloadUrl: string; readonly qualifiedName: string; readonly repositoryPath: string;} const urlDiagnosticsSchema = { urlInputLength: Schema.Number, urlProtocol: Schema.optionalKey(Schema.String), urlHostname: Schema.optionalKey(Schema.String),}; function urlDiagnosticFields(url: string) { const diagnostics = getUrlDiagnostics(url); return { urlInputLength: diagnostics.inputLength, ...(diagnostics.protocol === undefined ? {} : { urlProtocol: diagnostics.protocol }), ...(diagnostics.hostname === undefined ? {} : { urlHostname: diagnostics.hostname }), };} export class GeneratorFetchError extends Schema.TaggedErrorClass<GeneratorFetchError>()( "GeneratorFetchError", { ...urlDiagnosticsSchema, stage: Schema.Literals(["request", "read-response"]), cause: Schema.Defect(), },) { override get message(): string { const source = this.urlHostname === undefined ? "the configured source" : this.urlHostname; return `Failed to fetch a Codex generator input from ${source} during ${this.stage}.`; }} export class GeneratorDirectoryDecodeError extends Schema.TaggedErrorClass<GeneratorDirectoryDecodeError>()( "GeneratorDirectoryDecodeError", { directoryPath: Schema.String, ...urlDiagnosticsSchema, cause: Schema.Defect(), },) { override get message(): string { return `Failed to decode the GitHub directory listing for ${this.directoryPath}.`; }} export class GeneratorSchemaDocumentDecodeError extends Schema.TaggedErrorClass<GeneratorSchemaDocumentDecodeError>()( "GeneratorSchemaDocumentDecodeError", { repositoryPath: Schema.String, ...urlDiagnosticsSchema, cause: Schema.Defect(), },) { override get message(): string { return `Failed to decode the Codex schema document ${this.repositoryPath}.`; }} export class GeneratorFormatProcessError extends Schema.TaggedErrorClass<GeneratorFormatProcessError>()( "GeneratorFormatProcessError", { operation: Schema.Literals(["spawn", "wait-for-exit"]), command: Schema.String, argumentCount: Schema.Number, generatedDir: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Generator formatting command ${this.command} failed during ${this.operation} for ${this.generatedDir}.`; }} export class GeneratorFormatExitError extends Schema.TaggedErrorClass<GeneratorFormatExitError>()( "GeneratorFormatExitError", { command: Schema.String, argumentCount: Schema.Number, generatedDir: Schema.String, exitCode: Schema.Number, },) { override get message(): string { return `Generator formatting command ${this.command} exited with code ${this.exitCode} for ${this.generatedDir}.`; }} export class GeneratorSchemaValueDeclarationMissingError extends Schema.TaggedErrorClass<GeneratorSchemaValueDeclarationMissingError>()( "GeneratorSchemaValueDeclarationMissingError", { lineIndex: Schema.Number, typeDeclarationLength: Schema.Number, nextLinePresent: Schema.Boolean, nextLineLength: Schema.optional(Schema.Number), },) { override get message(): string { return `Generated schema type declaration at line ${this.lineIndex + 1} has no following value declaration.`; }} export class GeneratorSchemaNameParseError extends Schema.TaggedErrorClass<GeneratorSchemaNameParseError>()( "GeneratorSchemaNameParseError", { lineIndex: Schema.Number, typeDeclarationLength: Schema.Number, },) { override get message(): string { return `Could not extract a schema name from generated declaration at line ${this.lineIndex + 1}.`; }} export class GeneratorSchemaTypeResolutionError extends Schema.TaggedErrorClass<GeneratorSchemaTypeResolutionError>()( "GeneratorSchemaTypeResolutionError", { rawTypeName: Schema.String, candidates: Schema.Array(Schema.String), },) { override get message(): string { return `Unable to resolve schema type ${this.rawTypeName}; tried ${this.candidates.join(", ")}.`; }} export class GeneratorExternalReferenceResolutionError extends Schema.TaggedErrorClass<GeneratorExternalReferenceResolutionError>()( "GeneratorExternalReferenceResolutionError", { reference: Schema.String, currentNamespace: Schema.NullOr(Schema.String), candidates: Schema.Array(Schema.String), },) { override get message(): string { return `Unable to rewrite external reference ${this.reference} in namespace ${this.currentNamespace ?? "<root>"}; tried ${this.candidates.join(", ")}.`; }} const ManualSchemas: Record<string, Schema.Json> = { GetAuthStatusParams: { type: "object", title: "GetAuthStatusParams", properties: { includeToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, refreshToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, }, GetConversationSummaryParams: { title: "GetConversationSummaryParams", oneOf: [ { type: "object", properties: { rolloutPath: { type: "string" }, }, required: ["rolloutPath"], }, { type: "object", properties: { conversationId: { type: "string" }, }, required: ["conversationId"], }, ], }, GetConversationSummaryResponse: { type: "object", title: "GetConversationSummaryResponse", properties: { summary: {}, }, required: ["summary"], }, GitDiffToRemoteParams: { type: "object", title: "GitDiffToRemoteParams", properties: { cwd: { type: "string" }, }, required: ["cwd"], }, GitDiffToRemoteResponse: { type: "object", title: "GitDiffToRemoteResponse", properties: { sha: { type: "string" }, diff: { type: "string" }, }, required: ["sha", "diff"], }, GetAuthStatusResponse: { type: "object", title: "GetAuthStatusResponse", properties: { authMethod: { anyOf: [{}, { type: "null" }], }, authToken: { anyOf: [{ type: "string" }, { type: "null" }], }, requiresOpenaiAuth: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, required: ["authMethod", "authToken", "requiresOpenaiAuth"], },}; const getGeneratedPaths = Effect.fn("getGeneratedPaths")(function* () { const path = yield* Path.Path; const generatedDir = path.join(import.meta.dirname, "..", "src", "_generated"); return { generatedDir, schemaOutputPath: path.join(generatedDir, "schema.gen.ts"), metaOutputPath: path.join(generatedDir, "meta.gen.ts"), namespacesOutputPath: path.join(generatedDir, "namespaces.gen.ts"), } satisfies GeneratedPaths;}); const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () { const fs = yield* FileSystem.FileSystem; const { generatedDir } = yield* getGeneratedPaths(); yield* fs.makeDirectory(generatedDir, { recursive: true });}); export const fetchText = Effect.fn("fetchText")(function* (url: string) { const response = yield* HttpClientRequest.get(url).pipe( HttpClientRequest.setHeader("user-agent", USER_AGENT), HttpClient.execute, Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError( (cause) => new GeneratorFetchError({ ...urlDiagnosticFields(url), stage: "request", cause, }), ), ); return yield* response.text.pipe( Effect.mapError( (cause) => new GeneratorFetchError({ ...urlDiagnosticFields(url), stage: "read-response", cause, }), ), );}); export const fetchDirectoryEntries = Effect.fn("fetchDirectoryEntries")(function* ( directoryPath: string,) { const url = `${GITHUB_API_BASE}/${directoryPath}?ref=${UPSTREAM_REF}`; const raw = yield* fetchText(url); return yield* decodeGithubContentEntries(raw).pipe( Effect.mapError( (cause) => new GeneratorDirectoryDecodeError({ directoryPath, ...urlDiagnosticFields(url), cause, }), ), );}); export const decodeSchemaDocument = Effect.fn("decodeSchemaDocument")(function* (input: { readonly repositoryPath: string; readonly url: string; readonly raw: string;}) { return yield* decodeJsonSchemaDocument(input.raw).pipe( Effect.mapError( (cause) => new GeneratorSchemaDocumentDecodeError({ repositoryPath: input.repositoryPath, ...urlDiagnosticFields(input.url), cause, }), ), );}); export const formatGeneratedFiles = Effect.fn("formatGeneratedFiles")(function* ( generatedDir: string,) { const command = "vp"; const args = ["fmt", generatedDir, "--write"]; const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; const child = yield* spawner.spawn(ChildProcess.make(command, args)).pipe( Effect.mapError( (cause) => new GeneratorFormatProcessError({ operation: "spawn", command, argumentCount: args.length, generatedDir, cause, }), ), ); const exitCode = yield* child.exitCode.pipe( Effect.mapError( (cause) => new GeneratorFormatProcessError({ operation: "wait-for-exit", command, argumentCount: args.length, generatedDir, cause, }), ), ); if (exitCode !== 0) { return yield* new GeneratorFormatExitError({ command, argumentCount: args.length, generatedDir, exitCode, }); }}); export const collectSchemaEntries = Effect.fn("collectSchemaEntries")(function* (chunk: string) { const lines = chunk .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("//")); const entries: Array<{ name: string; code: string }> = []; for (let index = 0; index < lines.length; index += 1) { const typeLine = lines[index]; if (!typeLine?.startsWith("export type ")) { continue; } const constLine = lines[index + 1]; if (!constLine?.startsWith("export const ")) { return yield* new GeneratorSchemaValueDeclarationMissingError({ lineIndex: index, typeDeclarationLength: typeLine.length, nextLinePresent: constLine !== undefined, ...(constLine === undefined ? {} : { nextLineLength: constLine.length }), }); } const match = /^export type ([A-Za-z0-9_]+)/.exec(typeLine); if (!match?.[1]) { return yield* new GeneratorSchemaNameParseError({ lineIndex: index, typeDeclarationLength: typeLine.length, }); } entries.push({ name: match[1], code: `${typeLine}\n${constLine}`, }); index += 1; } return entries;}); function normalizeNullableTypes(value: Schema.Json): Schema.Json { if (Array.isArray(value)) { return value.map(normalizeNullableTypes); } if (value === null || typeof value !== "object") { return value; } const normalizedEntries = Object.entries(value).map(([key, child]) => [ key, normalizeNullableTypes(child), ]); const normalizedObject = Object.fromEntries(normalizedEntries) as Record<string, Schema.Json>; const typeValue = normalizedObject.type; if (!Array.isArray(typeValue)) { return normalizedObject; } const normalizedTypes = typeValue.filter((entry): entry is string => typeof entry === "string"); if (normalizedTypes.length !== typeValue.length || !normalizedTypes.includes("null")) { return normalizedObject; } const nonNullTypes = normalizedTypes.filter((entry) => entry !== "null"); if (nonNullTypes.length !== 1) { return normalizedObject; } const nonNullType = nonNullTypes[0]!; const nextObject: Record<string, Schema.Json> = {}; for (const [key, child] of Object.entries(normalizedObject)) { if (key !== "type") { nextObject[key] = child; } } return { anyOf: [ { ...nextObject, type: nonNullType, }, { type: "null" }, ], };} function stripNullDefaults(value: Schema.Json): Schema.Json { if (Array.isArray(value)) { return value.map(stripNullDefaults); } if (value === null || typeof value !== "object") { return value; } return Object.fromEntries( Object.entries(value) .filter(([key, child]) => !(key === "default" && child === null)) .map(([key, child]) => [key, stripNullDefaults(child)]), ) as Schema.Json;} function toPascalCaseMethod(method: string) { return method .split("/")Lines 1–500 of 988. Load more
#!/usr/bin/env node import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonSchemaGenerator"; import { getUrlDiagnostics } from "@t3tools/shared/urlDiagnostics"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Logger from "effect/Logger"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; const UPSTREAM_REF = "b39f943a634a6e7ba86c3d6e8cf6d5f35e612566"; const USER_AGENT = "effect-codex-app-server-generator"; const GITHUB_API_BASE = "https://api.github.com/repos/openai/codex/contents/codex-rs/app-server-protocol"; const GithubContentEntries = Schema.Array( Schema.Struct({ name: Schema.String, path: Schema.String, download_url: Schema.NullOr(Schema.String), type: Schema.String, }), ); type GithubContentEntry = (typeof GithubContentEntries.Type)[number]; const JsonSchemaDocument = Schema.StructWithRest( Schema.Struct({ definitions: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json)), }), [Schema.Record(Schema.String, Schema.Json)], ); const decodeGithubContentEntries = Schema.decodeEffect(Schema.fromJsonString(GithubContentEntries)); const decodeJsonSchemaDocument = Schema.decodeEffect(Schema.fromJsonString(JsonSchemaDocument)); interface GeneratedPaths { readonly generatedDir: string; readonly schemaOutputPath: string; readonly metaOutputPath: string; readonly namespacesOutputPath: string; } interface MethodEntry { readonly method: string; readonly paramsType?: string; } interface JsonSchemaFile { readonly namespace?: string; readonly exportName: string; readonly fileName: string; readonly downloadUrl: string; readonly qualifiedName: string; readonly repositoryPath: string; } const urlDiagnosticsSchema = { urlInputLength: Schema.Number, urlProtocol: Schema.optionalKey(Schema.String), urlHostname: Schema.optionalKey(Schema.String), }; function urlDiagnosticFields(url: string) { const diagnostics = getUrlDiagnostics(url); return { urlInputLength: diagnostics.inputLength, ...(diagnostics.protocol === undefined ? {} : { urlProtocol: diagnostics.protocol }), ...(diagnostics.hostname === undefined ? {} : { urlHostname: diagnostics.hostname }), }; } export class GeneratorFetchError extends Schema.TaggedErrorClass<GeneratorFetchError>()( "GeneratorFetchError", { ...urlDiagnosticsSchema, stage: Schema.Literals(["request", "read-response"]), cause: Schema.Defect(), }, ) { override get message(): string { const source = this.urlHostname === undefined ? "the configured source" : this.urlHostname; return `Failed to fetch a Codex generator input from ${source} during ${this.stage}.`; } } export class GeneratorDirectoryDecodeError extends Schema.TaggedErrorClass<GeneratorDirectoryDecodeError>()( "GeneratorDirectoryDecodeError", { directoryPath: Schema.String, ...urlDiagnosticsSchema, cause: Schema.Defect(), }, ) { override get message(): string { return `Failed to decode the GitHub directory listing for ${this.directoryPath}.`; } } export class GeneratorSchemaDocumentDecodeError extends Schema.TaggedErrorClass<GeneratorSchemaDocumentDecodeError>()( "GeneratorSchemaDocumentDecodeError", { repositoryPath: Schema.String, ...urlDiagnosticsSchema, cause: Schema.Defect(), }, ) { override get message(): string { return `Failed to decode the Codex schema document ${this.repositoryPath}.`; } } export class GeneratorFormatProcessError extends Schema.TaggedErrorClass<GeneratorFormatProcessError>()( "GeneratorFormatProcessError", { operation: Schema.Literals(["spawn", "wait-for-exit"]), command: Schema.String, argumentCount: Schema.Number, generatedDir: Schema.String, cause: Schema.Defect(), }, ) { override get message(): string { return `Generator formatting command ${this.command} failed during ${this.operation} for ${this.generatedDir}.`; } } export class GeneratorFormatExitError extends Schema.TaggedErrorClass<GeneratorFormatExitError>()( "GeneratorFormatExitError", { command: Schema.String, argumentCount: Schema.Number, generatedDir: Schema.String, exitCode: Schema.Number, }, ) { override get message(): string { return `Generator formatting command ${this.command} exited with code ${this.exitCode} for ${this.generatedDir}.`; } } export class GeneratorSchemaValueDeclarationMissingError extends Schema.TaggedErrorClass<GeneratorSchemaValueDeclarationMissingError>()( "GeneratorSchemaValueDeclarationMissingError", { lineIndex: Schema.Number, typeDeclarationLength: Schema.Number, nextLinePresent: Schema.Boolean, nextLineLength: Schema.optional(Schema.Number), }, ) { override get message(): string { return `Generated schema type declaration at line ${this.lineIndex + 1} has no following value declaration.`; } } export class GeneratorSchemaNameParseError extends Schema.TaggedErrorClass<GeneratorSchemaNameParseError>()( "GeneratorSchemaNameParseError", { lineIndex: Schema.Number, typeDeclarationLength: Schema.Number, }, ) { override get message(): string { return `Could not extract a schema name from generated declaration at line ${this.lineIndex + 1}.`; } } export class GeneratorSchemaTypeResolutionError extends Schema.TaggedErrorClass<GeneratorSchemaTypeResolutionError>()( "GeneratorSchemaTypeResolutionError", { rawTypeName: Schema.String, candidates: Schema.Array(Schema.String), }, ) { override get message(): string { return `Unable to resolve schema type ${this.rawTypeName}; tried ${this.candidates.join(", ")}.`; } } export class GeneratorExternalReferenceResolutionError extends Schema.TaggedErrorClass<GeneratorExternalReferenceResolutionError>()( "GeneratorExternalReferenceResolutionError", { reference: Schema.String, currentNamespace: Schema.NullOr(Schema.String), candidates: Schema.Array(Schema.String), }, ) { override get message(): string { return `Unable to rewrite external reference ${this.reference} in namespace ${this.currentNamespace ?? "<root>"}; tried ${this.candidates.join(", ")}.`; } } const ManualSchemas: Record<string, Schema.Json> = { GetAuthStatusParams: { type: "object", title: "GetAuthStatusParams", properties: { includeToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, refreshToken: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, }, GetConversationSummaryParams: { title: "GetConversationSummaryParams", oneOf: [ { type: "object", properties: { rolloutPath: { type: "string" }, }, required: ["rolloutPath"], }, { type: "object", properties: { conversationId: { type: "string" }, }, required: ["conversationId"], }, ], }, GetConversationSummaryResponse: { type: "object", title: "GetConversationSummaryResponse", properties: { summary: {}, }, required: ["summary"], }, GitDiffToRemoteParams: { type: "object", title: "GitDiffToRemoteParams", properties: { cwd: { type: "string" }, }, required: ["cwd"], }, GitDiffToRemoteResponse: { type: "object", title: "GitDiffToRemoteResponse", properties: { sha: { type: "string" }, diff: { type: "string" }, }, required: ["sha", "diff"], }, GetAuthStatusResponse: { type: "object", title: "GetAuthStatusResponse", properties: { authMethod: { anyOf: [{}, { type: "null" }], }, authToken: { anyOf: [{ type: "string" }, { type: "null" }], }, requiresOpenaiAuth: { anyOf: [{ type: "boolean" }, { type: "null" }], }, }, required: ["authMethod", "authToken", "requiresOpenaiAuth"], }, }; const getGeneratedPaths = Effect.fn("getGeneratedPaths")(function* () { const path = yield* Path.Path; const generatedDir = path.join(import.meta.dirname, "..", "src", "_generated"); return { generatedDir, schemaOutputPath: path.join(generatedDir, "schema.gen.ts"), metaOutputPath: path.join(generatedDir, "meta.gen.ts"), namespacesOutputPath: path.join(generatedDir, "namespaces.gen.ts"), } satisfies GeneratedPaths; }); const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () { const fs = yield* FileSystem.FileSystem; const { generatedDir } = yield* getGeneratedPaths(); yield* fs.makeDirectory(generatedDir, { recursive: true }); }); export const fetchText = Effect.fn("fetchText")(function* (url: string) { const response = yield* HttpClientRequest.get(url).pipe( HttpClientRequest.setHeader("user-agent", USER_AGENT), HttpClient.execute, Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.mapError( (cause) => new GeneratorFetchError({ ...urlDiagnosticFields(url), stage: "request", cause, }), ), ); return yield* response.text.pipe( Effect.mapError( (cause) => new GeneratorFetchError({ ...urlDiagnosticFields(url), stage: "read-response", cause, }), ), ); }); export const fetchDirectoryEntries = Effect.fn("fetchDirectoryEntries")(function* ( directoryPath: string, ) { const url = `${GITHUB_API_BASE}/${directoryPath}?ref=${UPSTREAM_REF}`; const raw = yield* fetchText(url); return yield* decodeGithubContentEntries(raw).pipe( Effect.mapError( (cause) => new GeneratorDirectoryDecodeError({ directoryPath, ...urlDiagnosticFields(url), cause, }), ), ); }); export const decodeSchemaDocument = Effect.fn("decodeSchemaDocument")(function* (input: { readonly repositoryPath: string; readonly url: string; readonly raw: string; }) { return yield* decodeJsonSchemaDocument(input.raw).pipe( Effect.mapError( (cause) => new GeneratorSchemaDocumentDecodeError({ repositoryPath: input.repositoryPath, ...urlDiagnosticFields(input.url), cause, }), ), ); }); export const formatGeneratedFiles = Effect.fn("formatGeneratedFiles")(function* ( generatedDir: string, ) { const command = "vp"; const args = ["fmt", generatedDir, "--write"]; const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; const child = yield* spawner.spawn(ChildProcess.make(command, args)).pipe( Effect.mapError( (cause) => new GeneratorFormatProcessError({ operation: "spawn", command, argumentCount: args.length, generatedDir, cause, }), ), ); const exitCode = yield* child.exitCode.pipe( Effect.mapError( (cause) => new GeneratorFormatProcessError({ operation: "wait-for-exit", command, argumentCount: args.length, generatedDir, cause, }), ), ); if (exitCode !== 0) { return yield* new GeneratorFormatExitError({ command, argumentCount: args.length, generatedDir, exitCode, }); } }); export const collectSchemaEntries = Effect.fn("collectSchemaEntries")(function* (chunk: string) { const lines = chunk .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0 && !line.startsWith("//")); const entries: Array<{ name: string; code: string }> = []; for (let index = 0; index < lines.length; index += 1) { const typeLine = lines[index]; if (!typeLine?.startsWith("export type ")) { continue; } const constLine = lines[index + 1]; if (!constLine?.startsWith("export const ")) { return yield* new GeneratorSchemaValueDeclarationMissingError({ lineIndex: index, typeDeclarationLength: typeLine.length, nextLinePresent: constLine !== undefined, ...(constLine === undefined ? {} : { nextLineLength: constLine.length }), }); } const match = /^export type ([A-Za-z0-9_]+)/.exec(typeLine); if (!match?.[1]) { return yield* new GeneratorSchemaNameParseError({ lineIndex: index, typeDeclarationLength: typeLine.length, }); } entries.push({ name: match[1], code: `${typeLine}\n${constLine}`, }); index += 1; } return entries; }); function normalizeNullableTypes(value: Schema.Json): Schema.Json { if (Array.isArray(value)) { return value.map(normalizeNullableTypes); } if (value === null || typeof value !== "object") { return value; } const normalizedEntries = Object.entries(value).map(([key, child]) => [ key, normalizeNullableTypes(child), ]); const normalizedObject = Object.fromEntries(normalizedEntries) as Record<string, Schema.Json>; const typeValue = normalizedObject.type; if (!Array.isArray(typeValue)) { return normalizedObject; } const normalizedTypes = typeValue.filter((entry): entry is string => typeof entry === "string"); if (normalizedTypes.length !== typeValue.length || !normalizedTypes.includes("null")) { return normalizedObject; } const nonNullTypes = normalizedTypes.filter((entry) => entry !== "null"); if (nonNullTypes.length !== 1) { return normalizedObject; } const nonNullType = nonNullTypes[0]!; const nextObject: Record<string, Schema.Json> = {}; for (const [key, child] of Object.entries(normalizedObject)) { if (key !== "type") { nextObject[key] = child; } } return { anyOf: [ { ...nextObject, type: nonNullType, }, { type: "null" }, ], }; } function stripNullDefaults(value: Schema.Json): Schema.Json { if (Array.isArray(value)) { return value.map(stripNullDefaults); } if (value === null || typeof value !== "object") { return value; } return Object.fromEntries( Object.entries(value) .filter(([key, child]) => !(key === "default" && child === null)) .map(([key, child]) => [key, stripNullDefaults(child)]), ) as Schema.Json; } function toPascalCaseMethod(method: string) { return method .split("/")