UNPKG

@proofkit/typegen

Version:

261 lines (235 loc) 8.22 kB
import { Project, ScriptKind } from "ts-morph"; import chalk from "chalk"; import { OttoAdapter, type OttoAPIKey } from "@proofkit/fmdapi/adapters/otto"; import DataApi from "@proofkit/fmdapi"; import { FetchAdapter } from "@proofkit/fmdapi/adapters/fetch"; import { memoryStore } from "@proofkit/fmdapi/tokenStore/memory"; import fs from "fs-extra"; import path from "path"; import os from "os"; import { typegenConfig, typegenConfigSingle, type BuildSchemaArgs, } from "./types"; import { commentHeader, defaultEnvNames, overrideCommentHeader, } from "./constants"; import { getLayoutMetadata } from "./getLayoutMetadata"; import { buildOverrideFile, buildSchema } from "./buildSchema"; import { buildLayoutClient } from "./buildLayoutClient"; import { z } from "zod/v4"; export const generateTypedClients = async ( config: z.infer<typeof typegenConfig>["config"], options?: { resetOverrides?: boolean }, ): Promise<{ successCount: number; errorCount: number; totalCount: number; } | void> => { const parsedConfig = typegenConfig.safeParse({ config }); if (!parsedConfig.success) { console.log(chalk.red("ERROR: Invalid config")); console.log(config); console.dir(parsedConfig.error, { depth: null }); return; } if (Array.isArray(parsedConfig.data.config)) { for (const option of parsedConfig.data.config) { await generateTypedClientsSingle(option, options); } } else { await generateTypedClientsSingle(parsedConfig.data.config, options); } }; const generateTypedClientsSingle = async ( config: z.infer<typeof typegenConfigSingle>, options?: { resetOverrides?: boolean }, ) => { const { envNames, layouts, clientSuffix = "Client", generateClient = true, clearOldFiles = false, ...rest } = config; const { resetOverrides = false } = options ?? {}; const validator = rest.validator ?? "zod/v4"; const rootDir = rest.path ?? "schema"; const project = new Project({}); const server = process.env[envNames?.server ?? defaultEnvNames.server]; const db = process.env[envNames?.db ?? defaultEnvNames.db]; const apiKey = (envNames?.auth && "apiKey" in envNames.auth ? process.env[envNames.auth.apiKey ?? defaultEnvNames.apiKey] : undefined) ?? process.env[defaultEnvNames.apiKey]; const username = (envNames?.auth && "username" in envNames.auth ? process.env[envNames.auth.username ?? defaultEnvNames.username] : undefined) ?? process.env[defaultEnvNames.username]; const password = (envNames?.auth && "password" in envNames.auth ? process.env[envNames.auth.password ?? defaultEnvNames.password] : undefined) ?? process.env[defaultEnvNames.password]; const auth: { apiKey: OttoAPIKey } | { username: string; password: string } = apiKey ? { apiKey: apiKey as OttoAPIKey } : { username: username ?? "", password: password ?? "" }; if (!server || !db || (!apiKey && !username)) { console.log(chalk.red("ERROR: Could not get all required config values")); console.log("Ensure the following environment variables are set:"); if (!server) console.log(`${envNames?.server ?? defaultEnvNames.server}`); if (!db) console.log(`${envNames?.db ?? defaultEnvNames.db}`); if (!apiKey) { // Determine the names to display in the error message const apiKeyNameToLog = envNames?.auth && "apiKey" in envNames.auth && envNames.auth.apiKey ? envNames.auth.apiKey : defaultEnvNames.apiKey; const usernameNameToLog = envNames?.auth && "username" in envNames.auth && envNames.auth.username ? envNames.auth.username : defaultEnvNames.username; const passwordNameToLog = envNames?.auth && "password" in envNames.auth && envNames.auth.password ? envNames.auth.password : defaultEnvNames.password; console.log( `${apiKeyNameToLog} (or ${usernameNameToLog} and ${passwordNameToLog})`, ); } console.log(); return; } await fs.ensureDir(rootDir); if (clearOldFiles) { fs.emptyDirSync(path.join(rootDir, "client")); fs.emptyDirSync(path.join(rootDir, "generated")); } const clientIndexFilePath = path.join(rootDir, "client", "index.ts"); fs.rmSync(clientIndexFilePath, { force: true }); // ensure clean slate for this file let successCount = 0; let errorCount = 0; let totalCount = 0; for await (const item of layouts) { totalCount++; const client = "apiKey" in auth ? DataApi({ adapter: new OttoAdapter({ auth, server, db }), layout: item.layoutName, }) : DataApi({ adapter: new FetchAdapter({ auth: auth as any, server, db, tokenStore: memoryStore(), }), layout: item.layoutName, }); const result = await getLayoutMetadata({ client, valueLists: item.valueLists, }); if (!result) { errorCount++; continue; } const { schema, portalSchema, valueLists } = result; const args: BuildSchemaArgs = { schemaName: item.schemaName, schema, layoutName: item.layoutName, portalSchema, valueLists, type: validator === "zod/v4" || validator === "zod/v3" ? validator : "ts", strictNumbers: item.strictNumbers, webviewerScriptName: config.webviewerScriptName, envNames: { auth: "apiKey" in auth ? { apiKey: envNames?.auth && "apiKey" in envNames.auth ? (envNames.auth.apiKey as OttoAPIKey) : (defaultEnvNames.apiKey as OttoAPIKey), } : { username: envNames?.auth && "username" in envNames.auth ? envNames.auth.username : defaultEnvNames.username, password: envNames?.auth && "password" in envNames.auth ? envNames.auth.password : defaultEnvNames.password, }, db: envNames?.db ?? defaultEnvNames.db, server: envNames?.server ?? defaultEnvNames.server, }, }; const schemaFile = project.createSourceFile( path.join(rootDir, "generated", `${item.schemaName}.ts`), { leadingTrivia: commentHeader }, { overwrite: true, scriptKind: ScriptKind.TS, }, ); buildSchema(schemaFile, args); const overrideFilePath = path.join(rootDir, `${item.schemaName}.ts`); if (!fs.existsSync(overrideFilePath) || resetOverrides) { // only build the override file if it doesn't exist const overrideFile = project.createSourceFile( overrideFilePath, { leadingTrivia: overrideCommentHeader, }, { overwrite: true, scriptKind: ScriptKind.TS, }, ); buildOverrideFile(overrideFile, schemaFile, args); } if (item.generateClient ?? generateClient) { await fs.ensureDir(path.join(rootDir, "client")); const layoutClientFile = project.createSourceFile( path.join(rootDir, "client", `${item.schemaName}.ts`), { leadingTrivia: commentHeader }, { overwrite: true, scriptKind: ScriptKind.TS, }, ); buildLayoutClient(layoutClientFile, args); await fs.ensureFile(clientIndexFilePath); const clientIndexFile = project.addSourceFileAtPath(clientIndexFilePath); clientIndexFile.addExportDeclaration({ namedExports: [ { name: "client", alias: `${item.schemaName}${clientSuffix}` }, ], moduleSpecifier: `./${item.schemaName}`, }); } else { console.log( chalk.yellow( `Skipping client generation for ${item.schemaName} because generateClient is false`, ), ); } successCount++; } // format all files project.getSourceFiles().forEach((file) => { file.formatText({ baseIndentSize: 2, }); }); await project.save(); return { successCount, errorCount, totalCount }; };