"use strict";

jest.mock("@octokit/rest");
jest.mock("@lerna/child-process");

const Octokit = require("@octokit/rest");
const childProcess = require("@lerna/child-process");
const { createGitHubClient, parseGitRepo } = require("../lib/github-client");

childProcess.execSync.mockReturnValue("5.6.0");

describe("createGitHubClient", () => {
  const oldEnv = Object.assign({}, process.env);

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

  it("errors if no GH_TOKEN env var", () => {
    expect(() => {
      createGitHubClient();
    }).toThrow("A GH_TOKEN environment variable is required.");
  });

  it("doesnt error if GH_TOKEN env var is set", () => {
    process.env.GH_TOKEN = "TOKEN";

    expect(() => {
      createGitHubClient();
    }).not.toThrow();
  });

  it("initializes GHE plugin when GHE_VERSION env var is set", () => {
    process.env.GH_TOKEN = "TOKEN";
    process.env.GHE_VERSION = "2.14";

    createGitHubClient();

    expect(Octokit.plugin).toHaveBeenCalledWith(expect.anything());
  });

  it("sets octokit `baseUrl` when GHE_API_URL is set", () => {
    process.env.GH_TOKEN = "TOKEN";
    process.env.GHE_API_URL = "http://some/host";

    createGitHubClient();

    expect(Octokit).toHaveBeenCalledWith({
      auth: "token TOKEN",
      baseUrl: "http://some/host",
    });
  });
});

describe("parseGitRepo", () => {
  it("returns a parsed URL", () => {
    childProcess.execSync.mockReturnValue("git@github.com:org/lerna.git");

    const repo = parseGitRepo();

    expect(childProcess.execSync).toHaveBeenCalledWith(
      "git",
      ["config", "--get", "remote.origin.url"],
      undefined
    );

    expect(repo).toEqual(
      expect.objectContaining({
        name: "lerna",
        owner: "org",
      })
    );
  });

  it("can change the origin", () => {
    childProcess.execSync.mockReturnValue("git@github.com:org/lerna.git");

    parseGitRepo("upstream");

    expect(childProcess.execSync).toHaveBeenCalledWith(
      "git",
      ["config", "--get", "remote.upstream.url"],
      undefined
    );
  });

  it("throws an error if no URL returned", () => {
    childProcess.execSync.mockReturnValue("");

    expect(() => parseGitRepo()).toThrow('Git remote URL could not be found using "origin".');
  });
});


? pkg => this.runScriptInPackageStreaming(pkg)

: pkg => this.runScriptInPackageCapturing(pkg);

keep keep keep replace replace keep keep

return runTopologically(this.packagesWithScript, runner, {


return pMap(this.packagesWithScript, pkg => this.runScriptInPackageStreaming(pkg));

keep keep replace keep keep keep keep keep

"use strict";

const pMap = require("p-map");

const ChildProcessUtilities = require("@lerna/child-process");
const Command = require("@lerna/command");
const runTopologically = require("@lerna/run-topologically");
const ValidationError = require("@lerna/validation-error");
const { getFilteredPackages } = require("@lerna/filter-options");

module.exports = factory;

function factory(argv) {
  return new ExecCommand(argv);
}

class ExecCommand extends Command {
  get requiresGit() {
    return false;
  }

  initialize() {
    const dashedArgs = this.options["--"] || [];

    this.command = this.options.cmd || dashedArgs.shift();
    this.args = (this.options.args || []).concat(dashedArgs);

    if (!this.command) {
      throw new ValidationError("ENOCOMMAND", "A command to execute is required");
    }

    // inverted boolean options
    this.bail = this.options.bail !== false;
    this.prefix = this.options.prefix !== false;

    // accessing properties of process.env can be expensive,
    // so cache it here to reduce churn during tighter loops
    this.env = Object.assign({}, process.env);

    let chain = Promise.resolve();

    chain = chain.then(() => getFilteredPackages(this.packageGraph, this.execOpts, this.options));
    chain = chain.then(filteredPackages => {
      this.filteredPackages = filteredPackages;
    });

    return chain.then(() => {
      this.count = this.filteredPackages.length;
      this.packagePlural = this.count === 1 ? "package" : "packages";
      this.joinedCommand = [this.command].concat(this.args).join(" ");
    });
  }

  execute() {
    this.logger.info(
      "",
      "Executing command in %d %s: %j",
      this.count,
      this.packagePlural,
      this.joinedCommand
    );

    let chain = Promise.resolve();

    if (this.options.parallel) {
      chain = chain.then(() => this.runCommandInPackagesParallel());
    } else if (this.toposort) {
      chain = chain.then(() => this.runCommandInPackagesTopological());
    } else {
      chain = chain.then(() => this.runCommandInPackagesLexical());
    }

    if (this.bail) {
      // only the first error is caught
      chain = chain.catch(err => {
        process.exitCode = err.code;

        // rethrow to halt chain and log properly
        throw err;
      });
    } else {
      // detect error (if any) from collected results
      chain = chain.then(results => {
        /* istanbul ignore else */
        if (results.some(result => result.failed)) {
          // propagate "highest" error code, it's probably the most useful
          const codes = results.filter(result => result.failed).map(result => result.code);
          const exitCode = Math.max(...codes, 1);

          this.logger.error("", "Received non-zero exit code %d during execution", exitCode);
          process.exitCode = exitCode;
        }
      });
    }

    return chain.then(() => {
      this.logger.success(
        "exec",
        "Executed command in %d %s: %j",
        this.count,
        this.packagePlural,
        this.joinedCommand
      );
    });
  }

  getOpts(pkg) {
    // these options are passed _directly_ to execa
    return {
      cwd: pkg.location,
      shell: true,
      extendEnv: false,
      env: Object.assign({}, this.env, {
        LERNA_PACKAGE_NAME: pkg.name,
        LERNA_ROOT_PATH: this.project.rootPath,
      }),
      reject: this.bail,
      pkg,
    };
  }

  runCommandInPackagesTopological() {
    const runner = this.options.stream
      ? pkg => this.runCommandInPackageStreaming(pkg)
      : pkg => this.runCommandInPackageCapturing(pkg);

    return runTopologically({
      packages: this.filteredPackages,
      concurrency: this.concurrency,
      rejectCycles: this.options.rejectCycles,
      runner,
    });
  }

  runCommandInPackagesParallel() {
    return pMap(this.filteredPackages, pkg => this.runCommandInPackageStreaming(pkg));
  }

  runCommandInPackagesLexical() {
    const runner = this.options.stream
      ? pkg => this.runCommandInPackageStreaming(pkg)
      : pkg => this.runCommandInPackageCapturing(pkg);

    return pMap(this.filteredPackages, runner, { concurrency: this.concurrency });
  }

  runCommandInPackageStreaming(pkg) {
    return ChildProcessUtilities.spawnStreaming(
      this.command,
      this.args,
      this.getOpts(pkg),
      this.prefix && pkg.name
    );
  }

  runCommandInPackageCapturing(pkg) {
    return ChildProcessUtilities.spawn(this.command, this.args, this.getOpts(pkg));
  }
}

module.exports.ExecCommand = ExecCommand;


@lerna/add

Add a dependency to matched packages


package-cycle-extraneous

This package is used to break ties between package-cycle-{1,2}.


"use strict";

module.exports = callsBack;

// for mocking the behaviour of methods that accept a callback
function callsBack(err, val) {
  return (...args) => process.nextTick(args.pop(), err, val);
}


"use strict";

const PQueue = require("p-queue");
const figgyPudding = require("figgy-pudding");
const QueryGraph = require("@lerna/query-graph");

module.exports = runTopologically;

const TopologicalConfig = figgyPudding({
  packages: {},
  concurrency: {},
  "reject-cycles": {},
  rejectCycles: "reject-cycles",
  runner: {},
});

function runTopologically(_opts) {
  const opts = TopologicalConfig(_opts);
  const { packages, concurrency, rejectCycles, runner } = opts;

  const queue = new PQueue({ concurrency });
  const graph = new QueryGraph(packages, { rejectCycles });

  return new Promise((resolve, reject) => {
    const returnValues = [];

    const queueNextAvailablePackages = () =>
      graph.getAvailablePackages().forEach(({ pkg, name }) => {
        graph.markAsTaken(name);

        queue
          .add(() =>
            runner(pkg)
              .then(value => returnValues.push(value))
              .then(() => graph.markAsDone(pkg))
              .then(() => queueNextAvailablePackages())
          )
          .catch(reject);
      });

    queueNextAvailablePackages();

    return queue.onIdle().then(() => resolve(returnValues));
  });
}


"use strict";

const os = require("os");
const chalk = require("chalk");
const dedent = require("dedent");
const minimatch = require("minimatch");
const pMap = require("p-map");
const pPipe = require("p-pipe");
const pReduce = require("p-reduce");
const pWaterfall = require("p-waterfall");
const semver = require("semver");

const Command = require("@lerna/command");
const ConventionalCommitUtilities = require("@lerna/conventional-commits");
const checkWorkingTree = require("@lerna/check-working-tree");
const PromptUtilities = require("@lerna/prompt");
const output = require("@lerna/output");
const collectUpdates = require("@lerna/collect-updates");
const { createRunner } = require("@lerna/run-lifecycle");
const runTopologically = require("@lerna/run-topologically");
const ValidationError = require("@lerna/validation-error");
const { createGitHubClient, parseGitRepo } = require("@lerna/github-client");
const prereleaseIdFromVersion = require("@lerna/prerelease-id-from-version");

const getCurrentBranch = require("./lib/get-current-branch");
const gitAdd = require("./lib/git-add");
const gitCommit = require("./lib/git-commit");
const gitPush = require("./lib/git-push");
const gitTag = require("./lib/git-tag");
const isBehindUpstream = require("./lib/is-behind-upstream");
const remoteBranchExists = require("./lib/remote-branch-exists");
const isBreakingChange = require("./lib/is-breaking-change");
const isAnythingCommitted = require("./lib/is-anything-committed");
const makePromptVersion = require("./lib/prompt-version");

const { collectPackages, getPackagesForOption } = collectUpdates;

module.exports = factory;

function factory(argv) {
  return new VersionCommand(argv);
}

class VersionCommand extends Command {
  get otherCommandConfigs() {
    // back-compat
    return ["publish"];
  }

  get requiresGit() {
    return (
      this.commitAndTag || this.pushToRemote || this.options.allowBranch || this.options.conventionalCommits
    );
  }

  configureProperties() {
    super.configureProperties();

    // Defaults are necessary here because yargs defaults
    // override durable options provided by a config file
    const {
      amend,
      commitHooks = true,
      gitRemote = "origin",
      gitTagVersion = true,
      push = true,
      signGitCommit,
      signGitTag,
      tagVersionPrefix = "v",
    } = this.options;

    this.gitRemote = gitRemote;
    this.tagPrefix = tagVersionPrefix;
    this.commitAndTag = gitTagVersion;
    this.pushToRemote = gitTagVersion && amend !== true && push;
    // never automatically push to remote when amending a commit

    this.createReleases = this.pushToRemote && this.options.githubRelease;
    this.releaseNotes = [];

    if (this.createReleases && this.options.conventionalCommits !== true) {
      throw new ValidationError(
        "ERELEASE",
        "To create a Github Release, you must enable --conventional-commits"
      );
    }

    if (this.createReleases && this.options.changelog === false) {
      throw new ValidationError("ERELEASE", "To create a Github Release, you cannot pass --no-changelog");
    }

    this.gitOpts = {
      amend,
      commitHooks,
      signGitCommit,
      signGitTag,
    };

    // https://docs.npmjs.com/misc/config#save-prefix
    this.savePrefix = this.options.exact ? "" : "^";
  }

  initialize() {
    if (!this.project.isIndependent()) {
      this.logger.info("current version", this.project.version);
    }

    if (this.requiresGit) {
      // git validation, if enabled, should happen before updates are calculated and versions picked
      if (!isAnythingCommitted(this.execOpts)) {
        throw new ValidationError(
          "ENOCOMMIT",
          "No commits in this repository. Please commit something before using version."
        );
      }

      this.currentBranch = getCurrentBranch(this.execOpts);

      if (this.currentBranch === "HEAD") {
        throw new ValidationError(
          "ENOGIT",
          "Detached git HEAD, please checkout a branch to choose versions."
        );
      }

      if (this.pushToRemote && !remoteBranchExists(this.gitRemote, this.currentBranch, this.execOpts)) {
        throw new ValidationError(
          "ENOREMOTEBRANCH",
          dedent`
            Branch '${this.currentBranch}' doesn't exist in remote '${this.gitRemote}'.
            If this is a new branch, please make sure you push it to the remote first.
          `
        );
      }

      if (
        this.options.allowBranch &&
        ![].concat(this.options.allowBranch).some(x => minimatch(this.currentBranch, x))
      ) {
        throw new ValidationError(
          "ENOTALLOWED",
          dedent`
            Branch '${this.currentBranch}' is restricted from versioning due to allowBranch config.
            Please consider the reasons for this restriction before overriding the option.
          `
        );
      }

      if (
        this.commitAndTag &&
        this.pushToRemote &&
        isBehindUpstream(this.gitRemote, this.currentBranch, this.execOpts)
      ) {
        const message = `Local branch '${this.currentBranch}' is behind remote upstream ${this.gitRemote}/${
          this.currentBranch
        }`;

        if (!this.options.ci) {
          // interrupt interactive execution
          throw new ValidationError(
            "EBEHIND",
            dedent`
              ${message}
              Please merge remote changes into '${this.currentBranch}' with 'git pull'
            `
          );
        }

        // CI execution should not error, but warn & exit
        this.logger.warn("EBEHIND", `${message}, exiting`);

        // still exits zero, aka "ok"
        return false;
      }
    } else {
      this.logger.notice(
        "FYI",
        "git repository validation has been skipped, please ensure your version bumps are correct"
      );
    }

    if (this.options.conventionalPrerelease && this.options.conventionalGraduate) {
      throw new ValidationError(
        "ENOTALLOWED",
        dedent`
          --conventional-prerelease cannot be combined with --conventional-graduate.
        `
      );
    }

    this.updates = collectUpdates(
      this.packageGraph.rawPackageList,
      this.packageGraph,
      this.execOpts,
      this.options
    ).filter(node => {
      if (!node.version) {
        // a package may be unversioned only if it is private
        if (node.pkg.private) {
          this.logger.info("version", "Skipping unversioned private package %j", node.name);
        } else {
          throw new ValidationError(
            "ENOVERSION",
            dedent`
              A version field is required in ${node.name}'s package.json file.
              If you wish to keep the package unversioned, it must be made private.
            `
          );
        }
      }

      return !!node.version;
    });

    if (!this.updates.length) {
      this.logger.success(`No changed packages to ${this.composed ? "publish" : "version"}`);

      // still exits zero, aka "ok"
      return false;
    }

    // a "rooted leaf" is the regrettable pattern of adding "." to the "packages" config in lerna.json
    this.hasRootedLeaf = this.packageGraph.has(this.project.manifest.name);

    if (this.hasRootedLeaf && !this.composed) {
      this.logger.info("version", "rooted leaf detected, skipping synthetic root lifecycles");
    }

    this.runPackageLifecycle = createRunner(this.options);

    // don't execute recursively if run from a poorly-named script
    this.runRootLifecycle = /^(pre|post)?version$/.test(process.env.npm_lifecycle_event)
      ? stage => {
          this.logger.warn("lifecycle", "Skipping root %j because it has already been called", stage);
        }
      : stage => this.runPackageLifecycle(this.project.manifest, stage);

    const tasks = [
      () => this.getVersionsForUpdates(),
      versions => this.setUpdatesForVersions(versions),
      () => this.confirmVersions(),
    ];

    // amending a commit probably means the working tree is dirty
    if (this.commitAndTag && this.gitOpts.amend !== true) {
      const { forcePublish, conventionalCommits, conventionalGraduate } = this.options;
      const checkUncommittedOnly = forcePublish || (conventionalCommits && conventionalGraduate);
      const check = checkUncommittedOnly ? checkWorkingTree.throwIfUncommitted : checkWorkingTree;
      tasks.unshift(() => check(this.execOpts));
    } else {
      this.logger.warn("version", "Skipping working tree validation, proceed at your own risk");
    }

    return pWaterfall(tasks);
  }

  execute() {
    const tasks = [() => this.updatePackageVersions()];

    if (this.commitAndTag) {
      tasks.push(() => this.commitAndTagUpdates());
    } else {
      this.logger.info("execute", "Skipping git tag/commit");
    }

    if (this.pushToRemote) {
      tasks.push(() => this.gitPushToRemote());
    } else {
      this.logger.info("execute", "Skipping git push");
    }

    if (this.createReleases) {
      tasks.push(() => this.createGitHubReleases());
    } else {
      this.logger.info("execute", "Skipping GitHub releases");
    }

    return pWaterfall(tasks).then(() => {
      if (!this.composed) {
        this.logger.success("version", "finished");
      }

      return {
        updates: this.updates,
        updatesVersions: this.updatesVersions,
      };
    });
  }

  getVersionsForUpdates() {
    const independentVersions = this.project.isIndependent();
    const { bump, conventionalCommits, preid } = this.options;
    const repoVersion = bump ? semver.clean(bump) : "";
    const increment = bump && !semver.valid(bump) ? bump : "";

    const resolvePrereleaseId = existingPreid => preid || existingPreid || "alpha";

    const makeGlobalVersionPredicate = nextVersion => {
      this.globalVersion = nextVersion;

      return () => nextVersion;
    };

    // decide the predicate in the conditionals below
    let predicate;

    if (repoVersion) {
      predicate = makeGlobalVersionPredicate(repoVersion);
    } else if (increment && independentVersions) {
      // compute potential prerelease ID for each independent update
      predicate = node => semver.inc(node.version, increment, resolvePrereleaseId(node.prereleaseId));
    } else if (increment) {
      // compute potential prerelease ID once for all fixed updates
      const prereleaseId = prereleaseIdFromVersion(this.project.version);
      const nextVersion = semver.inc(this.project.version, increment, resolvePrereleaseId(prereleaseId));

      predicate = makeGlobalVersionPredicate(nextVersion);
    } else if (conventionalCommits) {
      // it's a bit weird to have a return here, true
      return this.recommendVersions(resolvePrereleaseId);
    } else if (independentVersions) {
      // prompt for each independent update with potential prerelease ID
      predicate = makePromptVersion(resolvePrereleaseId);
    } else {
      // prompt once with potential prerelease ID
      const prereleaseId = prereleaseIdFromVersion(this.project.version);
      const node = { version: this.project.version, prereleaseId };

      predicate = makePromptVersion(resolvePrereleaseId);
      predicate = predicate(node).then(makeGlobalVersionPredicate);
    }

    return Promise.resolve(predicate).then(getVersion => this.reduceVersions(getVersion));
  }

  reduceVersions(getVersion) {
    const iterator = (versionMap, node) =>
      Promise.resolve(getVersion(node)).then(version => versionMap.set(node.name, version));

    return pReduce(this.updates, iterator, new Map());
  }

  getPrereleasePackageNames() {
    const prereleasePackageNames = getPackagesForOption(this.options.conventionalPrerelease);
    const isCandidate = prereleasePackageNames.has("*")
      ? () => true
      : (node, name) => prereleasePackageNames.has(name);

    return collectPackages(this.packageGraph, { isCandidate }).map(pkg => pkg.name);
  }

  recommendVersions(resolvePrereleaseId) {
    const independentVersions = this.project.isIndependent();
    const { changelogPreset, conventionalGraduate } = this.options;
    const rootPath = this.project.manifest.location;
    const type = independentVersions ? "independent" : "fixed";
    const prereleasePackageNames = this.getPrereleasePackageNames();
    const graduatePackageNames = Array.from(getPackagesForOption(conventionalGraduate));
    const shouldPrerelease = name => prereleasePackageNames && prereleasePackageNames.includes(name);
    const shouldGraduate = name => graduatePackageNames.includes("*") || graduatePackageNames.includes(name);
    const getPrereleaseId = node => {
      if (!shouldGraduate(node.name) && (shouldPrerelease(node.name) || node.prereleaseId)) {
        return resolvePrereleaseId(node.prereleaseId);
      }
    };

    let chain = Promise.resolve();

    if (type === "fixed") {
      chain = chain.then(() => this.setGlobalVersionFloor());
    }

    chain = chain.then(() =>
      this.reduceVersions(node =>
        ConventionalCommitUtilities.recommendVersion(node, type, {
          changelogPreset,
          rootPath,
          tagPrefix: this.tagPrefix,
          prereleaseId: getPrereleaseId(node),
        })
      )
    );

    if (type === "fixed") {
      chain = chain.then(versions => {
        this.globalVersion = this.setGlobalVersionCeiling(versions);

        return versions;
      });
    }

    return chain;
  }

  setGlobalVersionFloor() {
    const globalVersion = this.project.version;

    for (const { pkg } of this.updates) {
      if (semver.lt(pkg.version, globalVersion)) {
        this.logger.verbose(
          "version",
          `Overriding version of ${pkg.name} from ${pkg.version} to ${globalVersion}`
        );

        pkg.version = globalVersion;
      }
    }
  }

  setGlobalVersionCeiling(versions) {
    let highestVersion = this.project.version;

    versions.forEach(bump => {
      if (bump && semver.gt(bump, highestVersion)) {
        highestVersion = bump;
      }
    });

    versions.forEach((_, name) => versions.set(name, highestVersion));

    return highestVersion;
  }

  setUpdatesForVersions(versions) {
    if (this.project.isIndependent() || versions.size === this.packageGraph.size) {
      // only partial fixed versions need to be checked
      this.updatesVersions = versions;
    } else {
      let hasBreakingChange;

      for (const [name, bump] of versions) {
        hasBreakingChange = hasBreakingChange || isBreakingChange(this.packageGraph.get(name).version, bump);
      }

      if (hasBreakingChange) {
        // _all_ packages need a major version bump whenever _any_ package does
        this.updates = Array.from(this.packageGraph.values());
        this.updatesVersions = new Map(this.updates.map(({ name }) => [name, this.globalVersion]));
      } else {
        this.updatesVersions = versions;
      }
    }

    this.packagesToVersion = this.updates.map(({ pkg }) => pkg);
  }

  confirmVersions() {
    const changes = this.packagesToVersion.map(pkg => {
      let line = ` - ${pkg.name}: ${pkg.version} => ${this.updatesVersions.get(pkg.name)}`;
      if (pkg.private) {
        line += ` (${chalk.red("private")})`;
      }
      return line;
    });

    output("");
    output("Changes:");
    output(changes.join(os.EOL));
    output("");

    if (this.options.yes) {
      this.logger.info("auto-confirmed");
      return true;
    }

    // When composed from `lerna publish`, use this opportunity to confirm publishing
    const message = this.composed
      ? "Are you sure you want to publish these packages?"
      : "Are you sure you want to create these versions?";

    return PromptUtilities.confirm(message);
  }

  updatePackageVersions() {
    const { conventionalCommits, changelogPreset, changelog = true } = this.options;
    const independentVersions = this.project.isIndependent();
    const rootPath = this.project.manifest.location;
    const changedFiles = new Set();

    // my kingdom for async await :(
    let chain = Promise.resolve();

    // preversion:  Run BEFORE bumping the package version.
    // version:     Run AFTER bumping the package version, but BEFORE commit.
    // postversion: Run AFTER bumping the package version, and AFTER commit.
    // @see https://docs.npmjs.com/misc/scripts

    if (!this.hasRootedLeaf) {
      // exec preversion lifecycle in root (before all updates)
      chain = chain.then(() => this.runRootLifecycle("preversion"));
    }

    const actions = [
      pkg => this.runPackageLifecycle(pkg, "preversion").then(() => pkg),
      // manifest may be mutated by any previous lifecycle
      pkg => pkg.refresh(),
      pkg => {
        // set new version
        pkg.version = this.updatesVersions.get(pkg.name);

        // update pkg dependencies
        for (const [depName, resolved] of this.packageGraph.get(pkg.name).localDependencies) {
          const depVersion = this.updatesVersions.get(depName);

          if (depVersion && resolved.type !== "directory") {
            // don't overwrite local file: specifiers, they only change during publish
            pkg.updateLocalDependency(resolved, depVersion, this.savePrefix);
          }
        }

        return pkg.serialize().then(() => {
          // commit the updated manifest
          changedFiles.add(pkg.manifestLocation);

          return pkg;
        });
      },
      pkg => this.runPackageLifecycle(pkg, "version").then(() => pkg),
    ];

    if (conventionalCommits && changelog) {
      // we can now generate the Changelog, based on the
      // the updated version that we're about to release.
      const type = independentVersions ? "independent" : "fixed";

      actions.push(pkg =>
        ConventionalCommitUtilities.updateChangelog(pkg, type, {
          changelogPreset,
          rootPath,
          tagPrefix: this.tagPrefix,
        }).then(({ logPath, newEntry }) => {
          // commit the updated changelog
          changedFiles.add(logPath);

          // add release notes
          if (independentVersions) {
            this.releaseNotes.push({
              name: pkg.name,
              notes: newEntry,
            });
          }

          return pkg;
        })
      );
    }

    const mapUpdate = pPipe(actions);

    chain = chain.then(() =>
      runTopologically({
        packages: this.packagesToVersion,
        concurrency: this.concurrency,
        rejectCycles: this.options.rejectCycles,
        runner: mapUpdate,
      })
    );

    if (!independentVersions) {
      this.project.version = this.globalVersion;

      if (conventionalCommits && changelog) {
        chain = chain.then(() =>
          ConventionalCommitUtilities.updateChangelog(this.project.manifest, "root", {
            changelogPreset,
            rootPath,
            tagPrefix: this.tagPrefix,
            version: this.globalVersion,
          }).then(({ logPath, newEntry }) => {
            // commit the updated changelog
            changedFiles.add(logPath);

            // add release notes
            this.releaseNotes.push({
              name: "fixed",
              notes: newEntry,
            });
          })
        );
      }

      chain = chain.then(() =>
        this.project.serializeConfig().then(lernaConfigLocation => {
          // commit the version update
          changedFiles.add(lernaConfigLocation);
        })
      );
    }

    if (!this.hasRootedLeaf) {
      // exec version lifecycle in root (after all updates)
      chain = chain.then(() => this.runRootLifecycle("version"));
    }

    if (this.commitAndTag) {
      chain = chain.then(() => gitAdd(Array.from(changedFiles), this.execOpts));
    }

    return chain;
  }

  commitAndTagUpdates() {
    let chain = Promise.resolve();

    if (this.project.isIndependent()) {
      chain = chain.then(() => this.gitCommitAndTagVersionForUpdates());
    } else {
      chain = chain.then(() => this.gitCommitAndTagVersion());
    }

    chain = chain.then(tags => {
      this.tags = tags;
    });

    // run the postversion script for each update
    chain = chain.then(() =>
      pMap(this.packagesToVersion, pkg => this.runPackageLifecycle(pkg, "postversion"))
    );

    if (!this.hasRootedLeaf) {
      // run postversion, if set, in the root directory
      chain = chain.then(() => this.runRootLifecycle("postversion"));
    }

    return chain;
  }

  gitCommitAndTagVersionForUpdates() {
    const tags = this.packagesToVersion.map(pkg => `${pkg.name}@${this.updatesVersions.get(pkg.name)}`);
    const subject = this.options.message || "Publish";
    const message = tags.reduce((msg, tag) => `${msg}${os.EOL} - ${tag}`, `${subject}${os.EOL}`);

    return Promise.resolve()
      .then(() => gitCommit(message, this.gitOpts, this.execOpts))
      .then(() => Promise.all(tags.map(tag => gitTag(tag, this.gitOpts, this.execOpts))))
      .then(() => tags);
  }

  gitCommitAndTagVersion() {
    const version = this.globalVersion;
    const tag = `${this.tagPrefix}${version}`;
    const message = this.options.message
      ? this.options.message.replace(/%s/g, tag).replace(/%v/g, version)
      : tag;

    return Promise.resolve()
      .then(() => gitCommit(message, this.gitOpts, this.execOpts))
      .then(() => gitTag(tag, this.gitOpts, this.execOpts))
      .then(() => [tag]);
  }

  gitPushToRemote() {
    this.logger.info("git", "Pushing tags...");

    return gitPush(this.gitRemote, this.currentBranch, this.execOpts);
  }

  createGitHubReleases() {
    this.logger.info("github", "Creating GitHub releases...");

    const client = createGitHubClient();
    const repo = parseGitRepo(this.options.gitRemote, this.execOpts);

    return Promise.all(
      this.releaseNotes.map(({ notes, name }) => {
        const tag = name === "fixed" ? this.tags[0] : this.tags.find(t => t.startsWith(`${name}@`));

        /* istanbul ignore if */
        if (!tag) {
          return Promise.resolve();
        }

        const prereleaseParts = semver.prerelease(tag.replace(`${name}@`, "")) || [];

        return client.repos.createRelease({
          owner: repo.owner,
          repo: repo.name,
          tag_name: tag,
          name: tag,
          body: notes,
          draft: false,
          prerelease: prereleaseParts.length > 0,
        });
      })
    );
  }
}

module.exports.VersionCommand = VersionCommand;


@lerna/get-npm-exec-opts

An internal Lerna tool


const TopologicalConfig = figgyPudding({


@test/build-tool

echo "#!/usr/bin/env node
console.log('build tool executed')" > a-build-tool.js


concurrency: {},
"reject-cycles": {},
rejectCycles: "reject-cycles",
runner: {},
});

// query-graph options handled elsewhere


#!/usr/bin/env node
console.log("Hello, world!");


"use strict";

jest.mock("@lerna/child-process");

// mocked modules
const ChildProcessUtilities = require("@lerna/child-process");

// file under test
const npmRunScript = require("..");

describe("npm-run-script", () => {
  ChildProcessUtilities.exec.mockResolvedValue();
  ChildProcessUtilities.spawnStreaming.mockResolvedValue();

  describe("npmRunScript()", () => {
    it("runs an npm script in a directory", async () => {
      const script = "foo";
      const config = {
        args: ["--bar", "baz"],
        pkg: {
          location: "/test/npm/run/script",
        },
        npmClient: "npm",
      };

      await npmRunScript(script, config);

      expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith("npm", ["run", script, "--bar", "baz"], {
        cwd: config.pkg.location,
        env: {},
        pkg: config.pkg,
        reject: true,
      });
    });

    it("accepts opts.reject", async () => {
      const script = "foo";
      const config = {
        args: [],
        pkg: {
          location: "/test/npm/run/script",
        },
        npmClient: "npm",
        reject: false,
      };

      await npmRunScript(script, config);

      expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith("npm", ["run", script], {
        cwd: config.pkg.location,
        env: {},
        pkg: config.pkg,
        reject: false,
      });
    });

    it("supports a different npmClient", async () => {
      const script = "foo";
      const config = {
        args: ["--bar", "baz"],
        pkg: {
          location: "/test/npm/run/script",
        },
        npmClient: "yarn",
      };

      await npmRunScript(script, config);

      expect(ChildProcessUtilities.exec).toHaveBeenLastCalledWith("yarn", ["run", script, "--bar", "baz"], {
        cwd: config.pkg.location,
        env: {},
        pkg: config.pkg,
        reject: true,
      });
    });
  });

  describe("npmRunScript.stream()", () => {
    it("runs an npm script in a package with streaming", async () => {
      const script = "foo";
      const config = {
        args: ["--bar", "baz"],
        pkg: {
          name: "qux",
          location: "/test/npm/run/script/stream",
        },
        prefix: true,
        npmClient: "npm",
      };

      await npmRunScript.stream(script, config);

      expect(ChildProcessUtilities.spawnStreaming).toHaveBeenLastCalledWith(
        "npm",
        ["run", script, "--bar", "baz"],
        {
          cwd: config.pkg.location,
          env: {},
          pkg: config.pkg,
          reject: true,
        },
        config.pkg.name
      );
    });

    it("accepts opts.reject", async () => {
      const script = "foo";
      const config = {
        args: [],
        pkg: {
          name: "qux",
          location: "/test/npm/run/script/stream",
        },
        npmClient: "npm",
        reject: false,
      };

      await npmRunScript.stream(script, config);

      expect(ChildProcessUtilities.spawnStreaming).toHaveBeenLastCalledWith(
        "npm",
        ["run", script],
        {
          cwd: config.pkg.location,
          env: {},
          pkg: config.pkg,
          reject: false,
        },
        undefined
      );
    });
  });
});


# `@lerna/diff`

> Diff all packages or a single package since the last release

Install [lerna](https://www.npmjs.com/package/lerna) for access to the `lerna` CLI.

## Usage

```sh
$ lerna diff [package]

$ lerna diff
# diff a specific package
$ lerna diff package-name
```

Diff all packages or a single package since the last release.

> Similar to `lerna changed`. This command runs `git diff`.


const { packages, concurrency, rejectCycles, runner } = opts;

const queue = new PQueue({ concurrency });

const graph = new QueryGraph(packages, { rejectCycles });

return new Promise((resolve, reject) => {

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

* Run callback in maximally-saturated topological order.

* @param {Array<Package>} packages List of `Package` instances

* @param {Function} runner Callback to map each `Package` with

* @param {Number} [opts.concurrency] Concurrency of execution

* @param {String} [opts.graphType] "allDependencies" or "dependencies"

* @param {Boolean} [opts.rejectCycles] Whether or not to reject cycles

* @returns {Promise<Array<*>>} when all executions complete

function runTopologically(packages, runner, opts) {

const options = TopologicalConfig(opts);

const graph = new QueryGraph(packages, options);


"use strict";

const cliRunner = require("@lerna-test/cli-runner");
const initFixture = require("@lerna-test/init-fixture")(__dirname);

// ls never makes changes to repo, so we only need one fixture + runner
let lerna;

beforeAll(async () => {
  const cwd = await initFixture("lerna-ls");

  lerna = cliRunner(cwd);
});

test("lerna list", async () => {
  const { stdout } = await lerna("list");
  expect(stdout).toMatchInlineSnapshot(`
package-1
@test/package-2
package-3
`);
});

test("lerna ls", async () => {
  const { stdout } = await lerna("ls");
  expect(stdout).toMatchInlineSnapshot(`
package-1
@test/package-2
package-3
`);
});

test("lerna ls --all", async () => {
  const { stdout } = await lerna("ls", "--all");
  expect(stdout).toMatchInlineSnapshot(`
package-1
@test/package-2
package-3
package-4       (PRIVATE)
`);
});

test("lerna ls --long", async () => {
  const { stdout } = await lerna("ls", "--long");
  expect(stdout).toMatchInlineSnapshot(`
package-1        v1.0.0 packages/pkg-1
@test/package-2  v2.0.0 packages/pkg-2
package-3       MISSING packages/pkg-3
`);
});

test("lerna ls --parseable", async () => {
  const { stdout } = await lerna("ls", "--parseable");
  expect(stdout).toMatchInlineSnapshot(`
__TEST_ROOTDIR__/packages/pkg-1
__TEST_ROOTDIR__/packages/pkg-2
__TEST_ROOTDIR__/packages/pkg-3
`);
});

test("lerna ls --all --long --parseable", async () => {
  const { stdout } = await lerna("ls", "-alp");
  expect(stdout).toMatchInlineSnapshot(`
__TEST_ROOTDIR__/packages/pkg-1:package-1:1.0.0
__TEST_ROOTDIR__/packages/pkg-2:@test/package-2:2.0.0
__TEST_ROOTDIR__/packages/pkg-3:package-3:MISSING
__TEST_ROOTDIR__/packages/pkg-4:package-4:4.0.0:PRIVATE
`);
});

test("lerna la", async () => {
  const { stdout } = await lerna("la");
  expect(stdout).toMatchInlineSnapshot(`
package-1        v1.0.0 packages/pkg-1
@test/package-2  v2.0.0 packages/pkg-2
package-3       MISSING packages/pkg-3
package-4        v4.0.0 packages/pkg-4 (PRIVATE)
`);
});

test("lerna ll", async () => {
  const { stdout } = await lerna("ll");
  expect(stdout).toMatchInlineSnapshot(`
package-1        v1.0.0 packages/pkg-1
@test/package-2  v2.0.0 packages/pkg-2
package-3       MISSING packages/pkg-3
`);
});


  A tool for managing JavaScript projects with multiple packages.

- [About](#about)
- [Getting Started](#getting-started)
- [How It Works](#how-it-works)
- [Troubleshooting](#troubleshooting)
- Commands
  - [`lerna publish`](./commands/publish#readme)
  - [`lerna version`](./commands/version#readme)
  - [`lerna bootstrap`](./commands/bootstrap#readme)
  - [`lerna list`](./commands/list#readme)
  - [`lerna changed`](./commands/changed#readme)
  - [`lerna diff`](./commands/diff#readme)
  - [`lerna exec`](./commands/exec#readme)
  - [`lerna run`](./commands/run#readme)
  - [`lerna init`](./commands/init#readme)
  - [`lerna add`](./commands/add#readme)
  - [`lerna clean`](./commands/clean#readme)
  - [`lerna import`](./commands/import#readme)
  - [`lerna link`](./commands/link#readme)
  - [`lerna create`](./commands/create#readme)
- [Concepts](#concepts)
- [Lerna.json](#lernajson)
- [Global Flags](./core/global-options)
- [Filter Flags](./core/filter-options)

## About

Splitting up large codebases into separate independently versioned packages
is extremely useful for code sharing. However, making changes across many
repositories is _messy_ and difficult to track, and testing across repositories
gets complicated really fast.

To solve these (and many other) problems, some projects will organize their
codebases into multi-package repositories (sometimes called [monorepos](https://github.com/babel/babel/blob/master/doc/design/monorepo.md)). Projects like [Babel](https://github.com/babel/babel/tree/master/packages), [React](https://github.com/facebook/react/tree/master/packages), [Angular](https://github.com/angular/angular/tree/master/modules),
[Ember](https://github.com/emberjs/ember.js/tree/master/packages), [Meteor](https://github.com/meteor/meteor/tree/devel/packages), [Jest](https://github.com/facebook/jest/tree/master/packages), and many others develop all of their packages within a
single repository.

**Lerna is a tool that optimizes the workflow around managing multi-package
repositories with git and npm.**

Lerna can also reduce the time and space requirements for numerous
copies of packages in development and build environments - normally a
downside of dividing a project into many separate NPM package. See the
[hoist documentation](doc/hoist.md) for details.

### What does a Lerna repo look like?

There's actually very little to it. You have a file structure that looks like this:

```
my-lerna-repo/
  package.json
  packages/
    package-1/
      package.json
    package-2/
      package.json
```

### What can Lerna do?

The two primary commands in Lerna are `lerna bootstrap` and `lerna publish`.

`bootstrap` will link dependencies in the repo together.
`publish` will help publish any updated packages.

## Getting Started

> The instructions below are for Lerna 3.x.
> We recommend using it instead of 2.x for a new Lerna project.

Let's start by installing Lerna as a dev dependency of your project with [npm](https://www.npmjs.com/).

```sh
$ mkdir lerna-repo && cd $_
$ npx lerna init
```

This will create a `lerna.json` configuration file as well as a `packages` folder, so your folder should now look like this:

```
lerna-repo/
  packages/
  package.json
  lerna.json
```

## How It Works

Lerna allows you to manage your project using one of two modes: Fixed or Independent.

### Fixed/Locked mode (default)

Fixed mode Lerna projects operate on a single version line. The version is kept in the `lerna.json` file at the root of your project under the `version` key. When you run `lerna publish`, if a module has been updated since the last time a release was made, it will be updated to the new version you're releasing. This means that you only publish a new version of a package when you need to.

This is the mode that [Babel](https://github.com/babel/babel) is currently using. Use this if you want to automatically tie all package versions together. One issue with this approach is that a major change in any package will result in all packages having a new major version.

### Independent mode

`lerna init --independent`

Independent mode Lerna projects allows maintainers to increment package versions independently of each other. Each time you publish, you will get a prompt for each package that has changed to specify if it's a patch, minor, major or custom change.

Independent mode allows you to more specifically update versions for each package and makes sense for a group of components. Combining this mode with something like [semantic-release](https://github.com/semantic-release/semantic-release) would make it less painful. (There is work on this already at [atlassian/lerna-semantic-release](https://github.com/atlassian/lerna-semantic-release)).

> Set the `version` key in `lerna.json` to `independent` to run in independent mode.

## Troubleshooting

If you encounter any issues while using Lerna please check out our [Troubleshooting](doc/troubleshooting.md)
document where you might find the answer to your problem.

## Frequently asked questions

See [FAQ.md](FAQ.md).

## Concepts

Lerna will log to a `lerna-debug.log` file (same as `npm-debug.log`) when it encounters an error running a command.

Lerna also has support for [scoped packages](https://docs.npmjs.com/misc/scope).

Run `lerna --help` to see all available commands and options.

### lerna.json

```json
{
  "version": "1.1.3",
  "npmClient": "npm",
  "command": {
    "publish": {
      "ignoreChanges": ["ignored-file", "*.md"],
      "message": "chore(release): publish"
    },
    "bootstrap": {
      "ignore": "component-*",
      "npmClientArgs": ["--no-package-lock"]
    }
  },
  "packages": ["packages/*"]
}
```

- `version`: the current version of the repository.
- `npmClient`: an option to specify a specific client to run commands with (this can also be specified on a per command basis). Change to `"yarn"` to run all commands with yarn. Defaults to "npm".
- `command.publish.ignoreChanges`: an array of globs that won't be included in `lerna changed/publish`. Use this to prevent publishing a new version unnecessarily for changes, such as fixing a `README.md` typo.
- `command.publish.message`: a custom commit message when performing version updates for publication. See [@lerna/version](commands/version#--message-msg) for more details.
- `command.bootstrap.ignore`: an array of globs that won't be bootstrapped when running the `lerna bootstrap` command.
- `command.bootstrap.npmClientArgs`: array of strings that will be passed as arguments directly to `npm install` during the `lerna bootstrap` command.
- `command.bootstrap.scope`: an array of globs that restricts which packages will be bootstrapped when running the `lerna bootstrap` command.
- `packages`: Array of globs to use as package locations.

The packages config in `lerna.json` is a list of globs that match directories containing a `package.json`, which is how lerna recognizes "leaf" packages (vs the "root" `package.json`, which is intended to manage the dev dependencies and scripts for the entire repo).

By default, lerna initializes the packages list as `["packages/*"]`, but you can also use another directory such as `["modules/*"]`, or `["package1", "package2"]`. The globs defined are relative to the directory that `lerna.json` lives in, which is usually the repository root. The only restriction is that you can't directly nest package locations, but this is a restriction shared by "normal" npm packages as well.

For example, `["packages/*", "src/**"]` matches this tree:

```
packages/
├── foo-pkg
│   └── package.json
├── bar-pkg
│   └── package.json
├── baz-pkg
│   └── package.json
└── qux-pkg
    └── package.json
src/
├── admin
│   ├── my-app
│   │   └── package.json
│   ├── stuff
│   │   └── package.json
│   └── things
│       └── package.json
├── profile
│   └── more-things
│       └── package.json
├── property
│   ├── more-stuff
│   │   └── package.json
│   └── other-things
│       └── package.json
└── upload
    └── other-stuff
        └── package.json
```

Locating leaf packages under `packages/*` is considered a "best-practice", but is not a requirement for using Lerna.

### Common `devDependencies`

Most `devDependencies` can be pulled up to the root of a Lerna repo with `lerna link convert`

The above command will automatically hoist things and use relative `file:` specifiers.

Hoisting has a few benefits:
- All packages use the same version of a given dependency
- Can keep dependencies at the root up-to-date with an automated tool such as [GreenKeeper](https://greenkeeper.io/)
- Dependency installation time is reduced
- Less storage is needed

Note that `devDependencies` providing "binary" executables that are used by
npm scripts still need to be installed directly in each package where they're
used.

For example the `nsp` dependency is necessary in this case for `lerna run nsp`
(and `npm run nsp` within the package's directory) to work correctly:

```json
{
  "scripts": {
    "nsp": "nsp"
  },
  "devDependencies": {
    "nsp": "^2.3.3"
  }
}
```

### Git Hosted Dependencies

Lerna allows target versions of local dependent packages to be written as a [git remote url](https://docs.npmjs.com/cli/install) with a `committish` (e.g., `#v1.0.0` or `#semver:^1.0.0`) instead of the normal numeric version range.
This allows packages to be distributed via git repositories when packages must be private and a [private npm registry is not desired](https://www.dotconferences.com/2016/05/fabien-potencier-monolithic-repositories-vs-many-repositories).

Please note that lerna does _not_ perform the actual splitting of git history into the separate read-only repositories. This is the responsibility of the user. (See [this comment](https://github.com/lerna/lerna/pull/1033#issuecomment-335894690) for implementation details)

```
// packages/pkg-1/package.json
{
  name: "pkg-1",
  version: "1.0.0",
  dependencies: {
    "pkg-2": "github:example-user/pkg-2#v1.0.0"
  }
}

// packages/pkg-2/package.json
{
  name: "pkg-2",
  version: "1.0.0"
}
```

In the example above,

- `lerna bootstrap` will properly symlink `pkg-2` into `pkg-1`.
- `lerna publish` will update the committish (`#v1.0.0`) in `pkg-1` when `pkg-2` changes.

### README Badge

Using Lerna? Add a README badge to show it off: [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)

```
[![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)
```

### Wizard

If you prefer some guidance for cli (in case you're about to start using lerna or introducing it to a new team), you might like [lerna-wizard](https://github.com/szarouski/lerna-wizard). It will lead you through a series of well-defined steps:

![lerna-wizard demo image](https://raw.githubusercontent.com/szarouski/lerna-wizard/2e269fb5a3af7100397a1f874cea3fa78089486e/demo.png)


"use strict";

const cmdShim = require("cmd-shim");
const fs = require("fs-extra");
const log = require("npmlog");
const path = require("path");

module.exports = createSymlink;

function createSymlink(src, dest, type) {
  log.silly("createSymlink", [src, dest, type]);

  if (process.platform === "win32") {
    return createWindowsSymlink(src, dest, type);
  }

  return createPosixSymlink(src, dest, type);
}

function createSymbolicLink(src, dest, type) {
  log.silly("createSymbolicLink", [src, dest, type]);

  return fs
    .lstat(dest)
    .then(() => fs.unlink(dest))
    .catch(() => {
      /* nothing exists at destination */
    })
    .then(() => fs.symlink(src, dest, type));
}

function createPosixSymlink(origin, dest, _type) {
  const type = _type === "exec" ? "file" : _type;
  const relativeSymlink = path.relative(path.dirname(dest), origin);

  if (_type === "exec") {
    // If the target exists, create real symlink. If the target doesn't exist yet,
    // create a shim shell script.
    return fs.pathExists(origin).then(exists => {
      if (exists) {
        return createSymbolicLink(relativeSymlink, dest, type).then(() => fs.chmod(origin, "755"));
      }
      return shShim(origin, dest, type).then(() => fs.chmod(dest, "755"));
    });
  }

  return createSymbolicLink(relativeSymlink, dest, type);
}

function createWindowsSymlink(src, dest, type) {
  if (type === "exec") {
    return new Promise((resolve, reject) => {
      cmdShim(src, dest, err => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

  return createSymbolicLink(src, dest, type);
}

function shShim(target, script, type) {
  log.silly("shShim", [target, script, type]);

  const absTarget = path.resolve(path.dirname(script), target);

  const scriptLines = ["#!/bin/sh", `chmod +x ${absTarget} && exec ${absTarget} "$@"`];

  return fs.writeFile(script, scriptLines.join("\n"));
}


keep keep keep replace keep keep keep keep keep

<template>
  <v-theme-provider dark>
    <v-snackbar
      v-model="snack"
      :color="snackbar.color"
      :style="styles"
      :timeout="0"
      :vertical="$vuetify.breakpoint.xsOnly"
      top
    >
      <v-row
        align="center"
        class="mx-0"
      >
        <v-icon
          v-if="computedIcon"
          class="mr-2"
        >
          {{ computedIcon }}
        </v-icon>

        <span
          v-if="snackbar.emoji"
          class="mr-2"
          v-text="snackbar.emoji"
        />

        <base-markdown
          :code="snackbar.text"
          class="snack-markdown"
        />

        <v-btn
          :ripple="false"
          class="black--text ml-auto ml-sm-4"
          color="white"
          depressed
          v-bind="bind"
          @click="$ga.event('snackbar', 'click', snackbar.metadata.slug)"
        >
          {{ snackbar.action_text }}

          <v-icon right>mdi-open-in-new</v-icon>
        </v-btn>

        <v-btn
          :aria-label="$t('Vuetify.Snackbar.close')"
          :ripple="false"
          class="ml-4"
          color="grey darken-1"
          icon
          small
          @click="onClick"
        >
          <v-icon>$vuetify.cancel</v-icon>
        </v-btn>
      </v-row>
    </v-snackbar>
  </v-theme-provider>
</template>

<script>
  import {
    get,
    sync,
  } from 'vuex-pathify'

  export default {
    name: 'CoreSnackbar',

    computed: {
      snack: sync('snackbar/value'),
      snackbar: get('snackbar/snackbar'),
      bind () {
        const { action } = this.snackbar
        const isExternal = action.indexOf('http') > -1

        return !isExternal ? { to: action } : {
          href: action,
          target: '_blank',
        }
      },
      computedColor () {
        if (this.snackbar.color !== 'store') {
          return !this.computedIcon ? 'primary lighten-3' : null
        }

        return 'green'
      },
      computedIcon () {
        switch (this.snackbar.color) {
          case 'store': return 'mdi-cart'
          case 'success': return 'mdi-check'
          case 'info': return 'mdi-information'
          case 'warning': return 'mdi-alert'
          case 'error': return 'mdi-alert-circle'
          default: return false
        }
      },
      styles () {
        const { top, bar } = this.$vuetify.application

        return { top: `${top + bar + 16}px` }
      },
    },

    watch: {
      snackbar: {
        deep: true,
        handler () {
          this.snack = true
        },
      },
    },

    methods: {
      onClick () {
        this.$ga.event('snackbar', 'click', `dismissed ${this.snackbar.slug}`)
        this.snack = false
      },
    },
  }
</script>

<style>
  .snack-markdown p {
    margin-bottom: 0 !important;
  }
</style>


<template>
  <v-card dark>
    <v-toolbar
      color="primary"
      flat
    >
      <v-switch
        v-model="$vuetify.theme.dark"
        hide-details
        inset
        label="Theme Dark"
      ></v-switch>
    </v-toolbar>

    <v-card-text>
      <v-list>
        <v-subheader>I inherit dark from my parent</v-subheader>

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

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

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

      <v-divider class="my-8"></v-divider>

      <v-theme-provider root>
        <v-list>
          <v-subheader>I inherit from the root $vuetify.theme.dark</v-subheader>

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

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

          <v-list-item>
            <v-list-item-title>Three</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-theme-provider>
    </v-card-text>
  </v-card>
</template>

<script>
  export default {
    props: {
      attrs: {
        type: Object,
        default: () => ({}),
      },
    },

    data: vm => ({
      initialDark: vm.$vuetify
        ? vm.$vuetify.theme.dark
        : false,
    }),

    beforeDestroy () {
      if (!this.$vuetify) return

      this.$vuetify.theme.dark = this.initialDark
    },
  }
</script>


import { VuetifyIcons } from 'vuetify/types/services/icons'
import { Component } from 'vue'
import icons from './fa'

export function convertToComponentDeclarations (
  component: Component | string,
  iconSet: VuetifyIcons,
) {
  const result: Partial<VuetifyIcons> = {}

  for (const key in iconSet) {
    result[key] = {
      component,
      props: {
        icon: (iconSet[key] as string).split(' fa-'),
      },
    }
  }

  return result as VuetifyIcons
}

export default convertToComponentDeclarations('font-awesome-icon', icons)


keep keep keep keep replace keep keep keep keep


declare module 'vuetify/es5/install' {
  import { VueConstructor } from 'vue'

  const install: (Vue: VueConstructor, args: {}) => void

  export { install }
}
declare module 'vuetify/es5/components/Vuetify' {
  import Vuetify from 'vuetify'

  export default Vuetify
}

declare module 'vuetify/es5/components/*' {
  import { ComponentOrPack } from 'vuetify'
  import { VueConstructor } from 'vue'

  const VuetifyComponent: {
    // FIX: The & VueConstructor is a lie.
    // This might not be a valid component.
    // But registering arbitrary objects as components is the status quo.
    default: ComponentOrPack & VueConstructor
    [key: string]: ComponentOrPack & VueConstructor
  }

  export = VuetifyComponent
}

declare module 'vuetify/es5/directives' {
  import { DirectiveOptions } from 'vue'

  const ClickOutside: DirectiveOptions
  const Intersect: DirectiveOptions
  const Mutate: DirectiveOptions
  const Resize: DirectiveOptions
  const Ripple: DirectiveOptions
  const Scroll: DirectiveOptions
  const Touch: DirectiveOptions

  export {
    ClickOutside,
    Intersect,
    Mutate,
    Ripple,
    Resize,
    Scroll,
    Touch
  }
}


<template>

    To install Vuetify, type npm install vuetify into your console. Once complete, type cd <project name> and run npm install

</template>


# Aspect Ratios

The `v-responsive` component can be used to fix any section to a specific aspect ratio

Sets a base aspect ratio, calculated as width/height. This will only set a **minimum** height, the component can still grow if it has a lot of content.


keep keep keep replace replace replace replace keep keep keep

const last = localStorage.getItem('vuetify-notification-last-time')

<template>
  <v-menu
    max-height="320"
    max-width="320"
    offset-y
    origin="bottom left"
    right
    top
    transition="scale-transition"
  >
    <template v-slot:activator="{ attrs, on }">
      <v-btn
        icon
        v-bind="attrs"
        v-on="on"
      >
        <v-badge
          color="red"
          dot
          overlap
          left
        >
          <v-icon>mdi-bell</v-icon>
        </v-badge>
      </v-btn>
    </template>

    <v-list>
      <template v-for="({ created_at, metadata, slug, title }, i) in items">
        <v-list-item
          :key="i"
          :href="metadata.action"
          target="_blank"
          @click="$ga.event('notification', 'click', slug)"
        >
          <v-list-item-content>
            <div
              class="grey--text body-2 mb-2 font-weight-light"
              v-text="created_at"
            />

            <v-list-item-title
              class="mb-2"
              v-text="`${metadata.emoji} ${title}`"
            />

            <base-markdown
              :code="metadata.text"
              class="caption grey--text text--darken-1"
            />
          </v-list-item-content>

        </v-list-item>

        <v-divider
          v-if="i + 1 !== items.length"
          :key="`dv-${i}`"
        />
      </template>
    </v-list>
  </v-menu>
</template>

<script>
  // Utilities
  import bucket from '@/plugins/cosmicjs'
  import { formatDate } from '@/util/date.js'
  import { differenceInDays, parseISO } from 'date-fns'
  import {
    get,
    sync,
  } from 'vuex-pathify'

  export default {
    name: 'AppNotifications',

    data: () => ({
      snack: false,
      items: [],
    }),

    computed: {
      snackbar: sync('snackbar/snackbar'),
      value: get('snackbar/value'),
    },

    watch: {
      value (val) {
        if (val) return

        this.markViewed(this.snackbar.slug)
      },
    },

    async mounted () {
      const items = []
      const { objects } = await bucket.getObjects({
        type: 'notifications',
        props: 'created_at,metadata,slug,title',
        limit: 4,
        sort: '-created_at',
      })

      for (const object of objects) {
        const item = Object.assign({}, object, {
          created_at: formatDate(new Date(object.created_at)),
        })

        if (
          !this.hasRecentlyViewed() &&
          !this.hasBeenViewed(item) &&
          !this.snack
        ) {
          this.snack = true
          this.snackbar = {
            slug: item.slug,
            ...item.metadata,
          }

          continue
        }

        items.push(item)
      }

      this.items = items
    },

    methods: {
      getNow () {
        return (new Date()).toISOString()
      },
      hasBeenViewed (item) {
        return Boolean(localStorage.getItem(`vuetify-notification-${item.slug}`))
      },
      markViewed (slug) {
        localStorage.setItem(`vuetify-notification-${slug}`, true)
        localStorage.setItem('vuetify-notification-last-time', this.getNow())
      },
      hasRecentlyViewed () {
        const last = (
          localStorage.getItem('vuetify-notification-last-time') ||
          this.getNow()
        )

        return differenceInDays(parseISO(this.getNow()), parseISO(last)) < 2
      },
    },
  }
</script>


<template>
  <div class="ma-12 pa-12">
    <template>
      <v-card>
        <v-navigation-drawer
          permanent
          expand-on-hover
        >
          <v-list>
            <v-list-item class="px-2">
              <v-list-item-avatar>
                <v-img src="https://randomuser.me/api/portraits/women/85.jpg"></v-img>
              </v-list-item-avatar>
            </v-list-item>

            <v-list-item link>
              <v-list-item-content>
                <v-list-item-title class="title">Sandra Adams</v-list-item-title>
                <v-list-item-subtitle>sandra_a88@gmail.com</v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
          </v-list>

          <v-divider></v-divider>

          <v-list
            nav
            dense
          >
            <v-list-item link>
              <v-list-item-icon>
                <v-icon>mdi-folder</v-icon>
              </v-list-item-icon>
              <v-list-item-title>My Files</v-list-item-title>
            </v-list-item>
            <v-list-item link>
              <v-list-item-icon>
                <v-icon>mdi-account-multiple</v-icon>
              </v-list-item-icon>
              <v-list-item-title>Shared with me</v-list-item-title>
            </v-list-item>
            <v-list-item link>
              <v-list-item-icon>
                <v-icon>mdi-star</v-icon>
              </v-list-item-icon>
              <v-list-item-title>Starred</v-list-item-title>
            </v-list-item>
          </v-list>
        </v-navigation-drawer>
      </v-card>
    </template>
  </div>
</template>

<script>
  export default {
    //
  }
</script>


# Flex

レスポンシブなflexboxボックスユーティリティを使用して、位置合わせ、均等割付などを行い、フレックスコンテナのレイアウトを制御します。

`display`ユーティリティを使用すると、任意の要素を Flexbox コンテナに変換し、**直接の子要素** をフレックスアイテムに変換することができます。追加のflexプロパティを使用すると、その相互作用をさらにカスタマイズできます。

また、さまざまなブレークポイントに基づいて適用する flex ユーティリティをカスタマイズすることもできます。

デフォルトでは、'd-flex' は `flex-direction: row` が適用され、通常は省略できます。ただし、明示的に定義する必要がある場合もあります。

`flex-column`および`flex-column-reverse`ユーティリティクラスを使用して、フレックスボックスコンテナの向きを変更できます。**IE11**と**Safari**は、列の方向に問題がある可能性がありますので注意してください。

また、`flex-direction`にはレスポンシブなバリエーションもあります。

`align-items`フレックス設定はflex alignクラスを使用して変更できます。これはデフォルトで **y-axis** のフレックスボックスアイテムを変更しますが、 `flex-direction: column`を使用すると逆に**x-axis**に変更されます。 整列位置は `start`, `end`, `center`, `baseline` または `stretch` (ブラウザのデフォルト) から指定します。

IE11でflex alignを使用する場合、「min-height」では不十分であり、望ましくない結果が生じるため、明示的な「height」を設定する必要があります。

Flexbox コンテナのマージンヘルパー クラスを使用すると、 `flex-row` または `flex-column` をそれぞれ使用する場合に、**x-axis** または **y-axis** 上のフレックス アイテムの位置を制御できます。

**IE11**は、デフォルト以外の `justify-content` 値を持つ親を持つflexアイテムの自動マージンを適切にサポートしません。 詳細については、この [StackOverflowの回答を参照](https://stackoverflow.com/a/37535548) してください。

これらのヘルパークラスは、`flex-{breakpoint}-{condition}` の形式で適用して、ブレークポイントに基づいて、よりレスポンシブなバリエーションを作成することもできます。次の組み合わせを使用できます:

`order` ユーティリティを使用して、フレックスの表示順序を変更できます。

また、`order`にはレスポンシブなバリエーションもあります。

`align-content-start` `align-content-end` `align-content-center` `align-content-space-between` `align-content-space-around` `align-content-stretch` `align-sm-content-start` `align-sm-content-end` `align-sm-content-center` `align-sm-content-space-between` `align-sm-content-space-around` `align-sm-content-stretch` `align-md-content-start` `align-md-content-end` `align-md-content-center` `align-md-content-space-between` `align-md-content-space-around` `align-md-content-stretch` `align-lg-content-start` `align-lg-content-end` `align-lg-content-center` `align-lg-content-space-between` `align-lg-content-space-around` `align-lg-content-stretch` `align-xl-content-start` `align-xl-content-end` `align-xl-content-center` `align-xl-content-space-between` `align-xl-content-space-around` `align-xl-content-stretch`


Parallax 안에 어떤 컨텐츠라도 포함할 수 있습니다. 이로써 parallax 를 Hero 이미지 처럼 사용할 수 있습니다.

Parallax에 사용자 지정 높이를 지정할 수 있습니다. 이미지의 크기를 적절하게 조절하지 않으면 parallax가 깨질 수도 있다는 점을 명심하세요

parallax 이미지에 alt 프로퍼티를 추가


トランジション モードを設定します(transition-groupには適用されません)。詳細については、Vue のドキュメント [トランジションモード](https://jp.vuejs.org/v2/api/#transition) を参照してください。

要素にトランジションの起点を設定します。 MDNドキュメントで [for transition origin](https://developer.mozilla.org/ja/docs/Web/CSS/transform-origin) の詳細を参照できます。

コンポーネントのトランジションを設定します。 [組み込みトランジション](/styles/transitions) のいずれか、または独自のものを使用できます。


usage-new(:value=`{
  booleans: [
    'disabled',
    'loading',
    'block'
  ],
  sliders: [
    {
      prop: 'elevation',
      attrs: {
        min: 0,
        max: 24
      },
      value: 2
    }
  ],
  selects: [
    {
      prop: 'color',
      label: 'Colors',
      attrs: {
        items: [
          'deep-purple accent-4',
          'primary',
          'secondary',
          'accent'
        ]
      }
    },
    {
      prop: 'size',
      label: 'Sizes',
      attrs: {
        items: [
          'x-small',
          'small',
          'large',
          'x-large'
        ]
      }
    }
  ],
  tabs: [
    'raised',
    'depressed',
    'outlined',
    'rounded',
    'text',
    'fab',
    'icon',
    'tile'
  ]
}`)
api(:value=`[
  'v-btn',
  'v-btn-toggle'
]`)
alert(value="warning") alert
examples(:value=`[
  'simple/text',
  'simple/raised',
  'simple/depressed',
  'simple/dropdown',
  'simple/icon',
  'simple/floating',
  'simple/sizing',
  'simple/outlined',
  'simple/rounded',
  'simple/tile',
  'simple/block',
  'intermediate/loaders'
]`)

up-next(:value=`[
  'components/button-groups',
  'components/icons',
  'components/floating-action-buttons'
]`)


您对 Vue 的经验如何？

您的项目需要测试脚手架吗？

您的应用程序是否需要 SEO 优化？

如果您的选择有建议，它将在下面突出显示


node_modules
packages/api-generator/dist
packages/docs/dist
packages/vuetify/coverage
packages/vuetify/dist
packages/vuetify/es5
packages/vuetify/lib
packages/vuetify/lib-temp


// Libraries
import Vue from 'vue'

// Components
import VAvatar from '../VAvatar'

// Utilities
import {
  createLocalVue,
  mount,
  Wrapper,
} from '@vue/test-utils'

describe('VAvatar', () => {
  let mountFunction: (options?: object) => Wrapper<Vue>
  let localVue: typeof Vue

  beforeEach(() => {
    localVue = createLocalVue()

    mountFunction = (options = {}) => {
      return mount(VAvatar, {
        localVue,
        ...options,
      })
    }
  })

  it('should have an v-avatar class', () => {
    const wrapper = mountFunction()

    expect(wrapper.classes()).toContain('v-avatar')
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should have an proper class with tile prop', () => {
    const wrapper = mountFunction({
      propsData: {
        tile: true,
      },
    })

    expect(wrapper.classes()).toContain('v-avatar--tile')
  })
})


<template>
  <div class="grey lighten-5 pa-4">
    <v-row
      align="center"
      justify="center"
    >
      <v-col
        cols="12"
        md="8"
        sm="4"
      >
        <v-card>
          <v-subheader :inset="inset">Subheader</v-subheader>

          <v-list>
            <template v-for="(item, index) in items">
              <v-list-item
                v-if="item.action"
                :key="item.title"
                @click=""
              >
                <v-list-item-action>
                  <v-icon>{{ item.action }}</v-icon>
                </v-list-item-action>

                <v-list-item-content>
                  <v-list-item-title>{{ item.title }}</v-list-item-title>
                </v-list-item-content>
              </v-list-item>

              <v-divider
                v-else-if="item.divider"
                :key="index"
              ></v-divider>
            </template>
          </v-list>
        </v-card>
      </v-col>
    </v-row>
    <v-row
      class="mt-12"
      align="center"
      justify="center"
    >
      <v-col
        cols="12"
        md="8"
      >
        <template>
          <v-checkbox
            v-model="inset"
            hide-details
            label="Inset"
            light
          ></v-checkbox>
        </template>
      </v-col>
    </v-row>
  </div>
</template>

<script>
  export default {
    data: () => ({
      inset: false,
      items: [
        {
          action: 'inbox',
          title: 'inbox',
        },
        {
          divider: true,
        },
        {
          action: 'send',
          title: 'send',
        },
        {
          divider: true,
        },
        {
          action: 'delete',
          title: 'trash',
        },
      ],
    }),
  }
</script>


this.$ga.event('snackbar', 'click', `dismissed ${this.snackbar.slug}`)

keep keep keep keep keep replace replace keep keep keep


# Directive de redimensionnement

La directive `v-resize` peut être utilisée pour appeler des fonctions spécifiques lorsque la fenêtre est redimensionnée.

Redimensionnez votre fenêtre et observez les changements de valeurs.

`v-resize.quiet="callback"` N'invoquera **pas** automatiquement la fonction de rappel fournie lors de la liaison.

`v-resize="callback"` La fonction à appeler lors du redimensionnement de la fenêtre


# コンサルティングとサポート


crwdns44246:0crwdne44246:0