@compas/code-gen
Version:
Generate various boring parts of your server
209 lines (183 loc) • 6.16 kB
JavaScript
// @ts-nocheck
import { rmSync } from "fs";
import { pathToFileURL } from "url";
import { isPlainObject, pathJoin, uuid } from "@compas/stdlib";
import { generate, writeFiles } from "./generator/index.js";
/**
* @typedef {object} GenerateTypeOpts
* @property {string} outputDirectory
* @property {string[]} inputPaths
* @property {boolean|string[]|undefined} [dumpCompasTypes]
* @property {boolean|undefined} [verbose]
* @property {string|undefined} [fileHeader]
*/
/**
* @param {Logger} logger
* @param {GenerateTypeOpts} options
* @returns {Promise<void>}
*/
export async function generateTypes(logger, options) {
const structureAndOptionPairs = await validateAndReadStructureFiles(options);
if (options.verbose) {
logger.info({
specifiedInputPaths: options.inputPaths.length,
loadingInputPaths: structureAndOptionPairs.length,
});
}
options.typeCache = {
rawImports: new Set(),
typeMap: new Map(),
calculatingTypes: new Set(),
};
let context;
for (let i = 0; i < structureAndOptionPairs.length; i++) {
const pair = structureAndOptionPairs[i];
pair.options.typeCache = options.typeCache;
pair.options.returnContext = true;
if (i === structureAndOptionPairs.length - 1) {
// Make sure that the last one does type generation.
pair.options.enabledGenerators.push("type");
}
context = await generate(logger, pair.options, pair.structure);
}
const outputFiles = [];
const typeFile = context?.outputFiles?.find((it) =>
it.relativePath.endsWith("types.d.ts"),
);
if (typeFile) {
outputFiles.push(typeFile);
}
if (options.dumpCompasTypes) {
if (!Array.isArray(options.dumpCompasTypes)) {
options.dumpCompasTypes = [
"cli",
"code-gen",
"server",
"stdlib",
"store",
];
}
let contents = `${options.fileHeader}
${
options.dumpCompasTypes.includes("cli")
? `import * as cli from "@compas/cli";`
: ""
}
${
options.dumpCompasTypes.includes("code-gen")
? `import * as codeGen from "@compas/code-gen";`
: ""
}
${
options.dumpCompasTypes.includes("server")
? `import * as server from "@compas/server";`
: ""
}
${
options.dumpCompasTypes.includes("stdlib")
? `import * as stdlib from "@compas/stdlib";`
: ""
}
${
options.dumpCompasTypes.includes("store")
? `import * as store from "@compas/store";`
: ""
}
declare global {
`;
for (const generator of options.dumpCompasTypes) {
if (generator === "cli") {
contents += `
type CliCompletion = cli.CliCompletion;
type CliResult = cli.CliResult;
type CliCommandDefinitionInput = cli.CliCommandDefinitionInput;
type CliExecutorState = cli.CliExecutorState;
`;
} else if (generator === "code-gen") {
contents += `
type App = codeGen.App;
type TypeCreator = codeGen.TypeCreator;
type RouteCreator = codeGen.RouteCreator;
type TypeBuilder = codeGen.TypeBuilder;
type TypeBuilderLike = codeGen.TypeBuilderLike;
`;
} else if (generator === "stdlib") {
contents += `
type AppError = stdlib.AppError;
type ConfigLoaderOptions = stdlib.ConfigLoaderOptions;
type ConfigLoaderResult = stdlib.ConfigLoaderResult;
type InsightEvent = stdlib.InsightEvent;
type Logger = stdlib.Logger;
type Either<T, E = AppError> =
| { value: T; error?: never }
| { value?: never; error: E };
type EitherN<T, E = AppError> =
| { value: T; errors: never }
| { value: never; errors: E[] };
`;
} else if (generator === "store") {
contents += `
type Postgres = store.Postgres;
type S3Client = store.S3Client;
type QueryPart<T = any> = store.QueryPart<T>;
type QueryPartArg = store.QueryPartArg;
type Returning<Type, Selector extends undefined | "*" | string[]> = store.Returning<Type, Selector>;
type SessionStoreSettings = store.SessionStoreSettings;
type SessionTransportSettings = store.SessionTransportSettings;
`;
} else if (generator === "server") {
contents += `
type Application = server.Application
type Context<
StateT = import("koa").DefaultState,
ContextT = import("koa").DefaultContext,
ResponseBodyT = unknown,
> = server.Context<StateT, ContextT, ResponseBodyT>;
type Next = server.Next;
type Middleware = server.Middleware;
type BodyParserPair = server.BodyParserPair;
type AxiosInstance = import("axios").AxiosInstance;
type AxiosError = import("axios").AxiosError;
type AxiosRequestConfig = import("axios").AxiosRequestConfig;
`;
}
}
contents += "\n}";
outputFiles.push({
relativePath: "./common/compas.d.ts",
contents,
});
}
logger.info(`Cleaning output directory and writing files.`);
rmSync(options.outputDirectory, {
recursive: true,
force: true,
maxRetries: 3,
retryDelay: 10,
});
writeFiles({
outputFiles,
options,
});
}
/**
* Read and validate the structure files based on the input paths.
* Checking if the files exists is already done by the 'App'.
*
* @param {GenerateTypeOpts} options
* @returns {Promise<{ structure: CodeGenStructure, options: GenerateOpts }[]>}
*/
async function validateAndReadStructureFiles(options) {
const pairs = [];
for (const inputPath of options.inputPaths) {
const structureFile = pathJoin(inputPath, "/common/structure.js");
// @ts-ignore
const { structure, compasGenerateSettings: options } = await import(
`${pathToFileURL(structureFile)}?v=${uuid()}`
);
if (isPlainObject(structure) && isPlainObject(options)) {
pairs.push({ structure, options });
}
}
return pairs;
}