UNPKG

convex

Version:

Client for the Convex Cloud

287 lines (268 loc) 6.84 kB
import path from "path"; import prettier from "prettier"; import { DeploymentType } from "./api.js"; import { ProjectConfig } from "./config.js"; import { functionsDir } from "./utils.js"; import { reactCodegen } from "../codegen_templates/react.js"; import { dataModel, dataModelWithoutSchema, } from "../codegen_templates/dataModel.js"; import { server } from "../codegen_templates/server.js"; import { processTypeCheckResult, typeCheckFile, typeCheckFunctions, TypeCheckMode, } from "./typecheck.js"; import { tsconfigCodegen } from "../codegen_templates/tsconfig.js"; import { readmeCodegen } from "../codegen_templates/readme.js"; import { Context } from "./context.js"; import { devDeploymentConfig, prodDeploymentConfig, } from "../codegen_templates/clientConfig.js"; import { GeneratedJsWithTypes } from "../codegen_templates/common.js"; import { entryPoints } from "../../bundler/index.js"; /** * Run prettier so we don't have to think about formatting! * * This is a little sketchy because we are using the default prettier config * (not our user's one) but it's better than nothing. */ function format(source: string, filetype: string): string { return prettier.format(source, { parser: filetype }); } function writeFile( ctx: Context, filename: string, source: string, dir: string, dryRun: boolean, debug: boolean, quiet: boolean, filetype = "typescript" ) { const formattedSource = format(source, filetype); const dest = path.join(dir, filename); if (debug) { console.log(`# ${dest}`); console.log(formattedSource); return; } if (dryRun) { if (ctx.fs.exists(dest)) { const fileText = ctx.fs.readUtf8File(dest); if (fileText !== formattedSource) { console.log(`Command would replace file: ${dest}`); } } else { console.log(`Command would create file: ${dest}`); } return; } if (!quiet) { console.log(`writing ${dest}`); } ctx.fs.writeUtf8File(dest, formattedSource); } function writeJsWithTypes( ctx: Context, name: string, content: GeneratedJsWithTypes, dir: string, dryRun: boolean, debug: boolean, quiet: boolean ) { writeFile(ctx, `${name}.d.ts`, content.DTS, dir, dryRun, debug, quiet); writeFile(ctx, `${name}.js`, content.JS, dir, dryRun, debug, quiet); } function doServerCodegen( ctx: Context, codegenDir: string, dryRun: boolean, hasSchemaFile: boolean, debug: boolean, quiet = false ) { if (hasSchemaFile) { writeJsWithTypes( ctx, "dataModel", dataModel, codegenDir, dryRun, debug, quiet ); } else { writeJsWithTypes( ctx, "dataModel", dataModelWithoutSchema, codegenDir, dryRun, debug, quiet ); } writeJsWithTypes(ctx, "server", server, codegenDir, dryRun, debug, quiet); } async function doReactCodegen( ctx: Context, functionsDir: string, codegenDir: string, dryRun: boolean, debug: boolean, quiet = false ) { const modulePaths = (await entryPoints(ctx.fs, functionsDir, false)).map( entryPoint => path.relative(functionsDir, entryPoint) ); writeJsWithTypes( ctx, "react", reactCodegen(modulePaths), codegenDir, dryRun, debug, quiet ); } export async function doCodegen({ ctx, projectConfig, configPath, typeCheckMode, deploymentType, dryRun = false, debug = false, quiet = false, }: { ctx: Context; projectConfig: ProjectConfig; configPath: string; typeCheckMode: TypeCheckMode; deploymentType: DeploymentType; dryRun?: boolean; debug?: boolean; quiet?: boolean; }): Promise<void> { const funcDir = functionsDir(configPath, projectConfig); // Delete the old _generated.ts because v0.1.2 used to put the react generated // code there const legacyCodegenPath = path.join(funcDir, "_generated.ts"); if (ctx.fs.exists(legacyCodegenPath)) { if (!dryRun) { console.log(`Deleting legacy codegen file: ${legacyCodegenPath}}`); ctx.fs.unlink(legacyCodegenPath); } else { console.log( `Command would delete legacy codegen file: ${legacyCodegenPath}}` ); } } // Create the function dir if it doesn't already exist. ctx.fs.mkdir(funcDir, { allowExisting: true }); // Recreate the codegen directory blowing out whatever was there. const codegenDir = path.join(funcDir, "_generated"); if (!dryRun && !debug) { ctx.fs.rm(codegenDir, { force: true, recursive: true }); ctx.fs.mkdir(codegenDir); } const schemaPath = path.join(funcDir, "schema.ts"); const hasSchemaFile = ctx.fs.exists(schemaPath); writeJsWithTypes( ctx, "clientConfig", deploymentType === "dev" ? devDeploymentConfig : prodDeploymentConfig(projectConfig), codegenDir, dryRun, debug, quiet ); // Do things in a careful order so that we always: // - typecheck sources before we use them. // - generate code in dependency order. // // The dependency chain is: // _generated/react.js // -> query and mutation functions // -> _generated/server.js // -> schema.ts // (where -> means "depends on") // 1. Typecheck the schema.ts file if (hasSchemaFile) { await processTypeCheckResult(ctx, typeCheckMode, () => typeCheckFile(ctx, path.join(funcDir, "schema.ts")) ); } // 2. Use the schema.ts file to create the server codegen doServerCodegen(ctx, codegenDir, dryRun, hasSchemaFile, debug, quiet); // 3. Typecheck the query and mutation functions await processTypeCheckResult(ctx, typeCheckMode, () => typeCheckFunctions(ctx, funcDir) ); // 4. Generate the React code await doReactCodegen(ctx, funcDir, codegenDir, dryRun, debug, quiet); } // Code generated on new project init, after which these files are not // automatically written again in case developers have modified them. export function doInitCodegen( ctx: Context, functionsDir: string, convexPackageFromFunctions: string, quiet = false ) { const dryRun = false; const debug = false; doReadmeCodegen(ctx, functionsDir, dryRun, debug, quiet); doTsconfigCodegen( ctx, functionsDir, convexPackageFromFunctions, dryRun, debug, quiet ); } export function doReadmeCodegen( ctx: Context, functionsDir: string, dryRun = false, debug = false, quiet = false ) { writeFile( ctx, "README.md", readmeCodegen(), functionsDir, dryRun, debug, quiet, "markdown" ); } export function doTsconfigCodegen( ctx: Context, functionsDir: string, convexPackageFromFunctions: string, dryRun = false, debug = false, quiet = false ) { writeFile( ctx, "tsconfig.json", tsconfigCodegen(convexPackageFromFunctions), functionsDir, dryRun, debug, quiet, "json" ); }