988 lines · 30.7 KB · TypeScript
RawDownload
#!/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.