diff --git a/packages/pages/src/scaffold/scaffold.ts b/packages/pages/src/scaffold/scaffold.ts index 1156918e4..c2bbbae40 100644 --- a/packages/pages/src/scaffold/scaffold.ts +++ b/packages/pages/src/scaffold/scaffold.ts @@ -1,5 +1,6 @@ import { Command } from "commander"; import { modulesCommand } from "./modules/modules.js"; +import { templateCommand } from "./template/template.js"; export const scaffoldCommand = (program: Command) => { const scaffold = program @@ -16,4 +17,5 @@ export const scaffoldCommand = (program: Command) => { console.log('Must provide a subcommand of "scaffold".'); }); modulesCommand(scaffold); + templateCommand(scaffold); }; diff --git a/packages/pages/src/scaffold/template/generate.ts b/packages/pages/src/scaffold/template/generate.ts new file mode 100644 index 000000000..173419d7a --- /dev/null +++ b/packages/pages/src/scaffold/template/generate.ts @@ -0,0 +1,116 @@ +import prompts, { PromptObject } from "prompts"; +import { ProjectStructure } from "../../common/src/project/structure.js"; +import path from "node:path"; +import fs from "node:fs"; + +/* eslint-disable @typescript-eslint/no-unused-vars */ +// TODO: Remove after using adding generation code for templates + +export const generateTemplate = async ( + projectStructure: ProjectStructure +): Promise => { + const questions: PromptObject[] = [ + { + type: "text", + name: "templateName", + message: "What would you like to name your Template?", + validate: (templateName) => + validateTemplateName(templateName, projectStructure) || + "Please ensure the name provided isn't already used and is valid.", + }, + { + type: "confirm", + name: "isVisualEditor", + message: "Is this a Visual Editor template?", + initial: true, + }, + { + type: (prev) => (prev ? null : "toggle"), + name: "isDynamic", + message: "Is this a static or dynamic template?", + initial: true, + active: "Dynamic", + inactive: "Static", + }, + { + type: (prev) => (prev ? "select" : null), + name: "entityScope", + message: + "How would you like you to define the entity scope for your template?", + choices: [ + { title: "Entity Type", value: "entityTypes" }, + { title: "Saved Filter", value: "savedFilterIds" }, + { title: "Entity Id", value: "entityIds" }, + ], + }, + { + type: (prev, values) => + values.entityScope === "entityTypes" ? "list" : null, + name: "filter", + message: "Enter the entity type(s):", + initial: "", + separator: ",", + }, + { + type: (prev, values) => + values.entityScope === "savedFilterIds" ? "list" : null, + name: "filter", + message: "Enter the saved filter ID(s):", + initial: "", + separator: ",", + }, + { + type: (prev, values) => + values.entityScope === "entityIds" ? "list" : null, + name: "filter", + message: "Enter the entity ID(s):", + initial: "", + separator: ",", + }, + ]; + + const response = await prompts(questions); + + // TODO (SUMO-5251): handle generating VE templates + // TODO (SUMO-5252): handle generating non-VE templates +}; + +// Returns true if templateName can be formatted into valid filename and that filename isn't being used. +const validateTemplateName = ( + templateName: string, + projectStructure: ProjectStructure +): boolean => { + const formattedFileName = formatFileName(templateName); + + // Must start with an alphabetic char + if (/^[^a-zA-Z]/.test(formattedFileName)) { + return false; + } + + const templatePath = path.join( + projectStructure.getTemplatePaths()[0].path, + formattedFileName + ); + if (fs.existsSync(templatePath)) { + return false; + } + + return true; +}; + +const formatFileName = (templateName: string): string => { + const specialCharsRemoved = templateName.replace(/[^a-zA-Z0-9\s]+/g, ""); + + const words = specialCharsRemoved.split(" "); + if (words.length === 0) { + return ""; + } + + let fileName = words[0].toLowerCase(); + for (let i = 1; i < words.length; i++) { + fileName += + words[i].charAt(0).toUpperCase() + words[i].slice(1).toLowerCase(); + } + + return fileName; +}; diff --git a/packages/pages/src/scaffold/template/template.ts b/packages/pages/src/scaffold/template/template.ts new file mode 100644 index 000000000..70a76adcd --- /dev/null +++ b/packages/pages/src/scaffold/template/template.ts @@ -0,0 +1,24 @@ +import { Command } from "commander"; +import { logErrorAndExit } from "../../util/logError.js"; +import { ProjectStructure } from "../../common/src/project/structure.js"; +import { generateTemplate } from "./generate.js"; + +const handler = async () => { + const scope = process.env.YEXT_PAGES_SCOPE; + const projectStructure = await ProjectStructure.init({ scope }); + try { + await generateTemplate(projectStructure); + } catch (error) { + logErrorAndExit(error); + } + process.exit(0); +}; + +export const templateCommand = (program: Command) => { + program + .command("template") + .description( + "Adds the required files and folder structure for a new Pages template." + ) + .action(handler); +};