@import './_variables'
@import '../../styles/styles.sass'

+theme(v-file-input) using ($material)
  .v-file-input__text
    color: map-deep-get($material, 'text', 'primary')

    &--placeholder
      color: map-deep-get($material, 'text', 'secondary')

  &.v-input--is-disabled
    .v-file-input__text
      color: map-deep-get($material, 'text', 'disabled')

      .v-file-input__text--placeholder
        color: map-deep-get($material, 'text', 'disabled')

// Block
.v-file-input
  input[type="file"]
    left: 0
    opacity: 0
    pointer-events: none
    position: absolute
    max-width: 0
    width: 0

// Element
.v-file-input .v-file-input__text
  align-items: center
  align-self: stretch
  display: flex
  flex-wrap: wrap
  width: 100%

  &.v-file-input__text--chips
    flex-wrap: wrap

  .v-chip
    margin: $file-input-chip-margin

.v-file-input .v-text-field__slot
  min-height: $file-input-slot-min-height

.v-file-input
  &.v-input--dense
    .v-text-field__slot
      min-height: $file-input-slot-dense-min-height

.v-file-input.v-text-field--filled:not(.v-text-field--single-line)
  .v-file-input__text
    padding-top: $file-input-filled-padding-top

.v-file-input.v-text-field--outlined
  .v-text-field__slot
    padding: $file-input-outlined-padding

  &.v-input--dense
    .v-text-field__slot
      padding: $file-input-outlined-dense-padding


if (
handler && (
!modifiers.quiet ||
el._observe.init
)
) {

) && (
!modifiers.once ||
isIntersecting ||
!el._observe.init

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VSelect.ts should add color to selected index 1`] = `

            foo

          <input id="input-50"
                 readonly="readonly"
                 type="text"
                 aria-readonly="false"
                 autocomplete="off"
          >

              $dropdown

        <input type="hidden"
               value="foo"
        >

`;

exports[`VSelect.ts should open the select is multiple and key down is pressed 1`] = `

                $checkboxOff

            foo

                $checkboxOff

            bar

`;

exports[`VSelect.ts should open the select is multiple and key up is pressed 1`] = `

                $checkboxOff

            foo

                $checkboxOff

            bar

`;

exports[`VSelect.ts should open the select when enter is pressed 1`] = `

            foo

            bar

`;

exports[`VSelect.ts should open the select when space is pressed 1`] = `

            foo

            bar

`;


// Directives
import ripple from '../../directives/ripple'

// Types
import Vue, { VNode, VNodeData, VNodeDirective } from 'vue'

export default Vue.extend({
  name: 'rippleable',

  directives: { ripple },

  props: {
    ripple: {
      type: [Boolean, Object],
      default: true,
    },
  },

  methods: {
    genRipple (data: VNodeData = {}): VNode | null {
      if (!this.ripple) return null

      data.staticClass = 'v-input--selection-controls__ripple'

      data.directives = data.directives || []
      data.directives.push({
        name: 'ripple',
        value: { center: true },
      } as VNodeDirective)

      return this.$createElement('div', data)
    },
  },
})


keep keep replace replace keep keep keep

import { VNodeDirective } from 'vue/types/vnode'

type ObserveHandler = (
  entries: IntersectionObserverEntry[],
  observer: IntersectionObserver,
  isIntersecting: boolean,
) => void

interface ObserveVNodeDirective extends Omit<VNodeDirective, 'modifiers'> {
  value?: ObserveHandler | { handler: ObserveHandler, options?: IntersectionObserverInit }
  modifiers?: {
    once?: boolean
    quiet?: boolean
  }
}

function inserted (el: HTMLElement, binding: ObserveVNodeDirective) {
  if (typeof window === 'undefined' || !('IntersectionObserver' in window)) return

  const modifiers = binding.modifiers || {}
  const value = binding.value
  const { handler, options } = typeof value === 'object'
    ? value
    : { handler: value, options: {} }
  const observer = new IntersectionObserver((
    entries: IntersectionObserverEntry[] = [],
    observer: IntersectionObserver
  ) => {
    /* istanbul ignore if */
    if (!el._observe) return // Just in case, should never fire

    // If is not quiet or has already been
    // initted, invoke the user callback
    if (
      handler && (
        !modifiers.quiet ||
        el._observe.init
      )
    ) {
      const isIntersecting = Boolean(entries.find(entry => entry.isIntersecting))

      handler(entries, observer, isIntersecting)
    }

    // If has already been initted and
    // has the once modifier, unbind
    if (el._observe.init && modifiers.once) unbind(el)
    // Otherwise, mark the observer as initted
    else (el._observe.init = true)
  }, options)

  el._observe = { init: false, observer }

  observer.observe(el)
}

function unbind (el: HTMLElement) {
  /* istanbul ignore if */
  if (!el._observe) return

  el._observe.observer.unobserve(el)
  delete el._observe
}

export const Intersect = {
  inserted,
  unbind,
}

export default Intersect


<template>
  <v-card
    class="mx-auto"
    color="#26c6da"
    dark
    max-width="400"
  >
    <v-card-title>
      <v-icon
        large
        left
      >
        mdi-twitter
      </v-icon>
      Twitter
    </v-card-title>

    <v-card-text class="headline font-weight-bold">
      "Turns out semicolon-less style is easier and safer in TS because most gotcha edge cases are type invalid as well."
    </v-card-text>

    <v-card-actions>
      <v-list-item class="grow">
        <v-list-item-avatar color="grey darken-3">
          <v-img
            class="elevation-6"
            alt=""
            src="https://avataaars.io/?avatarStyle=Transparent&topType=ShortHairShortCurly&accessoriesType=Prescription02&hairColor=Black&facialHairType=Blank&clotheType=Hoodie&clotheColor=White&eyeType=Default&eyebrowType=DefaultNatural&mouthType=Default&skinColor=Light"
          ></v-img>
        </v-list-item-avatar>

        <v-list-item-content>
          <v-list-item-title>Evan You</v-list-item-title>
        </v-list-item-content>

        <v-row
          align="center"
          justify="end"
        >
          <v-icon class="mr-1">
            mdi-heart
          </v-icon>
          256
          ·
          <v-icon class="mr-1">
            mdi-share-variant
          </v-icon>
          45
        </v-row>
      </v-list-item>
    </v-card-actions>
  </v-card>
</template>


module.exports = {
  'v-tab-item': {
    slots: [
      {
        name: 'default',
        props: undefined,
      },
    ],
  },
}


@mixin prepend-append-margin-top($margin-top)
  .v-input__prepend-outer,
  .v-input__prepend-inner,
  .v-input__append-inner,
  .v-input__append-outer
    margin-top: $margin-top

@mixin outer-margin-top($margin-top)
  .v-input__prepend-outer,
  .v-input__append-outer
    margin-top: $margin-top

@mixin label-position($top, $translateY, $scale: .75)
  .v-label
    top: $top

    &--active
      transform: translateY($translateY) scale($scale)


if (el._observe.init && modifiers.once) unbind(el)

// Otherwise, mark the observer as initted

keep keep keep replace replace replace replace replace keep keep keep keep keep

if (isIntersecting && modifiers.once) unbind(el)

// Directives
import Intersect from '../'

describe('resize.ts', () => {
  it('should bind event on inserted', () => {
    const callback = jest.fn()
    const el = document.createElement('div')
    document.body.appendChild(el)

    Intersect.inserted(el, {
      value: callback,
      modifiers: { quiet: true },
    } as any)

    expect((el as any)._observe).toBeTruthy()
    expect(callback).not.toHaveBeenCalled()

    document.body.removeChild(el)

    Intersect.unbind(el)

    expect((el as any)._observe).toBeFalsy()
  })

  it('should invoke callback once and unbind', () => {
    const el = document.createElement('div')

    document.body.appendChild(el)

    const callback = jest.fn()

    Intersect.inserted(el, {
      value: callback,
      modifiers: { once: true },
    } as any)

    expect(callback).toHaveBeenCalled()
    expect((el as any)._observe).toBeTruthy()

    if ((el as any)._observe) {
      (el as any)._observe.observer.callback()
    }

    expect(callback).toHaveBeenCalledTimes(2)
    expect((el as any)._observe).toBeFalsy()
  })
})


<template>
  <v-card
    class="mx-auto"
    width="300"
  >
    <v-list>
      <v-list-item>
        <v-list-item-icon>
          <v-icon>mdi-home</v-icon>
        </v-list-item-icon>

        <v-list-item-title>Home</v-list-item-title>
      </v-list-item>

      <v-list-group
        :value="true"
        prepend-icon="mdi-account-circle"
      >
        <template v-slot:activator>
          <v-list-item-title>Users</v-list-item-title>
        </template>

        <v-list-group
          :value="true"
          no-action
          sub-group
        >
          <template v-slot:activator>
            <v-list-item-content>
              <v-list-item-title>Admin</v-list-item-title>
            </v-list-item-content>
          </template>

          <v-list-item
            v-for="([title, icon], i) in admins"
            :key="i"
            link
          >
            <v-list-item-title v-text="title"></v-list-item-title>

            <v-list-item-icon>
              <v-icon v-text="icon"></v-icon>
            </v-list-item-icon>
          </v-list-item>
        </v-list-group>

        <v-list-group
          no-action
          sub-group
        >
          <template v-slot:activator>
            <v-list-item-content>
              <v-list-item-title>Actions</v-list-item-title>
            </v-list-item-content>
          </template>

          <v-list-item
            v-for="([title, icon], i) in cruds"
            :key="i"
            link
          >
            <v-list-item-title v-text="title"></v-list-item-title>

            <v-list-item-icon>
              <v-icon v-text="icon"></v-icon>
            </v-list-item-icon>
          </v-list-item>
        </v-list-group>
      </v-list-group>
    </v-list>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      admins: [
        ['Management', 'mdi-account-multiple-outline'],
        ['Settings', 'mdi-cog-outline'],
      ],
      cruds: [
        ['Create', 'mdi-plus-outline'],
        ['Read', 'mdi-file-outline'],
        ['Update', 'mdi-update'],
        ['Delete', 'mdi-delete'],
      ],
    }),
  }
</script>


# Security Disclosure Procedures

This document outlines security procedures and general policies for the Vuetify project.

  * [Reporting a Bug](#reporting-a-bug)
  * [Disclosure Policy](#disclosure-policy)
  * [Comments on this Policy](#comments-on-this-policy)

## Reporting a Bug

The Vuetify team and community take all security bugs in Vuetify seriously. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.

To report a security issue, email [security@vuetifyjs.com](mailto:security@vuetifyjs.com?subject=SECURITY) and include the word **"SECURITY"** in the subject line.

The Vuetify team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.

Report security bugs in third-party modules to the person or team maintaining the module. You can also report a vulnerability through the [Node Security Project](https://nodesecurity.io/report).

## Disclosure Policy

When the security team receives a security bug report, they will assign it to a
primary handler. This person will coordinate the fix and release process,
involving the following steps:

  * Confirm the problem and determine the affected versions.
  * Audit code to find any potential similar problems.
  * Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible to npm.

## Comments on this Policy

If you have suggestions on how this process could be improved please submit a pull request using the [issue creator](https://issues.vuetifyjs.com).


<template>
  <v-stepper v-model="e1">
    <v-stepper-header>
      <v-stepper-step
        :complete="e1 > 1"
        step="1"
      >
        Name of step 1
      </v-stepper-step>

      <v-divider></v-divider>

      <v-stepper-step
        :complete="e1 > 2"
        step="2"
      >
        Name of step 2
      </v-stepper-step>

      <v-divider></v-divider>

      <v-stepper-step step="3">
        Name of step 3
      </v-stepper-step>
    </v-stepper-header>

    <v-stepper-items>
      <v-stepper-content step="1">
        <v-card
          class="mb-12"
          color="grey lighten-1"
          height="200px"
        ></v-card>

        <v-btn
          color="primary"
          @click="e1 = 2"
        >
          Continue
        </v-btn>

        <v-btn text>
          Cancel
        </v-btn>
      </v-stepper-content>

      <v-stepper-content step="2">
        <v-card
          class="mb-12"
          color="grey lighten-1"
          height="200px"
        ></v-card>

        <v-btn
          color="primary"
          @click="e1 = 3"
        >
          Continue
        </v-btn>

        <v-btn text>
          Cancel
        </v-btn>
      </v-stepper-content>

      <v-stepper-content step="3">
        <v-card
          class="mb-12"
          color="grey lighten-1"
          height="200px"
        ></v-card>

        <v-btn
          color="primary"
          @click="e1 = 1"
        >
          Continue
        </v-btn>

        <v-btn text>
          Cancel
        </v-btn>
      </v-stepper-content>
    </v-stepper-items>
  </v-stepper>
</template>

<script>
  export default {
    data () {
      return {
        e1: 1,
      }
    },
  }
</script>


createBinaryLink(src, dest, name, bin, callback) {

const destBinFolder = path.join(dest, ".bin");

// The `bin` in a package.json may be either a string or an object.

import fs from "graceful-fs";
import pathExists from "path-exists";
import assert from "assert";
import path from "path";
import normalize from "normalize-path";

import ChildProcessUtilities from "../src/ChildProcessUtilities";
import FileSystemUtilities from "../src/FileSystemUtilities";
import BootstrapCommand from "../src/commands/BootstrapCommand";
import exitWithCode from "./helpers/exitWithCode";
import initFixture from "./helpers/initFixture";
import stub from "./helpers/stub";

import assertStubbedCalls from "./helpers/assertStubbedCalls";

const STDIO_OPT = ["ignore", "pipe", "pipe"];

const resolveSymlink = (symlinkLocation) => {
  return path.resolve(path.dirname(symlinkLocation), fs.readlinkSync(symlinkLocation));
};

describe("BootstrapCommand", () => {
  describe("lifecycle scripts", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/lifecycle-scripts").then((dir) => {
      testDir = dir;
    }));

    it("should run preinstall, postinstall and prepublish scripts", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.ok(pathExists.sync(path.join(testDir, "packages", "package-preinstall", "did-preinstall")));
          assert.ok(pathExists.sync(path.join(testDir, "packages", "package-postinstall", "did-postinstall")));
          assert.ok(pathExists.sync(path.join(testDir, "packages", "package-prepublish", "did-prepublish")));

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("with hoisting", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/basic").then((dir) => {
      testDir = dir;
    }));

    it("should hoist", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {
        hoist: true
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const wantPackage = {
        [path.join(testDir, "package.json")]: {
          dependencies: { "foo": "^1.0.0", "@test/package-1": "^0.0.0" }
        },
        [path.join(testDir, "packages" ,"package-3", "package.json")]: {
          dependencies: { "foo": "0.1.12" }
        },
      };
      const gotPackage = {};
      stub(FileSystemUtilities, "writeFile", (fn, json, callback) => {
        gotPackage[fn] = JSON.parse(json);
        callback();
      });

      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        callback();
      });

      const wantRimraf = {
        [path.join(testDir, "packages" ,"package-1", "node_modules", "foo")]: true,
        [path.join(testDir, "packages" ,"package-2", "node_modules", "foo")]: true,
        [path.join(testDir, "packages" ,"package-4", "node_modules", "@test", "package-1")]: true,
      };
      const gotRimraf = {};
      stub(FileSystemUtilities, "rimraf", (path, callback) => {
        gotRimraf[path] = true;
        callback();
      });

      assertStubbedCalls([
        [FileSystemUtilities, "rename", { nodeCallback: true }, [
          { args: [
            path.join(testDir, "package.json"),
            path.join(testDir, "package.json.lerna_backup"),
          ] }
        ]],
        [FileSystemUtilities, "renameSync", { }, [
          { args: [
            path.join(testDir, "package.json.lerna_backup"),
            path.join(testDir, "package.json"),
          ] }
        ]],
        [FileSystemUtilities, "rename", { nodeCallback: true }, [
          { args: [
            path.join(testDir, "packages" ,"package-3", "package.json"),
            path.join(testDir, "packages" ,"package-3", "package.json.lerna_backup"),
          ] }
        ]],
        [FileSystemUtilities, "renameSync", { }, [
          { args: [
            path.join(testDir, "packages" ,"package-3", "package.json.lerna_backup"),
            path.join(testDir, "packages" ,"package-3", "package.json"),
          ] }
        ]],
      ]);

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.deepEqual(gotPackage, wantPackage, "Installed the right deps");
          assert.deepEqual(gotRimraf, wantRimraf, "Removed the right stuff");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    it("should not hoist when disallowed", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {
        hoist: true,
        nohoist: "@test/package-1"
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const wantPackage = {
        [path.join(testDir, "package.json")]: {
          dependencies: { "foo": "^1.0.0" }
        },
        [path.join(testDir, "packages" ,"package-3", "package.json")]: {
          dependencies: { "foo": "0.1.12" }
        },
        [path.join(testDir, "packages" ,"package-4", "package.json")]: {
          dependencies: { "@test/package-1": "^0.0.0" }
        },
      };
      const gotPackage = {};
      stub(FileSystemUtilities, "writeFile", (fn, json, callback) => {
        gotPackage[fn] = JSON.parse(json);
        callback();
      });

      const wantRimraf = {
        [path.join(testDir, "packages" ,"package-1", "node_modules", "foo")]: true,
        [path.join(testDir, "packages" ,"package-2", "node_modules", "foo")]: true,
      };
      const gotRimraf = {};
      stub(FileSystemUtilities, "rimraf", (path, callback) => {
        gotRimraf[path] = true;
        callback();
      });

      assertStubbedCalls([
        [ChildProcessUtilities, "spawn", { nodeCallback: true }, [
          { args: ["npm", ["install"], { cwd: testDir, stdio: STDIO_OPT }] },
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages" ,"package-3"), stdio: STDIO_OPT }] },
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages", "package-4"), stdio: STDIO_OPT }] },
        ]],
      ]);

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.deepEqual(gotPackage, wantPackage, "Installed the right deps");
          assert.deepEqual(gotRimraf, wantRimraf, "Removed the right stuff");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("dependencies between packages in the repo", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/basic").then((dir) => {
      testDir = dir;
    }));

    it("should bootstrap packages", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        callback();
      });

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          // package-1 should not have any packages symlinked
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-2")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-3")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-4")));
          // package-2 package dependencies are symlinked
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-2", "node_modules", "@test", "package-1"))),
            normalize(path.join(testDir, "packages", "package-1")),
            "package-1 should be symlinked to package-2"
          );
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-2", "node_modules", "package-3")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-2", "node_modules", "package-4")));
          // package-3 package dependencies are symlinked
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-3", "node_modules", "@test", "package-1"))),
            normalize(path.join(testDir, "packages", "package-1")),
            "package-1 should be symlinked to package-3"
          );
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-3", "node_modules", "package-2"))),
            normalize(path.join(testDir, "packages", "package-2")),
            "package-2 should be symlinked to package-3"
          );
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-3", "node_modules", "package-4")));
          // package-4 package dependencies are symlinked
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-1")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-2")));
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-4", "node_modules", "package-3"))),
            normalize(path.join(testDir, "packages", "package-3")),
            "package-3 should be symlinked to package-4"
          );
          // package binaries are symlinked
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-3", "node_modules", ".bin", "package-2"))),
            normalize(path.join(testDir, "packages", "package-2", "cli.js")),
            "package-2 binary should be symlinked in package-3"
          );
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli1"))),
            normalize(path.join(testDir, "packages", "package-3", "cli1.js")),
            "package-3 binary should be symlinked in package-4"
          );
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli2"))),
            normalize(path.join(testDir, "packages", "package-3", "cli2.js")),
            "package-3 binary should be symlinked in package-4"
          );

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    it("should not bootstrap ignored packages", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {
        ignore: "package-@(3|4)"
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const installed = [0,0,0];
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        installed[+options.cwd.match(/package-(\d)$/)[1]]++;
        callback();
      });

      assertStubbedCalls([
        [ChildProcessUtilities, "spawn", { nodeCallback: true }, [
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages", "package-1"), stdio: STDIO_OPT }] },
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages", "package-2"), stdio: STDIO_OPT }] },
        ]]
      ]);

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.deepEqual(installed, [0,1,1], "Did all our installs");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    it("should only bootstrap scoped packages", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {
        scope: "package-@(3|4)"
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const installed = [0,0,0,0,0];
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        installed[+options.cwd.match(/package-(\d)$/)[1]]++;
        callback();
      });

      assertStubbedCalls([
        [ChildProcessUtilities, "spawn", { nodeCallback: true }, [
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages" ,"package-3"), stdio: STDIO_OPT }] },
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages", "package-4"), stdio: STDIO_OPT }] },
        ]]
      ]);

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.deepEqual(installed, [0,0,0,1,1], "Did all our installs");

          // package-3 package dependencies are symlinked
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-3", "node_modules", "@test", "package-1"))),
            normalize(path.join(testDir, "packages", "package-1")),
            "package-1 should be symlinked to package-3"
          );
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-3", "node_modules", "package-2"))),
            normalize(path.join(testDir, "packages", "package-2")),
            "package-2 should be symlinked to package-3"
          );
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-3", "node_modules", "package-4")));
          // package-4 package dependencies are symlinked
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-1")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-2")));
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-4", "node_modules", "package-3"))),
            normalize(path.join(testDir, "packages", "package-3")),
            "package-3 should be symlinked to package-4"
          );
          // package binaries are symlinked
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-3", "node_modules", ".bin", "package-2"))),
            normalize(path.join(testDir, "packages", "package-2", "cli.js")),
            "package-2 binary should be symlinked in package-3"
          );
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli1"))),
            normalize(path.join(testDir, "packages", "package-3", "cli1.js")),
            "package-3 binary should be symlinked in package-4"
          );
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli2"))),
            normalize(path.join(testDir, "packages", "package-3", "cli2.js")),
            "package-3 binary should be symlinked in package-4"
          );

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("a repo with packages outside of packages/", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/extra").then((dir) => {
      testDir = dir;
    }));

    it("should bootstrap packages", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const want = {
        [path.join(testDir, "packages", "package-1")]: true,
        [path.join(testDir, "packages", "package-2")]: true,
        [path.join(testDir,             "package-3")]: true,
        [path.join(testDir, "packages", "package-4")]: true,
      };
      const got = {};
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        got[options.cwd] = true;
        callback();
      });

      assertStubbedCalls([
        [ChildProcessUtilities, "spawn", { nodeCallback: true }, [
          { args: ["npm", ["install", "foo@^1.0.0"], { cwd: path.join(testDir, "packages", "package-1"), stdio: STDIO_OPT }] },
          { args: ["npm", ["install", "foo@^1.0.0"], { cwd: path.join(testDir, "packages", "package-2"), stdio: STDIO_OPT }] },
          { args: ["npm", ["install", "foo@0.1.12"], { cwd: path.join(testDir, "package-3"), stdio: STDIO_OPT }] },
          { args: ["npm", ["install", "@test/package-1@^0.0.0"], { cwd: path.join(testDir, "packages", "package-4"), stdio: STDIO_OPT }] },
        ]],
      ]);

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.deepEqual(want, got, "Installed everywhere");

          // Make sure the `prepublish` script got run (index.js got created).
          assert.ok(pathExists.sync(path.join(testDir, "packages", "package-1", "index.js")));
          // package-1 should not have any packages symlinked
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-2")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-3")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-1", "node_modules", "package-4")));
          // package-2 package dependencies are symlinked
          assert.equal(
            path.resolve(resolveSymlink(path.join(testDir, "packages", "package-2", "node_modules", "@test", "package-1"))),
            path.resolve(path.join(testDir, "packages", "package-1")),
            "package-1 should be symlinked to package-2"
          );
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-2", "node_modules", "package-3")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-2", "node_modules", "package-4")));
          // package-3 package dependencies are symlinked
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "package-3", "node_modules", "@test", "package-1"))),
            normalize(path.join(testDir, "packages", "package-1")),
            "package-1 should be symlinked to package-3"
          );
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "package-3", "node_modules", "package-2"))),
            normalize(path.join(testDir, "packages", "package-2")),
            "package-2 should be symlinked to package-3"
          );
          assert.throws(() => fs.readlinkSync(path.join(testDir, "package-3", "node_modules", "package-4")));
          // package-4 package dependencies are symlinked
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-1")));
          assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-4", "node_modules", "package-2")));
          assert.equal(
            normalize(resolveSymlink(path.join(testDir, "packages", "package-4", "node_modules", "package-3"))),
            normalize(path.join(testDir, "package-3")),
            "package-3 should be symlinked to package-4"
          );
          // package binaries are symlinked
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "package-3", "node_modules", ".bin", "package-2"))),
            normalize(path.join(testDir, "packages", "package-2", "cli.js")),
            "package-2 binary should be symlinked in package-3"
          );
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli1"))),
            normalize(path.join(testDir, "package-3", "cli1.js")),
            "package-3 binary should be symlinked in package-4"
          );
          assert.equal(
            normalize(FileSystemUtilities.isSymlink(path.join(testDir, "packages", "package-4", "node_modules", ".bin", "package3cli2"))),
            normalize(path.join(testDir, "package-3", "cli2.js")),
            "package-3 binary should be symlinked in package-4"
          );

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    it("should not bootstrap ignored packages", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {
        ignore: "package-@(3|4)"
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const installed = [0,0,0];
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        installed[+options.cwd.match(/package-(\d)$/)[1]]++;
        callback();
      });

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    it("should bootstrap any dependencies not included by --scope when --include-filtered-dependencies is true", (done) => {
      // we scope to package-2 only but should still install package-1 as it is a dependency of package-2
      const bootstrapCommand = new BootstrapCommand([], {
        scope: "package-2",
        includeFilteredDependencies: true
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const installed = [0,0,0];
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        installed[+options.cwd.match(/package-(\d)$/)[1]]++;
        callback();
      });

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.deepEqual(installed, [0,1,1], "Did all our installs");

          // Make sure the `prepublish` script got run (index.js got created)
          assert.ok(pathExists.sync(path.join(testDir, "packages", "package-1", "index.js")));

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    it("should bootstrap any dependencies exluded by --ignore when --include-filtered-dependencies is true", (done) => {
      // we ignore package 1 but it should still be installed because it is a dependency of package-2
      const bootstrapCommand = new BootstrapCommand([], {
        ignore: "{@test/package-1,package-@(3|4)}",
        includeFilteredDependencies: true
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      const installed = [0,0,0];
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        installed[+options.cwd.match(/package-(\d)$/)[1]]++;
        callback();
      });

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.deepEqual(installed, [0,1,1], "Did all our installs");

          // Make sure the `prepublish` script got run (index.js got created), even though we --ignored package-1
          assert.ok(pathExists.sync(path.join(testDir, "packages", "package-1", "index.js")));

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("external dependencies that haven't been installed", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/cold").then((dir) => {
      testDir = dir;
    }));

    it("should get installed", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      assertStubbedCalls([
        [FileSystemUtilities, "writeFile", { nodeCallback: true }, [
          { args: [
            path.join(testDir, "packages", "package-1", "package.json"),
            JSON.stringify({ dependencies: { external: "^1.0.0" } }),
          ] },
        ]],
        [ChildProcessUtilities, "spawn", { nodeCallback: true }, [
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages", "package-1"), stdio: STDIO_OPT }] },
        ]],
        [FileSystemUtilities, "writeFile", { nodeCallback: true }, [
          { args: [
            path.join(testDir, "packages", "package-2", "package.json"),
            JSON.stringify({ dependencies: { external: "^2.0.0" } }),
          ] },
        ]],
        [ChildProcessUtilities, "spawn", { nodeCallback: true }, [
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages", "package-2"), stdio: STDIO_OPT }] },
        ]],
      ]);

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("external dependencies that have already been installed", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/warm").then((dir) => {
      testDir = dir;
    }));

    it("should not get re-installed", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      let installed = false;
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        installed = true;
        callback();
      });

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.ok(!installed, "The external dependency was not installed");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("packages with at least one external dependency to install", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/tepid").then((dir) => {
      testDir = dir;
    }));

    it("should install all dependencies", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        callback();
      });

      let got;
      stub(FileSystemUtilities, "writeFile", (fn, json, callback) => {
        got = JSON.parse(json);
        callback();
      });

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.deepEqual(got, {
            dependencies: {
              "external-1": "^1.0.0",
              "external-2": "^1.0.0",
            }
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("peer dependencies in packages in the repo", () => {
    let testDir;

    beforeEach(() => initFixture("BootstrapCommand/peer").then((dir) => {
      testDir = dir;
    }));

    it("should not bootstrap ignored peer dependencies", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {
        "ignore-peer-deps": true
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      let installed = false;
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        // This should never be reached
        assert.deepEqual(args, ["install", "external@^1.0.0"]);
        assert.deepEqual(options, { cwd: path.join(testDir, "packages/package-1"), stdio: STDIO_OPT });
        installed = true;
        callback();
      });

      bootstrapCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.ok(!pathExists.sync(path.join(testDir, "lerna-debug.log")), "lerna-debug.log should not exist");
          assert.ok(!pathExists.sync(path.join(testDir, "packages/package-1/node_modules/package-2")), "The linkable peer dependency should not be installed");
          assert.ok(!installed, "The external peer dependency should not be installed");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });
  });

  describe("zero packages", () => {
    beforeEach(() => initFixture("BootstrapCommand/zero-pkgs"));

    it("should succeed in repositories with zero packages", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      bootstrapCommand.runCommand(exitWithCode(0, done));
    });
  });

  describe("registries", () => {
    let testDir;

    const originalEnv = Object.assign({}, process.env);
    const mockEnv = {
      mock_value: 1,
      NODE_ENV: "lerna-test",
    };

    beforeEach(() => initFixture("BootstrapCommand/registries").then((dir) => {
      testDir = dir;

      // mock out the ENV to a simpler version for testing
      process.env = mockEnv;
    }));

    afterEach(() => {
      process.env = originalEnv;
    });

    it("should use config property", (done) => {
      const bootstrapCommand = new BootstrapCommand([], {});
      const env = Object.assign({}, mockEnv, {
        npm_config_registry: "https://my-secure-registry/npm",
      });

      bootstrapCommand.runValidations();
      bootstrapCommand.runPreparations();

      assertStubbedCalls([
        [ChildProcessUtilities, "spawn", { nodeCallback: true }, [
          { args: ["npm", ["install"], { cwd: path.join(testDir, "packages", "package-1"), stdio: STDIO_OPT, env }] }
        ]]
      ]);

      bootstrapCommand.runCommand(exitWithCode(0, done));
    });
  });
});


## Expected Behavior

## Current Behavior

## Possible Solution

## Steps to Reproduce (for bugs)

1.
2.
3.
4.

lerna.json

```json
<!-- Please paste your `lerna.json` here -->
```

lerna-debug.log

```txt
<!-- If you have a `lerna-debug.log` available, please paste it here -->
<!-- Otherwise, feel free to delete this <details> block -->
```

## Context

## Your Environment

| Executable | Version |
| ---: | :--- |
| `lerna --version` | VERSION |
| `npm --version`  | VERSION |
| `yarn --version` | VERSION |
| `node --version` | VERSION |

| OS | Version |
| --- | --- |
| NAME | VERSION |


keep keep keep keep replace keep keep keep


keep keep keep replace replace replace keep keep keep keep

import FileSystemUtilities from "../FileSystemUtilities";
import NpmUtilities from "../NpmUtilities";
import PackageUtilities from "../PackageUtilities";
import Command from "../Command";
import async from "async";
import find from "lodash/find";
import path from "path";
import semver from "semver";

export default class BootstrapCommand extends Command {
  initialize(callback) {
    this.npmConfig = {
      registry: this.npmRegistry,
      client: this.getOptions().npmClient,
    };
    callback(null, true);
  }

  execute(callback) {
    this.bootstrapPackages((err) => {
      if (err) {
        callback(err);
      } else {
        this.logger.success(`Successfully bootstrapped ${this.filteredPackages.length} packages.`);
        callback(null, true);
      }
    });
  }

  /**
   * Bootstrap packages
   * @param {Function} callback
   */
  bootstrapPackages(callback) {
    this.logger.info(`Bootstrapping ${this.filteredPackages.length} packages`);
    this.batchedPackages = this.toposort
      ? PackageUtilities.topologicallyBatchPackages(this.filteredPackages, {logger: this.logger})
      : [ this.filteredPackages ];

    async.series([
      // preinstall bootstrapped packages
      (cb) => this.preinstallPackages(cb),
      // install external dependencies
      (cb) => this.installExternalDependencies(cb),
      // symlink packages and their binaries
      (cb) => this.symlinkPackages(cb),
      // postinstall bootstrapped packages
      (cb) => this.postinstallPackages(cb),
      // prepublish bootstrapped packages
      (cb) => this.prepublishPackages(cb)
    ], callback);
  }

  runScriptInPackages(scriptName, callback) {
    if (!this.filteredPackages.length) {
      return callback(null, true);
    }

    this.progressBar.init(this.filteredPackages.length);
    PackageUtilities.runParallelBatches(this.batchedPackages, (pkg) => (done) => {
      pkg.runScript(scriptName, (err) => {
        this.progressBar.tick(pkg.name);
        done(err);
      });
    }, this.concurrency, (err) => {
      this.progressBar.terminate();
      callback(err);
    });
  }

  /**
   * Run the "preinstall" NPM script in all bootstrapped packages
   * @param callback
   */
  preinstallPackages(callback) {
    this.logger.info("Preinstalling packages");
    this.runScriptInPackages("preinstall", callback);
  }

  /**
   * Run the "postinstall" NPM script in all bootstrapped packages
   * @param callback
   */
  postinstallPackages(callback) {
    this.logger.info("Postinstalling packages");
    this.runScriptInPackages("postinstall", callback);
  }

  /**
   * Run the "prepublish" NPM script in all bootstrapped packages
   * @param callback
   */
  prepublishPackages(callback) {
    this.logger.info("Prepublishing packages");
    this.runScriptInPackages("prepublish", callback);
  }

  /**
   * Create a symlink to a dependency's binary in the node_modules/.bin folder
   * @param {String} src
   * @param {String} dest
   * @param {String} name
   * @param {String|Object} bin
   * @param {Function} callback
   */
  createBinaryLink(src, dest, name, bin, callback) {
    const destBinFolder = path.join(dest, ".bin");
    // The `bin` in a package.json may be either a string or an object.
    // Normalize to an object.
    const bins = typeof bin === "string"
      ? { [name]: bin }
      : bin;
    const srcBinFiles = [];
    const destBinFiles = [];
    Object.keys(bins).forEach((name) => {
      srcBinFiles.push(path.join(src, bins[name]));
      destBinFiles.push(path.join(destBinFolder, name));
    });
    // make sure when have a destination folder (node_modules/.bin)
    const actions = [(cb) => FileSystemUtilities.mkdirp(destBinFolder, cb)];
    // symlink each binary
    srcBinFiles.forEach((binFile, idx) => {
      actions.push((cb) => FileSystemUtilities.symlink(binFile, destBinFiles[idx], "exec", cb));
    });
    async.series(actions, callback);
  }

  hoistedDirectory(dependency) {
    return path.join(this.repository.rootPath, "node_modules", dependency);
  }

  hoistedPackageJson(dependency) {
    try {
      return require(path.join(this.hoistedDirectory(dependency), "package.json"));
    } catch (e) {
      // Pass.
    }
  }

  /**
   * Determine if a dependency installed at the root satifies the requirements of the passed packages
   * This helps to optimize the bootstrap process and skip dependencies that are already installed
   * @param {String} dependency
   * @param {Array.<String>} packages
   */
  dependencySatisfiesPackages(dependency, packages) {
    const {version} = (this.hoistedPackageJson(dependency) || {});
    return packages.every((pkg) => {
      return semver.satisfies(
        version,
        pkg.allDependencies[dependency]
      );
    });
  }

  /**
   * Given an array of packages, return map of dependencies to install
   * @param {Array.<Package>} packages An array of packages
   * @returns {Object}
   */
  getDependenciesToInstall(packages = []) {

    // find package by name
    const findPackage = (name, version) => find(this.packages, (pkg) => {
      return pkg.name === name && (!version || semver.satisfies(pkg.version, version));
    });

    const hasPackage = (name, version) => Boolean(findPackage(name, version));

    // Configuration for what packages to hoist may be in lerna.json or it may
    // come in as command line options.
    const {hoist: scope, nohoist: ignore} = this.getOptions();

    // This will contain entries for each hoistable dependency.
    const root = [];

    // This will map packages to lists of unhoistable dependencies
    const leaves = {};

    /**
     * Map of dependencies to install
     * {
     *   <name>: {
     *     versions: {
     *       <version>: <# of dependents>
     *     },
     *     dependents: {
     *       <version>: [<dependent1>, <dependent2>, ...]
     *     }
     *   }
     * }
     *
     * Example:
     *
     * {
     *   react: {
     *     versions: {
     *       "15.x": 3,
     *       "^0.14.0": 1
     *     },
     *     dependents: {
     *       "15.x": ["my-component1", "my-component2", "my-component3"],
     *       "^0.14.0": ["my-component4"],
     *     }
     *   }
     * }
     */
    const depsToInstall = {};

    Object.keys(this.repository.package.allDependencies).forEach((name) => {
      const version = this.repository.package.allDependencies[name];
      depsToInstall[name] = {
        versions   : {[version]: 0},
        dependents : {[version]: []},
      };
    });

    // get the map of external dependencies to install
    packages.forEach((pkg) => {

      // for all package dependencies
      Object.keys(pkg.allDependencies)

        // map to package or normalized external dependency
        .map((name) => findPackage(name, pkg.allDependencies[name]) || { name, version: pkg.allDependencies[name] })

        // match external and version mismatched local packages
        .filter((dep) => !hasPackage(dep.name, dep.version) || !pkg.hasMatchingDependency(dep, this.logger))

        .forEach(({name, version}) => {

          // Get the object for this package, auto-vivifying.
          const dep = depsToInstall[name] || (depsToInstall[name] = {
            versions   : {},
            dependents : {}
          });

          // Add this version if it's the first time we've seen it.
          if (!dep.versions[version]) {
            dep.versions  [version] = 0;
            dep.dependents[version] = [];
          }

          // Record the dependency on this version.
          dep.versions  [version]++;
          dep.dependents[version].push(pkg.name);
        });
    });

    // determine where each dependency will be installed
    Object.keys(depsToInstall).forEach((name) => {
      const {versions, dependents} = depsToInstall[name];

      let rootVersion;

      if (scope && PackageUtilities.getFilteredPackage({name}, {scope, ignore})) {

        // Get the most common version.
        const commonVersion = Object.keys(versions)
          .reduce((a, b) => versions[a] > versions[b] ? a : b);

        // Get the version required by the repo root (if any).
        // If the root doesn't have a dependency on this package then we'll
        // install the most common dependency there.
        rootVersion = this.repository.package.allDependencies[name] || commonVersion;

        if (rootVersion !== commonVersion) {
          this.logger.warn(
            `The repository root depends on ${name}@${rootVersion}, ` +
            `which differs from the more common ${name}@${commonVersion}.`
          );
        }

        // Install the best version we can in the repo root.
        // Even if it's already installed there we still need to make sure any
        // binaries are linked to the packages that depend on them.
        root.push({
          name,
          dependents: (dependents[rootVersion] || [])
            .map((dep) => this.packageGraph.get(dep).package),
          dependency: `${name}@${rootVersion}`,
          isSatisfied: this.repository.hasDependencyInstalled(name, rootVersion),
        });
      }

      // Add less common versions to package installs.
      Object.keys(versions).forEach((version) => {

        // Only install deps that can't be hoisted in the leaves.
        if (version === rootVersion) return;

        dependents[version].forEach((pkg) => {

          if (rootVersion) {
            this.logger.warn(
              `"${pkg}" package depends on ${name}@${version}, ` +
              `which differs from the hoisted ${name}@${rootVersion}.`
            );
          }

          // only install dependency if it's not already installed
          (leaves[pkg] || (leaves[pkg] = [])).push({
            dependency: `${name}@${version}`,
            isSatisfied: findPackage(pkg).hasDependencyInstalled(name),
          });
        });
      });
    });
    return { root, leaves };
  }

  /**
   * Install external dependencies for all packages
   * @param {Function} callback
   */
  installExternalDependencies(callback) {
    const {leaves, root} = this.getDependenciesToInstall(this.filteredPackages);
    const actions = [];

    // Start root install first, if any, since it's likely to take the longest.
    if (Object.keys(root).length) {

      // If we have anything to install in the root then we'll install
      // _everything_ that needs to go there.  This is important for
      // consistent behavior across npm clients.
      const depsToInstallInRoot = root.some(({isSatisfied}) => !isSatisfied)
        ? root.map(({dependency}) => dependency)
        : [];

      actions.push((cb) => NpmUtilities.installInDir(
        this.repository.rootPath,
        depsToInstallInRoot,
        this.npmConfig,
        (err) => {
          if (err) return cb(err);

          // Link binaries into dependent packages so npm scripts will have
          // access to them.
          async.series(root.map(({name, dependents}) => (cb) => {
            const {bin} = (this.hoistedPackageJson(name) || {});
            if (bin) {
              async.series(dependents.map((pkg) => (cb) => {
                const src  = this.hoistedDirectory(name);
                const dest = pkg.nodeModulesLocation;
                this.createBinaryLink(src, dest, name, bin, cb);
              }), cb);
            } else {
              cb();
            }
          }), (err) => {
            this.progressBar.tick("Install hoisted");
            cb(err);
          });
        }
      ));

      // Remove any hoisted dependencies that may have previously been
      // installed in package directories.
      actions.push((cb) => {
        async.series(root.map(({name, dependents}) => (cb) => {
          async.series(dependents.map(({nodeModulesLocation: dir}) => (cb) => {
            if (dir === this.repository.nodeModulesLocation) return cb();
            FileSystemUtilities.rimraf(path.join(dir, name), cb);
          }), cb);
        }), (err) => {
          this.progressBar.tick("Prune hoisted");
          cb(err);
        });
      });
    }

    // Install anything that needs to go into the leaves.
    Object.keys(leaves)
      .map((pkgName) => ({pkg: this.packageGraph.get(pkgName).package, deps: leaves[pkgName]}))
      .forEach(({pkg, deps}) => {

        // If we have any unsatisfied deps then we need to install everything.
        // This is important for consistent behavior across npm clients.
        if (deps.some(({isSatisfied}) => !isSatisfied)) {
          actions.push(
            (cb) => NpmUtilities.installInDir(
              pkg.location, deps.map(({dependency}) => dependency), this.npmConfig, (err) => {
                this.progressBar.tick(pkg.name);
                cb(err);
              }
            )
          );
        }
      });

    if (actions.length) {

      this.logger.info("Installing external dependencies");

      this.progressBar.init(actions.length);
    }

    async.parallelLimit(actions, this.concurrency, (err) => {
      this.progressBar.terminate();
      callback(err);
    });
  }

  /**
   * Symlink all packages to the packages/node_modules directory
   * Symlink package binaries to dependent packages' node_modules/.bin directory
   * @param {Function} callback
   */
  symlinkPackages(callback) {
    this.logger.info("Symlinking packages and binaries");
    this.progressBar.init(this.filteredPackages.length);
    const actions = [];
    this.filteredPackages.forEach((filteredPackage) => {
      // actions to run for this package
      const packageActions = [];
      Object.keys(filteredPackage.allDependencies)
        // filter out external dependencies and incompatible packages
        .filter((dependency) => {
          const match = this.packageGraph.get(dependency);
          return match && filteredPackage.hasMatchingDependency(match.package);
        })
        .forEach((dependency) => {
          // get Package of dependency
          const dependencyPackage = this.packageGraph.get(dependency).package;
          // get path to dependency and its scope
          const { location: dependencyLocation } = dependencyPackage;
          const dependencyPackageJsonLocation = path.join(dependencyLocation, "package.json");
          // ignore dependencies without a package.json file
          if (!FileSystemUtilities.existsSync(dependencyPackageJsonLocation)) {
            this.logger.error(
              `Unable to find package.json for ${dependency} dependency of ${filteredPackage.name},  ` +
              "Skipping..."
            );
          } else {
            // get the destination directory name of the dependency
            const pkgDependencyLocation = path.join(filteredPackage.nodeModulesLocation, dependencyPackage.name);
            // check if dependency is already installed
            if (FileSystemUtilities.existsSync(pkgDependencyLocation)) {
              const isDepSymlink = FileSystemUtilities.isSymlink(pkgDependencyLocation);
              // installed dependency is a symlink pointing to a different location
              if (isDepSymlink !== false && isDepSymlink !== dependencyLocation) {
                this.logger.warn(
                  `Symlink already exists for ${dependency} dependency of ${filteredPackage.name}, ` +
                  "but links to different location. Replacing with updated symlink..."
                );
              // installed dependency is not a symlink
              } else if (isDepSymlink === false) {
                this.logger.warn(
                  `${dependency} is already installed for ${filteredPackage.name}. ` +
                  "Replacing with symlink..."
                );
                // remove installed dependency
                packageActions.push((cb) => FileSystemUtilities.rimraf(pkgDependencyLocation, cb));
              }
            }
            // ensure destination path
            packageActions.push((cb) => FileSystemUtilities.mkdirp(
              pkgDependencyLocation.split(path.sep).slice(0, -1).join(path.sep), cb
            ));
            // create package symlink
            packageActions.push((cb) => FileSystemUtilities.symlink(
              dependencyLocation, pkgDependencyLocation, "junction", cb
            ));
            const dependencyPackageJson = require(dependencyPackageJsonLocation);
            if (dependencyPackageJson.bin) {
              const destFolder = filteredPackage.nodeModulesLocation;
              packageActions.push((cb) => {
                this.createBinaryLink(dependencyLocation, destFolder, dependency, dependencyPackageJson.bin, cb);
              });
            }
          }
        });
      actions.push((cb) => {
        async.series(packageActions, (err) => {
          this.progressBar.tick(filteredPackage.name);
          cb(err);
        });
      });
    });
    async.series(actions, (err) => {
      this.progressBar.terminate();
      callback(err);
    });
  }
}


import ProgressBar from "progress";
import padEnd from "lodash/padEnd";

class ProgressBarController {
  constructor() {
    this.bar = null;
  }

  init(total) {
    if (this.bar) {
      this.terminate();
    }

    // Intentionally a noop because node-progress doesn't work well in non-TTY
    // environments
    if (!process.stdout.isTTY) {
      return;
    }

    // Don't do any of this while testing
    if (process.env.NODE_ENV === "lerna-test") {
      return;
    }

    this.bar = new ProgressBar(":packagename ╢:bar╟", {
      total: total,
      complete: "█",
      incomplete: "░",
      clear: true,

      // terminal columns - package name length - additional characters length
      width: (process.stdout.columns || 100) - 50 - 3
    });
  }

  tick(name) {
    if (this.bar) {
      this.bar.tick({
        packagename: padEnd(name.slice(0, 50), 50)
      });
    }
  }

  clear() {
    if (this.bar) {
      this.bar.terminate();
    }
  }

  restore() {
    if (this.bar) {
      // This is a hack to get the bar to redraw it's last state.
      // See: https://github.com/tj/node-progress/blob/d47913502ba5b551fcaad9e94fe7b2f5876a7939/lib/node-progress.js#L154-L159
      this.bar.stream.write(this.bar.lastDraw);
    }
  }

  terminate() {
    this.clear();
    this.bar = null;
  }
}

export default new ProgressBarController();


"package-1 should be symlinked to package-3"

normalize(resolveSymlink(path.join(testDir, "packages", "package-3", "node_modules", "package-2"))),

"package-2 should be symlinked to package-3"

normalize(resolveSymlink(path.join(testDir, "packages", "package-3", "node_modules", "@test", "package-2"))),


assert.throws(() => fs.readlinkSync(path.join(testDir, "packages", "package-3", "node_modules", "package-4")));

keep keep keep replace keep keep keep keep


appveyor.yml
.babelrc
CHANGELOG.md
CODE_OF_CONDUCT.md
.editorconfig
.eslintrc
.gitignore
lerna.json
.travis.yml
yarn.lock
src
test
tmp
.npmignore
.nyc_output
coverage


import assert from "assert";
import path from "path";

import ChildProcessUtilities from "../src/ChildProcessUtilities";
import exitWithCode from "./helpers/exitWithCode";
import initFixture from "./helpers/initFixture";
import ExecCommand from "../src/commands/ExecCommand";
import stub from "./helpers/stub";

describe("ExecCommand", () => {
  describe("in a basic repo", () => {
    let testDir;

    beforeEach(() => initFixture("ExecCommand/basic").then((dir) => {
      testDir = dir;
    }));

    it("should complain if invoked without command", (done) => {
      const execCommand = new ExecCommand([], {});

      execCommand.runValidations();
      execCommand.runPreparations();

      execCommand.runCommand(exitWithCode(1, (err) => {
        assert.ok(err instanceof Error);
        done();
      }));
    });

    it("should filter packages with `ignore`", (done) => {
      const execCommand = new ExecCommand(["ls"], { ignore: "package-1" });

      execCommand.runValidations();
      execCommand.runPreparations();

      execCommand.initialize(function(error) {
        assert.equal(error, null);
      });

      assert.equal(execCommand.filteredPackages.length, 1);
      assert.equal(execCommand.filteredPackages[0].name, "package-2");

      done();
    });

    it("should run a command", (done) => {
      const execCommand = new ExecCommand(["ls"], {});

      execCommand.runValidations();
      execCommand.runPreparations();

      let calls = 0;
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        assert.equal(command, "ls");

        if (calls === 0) assert.deepEqual(options, { cwd: path.join(testDir, "packages/package-1"), env: Object.assign({}, process.env, { LERNA_PACKAGE_NAME: "package-1" }) });
        if (calls === 1) assert.deepEqual(options, { cwd: path.join(testDir, "packages/package-2"), env: Object.assign({}, process.env, { LERNA_PACKAGE_NAME: "package-2" }) });

        calls++;
        callback();
      });

      execCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.equal(calls, 2);
          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    it("should run a command with parameters", (done) => {
      const execCommand = new ExecCommand(["ls", "-la"], {});

      execCommand.runValidations();
      execCommand.runPreparations();

      let calls = 0;
      stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
        assert.equal(command, "ls");

        if (calls === 0) {
          assert.deepEqual(options, { cwd: path.join(testDir, "packages/package-1"), env: Object.assign({}, process.env, { LERNA_PACKAGE_NAME: "package-1" }) });
          assert.deepEqual(args, ["-la"]);
        }
        if (calls === 1) {
          assert.deepEqual(options, { cwd: path.join(testDir, "packages/package-2"), env: Object.assign({}, process.env, { LERNA_PACKAGE_NAME: "package-2" }) });
          assert.deepEqual(args, ["-la"]);
        }

        calls++;
        callback();
      });

      execCommand.runCommand(exitWithCode(0, (err) => {
        if (err) return done.fail(err);

        try {
          assert.equal(calls, 2);
          done();
        } catch (ex) {
          done.fail(ex);
        }
      }));
    });

    // Both of these commands should result in the same outcome
    const filters = [
      { test: "should run a command for a given scope", flag: "scope", flagValue: "package-1" },
      { test: "should not run a command for ignored packages", flag: "ignore", flagValue: "package-@(2|3|4)" },
    ];
    filters.forEach((filter) => {
      it(filter.test, (done) => {
        const execCommand = new ExecCommand(["ls"], {[filter.flag]: filter.flagValue});

        execCommand.runValidations();
        execCommand.runPreparations();

        const ranInPackages = [];
        stub(ChildProcessUtilities, "spawn", (command, args, options, callback) => {
          ranInPackages.push(options.cwd.substr(path.join(testDir, "packages/").length));
          callback();
        });

        execCommand.runCommand(exitWithCode(0, (err) => {
          if (err) return done.fail(err);

          try {
            assert.deepEqual(ranInPackages, ["package-1"]);
            done();
          } catch (ex) {
            done.fail(ex);
          }
        }));
      });
    });
  });
});


import fs from "graceful-fs";
import path from "path";

import {
  mkdirpAsync,
  rimrafAsync,
} from "./helpers/fixtureUtils";
import initFixture from "./helpers/initFixture";

import InitCommand from "../src/commands/InitCommand";

describe("InitCommand", () => {
  describe("in an empty directory", () => {
    let testDir;

    beforeEach(() => initFixture("InitCommand/empty").then((dir) => {
      testDir = dir;

      // ensure fixture directory is _completely_ empty
      return Promise.all([".git", "DELETE_ME"].map((fp) =>
        rimrafAsync(path.join(dir, fp))
      ));
    }));

    it("completely ignores validation and preparation lifecycle", () => {
      const instance = new InitCommand([], {});
      expect(() => instance.runValidations()).not.toThrow();
      expect(() => instance.runPreparations()).not.toThrow();
    });

    it("initializes git repo with lerna files", (done) => {
      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          expect(fs.readdirSync(testDir)).toEqual([
            ".git",
            "lerna.json",
            "package.json",
          ]);

          const lernaJson = require(path.join(testDir, "lerna.json"));
          expect(lernaJson).toEqual({
            lerna: instance.lernaVersion,
            packages: ["packages/*"],
            version: "0.0.0",
          });

          const packageJson = require(path.join(testDir, "package.json"));
          expect(packageJson).toEqual({
            devDependencies: {
              lerna: `^${instance.lernaVersion}`,
            },
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });

    it("initializes git repo with lerna files in independent mode", (done) => {
      const instance = new InitCommand([], {
        independent: true,
      });

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          expect(fs.readdirSync(testDir)).toEqual([
            ".git",
            "lerna.json",
            "package.json",
          ]);

          const lernaJson = require(path.join(testDir, "lerna.json"));
          expect(lernaJson.version).toBe("independent");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });

    describe("with --exact", () => {
      it("uses exact version when adding lerna dependency", (done) => {
        const instance = new InitCommand([], {
          exact: true,
        });

        instance.runCommand((err, code) => {
          if (err) return done.fail(err);

          try {
            expect(code).toBe(0);

            const packageJson = require(path.join(testDir, "package.json"));
            expect(packageJson).toEqual({
              devDependencies: {
                lerna: instance.lernaVersion,
              },
            });

            done();
          } catch (ex) {
            done.fail(ex);
          }
        });
      });

      it("sets lerna.json command.init.exact to true", (done) => {
        const instance = new InitCommand([], {
          exact: true,
        });

        instance.runCommand((err, code) => {
          if (err) return done.fail(err);

          try {
            expect(code).toBe(0);

            const lernaJson = require(path.join(testDir, "lerna.json"));
            expect(lernaJson).toHaveProperty("command.init.exact", true);

            done();
          } catch (ex) {
            done.fail(ex);
          }
        });
      });
    });
  });

  describe("in a subdirectory of a git repo", () => {
    let testDir;

    beforeEach(() => initFixture("InitCommand/empty").then((dir) => {
      const subDir = path.join(dir, "subdir");

      return mkdirpAsync(subDir).then(() => {
        process.chdir(subDir);
        testDir = subDir;
      });
    }));

    it("creates lerna files", (done) => {
      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          expect(fs.readdirSync(testDir)).toEqual([
            "lerna.json",
            "package.json",
          ]);

          const lernaJson = require(path.join(testDir, "lerna.json"));
          expect(lernaJson).toEqual({
            lerna: instance.lernaVersion,
            packages: ["packages/*"],
            version: "0.0.0",
          });

          const packageJson = require(path.join(testDir, "package.json"));
          expect(packageJson).toEqual({
            devDependencies: {
              lerna: `^${instance.lernaVersion}`,
            },
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });
  });

  describe("when package.json exists", () => {
    let packageJsonLocation;

    beforeEach(() => initFixture("InitCommand/has-package").then((dir) => {
      packageJsonLocation = path.join(dir, "package.json");
    }));

    it("adds lerna to sorted devDependencies", (done) => {
      fs.writeFileSync(packageJsonLocation, JSON.stringify({
        name: "repo-root",
        devDependencies: {
          alpha: "first",
          omega: "last",
        },
      }));

      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          const packageJson = require(packageJsonLocation);
          expect(packageJson).toEqual({
            name: "repo-root",
            devDependencies: {
              alpha: "first",
              lerna: `^${instance.lernaVersion}`,
              omega: "last",
            },
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });

    it("updates existing lerna in devDependencies", (done) => {
      fs.writeFileSync(packageJsonLocation, JSON.stringify({
        name: "repo-root",
        dependencies: {
          alpha: "first",
          omega: "last",
        },
        devDependencies: {
          lerna: "0.1.100",
        },
      }));

      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          const packageJson = require(packageJsonLocation);
          expect(packageJson).toEqual({
            name: "repo-root",
            dependencies: {
              alpha: "first",
              omega: "last",
            },
            devDependencies: {
              lerna: `^${instance.lernaVersion}`,
            },
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });

    it("updates existing lerna in sorted dependencies", (done) => {
      fs.writeFileSync(packageJsonLocation, JSON.stringify({
        name: "repo-root",
        dependencies: {
          alpha: "first",
          lerna: "0.1.100",
          omega: "last",
        },
      }));

      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          const packageJson = require(packageJsonLocation);
          expect(packageJson).toEqual({
            name: "repo-root",
            dependencies: {
              alpha: "first",
              lerna: `^${instance.lernaVersion}`,
              omega: "last",
            },
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });
  });

  describe("when lerna.json exists", () => {
    let lernaJsonLocation;

    beforeEach(() => initFixture("InitCommand/has-lerna").then((dir) => {
      lernaJsonLocation = path.join(dir, "lerna.json");
    }));

    it("updates lerna property to current version", (done) => {
      fs.writeFileSync(lernaJsonLocation, JSON.stringify({
        lerna: "0.1.100",
        packages: ["foo/*"],
        version: "1.2.3",
        hoist: true,
      }));

      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          const lernaJson = require(lernaJsonLocation);
          expect(lernaJson).toEqual({
            lerna: instance.lernaVersion,
            packages: ["foo/*"],
            version: "1.2.3",
            hoist: true,
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });

    it("updates lerna property to current version in independent mode", (done) => {
      fs.writeFileSync(lernaJsonLocation, JSON.stringify({
        lerna: "0.1.100",
        packages: ["bar/*"],
        version: "independent",
        hoist: true,
      }));

      const instance = new InitCommand([], {
        independent: true,
      });

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          const lernaJson = require(lernaJsonLocation);
          expect(lernaJson).toEqual({
            lerna: instance.lernaVersion,
            packages: ["bar/*"],
            version: "independent",
            hoist: true,
          });

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });
  });

  describe("when VERSION exists", () => {
    let testDir;

    beforeEach(() => initFixture("InitCommand/has-version").then((dir) => {
      testDir = dir;
    }));

    it("removes file", (done) => {
      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          expect(fs.readdirSync(testDir)).toEqual([
            ".git",
            "lerna.json",
            "package.json",
          ]);

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });

    it("logs deprecation", (done) => {
      const instance = new InitCommand([], {});
      instance.logger = {
        verbose: jest.fn(),
        info: jest.fn(),
        success: jest.fn(),
        warn: jest.fn(),
        error: jest.fn(),
      };

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          expect(instance.logger.info).toBeCalledWith("Removing old VERSION file.");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });

    it("uses value for lerna.json version property", (done) => {
      const instance = new InitCommand([], {});

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          const lernaJson = require(path.join(testDir, "lerna.json"));
          expect(lernaJson).toHaveProperty("version", "1.2.3");

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });
  });

  describe("when re-initializing with --exact", () => {
    let testDir;

    beforeEach(() => initFixture("InitCommand/updates").then((dir) => {
      testDir = dir;
    }));

    it("sets lerna.json commands.init.exact to true", (done) => {
      const instance = new InitCommand([], {
        exact: true,
      });

      instance.runCommand((err, code) => {
        if (err) return done.fail(err);

        try {
          expect(code).toBe(0);

          const lernaJson = require(path.join(testDir, "lerna.json"));
          expect(lernaJson).toHaveProperty("commands.init.exact", true);

          done();
        } catch (ex) {
          done.fail(ex);
        }
      });
    });
  });
});


import assert from "assert";
import os from "os";

import ChildProcessUtilities from "../src/ChildProcessUtilities";

describe("ChildProcessUtilities", () => {
  describe(".execSync()", () => {
    it("should execute a command in a child process and return the result", () => {
      assert.equal(ChildProcessUtilities.execSync("echo foo"), "foo");
    });

    it("does not error when stdout is ignored", () => {
      expect(() => ChildProcessUtilities.execSync("echo foo", { stdio: "ignore" })).not.toThrow();
    });
  });

  describe(".exec()", () => {
    it("should execute a command in a child process and call the callback with the result", (done) => {
      ChildProcessUtilities.exec("echo foo", null, (stderr, stdout) => {
        assert.equal(stderr, null);
        assert.equal(stdout, `foo${os.EOL}`);
        done();
      });
    });

    it("passes an error object to callback when stdout maxBuffer exceeded", (done) => {
      ChildProcessUtilities.exec("echo foo", { maxBuffer: 1 }, (stderr, stdout) => {
        assert.equal(stderr, "Error: stdout maxBuffer exceeded");
        assert.equal(stdout, "");
        done();
      });
    });

    if (process.platform !== "win32") {
      // windows is too weird
      it("passes an error object to callback when stdout maxBuffer exceeded", (done) => {
        ChildProcessUtilities.exec("echo foo >&2", { maxBuffer: 1 }, (stderr, stdout) => {
          assert.equal(stderr, "Error: stderr maxBuffer exceeded.  Partial output follows:\n\n");
          assert.equal(stdout, "");
          done();
        });
      });
    }
  });

  describe(".spawn()", () => {
    it("should spawn a command in a child process", (done) => {
      ChildProcessUtilities.spawn("echo", ["foo"], { stdio: "pipe" }, (err, output) => {
        assert.equal(err, null);
        assert.equal(output.trim(), "foo");
        done();
      });
    });

    it("passes error message to callback", (done) => {
      ChildProcessUtilities.spawn("iAmTheModelOfAModernMajorGeneral", [], { stdio: "pipe" }, (err) => {
        const inErrorMessage = String(err).indexOf("iAmTheModelOfAModernMajorGeneral") > -1;
        assert.ok(inErrorMessage, "did not log missing executable error");
        done();
      });
    });
  });
});