From f39ba1ff1379d5c67b44283dd9fd33ec21d7eba0 Mon Sep 17 00:00:00 2001 From: Siddharth Suresh Date: Wed, 12 Oct 2022 21:16:24 +0530 Subject: [PATCH] #3748 Add custom template option back to blitz generate command (#3866) --- .changeset/slow-impalas-tap.md | 6 ++ apps/toolkit-app/app/blitz-server.ts | 5 ++ packages/blitz/src/cli/commands/generate.ts | 59 +++++++++++++++ packages/blitz/src/types.ts | 4 + packages/generator/src/generator.ts | 82 +++++++++++++++++++++ 5 files changed, 156 insertions(+) create mode 100644 .changeset/slow-impalas-tap.md diff --git a/.changeset/slow-impalas-tap.md b/.changeset/slow-impalas-tap.md new file mode 100644 index 0000000000..740d5bd76d --- /dev/null +++ b/.changeset/slow-impalas-tap.md @@ -0,0 +1,6 @@ +--- +"blitz": patch +"@blitzjs/generator": patch +--- + +Allow passing custom templates to the `blitz generate` command. Extend the `generate` command with `custom-templates` to provide an easy starting point for users to customize the default templates: `blitz generate custom-templates`. diff --git a/apps/toolkit-app/app/blitz-server.ts b/apps/toolkit-app/app/blitz-server.ts index 37f87cd202..b911a664b7 100644 --- a/apps/toolkit-app/app/blitz-server.ts +++ b/apps/toolkit-app/app/blitz-server.ts @@ -1,3 +1,4 @@ +import type { BlitzCliConfig } from "blitz" import { setupBlitzServer } from "@blitzjs/next" import { AuthServerPlugin, PrismaStorage } from "@blitzjs/auth" import db from "db" @@ -14,3 +15,7 @@ const { gSSP, gSP, api } = setupBlitzServer({ }) export { gSSP, gSP, api } + +export const cliConfig: BlitzCliConfig = { + customTemplates: "app/templates", +} diff --git a/packages/blitz/src/cli/commands/generate.ts b/packages/blitz/src/cli/commands/generate.ts index 706175e167..6758eb3c2c 100644 --- a/packages/blitz/src/cli/commands/generate.ts +++ b/packages/blitz/src/cli/commands/generate.ts @@ -15,7 +15,9 @@ import { MutationsGenerator, ModelGenerator, QueryGenerator, + addCustomTemplatesBlitzConfig, } from "@blitzjs/generator" +import {log} from "../../logging" const getIsTypeScript = async () => require("fs").existsSync(require("path").join(process.cwd(), "tsconfig.json")) @@ -30,6 +32,7 @@ enum ResourceType { Mutations = "mutations", Mutation = "mutation", Resource = "resource", + CustomTemplates = "custom-templates", } function modelName(input: string = "") { @@ -45,6 +48,39 @@ function ModelNames(input: string = "") { return pluralPascal(input) } +const createCustomTemplates = async () => { + const continuePrompt = await prompts({ + type: "confirm", + name: "value", + message: `This will copy the default templates to your app/templates folder. Do you want to continue?`, + }) + if (!continuePrompt.value) { + process.exit(0) + } + const templatesPath = await prompts({ + type: "text", + name: "value", + message: `Enter the path to save the custom templates folder`, + initial: "app/templates", + }) + const templatesPathValue: string = templatesPath.value + const isTypeScript = await getIsTypeScript() + addCustomTemplatesBlitzConfig(templatesPathValue, isTypeScript) + log.success(`🚀 Custom templates path added/updated in app/blitz-server file`) + const customTemplatesPath = require("path").join(process.cwd(), templatesPathValue) + const fsExtra = await import("fs-extra") + const blitzGeneratorPath = require.resolve("@blitzjs/generator") + const templateFolder = ["form", "page", "query", "mutation", "queries", "mutations"] + for (const template of templateFolder) { + await fsExtra.copy( + require("path").join(blitzGeneratorPath, "..", "templates", template), + require("path").join(customTemplatesPath, template), + ) + } + log.success(`🚀 Custom templates created in ${templatesPathValue} directory`) + process.exit(0) +} + const generatorMap = { [ResourceType.All]: [ PageGenerator, @@ -61,6 +97,7 @@ const generatorMap = { [ResourceType.Mutations]: [MutationsGenerator], [ResourceType.Mutation]: [MutationGenerator], [ResourceType.Resource]: [QueriesGenerator, MutationsGenerator, ModelGenerator], + [ResourceType.CustomTemplates]: [], } const args = arg( @@ -199,6 +236,13 @@ const getHelp = async () => { > blitz generate all tasks --parent=projects + # To customize the templates used by the blitz generate command, + + > blitz generate custom-templates + + This command will copy the default templates to your app and update the app/blitz-server file to enable + the custom templating feature of the blitz CLI + # Database models can also be generated directly from the CLI. Model fields can be specified with any generator that generates a database model ("all", "model", "resource"). Both of the commands below will generate the proper database model for a Task. @@ -216,6 +260,9 @@ const getHelp = async () => { const generate: CliCommand = async () => { await getHelp() await determineType() + if (selectedType === "custom-templates") { + await createCustomTemplates() + } if (!selectedModelName) { await determineName() } @@ -227,9 +274,21 @@ const generate: CliCommand = async () => { const generators = generatorMap[selectedType as keyof typeof generatorMap] + const isTypeScript = await getIsTypeScript() + const blitzServerPath = isTypeScript ? "app/blitz-server.ts" : "app/blitz-server.js" + const blitzServer = require("path").join(process.cwd(), blitzServerPath) + const {register} = require("esbuild-register/dist/node") + const {unregister} = register({ + target: "es6", + }) + const blitzConfig = require(blitzServer) + const {cliConfig} = blitzConfig + unregister() + for (const GeneratorClass of generators) { const generator = new GeneratorClass({ destinationRoot: require("path").resolve(), + templateDir: cliConfig?.customTemplates, extraArgs: args["_"].slice(3) as string[], modelName: singularRootContext, modelNames: modelNames(singularRootContext), diff --git a/packages/blitz/src/types.ts b/packages/blitz/src/types.ts index 5ab173491a..ec2b8c58aa 100644 --- a/packages/blitz/src/types.ts +++ b/packages/blitz/src/types.ts @@ -6,6 +6,10 @@ export interface RouteUrlObject extends Pick { pathname: string } +export type BlitzCliConfig = { + customTemplates?: string +} + export const isRouteUrlObject = (x: any): x is RouteUrlObject => { return typeof x === "object" && "pathname" in x && typeof x.pathname === "string" } diff --git a/packages/generator/src/generator.ts b/packages/generator/src/generator.ts index 909b1b3224..518662e6aa 100644 --- a/packages/generator/src/generator.ts +++ b/packages/generator/src/generator.ts @@ -16,6 +16,88 @@ import {readdirRecursive} from "./utils/readdir-recursive" import prettier from "prettier" const debug = require("debug")("blitz:generator") +export const addCustomTemplatesBlitzConfig = ( + customTemplatesPath: string, + isTypeScript: boolean, +) => { + const blitzServer = isTypeScript ? "app/blitz-server.ts" : "app/blitz-server.js" + const blitzServerPath = require("path").join(process.cwd(), blitzServer) + const userConfigModuleSource = fs.readFileSync(blitzServerPath, {encoding: "utf-8"}) + const userConfigModule = j(userConfigModuleSource, {parser: customTsParser}) + const program = userConfigModule.get() + const cliConfigDeclaration = userConfigModule + .find(j.ExportNamedDeclaration, { + declaration: { + type: "VariableDeclaration", + declarations: [ + { + id: { + name: "cliConfig", + }, + }, + ], + }, + }) + .paths() + .at(0) + if (!cliConfigDeclaration) { + const config = j.identifier("cliConfig") + const configVariable = j.variableDeclaration("const", [ + j.variableDeclarator( + config, + j.objectExpression([ + j.objectProperty(j.identifier("customTemplates"), j.literal(customTemplatesPath)), + ]), + ), + ]) + if (isTypeScript) { + const type = j.tsTypeAnnotation(j.tsTypeReference(j.identifier("BlitzCliConfig"))) + const declaration: any = configVariable?.declarations + declaration[0].id.typeAnnotation = type + const typeImport = j.importDeclaration( + [j.importSpecifier(j.identifier("BlitzCliConfig"))], + j.literal("blitz"), + ) + typeImport.importKind = "type" + program.node.program.body.unshift(typeImport) + } + const exportConfig = j.exportNamedDeclaration(configVariable) + program.node.program.body.push(exportConfig) + } else { + const configType = cliConfigDeclaration.value.declaration?.type + if (configType === "VariableDeclaration") { + const config = cliConfigDeclaration.value.declaration.declarations[0] + if (config?.type === "VariableDeclarator") { + const configProperties = config.init + if (configProperties?.type === "ObjectExpression") { + const customTemplatesProperty = configProperties.properties.find((property) => { + if (property.type === "ObjectProperty") { + const key = property.key + if (key.type === "Identifier") { + return key.name === "customTemplates" + } + } + }) + if (!customTemplatesProperty) { + configProperties.properties.push( + j.objectProperty(j.identifier("customTemplates"), j.literal(customTemplatesPath)), + ) + } else { + if (customTemplatesProperty.type === "ObjectProperty") { + const customValue = customTemplatesProperty.value + if (customValue.type === "StringLiteral") { + customValue.value = customTemplatesPath + } + } + } + } + } + } + } + const newSource = userConfigModule.toSource() + fs.writeFileSync(blitzServerPath, newSource) +} + export const customTsParser = { parse(source: string, options?: Overrides) { const babelOptions = getBabelOptions(options)