From 88d18d8c74e250203205f3cfbb467861a6fb05ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BE=C3=B3r=20Magn=C3=BAsson?= Date: Thu, 31 Jan 2019 15:29:02 +0100 Subject: [PATCH] refactor(commands): remove create commands BREAKING CHANGE: After this, the `create project` and `create module` commands will no longer be available. We're removing them for now because currently they're more confusing than they are useful. There's an open Github issue (#312) for properly implementing the "create" functionality. --- docs/reference/commands.md | 62 ------ docs/using-garden/features-and-usage.md | 58 +++--- garden-service/src/commands/commands.ts | 2 - .../src/commands/create/config-templates.ts | 106 ---------- garden-service/src/commands/create/create.ts | 25 --- garden-service/src/commands/create/helpers.ts | 52 ----- garden-service/src/commands/create/module.ts | 119 ----------- garden-service/src/commands/create/project.ts | 184 ------------------ garden-service/src/commands/create/prompts.ts | 128 ------------ .../test-project-create-command/garden.yml | 2 - .../module-a/child-module-a/garden.yml | 11 -- .../module-a/garden.yml | 11 -- .../module-b/child-module-b/garden.yml | 11 -- .../module-b/garden.yml | 11 -- .../src/commands/create/config-templates.ts | 40 ---- .../test/src/commands/create/module.ts | 154 --------------- .../test/src/commands/create/project.ts | 163 ---------------- 17 files changed, 28 insertions(+), 1111 deletions(-) delete mode 100644 garden-service/src/commands/create/config-templates.ts delete mode 100644 garden-service/src/commands/create/create.ts delete mode 100644 garden-service/src/commands/create/helpers.ts delete mode 100644 garden-service/src/commands/create/module.ts delete mode 100644 garden-service/src/commands/create/project.ts delete mode 100644 garden-service/src/commands/create/prompts.ts delete mode 100644 garden-service/test/data/test-project-create-command/garden.yml delete mode 100644 garden-service/test/data/test-project-create-command/module-a/child-module-a/garden.yml delete mode 100644 garden-service/test/data/test-project-create-command/module-a/garden.yml delete mode 100644 garden-service/test/data/test-project-create-command/module-b/child-module-b/garden.yml delete mode 100644 garden-service/test/data/test-project-create-command/module-b/garden.yml delete mode 100644 garden-service/test/src/commands/create/config-templates.ts delete mode 100644 garden-service/test/src/commands/create/module.ts delete mode 100644 garden-service/test/src/commands/create/project.ts diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 305de69278..c3507fe439 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -75,68 +75,6 @@ Note: Currently only supports simple GET requests for HTTP/HTTPS ingresses. | -------- | -------- | ----------- | | `serviceAndPath` | Yes | The name of the service to call followed by the ingress path (e.g. my-container/somepath). -### garden create project - -Creates a new Garden project. - -Walks the user through setting up a new Garden project and -generates scaffolding based on user input. - -Examples: - - garden create project # creates a new Garden project in the current directory (project name defaults to - directory name) - garden create project my-project # creates a new Garden project in my-project directory - garden create project --module-dirs=path/to/modules-a,path/to/modules-b - # creates a new Garden project and looks for pre-existing modules in the modules-a and modules-b directories - garden create project --name my-project - # creates a new Garden project in the current directory and names it my-project - -##### Usage - - garden create project [project-dir] [options] - -##### Arguments - -| Argument | Required | Description | -| -------- | -------- | ----------- | - | `project-dir` | No | Directory of the project (defaults to current directory). - -##### Options - -| Argument | Alias | Type | Description | -| -------- | ----- | ---- | ----------- | - | `--module-dirs` | | array:path | Relative path to modules directory. Use comma as a separator to specify multiple directories. - | `--name` | | string | Assigns a custom name to the project (defaults to name of the current directory). - -### garden create module - -Creates a new Garden module. - -Examples: - - garden create module # creates a new module in the current directory (module name defaults to directory name) - garden create module my-module # creates a new module in my-module directory - garden create module --type=container # creates a new container module - garden create module --name=my-module # creates a new module in current directory and names it my-module - -##### Usage - - garden create module [module-dir] [options] - -##### Arguments - -| Argument | Required | Description | -| -------- | -------- | ----------- | - | `module-dir` | No | Directory of the module (defaults to current directory). - -##### Options - -| Argument | Alias | Type | Description | -| -------- | ----- | ---- | ----------- | - | `--name` | | string | Assigns a custom name to the module (defaults to name of the current directory) - | `--type` | | `container` `google-cloud-function` `npm-package` | Type of module. - ### garden delete secret Delete a secret from the environment. diff --git a/docs/using-garden/features-and-usage.md b/docs/using-garden/features-and-usage.md index 5984b4e861..79e6399b9d 100644 --- a/docs/using-garden/features-and-usage.md +++ b/docs/using-garden/features-and-usage.md @@ -4,40 +4,38 @@ Now that you've had a glimpse of the basic Garden commands in the [Quick Start]( ## Starting a new project -There are two ways to start a new project with Garden: +To start a new project, you create a `garden.yml` file in the project's root directory. At it's simplest, the project level `garden.yml` file looks something like this: -- You can create all the configuration files by hand. For that that you should take a look at our [Configuration files](./configuration-files.md) document. -- Or you can use the `garden create` command—often a lot easier. - -### `garden create` - -The `garden create` command can be used to create either whole projects, or just modules. Essentially what it does is help you create configuration files so you don't have to do it by hand. - -The command `garden create project` will create a new project in the current directory and prompt you to add modules to it, which should each have a name and a type. It will then create the appropriate folders and the configuration files within them. - -If this is a pre-existing project and you want to "gardenify" code that's already there, you can try, for example, `garden create project --module-dirs=./services`. This will prompt you to create configuration files for every subfolder within the `./services` directory. +```yaml +# examples/simple-project/garden.yml +project: + name: simple-project + environments: + - name: local + providers: + - name: local-kubernetes +``` -To add individual modules later on you can use `garden create module`. +You then add a `garden.yml` file at the root directory of every module in your project. Normally, a module is a single container or a single serverless function. A module level `garden.yml` file looks something like this: +```yaml +# examples/simple-project/services/go-service/garden.yml +module: + name: go-service + description: Go service container + type: container + services: + - name: go-service + ports: + - name: http + containerPort: 8080 + servicePort: 80 + ingresses: + - path: /hello-go + port: http ``` -➜ test-project g create project - -Initializing new Garden project test-project ---------- -? Would you like to add a module to your project? Yes -? Enter module name my-module -? Module type container -? Add another module? (current modules: my-module) Yes -? Enter module name my-module-2 -? Module type container -? Add another module? (current modules: my-module, my-module-2) No ---------- -✔ Setting up project -✔ Writing config for my-module -✔ Writing config for my-module-2 -✔ Writing config for test-project -Project created! Be sure to check out our docs for how to get sarted! -``` + +To learn more about how to configure a Garden project, please take a look at our [Configuration files](./configuration-files.md) document. For a practical example of "gardenifying" an existing project, check out the [Simple project](../examples/simple-project.md) example. diff --git a/garden-service/src/commands/commands.ts b/garden-service/src/commands/commands.ts index 9620eba979..3e3a9592e3 100644 --- a/garden-service/src/commands/commands.ts +++ b/garden-service/src/commands/commands.ts @@ -8,7 +8,6 @@ import { Command } from "./base" import { BuildCommand } from "./build" -import { CreateCommand } from "./create/create" import { CallCommand } from "./call" import { InitCommand } from "./init" import { DeleteCommand } from "./delete" @@ -32,7 +31,6 @@ import { VersionCommand } from "./version" export const coreCommands: Command[] = [ new BuildCommand(), new CallCommand(), - new CreateCommand(), new DeleteCommand(), new DeployCommand(), new DevCommand(), diff --git a/garden-service/src/commands/create/config-templates.ts b/garden-service/src/commands/create/config-templates.ts deleted file mode 100644 index a9ff65c854..0000000000 --- a/garden-service/src/commands/create/config-templates.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2018 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { capitalize, camelCase, uniq } from "lodash" - -import { DeepPartial } from "../../util/util" -import { ContainerModuleSpec } from "../../plugins/container/config" -import { GcfModuleSpec } from "../../plugins/google/google-cloud-functions" -import { ProjectConfig } from "../../config/project" -import { ModuleConfig } from "../../config/module" - -/** - * Ideally there would be some mechanism to discover available module types, - * and for plugins to expose a minimal config for the given type along with - * a list of providers per environment, rather than hard coding these values. - * - * Alternatively, consider co-locating the templates with the plugins. - */ -export const MODULE_PROVIDER_MAP = { - "container": "local-kubernetes", - "google-cloud-function": "local-google-cloud-functions", - "npm-package": "npm-package", -} - -export const availableModuleTypes = Object.keys(MODULE_PROVIDER_MAP) - -export type ModuleType = keyof typeof MODULE_PROVIDER_MAP - -export interface ProjectTemplate { - project: Partial -} - -export interface ModuleTemplate { - module: Partial -} - -const noCase = (str: string) => str.replace(/-|_/g, " ") -const titleize = (str: string) => capitalize(noCase(str)) - -export function containerTemplate(moduleName: string): DeepPartial { - return { - services: [ - { - name: `${moduleName}-service`, - ports: [{ - name: "http", - containerPort: 8080, - }], - ingresses: [{ - path: "/", - port: "http", - }], - }, - ], - } -} - -export function googleCloudFunctionTemplate(moduleName: string): DeepPartial { - return { - functions: [{ - name: `${moduleName}-google-cloud-function`, - entrypoint: camelCase(`${moduleName}-google-cloud-function`), - }], - } -} - -export function npmPackageTemplate(_moduleName: string): any { - return {} -} - -export const projectTemplate = (name: string, moduleTypes: ModuleType[]): ProjectTemplate => { - const providers = uniq(moduleTypes).map(type => ({ name: MODULE_PROVIDER_MAP[type] })) - return { - project: { - name, - environments: [ - { - name: "local", - providers, - variables: {}, - }, - ], - }, - } -} - -export const moduleTemplate = (name: string, type: ModuleType): ModuleTemplate => { - const moduleTypeTemplate = { - "container": containerTemplate, - "google-cloud-function": googleCloudFunctionTemplate, - "npm-package": npmPackageTemplate, - }[type] - return { - module: { - name, - type, - description: `${titleize(name)} ${noCase(type)}`, - ...moduleTypeTemplate(name), - }, - } -} diff --git a/garden-service/src/commands/create/create.ts b/garden-service/src/commands/create/create.ts deleted file mode 100644 index 37b37ca0fa..0000000000 --- a/garden-service/src/commands/create/create.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2018 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { Command } from "../base" -import { CreateProjectCommand } from "./project" -import { CreateModuleCommand } from "./module" - -export class CreateCommand extends Command { - name = "create" - help = "Create a new project or add a new module" - - cliOnly = true - - subCommands = [ - CreateProjectCommand, - CreateModuleCommand, - ] - - async action() { return {} } -} diff --git a/garden-service/src/commands/create/helpers.ts b/garden-service/src/commands/create/helpers.ts deleted file mode 100644 index 509471d7f2..0000000000 --- a/garden-service/src/commands/create/helpers.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import * as Joi from "joi" -import { - ModuleType, - moduleTemplate, -} from "./config-templates" -import { join } from "path" -import { pathExists } from "fs-extra" -import { validate } from "../../config/common" -import { dumpYaml } from "../../util/util" -import { MODULE_CONFIG_FILENAME } from "../../constants" -import { LogNode } from "../../logger/log-node" -import { NewModuleOpts, CommonOpts } from "./project" - -export function prepareNewModuleOpts(name: string, type: ModuleType, path: string): NewModuleOpts { - return { - name, - type, - path, - config: moduleTemplate(name, type), - } -} - -export async function dumpConfig(opts: CommonOpts, schema: Joi.Schema, logger: LogNode) { - const { config, name, path } = opts - const yamlPath = join(path, MODULE_CONFIG_FILENAME) - const task = logger.info({ - msg: `Writing config for ${name}`, - status: "active", - }) - - if (await pathExists(yamlPath)) { - task.setWarn({ msg: `Garden config file already exists at path, skipping`, append: true }) - return - } - - try { - validate(config, schema) - await dumpYaml(yamlPath, config) - task.setSuccess() - } catch (err) { - task.setError({ msg: `Generated config is invalid, skipping`, append: true }) - throw err - } -} diff --git a/garden-service/src/commands/create/module.ts b/garden-service/src/commands/create/module.ts deleted file mode 100644 index 70f2681690..0000000000 --- a/garden-service/src/commands/create/module.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2018 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { basename, join } from "path" -import dedent = require("dedent") -import { ensureDir } from "fs-extra" - -import { - Command, - CommandResult, - StringParameter, - ChoicesParameter, - CommandParams, -} from "../base" -import { ParameterError, GardenBaseError } from "../../exceptions" -import { availableModuleTypes, ModuleType } from "./config-templates" -import { - prepareNewModuleOpts, - dumpConfig, -} from "./helpers" -import { prompts } from "./prompts" -import { validate, joiUserIdentifier } from "../../config/common" -import { NewModuleOpts } from "./project" -import { configSchema } from "../../config/base" - -const createModuleOptions = { - name: new StringParameter({ - help: "Assigns a custom name to the module (defaults to name of the current directory)", - }), - type: new ChoicesParameter({ - help: "Type of module.", - choices: availableModuleTypes, - }), -} - -const createModuleArguments = { - "module-dir": new StringParameter({ - help: "Directory of the module (defaults to current directory).", - }), -} - -type Args = typeof createModuleArguments -type Opts = typeof createModuleOptions - -interface CreateModuleResult extends CommandResult { - result: { - module?: NewModuleOpts, - } -} - -export class CreateModuleCommand extends Command { - name = "module" - help = "Creates a new Garden module." - header = { emoji: "house_with_garden", command: "create" } - - description = dedent` - Examples: - - garden create module # creates a new module in the current directory (module name defaults to directory name) - garden create module my-module # creates a new module in my-module directory - garden create module --type=container # creates a new container module - garden create module --name=my-module # creates a new module in current directory and names it my-module - ` - - noProject = true - arguments = createModuleArguments - options = createModuleOptions - - async action({ garden, args, opts, log }: CommandParams): Promise { - let errors: GardenBaseError[] = [] - - const moduleRoot = join(garden.projectRoot, (args["module-dir"] || "").trim()) - const moduleName = validate( - opts.name || basename(moduleRoot), - joiUserIdentifier(), - { context: "module name" }, - ) - - await ensureDir(moduleRoot) - - log.info(`Initializing new module ${moduleName}`) - - let type: ModuleType - - if (opts.type) { - // Type passed as parameter - type = opts.type - if (!availableModuleTypes.includes(type)) { - throw new ParameterError("Module type not available", {}) - } - } else { - // Prompt for type - log.info("---------") - // Stop logger while prompting - log.stopAll() - type = (await prompts.addConfigForModule(moduleName)).type - log.info("---------") - if (!type) { - return { result: {} } - } - } - - const moduleOpts = prepareNewModuleOpts(moduleName, type, moduleRoot) - try { - await dumpConfig(moduleOpts, configSchema, log) - } catch (err) { - errors.push(err) - } - return { - result: { module: moduleOpts }, - errors, - } - } -} diff --git a/garden-service/src/commands/create/project.ts b/garden-service/src/commands/create/project.ts deleted file mode 100644 index 80fb78f8fa..0000000000 --- a/garden-service/src/commands/create/project.ts +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2018 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { resolve, join, basename } from "path" -import { ensureDir } from "fs-extra" -import Bluebird = require("bluebird") -import dedent = require("dedent") -import terminalLink = require("terminal-link") - -import { - Command, - CommandParams, - CommandResult, - StringParameter, - PathsParameter, -} from "../base" -import { GardenBaseError } from "../../exceptions" -import { - prepareNewModuleOpts, - dumpConfig, -} from "./helpers" -import { prompts } from "./prompts" -import { - projectTemplate, - ModuleTemplate, - ModuleType, - ProjectTemplate, -} from "./config-templates" -import { getChildDirNames } from "../../util/util" -import { validate, joiUserIdentifier } from "../../config/common" -import { configSchema } from "../../config/base" - -const flatten = (acc, val) => acc.concat(val) - -const createProjectOptions = { - "module-dirs": new PathsParameter({ - help: "Relative path to modules directory. Use comma as a separator to specify multiple directories.", - }), - "name": new StringParameter({ - help: "Assigns a custom name to the project (defaults to name of the current directory).", - }), -} - -const createProjectArguments = { - "project-dir": new StringParameter({ - help: "Directory of the project (defaults to current directory).", - }), -} - -type Args = typeof createProjectArguments -type Opts = typeof createProjectOptions - -export interface CommonOpts { - name: string - path: string - config: ModuleTemplate | ProjectTemplate -} - -export interface NewModuleOpts extends CommonOpts { - type: ModuleType - config: ModuleTemplate -} - -export interface NewProjectOpts extends CommonOpts { - config: ProjectTemplate -} - -interface CreateProjectResult extends CommandResult { - result: { - project: NewProjectOpts, - modules: NewModuleOpts[], - } -} - -export class CreateProjectCommand extends Command { - name = "project" - help = "Creates a new Garden project." - - description = dedent` - Walks the user through setting up a new Garden project and - generates scaffolding based on user input. - - Examples: - - garden create project # creates a new Garden project in the current directory (project name defaults to - directory name) - garden create project my-project # creates a new Garden project in my-project directory - garden create project --module-dirs=path/to/modules-a,path/to/modules-b - # creates a new Garden project and looks for pre-existing modules in the modules-a and modules-b directories - garden create project --name my-project - # creates a new Garden project in the current directory and names it my-project - ` - - noProject = true - arguments = createProjectArguments - options = createProjectOptions - - async action({ garden, args, opts, log }: CommandParams): Promise { - let moduleOpts: NewModuleOpts[] = [] - let errors: GardenBaseError[] = [] - - const projectRoot = args["project-dir"] ? join(garden.projectRoot, args["project-dir"].trim()) : garden.projectRoot - const moduleParentDirs = await Bluebird.map(opts["module-dirs"] || [], (dir: string) => resolve(projectRoot, dir)) - const projectName = validate( - opts.name || basename(projectRoot), - joiUserIdentifier(), - { context: "project name" }, - ) - - await ensureDir(projectRoot) - - log.info(`Initializing new Garden project ${projectName}`) - log.info("---------") - // Stop logger while prompting - log.stopAll() - - if (moduleParentDirs.length > 0) { - // If module-dirs option provided we scan for modules in the parent dir(s) and add them one by one - moduleOpts = (await Bluebird.mapSeries(moduleParentDirs, async parentDir => { - const moduleNames = await getChildDirNames(parentDir) - - return Bluebird.reduce(moduleNames, async (acc: NewModuleOpts[], moduleName: string) => { - const { type } = await prompts.addConfigForModule(moduleName) - if (type) { - acc.push(prepareNewModuleOpts(moduleName, type, join(parentDir, moduleName))) - } - return acc - }, []) - })) - .reduce(flatten, []) - .filter(m => m) - } else { - // Otherwise we prompt the user for modules to add - moduleOpts = (await prompts.repeatAddModule()) - .map(({ name, type }) => prepareNewModuleOpts(name, type, join(projectRoot, name))) - } - - log.info("---------") - const taskLog = log.info({ msg: "Setting up project", status: "active" }) - - for (const module of moduleOpts) { - await ensureDir(module.path) - try { - await dumpConfig(module, configSchema, log) - } catch (err) { - errors.push(err) - } - } - - const projectOpts: NewProjectOpts = { - path: projectRoot, - name: projectName, - config: projectTemplate(projectName, moduleOpts.map(module => module.type)), - } - - try { - await dumpConfig(projectOpts, configSchema, log) - } catch (err) { - errors.push(err) - } - - if (errors.length === 0) { - taskLog.setSuccess() - } else { - taskLog.setWarn({ msg: "Finished with errors", append: true }) - } - - const docs = terminalLink("docs", "https://docs.garden.io") - log.info(`Project created! Be sure to check out our ${docs} for how to get sarted!`) - - return { - result: { - modules: moduleOpts, - project: projectOpts, - }, - errors, - } - } -} diff --git a/garden-service/src/commands/create/prompts.ts b/garden-service/src/commands/create/prompts.ts deleted file mode 100644 index 5d5b67e197..0000000000 --- a/garden-service/src/commands/create/prompts.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2018 Garden Technologies, Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import * as inquirer from "inquirer" -import * as Joi from "joi" -import chalk from "chalk" - -import { joiUserIdentifier } from "../../config/common" -import { ModuleType } from "./config-templates" - -export interface ModuleTypeChoice extends inquirer.objects.ChoiceOption { - value: ModuleType -} - -export interface ModuleTypeMap { - type: ModuleType -} - -export interface ModuleTypeAndName extends ModuleTypeMap { - name: string -} - -export interface Prompts { - addConfigForModule: (...args: any[]) => Promise - addModule: (...args: any[]) => Promise - repeatAddModule: (...args: any[]) => Promise -} - -const moduleTypeChoices: ModuleTypeChoice[] = [ - { - name: "container", - value: "container", - }, - { - name: `google-cloud-function (${chalk.red.italic("experimental")})`, - value: "google-cloud-function", - }, - { - name: `npm package (${chalk.red.italic("experimental")})`, - value: "npm-package", - }, -] - -// Create config for an existing module -async function addConfigForModule(dir: string): Promise { - const qNames = { - ADD_MODULE: "addModule", - TYPE: "type", - } - const questions: inquirer.Questions = [ - { - name: qNames.ADD_MODULE, - message: `Add module config for ${chalk.italic(dir)}?`, - type: "confirm", - }, - { - name: qNames.TYPE, - message: "Module type", - choices: moduleTypeChoices, - when: ans => ans[qNames.ADD_MODULE], - type: "list", - }, - ] - return await inquirer.prompt(questions) as ModuleTypeMap -} - -// Create a new module with config -async function addModule(addModuleMessage: string): Promise { - const qNames = { - ADD_MODULE: "addModule", - NAME: "name", - TYPE: "type", - } - const questions: inquirer.Questions = [ - { - name: qNames.ADD_MODULE, - message: addModuleMessage, - type: "confirm", - }, - { - name: qNames.NAME, - message: "Enter module name", - type: "input", - validate: input => { - try { - Joi.attempt(input.trim(), joiUserIdentifier()) - } catch (err) { - return `Invalid module name, please try again\nError: ${err.message}` - } - return true - }, - filter: input => input.trim(), - when: ans => ans[qNames.ADD_MODULE], - }, - { - name: qNames.TYPE, - message: "Module type", - choices: moduleTypeChoices, - when: ans => ans[qNames.NAME], - type: "list", - }, - ] - return await inquirer.prompt(questions) as ModuleTypeAndName -} - -export async function repeatAddModule(): Promise { - let modules: ModuleTypeAndName[] = [] - let addModuleMessage = "Would you like to add a module to your project?" - let ans = await addModule(addModuleMessage) - - while (ans.type) { - modules.push({ name: ans.name, type: ans.type }) - addModuleMessage = `Add another module? (current modules: ${modules.map(m => m.name).join(", ")})` - ans = await addModule(addModuleMessage) - } - return modules -} - -export const prompts: Prompts = { - addConfigForModule, - addModule, - repeatAddModule, -} diff --git a/garden-service/test/data/test-project-create-command/garden.yml b/garden-service/test/data/test-project-create-command/garden.yml deleted file mode 100644 index a445562ade..0000000000 --- a/garden-service/test/data/test-project-create-command/garden.yml +++ /dev/null @@ -1,2 +0,0 @@ -project: - name: create-command diff --git a/garden-service/test/data/test-project-create-command/module-a/child-module-a/garden.yml b/garden-service/test/data/test-project-create-command/module-a/child-module-a/garden.yml deleted file mode 100644 index fd8a9586da..0000000000 --- a/garden-service/test/data/test-project-create-command/module-a/child-module-a/garden.yml +++ /dev/null @@ -1,11 +0,0 @@ -module: - name: child-module-a - type: container - description: Child Module a container - services: - - name: child-module-a-service - ports: - - name: http - containerPort: 8080 - ingresses: - - port: http diff --git a/garden-service/test/data/test-project-create-command/module-a/garden.yml b/garden-service/test/data/test-project-create-command/module-a/garden.yml deleted file mode 100644 index a0cdfce9d6..0000000000 --- a/garden-service/test/data/test-project-create-command/module-a/garden.yml +++ /dev/null @@ -1,11 +0,0 @@ -module: - name: module-parent-a - type: container - description: Module parent a container - services: - - name: module-parent-a-service - ports: - - name: http - containerPort: 8080 - ingresses: - - port: http diff --git a/garden-service/test/data/test-project-create-command/module-b/child-module-b/garden.yml b/garden-service/test/data/test-project-create-command/module-b/child-module-b/garden.yml deleted file mode 100644 index 8ee5b8bf00..0000000000 --- a/garden-service/test/data/test-project-create-command/module-b/child-module-b/garden.yml +++ /dev/null @@ -1,11 +0,0 @@ -module: - name: child-module-b - type: container - description: Child module b container - services: - - name: child-module-b-service - ports: - - name: http - containerPort: 8080 - ingresses: - - port: http diff --git a/garden-service/test/data/test-project-create-command/module-b/garden.yml b/garden-service/test/data/test-project-create-command/module-b/garden.yml deleted file mode 100644 index aeaaf84518..0000000000 --- a/garden-service/test/data/test-project-create-command/module-b/garden.yml +++ /dev/null @@ -1,11 +0,0 @@ -module: - name: module-parent-b - type: container - description: Module parent b container - services: - - name: module-parent-b-service - ports: - - name: http - containerPort: 8080 - ingresses: - - port: http diff --git a/garden-service/test/src/commands/create/config-templates.ts b/garden-service/test/src/commands/create/config-templates.ts deleted file mode 100644 index 9995ad4b7c..0000000000 --- a/garden-service/test/src/commands/create/config-templates.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { expect } from "chai" - -import { - availableModuleTypes, - projectTemplate, - moduleTemplate, -} from "../../../../src/commands/create/config-templates" -import { validate } from "../../../../src/config/common" -import { configSchema } from "../../../../src/config/base" - -describe("ConfigTemplates", () => { - describe("projectTemplate", () => { - for (const moduleType of availableModuleTypes) { - it(`should be valid for module type ${moduleType}`, async () => { - const config = projectTemplate("my-project", [moduleType]) - expect(() => validate(config, configSchema)).to.not.throw() - }) - } - it("should be valid for multiple module types", async () => { - const config = projectTemplate("my-project", availableModuleTypes) - expect(() => validate(config, configSchema)).to.not.throw() - }) - it("should be valid for multiple modules of same type", async () => { - const config = projectTemplate("my-project", [availableModuleTypes[0], availableModuleTypes[0]]) - expect(() => validate(config, configSchema)).to.not.throw() - }) - it("should be valid if no modules", async () => { - const config = projectTemplate("my-project", []) - expect(() => validate(config, configSchema)).to.not.throw() - }) - }) - describe("moduleTemplate", () => { - for (const moduleType of availableModuleTypes) { - it(`should be valid for module type ${moduleType}`, async () => { - const config = moduleTemplate("my-module", moduleType) - expect(() => validate(config, configSchema)).to.not.throw() - }) - } - }) -}) diff --git a/garden-service/test/src/commands/create/module.ts b/garden-service/test/src/commands/create/module.ts deleted file mode 100644 index 125af13351..0000000000 --- a/garden-service/test/src/commands/create/module.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { expect } from "chai" -import { - expectError, - makeTestGarden, -} from "../../../helpers" -import { pick } from "lodash" -import { join } from "path" -import * as td from "testdouble" - -import { CreateModuleCommand } from "../../../../src/commands/create/module" -import { - prompts, - ModuleTypeMap, -} from "../../../../src/commands/create/prompts" -import { remove } from "fs-extra" - -const projectRoot = join(__dirname, "../../..", "data", "test-project-create-command") - -const replaceAddConfigForModule = (returnVal?: ModuleTypeMap) => { - if (!returnVal) { - returnVal = { - type: "container", - } - td.replace(prompts, "addConfigForModule", async () => returnVal) - } -} - -describe("CreateModuleCommand", () => { - afterEach(async () => { - await remove(join(projectRoot, "new-module")) - }) - - const cmd = new CreateModuleCommand() - // garden create module - it("should add a module config to the current directory", async () => { - replaceAddConfigForModule() - const garden = await makeTestGarden(projectRoot) - const log = garden.log - - const { result } = await cmd.action({ - garden, - log, - args: { "module-dir": "" }, - opts: { name: "", type: "" }, - }) - - expect(pick(result.module, ["name", "type", "path"])).to.eql({ - name: "test-project-create-command", - type: "container", - path: garden.projectRoot, - }) - }) - // garden create module new-module - it("should add a module config to new-module directory", async () => { - replaceAddConfigForModule() - const garden = await makeTestGarden(projectRoot) - const log = garden.log - - const { result } = await cmd.action({ - garden, - log, - args: { "module-dir": "new-module" }, - opts: { name: "", type: "" }, - }) - expect(pick(result.module, ["name", "type", "path"])).to.eql({ - name: "new-module", - type: "container", - path: join(garden.projectRoot, "new-module"), - }) - }) - // garden create module --name=my-module - it("should optionally name the module my-module", async () => { - replaceAddConfigForModule() - const garden = await makeTestGarden(projectRoot) - const log = garden.log - - const { result } = await cmd.action({ - garden, - log, - args: { "module-dir": "" }, - opts: { name: "my-module", type: "" }, - }) - expect(pick(result.module, ["name", "type", "path"])).to.eql({ - name: "my-module", - type: "container", - path: garden.projectRoot, - }) - }) - // garden create module --type=google-cloud-function - it("should optionally create a module of a specific type (without prompting)", async () => { - const garden = await makeTestGarden(projectRoot) - const log = garden.log - - const { result } = await cmd.action({ - garden, - log, - args: { "module-dir": "" }, - opts: { name: "", type: "google-cloud-function" }, - }) - expect(pick(result.module, ["name", "type", "path"])).to.eql({ - name: "test-project-create-command", - type: "google-cloud-function", - path: garden.projectRoot, - }) - }) - // garden create module ___ - it("should throw if module name is invalid when inherited from current directory", async () => { - replaceAddConfigForModule() - const garden = await makeTestGarden(projectRoot) - const log = garden.log - - await expectError( - async () => await cmd.action({ - garden, - log, - args: { "module-dir": "___" }, - opts: { name: "", type: "" }, - }), - "configuration", - ) - }) - // garden create --name=___ - it("should throw if module name is invalid when explicitly specified", async () => { - replaceAddConfigForModule() - const garden = await makeTestGarden(projectRoot) - const log = garden.log - - await expectError( - async () => await cmd.action({ - garden, - log, - args: { "module-dir": "" }, - opts: { name: "___", type: "" }, - }), - "configuration", - ) - }) - // garden create module --type=banana - it("should throw if invalid type provided", async () => { - replaceAddConfigForModule() - const garden = await makeTestGarden(projectRoot) - const log = garden.log - - await expectError( - async () => await cmd.action({ - garden, - log, - args: { "module-dir": "" }, - opts: { name: "", type: "banana" }, - }), - "parameter", - ) - }) -}) diff --git a/garden-service/test/src/commands/create/project.ts b/garden-service/test/src/commands/create/project.ts deleted file mode 100644 index c7825fb1f9..0000000000 --- a/garden-service/test/src/commands/create/project.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { expect } from "chai" -import { expectError, makeTestGarden } from "../../../helpers" -import { pick } from "lodash" -import { remove } from "fs-extra" -import { join } from "path" -import * as td from "testdouble" - -import { CreateProjectCommand } from "../../../../src/commands/create/project" -import { LogEntry } from "../../../../src/logger/log-entry" -import { Garden } from "../../../../src/garden" -import { - prompts, - ModuleTypeAndName, - ModuleTypeMap, -} from "../../../../src/commands/create/prompts" - -const replaceRepeatAddModule = (returnVal?: ModuleTypeAndName[]) => { - if (!returnVal) { - returnVal = [ - { - type: "container", - name: "module-a", - }, - { - type: "container", - name: "module-b", - }, - ] - } - td.replace(prompts, "repeatAddModule", async () => returnVal) -} - -const replaceAddConfigForModule = (returnVal?: ModuleTypeMap) => { - if (!returnVal) { - returnVal = { - type: "container", - } - td.replace(prompts, "addConfigForModule", async () => returnVal) - } -} - -describe("CreateProjectCommand", () => { - const projectRoot = join(__dirname, "../../..", "data", "test-project-create-command") - const cmd = new CreateProjectCommand() - - let garden: Garden - let log: LogEntry - - beforeEach(async () => { - garden = await makeTestGarden(projectRoot) - log = garden.log - }) - - afterEach(async () => { - await remove(join(projectRoot, "new-project")) - }) - - // garden create project - it("should create a project in the current directory", async () => { - replaceRepeatAddModule() - const { result } = await cmd.action({ - garden, - log, - args: { "project-dir": "" }, - opts: { "name": "", "module-dirs": [] }, - }) - const modules = result.modules.map(m => pick(m, ["name", "type", "path"])) - const project = pick(result.project, ["name", "path"]) - - expect({ modules, project }).to.eql({ - modules: [ - { type: "container", name: "module-a", path: join(garden.projectRoot, "module-a") }, - { type: "container", name: "module-b", path: join(garden.projectRoot, "module-b") }, - ], - project: { - name: "test-project-create-command", - path: garden.projectRoot, - }, - }) - }) - // garden create project new-project - it("should create a project in directory new-project", async () => { - replaceRepeatAddModule() - const { result } = await cmd.action({ - garden, - log, - args: { "project-dir": "new-project" }, - opts: { "name": "", "module-dirs": [] }, - }) - expect(pick(result.project, ["name", "path"])).to.eql({ - name: "new-project", - path: join(garden.projectRoot, "new-project"), - }) - }) - // garden create project --name=my-project - it("should optionally create a project named my-project", async () => { - replaceRepeatAddModule() - const { result } = await cmd.action({ - garden, - log, - args: { "project-dir": "" }, - opts: { "name": "my-project", "module-dirs": [] }, - }) - expect(pick(result.project, ["name", "path"])).to.eql({ - name: "my-project", - path: join(garden.projectRoot), - }) - }) - // garden create project --module-dirs=. - it("should optionally create module configs for modules in current directory", async () => { - replaceAddConfigForModule() - const { result } = await cmd.action({ - garden, - log, - args: { "project-dir": "" }, - opts: { "name": "", "module-dirs": ["."] }, - }) - expect(result.modules.map(m => pick(m, ["name", "type", "path"]))).to.eql([ - { type: "container", name: "module-a", path: join(garden.projectRoot, "module-a") }, - { type: "container", name: "module-b", path: join(garden.projectRoot, "module-b") }, - ]) - }) - // garden create project --module-dirs=module-a,module-b - it("should optionally create module configs for modules in specified directories", async () => { - replaceAddConfigForModule() - const { result } = await cmd.action({ - garden, - log, - args: { "project-dir": "" }, - opts: { "name": "", "module-dirs": ["module-a", "module-b"] }, - }) - expect(result.modules.map(m => pick(m, ["name", "type", "path"]))).to.eql([ - { type: "container", name: "child-module-a", path: join(garden.projectRoot, "module-a", "child-module-a") }, - { type: "container", name: "child-module-b", path: join(garden.projectRoot, "module-b", "child-module-b") }, - ]) - }) - // garden create project ___ - it("should throw if project name is invalid when inherited from current directory", async () => { - replaceRepeatAddModule() - await expectError( - async () => await cmd.action({ - garden, - log, - args: { "project-dir": "___" }, - opts: { "name": "", "module-dirs": [] }, - }), - "configuration", - ) - }) - // garden create project --name=____ - it("should throw if project name is invalid when explicitly specified", async () => { - replaceRepeatAddModule() - await expectError( - async () => await cmd.action({ - garden, - log, - args: { "project-dir": "" }, - opts: { "name": "___", "module-dirs": [] }, - }), - "configuration", - ) - }) -})