From 123e6d025f1bc03049864c36a2323980ade93de0 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jul 2019 15:26:04 -0400 Subject: [PATCH 1/5] update chokidar to v3 --- package-lock.json | 626 +++++++--------------------------------------- package.json | 2 +- 2 files changed, 92 insertions(+), 536 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16156b50d4d0..41977c904656 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4277,28 +4277,91 @@ "dev": true }, "chokidar": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", - "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "requires": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "fsevents": "^2.0.6", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "readdirp": "^3.1.1" }, "dependencies": { + "anymatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", + "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } } } }, @@ -7132,491 +7195,10 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "optional": true - } - } + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "optional": true }, "function-bind": { "version": "1.1.1", @@ -11426,6 +11008,11 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -12213,37 +11800,11 @@ } }, "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz", + "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==", "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "picomatch": "^2.0.4" } }, "recast": { @@ -14633,11 +14194,6 @@ "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", "dev": true }, - "upath": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", - "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==" - }, "update-notifier": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", diff --git a/package.json b/package.json index ffa2f07ddbd3..be8a50399bdd 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "bit-javascript": "^2.1.0", "bluebird": "^3.5.3", "chalk": "^2.4.1", - "chokidar": "^2.1.6", + "chokidar": "^3.0.2", "cli-spinners": "^1.0.0", "commander": "^2.12.2", "comment-json": "^1.1.3", From 54b58495bb3c5da5764c63cff9e1ca210d5783e1 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jul 2019 15:38:56 -0400 Subject: [PATCH 2/5] exit with error when watch fails, add infrastructure for testing bit-watch command --- e2e/commands/watch.e2e.2.js | 34 ++++++++++++++++++++ e2e/e2e-helper.js | 35 +++++++++++++++++++-- src/api/consumer/lib/watch.js | 58 +++++++++++++++++++---------------- 3 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 e2e/commands/watch.e2e.2.js diff --git a/e2e/commands/watch.e2e.2.js b/e2e/commands/watch.e2e.2.js new file mode 100644 index 000000000000..1c3deeeec0c1 --- /dev/null +++ b/e2e/commands/watch.e2e.2.js @@ -0,0 +1,34 @@ +import { expect } from 'chai'; +import Helper from '../e2e-helper'; +import * as fixtures from '../fixtures/fixtures'; + +describe('bit watch command', function () { + this.timeout(0); + const helper = new Helper(); + after(() => { + helper.destroyEnv(); + }); + describe('watch', () => { + let watchProcess; + before(async () => { + helper.setNewLocalAndRemoteScopes(); + helper.populateWorkspaceWithComponents(); + helper.importDummyCompiler(); + helper.build(); + watchProcess = await helper.watch(); + }); + after(() => { + watchProcess.kill(); + }); + describe('changing a file', () => { + before(() => { + helper.createFile('utils', 'is-string.js', fixtures.isStringV2); + }); + it('should update the dist', async () => { + await helper.waitForWatchToRebuildComponent(watchProcess); + const distContent = helper.readFile('dist/utils/is-string.js'); + expect(distContent).to.equal(fixtures.isStringV2); + }); + }); + }); +}); diff --git a/e2e/e2e-helper.js b/e2e/e2e-helper.js index 489476826c4e..3055d36f6133 100644 --- a/e2e/e2e-helper.js +++ b/e2e/e2e-helper.js @@ -5,7 +5,7 @@ import chalk from 'chalk'; import glob from 'glob'; import os from 'os'; import path from 'path'; -import childProcess from 'child_process'; +import childProcess, { ChildProcess } from 'child_process'; import fs from 'fs-extra'; import json from 'comment-json'; import { expect } from 'chai'; @@ -53,7 +53,7 @@ export default class Helper { } // #region General - runCmd(cmd: string, cwd: string = this.localScopePath) { + runCmd(cmd: string, cwd: string = this.localScopePath): string { if (this.debugMode) console.log(rightpad(chalk.green('cwd: '), 20, ' '), cwd); // eslint-disable-line if (cmd.startsWith('bit ')) cmd = cmd.replace('bit', this.bitBin); if (this.debugMode) console.log(rightpad(chalk.green('command: '), 20, ' '), cmd); // eslint-disable-line @@ -62,6 +62,37 @@ export default class Helper { return cmdOutput.toString(); } + watch(): Promise { + const cmd = `${this.bitBin} watch`; + if (this.debugMode) console.log(rightpad(chalk.green('command: '), 20, ' '), cmd); // eslint-disable-line + return new Promise((resolve, reject) => { + const watchProcess = childProcess.exec(cmd, { cwd: this.localScopePath, detached: true }); + watchProcess.stdout.on('data', (data) => { + if (this.debugMode) console.log(`stdout: ${data}`); + if (data.includes('Ready for changes')) { + if (this.debugMode) console.log('bit watch is up and running'); + resolve(watchProcess); + } + }); + watchProcess.stderr.on('data', (data) => { + if (this.debugMode) console.log(`stderr: ${data}`); + reject(data); + }); + watchProcess.on('close', (code) => { + if (this.debugMode) console.log(`child process exited with code ${code}`); + }); + }); + } + async waitForWatchToRebuildComponent(watchProcess: ChildProcess) { + return new Promise((resolve) => { + watchProcess.stdout.on('data', (data) => { + if (data.includes('watching for changes')) { + resolve(); + } + }); + }); + } + parseOptions(options: Object): string { const value = Object.keys(options) .map((key) => { diff --git a/src/api/consumer/lib/watch.js b/src/api/consumer/lib/watch.js index 1298b9acdc07..afcc97e40a68 100644 --- a/src/api/consumer/lib/watch.js +++ b/src/api/consumer/lib/watch.js @@ -48,35 +48,38 @@ export default (async function watchAll(verbose: boolean) { } const log = console.log.bind(console); // eslint-disable-line no-console - // prefix your command with "BIT_LOG=*" to see all watch events - if (process.env.BIT_LOG) { - watcher.on('all', (event, path) => { - log(event, path); - }); - } - - watcher.on('ready', () => { - log(chalk.yellow('Initial scan complete. Ready for changes')); - // if (verbose) { - // const watchedPaths = watcher.getWatched(); - // console.log('watchedPaths', watchedPaths) - // } - }); - watcher.on('change', (p) => { - log(`File ${p} has been changed, calling build`); - _handleChange(); - }); - watcher.on('add', (p) => { - log(`File ${p} has been added`); - _handleChange(); - }); - watcher.on('unlink', (p) => { - log(`File ${p} has been removed`); - _handleChange(); + return new Promise((resolve, reject) => { + // prefix your command with "BIT_LOG=*" to see all watch events + if (process.env.BIT_LOG) { + watcher.on('all', (event, path) => { + log(event, path); + }); + } + watcher.on('ready', () => { + log(chalk.yellow('Initial scan complete. Ready for changes')); + // if (verbose) { + // const watchedPaths = watcher.getWatched(); + // console.log('watchedPaths', watchedPaths) + // } + }); + watcher.on('change', (p) => { + log(`File ${p} has been changed, calling build`); + _handleChange().catch(err => reject(err)); + }); + watcher.on('add', (p) => { + log(`File ${p} has been added`); + _handleChange().catch(err => reject(err)); + }); + watcher.on('unlink', (p) => { + log(`File ${p} has been removed`); + _handleChange().catch(err => reject(err)); + }); + watcher.on('error', (err) => { + log(`Watcher error ${err}`); + reject(err); + }); }); - - return new Promise(() => {}); }); async function _handleChange() { @@ -90,5 +93,6 @@ async function _handleChange() { }) .catch((err) => { console.log(err); // eslint-disable-line + throw err; }); } From 6311d2a77f471043051936360079acf3319c73d3 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jul 2019 16:06:49 -0400 Subject: [PATCH 3/5] improve the "verbose" flag to show the watched component names --- src/api/consumer/lib/watch.js | 31 +++++++++++++++-------- src/consumer/component/components-list.js | 19 -------------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/api/consumer/lib/watch.js b/src/api/consumer/lib/watch.js index afcc97e40a68..e8ec2f4d9a8b 100644 --- a/src/api/consumer/lib/watch.js +++ b/src/api/consumer/lib/watch.js @@ -1,11 +1,12 @@ /** @flow */ import chokidar from 'chokidar'; +import R from 'ramda'; import chalk from 'chalk'; import { loadConsumer } from '../../../consumer'; import { buildAll } from '../lib/build'; -import ComponentsList from '../../../consumer/component/components-list'; import loader from '../../../cli/loader'; +import Consumer from '../../../consumer/consumer'; /** * Watch all components specified in bit.map. @@ -18,10 +19,8 @@ import loader from '../../../cli/loader'; export default (async function watchAll(verbose: boolean) { // TODO: run build in the beginning of process (it's work like this in other envs) const consumer = await loadConsumer(); - const componentsList = new ComponentsList(consumer); - const bitMapComponentsPaths = componentsList.getPathsToWatchForAllComponents(undefined, true); - // const watcher = chokidar.watch(bitMapComponentsPaths, { - const watcher = chokidar.watch(bitMapComponentsPaths, { + const pathsToWatch = _getPathsToWatch(consumer, verbose); + const watcher = chokidar.watch(pathsToWatch, { ignoreInitial: true, // Using the function way since the regular way not working as expected // It might be solved when upgrading to chokidar > 3.0.0 @@ -40,12 +39,7 @@ export default (async function watchAll(verbose: boolean) { useFsEvents: false }); - console.log(chalk.yellow('Starting watch for changes')); // eslint-disable-line no-console - - if (verbose) { - // Print all watched paths - bitMapComponentsPaths.forEach(path => console.log(`Watching ${path}`)); // eslint-disable-line no-console - } + console.log(chalk.yellow('start watching for changes')); // eslint-disable-line no-console const log = console.log.bind(console); // eslint-disable-line no-console @@ -96,3 +90,18 @@ async function _handleChange() { throw err; }); } + +function _getPathsToWatch(consumer: Consumer, verbose: boolean): string[] { + const componentsFromBitMap = consumer.bitMap.getAllComponents(); + const pathsToWatch = Object.keys(componentsFromBitMap).map((componentId) => { + const componentMap = componentsFromBitMap[componentId]; + const trackDir = componentMap.getTrackDir(); + const relativePaths = trackDir ? [trackDir] : componentMap.getFilesRelativeToConsumer(); + const absPaths = relativePaths.map(relativePath => consumer.toAbsolutePath(relativePath)); + if (verbose) { + console.log(`watching ${chalk.bold(componentId)}\n${absPaths.join('\n')}`); // eslint-disable-line no-console + } + return absPaths; + }); + return R.flatten(pathsToWatch); +} diff --git a/src/consumer/component/components-list.js b/src/consumer/component/components-list.js index 99b275de703d..7a116b929faa 100644 --- a/src/consumer/component/components-list.js +++ b/src/consumer/component/components-list.js @@ -296,25 +296,6 @@ export default class ComponentsList { return res; } - getPathsToWatchForAllComponents(origin?: string, absolute: boolean = false): string[] { - // TODO: maybe cache this as well - const componentsFromBitMap = this.bitMap.getAllComponents(origin); - const res = []; - const getPaths = (agg, isAbsolute) => (componentMap, componentId) => { - const trackDir = componentMap.getTrackDir(); - const relativePaths = trackDir ? [trackDir] : componentMap.getFilesRelativeToConsumer(); - if (!isAbsolute) { - agg.push(...relativePaths); - return; - } - const consumerPath = this.consumer.getPath(); - const absPaths = relativePaths.map(relativePath => path.join(consumerPath, relativePath)); - agg.push(...absPaths); - }; - R.forEachObjIndexed(getPaths(res, absolute), componentsFromBitMap); - return res; - } - /** * get called when the Consumer is available, shows also components from remote scopes */ From c1794907781bdce202f2647f606658a0d993d1ce Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jul 2019 16:58:10 -0400 Subject: [PATCH 4/5] build only the modified component and not all of them. also, fix #1634, improve the bit-watch output --- src/api/consumer/lib/watch.js | 130 +++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 58 deletions(-) diff --git a/src/api/consumer/lib/watch.js b/src/api/consumer/lib/watch.js index e8ec2f4d9a8b..3d10cfd9f45f 100644 --- a/src/api/consumer/lib/watch.js +++ b/src/api/consumer/lib/watch.js @@ -1,10 +1,11 @@ /** @flow */ +/* eslint no-console: 0 */ import chokidar from 'chokidar'; import R from 'ramda'; import chalk from 'chalk'; import { loadConsumer } from '../../../consumer'; -import { buildAll } from '../lib/build'; +import { build } from '../lib/build'; import loader from '../../../cli/loader'; import Consumer from '../../../consumer/consumer'; @@ -19,29 +20,10 @@ import Consumer from '../../../consumer/consumer'; export default (async function watchAll(verbose: boolean) { // TODO: run build in the beginning of process (it's work like this in other envs) const consumer = await loadConsumer(); - const pathsToWatch = _getPathsToWatch(consumer, verbose); - const watcher = chokidar.watch(pathsToWatch, { - ignoreInitial: true, - // Using the function way since the regular way not working as expected - // It might be solved when upgrading to chokidar > 3.0.0 - // See: - // https://github.com/paulmillr/chokidar/issues/773 - // https://github.com/paulmillr/chokidar/issues/492 - // https://github.com/paulmillr/chokidar/issues/724 - ignored: (path) => { - // Ignore package.json temporarily since it cerates endless loop since it's re-written after each build - if (path.includes('dist') || path.includes('node_modules') || path.includes('package.json')) { - return true; - } - return false; - }, - persistent: true, - useFsEvents: false - }); + const watcher = _getWatcher(); - console.log(chalk.yellow('start watching for changes')); // eslint-disable-line no-console - - const log = console.log.bind(console); // eslint-disable-line no-console + console.log(chalk.yellow('started watching for component changes to rebuild')); + const log = console.log.bind(console); return new Promise((resolve, reject) => { // prefix your command with "BIT_LOG=*" to see all watch events @@ -52,56 +34,88 @@ export default (async function watchAll(verbose: boolean) { } watcher.on('ready', () => { log(chalk.yellow('Initial scan complete. Ready for changes')); - // if (verbose) { - // const watchedPaths = watcher.getWatched(); - // console.log('watchedPaths', watchedPaths) - // } }); watcher.on('change', (p) => { log(`File ${p} has been changed, calling build`); - _handleChange().catch(err => reject(err)); + _handleChange(p).catch(err => reject(err)); }); watcher.on('add', (p) => { log(`File ${p} has been added`); - _handleChange().catch(err => reject(err)); + _handleChange(p, true).catch(err => reject(err)); }); watcher.on('unlink', (p) => { log(`File ${p} has been removed`); - _handleChange().catch(err => reject(err)); + _handleChange(p).catch(err => reject(err)); }); watcher.on('error', (err) => { log(`Watcher error ${err}`); reject(err); }); }); -}); -async function _handleChange() { - loadConsumer.cache = null; - // TODO: Make sure the log for build is printed to console - buildAll(false, false) - .then((buildResult) => { - console.log(buildResult); // eslint-disable-line no-console - loader.stop(); - console.log(chalk.yellow('watching for changes')); // eslint-disable-line no-console - }) - .catch((err) => { - console.log(err); // eslint-disable-line - throw err; + async function _handleChange(filePath: string, isNew: boolean = false) { + const relativeFile = consumer.getPathRelativeToConsumer(filePath); + let componentId = consumer.bitMap.getComponentIdByPath(relativeFile); + if (!isNew && !componentId) { + log(`file ${filePath} is not part of any component, ignoring it`); + return; + } + const updatedConsumer = await loadConsumer(undefined, true); + if (!componentId) { + componentId = updatedConsumer.bitMap.getComponentIdByPath(relativeFile); + } + if (!componentId) { + log(`file ${filePath} is not part of any component, ignoring it`); + return; + } + const idStr = componentId.toString(); + console.log(`running build for ${chalk.bold(idStr)}`); + // TODO: Make sure the log for build is printed to console + const buildResults = await build(idStr, false, verbose); + if (buildResults) { + console.log(`\t${buildResults.join('\n\t')}`); + } else { + console.log(`${idStr} doesn't have a compiler, nothing to build`); + } + + loader.stop(); + console.log(chalk.yellow('watching for changes')); + } + + function _getWatcher() { + const pathsToWatch = _getPathsToWatch(); + return chokidar.watch(pathsToWatch, { + ignoreInitial: true, + // Using the function way since the regular way not working as expected + // It might be solved when upgrading to chokidar > 3.0.0 + // See: + // https://github.com/paulmillr/chokidar/issues/773 + // https://github.com/paulmillr/chokidar/issues/492 + // https://github.com/paulmillr/chokidar/issues/724 + ignored: (path) => { + // Ignore package.json temporarily since it cerates endless loop since it's re-written after each build + if (path.includes('dist') || path.includes('node_modules') || path.includes('package.json')) { + return true; + } + return false; + }, + persistent: true, + useFsEvents: false }); -} + } -function _getPathsToWatch(consumer: Consumer, verbose: boolean): string[] { - const componentsFromBitMap = consumer.bitMap.getAllComponents(); - const pathsToWatch = Object.keys(componentsFromBitMap).map((componentId) => { - const componentMap = componentsFromBitMap[componentId]; - const trackDir = componentMap.getTrackDir(); - const relativePaths = trackDir ? [trackDir] : componentMap.getFilesRelativeToConsumer(); - const absPaths = relativePaths.map(relativePath => consumer.toAbsolutePath(relativePath)); - if (verbose) { - console.log(`watching ${chalk.bold(componentId)}\n${absPaths.join('\n')}`); // eslint-disable-line no-console - } - return absPaths; - }); - return R.flatten(pathsToWatch); -} + function _getPathsToWatch(): string[] { + const componentsFromBitMap = consumer.bitMap.getAllComponents(); + const paths = Object.keys(componentsFromBitMap).map((componentId) => { + const componentMap = componentsFromBitMap[componentId]; + const trackDir = componentMap.getTrackDir(); + const relativePaths = trackDir ? [trackDir] : componentMap.getFilesRelativeToConsumer(); + const absPaths = relativePaths.map(relativePath => consumer.toAbsolutePath(relativePath)); + if (verbose) { + console.log(`watching ${chalk.bold(componentId)}\n${absPaths.join('\n')}`); + } + return absPaths; + }); + return R.flatten(paths); + } +}); From f85917182697658f541b3fc46ad3983256827c1c Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 22 Jul 2019 20:31:36 -0400 Subject: [PATCH 5/5] fix the case of adding new files to a component, update CHANGELOG, refactor the code of watch: move it to a new class outside the lib directory --- CHANGELOG.md | 2 + e2e/commands/watch.e2e.2.js | 71 +++++++-- e2e/e2e-helper.js | 9 +- e2e/fixtures/fixtures.js | 2 + src/api/consumer/lib/watch.js | 112 +------------- .../component-ops/watch-components.js | 144 ++++++++++++++++++ 6 files changed, 214 insertions(+), 126 deletions(-) create mode 100644 src/consumer/component-ops/watch-components.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 72cf20766221..9ad2af7b80f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +- build only the component of the modified/added/removed file upon `bit watch` +- [#1634](https://github.com/teambit/bit/issues/1634) improve the output of `bit watch` - [#1668](https://github.com/teambit/bit/issues/1668) bug fix - `bit watch` doesn't update files - improve `bit watch` to watch directories instead of only files to support addition / deletion diff --git a/e2e/commands/watch.e2e.2.js b/e2e/commands/watch.e2e.2.js index 1c3deeeec0c1..3db14fe19d46 100644 --- a/e2e/commands/watch.e2e.2.js +++ b/e2e/commands/watch.e2e.2.js @@ -1,7 +1,10 @@ -import { expect } from 'chai'; +import chai, { expect } from 'chai'; +import path from 'path'; import Helper from '../e2e-helper'; import * as fixtures from '../fixtures/fixtures'; +chai.use(require('chai-fs')); + describe('bit watch command', function () { this.timeout(0); const helper = new Helper(); @@ -9,25 +12,67 @@ describe('bit watch command', function () { helper.destroyEnv(); }); describe('watch', () => { - let watchProcess; - before(async () => { + let scopeAfterBuild; + before(() => { helper.setNewLocalAndRemoteScopes(); helper.populateWorkspaceWithComponents(); helper.importDummyCompiler(); helper.build(); - watchProcess = await helper.watch(); + scopeAfterBuild = helper.cloneLocalScope(); }); - after(() => { - watchProcess.kill(); + describe('as author', () => { + let watchProcess; + before(async () => { + watchProcess = await helper.watch(); + }); + after(() => { + watchProcess.kill(); + }); + describe('changing a file', () => { + before(() => { + helper.createFile('utils', 'is-string.js', fixtures.isStringV2); + }); + it('should update the dist', async () => { + await helper.waitForWatchToRebuildComponent(watchProcess); + const distContent = helper.readFile('dist/utils/is-string.js'); + expect(distContent).to.equal(fixtures.isStringV2); + }); + describe('changing it again', () => { + before(() => { + helper.createFile('utils', 'is-string.js', fixtures.isStringV3); + }); + it('should update the dist again', async () => { + await helper.waitForWatchToRebuildComponent(watchProcess); + const distContent = helper.readFile('dist/utils/is-string.js'); + expect(distContent).to.equal(fixtures.isStringV3); + }); + }); + }); }); - describe('changing a file', () => { - before(() => { - helper.createFile('utils', 'is-string.js', fixtures.isStringV2); + describe('as imported', () => { + let watchProcess; + before(async () => { + helper.getClonedLocalScope(scopeAfterBuild); + helper.tagAllComponents(); + helper.exportAllComponents(); + helper.reInitLocalScope(); + helper.addRemoteScope(); + helper.addRemoteEnvironment(); + helper.importManyComponents(['bar/foo', 'utils/is-string', 'utils/is-type']); + watchProcess = await helper.watch(); + }); + after(() => { + watchProcess.kill(); }); - it('should update the dist', async () => { - await helper.waitForWatchToRebuildComponent(watchProcess); - const distContent = helper.readFile('dist/utils/is-string.js'); - expect(distContent).to.equal(fixtures.isStringV2); + describe('adding a file to a tracked directory', () => { + before(() => { + helper.outputFile('components/utils/is-string/new-file.js', 'console.log();'); + }); + it('should create a dist file for that new file', async () => { + await helper.waitForWatchToRebuildComponent(watchProcess); + const expectedFile = path.join(helper.localScopePath, 'components/utils/is-string/dist/new-file.js'); + expect(expectedFile).to.be.a.file(); + }); }); }); }); diff --git a/e2e/e2e-helper.js b/e2e/e2e-helper.js index 3055d36f6133..53000b7b5ef1 100644 --- a/e2e/e2e-helper.js +++ b/e2e/e2e-helper.js @@ -16,6 +16,7 @@ import * as fixtures from './fixtures/fixtures'; import { NOTHING_TO_TAG_MSG } from '../src/cli/commands/public-cmds/tag-cmd'; import { removeChalkCharacters } from '../src/utils'; import { FileStatus } from '../src/consumer/versions-ops/merge-version'; +import { STARTED_WATCHING_MSG, WATCHER_COMPLETE_BUILD_MSG } from '../src/consumer/component-ops/watch-components'; const generateRandomStr = (size: number = 8): string => { return Math.random() @@ -63,13 +64,13 @@ export default class Helper { } watch(): Promise { - const cmd = `${this.bitBin} watch`; + const cmd = `${this.bitBin} watch --verbose`; if (this.debugMode) console.log(rightpad(chalk.green('command: '), 20, ' '), cmd); // eslint-disable-line return new Promise((resolve, reject) => { const watchProcess = childProcess.exec(cmd, { cwd: this.localScopePath, detached: true }); watchProcess.stdout.on('data', (data) => { if (this.debugMode) console.log(`stdout: ${data}`); - if (data.includes('Ready for changes')) { + if (data.includes(STARTED_WATCHING_MSG)) { if (this.debugMode) console.log('bit watch is up and running'); resolve(watchProcess); } @@ -86,8 +87,8 @@ export default class Helper { async waitForWatchToRebuildComponent(watchProcess: ChildProcess) { return new Promise((resolve) => { watchProcess.stdout.on('data', (data) => { - if (data.includes('watching for changes')) { - resolve(); + if (data.includes(WATCHER_COMPLETE_BUILD_MSG)) { + resolve(data); } }); }); diff --git a/e2e/fixtures/fixtures.js b/e2e/fixtures/fixtures.js index e47294d7af1d..fe5e3b646b96 100644 --- a/e2e/fixtures/fixtures.js +++ b/e2e/fixtures/fixtures.js @@ -19,6 +19,8 @@ export const isString = "const isType = require('./is-type.js'); module.exports = function isString() { return isType() + ' and got is-string'; };"; export const isStringV2 = "const isType = require('./is-type.js'); module.exports = function isString() { return isType() + ' and got is-string v2'; };"; +export const isStringV3 = + "const isType = require('./is-type.js'); module.exports = function isString() { return isType() + ' and got is-string v3'; };"; export const isStringSpec = testShouldPass => `const expect = require('chai').expect; const isString = require('./is-string.js'); diff --git a/src/api/consumer/lib/watch.js b/src/api/consumer/lib/watch.js index 3d10cfd9f45f..7a3c94aca7cd 100644 --- a/src/api/consumer/lib/watch.js +++ b/src/api/consumer/lib/watch.js @@ -1,13 +1,5 @@ /** @flow */ -/* eslint no-console: 0 */ - -import chokidar from 'chokidar'; -import R from 'ramda'; -import chalk from 'chalk'; -import { loadConsumer } from '../../../consumer'; -import { build } from '../lib/build'; -import loader from '../../../cli/loader'; -import Consumer from '../../../consumer/consumer'; +import WatchComponents from '../../../consumer/component-ops/watch-components'; /** * Watch all components specified in bit.map. @@ -18,104 +10,6 @@ import Consumer from '../../../consumer/consumer'; * @returns */ export default (async function watchAll(verbose: boolean) { - // TODO: run build in the beginning of process (it's work like this in other envs) - const consumer = await loadConsumer(); - const watcher = _getWatcher(); - - console.log(chalk.yellow('started watching for component changes to rebuild')); - const log = console.log.bind(console); - - return new Promise((resolve, reject) => { - // prefix your command with "BIT_LOG=*" to see all watch events - if (process.env.BIT_LOG) { - watcher.on('all', (event, path) => { - log(event, path); - }); - } - watcher.on('ready', () => { - log(chalk.yellow('Initial scan complete. Ready for changes')); - }); - watcher.on('change', (p) => { - log(`File ${p} has been changed, calling build`); - _handleChange(p).catch(err => reject(err)); - }); - watcher.on('add', (p) => { - log(`File ${p} has been added`); - _handleChange(p, true).catch(err => reject(err)); - }); - watcher.on('unlink', (p) => { - log(`File ${p} has been removed`); - _handleChange(p).catch(err => reject(err)); - }); - watcher.on('error', (err) => { - log(`Watcher error ${err}`); - reject(err); - }); - }); - - async function _handleChange(filePath: string, isNew: boolean = false) { - const relativeFile = consumer.getPathRelativeToConsumer(filePath); - let componentId = consumer.bitMap.getComponentIdByPath(relativeFile); - if (!isNew && !componentId) { - log(`file ${filePath} is not part of any component, ignoring it`); - return; - } - const updatedConsumer = await loadConsumer(undefined, true); - if (!componentId) { - componentId = updatedConsumer.bitMap.getComponentIdByPath(relativeFile); - } - if (!componentId) { - log(`file ${filePath} is not part of any component, ignoring it`); - return; - } - const idStr = componentId.toString(); - console.log(`running build for ${chalk.bold(idStr)}`); - // TODO: Make sure the log for build is printed to console - const buildResults = await build(idStr, false, verbose); - if (buildResults) { - console.log(`\t${buildResults.join('\n\t')}`); - } else { - console.log(`${idStr} doesn't have a compiler, nothing to build`); - } - - loader.stop(); - console.log(chalk.yellow('watching for changes')); - } - - function _getWatcher() { - const pathsToWatch = _getPathsToWatch(); - return chokidar.watch(pathsToWatch, { - ignoreInitial: true, - // Using the function way since the regular way not working as expected - // It might be solved when upgrading to chokidar > 3.0.0 - // See: - // https://github.com/paulmillr/chokidar/issues/773 - // https://github.com/paulmillr/chokidar/issues/492 - // https://github.com/paulmillr/chokidar/issues/724 - ignored: (path) => { - // Ignore package.json temporarily since it cerates endless loop since it's re-written after each build - if (path.includes('dist') || path.includes('node_modules') || path.includes('package.json')) { - return true; - } - return false; - }, - persistent: true, - useFsEvents: false - }); - } - - function _getPathsToWatch(): string[] { - const componentsFromBitMap = consumer.bitMap.getAllComponents(); - const paths = Object.keys(componentsFromBitMap).map((componentId) => { - const componentMap = componentsFromBitMap[componentId]; - const trackDir = componentMap.getTrackDir(); - const relativePaths = trackDir ? [trackDir] : componentMap.getFilesRelativeToConsumer(); - const absPaths = relativePaths.map(relativePath => consumer.toAbsolutePath(relativePath)); - if (verbose) { - console.log(`watching ${chalk.bold(componentId)}\n${absPaths.join('\n')}`); - } - return absPaths; - }); - return R.flatten(paths); - } + const watchComponent = new WatchComponents(verbose); + return watchComponent.watchAll(); }); diff --git a/src/consumer/component-ops/watch-components.js b/src/consumer/component-ops/watch-components.js new file mode 100644 index 000000000000..78c47dd1c645 --- /dev/null +++ b/src/consumer/component-ops/watch-components.js @@ -0,0 +1,144 @@ +// @flow +/* eslint no-console: 0 */ +import chokidar from 'chokidar'; +import R from 'ramda'; +import chalk from 'chalk'; +import Consumer from '../consumer'; +import { loadConsumer } from '..'; +import { build } from '../../api/consumer'; +import loader from '../../cli/loader'; +import { BitId } from '../../bit-id'; +import { BIT_VERSION } from '../../constants'; + +export const STARTED_WATCHING_MSG = 'started watching for component changes to rebuild'; +export const WATCHER_COMPLETE_BUILD_MSG = 'watching for changes'; + +export default class WatchComponents { + consumer: Consumer; + verbose: boolean; + trackDirs: { [dir: string]: string } = {}; // dir => component-id + + constructor(verbose: boolean) { + this.verbose = verbose; + } + + async watchAll() { + // TODO: run build in the beginning of process (it's work like this in other envs) + this.consumer = await loadConsumer(); + const watcher = this._getWatcher(); + console.log(chalk.yellow(`bit binary version: ${BIT_VERSION}`)); + console.log(chalk.yellow(`node version: ${process.version}`)); + const log = console.log.bind(console); + + return new Promise((resolve, reject) => { + // prefix your command with "BIT_LOG=*" to see all watch events + if (process.env.BIT_LOG) { + watcher.on('all', (event, path) => { + log(event, path); + }); + } + watcher.on('ready', () => { + log(chalk.yellow(STARTED_WATCHING_MSG)); + }); + watcher.on('change', (p) => { + log(`file ${p} has been changed`); + this._handleChange(p).catch(err => reject(err)); + }); + watcher.on('add', (p) => { + log(`file ${p} has been added`); + this._handleChange(p, true).catch(err => reject(err)); + }); + watcher.on('unlink', (p) => { + log(`file ${p} has been removed`); + this._handleChange(p).catch(err => reject(err)); + }); + watcher.on('error', (err) => { + log(`Watcher error ${err}`); + reject(err); + }); + }); + } + + async _handleChange(filePath: string, isNew: boolean = false) { + const componentId = await this._getBitIdByPathAndReloadConsumer(filePath, isNew); + if (!componentId) { + console.log(`file ${filePath} is not part of any component, ignoring it`); + return; + } + const idStr = componentId.toString(); + console.log(`running build for ${chalk.bold(idStr)}`); + // TODO: Make sure the log for build is printed to console + const buildResults = await build(idStr, false, this.verbose); + if (buildResults) { + console.log(`\t${chalk.cyan(buildResults.join('\n\t'))}`); + } else { + console.log(`${idStr} doesn't have a compiler, nothing to build`); + } + + loader.stop(); + console.log(chalk.yellow(WATCHER_COMPLETE_BUILD_MSG)); + } + + async _getBitIdByPathAndReloadConsumer(filePath: string, isNew: boolean): Promise { + const relativeFile = this.consumer.getPathRelativeToConsumer(filePath); + let componentId = this.consumer.bitMap.getComponentIdByPath(relativeFile); + if (!isNew && !componentId) { + return null; + } + this.consumer = await loadConsumer(undefined, true); + if (!componentId) { + componentId = this.consumer.bitMap.getComponentIdByPath(relativeFile); + } + if (isNew && !componentId) { + const trackDir = Object.keys(this.trackDirs).find(dir => relativeFile.startsWith(dir)); + if (trackDir) { + const id = this.trackDirs[trackDir]; + const bitId = this.consumer.getParsedId(id); + // loading the component causes the bitMap to be updated with the new path + await this.consumer.loadComponent(bitId); + componentId = this.consumer.bitMap.getComponentIdByPath(relativeFile); + } + } + return componentId; + } + + _getWatcher() { + const pathsToWatch = this._getPathsToWatch(); + return chokidar.watch(pathsToWatch, { + ignoreInitial: true, + // Using the function way since the regular way not working as expected + // It might be solved when upgrading to chokidar > 3.0.0 + // See: + // https://github.com/paulmillr/chokidar/issues/773 + // https://github.com/paulmillr/chokidar/issues/492 + // https://github.com/paulmillr/chokidar/issues/724 + ignored: (path) => { + // Ignore package.json temporarily since it cerates endless loop since it's re-written after each build + if (path.includes('dist') || path.includes('node_modules') || path.includes('package.json')) { + return true; + } + return false; + }, + persistent: true, + useFsEvents: false + }); + } + + _getPathsToWatch(): string[] { + const componentsFromBitMap = this.consumer.bitMap.getAllComponents(); + const paths = Object.keys(componentsFromBitMap).map((componentId) => { + const componentMap = componentsFromBitMap[componentId]; + const trackDir = componentMap.getTrackDir(); + if (trackDir) { + this.trackDirs[trackDir] = componentId; + } + const relativePaths = trackDir ? [trackDir] : componentMap.getFilesRelativeToConsumer(); + const absPaths = relativePaths.map(relativePath => this.consumer.toAbsolutePath(relativePath)); + if (this.verbose) { + console.log(`watching ${chalk.bold(componentId)}\n${absPaths.join('\n')}`); + } + return absPaths; + }); + return R.flatten(paths); + } +}