UNPKG

@compas/code-gen

Version:

Generate various boring parts of your server

194 lines (172 loc) 5.43 kB
import { readFileSync } from "node:fs"; import { AppError, environment, isNil, isProduction, loggerGetPrettyPrinter, loggerSetGlobalDestination, newLogger, pathJoin, } from "@compas/stdlib"; import { buildOrInfer } from "./builders/index.js"; import { generateExecute } from "./generate.js"; import { validateStructureGenerateOptions } from "./generated/structure/validators.js"; import { structureAddType, structureExtractGroups, structureIncludeReferences, structureNamedTypes, } from "./processors/structure.js"; /** * Compas code-gen entrypoint. * * TODO: expand the docs */ export class Generator { /** * @param {import("@compas/stdlib").Logger} [logger] */ constructor(logger) { if ( isNil(logger) && environment.CI !== "true" && (!isProduction() || isNil(environment.NODE_ENV)) ) { // Work nicely when the user doesn't use `mainFn` and doesn't explicitly set // NODE_ENV. Normally, we default to production behaviour when `NODE_ENV` is not // set, but we expect the generator to only be used in dev contexts. loggerSetGlobalDestination( loggerGetPrettyPrinter({ addGitHubActionsAnnotations: false }), ); } /** * @type {import("@compas/stdlib").Logger} */ this.logger = logger ?? newLogger(); /** * @type {import("./generated/common/types.js").StructureStructure} */ this.internalStructure = {}; } /** * Add new type definitions to this generator * * @param {...import("../types/advanced-types.js").TypeBuilderLike} builders * @returns {Generator} */ add(...builders) { for (let i = 0; i < builders.length; i++) { const builder = builders[i]; try { structureAddType(this.internalStructure, buildOrInfer(builder), { skipReferenceExtraction: false, }); } catch (/** @type {any} */ e) { throw AppError.serverError( { message: `Could not add builder to the structure`, index: i, builder, }, e, ); } } return this; } /** * Add an existing structure to this generator. * If a string is provided, it is expected to be a path to a 'structure.json' or to an * 'outputDirectory' of a generate call that included 'structure: {}'. * * @param {import("./generated/common/types.js").StructureStructure|string} structureOrDirectory * @returns {Generator} */ addStructure(structureOrDirectory) { if (typeof structureOrDirectory === "string") { if (!structureOrDirectory.endsWith("common/structure.json")) { structureOrDirectory = pathJoin( structureOrDirectory, "common/structure.json", ); } structureOrDirectory = JSON.parse( readFileSync(structureOrDirectory, "utf-8"), ); } // @ts-expect-error // // We already converted a string to a valid structure above. for (const namedDefinition of structureNamedTypes(structureOrDirectory)) { structureAddType(this.internalStructure, namedDefinition, { skipReferenceExtraction: true, }); } return this; } /** * Select a subset of groups from this generator and set them on a new generator. * This includes all references that are used in the current group. * * @param {Array<string>} groups * @returns {Generator} */ selectGroups(groups) { const nextGenerator = new Generator(this.logger); // Extract groups + create a deep copy nextGenerator.internalStructure = JSON.parse( JSON.stringify(structureExtractGroups(this.internalStructure, groups)), ); return nextGenerator; } /** * Select a subset of types from this generator and set them on a new generator. * This includes all references that are used in these types. * * @param {Array<{group: string, name: string}>} typeNames * @returns {Generator} */ selectTypes(typeNames) { const nextGenerator = new Generator(this.logger); for (const typeName of typeNames) { const namedType = this.internalStructure[typeName.group]?.[typeName.name]; if (!namedType) { throw AppError.serverError({ message: "Could not select the type from this generator, as it is not known.", typeName, }); } structureAddType(nextGenerator.internalStructure, namedType, { skipReferenceExtraction: true, }); structureIncludeReferences( this.internalStructure, nextGenerator.internalStructure, namedType, ); } // Create a deep copy. nextGenerator.internalStructure = JSON.parse( JSON.stringify(nextGenerator.internalStructure), ); return nextGenerator; } /** * Generate based on the structure that is known to this generator * * @param {import("./generated/common/types.js").StructureGenerateOptionsInput} options * @returns {Array<import("./generate.js").OutputFile>} */ generate(options) { const validationResultOptions = validateStructureGenerateOptions(options); if (validationResultOptions.error) { throw AppError.serverError({ message: "Static validation failed for the provided options.", error: validationResultOptions.error, }); } return generateExecute(this, validationResultOptions.value); } }