From 935213f903ccc38c91ad8a7df078a9fe4a69b73b Mon Sep 17 00:00:00 2001 From: Maxwell Brown Date: Tue, 10 Sep 2024 15:31:54 -0400 Subject: [PATCH] Improve / cleanup the templates (#31) --- .changeset/tender-files-sneeze.md | 5 + package.json | 7 +- packages/create-effect-app/README.md | 121 +++++ packages/create-effect-app/package.json | 14 +- packages/create-effect-app/src/Cli.ts | 412 ++++++++++++++---- packages/create-effect-app/src/Domain.ts | 20 +- packages/create-effect-app/src/GitHub.ts | 26 +- packages/create-effect-app/src/Utils.ts | 29 +- packages/create-effect-app/src/bin.ts | 14 +- .../src/internal/examples.ts | 3 + .../src/internal/templates.ts | 5 + pnpm-lock.yaml | 246 ++++++----- scripts/examples.mjs | 52 ++- scripts/examples.template.txt | 5 +- scripts/templates.mjs | 45 ++ scripts/templates.template.txt | 5 + templates/basic/.changeset/config.json | 16 + .../basic/.github/actions/setup/action.yml | 21 + templates/basic/.github/workflows/check.yml | 57 +++ templates/basic/.github/workflows/release.yml | 34 ++ .../basic/.github/workflows/snapshot.yml | 24 + templates/basic/.madgerc | 7 - templates/basic/deno.json | 3 - templates/basic/flake.lock | 6 +- templates/basic/package.json | 80 +++- ...ngesets__assemble-release-plan@6.0.3.patch | 26 ++ .../@changesets__get-github-info@0.6.0.patch | 48 ++ .../{monorepo => basic}/scripts/circular.mjs | 0 templates/cli/.changeset/config.json | 16 + .../cli/.github/actions/setup/action.yml | 21 + templates/cli/.github/workflows/check.yml | 54 +++ templates/cli/.github/workflows/release.yml | 0 templates/cli/.github/workflows/snapshot.yml | 24 + templates/cli/.prettierignore | 3 + templates/cli/.prettierrc.json | 4 + templates/cli/flake.lock | 6 +- templates/cli/flake.nix | 1 + templates/cli/package.json | 42 +- ...ngesets__assemble-release-plan@6.0.3.patch | 26 ++ .../@changesets__get-github-info@0.6.0.patch | 48 ++ templates/cli/scripts/copy-package-json.ts | 52 +-- templates/cli/tsconfig.base.json | 45 ++ templates/cli/tsconfig.eslint.json | 17 - templates/cli/tsconfig.json | 27 +- templates/cli/tsconfig.scripts.json | 11 + templates/cli/tsconfig.src.json | 10 + templates/cli/tsconfig.test.json | 10 + templates/cli/tsup.config.ts | 2 +- templates/monorepo/.changeset/config.json | 16 + .../monorepo/.github/actions/setup/action.yml | 21 + .../monorepo/.github/workflows/check.yml | 58 +++ .../monorepo/.github/workflows/release.yml | 31 ++ .../monorepo/.github/workflows/snapshot.yml | 22 + templates/monorepo/.madgerc | 7 - templates/monorepo/deno.json | 3 - templates/monorepo/flake.lock | 6 +- templates/monorepo/package.json | 33 +- templates/monorepo/packages/cli/package.json | 11 - .../monorepo/packages/domain/package.json | 14 +- .../monorepo/packages/domain/src/Todo.ts | 20 + .../monorepo/packages/server/package.json | 11 - ...ngesets__assemble-release-plan@6.0.3.patch | 26 ++ .../@changesets__get-github-info@0.6.0.patch | 48 ++ templates/monorepo/scripts/clean.mjs | 10 +- 64 files changed, 1680 insertions(+), 407 deletions(-) create mode 100644 .changeset/tender-files-sneeze.md create mode 100644 packages/create-effect-app/README.md create mode 100644 packages/create-effect-app/src/internal/templates.ts create mode 100644 scripts/templates.mjs create mode 100644 scripts/templates.template.txt create mode 100644 templates/basic/.changeset/config.json create mode 100644 templates/basic/.github/actions/setup/action.yml create mode 100644 templates/basic/.github/workflows/check.yml create mode 100644 templates/basic/.github/workflows/release.yml create mode 100644 templates/basic/.github/workflows/snapshot.yml delete mode 100644 templates/basic/.madgerc delete mode 100644 templates/basic/deno.json create mode 100644 templates/basic/patches/@changesets__assemble-release-plan@6.0.3.patch create mode 100644 templates/basic/patches/@changesets__get-github-info@0.6.0.patch rename templates/{monorepo => basic}/scripts/circular.mjs (100%) create mode 100644 templates/cli/.changeset/config.json create mode 100644 templates/cli/.github/actions/setup/action.yml create mode 100644 templates/cli/.github/workflows/check.yml create mode 100644 templates/cli/.github/workflows/release.yml create mode 100644 templates/cli/.github/workflows/snapshot.yml create mode 100644 templates/cli/.prettierignore create mode 100644 templates/cli/.prettierrc.json create mode 100644 templates/cli/patches/@changesets__assemble-release-plan@6.0.3.patch create mode 100644 templates/cli/patches/@changesets__get-github-info@0.6.0.patch create mode 100644 templates/cli/tsconfig.base.json delete mode 100644 templates/cli/tsconfig.eslint.json create mode 100644 templates/cli/tsconfig.scripts.json create mode 100644 templates/cli/tsconfig.src.json create mode 100644 templates/cli/tsconfig.test.json create mode 100644 templates/monorepo/.changeset/config.json create mode 100644 templates/monorepo/.github/actions/setup/action.yml create mode 100644 templates/monorepo/.github/workflows/check.yml create mode 100644 templates/monorepo/.github/workflows/release.yml create mode 100644 templates/monorepo/.github/workflows/snapshot.yml delete mode 100644 templates/monorepo/.madgerc delete mode 100644 templates/monorepo/deno.json create mode 100644 templates/monorepo/packages/domain/src/Todo.ts create mode 100644 templates/monorepo/patches/@changesets__assemble-release-plan@6.0.3.patch create mode 100644 templates/monorepo/patches/@changesets__get-github-info@0.6.0.patch diff --git a/.changeset/tender-files-sneeze.md b/.changeset/tender-files-sneeze.md new file mode 100644 index 0000000..33b4bb1 --- /dev/null +++ b/.changeset/tender-files-sneeze.md @@ -0,0 +1,5 @@ +--- +"create-effect-app": patch +--- + +add additional cli options for controlling features and configs diff --git a/package.json b/package.json index 87ef5f0..bebe017 100644 --- a/package.json +++ b/package.json @@ -10,23 +10,24 @@ "check": "pnpm --recursive --parallel run check", "lint": "eslint \"**/{src,test,examples,scripts}/**/*.{ts,mjs}\"", "lint-fix": "pnpm lint --fix", - "changeset-version": "changeset version && node ./scripts/version.mjs && node ./scripts/examples.mjs", + "changeset-version": "changeset version && node ./scripts/version.mjs && node ./scripts/examples.mjs && node ./scripts/templates.mjs", "changeset-publish": "pnpm build && TEST_DIST= pnpm vitest && changeset publish" }, "devDependencies": { "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.7", + "@dprint/formatter": "^0.4.1", "@effect/eslint-plugin": "^0.2.0", "@effect/language-service": "^0.1.0", "@effect/vitest": "^0.9.2", "@eslint/compat": "^1.1.1", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.9.1", + "@eslint/js": "^9.10.0", "@types/node": "^22.5.4", "@typescript-eslint/eslint-plugin": "^8.4.0", "@typescript-eslint/parser": "^8.4.0", "effect": "^3.7.2", - "eslint": "^9.9.1", + "eslint": "^9.10.0", "eslint-import-resolver-typescript": "^3.6.3", "eslint-plugin-codegen": "^0.28.0", "eslint-plugin-deprecation": "^3.0.0", diff --git a/packages/create-effect-app/README.md b/packages/create-effect-app/README.md new file mode 100644 index 0000000..18e532f --- /dev/null +++ b/packages/create-effect-app/README.md @@ -0,0 +1,121 @@ +# Create Effect App + +The `create-effect-app` command-line application is a tool which allows you to quickly bootstrap new Effect applications from either a template or from an official Effect example application. + +## Getting Started + +There are two main ways to use `create-effect-app`: + +### Interactive + +The easiest way to use `create-effect-app` is interactively via your preferred package manager: + +*`npm`* + +```sh +npx create-effect-app [project-name] +``` + +*`pnpm`* + +```sh +pnpm create effect-app [project-name] +``` + +*`yarn`* + +```sh +yarn create effect-app [project-name] +``` + +*`bun`* + +```sh +bunx create-effect-app [project-name] +``` + +You will then be prompted to select the type of project you want to create and customize your project with additional options (see the full [usage](#usage) documentation below). + +### Non-Interactive + +You can also invoke the `create-effect-app` CLI non-interactively: + +#### Usage + +```sh +Create Effect App + +USAGE + +$ create-effect-app [(-t, --template basic | cli | monorepo) [--changesets] [--flake] [--prettier] [--eslint] [--workflows]] [] + +$ create-effect-app [(-e, --example http-server)] [] + +DESCRIPTION + +Create an Effect application from an example or a template repository + +ARGUMENTS + + + + A directory that must not exist. + + The folder to output the Effect application code into + + This setting is optional. + +OPTIONS + +(-e, --example http-server) + + One of the following: http-server + + The name of an official Effect example to use to bootstrap the application + +(-t, --template basic | cli | monorepo) + + One of the following: basic, cli, monorepo + + The name of an official Effect example to use to bootstrap the application + +--changesets + + A true or false value. + + Initialize project with Changesets + + This setting is optional. + +--flake + + A true or false value. + + Initialize project with a Nix flake + + This setting is optional. + +--prettier + + A true or false value. + + Initialize project with Prettier + + This setting is optional. + +--eslint + + A true or false value. + + Initialize project with ESLint + + This setting is optional. + +--workflows + + A true or false value. + + Initialize project with Effect's recommended GitHub actions + + This setting is optional. +``` diff --git a/packages/create-effect-app/package.json b/packages/create-effect-app/package.json index 17eb7a0..5483cb7 100644 --- a/packages/create-effect-app/package.json +++ b/packages/create-effect-app/package.json @@ -8,7 +8,7 @@ "create-effect-app": "./dist/bin.cjs" }, "engines": { - "node": ">=20.14.0" + "node": ">=18.0.0" }, "repository": { "type": "git", @@ -28,13 +28,15 @@ "check": "tsc -b tsconfig.json" }, "devDependencies": { - "@effect/cli": "^0.42.2", - "@effect/platform": "^0.63.2", - "@effect/platform-node": "^0.58.2", + "@effect/cli": "^0.43.2", + "@effect/platform": "^0.64.0", + "@effect/platform-node": "^0.59.0", "@effect/printer": "^0.35.2", "@effect/printer-ansi": "^0.35.2", "effect": "^3.7.2", "tar": "^7.4.3", - "tsup": "^8.2.4" + "tsup": "^8.2.4", + "yaml": "^2.5.1" } -} \ No newline at end of file +} + diff --git a/packages/create-effect-app/src/Cli.ts b/packages/create-effect-app/src/Cli.ts index ee1b986..60e2883 100644 --- a/packages/create-effect-app/src/Cli.ts +++ b/packages/create-effect-app/src/Cli.ts @@ -6,41 +6,107 @@ import * as FileSystem from "@effect/platform/FileSystem" import * as Path from "@effect/platform/Path" import * as Ansi from "@effect/printer-ansi/Ansi" import * as AnsiDoc from "@effect/printer-ansi/AnsiDoc" +import * as Array from "effect/Array" import * as Effect from "effect/Effect" +import * as Match from "effect/Match" import * as Option from "effect/Option" -import type { TemplateType } from "./Domain.js" +import * as Yaml from "yaml" +import { ProjectType } from "./Domain.js" import { GitHub } from "./GitHub.js" -import * as InternalExamples from "./internal/examples.js" +import type { Example } from "./internal/examples.js" +import { examples } from "./internal/examples.js" +import { type Template, templates } from "./internal/templates.js" import * as InternalVersion from "./internal/version.js" import { validateProjectName } from "./Utils.js" -const example = Options.choice("example", InternalExamples.examples).pipe( +// ============================================================================= +// CLI Specification +// ============================================================================= + +const projectName = Args.directory({ name: "project-name", exists: "no" }).pipe( + Args.withDescription("The folder to output the Effect application code into"), + Args.mapEffect(validateProjectName), + Args.mapEffect((projectName) => Effect.map(Path.Path, (path) => path.resolve(projectName))), + Args.optional +) + +const exampleType = Options.choice("example", examples).pipe( Options.withAlias("e"), Options.withDescription( "The name of an official Effect example to use to bootstrap the application" - ), - Options.optional + ) +) + +const templateType = Options.choice("template", templates).pipe( + Options.withAlias("t"), + Options.withDescription( + "The name of an official Effect example to use to bootstrap the application" + ) +) + +const withChangesets = Options.boolean("changesets").pipe( + Options.withDescription("Initialize project with Changesets") +) + +const withNixFlake = Options.boolean("flake").pipe( + Options.withDescription("Initialize project with a Nix flake") ) -const projectName = Args.directory({ - name: "project-name", - exists: "no" +const withPrettier = Options.boolean("prettier").pipe( + Options.withDescription("Initialize project with Prettier") +) + +const withESLint = Options.boolean("eslint").pipe( + Options.withDescription("Initialize project with ESLint") +) + +const withWorkflows = Options.boolean("workflows").pipe( + Options.withDescription("Initialize project with Effect's recommended GitHub actions") +) + +const projectType: Options.Options> = Options.all({ + example: exampleType }).pipe( - Args.withDescription("The directory to output the Effect application code into"), - Args.optional + Options.map(ProjectType.Example), + Options.orElse( + Options.all({ + template: templateType, + withChangesets, + withNixFlake, + withPrettier, + withESLint, + withWorkflows + }).pipe(Options.map(ProjectType.Template)) + ), + Options.optional ) -const command = Command.make("create-effect-app", { example, projectName }).pipe( +export interface RawConfig { + readonly projectName: Option.Option + readonly projectType: Option.Option +} + +export interface ResolvedConfig { + readonly projectName: string + readonly projectType: ProjectType +} + +export interface ExampleConfig extends ResolvedConfig { + readonly projectType: Extract +} + +export interface TemplateConfig extends ResolvedConfig { + readonly projectType: Extract +} + +const options = { + projectName, + projectType +} + +const command = Command.make("create-effect-app", options).pipe( Command.withDescription("Create an Effect application from an example or a template repository"), - Command.withHandler(({ example, projectName }) => - Effect.gen(function*() { - const projectPath = yield* resolveProjectPath(projectName) - return yield* Option.match(example, { - onNone: () => createTemplate(projectPath), - onSome: (example) => createExample(projectPath, example) - }) - }) - ) + Command.withHandler(handleCommand) ) export const cli = Command.run(command, { @@ -48,102 +114,298 @@ export const cli = Command.run(command, { version: `v${InternalVersion.moduleVersion}` }) -function resolveProjectPath(projectName: Option.Option) { - return Effect.gen(function*() { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path - return yield* Option.match(projectName, { - onSome: (name) => Effect.succeed(path.resolve(name)), - onNone: () => - Prompt.text({ - message: "What is your project named?", - default: "effect-app", - validate: (name) => - Option.match(validateProjectName(name), { - onNone: () => - Effect.if(Effect.orDie(fs.exists(path.resolve(name))), { - onTrue: () => Effect.fail(`The path ${path.resolve(name)} already exists`), - onFalse: () => Effect.succeed(name) - }), - onSome: Effect.fail - }) - }) - }) +// ============================================================================= +// Utilities +// ============================================================================= + +function handleCommand(config: RawConfig) { + return Effect.all({ + projectName: resolveProjectName(config), + projectType: resolveProjectType(config) + }).pipe(Effect.flatMap(createProject)) +} + +const createProject = Match.type().pipe( + Match.when({ projectType: { _tag: "Example" } }, (config) => createExample(config)), + Match.when({ projectType: { _tag: "Template" } }, (config) => createTemplate(config)), + Match.orElseAbsurd +) + +function resolveProjectName(config: RawConfig) { + return Option.match(config.projectName, { + onSome: Effect.succeed, + onNone: () => + Prompt.text({ + message: "What is your project named?", + default: "effect-app" + }).pipe(Effect.flatMap((name) => Path.Path.pipe(Effect.map((path) => path.resolve(name))))) + }) +} + +function resolveProjectType(config: RawConfig) { + return Option.match(config.projectType, { + onSome: Effect.succeed, + onNone: () => Prompt.run(getUserInput) }) } -function createExample(projectPath: string, example: string) { +/** + * Examples are simply cloned as is from GitHub + */ +function createExample(config: ExampleConfig) { return Effect.gen(function*() { const fs = yield* FileSystem.FileSystem yield* Effect.logInfo(AnsiDoc.hsep([ AnsiDoc.text("Creating a new Effect application in"), - AnsiDoc.text(projectPath).pipe(AnsiDoc.annotate(Ansi.green)) + AnsiDoc.text(config.projectName).pipe(AnsiDoc.annotate(Ansi.green)) ])) // Create the project path - yield* fs.makeDirectory(projectPath) + yield* fs.makeDirectory(config.projectName, { recursive: true }) yield* Effect.logInfo(AnsiDoc.hsep([ AnsiDoc.text("Initializing example project:"), - AnsiDoc.text(example).pipe(AnsiDoc.annotate(Ansi.magenta)) + AnsiDoc.text(config.projectType.example).pipe(AnsiDoc.annotate(Ansi.magenta)) ])) // Download the example project from GitHub - yield* GitHub.downloadExample(projectPath, example) + yield* GitHub.downloadExample(config) yield* Effect.logInfo(AnsiDoc.hsep([ AnsiDoc.text("Success!").pipe(AnsiDoc.annotate(Ansi.green)), - AnsiDoc.text(`Effect example application was initialized in ${projectPath}`) + AnsiDoc.text(`Effect example application was initialized in ${config.projectName}`) ])) }) } -function createTemplate(projectPath: string) { +/** + * Templates are cloned from GitHub and then resolved against the preferences + * specified by the user + */ +function createTemplate(config: TemplateConfig) { return Effect.gen(function*() { const fs = yield* FileSystem.FileSystem - - // Prompt user for the project template to use - const template = yield* Prompt.select({ - message: "What project template should be used?", - choices: [ - { - title: "Basic", - value: "basic", - description: "A project containing a single package" - }, - { - title: "Monorepo", - value: "monorepo", - description: "A project containing multiple packages" - }, - { - title: "CLI Application", - value: "cli", - description: "A project containing a CLI application" - } - ] - }) + const path = yield* Path.Path yield* Effect.logInfo(AnsiDoc.hsep([ AnsiDoc.text("Creating a new Effect project in"), - AnsiDoc.text(projectPath).pipe(AnsiDoc.annotate(Ansi.green)) + AnsiDoc.text(config.projectName).pipe(AnsiDoc.annotate(Ansi.green)) ])) // Create the project directory - yield* fs.makeDirectory(projectPath) + yield* fs.makeDirectory(config.projectName, { recursive: true }) yield* Effect.logInfo(AnsiDoc.hsep([ AnsiDoc.text("Initializing project with template:"), - AnsiDoc.text(template).pipe(AnsiDoc.annotate(Ansi.magenta)) + AnsiDoc.text(config.projectType.template).pipe(AnsiDoc.annotate(Ansi.magenta)) ])) // Download the template project from GitHub - yield* GitHub.downloadTemplate(projectPath, template) + yield* GitHub.downloadTemplate(config) + + const packageJson = yield* fs.readFileString(path.join(config.projectName, "package.json")).pipe( + Effect.map((json) => JSON.parse(json)) + ) + + // Handle user preferences for changesets + if (!config.projectType.withChangesets) { + // Remove the .changesets directory + yield* fs.remove(path.join(config.projectName, ".changeset"), { + recursive: true + }) + // Remove patches for changesets + const patches = yield* fs.readDirectory(path.join(config.projectName, "patches")).pipe( + Effect.map(Array.filter((file) => file.includes("changeset"))) + ) + yield* Effect.forEach(patches, (patch) => fs.remove(path.join(config.projectName, "patches", patch))) + // Remove patched dependencies for changesets + const depsToRemove = Array.filter( + Object.keys(packageJson["pnpm"]["patchedDependencies"]), + (key) => key.includes("changeset") + ) + for (const patch of depsToRemove) { + delete packageJson["pnpm"]["patchedDependencies"][patch] + } + // Remove scripts for changesets + const scriptsToRemove = Array.filter( + Object.keys(packageJson["scripts"]), + (key) => key.includes("changeset") + ) + for (const script of scriptsToRemove) { + delete packageJson["scripts"][script] + } + // Remove packages for changesets + const pkgsToRemove = Array.filter( + Object.keys(packageJson["devDependencies"]), + (key) => key.includes("changeset") + ) + for (const pkg of pkgsToRemove) { + delete packageJson["devDependencies"][pkg] + } + // If git workflows are enabled, remove changesets related workflows + if (config.projectType.withWorkflows) { + yield* fs.remove(path.join(config.projectName, ".github", "workflows", "release.yml")) + } + } + + // Handle user preferences for Nix flakes + if (!config.projectType.withNixFlake) { + yield* Effect.forEach( + [".envrc", "flake.lock", "flake.nix"], + (file) => fs.remove(path.join(config.projectName, file)) + ) + } + + // Handle user preferences for Prettier + if (!config.projectType.withPrettier) { + // Remove prettier configuration files + yield* Effect.forEach( + [".prettierignore", ".prettierrc.json"], + (file) => fs.remove(path.join(config.projectName, file)) + ) + // Remove prettier from dependencies + delete packageJson["devDependencies"]["prettier"] + } + + // Handle user preferences for ESLint + if (!config.projectType.withESLint) { + // Remove eslint.config.mjs + yield* fs.remove(path.join(config.projectName, "eslint.config.mjs")) + // Remove eslint dependencies + const eslintDeps = Array.filter( + Object.keys(packageJson["devDependencies"]), + (key) => key.includes("eslint") + ) + for (const dep of eslintDeps) { + delete packageJson["devDependencies"][dep] + } + // Remove linting scripts + const scriptsToRemove = Array.filter( + Object.keys(packageJson["scripts"]), + (key) => key.includes("lint") + ) + for (const script of scriptsToRemove) { + delete packageJson["scripts"][script] + } + // If git workflows are enabled, remove lint workflows + if (config.projectType.withWorkflows) { + const checkWorkflowPath = path.join(config.projectName, ".github", "workflows", "check.yml") + const checkWorkflow = yield* fs.readFileString(checkWorkflowPath) + const checkYaml = Yaml.parse(checkWorkflow) + delete checkYaml["jobs"]["lint"] + yield* fs.writeFileString(checkWorkflowPath, Yaml.stringify(checkYaml, undefined, 2)) + } + } + + // Handle user preferences for GitHub workflows + if (!config.projectType.withWorkflows) { + // Remove the .github directory + yield* fs.remove(path.join(config.projectName, ".github"), { + recursive: true + }) + } + + // Write out the updated package.json + yield* fs.writeFileString( + path.join(config.projectName, "package.json"), + JSON.stringify(packageJson, undefined, 2) + ) yield* Effect.logInfo(AnsiDoc.hsep([ AnsiDoc.text("Success!").pipe(AnsiDoc.annotate(Ansi.green)), - AnsiDoc.text(`Effect template project was initialized in ${projectPath}`) + AnsiDoc.text(`Effect template project was initialized in:`), + AnsiDoc.hardLine, + AnsiDoc.indent(AnsiDoc.text(config.projectName).pipe(AnsiDoc.annotate(Ansi.magenta)), 2) ])) + + if (config.projectType.withChangesets) { + yield* Effect.logInfo(AnsiDoc.hsep([ + AnsiDoc.text("Make sure to update the Changesets configuration file"), + AnsiDoc.text("with your target GitHub repository for changelog links:"), + AnsiDoc.hardLine, + AnsiDoc.text(path.join(config.projectName, ".changeset", "config.json")).pipe( + AnsiDoc.annotate(Ansi.magenta), + AnsiDoc.indent(2) + ) + ])) + } }) } + +const getUserInput = Prompt.select<"example" | "template">({ + message: "What type of project would you like to create?", + choices: [ + { + title: "Template", + value: "template", + description: "A template project suitable for a package or application" + }, + { + title: "Example", + value: "example", + description: "An example project demonstrating usage of Effect" + } + ] +}).pipe(Prompt.flatMap((type): Prompt.Prompt => { + switch (type) { + case "example": { + return Prompt.all({ + example: Prompt.select({ + message: "What project template should be used?", + choices: [ + { + title: "HTTP Server", + value: "http-server", + description: "An HTTP server application with authentication / authorization" + } + ] + }) + }).pipe(Prompt.map(ProjectType.Example)) + } + case "template": { + return Prompt.all({ + template: Prompt.select