diff --git a/docs/turbo/generators/utils.ts b/docs/turbo/generators/utils.ts index 43dbd14f50c10..ddba993433a8e 100644 --- a/docs/turbo/generators/utils.ts +++ b/docs/turbo/generators/utils.ts @@ -6,6 +6,8 @@ interface Answers extends Object { turboYearsSaved: string; } +const MINUTES_IN_YEAR = 60 * 24 * 365; + const PUBLIC_TB_TOKEN = "p.eyJ1IjogIjAzYzA0Y2MyLTM1YTAtNDhhNC05ZTZjLThhMWE0NGNhNjhkZiIsICJpZCI6ICJmOWIzMTU5Yi0wOTVjLTQyM2UtOWIwNS04ZDZlNzIyNjEwNzIifQ.A3TOPdm3Lhmn-1x5m6jNvulCQbbgUeQfAIO3IaaAt5k"; @@ -37,7 +39,9 @@ export async function releasePostStats(answers: Answers): Promise { const totalMinutesSaved: number = timeSavedData.data[0].remote_cache_minutes_saved + timeSavedData.data[0].local_cache_minutes_saved; - const totalYearsSaved: number = Math.floor(totalMinutesSaved / 60 / 24 / 365); + const totalYearsSaved: number = Math.floor( + totalMinutesSaved / MINUTES_IN_YEAR + ); const weeklyDownloads: number = Object.keys(downloadsData.downloads).reduce( (sum, version) => sum + downloadsData.downloads[version], 0 diff --git a/packages/turbo-gen/src/commands/generate/prompts.ts b/packages/turbo-gen/src/commands/generate/prompts.ts index ade3f80116b26..36b95df80d662 100644 --- a/packages/turbo-gen/src/commands/generate/prompts.ts +++ b/packages/turbo-gen/src/commands/generate/prompts.ts @@ -45,3 +45,30 @@ export async function customGenerators({ return generatorAnswer; } + +export async function chooseGeneratorTemplate() { + return await inquirer.prompt<{ answer: "ts" | "js" }>({ + type: "list", + name: "answer", + message: "Should the generator config be created with TS or JS?", + default: "ts", + choices: [ + { + name: "js", + value: "js", + }, + { + name: "ts", + value: "ts", + }, + ], + }); +} + +export async function confirm({ message }: { message: string }) { + return await inquirer.prompt<{ answer: boolean }>({ + type: "confirm", + name: "answer", + message, + }); +} diff --git a/packages/turbo-gen/src/generators/custom.ts b/packages/turbo-gen/src/generators/custom.ts index 656ea394e646c..eeb52dd4f7768 100644 --- a/packages/turbo-gen/src/generators/custom.ts +++ b/packages/turbo-gen/src/generators/custom.ts @@ -4,17 +4,39 @@ import { getCustomGenerators, runCustomGenerator } from "../utils/plop"; import * as prompts from "../commands/generate/prompts"; import type { CustomGeneratorArguments } from "./types"; import { GeneratorError } from "../utils/error"; +import { setupFromTemplate } from "../utils/setupFromTemplate"; export async function generate({ generator, project, opts, }: CustomGeneratorArguments) { - const generators = getCustomGenerators({ project, configPath: opts.config }); + let generators = getCustomGenerators({ project, configPath: opts.config }); if (!generators.length) { logger.error(`No custom generators found.`); console.log(); - return; + + const { answer } = await prompts.confirm({ + message: `Would you like to add generators to ${project.name}?`, + }); + + if (answer) { + const { answer: template } = await prompts.chooseGeneratorTemplate(); + try { + await setupFromTemplate({ project, template }); + } catch (err) { + if (err instanceof GeneratorError) { + throw err; + } + logger.error(`Failed to create generator config`); + throw err; + } + + // fetch generators again, and continue to selection prompt + generators = getCustomGenerators({ project, configPath: opts.config }); + } else { + return; + } } const { selectedGenerator } = await prompts.customGenerators({ generators, diff --git a/packages/turbo-gen/src/templates/simple-js/config.js b/packages/turbo-gen/src/templates/simple-js/config.js new file mode 100644 index 0000000000000..4b2f3aaa09e41 --- /dev/null +++ b/packages/turbo-gen/src/templates/simple-js/config.js @@ -0,0 +1,31 @@ +module.exports = function generator(plop) { + plop.setGenerator("example", { + description: + "An example Turborepo generator - creates a new file at the root of the project", + prompts: [ + { + type: "input", + name: "file", + message: "What is the name of the file to create?", + }, + { + type: "input", + name: "author", + message: "What is your name? (Will be added as the file author)", + }, + { + type: "list", + name: "type", + message: "What type of file should be created?", + choices: [".md", ".txt"], + }, + ], + actions: [ + { + type: "add", + path: "{{ turbo.paths.root }}/{{ dashCase file }}{{ type }}", + templateFile: "templates/turborepo-generators.hbs", + }, + ], + }); +}; diff --git a/packages/turbo-gen/src/templates/simple-js/templates/turborepo-generators.hbs b/packages/turbo-gen/src/templates/simple-js/templates/turborepo-generators.hbs new file mode 100644 index 0000000000000..2b7e200b2f0e7 --- /dev/null +++ b/packages/turbo-gen/src/templates/simple-js/templates/turborepo-generators.hbs @@ -0,0 +1,5 @@ +# Turborepo Generators + +Read the docs at [turbo.build](https://turbo.build/repo/docs). + +Created by {{ author }}. diff --git a/packages/turbo-gen/src/templates/simple-ts/config.ts b/packages/turbo-gen/src/templates/simple-ts/config.ts new file mode 100644 index 0000000000000..ae6a49b933d25 --- /dev/null +++ b/packages/turbo-gen/src/templates/simple-ts/config.ts @@ -0,0 +1,33 @@ +import { PlopTypes } from "@turbo/gen"; + +export default function generator(plop: PlopTypes.NodePlopAPI): void { + plop.setGenerator("example", { + description: + "An example Turborepo generator - creates a new file at the root of the project", + prompts: [ + { + type: "input", + name: "file", + message: "What is the name of the file to create?", + }, + { + type: "input", + name: "author", + message: "What is your name? (Will be added as the file author)", + }, + { + type: "list", + name: "type", + message: "What type of file should be created?", + choices: [".md", ".txt"], + }, + ], + actions: [ + { + type: "add", + path: "{{ turbo.paths.root }}/{{ dashCase file }}{{ type }}", + templateFile: "templates/turborepo-generators.hbs", + }, + ], + }); +} diff --git a/packages/turbo-gen/src/templates/simple-ts/templates/turborepo-generators.hbs b/packages/turbo-gen/src/templates/simple-ts/templates/turborepo-generators.hbs new file mode 100644 index 0000000000000..2b7e200b2f0e7 --- /dev/null +++ b/packages/turbo-gen/src/templates/simple-ts/templates/turborepo-generators.hbs @@ -0,0 +1,5 @@ +# Turborepo Generators + +Read the docs at [turbo.build](https://turbo.build/repo/docs). + +Created by {{ author }}. diff --git a/packages/turbo-gen/src/utils/error.ts b/packages/turbo-gen/src/utils/error.ts index d01273c64da4c..13dc78bab8020 100644 --- a/packages/turbo-gen/src/utils/error.ts +++ b/packages/turbo-gen/src/utils/error.ts @@ -3,6 +3,7 @@ export type GenerateErrorType = | "plop_error_running_generator" | "plop_unable_to_load_config" | "plop_generator_not_found" + | "config_directory_already_exists" // default | "unknown"; diff --git a/packages/turbo-gen/src/utils/setupFromTemplate.ts b/packages/turbo-gen/src/utils/setupFromTemplate.ts new file mode 100644 index 0000000000000..5be65fca32b44 --- /dev/null +++ b/packages/turbo-gen/src/utils/setupFromTemplate.ts @@ -0,0 +1,30 @@ +import type { Project } from "@turbo/workspaces"; +import path from "path"; +import fs from "fs-extra"; +import { GeneratorError } from "./error"; + +export async function setupFromTemplate({ + project, + template, +}: { + project: Project; + template: "ts" | "js"; +}) { + const configDirectory = path.join(project.paths.root, "turbo", "generators"); + + // TODO: could create some more complex starters in the future + const toCopy = `simple-${template}`; + + // required to ensure we don't overwrite any existing files at this location + if (await fs.pathExists(configDirectory)) { + throw new GeneratorError( + `Generator config directory already exists at ${configDirectory}`, + { type: "config_directory_already_exists" } + ); + } + + // copy templates to project + await fs.copy(path.join(__dirname, "templates", toCopy), configDirectory, { + recursive: true, + }); +} diff --git a/packages/turbo-gen/tsup.config.ts b/packages/turbo-gen/tsup.config.ts index 8188f75886a34..9e4678cd7221d 100644 --- a/packages/turbo-gen/tsup.config.ts +++ b/packages/turbo-gen/tsup.config.ts @@ -1,4 +1,6 @@ import { defineConfig, Options } from "tsup"; +import fs from "fs-extra"; +import chalk from "chalk"; export default defineConfig((options: Options) => ({ entry: ["src/cli.ts", "src/types.ts"], @@ -6,5 +8,16 @@ export default defineConfig((options: Options) => ({ dts: true, clean: true, minify: true, + onSuccess: async () => { + // start time + const start = Date.now(); + await fs.copy("src/templates", "dist/templates"); + // make the output match + console.log( + chalk.hex("#7c5cad")("TEMPLATES"), + "copied in", + chalk.green(`${Date.now() - start}ms`) + ); + }, ...options, })); diff --git a/turbo/generators/config.ts b/turbo/generators/config.ts new file mode 100644 index 0000000000000..4fab73712ab6c --- /dev/null +++ b/turbo/generators/config.ts @@ -0,0 +1,36 @@ +import { PlopTypes } from "@turbo/gen"; + +export default function generator(plop: PlopTypes.NodePlopAPI): void { + plop.setGenerator("example", { + description: + "An example Turborepo generator - creates a new file at the root of the project", + prompts: [ + { + type: "input", + name: "file", + placeholder: "generator-docs", + message: "What is the name of the file to create?", + }, + { + type: "input", + name: "author", + default: "turbobot", + message: "What is your name? (Will be added as the file author)", + }, + { + type: "list", + name: "type", + message: "What type of file should be created?", + choices: [".md", ".txt"], + default: ".md", + }, + ], + actions: [ + { + type: "add", + path: "{{ turbo.paths.root }}/{{ dashCase file }}{{ type }}", + templateFile: "templates/turborepo-generators.hbs", + }, + ], + }); +} diff --git a/turbo/generators/templates/turborepo-generators.hbs b/turbo/generators/templates/turborepo-generators.hbs new file mode 100644 index 0000000000000..2b7e200b2f0e7 --- /dev/null +++ b/turbo/generators/templates/turborepo-generators.hbs @@ -0,0 +1,5 @@ +# Turborepo Generators + +Read the docs at [turbo.build](https://turbo.build/repo/docs). + +Created by {{ author }}.