diff --git a/README.md b/README.md index 232a6bf6ce..e8283333ac 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ but still), overall stability will improve and platform support is still limited All that said, Garden can already be highly useful if the following applies to you: * **You're deploying to (or transitioning to) Kubernetes.** -* **You develop on Mac or Linux.** * **You work mostly with containers** _**today**_ _\(but perhaps plan on adopting serverless platforms in the future\)._ * **You keep all your services in a single repository** _(multi-repo support coming soon!)._ * **You really don't want to spend your precious hours building your own developer tooling!** diff --git a/docs/guides/minikube.md b/docs/guides/minikube.md index 1a2da3f5c0..85d58da713 100644 --- a/docs/guides/minikube.md +++ b/docs/guides/minikube.md @@ -1,32 +1,31 @@ -## Using Garden with Minikube +# Using Garden with Minikube Garden can be used with [Minikube](https://github.com/kubernetes/minikube) on supported platforms. -### Installation +_NOTE: We highly recommend using Docker for Mac and Docker for Windows, for macOS and Windows respectively._ -For Minikube installation instructions, please see the +## Installation + +For Minikube installation instructions, please see the [official guide](https://github.com/kubernetes/minikube#installation). -You'll likely also need to install a driver to run the Minikube VM, please follow the +You'll likely also need to install a driver to run the Minikube VM, please follow the [instructions here](https://github.com/kubernetes/minikube/blob/master/docs/drivers.md) and note the name of the driver you use. The driver you choose will likely vary depending on your -OS/platform. We recommend [hyperkit](https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperkit-driver) +OS/platform. We recommend [hyperkit](https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperkit-driver) for macOS and [kvm2](https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#kvm2-driver) on most Linux platforms. - + Once Minikube and the appropriate driver for your OS is installed, you can start it by running: minikube start --vm-driver= # e.g. hyperkit on macOS - + You'll also need to have Docker (for macOS, we recommend [Docker for Mac](https://docs.docker.com/engine/installation/)) and [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) installed. -_NOTE: Garden is not yet officially supported on Windows, but we have every intention to support it. -Please file any issues and we will try and respond promptly._ - -### Usage +## Usage -The `local-kubernetes` plugin attempts to automatically detect if it is installed and set the appropriate context +The `local-kubernetes` plugin attempts to automatically detect if it is installed and set the appropriate context for connecting to the local Kubernetes instance. In most cases you should not have to update your `garden.yml` since it uses the `local-kubernetes` plugin by default, but you can configure it explicitly in your project `garden.yml` like so: @@ -37,20 +36,20 @@ project: - name: local providers: - name: local-kubernetes - context: minikube -``` + context: minikube +``` _Note: If you happen to have installed both Minikube and the Docker for Mac version with Kubernetes enabled, `garden` will choose whichever one is configured as the current context in your `kubectl` configuration, and if neither is set as the current context, Docker for Mac is preferred by default._ -### Hostname +## Hostname -Garden needs the Kubernetes instance to have a hostname. By default Garden will use `.nip.io`. If you'd +Garden needs the Kubernetes instance to have a hostname. By default Garden will use `.nip.io`. If you'd like to use a custom hostname, you can specify it via the `ingressHostname` in the `local-kubernetes` provider config (see above). -### Anything else? +## Anything else? -Once the above is set up, the `local-kubernetes` plugin will automatically configure everything else Garden needs to +Once the above is set up, the `local-kubernetes` plugin will automatically configure everything else Garden needs to work. The built-in nginx ingress controller will be automatically enabled and used to route requests to services. diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index b76486fbda..c1ddfa77a6 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -2,7 +2,6 @@ This guide will walk you through setting up the Garden framework. - ## Installation ### macOS @@ -15,16 +14,17 @@ steps below if you prefer. If you haven't already set up homebrew, please follow [their instructions](https://brew.sh/) to set it up. #### Step 2: Docker and local Kubernetes -To install Docker, Kubernetes and kubectl, we strongly recommend Docker for Mac (edge version). -_Note: you need to install the **edge version** of Docker for Mac in +To install Docker, Kubernetes and kubectl, we strongly recommend Docker for Mac (edge version). + +_Note: you need to install the **edge version** of Docker for Mac in order to enable Kubernetes support._ -Once installed, open the Docker for Mac preferences, go to the Kubernetes section, -tick `Enable Kubernetes` and save. Please refer to their +Once installed, open the Docker for Mac preferences, go to the Kubernetes section, +tick `Enable Kubernetes` and save. Please refer to their [installation guide](https://docs.docker.com/engine/installation/) for details. -Alternatively, you can use Minikube. We generally find it less stable and more hassle to +Alternatively, you can use Minikube. We generally find it less stable and more hassle to configure and use, but we do fully support it on Mac if you have it running. Please look at our [Minikube guide](../guides/minikube.md) for details. @@ -37,9 +37,61 @@ brew tap garden-io/garden brew install garden-cli ``` -To later upgrade to the newest version, simply run `brew update` and then `brew upgrade garden-cli` +To later upgrade to the newest version, simply run `brew update` and then `brew upgrade garden-cli` (or `brew upgrade` to upgrade all your Homebrew packages). +### Windows + +You can run garden on Windows 10 Pro or Enterprise editions. Follow these instructions to get started. + +#### Step 1: Chocolatey + +If you haven't already, install the [Chocolatey](https://chocolatey.org) package manager. + +#### Step 2: Enable Hyper-V + +This is required for _Docker for Windows_ to run. Open PowerShell as an administrator and run: + +```powershell +Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All +``` + +This will require a restart. + +#### Step 2: Docker for Windows + +Install the Edge version of [Docker for Windows](https://www.docker.com/docker-windows). + +Once installed, open the Docker for Windows settings, go to the Kubernetes section, +tick `Enable Kubernetes` and save. Please refer to their +[installation guide](https://docs.docker.com/engine/installation/) for details. + +#### Step 3: Install dependencies + +Open PowerShell as an admistrator and run: + +```powershell +# install choco packages (note: python is needed to build some dependencies) +choco install -y git nodejs rsync kubernetes-helm + +# install Stern (currently not available as a choco package) +[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" +Invoke-WebRequest -Uri "https://github.com/wercker/stern/releases/download/1.7.0/stern_windows_amd64.exe" -OutFile "$Env:SystemRoot\system32\stern.exe" + +# install build tools so that node-gyp works +npm install --global --production windows-build-tools +npm config set msvs_version 2015 --global +``` + +#### Step 4: Install `garden-cli` + +Once you have the dependencies set up, open a new PowerShell and install the Garden CLI via `npm`: + +```powershell +npm install -g garden-cli +``` + +To later upgrade to the newest version, run `npm install -g -U garden-cli`. ### Linux / manual installation @@ -53,10 +105,12 @@ You need the following dependencies on your local machine to use Garden: * Local installation of Kubernetes and kubectl #### Step 1: Docker + To install Docker, please follow the instructions in the [official documentation](https://docs.docker.com/install/). #### Step 2: Local Kubernetes -For local Kubernetes, you can use [Minikube](https://github.com/kubernetes/minikube). Please see our + +For local Kubernetes, you can use [Minikube](https://github.com/kubernetes/minikube). Please see our [Minikube guide](../guides/minikube.md) for instructions. #### Step 3: Install other dependencies @@ -74,10 +128,9 @@ npm install -g garden-cli To later upgrade to the newest version, run `npm install -g -U garden-cli`. - ## Using the CLI -With the CLI installed, we can now try out a few commands using the [hello-world](https://github.com/garden-io/garden-examples/tree/master/hello-world) project from our Github [examples repository](https://github.com/garden-io/garden-examples). The example consists of a container service that runs an [Express](http://expressjs.com/) app, a serverless function, and an npm library package. +With the CLI installed, we can now try out a few commands using the [hello-world](https://github.com/garden-io/garden-examples/tree/master/simple-project) project from our Github [examples repository](https://github.com/garden-io/garden-examples). The example consists of a a couple of simple services. _Note: check if Kubernetes is running with `kubectl version`. You should see both a `Client Version` and a `Server Version` in the response. If not, please start it up before proceeding._ diff --git a/examples/hello-world/services/hello-function/libraries b/examples/hello-world/services/hello-function/libraries deleted file mode 120000 index 195d8f0975..0000000000 --- a/examples/hello-world/services/hello-function/libraries +++ /dev/null @@ -1 +0,0 @@ -../../libraries \ No newline at end of file diff --git a/garden-cli/package-lock.json b/garden-cli/package-lock.json index a338132a65..23d8c694e0 100644 --- a/garden-cli/package-lock.json +++ b/garden-cli/package-lock.json @@ -1262,6 +1262,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "dev": true, "requires": { "follow-redirects": "^1.3.0", "is-buffer": "^1.1.5" @@ -2898,6 +2899,11 @@ "event-emitter": "~0.3.5" } }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" + }, "es6-promise": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", @@ -3301,9 +3307,10 @@ } }, "follow-redirects": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", - "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", + "integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==", + "dev": true, "requires": { "debug": "^3.1.0" }, @@ -3312,6 +3319,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -9818,11 +9826,6 @@ "glob": "^7.0.5" } }, - "rsync": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/rsync/-/rsync-0.6.1.tgz", - "integrity": "sha1-NoGgCYvYdQRI+L+dof7gn3djdCs=" - }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -9980,6 +9983,33 @@ "rechoir": "^0.6.2" } }, + "shx": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.2.tgz", + "integrity": "sha512-aS0mWtW3T2sHAenrSrip2XGv39O9dXIFUqxAEWHEOS1ePtGIBavdPJY1kE2IHl14V/4iCbUiNDPGdyYTtmhSoA==", + "requires": { + "es6-object-assign": "^1.0.3", + "minimist": "^1.2.0", + "shelljs": "^0.8.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "shelljs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", + "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + } + } + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", diff --git a/garden-cli/package.json b/garden-cli/package.json index ab9d1ea51d..f3cad450d2 100644 --- a/garden-cli/package.json +++ b/garden-cli/package.json @@ -25,7 +25,6 @@ "@kubernetes/client-node": "^0.3.0", "ansi-escapes": "^3.1.0", "async-exit-hook": "^2.0.1", - "axios": "^0.18.0", "bluebird": "^3.5.1", "chalk": "^2.4.1", "child-process-promise": "^2.2.1", @@ -54,7 +53,7 @@ "moment": "^2.22.2", "node-emoji": "^1.8.1", "node-pty": "^0.7.4", - "rsync": "^0.6.1", + "shx": "^0.3.2", "snyk": "^1.82.2", "split": "^1.0.1", "strip-ansi": "^4.0.0", @@ -95,6 +94,7 @@ "@types/prettyjson": "0.0.28", "@types/uniqid": "^4.1.2", "@types/wrap-ansi": "^3.0.0", + "axios": "^0.18.0", "chai": "^4.1.2", "gulp": "^4.0.0", "gulp-cached": "^1.1.1", @@ -123,7 +123,7 @@ "scripts": { "build": "npm run clean && gulp build", "check-package-lock": "git diff-index --quiet HEAD -- package-lock.json || (echo 'package-lock.json is dirty!' && exit 1)", - "clean": "rm -rf build && git clean -X -f", + "clean": "shx rm -rf build && git clean -X -f", "dev": "npm run clean && gulp build && npm link && gulp watch", "dist": "npm run build", "generate-docs": "gulp generate-docs", diff --git a/garden-cli/src/build-dir.ts b/garden-cli/src/build-dir.ts index 38c30d0324..bafb98ca7e 100644 --- a/garden-cli/src/build-dir.ts +++ b/garden-cli/src/build-dir.ts @@ -13,20 +13,23 @@ import { parse, resolve, sep, + win32, + posix, } from "path" import { emptyDir, ensureDir, } from "fs-extra" -import * as Rsync from "rsync" import { GARDEN_DIR_NAME } from "./constants" import { ConfigurationError } from "./exceptions" -import { execRsyncCmd } from "./util/util" import { BuildCopySpec, Module, } from "./types/module" import { zip } from "lodash" +import * as execa from "execa" +import { platform } from "os" +import { toCygwinPath } from "./util/util" // Lazily construct a directory of modules inside which all build steps are performed. @@ -96,11 +99,12 @@ export class BuildDir { const destinationDir = parse(destinationPath).dir await ensureDir(destinationDir) - const syncCmd = new Rsync() - .flags(["r", "p", "t", "g", "o"]) - .source(sourcePath) - .destination(destinationPath) + if (platform() === "win32") { + // this is so that the cygwin-based rsync client can deal with the paths + sourcePath = toCygwinPath(sourcePath) + destinationPath = toCygwinPath(destinationPath) + } - await execRsyncCmd(syncCmd) + await execa("rsync", ["-rptgo", sourcePath, destinationPath]) } } diff --git a/garden-cli/src/plugins/kubernetes/api.ts b/garden-cli/src/plugins/kubernetes/api.ts index b2119fb789..4aa8c3d09d 100644 --- a/garden-cli/src/plugins/kubernetes/api.ts +++ b/garden-cli/src/plugins/kubernetes/api.ts @@ -23,6 +23,7 @@ import { } from "lodash" import { GardenBaseError, ConfigurationError } from "../../exceptions" import { KubernetesObject } from "./helm" +import { homedir } from "os" let kubeConfigStr: string let kubeConfig: any @@ -33,8 +34,7 @@ const configs: { [context: string]: KubeConfig } = {} function getConfig(context: string): KubeConfig { if (!kubeConfigStr) { - kubeConfigStr = readFileSync(process.env.KUBECONFIG || join(process.env.HOME || "/home", ".kube", "config")) - .toString() + kubeConfigStr = readFileSync(join(homedir(), ".kube", "config")).toString() kubeConfig = safeLoad(kubeConfigStr) } diff --git a/garden-cli/src/plugins/kubernetes/kubectl.ts b/garden-cli/src/plugins/kubernetes/kubectl.ts index 283b30262e..8484da38be 100644 --- a/garden-cli/src/plugins/kubernetes/kubectl.ts +++ b/garden-cli/src/plugins/kubernetes/kubectl.ts @@ -12,6 +12,7 @@ import { extend } from "lodash" import { encodeYamlMulti, spawnPty } from "../../util/util" import { RuntimeError } from "../../exceptions" import { getLogger } from "../../logger/logger" +import { platform } from "os" import hasAnsi = require("has-ansi") export interface KubectlParams { @@ -64,7 +65,7 @@ export class Kubectl { } const preparedArgs = this.prepareArgs(args) - const proc = spawn("kubectl", preparedArgs) + const proc = spawn(this.getExececutable(), preparedArgs) proc.stdout.on("data", (s) => { if (!silent) { @@ -133,11 +134,16 @@ export class Kubectl { } async tty(args: string[], opts: KubectlParams = {}): Promise { - return spawnPty("kubectl", this.prepareArgs(args), opts) + return spawnPty(this.getExececutable(), this.prepareArgs(args), opts) } spawn(args: string[]): ChildProcess { - return spawn("kubectl", this.prepareArgs(args)) + return spawn(this.getExececutable(), this.prepareArgs(args)) + } + + private getExececutable() { + // workaround for https://github.com/Microsoft/node-pty/issues/109 + return platform() === "win32" ? "kubectl.exe" : "kubectl" } private prepareArgs(args: string[]) { diff --git a/garden-cli/src/plugins/kubernetes/local.ts b/garden-cli/src/plugins/kubernetes/local.ts index eaa630816e..8d787d7379 100644 --- a/garden-cli/src/plugins/kubernetes/local.ts +++ b/garden-cli/src/plugins/kubernetes/local.ts @@ -42,13 +42,14 @@ import { import { readFile } from "fs-extra" import { processServices } from "../../process" import { LogEntry } from "../../logger/logger" +import { homedir } from "os" // TODO: split this into separate plugins to handle Docker for Mac and Minikube // note: this is in order of preference, in case neither is set as the current kubectl context // and none is explicitly configured in the garden.yml const supportedContexts = ["docker-for-desktop", "minikube"] -const kubeConfigPath = join(process.env.HOME || "~", ".kube", "config") +const kubeConfigPath = join(homedir(), ".kube", "config") // extend the environment configuration to also set up an ingress controller and dashboard export async function getLocalEnvironmentStatus( diff --git a/garden-cli/src/util/util.ts b/garden-cli/src/util/util.ts index 167f8caf62..5529116a3c 100644 --- a/garden-cli/src/util/util.ts +++ b/garden-cli/src/util/util.ts @@ -16,12 +16,9 @@ import * as yaml from "js-yaml" import * as Cryo from "cryo" import { spawn as _spawn } from "child_process" import { pathExists, readFile, writeFile } from "fs-extra" -import { join, basename } from "path" +import { join, basename, win32, posix } from "path" import { find } from "lodash" -import { - TimeoutError, - GardenBaseError, -} from "../exceptions" +import { TimeoutError } from "../exceptions" import { PassThrough } from "stream" import { isArray, isPlainObject, extend, mapValues, pickBy } from "lodash" import highlight from "cli-highlight" @@ -58,42 +55,6 @@ export function shutdown(code) { process.exit(code) } -export class RsyncError extends GardenBaseError { - type = "rsync" -} - -export type RsyncStdIOCallback = () => void - -export type RsyncErrorCallback = (error: Error, code: string, cmd: string) => void - -// Note: Rsync instances from the rsync npm module fit this interface. -export interface RsyncCommand { - execute: ( - errorCallback: RsyncErrorCallback, - stdoutHandler?: RsyncErrorCallback, - stderrHandler?: RsyncErrorCallback, - ) => void -} - -export function execRsyncCmd(rsyncCmd: RsyncCommand, stdoutHandler?: RsyncStdIOCallback, - stderrHandler?: RsyncStdIOCallback): Bluebird { - - return new Bluebird((resolve, reject) => { - rsyncCmd.execute((error: Error, code: string, cmd: string) => { - if (!error) { - resolve() - } else { - reject(new RsyncError(`Unable to sync files`, { - error, - code, - cmd, - })) - } - }, stdoutHandler, stderrHandler) - }) - -} - export function registerCleanupFunction(name: string, func: HookCallback) { exitHookNames.push(name) exitHook(func) @@ -258,6 +219,8 @@ export function spawnPty( }: SpawnPtyParams = {}, ): Bluebird { + // + let _process = process let proc: any = pty.spawn(cmd, args, { @@ -430,3 +393,16 @@ export function getNames(array: T[]) { export function findByName(array: T[], name: string): T | undefined { return find(array, ["name", name]) } + +/** + * Converts a Windows-style path to a cygwin style path (e.g. C:\some\folder -> /cygdrive/c/some/folder). + */ +export function toCygwinPath(path: string) { + const parsed = win32.parse(path) + const drive = parsed.root.split(":")[0].toLowerCase() + const dirs = parsed.dir.split(win32.sep).slice(1) + const cygpath = posix.join("/cygdrive", drive, ...dirs, parsed.base) + + // make sure trailing slash is retained + return path.endsWith(win32.sep) ? cygpath + posix.sep : cygpath +} diff --git a/garden-cli/support/homebrew-formula.rb b/garden-cli/support/homebrew-formula.rb index bfbbd7b719..b46d3af5ca 100644 --- a/garden-cli/support/homebrew-formula.rb +++ b/garden-cli/support/homebrew-formula.rb @@ -8,6 +8,7 @@ class GardenCli < Formula depends_on "node" depends_on "rsync" + depends_on "stern" depends_on "python" => :build def install diff --git a/garden-cli/test/src/util.ts b/garden-cli/test/src/util.ts index 9e9974fc65..99608c401c 100644 --- a/garden-cli/test/src/util.ts +++ b/garden-cli/test/src/util.ts @@ -1,6 +1,6 @@ import { expect } from "chai" import { join } from "path" -import { scanDirectory, getChildDirNames } from "../../src/util/util" +import { scanDirectory, getChildDirNames, toCygwinPath } from "../../src/util/util" describe("util", () => { describe("scanDirectory", () => { @@ -40,4 +40,16 @@ describe("util", () => { expect(await getChildDirNames(testPath)).to.eql(["a", "b"]) }) }) + + describe("toCygwinPath", () => { + it("should convert a win32 path to a cygwin path", () => { + const path = "C:\\some\\path" + expect(toCygwinPath(path)).to.equal("/cygdrive/c/some/path") + }) + + it("should retain a trailing slash", () => { + const path = "C:\\some\\path\\" + expect(toCygwinPath(path)).to.equal("/cygdrive/c/some/path/") + }) + }) }) diff --git a/package-lock.json b/package-lock.json index 36f822fbba..d18f568754 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3089,6 +3089,12 @@ "es6-symbol": "^3.1.1" } }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", + "dev": true + }, "es6-symbol": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", @@ -7766,6 +7772,36 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shelljs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", + "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "shx": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.2.tgz", + "integrity": "sha512-aS0mWtW3T2sHAenrSrip2XGv39O9dXIFUqxAEWHEOS1ePtGIBavdPJY1kE2IHl14V/4iCbUiNDPGdyYTtmhSoA==", + "dev": true, + "requires": { + "es6-object-assign": "^1.0.3", + "minimist": "^1.2.0", + "shelljs": "^0.8.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", diff --git a/package.json b/package.json index bdd247834f..181e7d4ac8 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "gulp-typescript": "^5.0.0-alpha.1", "husky": "^0.15.0-rc.13", "lerna": "^3.0.0-beta.21", + "shx": "^0.3.2", "ts-node": "^6.1.1", "tslint": "^5.10.0", "tslint-microsoft-contrib": "^5.0.3",