index.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. "use strict";
  2. const os = require("os");
  3. const chalk = require("chalk");
  4. const dedent = require("dedent");
  5. const minimatch = require("minimatch");
  6. const pMap = require("p-map");
  7. const pPipe = require("p-pipe");
  8. const pReduce = require("p-reduce");
  9. const pWaterfall = require("p-waterfall");
  10. const semver = require("semver");
  11. const { Command } = require("@lerna/command");
  12. const { recommendVersion, updateChangelog } = require("@lerna/conventional-commits");
  13. const { checkWorkingTree, throwIfUncommitted } = require("@lerna/check-working-tree");
  14. const { promptConfirmation } = require("@lerna/prompt");
  15. const { output } = require("@lerna/output");
  16. const { collectUpdates, collectPackages, getPackagesForOption } = require("@lerna/collect-updates");
  17. const { createRunner } = require("@lerna/run-lifecycle");
  18. const { runTopologically } = require("@lerna/run-topologically");
  19. const { ValidationError } = require("@lerna/validation-error");
  20. const { prereleaseIdFromVersion } = require("@lerna/prerelease-id-from-version");
  21. const { getCurrentBranch } = require("./lib/get-current-branch");
  22. const { gitAdd } = require("./lib/git-add");
  23. const { gitCommit } = require("./lib/git-commit");
  24. const { gitPush } = require("./lib/git-push");
  25. const { gitTag } = require("./lib/git-tag");
  26. const { isBehindUpstream } = require("./lib/is-behind-upstream");
  27. const { remoteBranchExists } = require("./lib/remote-branch-exists");
  28. const { isBreakingChange } = require("./lib/is-breaking-change");
  29. const { isAnythingCommitted } = require("./lib/is-anything-committed");
  30. const { makePromptVersion } = require("./lib/prompt-version");
  31. const { createRelease, createReleaseClient } = require("./lib/create-release");
  32. const { updateLockfileVersion } = require("./lib/update-lockfile-version");
  33. module.exports = factory;
  34. function factory(argv) {
  35. return new VersionCommand(argv);
  36. }
  37. class VersionCommand extends Command {
  38. get otherCommandConfigs() {
  39. // back-compat
  40. return ["publish"];
  41. }
  42. get requiresGit() {
  43. return (
  44. this.commitAndTag || this.pushToRemote || this.options.allowBranch || this.options.conventionalCommits
  45. );
  46. }
  47. configureProperties() {
  48. super.configureProperties();
  49. // Defaults are necessary here because yargs defaults
  50. // override durable options provided by a config file
  51. const {
  52. amend,
  53. commitHooks = true,
  54. gitRemote = "origin",
  55. gitTagVersion = true,
  56. granularPathspec = true,
  57. push = true,
  58. signGitCommit,
  59. signGitTag,
  60. forceGitTag,
  61. tagVersionPrefix = "v",
  62. } = this.options;
  63. this.gitRemote = gitRemote;
  64. this.tagPrefix = tagVersionPrefix;
  65. this.commitAndTag = gitTagVersion;
  66. this.pushToRemote = gitTagVersion && amend !== true && push;
  67. // never automatically push to remote when amending a commit
  68. this.releaseClient =
  69. this.pushToRemote && this.options.createRelease && createReleaseClient(this.options.createRelease);
  70. this.releaseNotes = [];
  71. if (this.releaseClient && this.options.conventionalCommits !== true) {
  72. throw new ValidationError("ERELEASE", "To create a release, you must enable --conventional-commits");
  73. }
  74. if (this.releaseClient && this.options.changelog === false) {
  75. throw new ValidationError("ERELEASE", "To create a release, you cannot pass --no-changelog");
  76. }
  77. this.gitOpts = {
  78. amend,
  79. commitHooks,
  80. granularPathspec,
  81. signGitCommit,
  82. signGitTag,
  83. forceGitTag,
  84. };
  85. // https://docs.npmjs.com/misc/config#save-prefix
  86. this.savePrefix = this.options.exact ? "" : "^";
  87. }
  88. initialize() {
  89. if (!this.project.isIndependent()) {
  90. this.logger.info("current version", this.project.version);
  91. }
  92. if (this.requiresGit) {
  93. // git validation, if enabled, should happen before updates are calculated and versions picked
  94. if (!isAnythingCommitted(this.execOpts)) {
  95. throw new ValidationError(
  96. "ENOCOMMIT",
  97. "No commits in this repository. Please commit something before using version."
  98. );
  99. }
  100. this.currentBranch = getCurrentBranch(this.execOpts);
  101. if (this.currentBranch === "HEAD") {
  102. throw new ValidationError(
  103. "ENOGIT",
  104. "Detached git HEAD, please checkout a branch to choose versions."
  105. );
  106. }
  107. if (this.pushToRemote && !remoteBranchExists(this.gitRemote, this.currentBranch, this.execOpts)) {
  108. throw new ValidationError(
  109. "ENOREMOTEBRANCH",
  110. dedent`
  111. Branch '${this.currentBranch}' doesn't exist in remote '${this.gitRemote}'.
  112. If this is a new branch, please make sure you push it to the remote first.
  113. `
  114. );
  115. }
  116. if (
  117. this.options.allowBranch &&
  118. ![].concat(this.options.allowBranch).some((x) => minimatch(this.currentBranch, x))
  119. ) {
  120. throw new ValidationError(
  121. "ENOTALLOWED",
  122. dedent`
  123. Branch '${this.currentBranch}' is restricted from versioning due to allowBranch config.
  124. Please consider the reasons for this restriction before overriding the option.
  125. `
  126. );
  127. }
  128. if (
  129. this.commitAndTag &&
  130. this.pushToRemote &&
  131. isBehindUpstream(this.gitRemote, this.currentBranch, this.execOpts)
  132. ) {
  133. // eslint-disable-next-line max-len
  134. const message = `Local branch '${this.currentBranch}' is behind remote upstream ${this.gitRemote}/${this.currentBranch}`;
  135. if (!this.options.ci) {
  136. // interrupt interactive execution
  137. throw new ValidationError(
  138. "EBEHIND",
  139. dedent`
  140. ${message}
  141. Please merge remote changes into '${this.currentBranch}' with 'git pull'
  142. `
  143. );
  144. }
  145. // CI execution should not error, but warn & exit
  146. this.logger.warn("EBEHIND", `${message}, exiting`);
  147. // still exits zero, aka "ok"
  148. return false;
  149. }
  150. } else {
  151. this.logger.notice(
  152. "FYI",
  153. "git repository validation has been skipped, please ensure your version bumps are correct"
  154. );
  155. }
  156. if (this.options.conventionalPrerelease && this.options.conventionalGraduate) {
  157. throw new ValidationError(
  158. "ENOTALLOWED",
  159. dedent`
  160. --conventional-prerelease cannot be combined with --conventional-graduate.
  161. `
  162. );
  163. }
  164. this.updates = collectUpdates(
  165. this.packageGraph.rawPackageList,
  166. this.packageGraph,
  167. this.execOpts,
  168. this.options
  169. ).filter((node) => {
  170. // --no-private completely removes private packages from consideration
  171. if (node.pkg.private && this.options.private === false) {
  172. // TODO: (major) make --no-private the default
  173. return false;
  174. }
  175. if (!node.version) {
  176. // a package may be unversioned only if it is private
  177. if (node.pkg.private) {
  178. this.logger.info("version", "Skipping unversioned private package %j", node.name);
  179. } else {
  180. throw new ValidationError(
  181. "ENOVERSION",
  182. dedent`
  183. A version field is required in ${node.name}'s package.json file.
  184. If you wish to keep the package unversioned, it must be made private.
  185. `
  186. );
  187. }
  188. }
  189. return !!node.version;
  190. });
  191. if (!this.updates.length) {
  192. this.logger.success(`No changed packages to ${this.composed ? "publish" : "version"}`);
  193. // still exits zero, aka "ok"
  194. return false;
  195. }
  196. // a "rooted leaf" is the regrettable pattern of adding "." to the "packages" config in lerna.json
  197. this.hasRootedLeaf = this.packageGraph.has(this.project.manifest.name);
  198. if (this.hasRootedLeaf && !this.composed) {
  199. this.logger.info("version", "rooted leaf detected, skipping synthetic root lifecycles");
  200. }
  201. this.runPackageLifecycle = createRunner(this.options);
  202. // don't execute recursively if run from a poorly-named script
  203. this.runRootLifecycle = /^(pre|post)?version$/.test(process.env.npm_lifecycle_event)
  204. ? (stage) => {
  205. this.logger.warn("lifecycle", "Skipping root %j because it has already been called", stage);
  206. }
  207. : (stage) => this.runPackageLifecycle(this.project.manifest, stage);
  208. const tasks = [
  209. () => this.getVersionsForUpdates(),
  210. (versions) => this.setUpdatesForVersions(versions),
  211. () => this.confirmVersions(),
  212. ];
  213. // amending a commit probably means the working tree is dirty
  214. if (this.commitAndTag && this.gitOpts.amend !== true) {
  215. const { forcePublish, conventionalCommits, conventionalGraduate } = this.options;
  216. const checkUncommittedOnly = forcePublish || (conventionalCommits && conventionalGraduate);
  217. const check = checkUncommittedOnly ? throwIfUncommitted : checkWorkingTree;
  218. tasks.unshift(() => check(this.execOpts));
  219. } else {
  220. this.logger.warn("version", "Skipping working tree validation, proceed at your own risk");
  221. }
  222. return pWaterfall(tasks);
  223. }
  224. execute() {
  225. const tasks = [() => this.updatePackageVersions()];
  226. if (this.commitAndTag) {
  227. tasks.push(() => this.commitAndTagUpdates());
  228. } else {
  229. this.logger.info("execute", "Skipping git tag/commit");
  230. }
  231. if (this.pushToRemote) {
  232. tasks.push(() => this.gitPushToRemote());
  233. } else {
  234. this.logger.info("execute", "Skipping git push");
  235. }
  236. if (this.releaseClient) {
  237. this.logger.info("execute", "Creating releases...");
  238. tasks.push(() =>
  239. createRelease(
  240. this.releaseClient,
  241. { tags: this.tags, releaseNotes: this.releaseNotes },
  242. { gitRemote: this.options.gitRemote, execOpts: this.execOpts }
  243. )
  244. );
  245. } else {
  246. this.logger.info("execute", "Skipping releases");
  247. }
  248. return pWaterfall(tasks).then(() => {
  249. if (!this.composed) {
  250. this.logger.success("version", "finished");
  251. }
  252. return {
  253. updates: this.updates,
  254. updatesVersions: this.updatesVersions,
  255. };
  256. });
  257. }
  258. getVersionsForUpdates() {
  259. const independentVersions = this.project.isIndependent();
  260. const { bump, conventionalCommits, preid } = this.options;
  261. const repoVersion = bump ? semver.clean(bump) : "";
  262. const increment = bump && !semver.valid(bump) ? bump : "";
  263. const resolvePrereleaseId = (existingPreid) => preid || existingPreid || "alpha";
  264. const makeGlobalVersionPredicate = (nextVersion) => {
  265. this.globalVersion = nextVersion;
  266. return () => nextVersion;
  267. };
  268. // decide the predicate in the conditionals below
  269. let predicate;
  270. if (repoVersion) {
  271. predicate = makeGlobalVersionPredicate(repoVersion);
  272. } else if (increment && independentVersions) {
  273. // compute potential prerelease ID for each independent update
  274. predicate = (node) => semver.inc(node.version, increment, resolvePrereleaseId(node.prereleaseId));
  275. } else if (increment) {
  276. // compute potential prerelease ID once for all fixed updates
  277. const prereleaseId = prereleaseIdFromVersion(this.project.version);
  278. const nextVersion = semver.inc(this.project.version, increment, resolvePrereleaseId(prereleaseId));
  279. predicate = makeGlobalVersionPredicate(nextVersion);
  280. } else if (conventionalCommits) {
  281. // it's a bit weird to have a return here, true
  282. return this.recommendVersions(resolvePrereleaseId);
  283. } else if (independentVersions) {
  284. // prompt for each independent update with potential prerelease ID
  285. predicate = makePromptVersion(resolvePrereleaseId);
  286. } else {
  287. // prompt once with potential prerelease ID
  288. const prereleaseId = prereleaseIdFromVersion(this.project.version);
  289. const node = { version: this.project.version, prereleaseId };
  290. predicate = makePromptVersion(resolvePrereleaseId);
  291. predicate = predicate(node).then(makeGlobalVersionPredicate);
  292. }
  293. return Promise.resolve(predicate).then((getVersion) => this.reduceVersions(getVersion));
  294. }
  295. reduceVersions(getVersion) {
  296. const iterator = (versionMap, node) =>
  297. Promise.resolve(getVersion(node)).then((version) => versionMap.set(node.name, version));
  298. return pReduce(this.updates, iterator, new Map());
  299. }
  300. getPrereleasePackageNames() {
  301. const prereleasePackageNames = getPackagesForOption(this.options.conventionalPrerelease);
  302. const isCandidate = prereleasePackageNames.has("*")
  303. ? () => true
  304. : (node, name) => prereleasePackageNames.has(name);
  305. return collectPackages(this.packageGraph, { isCandidate }).map((pkg) => pkg.name);
  306. }
  307. recommendVersions(resolvePrereleaseId) {
  308. const independentVersions = this.project.isIndependent();
  309. const { changelogPreset, conventionalGraduate } = this.options;
  310. const rootPath = this.project.manifest.location;
  311. const type = independentVersions ? "independent" : "fixed";
  312. const prereleasePackageNames = this.getPrereleasePackageNames();
  313. const graduatePackageNames = Array.from(getPackagesForOption(conventionalGraduate));
  314. const shouldPrerelease = (name) => prereleasePackageNames && prereleasePackageNames.includes(name);
  315. const shouldGraduate = (name) =>
  316. graduatePackageNames.includes("*") || graduatePackageNames.includes(name);
  317. const getPrereleaseId = (node) => {
  318. if (!shouldGraduate(node.name) && (shouldPrerelease(node.name) || node.prereleaseId)) {
  319. return resolvePrereleaseId(node.prereleaseId);
  320. }
  321. };
  322. let chain = Promise.resolve();
  323. if (type === "fixed") {
  324. chain = chain.then(() => this.setGlobalVersionFloor());
  325. }
  326. chain = chain.then(() =>
  327. this.reduceVersions((node) =>
  328. recommendVersion(node, type, {
  329. changelogPreset,
  330. rootPath,
  331. tagPrefix: this.tagPrefix,
  332. prereleaseId: getPrereleaseId(node),
  333. })
  334. )
  335. );
  336. if (type === "fixed") {
  337. chain = chain.then((versions) => {
  338. this.globalVersion = this.setGlobalVersionCeiling(versions);
  339. return versions;
  340. });
  341. }
  342. return chain;
  343. }
  344. setGlobalVersionFloor() {
  345. const globalVersion = this.project.version;
  346. for (const node of this.updates) {
  347. if (semver.lt(node.version, globalVersion)) {
  348. this.logger.verbose(
  349. "version",
  350. `Overriding version of ${node.name} from ${node.version} to ${globalVersion}`
  351. );
  352. node.pkg.set("version", globalVersion);
  353. }
  354. }
  355. }
  356. setGlobalVersionCeiling(versions) {
  357. let highestVersion = this.project.version;
  358. versions.forEach((bump) => {
  359. if (bump && semver.gt(bump, highestVersion)) {
  360. highestVersion = bump;
  361. }
  362. });
  363. versions.forEach((_, name) => versions.set(name, highestVersion));
  364. return highestVersion;
  365. }
  366. setUpdatesForVersions(versions) {
  367. if (this.project.isIndependent() || versions.size === this.packageGraph.size) {
  368. // only partial fixed versions need to be checked
  369. this.updatesVersions = versions;
  370. } else {
  371. let hasBreakingChange;
  372. for (const [name, bump] of versions) {
  373. hasBreakingChange = hasBreakingChange || isBreakingChange(this.packageGraph.get(name).version, bump);
  374. }
  375. if (hasBreakingChange) {
  376. // _all_ packages need a major version bump whenever _any_ package does
  377. this.updates = Array.from(this.packageGraph.values());
  378. // --no-private completely removes private packages from consideration
  379. if (this.options.private === false) {
  380. // TODO: (major) make --no-private the default
  381. this.updates = this.updates.filter((node) => !node.pkg.private);
  382. }
  383. this.updatesVersions = new Map(this.updates.map((node) => [node.name, this.globalVersion]));
  384. } else {
  385. this.updatesVersions = versions;
  386. }
  387. }
  388. this.packagesToVersion = this.updates.map((node) => node.pkg);
  389. }
  390. confirmVersions() {
  391. const changes = this.packagesToVersion.map((pkg) => {
  392. let line = ` - ${pkg.name}: ${pkg.version} => ${this.updatesVersions.get(pkg.name)}`;
  393. if (pkg.private) {
  394. line += ` (${chalk.red("private")})`;
  395. }
  396. return line;
  397. });
  398. output("");
  399. output("Changes:");
  400. output(changes.join(os.EOL));
  401. output("");
  402. if (this.options.yes) {
  403. this.logger.info("auto-confirmed");
  404. return true;
  405. }
  406. // When composed from `lerna publish`, use this opportunity to confirm publishing
  407. const message = this.composed
  408. ? "Are you sure you want to publish these packages?"
  409. : "Are you sure you want to create these versions?";
  410. return promptConfirmation(message);
  411. }
  412. updatePackageVersions() {
  413. const { conventionalCommits, changelogPreset, changelog = true } = this.options;
  414. const independentVersions = this.project.isIndependent();
  415. const rootPath = this.project.manifest.location;
  416. const changedFiles = new Set();
  417. // my kingdom for async await :(
  418. let chain = Promise.resolve();
  419. // preversion: Run BEFORE bumping the package version.
  420. // version: Run AFTER bumping the package version, but BEFORE commit.
  421. // postversion: Run AFTER bumping the package version, and AFTER commit.
  422. // @see https://docs.npmjs.com/misc/scripts
  423. if (!this.hasRootedLeaf) {
  424. // exec preversion lifecycle in root (before all updates)
  425. chain = chain.then(() => this.runRootLifecycle("preversion"));
  426. }
  427. const actions = [
  428. (pkg) => this.runPackageLifecycle(pkg, "preversion").then(() => pkg),
  429. // manifest may be mutated by any previous lifecycle
  430. (pkg) => pkg.refresh(),
  431. (pkg) => {
  432. // set new version
  433. pkg.set("version", this.updatesVersions.get(pkg.name));
  434. // update pkg dependencies
  435. for (const [depName, resolved] of this.packageGraph.get(pkg.name).localDependencies) {
  436. const depVersion = this.updatesVersions.get(depName);
  437. if (depVersion && resolved.type !== "directory") {
  438. // don't overwrite local file: specifiers, they only change during publish
  439. pkg.updateLocalDependency(resolved, depVersion, this.savePrefix);
  440. }
  441. }
  442. return Promise.all([updateLockfileVersion(pkg), pkg.serialize()]).then(([lockfilePath]) => {
  443. // commit the updated manifest
  444. changedFiles.add(pkg.manifestLocation);
  445. if (lockfilePath) {
  446. changedFiles.add(lockfilePath);
  447. }
  448. return pkg;
  449. });
  450. },
  451. (pkg) => this.runPackageLifecycle(pkg, "version").then(() => pkg),
  452. ];
  453. if (conventionalCommits && changelog) {
  454. // we can now generate the Changelog, based on the
  455. // the updated version that we're about to release.
  456. const type = independentVersions ? "independent" : "fixed";
  457. actions.push((pkg) =>
  458. updateChangelog(pkg, type, {
  459. changelogPreset,
  460. rootPath,
  461. tagPrefix: this.tagPrefix,
  462. }).then(({ logPath, newEntry }) => {
  463. // commit the updated changelog
  464. changedFiles.add(logPath);
  465. // add release notes
  466. if (independentVersions) {
  467. this.releaseNotes.push({
  468. name: pkg.name,
  469. notes: newEntry,
  470. });
  471. }
  472. return pkg;
  473. })
  474. );
  475. }
  476. const mapUpdate = pPipe(...actions);
  477. chain = chain.then(() =>
  478. runTopologically(this.packagesToVersion, mapUpdate, {
  479. concurrency: this.concurrency,
  480. rejectCycles: this.options.rejectCycles,
  481. })
  482. );
  483. if (!independentVersions) {
  484. this.project.version = this.globalVersion;
  485. if (conventionalCommits && changelog) {
  486. chain = chain.then(() =>
  487. updateChangelog(this.project.manifest, "root", {
  488. changelogPreset,
  489. rootPath,
  490. tagPrefix: this.tagPrefix,
  491. version: this.globalVersion,
  492. }).then(({ logPath, newEntry }) => {
  493. // commit the updated changelog
  494. changedFiles.add(logPath);
  495. // add release notes
  496. this.releaseNotes.push({
  497. name: "fixed",
  498. notes: newEntry,
  499. });
  500. })
  501. );
  502. }
  503. chain = chain.then(() =>
  504. this.project.serializeConfig().then((lernaConfigLocation) => {
  505. // commit the version update
  506. changedFiles.add(lernaConfigLocation);
  507. })
  508. );
  509. }
  510. if (!this.hasRootedLeaf) {
  511. // exec version lifecycle in root (after all updates)
  512. chain = chain.then(() => this.runRootLifecycle("version"));
  513. }
  514. if (this.commitAndTag) {
  515. chain = chain.then(() => gitAdd(Array.from(changedFiles), this.gitOpts, this.execOpts));
  516. }
  517. return chain;
  518. }
  519. commitAndTagUpdates() {
  520. let chain = Promise.resolve();
  521. if (this.project.isIndependent()) {
  522. chain = chain.then(() => this.gitCommitAndTagVersionForUpdates());
  523. } else {
  524. chain = chain.then(() => this.gitCommitAndTagVersion());
  525. }
  526. chain = chain.then((tags) => {
  527. this.tags = tags;
  528. });
  529. // run the postversion script for each update
  530. chain = chain.then(() =>
  531. pMap(this.packagesToVersion, (pkg) => this.runPackageLifecycle(pkg, "postversion"))
  532. );
  533. if (!this.hasRootedLeaf) {
  534. // run postversion, if set, in the root directory
  535. chain = chain.then(() => this.runRootLifecycle("postversion"));
  536. }
  537. return chain;
  538. }
  539. gitCommitAndTagVersionForUpdates() {
  540. const tags = this.packagesToVersion.map((pkg) => `${pkg.name}@${this.updatesVersions.get(pkg.name)}`);
  541. const subject = this.options.message || "Publish";
  542. const message = tags.reduce((msg, tag) => `${msg}${os.EOL} - ${tag}`, `${subject}${os.EOL}`);
  543. return Promise.resolve()
  544. .then(() => gitCommit(message, this.gitOpts, this.execOpts))
  545. .then(() => Promise.all(tags.map((tag) => gitTag(tag, this.gitOpts, this.execOpts))))
  546. .then(() => tags);
  547. }
  548. gitCommitAndTagVersion() {
  549. const version = this.globalVersion;
  550. const tag = `${this.tagPrefix}${version}`;
  551. const message = this.options.message
  552. ? this.options.message.replace(/%s/g, tag).replace(/%v/g, version)
  553. : tag;
  554. return Promise.resolve()
  555. .then(() => gitCommit(message, this.gitOpts, this.execOpts))
  556. .then(() => gitTag(tag, this.gitOpts, this.execOpts))
  557. .then(() => [tag]);
  558. }
  559. gitPushToRemote() {
  560. this.logger.info("git", "Pushing tags...");
  561. return gitPush(this.gitRemote, this.currentBranch, this.execOpts);
  562. }
  563. }
  564. module.exports.VersionCommand = VersionCommand;