diff --git a/src/commands/auto-reload.ts b/src/commands/auto-reload.ts deleted file mode 100644 index 77ef3fdacb..0000000000 --- a/src/commands/auto-reload.ts +++ /dev/null @@ -1,60 +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 { PluginContext } from "../plugin-context" -import { BuildTask } from "../tasks/build" -import { DeployTask } from "../tasks/deploy" -import { - registerCleanupFunction, - sleep, -} from "../util" -import { watchModules } from "../watch" -import { Command } from "./base" -import { - values, - keys, -} from "lodash" - -export class AutoReloadCommand extends Command { - name = "autoreload" - help = "Auto-reload modules when sources change" - - async action(ctx: PluginContext): Promise { - const modules = values(await ctx.getModules()) - - if (modules.length === 0) { - if (modules.length === 0) { - ctx.log.info({ msg: "No modules found in project." }) - } - ctx.log.info({ msg: "Aborting..." }) - return - } - - const watcher = await watchModules(ctx, modules, async (_, module) => { - const serviceNames = keys(module.services || {}) - - if (serviceNames.length === 0) { - await ctx.addTask(new BuildTask(ctx, module, false)) - } else { - for (const service of values(await ctx.getServices(serviceNames))) { - await ctx.addTask(new DeployTask(ctx, service, true, true)) - } - } - }) - - registerCleanupFunction("clearAutoReloadWatches", () => { - watcher.end() - }) - - while (true) { - ctx.log.info({ msg: "Sup bruh" }) - await sleep(1000) - } - } - -} diff --git a/src/commands/deploy.ts b/src/commands/deploy.ts index fed50f9cca..3d6ad8644f 100644 --- a/src/commands/deploy.ts +++ b/src/commands/deploy.ts @@ -55,7 +55,7 @@ export class DeployCommand extends Command const modules = Array.from(new Set(values(services).map(s => s.module))) - const result = ctx.processModules(modules, watch, async (module) => { + const result = await ctx.processModules(modules, watch, async (module) => { const servicesToDeploy = values(await module.getServices()).filter(s => !!services[s.name]) for (const service of servicesToDeploy) { await ctx.addTask(new DeployTask(ctx, service, force, forceBuild)) diff --git a/src/commands/dev.ts b/src/commands/dev.ts index b4f892a8cc..cee47634c5 100644 --- a/src/commands/dev.ts +++ b/src/commands/dev.ts @@ -7,14 +7,18 @@ */ import { PluginContext } from "../plugin-context" +import { BuildTask } from "../tasks/build" +import { Task } from "../types/task" import { Command } from "./base" import { join } from "path" import { STATIC_DIR } from "../constants" import { spawnSync } from "child_process" import chalk from "chalk" -import { sleep } from "../util" +import Bluebird = require("bluebird") +import { values } from "lodash" +import moment = require("moment") -const imgcatPath = join(__dirname, "..", "..", "bin", "imgcat") +const imgcatPath = join(STATIC_DIR, "imgcat") const bannerPath = join(STATIC_DIR, "garden-banner-1-half.png") export class DevCommand extends Command { @@ -26,24 +30,48 @@ export class DevCommand extends Command { spawnSync(imgcatPath, [bannerPath], { stdio: "inherit", }) - console.log() } catch (_) { // the above fails for terminals other than iTerm2. just ignore the error and move on. } - // console.log(chalk.bold(` garden - dev\n`)) - console.log(chalk.gray.italic(` Good afternoon, Jon! Let's get your environment wired up...\n`)) + ctx.log.info(chalk.gray.italic(`\nGood ${getGreetingTime()}! Let's get your environment wired up...\n`)) await ctx.configureEnvironment() - await ctx.deployServices({}) - ctx.log.info({ msg: "" }) - const watchEntry = ctx.log.info({ emoji: "koala", msg: `Waiting for code changes...` }) + const modules = values(await ctx.getModules()) - while (true) { - // TODO: actually do stuff - await sleep(10000) - watchEntry.setState({ emoji: "koala", msg: `Waiting for code changes...` }) + if (modules.length === 0) { + if (modules.length === 0) { + ctx.log.info({ msg: "No modules found in project." }) + } + ctx.log.info({ msg: "Aborting..." }) + return } + + await ctx.processModules(modules, true, async (module) => { + const testTasks: Task[] = await module.getTestTasks({}) + const deployTasks = await module.getDeployTasks({}) + const tasks = testTasks.concat(deployTasks) + + if (tasks.length === 0) { + await ctx.addTask(new BuildTask(ctx, module, false)) + } else { + await Bluebird.map(tasks, ctx.addTask) + } + }) + } +} + +function getGreetingTime() { + const m = moment() + + const currentHour = parseFloat(m.format("HH")) + + if ( currentHour >= 17 ) { + return "evening" + } else if ( currentHour >= 12 ) { + return "afternoon" + } else { + return "morning" } } diff --git a/src/commands/test.ts b/src/commands/test.ts index f23cf7775e..591f961a13 100644 --- a/src/commands/test.ts +++ b/src/commands/test.ts @@ -9,9 +9,9 @@ import { PluginContext } from "../plugin-context" import { BooleanParameter, Command, ParameterValues, StringParameter } from "./base" import { values } from "lodash" -import { TestTask } from "../tasks/test" import chalk from "chalk" import { TaskResults } from "../task-graph" +import Bluebird = require("bluebird") export const testArgs = { module: new StringParameter({ @@ -51,23 +51,19 @@ export class TestCommand extends Command { await ctx.configureEnvironment() - const results = await ctx.processModules(modules, opts.watch, async (module) => { - const config = await module.getConfig() + const group = opts.group + const force = opts.force + const forceBuild = opts["force-build"] - for (const testName of Object.keys(config.test)) { - if (opts.group && testName !== opts.group) { - continue - } - const testSpec = config.test[testName] - const task = new TestTask(ctx, module, testName, testSpec, opts.force, opts["force-build"]) - await ctx.addTask(task) - } + const results = await ctx.processModules(modules, opts.watch, async (module) => { + const tasks = await module.getTestTasks({ group, force, forceBuild }) + await Bluebird.map(tasks, ctx.addTask) }) const failed = values(results).filter(r => !!r.error).length if (failed) { - ctx.log.error({ emoji: "warning", msg: `${failed} tests runs failed! See log output above.\n` }) + ctx.log.error({ emoji: "warning", msg: `${failed} test runs failed! See log output above.\n` }) } else { ctx.log.info("") ctx.log.info({ emoji: "heavy_check_mark", msg: chalk.green(` All tests passing!\n`) }) diff --git a/src/types/module.ts b/src/types/module.ts index cf5461d1f3..6edde7c000 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -9,6 +9,8 @@ import * as Joi from "joi" import { ServiceMap } from "../garden" import { PluginContext } from "../plugin-context" +import { DeployTask } from "../tasks/deploy" +import { TestTask } from "../tasks/test" import { identifierRegex, joiIdentifier, joiVariables, PrimitiveMap } from "./common" import { ConfigurationError } from "../exceptions" import Bluebird = require("bluebird") @@ -16,8 +18,12 @@ import { extend, keys, set, + values, } from "lodash" -import { ServiceConfig } from "./service" +import { + Service, + ServiceConfig, +} from "./service" import { resolveTemplateStrings, TemplateStringContext } from "../template-string" import { Memoize } from "typescript-memoize" import { TreeVersion } from "../vcs/base" @@ -68,6 +74,10 @@ export interface ModuleConfig { variables: PrimitiveMap } +interface ModuleConstructor { + new (ctx: PluginContext, config: T): Module +} + export class Module { public name: string public type: string @@ -153,6 +163,32 @@ export class Module { const serviceNames = keys(this.services || {}) return this.ctx.getServices(serviceNames) } + + async getDeployTasks( + { force = false, forceBuild = false }: { force?: boolean, forceBuild?: boolean }, + ): Promise>>[]> { + const services = await this.getServices() + const module = this + + return values(services).map(s => new DeployTask(module.ctx, s, force, forceBuild)) + } + + async getTestTasks( + { group, force = false, forceBuild = false }: { group?: string, force?: boolean, forceBuild?: boolean }, + ) { + const tasks: TestTask>[] = [] + const config = await this.getConfig() + + for (const testName of Object.keys(config.test)) { + if (group && testName !== group) { + continue + } + const testSpec = config.test[testName] + tasks.push(new TestTask>(this.ctx, this, testName, testSpec, force, forceBuild)) + } + + return tasks + } } export type ModuleConfigType = T["_ConfigType"] diff --git a/bin/imgcat b/static/imgcat similarity index 100% rename from bin/imgcat rename to static/imgcat diff --git a/test/src/commands/auto-reload.ts b/test/src/commands/auto-reload.ts deleted file mode 100644 index 6fa210adc9..0000000000 --- a/test/src/commands/auto-reload.ts +++ /dev/null @@ -1,158 +0,0 @@ -import * as Bluebird from "bluebird" -import { expect } from "chai" -import { join } from "path" -import { pathExists, remove, writeFile } from "fs-extra" -import { merge, sortedUniq, values } from "lodash" -import { FSWatcher } from "../../../src/watch" -import { - computeAutoReloadDependants, -} from "../../../src/watch" -import { makeTestGarden } from "../../helpers" -import { Garden } from "../../../src/garden" - -/* - - Build dependency diagram for auto-reload-project: - - a - | - b c - / \ / \ - d e f - - module-a has no service (i.e. is a build-only module). - - Service dependency diagram: - - b c - / \ / \ - d e f - - */ - -const projectRoot = join(__dirname, "data", "auto-reload-project") - -const makeGarden = async () => { - return await makeTestGarden(projectRoot) -} - -async function changeSource(garden: Garden, moduleName: string) { - await writeFile(join(projectRoot, moduleName, "bar"), "bar") - console.log("wrote", join(projectRoot, moduleName, "bar")) -} - -async function resetSources(ctx) { - const dirNames = ["module-a", "module-b", "module-c", "module-d", "module-e", "module-f"] - - await Bluebird.each(dirNames, async (dirName) => { - const dirPath = join(projectRoot, dirName) - - await writeFile(join(dirPath, "foo"), "foo") - - const barPath = join(dirPath, "bar") - console.log("barPath", barPath) - if (await pathExists(barPath)) { - await remove(barPath) - } - }) -} - -async function watch( - watcher: FSWatcher, garden: Garden, moduleNames: string[], - changeHandler?: (changedModule, taskResults, response) => void, -) { - console.log("start of watch") - const modules = values(await garden.getModules(moduleNames)) - // const autoReloadDependants = await computeAutoReloadDependants(modules) - - await watcher.watchModules(modules, "testAutoReload", async (changedModule, response) => { - console.log(`files changed for module ${changedModule.name}`) - - // await addTasksForAutoReload(garden.pluginContext, changedModule, autoReloadDependants) - const taskResults = await garden.processTasks() - - if (changeHandler) { - changeHandler(changedModule, taskResults, response) - } - }) - - console.log("end of watch") -} - -// async function testAutoReload(ctx: GardenContext, moduleName: string) { -// const modules = values(await ctx.getModules()) -// const autoReloadDependants = await computeAutoReloadDependants(modules) -// const entryModule = modules.find(m => m.name === moduleName) as Module -// -// await addTasksForAutoReload(ctx, entryModule, autoReloadDependants) -// return await ctx.processTasks() -// } - -const setup = async () => { - const garden = await makeGarden() - // await resetSources(garden) - const autoReloadDependants = await computeAutoReloadDependants(values(await garden.getModules())) - const watcher = new FSWatcher() - - return { autoReloadDependants, garden, watcher } -} - -describe("commands.autoreload", () => { - - // WIP - it.skip("should re-deploy a module and its dependant modules when its sources change", async () => { - const { autoReloadDependants, garden, watcher } = await setup() - - let entryModuleNames: string[] = [] - let reloadResults = {} - - const changeHandler = async (changedModule, taskResults, response) => { - // watchCounter = watchCounter + 1 - entryModuleNames.push(changedModule.name) - // console.log("module changed:", changedModule.name, "entryModuleNames:", [...entryModuleNames]) - console.log("module changed:", changedModule.name, "entryModuleNames:", [...entryModuleNames], "response", response.files.map(f => f.name)) - // await addTasksForAutoReload(garden.pluginContext, changedModule, autoReloadDependants) - merge(reloadResults, taskResults) - } - - await watch(watcher, garden, ["module-a"], changeHandler) - - // for (const module of values(await garden.getModules())) { - // await addTasksForAutoReload(garden.pluginContext, module, autoReloadDependants) - // } - // - // const results = await garden.processTasks() - // console.log("results", results) - - await changeSource(garden, "module-a") - // await changeSource(garden, "module-b") - // await changeSource(garden, "module-f") - - // console.log("start of sleep") - // await sleep(2000) - // console.log("end of sleep") - - watcher.end() - - expect(sortedUniq(entryModuleNames)) - .to.eql(["module-a", "module-b"]) - - const expectedResult = { - "build.module-a": { fresh: true, buildLog: "A\n" }, - - "build.module-b": { fresh: true, buildLog: "B\n" }, - "deploy.service-b": { version: "1", state: "ready" }, - - "build.module-c": { fresh: true, buildLog: "C\n" }, - "deploy.service-c": { version: "1", state: "ready" }, - - "build.module-d": { fresh: true, buildLog: "D\n" }, - "deploy.service-d": { version: "1", state: "ready" }, - - "build.module-e": { fresh: true, buildLog: "E\n" }, - "deploy.service-e": { version: "1", state: "ready" }, - } - - expect(reloadResults).to.eql(expectedResult) - }) -})