This commit is contained in:
chrosey
2017-09-13 07:52:34 +02:00
parent a1f16c37f4
commit 2340b0226b
24621 changed files with 2912161 additions and 149 deletions
+356
View File
@@ -0,0 +1,356 @@
<a name="2.8.2"></a>
## 2.8.2 (2015-07-31)
### Bug Fixes
* **https:** add newly generated ssl self-signed certs that will expire for 10 years - fixes ([45104a7](https://github.com/browsersync/browser-sync/commit/45104a7)), closes [#750](https://github.com/browsersync/browser-sync/issues/750)
<a name="2.8.1"></a>
## 2.8.1 (2015-07-28)
### Bug Fixes
* **web-sockets:** Use separate server for web sockets in proxy mode - fixes #625 ([40017b4](https://github.com/browsersync/browser-sync/commit/40017b4)), closes [#625](https://github.com/browsersync/browser-sync/issues/625)
### Features
* **serve-static:** Added option `serveStatic` to allow proxy/snippet mode to easily serve local fil ([384ef67](https://github.com/browsersync/browser-sync/commit/384ef67))
<a name="2.7.13"></a>
## 2.7.13 (2015-06-28)
### Bug Fixes
* **snippet:** Allow async attribute to be removed from snippet with snippetOptions.async = fal ([c32bec6](https://github.com/browsersync/browser-sync/commit/c32bec6)), closes [#670](https://github.com/browsersync/browser-sync/issues/670)
* **socket-options:** allow socket.domain string|fn for setting domain only on socket path - fixes #69 ([5157432](https://github.com/browsersync/browser-sync/commit/5157432)), closes [#690](https://github.com/browsersync/browser-sync/issues/690)
### Features
* **api:** expose sockets to public api ([985682c](https://github.com/browsersync/browser-sync/commit/985682c))
<a name="2.7.12"></a>
## 2.7.12 (2015-06-17)
### Bug Fixes
* **client-script:** allow proxy to also use client script middleware ([c5fdbbf](https://github.com/browsersync/browser-sync/commit/c5fdbbf))
* **client-script:** serve cached/gzipped client JS file - fixes #657 ([dbe9ffe](https://github.com/browsersync/browser-sync/commit/dbe9ffe)), closes [#657](https://github.com/browsersync/browser-sync/issues/657)
<a name"2.7.11"></a>
### 2.7.11 (2015-06-16)
#### Bug Fixes
* **client-scroll:** add scrollRestoreTechnique option as alternative to cookie for restoring scroll ([7897ea6a](https://github.com/Browsersync/browser-sync/commit/7897ea6a), closes [#630](https://github.com/Browsersync/browser-sync/issues/630))
<a name"2.7.9"></a>
### 2.7.9 (2015-06-11)
#### Bug Fixes
* **cli:** Remove --exclude flag - ([133aa1a6](https://github.com/Browsersync/browser-sync/commit/133aa1a6), closes [#667](https://github.com/Browsersync/browser-sync/issues/667))
* **proxy:** only rewrite domains within attributes (via foxy bump to 11.0.2) - ([d80d9481](https://github.com/Browsersync/browser-sync/commit/d80d9481), closes [#647](https://github.com/Browsersync/browser-sync/issues/647))
<a name"2.7.7"></a>
### 2.7.7 (2015-06-09)
#### Bug Fixes
* **plugins:** Allow plugins to register middleware via server:middleware hook when in proxy mo ([104dbb4a](https://github.com/Browsersync/browser-sync/commit/104dbb4a), closes [#663](https://github.com/Browsersync/browser-sync/issues/663))
<a name"2.7.6"></a>
### 2.7.6 (2015-05-28)
#### Bug Fixes
* **plugins:** allow module references in options.plugins array - ([aabc03c8](https://github.com/Browsersync/browser-sync/commit/aabc03c8), closes [#648](https://github.com/Browsersync/browser-sync/issues/648))
<a name"2.7.5"></a>
### 2.7.5 (2015-05-26)
#### Bug Fixes
* **file-watcher:** defer to default callback should `fn` property be absent from file watching obje ([9f826cbe](https://github.com/Browsersync/browser-sync/commit/9f826cbe), closes [#643](https://github.com/Browsersync/browser-sync/issues/643))
<a name"2.7.3"></a>
### 2.7.3 (2015-05-24)
#### Bug Fixes
* **file-watching:** bind public running instance to watch callbacks given in options - ([d7c96e4f](https://github.com/Browsersync/browser-sync/commit/d7c96e4f), closes [#631](https://github.com/Browsersync/browser-sync/issues/631))
* **snippet:** Bump resp-modifier to allow more flexible whitelist/blacklist paths for snippet ([f09c2797](https://github.com/Browsersync/browser-sync/commit/f09c2797), closes [#553](https://github.com/Browsersync/browser-sync/issues/553))
#### Features
* **rewrite-rules:** enable live updating of rewrite rules for both static server & proxy ([a4e2bf6f](https://github.com/Browsersync/browser-sync/commit/a4e2bf6f))
<a name"2.7.1"></a>
### 2.7.1 (2015-05-06)
#### Bug Fixes
* **web-sockets:** revert handling upgrade event on proxy as it causes regression fix #606 ([1c6b1c03](https://github.com/Browsersync/browser-sync/commit/1c6b1c03))
<a name"2.6.8"></a>
### 2.6.8 (2015-04-29)
#### Bug Fixes
* **cli:** Allow absolute paths for config file - ([8fcd9048](https://github.com/Browsersync/browser-sync/commit/8fcd9048), closes [#583](https://github.com/Browsersync/browser-sync/issues/583))
<a name"2.6.5"></a>
### 2.6.5 (2015-04-25)
#### Bug Fixes
* **file-watching:** use canLogFileChange() to determine whether file:reload, stream:changed & browse ([164154ea](https://github.com/Browsersync/browser-sync/commit/164154ea), closes [#479](https://github.com/Browsersync/browser-sync/issues/479))
<a name"2.6.1"></a>
### 2.6.1 (2015-04-13)
#### Bug Fixes
* **stream:** Allow deprecated .reload({stream: true}) when no instance running, closes [#573](https://github.com/Browsersync/browser-sync/issues/573)
<a name"2.6.0"></a>
## 2.6.0 (2015-04-12)
#### Bug Fixes
* **open:** Allow open: 'ui' and open: 'ui-external' when in snippet mode - ([d0333582](https://github.com/Browsersync/browser-sync/commit/d0333582), closes [#571](https://github.com/Browsersync/browser-sync/issues/571))
* **server:** set index correctly if serveStaticOptions: {index: <path>} given ([34816a76](https://github.com/Browsersync/browser-sync/commit/34816a76))
#### Features
* **cli:** allow 'browser' option from cli - ([ca517d03](https://github.com/Browsersync/browser-sync/commit/ca517d03), closes [#552](https://github.com/Browsersync/browser-sync/issues/552))
* **client:** Bump client to allow wildcards in reload method - ([1e4de8f7](https://github.com/Browsersync/browser-sync/commit/1e4de8f7), closes [#572](https://github.com/Browsersync/browser-sync/issues/572))
* **commands:** Add reload command for http-protocol comms ([c0fe70dc](https://github.com/Browsersync/browser-sync/commit/c0fe70dc))
* **file-watcher:** Add `.watch()` to public api ([6a2609f0](https://github.com/Browsersync/browser-sync/commit/6a2609f0))
* **http-protocol:**
* Add support for https comms ([efd4f39c](https://github.com/Browsersync/browser-sync/commit/efd4f39c))
* Add reload method to http protocol ([f6a3601f](https://github.com/Browsersync/browser-sync/commit/f6a3601f))
* **plugins:** Accept object literal as plugin + options ([757f492e](https://github.com/Browsersync/browser-sync/commit/757f492e))
* **reload:** Add reload-delay and reload-debounce to cli -, ([38d62c96](https://github.com/Browsersync/browser-sync/commit/38d62c96), closes [#329](https://github.com/Browsersync/browser-sync/issues/329), [#562](https://github.com/Browsersync/browser-sync/issues/562))
* **stream:** Implement dedicated `.stream()` method for better handling streams & to pave the ([2581e7a1](https://github.com/Browsersync/browser-sync/commit/2581e7a1))
* **watchers:**
* Allow per-watcher options hash. ([3c069fba](https://github.com/Browsersync/browser-sync/commit/3c069fba))
* switch to chokidar for file-watching, implement callback interface on per-patter ([14afddfc](https://github.com/Browsersync/browser-sync/commit/14afddfc))
<a name"2.5.1"></a>
### 2.5.1 (2015-03-31)
#### Bug Fixes
* **proxy:** Bump foxy dep to ensure middlewares work correctly for old IEs ([104e9dd1](https://github.com/Browsersync/browser-sync/commit/104e9dd1))
* **snippet:** Log UI access urls when in snippet mode ([c448fa0b](https://github.com/Browsersync/browser-sync/commit/c448fa0b))
<a name"2.5.0"></a>
## 2.5.0 (2015-03-29)
#### Bug Fixes
* **proxy:** Bump Foxy to stop cookies being altered when parsed ([ff3c46bd](https://github.com/Browsersync/browser-sync/commit/ff3c46bd))
#### Features
* **options:** Allow any serve-static specific configuration under new property - ([4c58541f](https://github.com/Browsersync/browser-sync/commit/4c58541f), closes [#539](https://github.com/Browsersync/browser-sync/issues/539))
* **throttle:** Bump UI for network throttle ([7e2f588e](https://github.com/Browsersync/browser-sync/commit/7e2f588e))
<a name"2.4.0"></a>
## 2.4.0 (2015-03-21)
#### Features
* **rewrite:** Allow addtional HTML rewrite rules through server + proxy modes to help with #51 ([76ae686d](https://github.com/Browsersync/browser-sync/commit/76ae686d))
<a name"2.3.2"></a>
### 2.3.2 (2015-03-21)
#### Bug Fixes
* **client:** Bump UI to fix safari deprecated error messages - fix #445 ([6bb7513c](https://github.com/Browsersync/browser-sync/commit/6bb7513c))
<a name"2.2.5"></a>
### 2.2.5 (2015-03-17)
#### Features
* **plugins:** Allow plugins to be given inline within options hash ([fd4ccd9e](https://github.com/Browsersync/browser-sync/commit/fd4ccd9e))
### 2.2.4 (2015-03-13)
#### Bug Fixes
* **reload:** Allow multiple instances to call their own `.reload()` method - ([da53dc21](https://github.com/Browsersync/browser-sync/commit/da53dc21c6f7afd801a9f00489a6df2ab46156bb), closes [#511](https://github.com/Browsersync/browser-sync/issues/511))
### 2.2.3 (2015-03-08)
#### Bug Fixes
* **socket:** Set heartbeat interval correctly - ([7621c0de](https://github.com/Browsersync/browser-sync/commit/7621c0dece1fea6c170ffdc117bd7c67be2138ed), closes [#499](https://github.com/Browsersync/browser-sync/issues/499))
### 2.2.2 (2015-03-04)
#### Bug Fixes
* **paths:** Fix regression with absolute/relative paths to scripts/sockets/https etc - ([2386fe1b](https://github.com/Browsersync/browser-sync/commit/2386fe1bbde175b18211ef9b242b6af0bf11128c), closes [#463](https://github.com/Browsersync/browser-sync/issues/463))
* **snippet:** Allow serving the client js over https when in snippet mode - ([196bafbe](https://github.com/Browsersync/browser-sync/commit/196bafbee2b09c2a1e8b09a988a2c8aa43bac2b9), closes [#459](https://github.com/Browsersync/browser-sync/issues/459))
* **socket:** Bump socket io + client to fix #477 & https://github.com/Browsersync/browser-syn ([659c281e](https://github.com/Browsersync/browser-sync/commit/659c281eac8ab8343a7ba7b13fa532560dc1bd9c))
### 2.1.4 (2015-02-18)
#### Bug Fixes
* **cli:** allow disable injection via cli - ([12ffbd79](https://github.com/Browsersync/browser-sync/commit/12ffbd793443c7ede191aad55bcd530e566f0947), closes [#444](https://github.com/Browsersync/browser-sync/issues/444))
* **snippet:**
* Allow serving the client js over https when in snippet mode - ([196bafbe](https://github.com/Browsersync/browser-sync/commit/196bafbee2b09c2a1e8b09a988a2c8aa43bac2b9), closes [#459](https://github.com/Browsersync/browser-sync/issues/459))
* Allow serving the snippet on secure server + base url - re: #437 ([96d689c0](https://github.com/Browsersync/browser-sync/commit/96d689c0830975dbf2baee5aaaaa396415052512))
* Always use full url path for scripts - ([14bd6f51](https://github.com/Browsersync/browser-sync/commit/14bd6f5126c52228a0ed306a118feac0e65c50db), closes [#437](https://github.com/Browsersync/browser-sync/issues/437))
## 2.1.0 (2015-02-16)
#### Features
* **https:** Add HTTPS proxying - re: #399 ([09dbca6e](https://github.com/Browsersync/browser-sync/commit/09dbca6e3e60fa699ca2519d56ada3cbd5a2237b))
* **proxy:** Allow user-specified proxy request headers ([0c303a7e](https://github.com/Browsersync/browser-sync/commit/0c303a7e4a8bafa554d42c6895698b7338d036f4), closes [#430](https://github.com/Browsersync/browser-sync/issues/430))
### 2.0.1 (2015-02-10)
#### Bug Fixes
* **cli:**
* Ensure server options are merged from command line ([8d677328](https://github.com/Browsersync/browser-sync/commit/8d677328a779502ba6f6e16b74f125dc2caeaf92))
* explode files args when given on command line., ([18324f0a](https://github.com/Browsersync/browser-sync/commit/18324f0a7b4d3c49bd16800a7ba77cf13ea2449a), closes [#425](https://github.com/Browsersync/browser-sync/issues/425), [#426](https://github.com/Browsersync/browser-sync/issues/426))
* Don't double-merge cli options, re: #417 ([057d97f3](https://github.com/Browsersync/browser-sync/commit/057d97f35786f120bc2057c884c80c5ce95aaf79))
* **https:** Ensure HTTPS option is used in legacy mode + top level, re: #427 ([799c0a59](https://github.com/Browsersync/browser-sync/commit/799c0a59cd152eb11e6f8e66a1d3adcf082624f7))
* **proxy:**
* use path as startPath if given as proxy option ([f4ac4c59](https://github.com/Browsersync/browser-sync/commit/f4ac4c595a479b44676824cdbdaa34cc1dc9d966))
* Bump Foxy module to fix issues with redirects, ([e5d8fe18](https://github.com/Browsersync/browser-sync/commit/e5d8fe180bfd46f1380ec1f532d81f62f2f6ab11), closes [#381](https://github.com/Browsersync/browser-sync/issues/381))
* **reload:** Bump browser-sync-client fix ##369 ([9bcf1086](https://github.com/Browsersync/browser-sync/commit/9bcf108694f0e51bafc3bd6d0a584781e2950f68))
* **stream:** Don't log file info when once: true - fixes https://github.com/google/web-starter-kit/issues/593 ([8f4d7275](https://github.com/Browsersync/browser-sync/commit/8f4d7275d4dfa6e22dec4b87d19b3be51bab8af3))
#### Features
* **core:** Use immutable data internally to enable advanced features needed in upcoming UI ([b5d6d9c1](https://github.com/Browsersync/browser-sync/commit/b5d6d9c1866cf8451cf235dc3bca674af9e6d767))
* **options:**
* Allow silent setting of options ([31e196a0](https://github.com/Browsersync/browser-sync/commit/31e196a0e900356cf5cbb9b1e8a4c3202011d01e))
* added reloadOnRestart option - https://github.com/shakyShane/browser-sync/issues ([b1bcfa81](https://github.com/Browsersync/browser-sync/commit/b1bcfa81638b1f99fed7d71ee051c00ceebaf8f9))
* **server:** add serveFile method for plugin use ([c5007871](https://github.com/Browsersync/browser-sync/commit/c50078717f291f3cb301b0bc315eac1b42f6d7b6))
* **snippet:** Add black/white lists - ([6a2a296e](https://github.com/Browsersync/browser-sync/commit/6a2a296ee05312d56de3ae47f5dfb6e04f877692), closes [#373](https://github.com/Browsersync/browser-sync/issues/373))
* **tunnel:** Switch to ngrok - re: #192 ([7359435c](https://github.com/Browsersync/browser-sync/commit/7359435ca4efd429c9421aa912a036f82d022d82))
### 1.8.2 (2014-12-22)
#### Bug Fixes
* **proxy:** Bump foxy to fix #376 ([fe6c73db](https://github.com/shakyShane/browser-sync/commit/fe6c73db47f82d10ea25b0b8c58b032e972a4663))
#### Features
* **server:** allow to inject browser-sync client.js in custom middlewares ([841c6c31](https://github.com/shakyShane/browser-sync/commit/841c6c31015955ff92cffd937f19f2c78ce27e8d))
### 1.8.1 (2014-12-19)
#### Bug Fixes
* **proxy:** Bump foxy to fix #376 ([284cf84a](https://github.com/shakyShane/browser-sync/commit/284cf84a0390a07d9824972c8ab67ec95cf8109f))
### 1.7.3 (2014-12-16)
#### Features
* **files:** pause/resume ([a3c697f6](https://github.com/shakyShane/browser-sync/commit/a3c697f66b4fcec3966ed77a841e55aafb70f69a))
### 1.6.5 (2014-11-16)
#### Bug Fixes
* **snippet:** Add snippet.ignorePaths option - ([dd9b284b](https://github.com/shakyShane/browser-sync/commit/dd9b284b47f01884996619c012f134c982639b8c), closes [#330](https://github.com/shakyShane/browser-sync/issues/330))
#### Features
* **snippet:** Allow user-provided rule for writing the snippet ([33c4586d](https://github.com/shakyShane/browser-sync/commit/33c4586dce26a4c9672b99d14d29adb064dac6ec))
### 1.6.4 (2014-11-08)
#### Bug Fixes
* **proxy:** Bump Foxy to fix issues with redirects ([e2f30be2](https://github.com/shakyShane/browser-sync/commit/e2f30be2269629a96503ea487b5248ab3b6918ab))
### 1.6.2 (2014-11-02)
#### Bug Fixes
* **options:** Ignore cli options from public api usage fix #314 ([1de4a3b0](https://github.com/shakyShane/browser-sync/commit/1de4a3b06cd888345aa5130a03cad070b1f5b466))
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2015] [Shane Osbourne]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+79
View File
@@ -0,0 +1,79 @@
<p align="center">
<a href="https://ci.appveyor.com/project/shakyShane/browser-sync" title="AppVeyor branch">
<img src="https://img.shields.io/appveyor/ci/shakyshane/browser-sync/master.svg?style=flat-square&label=windows" />
</a>
<a href="https://travis-ci.org/BrowserSync/browser-sync" title="Travis branch">
<img src="https://img.shields.io/travis/BrowserSync/browser-sync/master.svg?style=flat-square&label=linux" />
</a>
<a href="https://coveralls.io/r/BrowserSync/browser-sync?branch=master" title="Coverage Status">
<img src="https://img.shields.io/coveralls/BrowserSync/browser-sync.svg?style=flat-square" />
</a>
<a href="https://www.npmjs.com/package/browser-sync">
<img src="https://img.shields.io/npm/dm/browser-sync.svg?style=flat-square" />
</a>
</p>
<p align="center">
<a href="https://www.npmjs.com/package/browser-sync" title="NPM version">
<img src="https://img.shields.io/npm/v/browser-sync.svg?style=flat-square" />
</a>
<a href="https://david-dm.org/Browsersync/browser-sync" title="Dependency Status">
<img src="https://img.shields.io/david/Browsersync/browser-sync.svg?style=flat-square&label=deps" />
</a>
<a href="https://david-dm.org/Browsersync/browser-sync#info=devDependencies" title="devDependency Status">
<img src="https://img.shields.io/david/dev/Browsersync/browser-sync.svg?style=flat-square&label=devDeps" />
</a>
</p>
<p align="center"><a href="http://www.browsersync.io"><img src="https://raw.githubusercontent.com/BrowserSync/browsersync.github.io/master/public/img/logo-gh.png" /></a></p>
<p align="center">Keep multiple browsers & devices in sync when building websites.</p>
<p align="center">Browsersync is developed and maintained internally at <a href="http://www.wearejh.com">JH</a></p>
<p align="center">Follow <a href="https://twitter.com/browsersync">@Browsersync</a> on twitter for news & updates.</p>
<p align="center">Community <a href="https://browsersync.herokuapp.com"><img src="https://browsersync.herokuapp.com/badge.svg" /></a></p>
## Features
Please visit [browsersync.io](http://browsersync.io) for a full run-down of features
## Requirements
Browsersync works by injecting an asynchronous script tag (`<script async>...</script>`) right after the `<body>` tag
during initial request. In order for this to work properly the `<body>` tag must be present. Alternatively you
can provide a custom rule for the snippet using [snippetOptions](http://www.browsersync.io/docs/options/#option-snippetOptions)
## Upgrading from 1.x to 2.x ?
Providing you haven't accessed any internal properties, everything will just work as
there are no breaking changes to the public API. Internally however, we now use an
immutable data structure for storing/retrieving options. So whereas before you could access urls like this...
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.urls.local);
});
```
... you now access them in the following way:
```js
browserSync({server: true}, function(err, bs) {
console.log(bs.options.getIn(["urls", "local"]));
});
```
## Install and trouble shooting
[browsersync.io docs](http://browsersync.io)
## Integrations / recipes
[Browsersync recipes](https://github.com/Browsersync/recipes)
## Support
If you've found Browser-sync useful and would like to contribute to its continued development & support, please feel free to send a donation of any size - it would be greatly appreciated!
[![Support via Gittip](https://rawgithub.com/chris---/Donation-Badges/master/gittip.jpeg)](https://www.gittip.com/shakyshane)
[![Support via PayPal](https://rawgithub.com/chris---/Donation-Badges/master/paypal.jpeg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=shakyshane%40gmail%2ecom&lc=US&item_name=browser%2dsync)
Apache 2
Copyright (c) 2016 Shane Osbourne
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env node
var startOpts = require("../lib/cli/opts.start.json");
var reloadOpts = require("../lib/cli/opts.reload.json");
var recipeOpts = require("../lib/cli/opts.recipe.json");
var pkg = require("../package.json");
var utils = require("../lib/utils");
/**
* Handle cli input
*/
if (!module.parent) {
var yargs = require("yargs")
.command("start", "Start the server")
.command("init", "Create a configuration file")
.command("reload", "Send a reload event over HTTP protocol")
.command("recipe", "Generate the files for a recipe")
.version(function () {
return pkg.version;
})
.epilogue("For help running a certain command, type <command> --help\neg: $0 start --help");
var argv = yargs.argv;
var command = argv._[0];
var valid = ["start", "init", "reload", "recipe"];
if (valid.indexOf(command) > -1) {
handleIncoming(command, yargs.reset());
} else {
yargs.showHelp();
}
}
/**
* @param {{cli: object, [whitelist]: array, [cb]: function}} opts
* @returns {*}
*/
function handleCli(opts) {
opts.cb = opts.cb || utils.defaultCallback;
return require("../lib/cli/command." + opts.cli.input[0])(opts);
}
module.exports = handleCli;
/**
* @param {string} command
* @param {object} yargs
*/
function handleIncoming(command, yargs) {
var out;
if (command === "start") {
out = yargs
.usage("Usage: $0 start [options]")
.options(startOpts)
.example("$0 start -s app", "- Use the App directory to serve files")
.example("$0 start -p www.bbc.co.uk", "- Proxy an existing website")
.help()
.argv;
}
if (command === "init") {
out = yargs
.usage("Usage: $0 init")
.example("$0 init")
.help()
.argv;
}
if (command === "reload") {
out = yargs
.usage("Usage: $0 reload")
.options(reloadOpts)
.example("$0 reload")
.example("$0 reload --port 4000")
.help()
.argv;
}
if (command === "recipe") {
out = yargs
.usage("Usage: $0 recipe <recipe-name>")
.option(recipeOpts)
.example("$0 recipe ls", "list the recipes")
.example("$0 recipe gulp.sass", "use the gulp.sass recipe")
.help()
.argv;
}
if (out.help) {
return yargs.showHelp();
}
handleCli({cli: {flags: out, input: out._}});
}
+372
View File
@@ -0,0 +1,372 @@
#! /usr/bin/env node
"use strict";
/**
* @module BrowserSync
*/
var pjson = require("./package.json");
var BrowserSync = require("./lib/browser-sync");
var publicUtils = require("./lib/public/public-utils");
var events = require("events");
var PassThrough = require("stream").PassThrough;
var logger = require("eazy-logger").Logger({
useLevelPrefixes: true
});
var singleton = false;
var singletonPlugins = [];
var instances = [];
/**
* @type {boolean|EventEmitter}
*/
var singletonEmitter = false;
module.exports = initSingleton;
/**
* Create a Browsersync instance
* @method create
* @param {String} name an identifier that can used for retrieval later
*/
module.exports.create = create;
/**
* Get a single instance by name. This is useful if you have your
* build scripts in separate files
* @method get
* @param {String} name
* @returns {Object|Boolean}
*/
module.exports.get = function (name) {
var instance = getSingle(name);
if (instance) {
return instance;
}
throw new Error("An instance with the name `%s` was not found.".replace("%s", name));
};
/**
* Check if an instance has been created.
* @method has
* @param {String} name
* @returns {Boolean}
*/
module.exports.has = function (name) {
var instance = getSingle(name);
if (instance) {
return true;
}
return false;
};
/**
* Start the Browsersync service. This will launch a server, proxy or start the snippet
* mode depending on your use-case.
* @method init
* @param {Object} [config] This is the main configuration for your Browsersync instance and can contain any of the [available options]({{site.links.options}})
* If you do not pass a config an argument for configuration, Browsersync will still run; but it will be in the `snippet` mode
* @param {Function} [cb] If you pass a callback function, it will be called when Browsersync has completed all setup tasks and is ready to use. This
* is useful when you need to wait for information (for example: urls, port etc) or perform other tasks synchronously.
* @returns {BrowserSync}
*/
module.exports.init = initSingleton;
/**
* Register a plugin. Must implement at least a 'plugin' method that returns a
* callable function.
*
* @method use
* @param {String} name The name of the plugin
* @param {Object} module The object to be `required`.
* @param {Function} [cb] A callback function that will return any errors.
*/
module.exports.use = function () {
var args = Array.prototype.slice.call(arguments);
singletonPlugins.push({
args: args
});
};
/**
* The `reload` method will inform all browsers about changed files and will either cause the browser to refresh, or inject the files where possible.
*
* @method reload
* @param {String|Array|Object} [arg] The file or files to be reloaded.
* @returns {*}
*/
module.exports.reload = noop("reload");
/**
* The `stream` method returns a transform stream and can act once or on many files.
*
* @method stream
* @param {Object} [opts] Configuration for the stream method
* @since 2.6.0
* @returns {*}
*/
module.exports.stream = noop("stream");
/**
* Helper method for browser notifications
*
* @method notify
* @param {String|HTML} msg Can be a simple message such as 'Connected' or HTML
* @param {Number} [timeout] How long the message will remain in the browser. @since 1.3.0
*/
module.exports.notify = noop("notify");
/**
* This method will close any running server, stop file watching & exit the current process.
*
* @method exit
*/
module.exports.exit = noop("exit");
/**
* Stand alone file-watcher. Use this along with Browsersync to create your own, minimal build system
* @method watch
* @param {string} patterns Glob patterns for files to watch
* @param {object} [opts] Options to be passed to Chokidar - check what's available in [their docs](https://github.com/paulmillr/chokidar#getting-started)
* @param {function} [fn] Callback function for each event.
* @since 2.6.0
*/
module.exports.watch = noop("watch");
/**
* Method to pause file change events
*
* @method pause
*/
module.exports.pause = noop("pause");
/**
* Method to resume paused watchers
*
* @method resume
*/
module.exports.resume = noop("resume");
/**
* Add properties fo
*/
Object.defineProperties(module.exports, {
/**
* The internal Event Emitter used by the running Browsersync instance (if there is one).
* You can use this to emit your own events, such as changed files, logging etc.
*
* @property emitter
*/
"emitter": {
get: function () {
if (!singletonEmitter) {
singletonEmitter = newEmitter();
return singletonEmitter;
}
return singletonEmitter;
}
},
/**
* A simple true/false flag that you can use to determine if there's a currently-running Browsersync instance.
*
* @property active
*/
"active": {
get: getSingletonValue.bind(null, "active")
},
/**
* A simple true/false flag to determine if the current instance is paused
*
* @property paused
*/
"paused": {
get: getSingletonValue.bind(null, "paused")
}
});
/**
* Event emitter factory
* @returns {EventEmitter}
*/
function newEmitter() {
var emitter = new events.EventEmitter();
emitter.setMaxListeners(20);
return emitter;
}
/**
* Get the singleton's emitter, or a new one.
* @returns {EventEmitter}
*/
function getSingletonEmitter() {
if (singletonEmitter) {
return singletonEmitter;
}
singletonEmitter = newEmitter();
return singletonEmitter;
}
/**
* Helper to allow methods to be called on the module export
* before there's a running instance
* @param {String} name
* @returns {Function}
*/
function noop(name) {
return function () {
var args = Array.prototype.slice.call(arguments);
if (singleton) {
return singleton[name].apply(singleton, args);
} else {
if (publicUtils.isStreamArg(name, args)) {
return new PassThrough({objectMode: true});
}
}
};
}
/**
* Create a single instance when module export is used directly via browserSync({});
* This is mostly for back-compatibility, for also for the nicer api.
* This will never be removed to ensure we never break user-land, but
* we should discourage it's use.
* @returns {*}
*/
function initSingleton() {
var instance;
if (instances.length) {
instance = instances.filter(function (item) {
return item.name === "singleton";
});
if (instance.length) {
logger.error("{yellow:You tried to start Browsersync twice!} To create multiple instances, use {cyan:browserSync.create().init()");
return instance;
}
}
var args = Array.prototype.slice.call(arguments);
singleton = create("singleton", getSingletonEmitter());
if (singletonPlugins.length) {
singletonPlugins.forEach(function (obj) {
singleton.instance.registerPlugin.apply(singleton.instance, obj.args);
});
}
singleton.init.apply(null, args);
return singleton;
}
/**
* @param {String} prop
* @returns {Object|Boolean}
*/
function getSingletonValue(prop) {
var single = getSingle("singleton");
if (single) {
return single[prop];
}
return false;
}
/**
* Get a single instance by name
* @param {String} name
* @returns {Object|Boolean}
*/
function getSingle(name) {
if (instances.length) {
var match = instances.filter(function (item) {
return item.name === name;
});
if (match.length) {
return match[0];
}
}
return false;
}
/**
* Create an instance of Browsersync
* @param {String} [name]
* @param {EventEmitter} [emitter]
* @returns {{init: *, exit: (exit|exports), notify: *, reload: *, cleanup: *, emitter: (Browsersync.events|*), use: *}}
*/
function create(name, emitter) {
name = name || new Date().getTime();
emitter = emitter || newEmitter();
var browserSync = new BrowserSync(emitter);
var instance = {
name: name,
instance: browserSync,
exit: require("./lib/public/exit")(browserSync),
notify: require("./lib/public/notify")(browserSync),
pause: require("./lib/public/pause")(browserSync),
resume: require("./lib/public/resume")(browserSync),
reload: require("./lib/public/reload")(emitter),
stream: require("./lib/public/stream")(emitter),
cleanup: browserSync.cleanup.bind(browserSync),
use: browserSync.registerPlugin.bind(browserSync),
getOption: browserSync.getOption.bind(browserSync),
emitter: browserSync.events,
watch: require("./lib/file-watcher").watch
};
browserSync.publicInstance = instance;
instance.init = require("./lib/public/init")(browserSync, name, pjson);
Object.defineProperty(instance, "active", {
get: function () {
return browserSync.active;
}
});
Object.defineProperty(instance, "paused", {
get: function () {
return browserSync.paused;
}
});
/**
* Access to client-side socket for emitting events
*
* @property sockets
*/
Object.defineProperty(instance, "sockets", {
get: function () {
if (!browserSync.active) {
return {
emit: function () {},
on: function () {}
};
} else {
return browserSync.io.sockets;
}
}
});
instances.push(instance);
return instance;
}
/**
* Reset the state of the module.
* (should only be needed for test environments)
*/
module.exports.reset = function () {
instances.forEach(function (item) {
item.cleanup();
});
instances = [];
singletonPlugins = [];
singletonEmitter = false;
singleton = false;
};
/**
* @type {Array}
*/
module.exports.instances = instances;
+86
View File
@@ -0,0 +1,86 @@
"use strict";
/**
* The purpose of this function is
* to handle back-backwards compatibility
* @param {Object} args
* @returns {{config: {}, cb: *}}
*/
module.exports = function (args) {
var config = {};
var cb;
switch (args.length) {
case 1 :
if (isFilesArg(args[0])) {
config.files = args[0];
} else if (typeof args[0] === "function") {
cb = args[0];
} else {
config = args[0];
}
break;
case 2 :
// if second is a function, first MUST be config
if (typeof args[1] === "function") {
config = args[0] || {};
cb = args[1];
} else {
if (args[1] === null || args[1] === undefined) {
config = args[0];
} else {
// finally, second arg could be a plain object for config
config = args[1] || {};
if (!config.files) {
config.files = args[0];
}
}
}
break;
case 3 :
config = args[1] || {};
if (!config.files) {
config.files = args[0];
}
cb = args[2];
}
return {
config: config,
cb: cb
};
};
/**
* Files args were only ever strings or arrays
* @param arg
* @returns {*|boolean}
*/
function isFilesArg (arg) {
return Array.isArray(arg) || typeof arg === "string";
}
+60
View File
@@ -0,0 +1,60 @@
var async = require("./async");
module.exports = [
{
step: "Finding an empty port",
fn: async.getEmptyPort
},
{
step: "Getting an extra port for Proxy",
fn: async.getExtraPortForProxy
},
{
step: "Checking online status",
fn: async.getOnlineStatus
},
{
step: "Resolve user plugins from options",
fn: async.resolveInlineUserPlugins
},
{
step: "Set Urls and other options that rely on port/online status",
fn: async.setOptions
},
{
step: "Setting Internal Events",
fn: async.setInternalEvents
},
{
step: "Setting file watchers",
fn: async.setFileWatchers
},
{
step: "Merging middlewares from core + plugins",
fn: async.mergeMiddlewares
},
{
step: "Starting the Server",
fn: async.startServer
},
{
step: "Starting the HTTPS Tunnel",
fn: async.startTunnel
},
{
step: "Starting the web-socket server",
fn: async.startSockets
},
{
step: "Starting the UI",
fn: async.startUi
},
{
step: "Merge UI settings",
fn: async.mergeUiSettings
},
{
step: "Init user plugins",
fn: async.initUserPlugins
}
];
+323
View File
@@ -0,0 +1,323 @@
"use strict";
var _ = require("../lodash.custom");
var Immutable = require("immutable");
var utils = require("./utils");
var pluginUtils = require("./plugins");
var connectUtils = require("./connect-utils");
module.exports = {
/**
* BrowserSync needs at least 1 free port.
* It will check the one provided in config
* and keep incrementing until an available one is found.
* @param {BrowserSync} bs
* @param {Function} done
*/
getEmptyPort: function (bs, done) {
utils.getPorts(bs.options, function (err, port) {
if (err) {
return utils.fail(true, err, bs.cb);
}
bs.debug("Found a free port: {magenta:%s", port);
done(null, {
options: {
port: port
}
});
});
},
/**
* If the running mode is proxy, we'll use a separate port
* for the Browsersync web-socket server. This is to eliminate any issues
* with trying to proxy web sockets through to the users server.
* @param bs
* @param done
*/
getExtraPortForProxy: function (bs, done) {
/**
* An extra port is not needed in snippet/server mode
*/
if (bs.options.get("mode") !== "proxy") {
return done();
}
/**
* Web socket support is disabled by default
*/
if (!bs.options.getIn(["proxy", "ws"])) {
return done();
}
/**
* Use 1 higher than server port by default...
*/
var socketPort = bs.options.get("port") + 1;
/**
* Or use the user-defined socket.port option instead
*/
if (bs.options.hasIn(["socket", "port"])) {
socketPort = bs.options.getIn(["socket", "port"]);
}
utils.getPort(socketPort, null, function (err, port) {
if (err) {
return utils.fail(true, err, bs.cb);
}
done(null, {
optionsIn: [
{
path: ["socket", "port"],
value: port
}
]
});
});
},
/**
* Some features require an internet connection.
* If the user did not provide either `true` or `false`
* for the online option, we will attempt to resolve www.google.com
* as a way of determining network connectivity
* @param {BrowserSync} bs
* @param {Function} done
*/
getOnlineStatus: function (bs, done) {
if (_.isUndefined(bs.options.get("online")) && _.isUndefined(process.env.TESTING)) {
require("dns").resolve("www.google.com", function (err) {
var online = false;
if (err) {
bs.debug("Could not resolve www.google.com, setting {magenta:online: false}");
} else {
bs.debug("Resolved www.google.com, setting {magenta:online: true}");
online = true;
}
done(null, {
options: {
online: online
}
});
});
} else {
done();
}
},
/**
* Try to load plugins that were given in options
* @param {BrowserSync} bs
* @param {Function} done
*/
resolveInlineUserPlugins: function (bs, done) {
var plugins = bs.options
.get("plugins")
.map(pluginUtils.resolvePlugin)
.map(pluginUtils.requirePlugin);
plugins
.forEach(function (plugin) {
if (plugin.get("errors").size) {
return logPluginError(plugin);
}
var jsPlugin = plugin.toJS();
jsPlugin.options = jsPlugin.options || {};
jsPlugin.options.moduleName = jsPlugin.moduleName;
bs.registerPlugin(jsPlugin.module, jsPlugin.options);
});
function logPluginError (plugin) {
utils.fail(true, plugin.getIn(["errors", 0]), bs.cb);
}
done();
},
/**
*
* @param {BrowserSync} bs
* @param {Function} done
*/
setOptions: function (bs, done) {
done(null, {
options: {
urls: utils.getUrlOptions(bs.options),
snippet: connectUtils.scriptTags(bs.options),
scriptPaths: Immutable.fromJS(connectUtils.clientScript(bs.options, true)),
files: bs.pluginManager.hook(
"files:watch",
bs.options.get("files"),
bs.pluginManager.pluginOptions
)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
setInternalEvents: function (bs, done) {
require("./internal-events")(bs);
done();
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
setFileWatchers: function (bs, done) {
done(null, {
instance: {
watchers: bs.pluginManager.get("file:watcher")(bs)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
mergeMiddlewares: function (bs, done) {
done(null, {
options: {
middleware: bs.pluginManager.hook(
"server:middleware",
bs.options.get("middleware")
)
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startServer: function (bs, done) {
var server = bs.pluginManager.get("server")(bs);
done(null, {
instance: {
server: server.server,
app: server.app
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startTunnel: function (bs, done) {
if (bs.options.get("tunnel") && bs.options.get("online")) {
var localTunnel = require("./tunnel");
localTunnel(bs, function (err, tunnel) {
if (err) {
return done(err);
} else {
return done(null, {
optionsIn: [
{
path: ["urls", "tunnel"],
value: tunnel.url
}
],
instance: {
tunnel: tunnel
}
});
}
});
} else {
done();
}
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
startSockets: function (bs, done) {
var clientEvents = bs.pluginManager.hook(
"client:events",
bs.options.get("clientEvents").toJS()
);
// Start the socket, needs an existing server.
var io = bs.pluginManager.get("socket")(
bs.server,
clientEvents,
bs
);
done(null, {
instance: {
io: io
},
options: {
clientEvents: Immutable.fromJS(clientEvents)
}
});
},
/**
*
* @param {BrowserSync} bs
* @param {Function} done
*/
startUi: function (bs, done) {
var PLUGIN_NAME = "UI";
var userPlugins = bs.getUserPlugins();
var ui = bs.pluginManager.get(PLUGIN_NAME);
var uiOpts = bs.options.get("ui");
if (!uiOpts || uiOpts.get("enabled") === false) {
return done();
}
// if user provided a UI, use it instead
if (userPlugins.some(function (item) {
return item.name === PLUGIN_NAME;
})) {
uiOpts = bs.options.get("ui").mergeDeep(Immutable.fromJS(bs.pluginManager.pluginOptions[PLUGIN_NAME]));
}
return ui(uiOpts.toJS(), bs, function (err, ui) {
if (err) {
return done(err);
}
done(null, {
instance: {
ui: ui
}
});
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
mergeUiSettings: function (bs, done) {
if (!bs.ui) {
return done();
}
done(null, {
options: {
urls: bs.options.get("urls").merge(bs.ui.options.get("urls"))
}
});
},
/**
* @param {BrowserSync} bs
* @param {Function} done
*/
initUserPlugins: function (bs, done) {
bs.pluginManager.initUserPlugins(bs);
done(null, {
options: {
userPlugins: bs.getUserPlugins()
}
});
}
};
+696
View File
@@ -0,0 +1,696 @@
"use strict";
var hooks = require("./hooks");
var asyncTasks = require("./async-tasks");
var config = require("./config");
var connectUtils = require("./connect-utils");
var utils = require("./utils");
var logger = require("./logger");
var eachSeries = utils.eachSeries;
var _ = require("../lodash.custom");
var EE = require("easy-extender");
/**
* Required internal plugins.
* Any of these can be overridden by deliberately
* causing a name-clash.
*/
var defaultPlugins = {
"logger": logger,
"socket": require("./sockets"),
"file:watcher": require("./file-watcher"),
"server": require("./server"),
"tunnel": require("./tunnel"),
"client:script": require("browser-sync-client"),
"UI": require("browser-sync-ui")
};
/**
* @constructor
*/
var BrowserSync = function (emitter) {
var bs = this;
bs.cwd = process.cwd();
bs.active = false;
bs.paused = false;
bs.config = config;
bs.utils = utils;
bs.events = bs.emitter = emitter;
bs._userPlugins = [];
bs._reloadQueue = [];
bs._cleanupTasks = [];
bs._browserReload = false;
// Plugin management
bs.pluginManager = new EE(defaultPlugins, hooks);
};
/**
* Call a user-options provided callback
* @param name
*/
BrowserSync.prototype.callback = function (name) {
var bs = this;
var cb = bs.options.getIn(["callbacks", name]);
if (_.isFunction(cb)) {
cb.apply(bs.publicInstance, _.toArray(arguments).slice(1));
}
};
/**
* @param {Map} options
* @param {Function} cb
* @returns {BrowserSync}
*/
BrowserSync.prototype.init = function (options, cb) {
/**
* Safer access to `this`
* @type {BrowserSync}
*/
var bs = this;
/**
* Set user-provided callback, or assign a noop
* @type {Function}
*/
bs.cb = cb || utils.defaultCallback;
/**
* Verify provided config.
* Some options are not compatible and will cause us to
* end the process.
*/
if (!utils.verifyConfig(options, bs.cb)) {
return;
}
/**
* Save a reference to the original options
* @type {Map}
* @private
*/
bs._options = options;
/**
* Set additional options that depend on what the
* user may of provided
* @type {Map}
*/
bs.options = require("./options").update(options);
/**
* Kick off default plugins.
*/
bs.pluginManager.init();
/**
* Create a base logger & debugger.
*/
bs.logger = bs.pluginManager.get("logger")(bs.events, bs);
bs.debugger = bs.logger.clone({useLevelPrefixes: true});
bs.debug = bs.debugger.debug;
/**
* Run each setup task in sequence
*/
eachSeries(
asyncTasks,
taskRunner(bs),
tasksComplete(bs)
);
return this;
};
/**
* Run 1 setup task.
* Each task is a pure function.
* They can return options or instance properties to set,
* but they cannot set them directly.
* @param {BrowserSync} bs
* @returns {Function}
*/
function taskRunner (bs) {
return function (item, cb) {
bs.debug("-> {yellow:Starting Step: " + item.step);
/**
* Execute the current task.
*/
item.fn(bs, executeTask);
function executeTask(err, out) {
/**
* Exit early if any task returned an error.
*/
if (err) {
return cb(err);
}
/**
* Act on return values (such as options to be set,
* or instance properties to be set
*/
if (out) {
handleOut(bs, out);
}
bs.debug("+ {green:Step Complete: " + item.step);
cb();
}
};
}
/**
* @param bs
* @param out
*/
function handleOut (bs, out) {
/**
* Set a single/many option.
*/
if (out.options) {
setOptions(bs, out.options);
}
/**
* Any options returned that require path access?
*/
if (out.optionsIn) {
out.optionsIn.forEach(function (item) {
bs.setOptionIn(item.path, item.value);
});
}
/**
* Any instance properties returned?
*/
if (out.instance) {
Object.keys(out.instance).forEach(function (key) {
bs[key] = out.instance[key];
});
}
}
/**
* Update the options Map
* @param bs
* @param options
*/
function setOptions (bs, options) {
/**
* If multiple options were set, act on the immutable map
* in an efficient way
*/
if (Object.keys(options).length > 1) {
bs.setMany(function (item) {
Object.keys(options).forEach(function (key) {
item.set(key, options[key]);
return item;
});
});
} else {
Object.keys(options).forEach(function (key) {
bs.setOption(key, options[key]);
});
}
}
/**
* At this point, ALL async tasks have completed
* @param {BrowserSync} bs
* @returns {Function}
*/
function tasksComplete (bs) {
return function (err) {
if (err) {
bs.logger.setOnce("useLevelPrefixes", true).error(err.message);
}
/**
* Set active flag
*/
bs.active = true;
/**
* @deprecated
*/
bs.events.emit("init", bs);
/**
* This is no-longer needed as the Callback now only resolves
* when everything (including slow things, like the tunnel) is ready.
* It's here purely for backwards compatibility.
* @deprecated
*/
bs.events.emit("service:running", {
options: bs.options,
baseDir: bs.options.getIn(["server", "baseDir"]),
type: bs.options.get("mode"),
port: bs.options.get("port"),
url: bs.options.getIn(["urls", "local"]),
urls: bs.options.get("urls").toJS(),
tunnel: bs.options.getIn(["urls", "tunnel"])
});
/**
* Call any option-provided callbacks
*/
bs.callback("ready", null, bs);
/**
* Finally, call the user-provided callback given as last arg
*/
bs.cb(null, bs);
};
}
/**
* @param module
* @param opts
* @param cb
*/
BrowserSync.prototype.registerPlugin = function (module, opts, cb) {
var bs = this;
bs.pluginManager.registerPlugin(module, opts, cb);
if (module["plugin:name"]) {
bs._userPlugins.push(module);
}
};
/**
* Get a plugin by name
* @param name
*/
BrowserSync.prototype.getUserPlugin = function (name) {
var bs = this;
var items = bs.getUserPlugins(function (item) {
return item["plugin:name"] === name;
});
if (items && items.length) {
return items[0];
}
return false;
};
/**
* @param {Function} [filter]
*/
BrowserSync.prototype.getUserPlugins = function (filter) {
var bs = this;
filter = filter || function () {
return true;
};
/**
* Transform Plugins option
*/
bs.userPlugins = bs._userPlugins.filter(filter).map(function (plugin) {
return {
name: plugin["plugin:name"],
active: plugin._enabled,
opts: bs.pluginManager.pluginOptions[plugin["plugin:name"]]
};
});
return bs.userPlugins;
};
/**
* Get middleware
* @returns {*}
*/
BrowserSync.prototype.getMiddleware = function (type) {
var types = {
"connector": connectUtils.socketConnector(this.options),
"socket-js": require("./snippet").utils.getSocketScript()
};
if (type in types) {
return function (req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(types[type]);
};
}
};
/**
* Shortcut for pushing a file-serving middleware
* onto the stack
* @param {String} path
* @param {{type: string, content: string}} props
*/
var _serveFileCount = 0;
BrowserSync.prototype.serveFile = function (path, props) {
var bs = this;
var mode = bs.options.get("mode");
var entry = {
handle: function (req, res) {
res.setHeader("Content-Type", props.type);
res.end(props.content);
},
id: "Browsersync - " + _serveFileCount++,
route: path
};
bs._addMiddlewareToStack(entry);
};
/**
* Add middlewares on the fly
* @param {{route: string, handle: function, id?: string}}
*/
BrowserSync.prototype._addMiddlewareToStack = function (entry) {
var bs = this;
if (bs.options.get("mode") === "proxy") {
bs.app.stack.splice(bs.app.stack.length-1, 0, entry);
} else {
bs.app.stack.push(entry);
}
};
var _addMiddlewareCount = 0;
BrowserSync.prototype.addMiddleware = function (route, handle, opts) {
var bs = this;
if (!bs.app) {
return;
}
opts = opts || {};
if (!opts.id) {
opts.id = "bs-mw-" + _addMiddlewareCount++;
}
if (route === "*") {
route = "";
}
var entry = {
id: opts.id,
route: route,
handle: handle
};
if (opts.override) {
entry.override = true;
}
bs.options = bs.options.update("middleware", function (mw) {
if (bs.options.get("mode") === "proxy") {
return mw.insert(mw.size-1, entry);
}
return mw.concat(entry);
});
bs.resetMiddlewareStack();
};
/**
* Remove middlewares on the fly
* @param {String} id
* @returns {Server}
*/
BrowserSync.prototype.removeMiddleware = function (id) {
var bs = this;
if (!bs.app) {
return;
}
bs.options = bs.options.update("middleware", function (mw) {
return mw.filter(function (mw) {
return mw.id !== id;
});
});
bs.resetMiddlewareStack();
};
/**
* Middleware for socket connection (external usage)
* @param opts
* @returns {*}
*/
BrowserSync.prototype.getSocketConnector = function (opts) {
var bs = this;
return function (req, res) {
res.setHeader("Content-Type", "text/javascript");
res.end(bs.getExternalSocketConnector(opts));
};
};
/**
* Socket connector as a string
* @param {Object} opts
* @returns {*}
*/
BrowserSync.prototype.getExternalSocketConnector = function (opts) {
var bs = this;
return connectUtils.socketConnector(
bs.options.withMutations(function (item) {
item.set("socket", item.get("socket").merge(opts));
if (!bs.options.getIn(["proxy", "ws"])) {
item.set("mode", "snippet");
}
})
);
};
/**
* Socket io as string (for embedding)
* @returns {*}
*/
BrowserSync.prototype.getSocketIoScript = function () {
return require("./snippet").utils.getSocketScript();
};
/**
* Callback helper
* @param name
*/
BrowserSync.prototype.getOption = function (name) {
this.debug("Getting option: {magenta:%s", name);
return this.options.get(name);
};
/**
* Callback helper
* @param path
*/
BrowserSync.prototype.getOptionIn = function (path) {
this.debug("Getting option via path: {magenta:%s", path);
return this.options.getIn(path);
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getOptions = function () {
return this.options;
};
/**
* @returns {BrowserSync.options}
*/
BrowserSync.prototype.getLogger = logger.getLogger;
/**
* @param {String} name
* @param {*} value
* @returns {BrowserSync.options|*}
*/
BrowserSync.prototype.setOption = function (name, value, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting Option: {cyan:%s} - {magenta:%s", name, value.toString());
bs.options = bs.options.set(name, value);
if (!opts.silent) {
bs.events.emit("options:set", {path: name, value: value, options: bs.options});
}
return this.options;
};
/**
* @param path
* @param value
* @param opts
* @returns {Map|*|BrowserSync.options}
*/
BrowserSync.prototype.setOptionIn = function (path, value, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting Option: {cyan:%s} - {magenta:%s", path.join("."), value.toString());
bs.options = bs.options.setIn(path, value);
if (!opts.silent) {
bs.events.emit("options:set", {path: path, value: value, options: bs.options});
}
return bs.options;
};
/**
* Set multiple options with mutations
* @param fn
* @param opts
* @returns {Map|*}
*/
BrowserSync.prototype.setMany = function (fn, opts) {
var bs = this;
opts = opts || {};
bs.debug("Setting multiple Options");
bs.options = bs.options.withMutations(fn);
if (!opts.silent) {
bs.events.emit("options:set", {options: bs.options.toJS()});
}
return this.options;
};
BrowserSync.prototype.addRewriteRule = function (rule) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (rules) {
return rules.concat(rule);
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.removeRewriteRule = function (id) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (rules) {
return rules.filter(function (rule) {
return rule.id !== id;
});
});
bs.resetMiddlewareStack();
};
BrowserSync.prototype.setRewriteRules = function (rules) {
var bs = this;
bs.options = bs.options.update("rewriteRules", function (_) {
return rules;
});
bs.resetMiddlewareStack();
};
/**
* Add a new rewrite rule to the stack
* @param {Object} rule
*/
BrowserSync.prototype.resetMiddlewareStack = function () {
var bs = this;
var middlewares = require("./server/utils").getMiddlewares(bs, bs.options);
bs.app.stack = middlewares;
};
/**
* @param fn
*/
BrowserSync.prototype.registerCleanupTask = function (fn) {
this._cleanupTasks.push(fn);
};
/**
* Instance Cleanup
*/
BrowserSync.prototype.cleanup = function (cb) {
var bs = this;
if (!bs.active) {
return;
}
// Remove all event listeners
if (bs.events) {
bs.debug("Removing event listeners...");
bs.events.removeAllListeners();
}
// Close any core file watchers
if (bs.watchers) {
Object.keys(bs.watchers).forEach(function (key) {
bs.watchers[key].watchers.forEach(function (watcher) {
watcher.close();
});
});
}
// Run any additional clean up tasks
bs._cleanupTasks.forEach(function (fn) {
if (_.isFunction(fn)) {
fn(bs);
}
});
// Reset the flag
bs.debug("Setting {magenta:active: false");
bs.active = false;
bs.paused = false;
bs.pluginManager.plugins = {};
bs.pluginManager.pluginOptions = {};
bs.pluginManager.defaultPlugins = defaultPlugins;
bs._userPlugins = [];
bs.userPlugins = [];
bs._reloadTimer = undefined;
bs._reloadQueue = [];
bs._cleanupTasks = [];
if (_.isFunction(cb)) {
cb(null, bs);
}
};
module.exports = BrowserSync;
+59
View File
@@ -0,0 +1,59 @@
"use strict";
var config = require("../config");
var logger = require("../logger").logger;
var fs = require("fs");
var _ = require("../../lodash.custom");
var path = require("path");
var info = {
/**
* Version info
* @param {Object} pjson
* @returns {String}
*/
getVersion: function (pjson) {
console.log(pjson.version);
return pjson.version;
},
/**
* Retrieve the config file
* @returns {*}
* @private
* @param filePath
*/
getConfigFile: function (filePath) {
return require(path.resolve(filePath));
},
/**
* Generate an example Config file.
*/
makeConfig: function (cwd, cb) {
var opts = require(path.join(__dirname, "..", config.configFile));
var userOpts = {};
var ignore = ["excludedFileTypes", "injectFileTypes", "snippetOptions"];
Object.keys(opts).forEach(function (key) {
if (!_.includes(ignore, key)) {
userOpts[key] = opts[key];
}
});
var file = fs.readFileSync(path.join(__dirname, config.template), "utf8");
file = file.replace("//OPTS", JSON.stringify(userOpts, null, 4));
fs.writeFile(path.resolve(cwd, config.userFile), file, function () {
logger.info("Config file created {magenta:%s}", config.userFile);
logger.info(
"To use it, in the same directory run: " +
"{cyan:browser-sync start --config bs-config.js}"
);
cb();
});
}
};
module.exports = info;
+317
View File
@@ -0,0 +1,317 @@
"use strict";
var path = require("path");
var url = require("url");
var _ = require("../../lodash.custom");
var Immutable = require("immutable");
var isList = Immutable.List.isList;
var isMap = Immutable.Map.isMap;
var defaultConfig = require("../default-config");
var immDefs = Immutable.fromJS(defaultConfig);
var opts = exports;
/**
* @type {{wrapPattern: Function}}
*/
opts.utils = {
/**
* Transform a string arg such as "*.html, css/*.css" into array
* @param string
* @returns {Array}
*/
explodeFilesArg: function (string) {
return string.split(",").map(function (item) {
return item.trim();
});
},
/**
* @param pattern
* @returns {*|string}
* @private
*/
wrapPattern: function (pattern) {
var prefix = "!";
var suffix = "/**";
var lastChar = pattern.charAt(pattern.length - 1);
var extName = path.extname(pattern);
// If there's a file ext, don't append any suffix
if (extName.length) {
suffix = "";
} else {
if (lastChar === "/") {
suffix = "**";
}
if (lastChar === "*") {
suffix = "";
}
}
return [prefix, pattern, suffix].join("");
}
};
opts.callbacks = {
/**
* Merge server options
* @param {String|Boolean|Object} value
* @param [argv]
* @returns {*}
*/
server: function (value, argv) {
if (value === false) {
if (!argv || !argv.server) {
return false;
}
}
var obj = {
baseDir: "./"
};
if (_.isString(value) || isList(value)) {
obj.baseDir = value;
} else {
if (value && value !== true) {
if (value.get("baseDir")) {
return value;
}
}
}
if (argv) {
if (argv.index) {
obj.index = argv.index;
}
if (argv.directory) {
obj.directory = true;
}
}
return Immutable.fromJS(obj);
},
/**
* @param value
* @param argv
* @returns {*}
*/
proxy: function (value) {
var mw;
var target;
if (!value || value === true) {
return false;
}
if (typeof value !== "string") {
target = value.get("target");
mw = value.get("middleware");
} else {
target = value;
value = Immutable.Map({});
}
if (!target.match(/^(https?):\/\//)) {
target = "http://" + target;
}
var parsedUrl = url.parse(target);
if (!parsedUrl.port) {
parsedUrl.port = 80;
}
var out = {
target: parsedUrl.protocol + "//" + parsedUrl.host,
url: Immutable.Map(parsedUrl)
};
if (mw) {
out.middleware = mw;
}
return value.mergeDeep(out);
},
/**
* @param value
* @private
*/
ports: function (value) {
var segs;
var obj = {};
if (typeof value === "string") {
if (~value.indexOf(",")) {
segs = value.split(",");
obj.min = parseInt(segs[0], 10);
obj.max = parseInt(segs[1], 10);
} else {
obj.min = parseInt(value, 10);
obj.max = null;
}
} else {
obj.min = value.get("min");
obj.max = value.get("max") || null;
}
return Immutable.Map(obj);
},
/**
* @param value
* @param argv
* @returns {*}
*/
ghostMode: function (value, argv) {
var trueAll = {
clicks: true,
scroll: true,
forms: {
submit: true,
inputs: true,
toggles: true
}
};
var falseAll = {
clicks: false,
scroll: false,
forms: {
submit: false,
inputs: false,
toggles: false
}
};
if (value === false || value === "false" || argv && argv.ghost === false) {
return Immutable.fromJS(falseAll);
}
if (value === true || value === "true" || argv && argv.ghost === true) {
return Immutable.fromJS(trueAll);
}
if (value.get("forms") === false) {
return value.withMutations(function (map) {
map.set("forms", Immutable.fromJS({
submit: false,
inputs: false,
toggles: false
}));
});
}
if (value.get("forms") === true) {
return value.withMutations(function (map) {
map.set("forms", Immutable.fromJS({
submit: true,
inputs: true,
toggles: true
}));
});
}
return value;
},
/**
* @param value
* @returns {*}
*/
files: function (value) {
var namespaces = {core: {}};
namespaces.core.globs = [];
namespaces.core.objs = [];
var processed = opts.makeFilesArg(value);
if (processed.globs.length) {
namespaces.core.globs = processed.globs;
}
if (processed.objs.length) {
namespaces.core.objs = processed.objs;
}
return Immutable.fromJS(namespaces);
},
/**
* @param value
*/
extensions: function (value) {
if (_.isString(value)) {
var split = opts.utils.explodeFilesArg(value);
if (split.length) {
return Immutable.List(split);
}
}
if (Immutable.List.isList(value)) {
return value;
}
return value;
}
};
/**
* @param {Object} values
* @param {Object} [argv]
* @returns {Map}
*/
opts.merge = function (values, argv) {
return immDefs
.mergeDeep(values)
.withMutations(function (item) {
item.map(function (value, key) {
if (opts.callbacks[key]) {
item.set(key, opts.callbacks[key](value, argv));
}
});
});
};
/**
* @param value
* @returns {{globs: Array, objs: Array}}
*/
opts.makeFilesArg = function (value) {
var globs = [];
var objs = [];
if (_.isString(value)) {
globs = globs.concat(
opts.utils.explodeFilesArg(value)
);
}
if (isList(value) && value.size) {
value.forEach(function (value) {
if (_.isString(value)) {
globs.push(value);
} else {
if (isMap(value)) {
objs.push(value);
}
}
});
}
return {
globs: globs,
objs: objs
};
};
+15
View File
@@ -0,0 +1,15 @@
/*
|--------------------------------------------------------------------------
| Browser-sync config file
|--------------------------------------------------------------------------
|
| For up-to-date information about the options:
| http://www.browsersync.io/docs/options/
|
| There are more options than you see here, these are just the ones that are
| set internally. See the website for more info.
|
|
*/
module.exports = //OPTS;
+15
View File
@@ -0,0 +1,15 @@
"use strict";
var info = require("./cli-info");
/**
* $ browser-sync init
*
* This command will generate a configuration
* file in the current directory
*
* @param opts
*/
module.exports = function (opts) {
info.makeConfig(process.cwd(), opts.cb);
};
+64
View File
@@ -0,0 +1,64 @@
"use strict";
var logger = require("../logger").logger;
/**
* $ browser-sync recipe <name> <options>
*
* This command will copy a recipe into either the current directory
* or one given with the --output flag
*
* @param opts
* @returns {Function}
*/
module.exports = function (opts) {
var path = require("path");
var fs = require("fs-extra");
var input = opts.cli.input.slice(1);
var resolved = require.resolve("bs-recipes");
var dir = path.dirname(resolved);
var logRecipes = function () {
var dirs = fs.readdirSync(path.join(dir, "recipes"));
logger.info("Install one of the following with {cyan:browser-sync recipe <name>\n");
dirs.forEach(function (name) {
console.log(" " + name);
});
};
if (!input.length) {
logger.info("No recipe name provided!");
logRecipes();
return opts.cb();
}
if (opts.cli.input[1] === "ls") {
logRecipes();
return opts.cb();
}
input = input[0];
var flags = opts.cli.flags;
var output = flags.output ? path.resolve(flags.output) : path.join(process.cwd(), input);
var targetDir = path.join(dir, "recipes", input);
if (fs.existsSync(output)) {
return opts.cb(new Error("Target folder exists remove it first and then try again"));
}
if (fs.existsSync(targetDir)) {
fs.copy(targetDir, output, function (err) {
if (err) {
opts.cb(err);
} else {
logger.info("Recipe copied into {cyan:%s}", output);
logger.info("Next, inside that folder, run {cyan:npm i && npm start}");
opts.cb(null);
}
});
} else {
logger.info("Recipe {cyan:%s} not found. The following are available though", input);
logRecipes();
opts.cb();
}
};
+44
View File
@@ -0,0 +1,44 @@
"use strict";
/**
* $ browser-sync reload <options>
*
* This commands starts the Browsersync servers
* & Optionally UI.
*
* @param opts
* @returns {Function}
*/
module.exports = function (opts) {
var flags = opts.cli.flags;
if (!flags.url) {
flags.url = "http://localhost:" + (flags.port || 3000);
}
var proto = require("../http-protocol");
var scheme = flags.url.match(/^https/) ? "https" : "http";
var args = {method: "reload"};
if (flags.files) {
args.args = flags.files;
}
var url = proto.getUrl(args, flags.url);
if (scheme === "https") {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
require(scheme).get(url, function (res) {
res.on("data", function () {
if (res.statusCode === 200) {
opts.cb(null, res);
}
});
}).on("error", function (err) {
if (err.code === "ECONNREFUSED") {
err.message = "Browsersync not running at " + flags.url;
}
return opts.cb(err);
});
};
+88
View File
@@ -0,0 +1,88 @@
"use strict";
var path = require("path");
var fs = require("fs");
var _ = require("../../lodash.custom");
var utils = require("../utils");
var opts = require("./cli-options").utils;
/**
* $ browser-sync start <options>
*
* This commands starts the Browsersync servers
* & Optionally UI.
*
* @param opts
* @returns {Function}
*/
module.exports = function (opts) {
var flags = preprocessFlags(opts.cli.flags);
var maybepkg = path.resolve(process.cwd(), "package.json");
var input = flags;
if (flags.config) {
var maybeconf = path.resolve(process.cwd(), flags.config);
if (fs.existsSync(maybeconf)) {
var conf = require(maybeconf);
input = _.merge({}, conf, flags);
} else {
utils.fail(true, new Error("Configuration file '" + flags.config + "' not found"), opts.cb);
}
} else {
if (fs.existsSync(maybepkg)) {
var pkg = require(maybepkg);
if (pkg["browser-sync"]) {
console.log("> Configuration obtained from package.json");
input = _.merge({}, pkg["browser-sync"], flags);
}
}
}
return require("../../")
.create("cli")
.init(input, opts.cb);
};
/**
* @param flags
* @returns {*}
*/
function preprocessFlags (flags) {
return [
stripUndefined,
legacyFilesArgs
].reduce(function (flags, fn) {
return fn.call(null, flags);
}, flags);
}
/**
* Incoming undefined values are problematic as
* they interfere with Immutable.Map.mergeDeep
* @param subject
* @returns {*}
*/
function stripUndefined (subject) {
return Object.keys(subject).reduce(function (acc, key) {
var value = subject[key];
if (typeof value === "undefined") {
return acc;
}
acc[key] = value;
return acc;
}, {});
}
/**
* @param flags
* @returns {*}
*/
function legacyFilesArgs(flags) {
if (flags.files && flags.files.length) {
flags.files = flags.files.reduce(function (acc, item) {
return acc.concat(opts.explodeFilesArg(item));
}, []);
}
return flags;
}
+11
View File
@@ -0,0 +1,11 @@
{cyan:Server Example:}
{gray:---------------}
Use current directory as root & watch CSS files
{gray:$} browser-sync start --server --files="css/*.css"
{cyan:Proxy Example:}
{gray:---------------}
Proxy `localhost:8080` & watch CSS/HTML files
{gray:$} browser-sync start --proxy="localhost:8080" --files="*.html, css/*.css"
+1
View File
@@ -0,0 +1 @@
{}
+6
View File
@@ -0,0 +1,6 @@
{
"output": {
"alias": "o",
"desc": "Specify an output directory"
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"files": {
"desc": "File paths to reload",
"type": "array",
"alias": "f"
},
"port": {
"alias": "p",
"type": "number",
"desc": "Target a running instance by port number"
},
"url": {
"alias": "u",
"desc": "Provide the full the url to the running Browsersync instance"
}
}
+120
View File
@@ -0,0 +1,120 @@
{
"server": {
"alias": "s",
"desc": "Run a Local server (uses your cwd as the web root)"
},
"serveStatic": {
"type": "array",
"alias": "ss",
"desc": "Directories to serve static files from"
},
"port": {
"type": "number",
"desc": "Specify a port to use"
},
"proxy": {
"alias": "p",
"desc": "Proxy an existing server",
"example": "$0 shane is cool"
},
"ws": {
"type": "boolean",
"desc": "Proxy mode only - enable websocket proxying"
},
"browser": {
"type": "array",
"alias": "b",
"desc": "Choose which browser should be auto-opened"
},
"files": {
"type": "array",
"alias": "f",
"desc": "File paths to watch"
},
"index": {
"type": "string",
"desc": "Specify which file should be used as the index page"
},
"plugins": {
"type": "array",
"desc": "Load Browsersync plugins"
},
"extensions": {
"type": "array",
"desc": "Specify file extension fallbacks"
},
"startPath": {
"type": "string",
"desc": "Specify the start path for the opened browser"
},
"https": {
"desc": "Enable SSL for local development"
},
"directory": {
"type": "boolean",
"desc": "Show a directory listing for the server"
},
"xip": {
"type": "boolean",
"desc": "Use xip.io domain routing"
},
"tunnel": {
"desc": "Use a public URL"
},
"open": {
"type": "string",
"desc": "Choose which URL is auto-opened (local, external or tunnel), or provide a url"
},
"cors": {
"type": "boolean",
"desc": "Add Access Control headers to every request"
},
"config": {
"type": "string",
"alias": "c",
"desc": "Specify a path to a configuration file"
},
"host": {
"desc": "Specify a hostname to use"
},
"logLevel": {
"desc": "Set the logger output level (silent, info or debug)"
},
"reload-delay": {
"type": "number",
"desc": "Time in milliseconds to delay the reload event following file changes"
},
"reload-debounce": {
"type": "number",
"desc": "Restrict the frequency in which browser:reload events can be emitted to connected clients"
},
"ui-port": {
"type": "number",
"desc": "Specify a port for the UI to use"
},
"watchEvents": {
"type": "array",
"desc": "Specify which file events to respond to"
},
"no-notify": {
"desc": "Disable the notify element in browsers"
},
"no-open": {
"desc": "Don't open a new browser window"
},
"no-online": {
"desc": "Force offline usage"
},
"no-ui": {
"desc": "Don't start the user interface"
},
"no-ghost-mode": {
"desc": "Disable Ghost Mode"
},
"no-inject-changes": {
"desc": "Reload on every file change"
},
"no-reload-on-restart": {
"desc": "Don't auto-reload all browsers following a restart"
}
}
+31
View File
@@ -0,0 +1,31 @@
"use strict";
var path = require("path");
/**
* @type {{controlPanel: {jsFile: string, baseDir: *}, socketIoScript: string, configFile: string, client: {shims: string}}}
*/
module.exports = {
controlPanel: {
jsFile: "/js/app.js",
baseDir: path.join(__dirname, "control-panel")
},
templates: {
scriptTag: path.join(__dirname, "templates/script-tags.tmpl"),
scriptTagSimple: path.join(__dirname, "templates/script-tags-simple.tmpl"),
connector: path.join(__dirname, "templates/connector.tmpl")
},
socketIoScript: "/public/socket.io.min.1.6.0.js",
configFile: "default-config.js",
userFile: "bs-config.js",
template: "cli-template.js",
httpProtocol: {
path: "/__browser_sync__"
},
client: {
shims: "/client/client-shims.js"
},
errors: {
"server+proxy": "Invalid config. You cannot specify both server & proxy options.",
"proxy+https": "Invalid config. You set https: true, but your proxy target doesn't reflect this."
}
};
+245
View File
@@ -0,0 +1,245 @@
"use strict";
var _ = require("../lodash.custom");
var fs = require("fs");
var config = require("./config");
function getPath(options, relative, port) {
if (options.get("mode") === "snippet") {
return options.get("scheme") + "://HOST:" + port + relative;
} else {
return "//HOST:" + port + relative;
}
}
var connectUtils = {
/**
* @param {Immutable.Map} options
* @returns {String}
*/
scriptTags: function (options) {
var scriptPath = this.clientScript(options);
var async = options.getIn(["snippetOptions", "async"]);
var scriptDomain = options.getIn(["script", "domain"]);
/**
* Generate the [src] attribute based on user options
*/
var scriptSrc = (function () {
if (options.get("localOnly")) {
return [
options.get("scheme"),
"://localhost:",
options.get("port"),
scriptPath
].join("");
}
/**
* First, was "scriptPath" set? if so the user wanted full control over the
* script tag output
*
*/
if (_.isFunction(options.get("scriptPath"))) {
return options.get("scriptPath").apply(null, getScriptArgs(options, scriptPath));
}
/**
* Next, if "script.domain" was given, allow that + the path to the JS file
* eg:
* script.domain=localhost:3000
* -> localhost:3000/browser-sync/browser-sync-client.js
*/
if (scriptDomain) {
if (_.isFunction(scriptDomain)) {
return scriptDomain.call(null, options) + scriptPath;
}
if (scriptDomain.match(/\{port\}/)) {
return scriptDomain.replace("{port}", options.get("port")) + scriptPath;
}
return scriptDomain + scriptPath;
}
/**
* Now if server or proxy, use dynamic script
* eg:
* browser-sync start --server
* ->
* "HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)
*/
if (options.get("server") || options.get("proxy")) {
return scriptPath;
}
/**
* Final use case is snippet mode
* -> "http://HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)
* -> "//HOST:3000/browser-sync/browser-sync-client.js".replace("HOST", location.hostname)"
*/
return getPath(options, scriptPath, options.get("port"));
})();
/**
* Decide which template shall be used to generate the script tags
*/
var template = (function () {
if (scriptDomain || options.get("localOnly")) {
return config.templates.scriptTagSimple;
}
return config.templates.scriptTag;
})();
/**
* Finally read the template file from disk and replace
* the dynamic values.
*/
return fs.readFileSync(template, "utf8")
.replace("%script%", scriptSrc)
.replace("%async%", async ? "async" : "");
},
/**
* @param {Map} options
* @returns {String}
*/
socketConnector: function (options) {
var socket = options.get("socket");
var template = fs.readFileSync(config.templates.connector, "utf-8");
var url = connectUtils.getConnectionUrl(options);
/**
* ***Backwards compatibility***. While `socket.path` is technically a
* socketIoClientConfig property, it's been documented previously
* as a top-level option, so must stay.
*/
var clientConfig = socket
.get("socketIoClientConfig")
.merge({
path: socket.get("path")
});
template = template
.replace("%config%", JSON.stringify(clientConfig.toJS()))
.replace("%url%", url);
return template;
},
/**
* @param {Object} socketOpts
* @param {Map} options
* @returns {String|Function}
*/
getNamespace: function (socketOpts, options) {
var namespace = socketOpts.namespace;
if (typeof namespace === "function") {
return namespace(options);
}
if (!namespace.match(/^\//)) {
namespace = "/" + namespace;
}
return namespace;
},
/**
* @param {Map} options
* @returns {string}
*/
getConnectionUrl: function (options) {
var socketOpts = options.get("socket").toJS();
var namespace = connectUtils.getNamespace(socketOpts, options);
var protocol = "";
var withHostnamePort = "'{protocol}' + location.hostname + ':{port}{ns}'";
var withHost = "'{protocol}' + location.host + '{ns}'";
var withDomain = "'{domain}{ns}'";
var port = options.get("port");
// default use-case is server/proxy
var string = withHost;
if (options.get("mode") !== "server") {
protocol = options.get("scheme") + "://";
string = withHostnamePort;
}
if (options.get("mode") === "proxy" && options.getIn(["proxy", "ws"])) {
port = options.getIn(["socket", "port"]);
}
/**
* Ensure socket.domain is always a string (for noop replacements later)
*/
socketOpts.domain = (function () {
if (options.get("localOnly")) {
string = withDomain;
return [
options.get("scheme"),
"://localhost:",
options.get("port")
].join("");
}
if (socketOpts.domain) {
string = withDomain;
/**
* User provided a function
*/
if (_.isFunction(socketOpts.domain)) {
return socketOpts.domain.call(null, options);
}
/**
* User provided a string
*/
if (_.isString(socketOpts.domain)) {
return socketOpts.domain;
}
}
return "";
})();
return string
.replace("{protocol}", protocol)
.replace("{port}", port)
.replace("{domain}", socketOpts.domain.replace("{port}", port))
.replace("{ns}", namespace);
},
/**
* @param {Object} [options]
* @param {Boolean} [both]
*/
clientScript: function (options, both) {
var prefix = options.getIn(["socket", "clientPath"]);
var script = prefix + "/browser-sync-client.js";
var versioned = prefix + "/browser-sync-client.js?v=" + options.get("version");
if (both) {
return {
path: script,
versioned: versioned
};
}
return versioned;
}
};
/**
* @param options
* @returns {*[]}
*/
function getScriptArgs (options, scriptPath) {
var abspath = options.get("scheme") + "://HOST:" + options.get("port") + scriptPath;
return [
scriptPath,
options.get("port"),
options.set("absolute", abspath)
];
}
module.exports = connectUtils;
+546
View File
@@ -0,0 +1,546 @@
"use strict";
/**
* @module BrowserSync.options
*/
module.exports = {
/**
* Browsersync includes a user-interface that is accessed via a separate port.
* The UI allows to controls all devices, push sync updates and much more.
* @property ui
* @type Object
* @param {Number} [port=3001]
* @param {Number} [weinre.port=8080]
* @since 2.0.0
* @default false
*/
ui: {
port: 3001,
weinre: {
port: 8080
}
},
/**
* Browsersync can watch your files as you work. Changes you make will either
* be injected into the page (CSS & images) or will cause all browsers to do
* a full-page refresh.
* @property files
* @type Array|String
* @default false
*/
files: false,
/**
* Specify which file events to respond to.
* Available events: `add`, `change`, `unlink`, `addDir`, `unlinkDir`
* @property watchEvents
* @type Array
* @default ["change"]
* @since 2.18.8
*/
watchEvents: ["change"],
/**
* File watching options that get passed along to [Chokidar](https://github.com/paulmillr/chokidar).
* Check their docs for available options
* @property watchOptions
* @type Object
* @default undefined
* @since 2.6.0
*/
watchOptions: {
ignoreInitial: true
/*
persistent: true,
ignored: '*.txt',
followSymlinks: true,
cwd: '.',
usePolling: true,
alwaysStat: false,
depth: undefined,
interval: 100,
ignorePermissionErrors: false,
atomic: true
*/
},
/**
* Use the built-in static server for basic HTML/JS/CSS websites.
* @property server
* @type Object|Boolean
* @default false
*/
server: false,
/**
* Proxy an EXISTING vhost. Browsersync will wrap your vhost with a proxy URL to view your site.
* @property proxy
* @type String|Object|Boolean
* @param {String} [target]
* @param {Boolean} [ws] - Enable websocket proxying
* @param {Function|Array} [middleware]
* @param {Function} [reqHeaders]
* @param {Array} [proxyReq]
* @param {Array} [proxyRes]
* @default false
*/
proxy: false,
/**
* @property port
* @type Number
* @default 3000
*/
port: 3000,
/**
* @property middleware
* @type Function|Array
* @default false
*/
middleware: false,
/**
* Add additional directories from which static
* files should be served. Should only be used in `proxy` or `snippet`
* mode.
* @property serveStatic
* @type Array
* @default []
* @since 2.8.0
*/
serveStatic: [],
/**
* Options that are passed to the serve-static middleware
* when you use the string[] syntax: eg: `serveStatic: ['./app']`. Please see
* [serve-static](https://github.com/expressjs/serve-static) for details
*
* @property serveStaticOptions
* @type Object
* @since 2.17.0
*/
/**
* Enable https for localhost development. **Note** - this is not needed for proxy
* option as it will be inferred from your target url.
* @property https
* @type Boolean
* @default undefined
* @since 1.3.0
*/
/**
* Override http module to allow using 3rd party server modules (such as http2)
* @property httpModule
* @type string
* @default undefined
* @since 2.18.0
*/
/**
* Clicks, Scrolls & Form inputs on any device will be mirrored to all others.
* @property ghostMode
* @param {Boolean} [clicks=true]
* @param {Boolean} [scroll=true]
* @param {Boolean} [location=true]
* @param {Boolean} [forms=true]
* @param {Boolean} [forms.submit=true]
* @param {Boolean} [forms.inputs=true]
* @param {Boolean} [forms.toggles=true]
* @type Object
*/
ghostMode: {
clicks: true,
scroll: true,
location: true,
forms: {
submit: true,
inputs: true,
toggles: true
}
},
/**
* Can be either "info", "debug", "warn", or "silent"
* @property logLevel
* @type String
* @default info
*/
logLevel: "info",
/**
* Change the console logging prefix. Useful if you're creating your
* own project based on Browsersync
* @property logPrefix
* @type String
* @default BS
* @since 1.5.1
*/
logPrefix: "BS",
/**
* @property logConnections
* @type Boolean
* @default false
*/
logConnections: false,
/**
* @property logFileChanges
* @type Boolean
* @default true
*/
logFileChanges: true,
/**
* Log the snippet to the console when you're in snippet mode (no proxy/server)
* @property logSnippet
* @type: Boolean
* @default true
* @since 1.5.2
*/
logSnippet: true,
/**
* You can control how the snippet is injected
* onto each page via a custom regex + function.
* You can also provide patterns for certain urls
* that should be ignored from the snippet injection.
* @property snippetOptions
* @since 2.0.0
* @param {Boolean} [async] - should the script tags have the async attribute?
* @param {Array} [blacklist]
* @param {Array} [whitelist]
* @param {RegExp} [rule.match=/$/]
* @param {Function} [rule.fn=Function]
* @type Object
*/
snippetOptions: {
async: true,
whitelist: [],
blacklist: [],
rule: {
match: /<body[^>]*>/i,
fn: function (snippet, match) {
return match + snippet;
}
}
},
/**
* Add additional HTML rewriting rules.
* @property rewriteRules
* @since 2.4.0
* @type Array
* @default false
*/
rewriteRules: [],
/**
* @property tunnel
* @type String|Boolean
* @default null
*/
/**
* Some features of Browsersync (such as `xip` & `tunnel`) require an internet connection, but if you're
* working offline, you can reduce start-up time by setting this option to `false`
* @property online
* @type Boolean
* @default undefined
*/
/**
* Decide which URL to open automatically when Browsersync starts. Defaults to "local" if none set.
* Can be `true`, `local`, `external`, `ui`, `ui-external`, `tunnel` or `false`
* @property open
* @type Boolean|String
* @default true
*/
open: "local",
/**
* @property browser
* @type String|Array
* @default default
*/
browser: "default",
/**
* Add HTTP access control (CORS) headers to assets served by Browsersync.
* @property cors
* @type boolean
* @default false
* @since 2.16.0
*/
cors: false,
/**
* Requires an internet connection - useful for services such as [Typekit](https://typekit.com/)
* as it allows you to configure domains such as `*.xip.io` in your kit settings
* @property xip
* @type Boolean
* @default false
*/
xip: false,
hostnameSuffix: false,
/**
* Reload each browser when Browsersync is restarted.
* @property reloadOnRestart
* @type Boolean
* @default false
*/
reloadOnRestart: false,
/**
* The small pop-over notifications in the browser are not always needed/wanted.
* @property notify
* @type Boolean
* @default true
*/
notify: true,
/**
* @property scrollProportionally
* @type Boolean
* @default true
*/
scrollProportionally: true,
/**
* @property scrollThrottle
* @type Number
* @default 0
*/
scrollThrottle: 0,
/**
* Decide which technique should be used to restore
* scroll position following a reload.
* Can be `window.name` or `cookie`
* @property scrollRestoreTechnique
* @type String
* @default 'window.name'
*/
scrollRestoreTechnique: "window.name",
/**
* Sync the scroll position of any element
* on the page. Add any amount of CSS selectors
* @property scrollElements
* @type Array
* @default []
* @since 2.9.0
*/
scrollElements: [],
/**
* Sync the scroll position of any element
* on the page - where any scrolled element
* will cause all others to match scroll position.
* This is helpful when a breakpoint alters which element
* is actually scrolling
* @property scrollElementMapping
* @type Array
* @default []
* @since 2.9.0
*/
scrollElementMapping: [],
/**
* Time, in milliseconds, to wait before
* instructing the browser to reload/inject following a
* file change event
* @property reloadDelay
* @type Number
* @default 0
*/
reloadDelay: 0,
/**
* Wait for a specified window of event-silence before
* sending any reload events.
* @property reloadDebounce
* @type Number
* @default 0
* @since 2.6.0
*/
reloadDebounce: 0,
/**
* Emit only the first event during sequential time windows
* of a specified duration.
* @property reloadThrottle
* @type Number
* @default 0
* @since 2.13.0
*/
reloadThrottle: 0,
/**
* User provided plugins
* @property plugins
* @type Array
* @default []
* @since 2.6.0
*/
plugins: [],
/**
* @property injectChanges
* @type Boolean
* @default true
*/
injectChanges: true,
/**
* @property startPath
* @type String|Null
* @default null
*/
startPath: null,
/**
* Whether to minify client script, or not.
* @property minify
* @type Boolean
* @default true
*/
minify: true,
/**
* @property host
* @type String
* @default null
*/
host: null,
/**
* Support environments where dynamic hostnames are not required
* (ie: electron)
* @property localOnly
* @type Boolean
* @default false
* @since 2.14.0
*/
localOnly: false,
/**
* @property codeSync
* @type Boolean
* @default true
*/
codeSync: true,
/**
* @property timestamps
* @type Boolean
* @default true
*/
timestamps: true,
clientEvents: [
"scroll",
"scroll:element",
"input:text",
"input:toggles",
"form:submit",
"form:reset",
"click"
],
/**
* Alter the script path for complete control over where the Browsersync
* Javascript is served from. Whatever you return from this function
* will be used as the script path.
* @property scriptPath
* @default undefined
* @since 1.5.0
* @type Function
*/
/**
* Configure the Socket.IO path and namespace & domain to avoid collisions.
* @property socket
* @param {String} [path="/browser-sync/socket.io"]
* @param {String} [clientPath="/browser-sync"]
* @param {String|Function} [namespace="/browser-sync"]
* @param {String|Function} [domain=undefined]
* @param {String|Function} [port=undefined]
* @param {Object} [clients.heartbeatTimeout=5000]
* @since 1.6.2
* @type Object
*/
socket: {
socketIoOptions: {
log: false
},
socketIoClientConfig: {
reconnectionAttempts: 50
},
path: "/browser-sync/socket.io",
clientPath: "/browser-sync",
namespace: "/browser-sync",
clients: {
heartbeatTimeout: 5000
}
},
/**
* Configure the script domain
* @property script
* @param {String|Function} [domain=undefined]
* @since 2.14.0
* @type Object
*/
tagNames: {
"less": "link",
"scss": "link",
"css": "link",
"jpg": "img",
"jpeg": "img",
"png": "img",
"svg": "img",
"gif": "img",
"js": "script"
},
injectFileTypes: ["css", "png", "jpg", "jpeg", "svg", "gif", "webp", "map"],
excludedFileTypes: [
"js",
"css",
"pdf",
"map",
"svg",
"ico",
"woff",
"json",
"eot",
"ttf",
"png",
"jpg",
"jpeg",
"webp",
"gif",
"mp4",
"mp3",
"3gp",
"ogg",
"ogv",
"webm",
"m4a",
"flv",
"wmv",
"avi",
"swf",
"scss"
]
};
+120
View File
@@ -0,0 +1,120 @@
var utils = require("./utils");
/**
* Apply the operators that apply to the 'file:changed' event
* @param {Rx.Observable} subject
* @param options
* @return {Rx.Observable<{type: string, files: Array<any>}>}
*/
function fileChanges(subject, options) {
var operators = [
{
option: "reloadThrottle",
fnName: "throttle"
},
{
option: "reloadDelay",
fnName: "delay"
}
];
var scheduler = options.getIn(["debug", "scheduler"]);
/**
* if the 'reloadDebounce' option was provided, create
* a stream buffered/debounced stream of events
*/
var initial = (function() {
if (options.get("reloadDebounce") > 0) {
return getAggregatedDebouncedStream(subject, options, scheduler);
}
return subject;
})();
return applyOperators(operators, initial, options, scheduler)
.map(function(xs) {
var items = [].concat(xs);
var paths = items.map(function (x) { return x.path });
if (utils.willCauseReload(paths, options.get("injectFileTypes").toJS())) {
return {
type: "reload",
files: items
}
}
return {
type: "inject",
files: items
}
});
}
module.exports.fileChanges = fileChanges;
/**
* Apply the operators that apply to the 'browser:reload' event
* @param {Rx.Observable} subject
* @param options
* @returns {Rx.Observable}
*/
function applyReloadOperators (subject, options) {
var operators = [
{
option: "reloadDebounce",
fnName: "debounce"
},
{
option: "reloadThrottle",
fnName: "throttle"
},
{
option: "reloadDelay",
fnName: "delay"
}
];
return applyOperators(operators, subject, options, options.getIn(["debug", "scheduler"]));
}
module.exports.applyReloadOperators = applyReloadOperators;
/**
* @param items
* @param subject
* @param options
* @param scheduler
*/
function applyOperators (items, subject, options, scheduler) {
return items.reduce(function(subject, item) {
var value = options.get(item.option);
if (value > 0) {
return subject[item.fnName].call(subject, value, scheduler);
}
return subject;
}, subject);
}
/**
* @param subject
* @param options
* @param scheduler
*/
function getAggregatedDebouncedStream (subject, options, scheduler) {
return subject
.filter(function(x) { return options.get("watchEvents").indexOf(x.event) > -1 })
.buffer(subject.debounce(options.get("reloadDebounce"), scheduler))
.map(function(buffered) {
return buffered.reduce(function (acc, item) {
if (!acc[item.path]) acc[item.path] = item;
if (acc[item.path]) acc[item.path] = item;
return acc;
}, {});
})
.map(function(group) {
return Object
.keys(group)
.map(function(key) {
return group[key];
});
})
.filter(function (x) { return x.length })
}
+61
View File
@@ -0,0 +1,61 @@
"use strict";
var _ = require("../lodash.custom");
var fileUtils = {
/**
* React to file-change events that occur on "core" namespace only
* @param bs
* @param data
*/
changedFile: function (bs, data) {
/**
* If the event property is undefined, infer that it's a 'change'
* event due the fact this handler is for emitter.emit("file:changed")
*/
if (_.isUndefined(data.event)) {
data.event = "change";
}
/**
* Chokidar always sends an 'event' property - which could be
* `add` `unlink` etc etc so we need to check for that and only
* respond to 'change', for now.
*/
if (bs.options.get("watchEvents").indexOf(data.event) > -1) {
if (!bs.paused && data.namespace === "core") {
bs.events.emit("file:reload", fileUtils.getFileInfo(data, bs.options));
}
}
},
/**
* @param data
* @param options
* @returns {{assetFileName: *, fileExtension: String}}
*/
getFileInfo: function (data, options) {
data.ext = require("path").extname(data.path).slice(1);
data.basename = require("path").basename(data.path);
var obj = {
ext: data.ext,
path: data.path,
basename: data.basename,
event: data.event,
type: "inject"
};
// RELOAD page
if (!_.includes(options.get("injectFileTypes").toJS(), obj.ext)) {
obj.url = obj.path;
obj.type = "reload";
}
obj.path = data.path;
obj.log = data.log;
return obj;
}
};
module.exports = fileUtils;
+86
View File
@@ -0,0 +1,86 @@
"use strict";
var _ = require("../lodash.custom");
var utils = require("./utils");
var Rx = require("rx");
/**
* Plugin interface
* @returns {*|function(this:exports)}
*/
module.exports.plugin = function (bs) {
var options = bs.options;
var emitter = bs.emitter;
var defaultWatchOptions = options.get("watchOptions").toJS();
return options.get("files").reduce(function (map, glob, namespace) {
/**
* Default CB when not given
* @param event
* @param path
*/
var fn = function (event, path) {
emitter.emit("file:changed", {
event: event,
path: path,
namespace: namespace
});
};
var jsItem = glob.toJS();
if (jsItem.globs.length) {
var watcher = watch(jsItem.globs, defaultWatchOptions, fn);
map[namespace] = {
watchers: [watcher]
};
}
if (jsItem.objs.length) {
jsItem.objs.forEach(function (item) {
if (!_.isFunction(item.fn)) {
item.fn = fn;
}
var watcher = watch(item.match, item.options || defaultWatchOptions, item.fn.bind(bs.publicInstance));
if (!map[namespace]) {
map[namespace] = {
watchers: [watcher]
};
} else {
map[namespace].watchers.push(watcher);
}
});
}
return map;
}, {});
};
/**
* @param patterns
* @param opts
* @param cb
* @returns {*}
*/
function watch (patterns, opts, cb) {
if (typeof opts === "function") {
cb = opts;
opts = {};
}
var watcher = require("chokidar")
.watch(patterns, opts);
if (_.isFunction(cb)) {
watcher.on("all", cb);
}
return watcher;
}
module.exports.watch = watch;
+96
View File
@@ -0,0 +1,96 @@
"use strict";
var _ = require("../lodash.custom");
var Immutable = require("immutable");
var snippetUtils = require("./snippet").utils;
module.exports = {
/**
*
* @this {BrowserSync}
* @returns {String}
*/
"client:js": function (hooks, data) {
var js = snippetUtils.getClientJs(data.port, data.options);
return hooks.reduce(function (joined, hook) {
return joined + hook;
}, js);
},
/**
* @this {BrowserSync}
* @returns {Array}
*/
"client:events": function (hooks, clientEvents) {
hooks.forEach(function (hook) {
var result = hook(this);
if (Array.isArray(result)) {
clientEvents = _.union(clientEvents, result);
} else {
clientEvents.push(result);
}
}, this);
return clientEvents;
},
/**
* @returns {Array}
*/
"server:middleware": function (hooks, initial) {
initial = initial || [];
_.each(hooks, function (hook) {
var result = hook(this);
if (Array.isArray(result)) {
result.forEach(function (res) {
if (_.isFunction(res)) {
initial = initial.push(res);
}
});
} else {
if (_.isFunction(result)) {
initial = initial.push(result);
}
}
}, this);
return initial;
},
/**
* @param {Array} hooks
* @param {Map|List} initial
* @param pluginOptions
* @returns {any}
*/
"files:watch": function (hooks, initial, pluginOptions) {
var opts;
if (pluginOptions) {
opts = Immutable.fromJS(pluginOptions);
opts.forEach(function (value, key) {
if (!value) {
return;
}
var files = value.get("files");
if (files) {
var fileArg = require("./cli/cli-options").makeFilesArg(files);
if (fileArg) {
initial = initial.set(key, Immutable.fromJS(fileArg));
}
}
});
}
return initial;
}
};
+69
View File
@@ -0,0 +1,69 @@
"use strict";
var queryString = require("qs");
var proto = exports;
/**
* Use BrowserSync options + querystring to create a
* full HTTP/HTTTPS url.
*
* Eg. http://localhost:3000/__browser_sync__?method=reload
* Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css
* Eg. http://localhost:3000/__browser_sync__?method=reload&args=core.css&args=core.min
*
* @param args
* @param url
* @returns {string}
*/
proto.getUrl = function (args, url) {
return [
url,
require("./config").httpProtocol.path,
"?",
queryString.stringify(args)
].join("");
};
/**
* Return a middleware for handling the requests
* @param {BrowserSync} bs
* @returns {Function}
*/
proto.middleware = function (bs) {
return function (req, res) {
var params = queryString.parse(req.url.replace(/^.*\?/, ""));
var output;
if (!Object.keys(params).length) {
output = [
"Error: No Parameters were provided.",
"Example: http://localhost:3000/__browser_sync__?method=reload&args=core.css"
];
res.writeHead(500, {"Content-Type": "text/plain"});
res.end(output.join("\n"));
return;
}
try {
require("./public/" + params.method)(bs.events).apply(null, [params.args]);
output = [
"Called public API method `.%s()`".replace("%s", params.method),
"With args: " + JSON.stringify(params.args)
];
res.end(output.join("\n"));
} catch (e) {
res.writeHead(404, {"Content-Type": "text/plain"});
res.write("Public API method `" + params.method + "` not found.");
res.end();
return;
}
};
};
+110
View File
@@ -0,0 +1,110 @@
"use strict";
var utils = require("./utils");
var fileUtils = require("./file-utils");
var Rx = require("rx");
var fromEvent = Rx.Observable.fromEvent;
var fileHandler = require("./file-event-handler");
module.exports = function (bs) {
var events = {
/**
* File reloads
* @param data
*/
"file:reload": function (data) {
bs.io.sockets.emit("file:reload", data);
},
/**
* Browser Reloads
*/
"browser:reload": function () {
bs.io.sockets.emit("browser:reload");
},
/**
* Browser Notify
* @param data
*/
"browser:notify": function (data) {
bs.io.sockets.emit("browser:notify", data);
},
/**
* Things that happened after the service is running
* @param data
*/
"service:running": function (data) {
var mode = bs.options.get("mode");
var open = bs.options.get("open");
if (mode === "proxy" || mode === "server" || open === "ui" || open === "ui-external") {
utils.openBrowser(data.url, bs.options, bs);
}
// log about any file watching
if (bs.watchers) {
bs.events.emit("file:watching", bs.watchers);
}
},
/**
* Option setting
* @param data
*/
"options:set": function (data) {
if (bs.io) {
bs.io.sockets.emit("options:set", data);
}
},
/**
* Plugin configuration setting
* @param data
*/
"plugins:configure": function (data) {
if (data.active) {
bs.pluginManager.enablePlugin(data.name);
} else {
bs.pluginManager.disablePlugin(data.name);
}
bs.setOption("userPlugins", bs.getUserPlugins());
},
"plugins:opts": function (data) {
if (bs.pluginManager.pluginOptions[data.name]) {
bs.pluginManager.pluginOptions[data.name] = data.opts;
bs.setOption("userPlugins", bs.getUserPlugins());
}
}
};
Object.keys(events).forEach(function (event) {
bs.events.on(event, events[event]);
});
var reloader = fileHandler.applyReloadOperators(fromEvent(bs.events, "_browser:reload"), bs.options)
.subscribe(function() {
bs.events.emit("browser:reload");
});
var coreNamespacedWatchers = fromEvent(bs.events, "file:changed")
.filter(function() { return bs.options.get("codeSync") })
.filter(function(x) { return x.namespace === "core" });
var handler = fileHandler.fileChanges(coreNamespacedWatchers, bs.options)
.subscribe(function (x) {
if (x.type === "reload") {
bs.events.emit("browser:reload");
}
if (x.type === "inject") {
x.files.forEach(function(data) {
if (!bs.paused && data.namespace === "core") {
bs.events.emit("file:reload", fileUtils.getFileInfo(data, bs.options));
}
});
}
});
bs.registerCleanupTask(function() {
handler.dispose();
reloader.dispose();
});
};
+288
View File
@@ -0,0 +1,288 @@
"use strict";
var messages = require("./connect-utils");
var utils = require("./utils");
var _ = require("../lodash.custom");
var template = "[{blue:%s}] ";
var logger = require("eazy-logger").Logger({
prefix: template.replace("%s", "BS"),
useLevelPrefixes: false
});
module.exports.logger = logger;
/**
* @param name
* @returns {*}
*/
module.exports.getLogger = function (name) {
return logger.clone(function (config) {
config.prefix = config.prefix + template.replace("%s", name);
return config;
});
};
/**
* Logging Callbacks
*/
module.exports.callbacks = {
/**
* Log when file-watching has started
* @param {BrowserSync} bs
* @param data
*/
"file:watching": function (bs, data) {
if (Object.keys(data).length) {
logger.info("Watching files...");
}
},
/**
* Log when a file changes
* @param {BrowserSync} bs
* @param data
*/
"file:reload": function (bs, data) {
if (canLogFileChange(bs, data)) {
if (data.path[0] === "*") {
return logger.info("{cyan:Reloading files that match: {magenta:%s", data.path);
}
logger.info("{cyan:File event [" + data.event + "] : {magenta:%s", data.path);
}
},
/**
*
*/
"service:exit": function () {
logger.debug("Exiting...");
},
/**
*
*/
"browser:reload": function (bs) {
if (canLogFileChange(bs)) {
logger.info("{cyan:Reloading Browsers...");
}
},
/**
*
*/
"browser:error": function () {
logger.error("Couldn't open browser (if you are using BrowserSync in a headless environment, you might want to set the {cyan:open} option to {cyan:false})");
},
/**
* @param {BrowserSync} bs
* @param data
*/
"stream:changed": function (bs, data) {
if (canLogFileChange(bs)) {
var changed = data.changed;
logger.info("{cyan:%s %s changed} ({magenta:%s})",
changed.length,
changed.length > 1 ? "files" : "file",
changed.join(", ")
);
}
},
/**
* Client connected logging
* @param {BrowserSync} bs
* @param data
*/
"client:connected": function (bs, data) {
var uaString = utils.getUaString(data.ua);
var msg = "{cyan:Browser Connected: {magenta:%s, version: %s}";
var method = "info";
if (!bs.options.get("logConnections")) {
method = "debug";
}
logger.log(method, msg,
uaString.name,
uaString.version
);
},
/**
* Main logging when the service is running
* @param {BrowserSync} bs
* @param data
*/
"service:running": function (bs, data) {
var type = data.type;
if (type === "server") {
var baseDir = bs.options.getIn(["server", "baseDir"]);
logUrls(bs.options.get("urls").toJS());
if (baseDir) {
if (utils.isList(baseDir)) {
baseDir.forEach(serveFiles);
} else {
serveFiles(baseDir);
}
}
}
if (type === "proxy") {
logger.info("Proxying: {cyan:%s}", bs.options.getIn(["proxy", "target"]));
logUrls(bs.options.get("urls").toJS());
}
if (type === "snippet") {
if (bs.options.get("logSnippet")) {
logger.info(
"{bold:Copy the following snippet into your website, " +
"just before the closing {cyan:</body>} tag"
);
logger.unprefixed("info",
messages.scriptTags(bs.options)
);
}
logUrls(bs.options.get("urls").filter(function (value, key) {
return key.slice(0, 2) === "ui";
}).toJS());
}
function serveFiles (base) {
logger.info("Serving files from: {magenta:%s}", base);
}
}
};
/**
* Plugin interface for BrowserSync
* @param {EventEmitter} emitter
* @param {BrowserSync} bs
* @returns {Object}
*/
module.exports.plugin = function (emitter, bs) {
var logPrefix = bs.options.get("logPrefix");
var logLevel = bs.options.get("logLevel");
// Should set logger level here!
logger.setLevel(logLevel);
if (logPrefix) {
if (_.isFunction(logPrefix)) {
logger.setPrefix(logPrefix);
} else {
logger.setPrefix(template.replace("%s", logPrefix));
}
}
_.each(exports.callbacks, function (func, event) {
emitter.on(event, func.bind(this, bs));
});
return logger;
};
/**
*
* @param urls
*/
function logUrls (urls) {
var keys = Object.keys(urls);
var longestName = 0;
var longesturl = 0;
var offset = 2;
if (!keys.length) {
return;
}
var names = keys.map(function (key) {
if (key.length > longestName) {
longestName = key.length;
}
if (urls[key].length > longesturl) {
longesturl = urls[key].length;
}
return key;
});
var underline = getChars(longestName + offset + longesturl + 1, "-");
var underlined = false;
logger.info("{bold:Access URLs:");
logger.unprefixed("info", "{grey: %s", underline);
keys.forEach(function (key, i) {
var keyname = getKeyName(key);
logger.unprefixed("info", " %s: {magenta:%s}",
getPadding(key.length, longestName + offset) + keyname,
urls[key]
);
if (!underlined && names[i + 1] && names[i + 1].indexOf("ui") > -1) {
underlined = true;
logger.unprefixed("info", "{grey: %s}", underline);
}
});
logger.unprefixed("info", "{grey: %s}", underline);
}
/**
* @param {Number} len
* @param {Number} max
* @returns {string}
*/
function getPadding (len, max) {
return new Array(max - (len + 1)).join(" ");
}
/**
* @param {Number} len
* @param {String} char
* @returns {string}
*/
function getChars (len, char) {
return new Array(len).join(char);
}
/**
* Transform url-key names into something more presentable
* @param key
* @returns {string}
*/
function getKeyName(key) {
if (key.indexOf("ui") > -1) {
if (key === "ui") {
return "UI";
}
if (key === "ui-external") {
return "UI External";
}
}
return key.substr(0, 1).toUpperCase() + key.substring(1);
}
/**
* Determine if file changes should be logged
* @param bs
* @param data
* @returns {boolean}
*/
function canLogFileChange(bs, data) {
if (data && data.log === false) {
return false;
}
return bs.options.get("logFileChanges");
}
+250
View File
@@ -0,0 +1,250 @@
"use strict";
var _ = require("../lodash.custom");
var Immutable = require("immutable");
var defaultConfig = require("./default-config");
/**
* @param {Map} options
* @returns {Map}
*/
module.exports.update = function (options) {
return options.withMutations(function (item) {
setMode(item);
setScheme(item);
setStartPath(item);
setProxyWs(item);
setServerOpts(item);
setNamespace(item);
fixSnippetOptions(item);
fixRewriteRules(item);
setMiddleware(item);
setOpen(item);
if (item.get("uiPort")) {
item.setIn(["ui", "port"], item.get("uiPort"));
}
});
};
/**
* Move top-level ws options to proxy.ws
* This is to allow it to be set from the CLI
* @param item
*/
function setProxyWs(item) {
if (item.get("ws") && item.get("mode") === "proxy") {
item.setIn(["proxy", "ws"], true);
}
}
/**
* @param item
*/
function setOpen (item) {
var open = item.get("open");
if (item.get("mode") === "snippet") {
if (open !== "ui" && open !== "ui-external") {
item.set("open", false);
}
}
}
/**
* Set the running mode
* @param item
*/
function setMode (item) {
item.set("mode", (function () {
if (item.get("server")) {
return "server";
}
if (item.get("proxy")) {
return "proxy";
}
return "snippet";
})());
}
/**
* @param item
*/
function setScheme (item) {
var scheme = "http";
if (item.getIn(["server", "https"])) {
scheme = "https";
}
if (item.get("https")) {
scheme = "https";
}
if (item.getIn(["proxy", "url", "protocol"])) {
if (item.getIn(["proxy", "url", "protocol"]) === "https:") {
scheme = "https";
}
}
item.set("scheme", scheme);
}
/**
* @param item
*/
function setStartPath (item) {
if (item.get("proxy")) {
var path = item.getIn(["proxy", "url", "path"]);
if (path !== "/") {
item.set("startPath", path);
}
}
}
/**
* @param item
*/
function setNamespace(item) {
var namespace = item.getIn(["socket", "namespace"]);
if (_.isFunction(namespace)) {
item.setIn(["socket", "namespace"], namespace(defaultConfig.socket.namespace));
}
}
/**
* @param item
*/
function setServerOpts(item) {
if (item.get("server")) {
var indexarg = item.get("index") || item.getIn(["server", "index"]) || "index.html";
var optPath = ["server", "serveStaticOptions"];
if (item.get("directory")) {
item.setIn(["server", "directory"], true);
}
if (!item.getIn(optPath)) {
item.setIn(optPath, Immutable.Map({
index: indexarg
}));
} else {
if (!item.hasIn(optPath.concat(["index"]))) {
item.setIn(optPath.concat(["index"]), indexarg);
}
}
// cli extensions
if (item.get("extensions")) {
item.setIn(optPath.concat(["extensions"]), item.get("extensions"));
}
}
}
/**
* Back-compat fixes for rewriteRules being set to a boolean
*/
function fixRewriteRules (item) {
return item.update("rewriteRules", function (rr) {
return Immutable.List([]).concat(rr).filter(Boolean)
});
}
function fixSnippetOptions (item) {
var ignorePaths = item.getIn(["snippetOptions", "ignorePaths"]);
var includePaths = item.getIn(["snippetOptions", "whitelist"]);
if (ignorePaths) {
if (_.isString(ignorePaths)) {
ignorePaths = [ignorePaths];
}
ignorePaths = ignorePaths.map(ensureSlash);
item.setIn(["snippetOptions", "blacklist"], Immutable.List(ignorePaths));
}
if (includePaths) {
includePaths = includePaths.map(ensureSlash);
item.setIn(["snippetOptions", "whitelist"], Immutable.List(includePaths));
}
}
/**
* Enforce paths to begin with a forward slash
*/
function ensureSlash (item) {
if (item[0] !== "/") {
return "/" + item;
}
return item;
}
/**
*
*/
function setMiddleware (item) {
var mw = getMiddlwares(item);
item.set("middleware", mw);
}
/**
* top-level option, or given as part of the proxy/server option
* @param item
* @returns {*}
*/
function getMiddlwares (item) {
var mw = item.get("middleware");
var serverMw = item.getIn(["server", "middleware"]);
var proxyMw = item.getIn(["proxy", "middleware"]);
var list = Immutable.List([]);
if (mw) {
return listMerge(list, mw);
}
if (serverMw) {
return listMerge(list, serverMw);
}
if (proxyMw) {
return listMerge(list, proxyMw);
}
return list;
}
/**
* @param item
* @returns {*}
*/
function isList (item) {
return Immutable.List.isList(item);
}
/**
* @param list
* @param item
* @returns {*}
*/
function listMerge(list, item) {
if (_.isFunction(item)) {
list = list.push(item);
}
if (isList(item) && item.size) {
list = list.merge(item);
}
return list;
}
+185
View File
@@ -0,0 +1,185 @@
var Immutable = require("immutable");
var Map = Immutable.Map;
var isMap = Immutable.Map.isMap;
var List = Immutable.List;
var qs = require("qs");
var path = require("path");
var fs = require("fs");
var Plugin = Immutable.Record({
moduleName: "",
name: "",
active: true,
module: undefined,
options: Map({}),
via: "inline",
dir: process.cwd(),
init: undefined,
errors: List([])
});
/**
* Accept a string/object
* and resolve it into the plugin format above
* @param item
* @returns {*}
*/
function resolvePlugin(item) {
/**
* Handle when string was given, such as plugins: ['bs-html-injector']
*/
if (typeof item === "string") {
return getFromString(item);
}
if (!isMap(item)) {
return new Plugin().mergeDeep({errors: [new Error("Plugin not supported in this format")]});
}
if (item.has("module")) {
var nameOrObj = item.get("module");
var options = item.get("options");
/**
* The 'module' key can be a string, this allows
* inline plugin references, but with options
* eg:
*
* bs.init({
* plugins: [
* {
* module: './myjs-file.js'
* options: {
* files: "*.html"
* }
* }
* ]
* });
*/
if (typeof nameOrObj === "string") {
return getFromString(nameOrObj)
.mergeDeep({
options: options
});
}
/**
* If the plugin was given completely inline (because it needs options)
* eg:
*
* bs.init({
* plugins: [
* {
* module: {
* plugin: function() {
* console.log('My plugin code')
* }
* },
* options: {
* files: "*.html"
* }
* }
* ]
* })
*/
if (Immutable.Map.isMap(nameOrObj)) {
return new Plugin({
module: nameOrObj,
options: options
});
}
}
/**
* If a module was given directly. For example, ater calling require.
*
* eg:
* var myplugin = require('./some-js');
* bs.init({plugins: [myplugin]});
*/
if (item.has("plugin")) {
return new Plugin({
module: item
})
}
/**
* If we reach here, the plugin option was used incorrectly
*/
return new Plugin().mergeDeep({errors: [new Error("Plugin was not configured correctly")]})
}
module.exports.resolvePlugin = resolvePlugin;
/**
* Load a plugin from disk
* @param item
* @returns {*}
*/
function requirePlugin (item) {
/**
* if the "module" property already exists and
* is not a string, then we bail and don't bother looking
* for the file
*/
if (item.get("module") && typeof item.get("module") !== "string") {
return item;
}
try {
/**
* Try a raw node require() call - this will be how
* regular "npm installed" plugins wil work
*/
var maybe = path.resolve(process.cwd(), "node_modules", item.get("name"));
return item.set("module", require(maybe));
} catch (e) {
/**
* If require threw an MODULE_NOT_FOUND error, try again
* by resolving from cwd. This is needed since cli
* users will not add ./ to the front of a path (which
* node requires to resolve from cwd)
*/
if (e.code === "MODULE_NOT_FOUND") {
var maybe = path.resolve(process.cwd(), item.get("name"));
if (fs.existsSync(maybe)) {
return item.set("module", require(maybe));
} else {
/**
* Finally return a plugin that contains the error
* this will be picked up later and discarded
*/
return item.update("errors", function (errors) {
return errors.concat(e);
});
}
}
throw e;
}
}
module.exports.requirePlugin = requirePlugin;
function getFromString(string) {
/**
* We allow query strings for plugins, so always split on ?
*/
var split = string.split("?");
var outGoing = new Plugin({
moduleName: split[0],
name: split[0]
});
if (split.length > 1) {
return outGoing.update("options", function (opts) {
return opts.mergeDeep(qs.parse(split[1]));
});
}
return outGoing;
}
+17
View File
@@ -0,0 +1,17 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
function exit() {
if (browserSync.active) {
browserSync.events.emit("service:exit");
browserSync.cleanup();
}
}
return exit;
};
+32
View File
@@ -0,0 +1,32 @@
"use strict";
var _ = require("../../lodash.custom");
var merge = require("../cli/cli-options").merge;
/**
* @param {BrowserSync} browserSync
* @param {String} [name] - instance name
* @param {Object} pjson
* @returns {Function}
*/
module.exports = function (browserSync, name, pjson) {
return function () {
/**
* Handle new + old signatures for init.
*/
var args = require("../args")(_.toArray(arguments));
/**
* If the current instance is already running, just return an error
*/
if (browserSync.active) {
return args.cb(new Error("Instance: " + name + " is already running!"));
}
args.config.version = pjson.version;
return browserSync.init(merge(args.config), args.cb);
};
};
+19
View File
@@ -0,0 +1,19 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
return function (msg, timeout) {
if (msg) {
browserSync.events.emit("browser:notify", {
message: msg,
timeout: timeout || 2000,
override: true
});
}
};
};
+12
View File
@@ -0,0 +1,12 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
return function () {
browserSync.paused = true;
};
};
+65
View File
@@ -0,0 +1,65 @@
"use strict";
var _ = require("../../lodash.custom");
module.exports = {
/**
* Emit the internal `file:change` event
* @param {EventEmitter} emitter
* @param {string} path
* @param {boolean} [log]
*/
emitChangeEvent: function emitChangeEvent (emitter, path, log) {
emitter.emit("file:changed", {
path: path,
log: log,
namespace: "core",
event: "change"
});
},
/**
* Emit the internal `browser:reload` event
* @param {EventEmitter} emitter
*/
emitBrowserReload: function emitChangeEvent (emitter) {
emitter.emit("_browser:reload");
},
/**
* Emit the internal `stream:changed` event
* @param {EventEmitter} emitter
* @param {Array} changed
*/
emitStreamChangedEvent: function (emitter, changed) {
emitter.emit("stream:changed", {changed: changed});
},
/**
* This code handles the switch between .reload & .stream
* since 2.6.0
* @param name
* @param args
* @returns {boolean}
*/
isStreamArg: function (name, args) {
if (name === "stream") {
return true;
}
if (name !== "reload") {
return false;
}
var firstArg = args[0];
/**
* If here, it's reload with args
*/
if (_.isObject(firstArg)) {
if (!Array.isArray(firstArg) && Object.keys(firstArg).length) {
return firstArg.stream === true;
}
}
return false;
}
};
+67
View File
@@ -0,0 +1,67 @@
"use strict";
var utils = require("../utils");
var publicUtils = require("./public-utils");
var _ = require("../../lodash.custom");
var defaultConfig = require("../default-config");
var stream = require("./stream");
/**
* @param emitter
* @returns {Function}
*/
module.exports = function (emitter) {
/**
* Inform browsers about file changes.
*
* eg: reload("core.css")
*/
function browserSyncReload (opts) {
/**
* BACKWARDS COMPATIBILITY:
* Passing an object as the only arg to the `reload`
* method with at *least* the key-value pair of {stream: true},
* was only ever used for streams support - so it's safe to check
* for that signature here and defer to the
* dedicated `.stream()` method instead.
*/
if (_.isObject(opts)) {
if (!Array.isArray(opts) && Object.keys(opts).length) {
if (opts.stream === true) {
return stream(emitter)(opts);
}
}
}
/**
* Handle single string paths such as
* reload("core.css")
*/
if (typeof opts === "string" && opts !== "undefined") {
return publicUtils.emitChangeEvent(emitter, opts, true);
}
/**
* Handle an array of file paths such as
* reload(["core.css, "ie.css"])
*/
if (Array.isArray(opts)) {
return opts.forEach(function (filepath) {
publicUtils.emitChangeEvent(emitter, filepath, true);
});
}
/**
* At this point the argument given was neither an object,
* array or string so we simply perform a reload. This is to
* allow the following syntax to work as expected
*
* reload();
*/
return publicUtils.emitBrowserReload(emitter);
}
return browserSyncReload;
};
+12
View File
@@ -0,0 +1,12 @@
"use strict";
/**
* @param {BrowserSync} browserSync
* @returns {Function}
*/
module.exports = function (browserSync) {
return function () {
browserSync.paused = false;
};
};
File diff suppressed because one or more lines are too long
+98
View File
@@ -0,0 +1,98 @@
"use strict";
var path = require("path");
var micromatch = require("micromatch");
var utils = require("./public-utils");
/**
* @param emitter
* @returns {Function}
*/
module.exports = function (emitter) {
/**
* Return a transform/through stream that listens to file
* paths and fires internal Browsersync events.
* @param {{once: boolean, match: string|array}} [opts]
* @returns {Stream.Transform}
*/
function browserSyncThroughStream (opts) {
opts = opts || {};
var emitted = false;
var Transform = require("stream").Transform;
var reload = new Transform({objectMode: true});
var changed = [];
reload._transform = function (file, encoding, next) {
var stream = this;
/**
* End is always called to send the current file down
* stream. Browsersync never acts upon a stream,
* we only `listen` to it.
*/
function end () {
stream.push(file); // always send the file down-stream
next();
}
/**
* If {match: <pattern>} was provided, test the
* current filepath against it
*/
if (opts.match) {
if (!micromatch(file.path, opts.match, {dot: true}).length) {
return end();
}
}
/**
* if {once: true} provided, emit the reload event for the
* first file only
*/
if (opts.once === true && !emitted) {
utils.emitBrowserReload(emitter);
emitted = true;
} else { // handle multiple
if (opts.once === true && emitted) {
} else {
if (file.path) {
emitted = true;
utils.emitChangeEvent(emitter, file.path, false);
changed.push(path.basename(file.path));
}
}
}
end();
};
/**
* When this current operation has finished, emit the
* steam:changed event so that any loggers can pick up it
* @param next
* @private
*/
reload._flush = function (next) {
if (changed.length) {
utils.emitStreamChangedEvent(emitter, changed);
}
next();
};
return reload;
}
return browserSyncThroughStream;
};
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
openssl genrsa -des3 -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
cp server.key server.key.copy
openssl rsa -in server.key.copy -out server.key
rm server.key.copy
+19
View File
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe4CCQCir/8eGDIE/jANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJH
QjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE3MDQxMDExNDcyNloXDTI3MDQwODExNDcyNlowRTELMAkG
A1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMRLR2crKB4X/9pM3gR641iDscZWW3aqo70nUDxzo5Bhk8uupqz0EfdRoCLCUeQi
xVp3HJ1HqnilMW7dETGGkDHKdxJRjrkBrYHhE3Kw/LCC4tEb400F6Ikm6OudVPIB
P+CuwfNAw70KHSx/CtIrbTz0HhDC6XN0azp39pDLRBnWWluz3iU+rFLMx7YT2Q8k
1nQAwcXkzLjeU7txAt2pYGQUgvBQETO5RI7QQ0CmwaV4gfHWGABBTX34WQun7g1Q
YukrG3/4fVeNLzGW787FKCvL07BTymJTwXXbTTPXg4chw9p+YkLLPrr+AOVe/PF1
MJppDT3gKdKMHFo3vMycUf0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAEVmUYRrT
fCQmBDox3evqj4n8dJVStjgdw/7BPkm49SmGtaxsST/eUSGwT7dTvZBLov6BK/OW
+arhHZIvUY/DXlsV4NfCM4TK8KVefrwgd8ZsfQJW73L+FB0IOAY/6s+YKHm/wQGF
ptSOycJvEltsqfIegtYcvvD6c6SkSOvqApaF+Ai10+yiLe20KyOvM3PefZLV7mFE
0zCNyglZ75HftvHHV0wh82T2Et/R+txH+6dTwh065Dd6rrDzljtcAd2HC7B26ERK
dA2zJd9Y4eMz8osacmG/afVuR9rqtFGwdyZ1Kb5xQRzGWlrjvSmAFUx9W9iA4Ilv
3+56a5njSTFYKw==
-----END CERTIFICATE-----
+17
View File
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICoTCCAYkCAQAwRTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMRLR2crKB4X/9pM3gR641iDscZWW3aqo70nUDxz
o5Bhk8uupqz0EfdRoCLCUeQixVp3HJ1HqnilMW7dETGGkDHKdxJRjrkBrYHhE3Kw
/LCC4tEb400F6Ikm6OudVPIBP+CuwfNAw70KHSx/CtIrbTz0HhDC6XN0azp39pDL
RBnWWluz3iU+rFLMx7YT2Q8k1nQAwcXkzLjeU7txAt2pYGQUgvBQETO5RI7QQ0Cm
waV4gfHWGABBTX34WQun7g1QYukrG3/4fVeNLzGW787FKCvL07BTymJTwXXbTTPX
g4chw9p+YkLLPrr+AOVe/PF1MJppDT3gKdKMHFo3vMycUf0CAwEAAaAXMBUGCSqG
SIb3DQEJBzEIDAYxMjM0NTYwDQYJKoZIhvcNAQELBQADggEBABlVUaWK/UUovgPZ
+rqNG8/j6aggSCCye9CkauLB/WqhQFfLl9lWTYdUVmWweNU0SJwDU9lWF/TngipF
RZs6501DkXKxaDT9/3JYg4lRz6zHLy+a77pavJOeN0+cFAPZZuGyxZvYHFYPVSVH
EeJL6bO/nZ/ARgIp0YNkQblDarxq1ARj7YT1Z24D5KgO1kQ55+fCY/wtct8TLGk9
ujvWBbFEBSrJCDjRQeSctlP5qG8yfeJqZwZzo4PQMUwABhNLGUz0z0ceK8W1QnOm
T+nCN5Fni04wO4dAZvYPp6wmU0Gpi2XBihbFl9CT3B5AmJmM8hQVpuQlmXeXT0FO
pOZFpko=
-----END CERTIFICATE REQUEST-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAxEtHZysoHhf/2kzeBHrjWIOxxlZbdqqjvSdQPHOjkGGTy66m
rPQR91GgIsJR5CLFWnccnUeqeKUxbt0RMYaQMcp3ElGOuQGtgeETcrD8sILi0Rvj
TQXoiSbo651U8gE/4K7B80DDvQodLH8K0ittPPQeEMLpc3RrOnf2kMtEGdZaW7Pe
JT6sUszHthPZDyTWdADBxeTMuN5Tu3EC3algZBSC8FARM7lEjtBDQKbBpXiB8dYY
AEFNffhZC6fuDVBi6Ssbf/h9V40vMZbvzsUoK8vTsFPKYlPBddtNM9eDhyHD2n5i
Qss+uv4A5V788XUwmmkNPeAp0owcWje8zJxR/QIDAQABAoIBADbeT/wvnQwkazkL
CXg5HXltfnDRTMmz0wcZiR0MueiuzdA+ZoqrwqXeJCPzK07YxU+PQelY0fbdPh8e
HiM42O+CB5yQPZPLO0O1tWj2vftc6qfG4tdx0lkcDjlmBguLe96DGuWy8cPSousA
K/cpemRyXEEVKopCPYLfa4V3u/Z4be2U/39KNjVkHFhSdSYQl6ferhEfUPwTPi7O
7l1/QUBabqN5FzNc2TeMVhhcJkXtYqF3RxGsaRfT0lK/j2hpbX7Bn2T0CfA/40jY
2OCERqFPfZWx/ShTT52b3fyX/FEua7Nukq/MZdYZou63dDIjCQQyTJSflX6lVojO
SuUoumECgYEA6CSkLiKcRLlTfec3LkjqkWtXR5ibL33g/H1fsZEQKFOyMbIXpUkX
Hybpku8NGeetjKynO3yRirp+NiBHGPn3cHc9WJ5GGG1ew9hRQ9QzyC3Tit15TDbu
J8i50/MaQHZSiUCnPQ/ceIZCNz8STcsEz87o/7utRLJKOvIIAPj+/8kCgYEA2Hd/
v5oUroMRbtzPtMJDMHiGEQyNxEGDNqcuxgXSmiEEqPLfk2qR3yLffzA9UQOg4wkX
/dSXsyomPriKWTvADXu1lNdkPGmW/1tk+onnHu6qgOalva30ZKhtteVjUqxEJEke
mHhNHyIVuj6lExLw9LZhVvzoOi+aj4AD+DRS4pUCgYBEtuveOCJ3eUAMiY9c5PqB
9vsL11FAOouJUXcs8VqOBVA+w4+aPktYzkTfWGFRZLGLbWPHCPVv0gof7Wf+Laef
o7wF6junaWBeqj5LzJlTTLVMaohIFg5iuli/Mzt3D08ZD4kxWuuQxXT+M24wlsKi
3IU9hYkhR4EPd6sE1q9seQKBgEpQRBAgModywbJgpgH1SyHBzqzdtXGx1/0USg97
gkCdoz7pGm4+gNOs4jOE+Rft+fbXcWAX8vh0OOsBaaWWyKkYVk9B3syKp2cFFlaY
rzrETs6v4CiNJsDDvd5bYMzKDR6z54gKjNdqWTE2Pm+c6hHo5uP5MTSAkTxAg5xb
QjU9AoGAaYPXlm3IKVO12FgNg/ffduooi0PKa1yRNJGnhpQKNvBQXs8eV+CQ83aK
kQHUExuJDrOfsC2iwF/2ZywXhEfbhL7ar0aw5zrhV+r7qvYFWxu/YoLoNVMDByw5
wAN0oIbsGWYmtIIti8+b9IcacTbAZ79ctlTLb1HCyPMosHxDkv8=
-----END RSA PRIVATE KEY-----
+95
View File
@@ -0,0 +1,95 @@
"use strict";
var enableDestroy = require("server-destroy");
var _ = require("../../lodash.custom");
/**
* Browsersync server
* Three available modes: Snippet, Server or Proxy
*/
module.exports.plugin = function (bs) {
var debug = bs.debug;
var proxy = bs.options.get("proxy");
var type = bs.options.get("mode");
var bsServer = createServer(bs);
if (type === "server" || type === "snippet") {
debug("Static Server running ({magenta:%s}) ...", bs.options.get("scheme"));
}
if (proxy) {
debug("Proxy running, proxing: {magenta:%s}", proxy.get("target"));
}
if (bsServer) {
/**
* Allow server to be destroyed gracefully
*/
enableDestroy(bsServer.server);
/**
* Listen on the available port
*/
bsServer.server.listen(bs.options.get("port"));
/**
* Hack to deal with https://github.com/socketio/socket.io/issues/1602#issuecomment-224270022
*/
bs.registerCleanupTask(function () {
if (bs.io && bs.io.sockets) {
setCloseReceived(bs.io.sockets);
}
if (bs.ui && bs.ui.socket) {
setCloseReceived(bs.ui.socket);
}
});
/**
* Destroy the server on cleanup
*/
bs.registerCleanupTask(function () {
bsServer.server.destroy();
});
}
function setCloseReceived(io) {
Object.keys(io.sockets).forEach(function (key) {
_.set(io.sockets[key], "conn.transport.socket._closeReceived", true);
});
}
debug("Running mode: %s", type.toUpperCase());
return {
server: bsServer.server,
app: bsServer.app
};
};
/**
* Launch the server for serving the client JS plus static files
* @param {BrowserSync} bs
* @returns {{staticServer: (http.Server), proxyServer: (http.Server)}}
*/
function createServer (bs) {
var proxy = bs.options.get("proxy");
var server = bs.options.get("server");
if (!proxy && !server) {
return require("./snippet-server")(bs);
}
if (proxy) {
return require("./proxy-server")(bs);
}
if (server) {
return require("./static-server")(bs);
}
}
module.exports.createServer = createServer;
+195
View File
@@ -0,0 +1,195 @@
"use strict";
var httpProxy = require("http-proxy");
var utils = require("./utils");
var proxyUtils = require("./proxy-utils");
var Immutable = require("immutable");
var Map = require("immutable").Map;
var List = require("immutable").List;
/**
* Default options that are passed along to http-proxy
*/
var defaultHttpProxyOptions = Map({
/**
* This ensures targets are more likely to
* accept each request
*/
changeOrigin: true,
/**
* This handles redirects
*/
autoRewrite: true,
/**
* This allows our self-signed certs to be used for development
*/
secure: false,
ws: true
});
var defaultCookieOptions = Map({
stripDomain: true
});
var ProxyOption = Immutable.Record({
route: "",
target: "",
rewriteRules: true,
/**
* Functions to be called on proxy request
* with args [proxyReq, req, res, options]
*/
proxyReq: List([]),
/**
* Functions to be called on proxy response
* with args [proxyRes, req, res]
*/
proxyRes: List([]),
/**
* Functions to be called on proxy response
* with args [proxyReq, req, socket, options, head]
*/
proxyReqWs: List([]),
errHandler: undefined,
url: Map({}),
proxyOptions: Map(defaultHttpProxyOptions),
cookies: Map(defaultCookieOptions),
ws: false,
middleware: List([]),
reqHeaders: undefined
});
/**
* @param {BrowserSync} bs
* @param {String} scripts
* @returns {*}
*/
module.exports = function createProxyServer (bs) {
var opt = new ProxyOption().mergeDeep(bs.options.get("proxy"));
var proxy = httpProxy.createProxyServer(opt.get("proxyOptions").set("target", opt.get("target")).toJS());
var target = opt.get("target");
var proxyReq = getProxyReqFunctions(opt.get("proxyReq"), opt, bs);
var proxyRes = getProxyResFunctions(opt.get("proxyRes"), opt);
var proxyResWs = opt.get("proxyReqWs");
bs.options = bs.options.update("middleware", function (mw) {
return mw.concat({
id: "Browsersync Proxy",
route: opt.get("route"),
handle: function (req, res) {
proxy.web(req, res, {
target: target
});
}
});
});
var app = utils.getBaseApp(bs);
/**
* @type {*|{server, app}}
*/
var browserSyncServer = utils.getServer(app, bs.options);
browserSyncServer.proxy = proxy;
if (opt.get("ws")) {
// debug(`+ ws upgrade for: ${x.get("target")}`);
browserSyncServer.server.on("upgrade", function (req, socket, head) {
proxy.ws(req, socket, head);
});
}
/**
* Add any user provided functions for proxyReq, proxyReqWs and proxyRes
*/
applyFns("proxyReq", proxyReq);
applyFns("proxyRes", proxyRes);
applyFns("proxyReqWs", proxyResWs);
/**
* Handle Proxy errors
*/
proxy.on("error", function (err) {
if (typeof opt.get("errHandler") === "function") {
opt.get("errHandler").call(null, err);
}
});
/**
* Apply functions to proxy events
* @param {string} name - the name of the http-proxy event
* @param {Array} fns - functions to call on each event
*/
function applyFns (name, fns) {
if (!List.isList(fns)) fns = [fns];
proxy.on(name, function () {
var args = arguments;
fns.forEach(function(fn) {
if (typeof fn === "function") {
fn.apply(null, args);
}
});
});
}
return browserSyncServer;
};
/**
* @param resFns
* @returns {*}
*/
function getProxyResFunctions (resFns, opt) {
if (opt.getIn(["cookies", "stripDomain"])) {
return resFns.push(proxyUtils.checkCookies);
}
return resFns;
}
/**
* @param reqFns
* @returns {*}
*/
function getProxyReqFunctions (reqFns, opt, bs) {
var reqHeaders = opt.getIn(["reqHeaders"]);
if (!reqHeaders) {
return reqFns;
}
/**
* Back-compat for old `reqHeaders` option here a
* function was given that returned an object
* where key:value was header-name:header-value
* This didn't really work as it clobbered all other headers,
* but it remains for the unlucky few who used it.
*/
if (typeof reqHeaders === "function") {
var output = reqHeaders.call(bs, opt.toJS());
if (Object.keys(output).length) {
return reqFns.concat(function (proxyReq) {
Object.keys(output).forEach(function (key) {
proxyReq.setHeader(key, output[key]);
});
});
}
}
/**
* Now, if {key:value} given, set the each header
*
* eg: reqHeaders: {
* 'is-dev': 'true'
* }
*/
if (Map.isMap(reqHeaders)) {
return reqFns.concat(function (proxyReq) {
reqHeaders.forEach(function (value, key) {
proxyReq.setHeader(key, value);
});
});
}
return reqFns;
}
+138
View File
@@ -0,0 +1,138 @@
var url = require("url");
module.exports.rewriteLinks = function (userServer) {
var host = userServer.hostname;
var string = host;
var port = userServer.port;
if (host && port) {
if (parseInt(port, 10) !== 80) {
string = host + ":" + port;
}
}
return {
match: new RegExp("https?:\\\\/\\\\/" + string + "|('|\")\\\/\\\/" + string + "|https?://" + string + "(\/)?|('|\")(https?://|/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp("https?:\\\\/\\\\/" + string + "|https?://" + string + "(\/)?|('|\")(https?://|/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp("https?:\\\\?/\\\\?/" + string + "(\/)?|('|\")(https?://|\\\\?/|\\.)?" + string + "(\/)?(.*?)(?=[ ,'\"\\s])", "g"),
//match: new RegExp('https?://' + string + '(\/)?|(\'|")(https?://|/|\\.)?' + string + '(\/)?(.*?)(?=[ ,\'"\\s])', 'g'),
//match: new RegExp("https?:\\\\/\\\\/" + string, "g"),
fn: function (req, res, match) {
var proxyUrl = req.headers["host"];
/**
* Reject subdomains
*/
if (match[0] === ".") {
return match;
}
var captured = match[0] === "'" || match[0] === "\"" ? match[0] : "";
/**
* allow http https
* @type {string}
*/
var pre = "//";
if (match[0] === "'" || match[0] === "\"") {
match = match.slice(1);
}
/**
* parse the url
* @type {number|*}
*/
var out = url.parse(match);
/**
* If host not set, just do a simple replace
*/
if (!out.host) {
string = string.replace(/^(\/)/, "");
return captured + match.replace(string, proxyUrl);
}
/**
* Only add trailing slash if one was
* present in the original match
*/
if (out.path === "/") {
if (match.slice(-1) === "/") {
out.path = "/";
} else {
out.path = "";
}
}
/**
* Finally append all of parsed url
*/
return [
captured,
pre,
proxyUrl,
out.path || "",
out.hash || ""
].join("");
}
};
};
/**
* Remove 'domain' from any cookies
* @param {Object} res
*/
module.exports.checkCookies = function checkCookies (res) {
if (typeof(res.headers["set-cookie"]) !== "undefined") {
res.headers["set-cookie"] = res.headers["set-cookie"].map(function (item) {
return rewriteCookies(item);
});
}
};
/**
* Remove the domain from any cookies.
* @param rawCookie
* @returns {string}
*/
function rewriteCookies (rawCookie) {
var objCookie = (function () {
// simple parse function (does not remove quotes)
var obj = {};
var pairs = rawCookie.split(/; */);
pairs.forEach( function( pair ) {
var eqIndex = pair.indexOf("=");
// skip things that don't look like key=value
if (eqIndex < 0) {
return;
}
var key = pair.substr(0, eqIndex).trim();
obj[key] = pair.substr(eqIndex + 1, pair.length).trim();
});
return obj;
})();
var pairs = Object.keys(objCookie)
.filter(function (item) {
return item !== "domain";
})
.map(function (key) {
return key + "=" + objCookie[key];
});
if (rawCookie.match(/httponly/i)) {
pairs.push("HttpOnly");
}
return pairs.join("; ");
}
module.exports.rewriteCookies = rewriteCookies;
+16
View File
@@ -0,0 +1,16 @@
"use strict";
var connect = require("connect");
var serverUtils = require("./utils.js");
/**
* Create a server for the snippet
* @param {BrowserSync} bs
* @param scripts
* @returns {*}
*/
module.exports = function createSnippetServer (bs, scripts) {
var app = serverUtils.getBaseApp(bs, bs.options, scripts);
return serverUtils.getServer(app, bs.options);
};
+79
View File
@@ -0,0 +1,79 @@
"use strict";
var connect = require("connect");
var serverUtils = require("./utils.js");
var resolve = require("path").resolve;
var utils = require("../utils.js");
var serveStatic = require("serve-static");
var serveIndex = require("serve-index");
/**
* @param {BrowserSync} bs
* @param scripts
* @returns {*}
*/
module.exports = function createServer (bs) {
var options = bs.options;
var server = options.get("server");
var basedirs = utils.arrayify(server.get("baseDir"));
var serveStaticOptions = server.get("serveStaticOptions").toJS(); // passed to 3rd party
var _serveStatic = 0;
var _routes = 0;
bs.options = bs.options
/**
* Add directory Middleware if given in server.directory
*/
.update("middleware", function (mw) {
if (!server.get("directory")) {
return mw;
}
return mw.concat({
route: "",
handle: serveIndex(resolve(basedirs[0]), {icons:true}),
id: "Browsersync Server Directory Middleware"
});
})
/**
* Add middleware for server.baseDir Option
*/
.update("middleware", function (mw) {
return mw.concat(basedirs.map(function (root) {
return {
route: "",
id: "Browsersync Server ServeStatic Middleware - " + _serveStatic++,
handle: serveStatic(resolve(root), serveStaticOptions)
}
}));
})
/**
* Add middleware for server.routes
*/
.update("middleware", function (mw) {
if (!server.get("routes")) {
return mw;
}
return mw.concat(server.get("routes").map(function (root, urlPath) {
// strip trailing slash
if (urlPath[urlPath.length - 1] === "/") {
urlPath = urlPath.slice(0, -1);
}
return {
route: urlPath,
id: "Browsersync Server Routes Middleware - " + _routes++,
handle: serveStatic(resolve(root))
}
}));
});
var app = serverUtils.getBaseApp(bs);
/**
* Finally, return the server + App
*/
return serverUtils.getServer(app, bs.options);
};
+453
View File
@@ -0,0 +1,453 @@
"use strict";
var fs = require("fs");
var path = require("path");
var join = require("path").join;
var connect = require("connect");
var Immutable = require("immutable");
var http = require("http");
var https = require("https");
var Map = require("immutable").Map;
var fromJS = require("immutable").fromJS;
var List = require("immutable").List;
var snippet = require("./../snippet").utils;
var _ = require("./../../lodash.custom");
var serveStatic = require("serve-static");
var logger = require("../logger");
var snippetUtils = require("../snippet").utils;
var lrSnippet = require("resp-modifier");
var utils = require("../utils");
function getCa (options) {
var caOption = options.getIn(["https", "ca"]);
// if not provided, use Browsersync self-signed
if (typeof caOption === "undefined") {
return fs.readFileSync(join(__dirname, "certs", "server.csr"));
}
// if a string was given, read that file from disk
if (typeof caOption === "string") {
return fs.readFileSync(caOption);
}
// if an array was given, read all
if (List.isList(caOption)) {
return caOption.toArray().map(function (x) {
return fs.readFileSync(x);
});
}
}
function getKey(options) {
return fs.readFileSync(options.getIn(["https", "key"]) || join(__dirname, "certs", "server.key"));
}
function getCert(options) {
return fs.readFileSync(options.getIn(["https", "cert"]) || join(__dirname, "certs", "server.crt"));
}
function getHttpsServerDefaults (options) {
return fromJS({
key: getKey(options),
cert: getCert(options),
ca: getCa(options),
passphrase: ""
});
}
function getPFXDefaults (options) {
return fromJS({
pfx: fs.readFileSync(options.getIn(["https", "pfx"]))
});
}
var serverUtils = {
/**
* @param options
* @returns {{key, cert}}
*/
getHttpsOptions: function (options) {
var userOption = options.get("https");
if (Map.isMap(userOption)) {
if (userOption.has("pfx")) {
return userOption.mergeDeep(getPFXDefaults(options));
}
return userOption.mergeDeep(getHttpsServerDefaults(options));
}
return getHttpsServerDefaults(options);
},
/**
* Get either http or https server
* or use the httpModule provided in options if present
*/
getServer: function (app, options) {
return {
server: (function () {
var httpModule = serverUtils.getHttpModule(options);
if (options.get("scheme") === "https") {
var opts = serverUtils.getHttpsOptions(options);
return httpModule.createServer(opts.toJS(), app);
}
return httpModule.createServer(app);
})(),
app: app
};
},
getHttpModule: function (options) {
/**
* Users may provide a string to be used by nodes
* require lookup.
*/
var httpModule = options.get("httpModule");
if (typeof httpModule === "string") {
/**
* Note, this could throw, but let that happen as
* the error message good enough.
*/
var maybe = path.resolve(process.cwd(), "node_modules", httpModule);
return require(maybe);
}
if (options.get("scheme") === "https") {
return https;
}
return http;
},
getMiddlewares: function (bs) {
var clientJs = bs.pluginManager.hook("client:js", {
port: bs.options.get("port"),
options: bs.options
});
var scripts = bs.pluginManager.get("client:script")(
bs.options.toJS(),
clientJs,
"middleware"
);
var defaultMiddlewares = [
{
id: "Browsersync HTTP Protocol",
route: require("../config").httpProtocol.path,
handle: require("../http-protocol").middleware(bs)
},
{
id: "Browsersync IE8 Support",
route: "",
handle: snippet.isOldIe(bs.options.get("excludedFileTypes").toJS())
},
{
id: "Browsersync Response Modifier",
route: "",
handle: serverUtils.getSnippetMiddleware(bs)
},
{
id: "Browsersync Client - versioned",
route: bs.options.getIn(["scriptPaths", "versioned"]),
handle: scripts
},
{
id: "Browsersync Client",
route: bs.options.getIn(["scriptPaths", "path"]),
handle: scripts
}
];
/**
* Add cors middleware to the front of the stack
* if a user provided a 'cors' flag
*/
if (bs.options.get("cors")) {
defaultMiddlewares.unshift({
id: "Browsersync CORS support",
route: "",
handle: serverUtils.getCorsMiddlewware()
})
}
/**
* Add serve static middleware
*/
if (bs.options.get("serveStatic")) {
var ssMiddlewares = serverUtils.getServeStaticMiddlewares(bs.options.get("serveStatic"), bs.options.get("serveStaticOptions", Immutable.Map({})).toJS());
var withErrors = ssMiddlewares.filter(function(x) { return x.get("errors").size > 0 });
var withoutErrors = ssMiddlewares.filter(function(x) { return x.get("errors").size === 0 });
if (withErrors.size) {
withErrors.forEach(function (item) {
logger.logger.error("{red:Warning!} %s", item.getIn(["errors", 0, "data", "message"]));
});
}
if (withoutErrors.size) {
withoutErrors.forEach(function (item) {
defaultMiddlewares.push.apply(defaultMiddlewares, item.get("items").toJS());
});
}
}
/**
* Add user-provided middlewares
*/
var userMiddlewares = bs.options.get("middleware").map(normaliseMiddleware).toArray();
var beforeMiddlewares = userMiddlewares.filter(function (x) { return x.override; });
var afterMiddlewares = userMiddlewares.filter(function (x) { return !x.override; });
return [].concat(beforeMiddlewares, defaultMiddlewares, afterMiddlewares);
function normaliseMiddleware(item) {
/**
* Object given in options, which
* ended up being a Map
*/
if (Map.isMap(item)) {
return item.toJS();
}
/**
* Single function
*/
if (typeof item === "function") {
return {
route: "",
handle: item
}
}
/**
* Plain obj
*/
if ((item.route !== undefined) && item.handle) {
return item;
}
}
},
getBaseApp: function (bs) {
var app = connect();
var middlewares = serverUtils.getMiddlewares(bs);
/**
* Add all internal middlewares
*/
middlewares.forEach(function (item) {
app.stack.push(item);
});
return app;
},
getSnippetMiddleware: function (bs) {
var rules = [];
var blacklist = List([])
.concat(bs.options.getIn(["snippetOptions", "ignorePaths"]))
.concat(bs.options.getIn(["snippetOptions", "blacklist"]))
.filter(Boolean);
var whitelist = List([])
.concat(bs.options.getIn(["snippetOptions", "whitelist"]));
// Snippet
rules.push(snippetUtils.getRegex(bs.options.get("snippet"), bs.options.get("snippetOptions")));
// User
bs.options.get("rewriteRules").forEach(function (rule) {
if (Map.isMap(rule)) {
rules.push(rule.toJS());
}
if (_.isPlainObject(rule)) {
rules.push(rule);
}
});
// Proxy
if (bs.options.get("proxy")) {
var proxyRule = require("./proxy-utils").rewriteLinks(bs.options.getIn(["proxy", "url"]).toJS());
rules.push(proxyRule);
}
var lr = lrSnippet.create({
rules: rules,
blacklist: blacklist.toArray(),
whitelist: whitelist.toArray()
});
return lr.middleware;
},
getCorsMiddlewware: function () {
return function (req, res, next) {
// Website you wish to allow to connect
res.setHeader("Access-Control-Allow-Origin", "*");
// Request methods you wish to allow
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
// Request headers you wish to allow
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader("Access-Control-Allow-Credentials", true);
next();
}
},
/**
* @param ssOption
* @param serveStaticOptions
* @returns {*}
*/
getServeStaticMiddlewares: function (ssOption, serveStaticOptions) {
return ssOption.map(function (dir, i) {
/**
* When a user gives a plain string only, eg:
* serveStatic: ['./temp']
* ->
* This means a middleware will be created with
* route: ''
* handle: serveStatic('./temp', options)
*/
if (_.isString(dir)) {
return getFromString(dir)
}
/**
* If a user gave an object eg:
* serveStatic: [{route: "", dir: ["test", "./tmp"]}]
* ->
* This means we need to create a middle for each route + dir combo
*/
if (Immutable.Map.isMap(dir)) {
return getFromMap(dir, i);
}
/**
* At this point, an item in the serveStatic array was not a string
* or an object so we return an error that can be logged
*/
return fromJS({
items: [],
errors: [{
type: "Invalid Type",
data: {
message: "Only strings and Objects (with route+dir) are supported for the ServeStatic option"
}
}]
})
});
/**
* @param {string} x
* @returns {string}
*/
function getRoute (x) {
if (x === "") return "";
return x[0] === "/" ? x : "/" + x;
}
/**
* @param dir
* @returns {Map}
*/
function getFromString(dir) {
return fromJS({
items: [
{
route: "",
handle: serveStatic(dir, serveStaticOptions)
}
],
errors: []
})
}
/**
* @param dir
* @returns {Map}
*/
function getFromMap(dir) {
var ssOptions = (function () {
if (dir.get("options")) {
return dir.get("options").toJS();
}
return {}
})();
var route = Immutable.List([]).concat(dir.get("route")).filter(_.isString);
var _dir = Immutable.List([]).concat(dir.get("dir")).filter(_.isString);
if (_dir.size === 0) {
return fromJS({
items: [],
errors: [{
type: "Invalid Object",
data: {
message: "Serve Static requires a 'dir' property when using an Object"
}
}]
})
}
var ssItems = (function () {
/**
* iterate over every 'route' item
* @type {Immutable.List<any>|Immutable.List<*>|Immutable.List<any>|*}
*/
var routeItems = (function () {
/**
* If no 'route' was given, assume we want to match all
* paths
*/
if (route.size === 0) {
return _dir.map(function (dirString) {
return Map({
route: "",
dir: dirString
});
});
}
return route.reduce(function (acc, routeString) {
/**
* For each 'route' item, also iterate through 'dirs'
* @type {Immutable.Iterable<K, M>}
*/
var perDir = _dir.map(function (dirString) {
return Map({
route: getRoute(routeString),
dir: dirString
})
});
return acc.concat(perDir);
}, List([]));
})();
/**
* Now create a serverStatic Middleware for each item
*/
return routeItems.map(function (routeItem) {
return routeItem.merge({
handle: serveStatic(routeItem.get("dir"), ssOptions)
});
});
})();
return fromJS({
items: ssItems,
errors: []
});
}
}
};
module.exports = serverUtils;
+106
View File
@@ -0,0 +1,106 @@
"use strict";
var connectUtils = require("./connect-utils");
var config = require("./config");
var lrSnippet = require("resp-modifier");
var path = require("path");
var _ = require("../lodash.custom");
var utils = require("./utils");
var fs = require("fs");
/**
* Utils for snippet injection
*/
var snippetUtils = {
/**
* @param {String} url
* @param {Array} excludeList
* @returns {boolean}
*/
isExcluded: function (url, excludeList) {
var extension = path.extname(url);
if (extension) {
if (~url.indexOf("?")) {
return true;
}
extension = extension.slice(1);
return _.includes(excludeList, extension);
}
return false;
},
/**
* @param {String} snippet
* @param {Object} options
* @returns {{match: RegExp, fn: Function}}
*/
getRegex: function (snippet, options) {
var fn = options.getIn(["rule", "fn"]);
return {
match: options.getIn(["rule", "match"]),
fn: function (req, res, match) {
return fn.apply(null, [snippet, match]);
},
once: true,
id: "bs-snippet"
};
},
getSnippetMiddleware: function (snippet, options, rewriteRules) {
return lrSnippet.create(snippetUtils.getRules(snippet, options, rewriteRules));
},
getRules: function (snippet, options, rewriteRules) {
var rules = [snippetUtils.getRegex(snippet, options)];
if (rewriteRules) {
rules = rules.concat(rewriteRules);
}
return {
rules: rules,
blacklist: utils.arrayify(options.get("blacklist")),
whitelist: utils.arrayify(options.get("whitelist"))
};
},
/**
* @param {Object} req
* @param {Array} [excludeList]
* @returns {Object}
*/
isOldIe: function (excludeList) {
return function (req, res, next) {
var ua = req.headers["user-agent"];
var match = /MSIE (\d)\.\d/.exec(ua);
if (match) {
if (parseInt(match[1], 10) < 9) {
if (!snippetUtils.isExcluded(req.url, excludeList)) {
req.headers["accept"] = "text/html";
}
}
}
next();
}
},
/**
* @param {Number} port
* @param {BrowserSync.options} options
* @returns {String}
*/
getClientJs: function (port, options) {
var socket = snippetUtils.getSocketScript();
var noConflictJs = "window.___browserSync___oldSocketIo = window.io;";
return noConflictJs + socket + ";" + connectUtils.socketConnector(options);
},
/**
* @returns {String}
*/
getSocketScript: function () {
return fs.readFileSync(path.join(__dirname, config.socketIoScript), "utf-8");
}
};
module.exports.utils = snippetUtils;
+100
View File
@@ -0,0 +1,100 @@
"use strict";
var socket = require("socket.io");
var utils = require("./server/utils");
var Steward = require("emitter-steward");
/**
* Plugin interface
* @returns {*|function(this:exports)}
*/
module.exports.plugin = function (server, clientEvents, bs) {
return exports.init(server, clientEvents, bs);
};
/**
* @param {http.Server} server
* @param clientEvents
* @param {BrowserSync} bs
*/
module.exports.init = function (server, clientEvents, bs) {
var emitter = bs.events;
var socketConfig = bs.options
.get("socket")
.toJS();
if (bs.options.get("mode") === "proxy" && bs.options.getIn(["proxy", "ws"])) {
server = utils.getServer(null, bs.options).server;
server.listen(bs.options.getIn(["socket", "port"]));
bs.registerCleanupTask(function () {
server.close();
});
}
var socketIoConfig = socketConfig.socketIoOptions;
socketIoConfig.path = socketConfig.path;
var io = socket(server, socketIoConfig);
// Override default namespace.
io.sockets = io.of(socketConfig.namespace);
io.set("heartbeat interval", socketConfig.clients.heartbeatTimeout);
var steward = new Steward(emitter);
bs.registerCleanupTask(steward.destroy.bind(steward));
/**
* Listen for new connections
*/
io.sockets.on("connection", handleConnection);
/**
* Handle each new connection
* @param {Object} client
*/
function handleConnection (client) {
// set ghostmode callbacks
if (bs.options.get("ghostMode")) {
addGhostMode(client);
}
client.emit("connection", bs.options.toJS()); //todo - trim the amount of options sent to clients
emitter.emit("client:connected", {
ua: client.handshake.headers["user-agent"]
});
}
/**
* @param {string} event
* @param {Socket.client} client
* @param {Object} data
*/
function handleClientEvent(event, client, data) {
if (steward.valid(client.id)) {
client.broadcast.emit(event, data);
}
}
/**
* @param client
*/
function addGhostMode (client) {
clientEvents.forEach(addEvent);
function addEvent(event) {
client.on(event, handleClientEvent.bind(null, event, client));
}
}
return io;
};
+10
View File
@@ -0,0 +1,10 @@
window.___browserSync___ = {};
___browserSync___.io = window.io;
window.io = window.___browserSync___oldSocketIo;
window.___browserSync___oldSocketIo=undefined;
___browserSync___.socketConfig = %config%;
___browserSync___.url = %url%;
if (location.protocol == "https:" && /^http:/.test(___browserSync___.url)) {
___browserSync___.url = ___browserSync___.url.replace(/^http:/, "https:");
}
___browserSync___.socket = ___browserSync___.io(___browserSync___.url, ___browserSync___.socketConfig);
+1
View File
@@ -0,0 +1 @@
<script %async% id="__bs_script__" src="%script%"></script>
+3
View File
@@ -0,0 +1,3 @@
<script id="__bs_script__">//<![CDATA[
document.write("<script %async% src='%script%'><\/script>".replace("HOST", location.hostname));
//]]></script>
+35
View File
@@ -0,0 +1,35 @@
"use strict";
var _ = require("../lodash.custom");
var utils = require("util");
/**
* @param {BrowserSync} bs
* @param {Function} cb
*/
module.exports = function (bs, cb) {
var opts = {};
var options = bs.options;
var port = options.get("port");
if (_.isString(options.get("tunnel"))) {
opts.subdomain = options.get("tunnel");
}
bs.debug("Requesting a tunnel connection on port: {magenta:%s}", port);
bs.debug("Requesting a tunnel connection with options: {magenta:%s}", utils.inspect(opts));
require("localtunnel")(port, opts, function (err, tunnel) {
if (err) {
return cb(err);
}
tunnel.on("error", function (err) {
bs.logger.info("Localtunnel issue: " + err.message);
bs.logger.info("Oops! The localtunnel appears to have disconnected. Reconnecting...");
});
return cb(null, tunnel);
});
};
+310
View File
@@ -0,0 +1,310 @@
"use strict";
var _ = require("../lodash.custom");
var devIp = require("dev-ip")();
var Immutable = require("immutable");
var portScanner = require("portscanner");
var path = require("path");
var List = require("immutable").List;
var UAParser = require("ua-parser-js");
var parser = new UAParser();
var utils = {
/**
* @param {Object} options
* @returns {String|boolean} - the IP address
* @param devIp
*/
getHostIp: function (options, devIp) {
if (options) {
var host = options.get("host");
if (host && host !== "localhost") {
return host;
}
if (options.get("detect") === false || !devIp.length) {
return false;
}
}
return devIp.length ? devIp[0] : false;
},
/**
* Set URL Options
*/
getUrlOptions: function (options) {
var scheme = options.get("scheme");
var port = options.get("port");
var urls = {};
if (options.get("online") === false) {
urls.local = utils.getUrl(scheme + "://localhost:" + port, options);
return Immutable.fromJS(urls);
}
var external = utils.xip(utils.getHostIp(options, devIp), options);
var localhost = "localhost";
if (options.get("xip")) {
localhost = "127.0.0.1";
}
localhost = utils.xip(localhost, options);
return Immutable.fromJS(utils.getUrls(external, localhost, scheme, options));
},
/**
* Append a start path if given in options
* @param {String} url
* @param {Object} options
* @returns {String}
*/
getUrl: function (url, options) {
var prefix = "/";
var startPath = options.get("startPath");
if (startPath) {
if (startPath.charAt(0) === "/") {
prefix = "";
}
url = url + prefix + startPath;
}
return url;
},
/**
* @param {String} external
* @param {String} local
* @param {String} scheme
* @param {Object} options
* @returns {{local: string, external: string}}
*/
getUrls: function (external, local, scheme, options) {
var urls = {
local: utils.getUrl(utils._makeUrl(scheme, local, options.get("port")), options)
};
if (external !== local) {
urls.external = utils.getUrl(utils._makeUrl(scheme, external, options.get("port")), options);
}
return urls;
},
/**
* @param {String} scheme
* @param {String} host
* @param {Number} port
* @returns {String}
* @private
*/
_makeUrl: function (scheme, host, port) {
return scheme + "://" + host + ":" + port;
},
/**
* Get ports
* @param {Object} options
* @param {Function} cb
*/
getPorts: function (options, cb) {
var port = options.get("port");
var ports = options.get("ports"); // backwards compatibility
var max;
if (ports) {
port = ports.get("min");
max = ports.get("max") || null;
}
utils.getPort(port, max, cb);
},
getPort: function (port, max, cb) {
portScanner.findAPortNotInUse(port, max, {
host: "localhost",
timeout: 1000
}, cb);
},
/**
* @param {String} ua
* @returns {Object}
*/
getUaString: function (ua) {
return parser.setUA(ua).getBrowser();
},
/**
* Open the page in browser
* @param {String} url
* @param {Object} options
* @param {BrowserSync} bs
*/
openBrowser: function (url, options, bs) {
var open = options.get("open");
var browser = options.get("browser");
if (_.isString(open)) {
if (options.getIn(["urls", open])) {
url = options.getIn(["urls", open]);
}
}
if (open) {
if (browser !== "default") {
if (utils.isList(browser)) {
browser.forEach(function (browser) {
utils.open(url, browser, bs);
});
} else {
utils.open(url, browser, bs); // single
}
} else {
utils.open(url, null, bs);
}
}
},
/**
* Wrapper for opn module
* @param url
* @param name
* @param bs
*/
open: function (url, name, bs) {
var options = (function () {
if (_.isString(name)) {
return {app: name};
}
if (Immutable.Map.isMap(name)) {
return name.toJS();
}
return {};
})();
var opn = require("opn");
opn(url, options).catch(function() {
bs.events.emit("browser:error");
});
},
/**
* @param {Boolean} kill
* @param {String|Error} [errMessage]
* @param {Function} [cb]
*/
fail: function (kill, errMessage, cb) {
if (kill) {
if (_.isFunction(cb)) {
if (errMessage.message) { // Is this an error object?
cb(errMessage);
} else {
cb(new Error(errMessage));
}
}
process.exit(1);
}
},
/**
* Add support for xip.io urls
* @param {String} host
* @param {Object} options
* @returns {String}
*/
xip: function (host, options) {
var suffix = options.get("hostnameSuffix");
if (options.get("xip")) {
return host + ".xip.io";
}
if (suffix) {
return host + suffix;
}
return host;
},
/**
* Determine if an array of file paths will cause a full page reload.
* @param {Array} needles - filepath such as ["core.css", "index.html"]
* @param {Array} haystack
* @returns {Boolean}
*/
willCauseReload: function (needles, haystack) {
return needles.some(function (needle) {
return !_.includes(haystack, path.extname(needle).replace(".", ""));
});
},
isList: Immutable.List.isList,
isMap: Immutable.List.isMap,
/**
* @param {Map} options
* @returns {Array}
*/
getConfigErrors: function (options) {
var messages = require("./config").errors;
var errors = [];
if (options.get("server") && options.get("proxy")) {
errors.push(messages["server+proxy"]);
}
return errors;
},
/**
* @param {Map} options
* @param {Function} [cb]
*/
verifyConfig: function (options, cb) {
var errors = utils.getConfigErrors(options);
if (errors.length) {
utils.fail(true, errors.join("\n"), cb);
return false;
}
return true;
},
/**
* @param err
*/
defaultCallback: function (err) {
if (err && err.message) {
console.error(err.message);
}
},
eachSeries: function (arr, iterator, callback) {
callback = callback || function () {};
var completed = 0;
var iterate = function () {
iterator(arr[completed], function (err) {
if (err) {
callback(err);
callback = function () {};
} else {
++completed;
if (completed >= arr.length) {
callback();
} else {
iterate();
}
}
});
};
iterate();
},
/**
* @param {Immutable.List|Array|String} incoming
* @returns {Array}
*/
arrayify: function (incoming) {
if (List.isList(incoming)) {
return incoming.toArray();
}
return [].concat(incoming).filter(Boolean);
}
};
module.exports = utils;
module.exports.portscanner = portScanner;
module.exports.UAParser = UAParser;
module.exports.connect = require("connect");
module.exports.devIp = devIp;
module.exports.serveStatic = require("serve-static");
module.exports.easyExtender = require("easy-extender");
+4314
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../window-size/cli.js" "$@"
ret=$?
else
node "$basedir/../window-size/cli.js" "$@"
ret=$?
fi
exit $ret
+7
View File
@@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\window-size\cli.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\window-size\cli.js" %*
)
+56
View File
@@ -0,0 +1,56 @@
'use strict';
function preserveCamelCase(str) {
var isLastCharLower = false;
for (var i = 0; i < str.length; i++) {
var c = str.charAt(i);
if (isLastCharLower && (/[a-zA-Z]/).test(c) && c.toUpperCase() === c) {
str = str.substr(0, i) + '-' + str.substr(i);
isLastCharLower = false;
i++;
} else {
isLastCharLower = (c.toLowerCase() === c);
}
}
return str;
}
module.exports = function () {
var str = [].map.call(arguments, function (str) {
return str.trim();
}).filter(function (str) {
return str.length;
}).join('-');
if (!str.length) {
return '';
}
if (str.length === 1) {
return str.toLowerCase();
}
if (!(/[_.\- ]+/).test(str)) {
if (str === str.toUpperCase()) {
return str.toLowerCase();
}
if (str[0] !== str[0].toLowerCase()) {
return str[0].toLowerCase() + str.slice(1);
}
return str;
}
str = preserveCamelCase(str);
return str
.replace(/^[_.\- ]+/, '')
.toLowerCase()
.replace(/[_.\- ]+(\w|$)/g, function (m, p1) {
return p1.toUpperCase();
});
};
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+107
View File
@@ -0,0 +1,107 @@
{
"_args": [
[
{
"raw": "camelcase@^3.0.0",
"scope": null,
"escapedName": "camelcase",
"name": "camelcase",
"rawSpec": "^3.0.0",
"spec": ">=3.0.0 <4.0.0",
"type": "range"
},
"c:\\xampp\\htdocs\\laravel\\node_modules\\browser-sync\\node_modules\\yargs"
]
],
"_from": "camelcase@>=3.0.0 <4.0.0",
"_id": "camelcase@3.0.0",
"_inCache": true,
"_location": "/browser-sync/camelcase",
"_nodeVersion": "4.4.2",
"_npmOperationalInternal": {
"host": "packages-16-east.internal.npmjs.com",
"tmp": "tmp/camelcase-3.0.0.tgz_1462383205197_0.03801905922591686"
},
"_npmUser": {
"name": "sindresorhus",
"email": "sindresorhus@gmail.com"
},
"_npmVersion": "3.8.9",
"_phantomChildren": {},
"_requested": {
"raw": "camelcase@^3.0.0",
"scope": null,
"escapedName": "camelcase",
"name": "camelcase",
"rawSpec": "^3.0.0",
"spec": ">=3.0.0 <4.0.0",
"type": "range"
},
"_requiredBy": [
"/browser-sync/yargs"
],
"_resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"_shasum": "32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a",
"_shrinkwrap": null,
"_spec": "camelcase@^3.0.0",
"_where": "c:\\xampp\\htdocs\\laravel\\node_modules\\browser-sync\\node_modules\\yargs",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "http://sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/camelcase/issues"
},
"dependencies": {},
"description": "Convert a dash/dot/underscore/space separated string to camelCase: foo-bar → fooBar",
"devDependencies": {
"ava": "*",
"xo": "*"
},
"directories": {},
"dist": {
"shasum": "32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a",
"tarball": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"gitHead": "d4de0e37b625e38a880efc6517194917a5beda01",
"homepage": "https://github.com/sindresorhus/camelcase#readme",
"keywords": [
"camelcase",
"camel-case",
"camel",
"case",
"dash",
"hyphen",
"dot",
"underscore",
"separator",
"string",
"text",
"convert"
],
"license": "MIT",
"maintainers": [
{
"name": "sindresorhus",
"email": "sindresorhus@gmail.com"
}
],
"name": "camelcase",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/camelcase.git"
},
"scripts": {
"test": "xo && ava"
},
"version": "3.0.0"
}
+57
View File
@@ -0,0 +1,57 @@
# camelcase [![Build Status](https://travis-ci.org/sindresorhus/camelcase.svg?branch=master)](https://travis-ci.org/sindresorhus/camelcase)
> Convert a dash/dot/underscore/space separated string to camelCase: `foo-bar` → `fooBar`
## Install
```
$ npm install --save camelcase
```
## Usage
```js
const camelCase = require('camelcase');
camelCase('foo-bar');
//=> 'fooBar'
camelCase('foo_bar');
//=> 'fooBar'
camelCase('Foo-Bar');
//=> 'fooBar'
camelCase('--foo.bar');
//=> 'fooBar'
camelCase('__foo__bar__');
//=> 'fooBar'
camelCase('foo bar');
//=> 'fooBar'
console.log(process.argv[3]);
//=> '--foo-bar'
camelCase(process.argv[3]);
//=> 'fooBar'
camelCase('foo', 'bar');
//=> 'fooBar'
camelCase('__foo__', '--bar');
//=> 'fooBar'
```
## Related
- [decamelize](https://github.com/sindresorhus/decamelize) - The inverse of this module
- [uppercamelcase](https://github.com/SamVerschueren/uppercamelcase) - Like this module, but to PascalCase instead of camelCase
## License
MIT © [Sindre Sorhus](http://sindresorhus.com)
+8
View File
@@ -0,0 +1,8 @@
.nyc_output/
coverage/
test/
.travis.yml
appveyor.yml
lib/**/__tests__/
test/readme.md
test.js
+761
View File
@@ -0,0 +1,761 @@
3.0.1 / 2017-05-04
------------------
- Fix bug in `move()` & `moveSync()` when source and destination are the same, and source does not exist. [#415](https://github.com/jprichardson/node-fs-extra/pull/415)
3.0.0 / 2017-04-27
------------------
### Added
- **BREAKING:** Added Promise support. All asynchronous native fs methods and fs-extra methods now return a promise if the callback is not passed. [#403](https://github.com/jprichardson/node-fs-extra/pull/403)
- `pathExists()`, a replacement for the deprecated `fs.exists`. `pathExists` has a normal error-first callback signature. Also added `pathExistsSync`, an alias to `fs.existsSync`, for completeness. [#406](https://github.com/jprichardson/node-fs-extra/pull/406)
### Removed
- **BREAKING:** Removed support for setting the default spaces for `writeJson()`, `writeJsonSync()`, `outputJson()`, & `outputJsonSync()`. This was undocumented. [#402](https://github.com/jprichardson/node-fs-extra/pull/402)
### Changed
- Upgraded jsonfile dependency to v3.0.0:
- **BREAKING:** Changed behavior of `throws` option for `readJsonSync()`; now does not throw filesystem errors when `throws` is `false`.
- **BREAKING:** `writeJson()`, `writeJsonSync()`, `outputJson()`, & `outputJsonSync()` now output minified JSON by default for consistency with `JSON.stringify()`; set the `spaces` option to `2` to override this new behavior. [#402](https://github.com/jprichardson/node-fs-extra/pull/402)
- Use `Buffer.allocUnsafe()` instead of `new Buffer()` in environments that support it. [#394](https://github.com/jprichardson/node-fs-extra/pull/394)
### Fixed
- `removeSync()` silently failed on Windows in some cases. Now throws an `EBUSY` error. [#408](https://github.com/jprichardson/node-fs-extra/pull/408)
2.1.2 / 2017-03-16
------------------
### Fixed
- Weird windows bug that resulted in `ensureDir()`'s callback being called twice in some cases. This bug may have also affected `remove()`. See [#392](https://github.com/jprichardson/node-fs-extra/issues/392), [#393](https://github.com/jprichardson/node-fs-extra/pull/393)
2.1.1 / 2017-03-15
------------------
### Fixed
- Reverted [`5597bd`](https://github.com/jprichardson/node-fs-extra/commit/5597bd5b67f7d060f5f5bf26e9635be48330f5d7), this broke compatibility with Node.js versions v4+ but less than `v4.5.0`.
- Remove `Buffer.alloc()` usage in `moveSync()`.
2.1.0 / 2017-03-15
------------------
Thanks to [Mani Maghsoudlou (@manidlou)](https://github.com/manidlou) & [Jan Peer Stöcklmair (@JPeer264)](https://github.com/JPeer264) for their extraordinary help with this release!
### Added
- `moveSync()` See [#309], [#381](https://github.com/jprichardson/node-fs-extra/pull/381). ([@manidlou](https://github.com/manidlou))
- `copy()` and `copySync()`'s `filter` option now gets the destination path passed as the second parameter. [#366](https://github.com/jprichardson/node-fs-extra/pull/366) ([@manidlou](https://github.com/manidlou))
### Changed
- Use `Buffer.alloc()` instead of deprecated `new Buffer()` in `copySync()`. [#380](https://github.com/jprichardson/node-fs-extra/pull/380) ([@manidlou](https://github.com/manidlou))
- Refactored entire codebase to use ES6 features supported by Node.js v4+ [#355](https://github.com/jprichardson/node-fs-extra/issues/355). [(@JPeer264)](https://github.com/JPeer264)
- Refactored docs. ([@manidlou](https://github.com/manidlou))
### Fixed
- `move()` shouldn't error out when source and dest are the same. [#377](https://github.com/jprichardson/node-fs-extra/issues/377), [#378](https://github.com/jprichardson/node-fs-extra/pull/378) ([@jdalton](https://github.com/jdalton))
2.0.0 / 2017-01-16
------------------
### Removed
- **BREAKING:** Removed support for Node `v0.12`. The Node foundation stopped officially supporting it
on Jan 1st, 2017.
- **BREAKING:** Remove `walk()` and `walkSync()`. `walkSync()` was only part of `fs-extra` for a little
over two months. Use [klaw](https://github.com/jprichardson/node-klaw) instead of `walk()`, in fact, `walk()` was just
an alias to klaw. For `walkSync()` use [klaw-sync](https://github.com/mawni/node-klaw-sync). See: [#338], [#339]
### Changed
- **BREAKING:** Renamed `clobber` to `overwrite`. This affects `copy()`, `copySync()`, and `move()`. [#330], [#333]
- Moved docs, to `docs/`. [#340]
### Fixed
- Apply filters to directories in `copySync()` like in `copy()`. [#324]
- A specific condition when disk is under heavy use, `copy()` can fail. [#326]
1.0.0 / 2016-11-01
------------------
After five years of development, we finally have reach the 1.0.0 milestone! Big thanks goes
to [Ryan Zim](https://github.com/RyanZim) for leading the charge on this release!
### Added
- `walkSync()`
### Changed
- **BREAKING**: dropped Node v0.10 support.
- disabled `rimaf` globbing, wasn't used. [#280]
- deprecate `copy()/copySync()` option `filter` if it's a `RegExp`. `filter` should now be a function.
- inline `rimraf`. This is temporary and was done because `rimraf` depended upon the beefy `glob` which `fs-extra` does not use. [#300]
### Fixed
- bug fix proper closing of file handle on `utimesMillis()` [#271]
- proper escaping of files with dollar signs [#291]
- `copySync()` failed if user didn't own file. [#199], [#301]
0.30.0 / 2016-04-28
-------------------
- Brought back Node v0.10 support. I didn't realize there was still demand. Official support will end **2016-10-01**.
0.29.0 / 2016-04-27
-------------------
- **BREAKING**: removed support for Node v0.10. If you still want to use Node v0.10, everything should work except for `ensureLink()/ensureSymlink()`. Node v0.12 is still supported but will be dropped in the near future as well.
0.28.0 / 2016-04-17
-------------------
- **BREAKING**: removed `createOutputStream()`. Use https://www.npmjs.com/package/create-output-stream. See: [#192][#192]
- `mkdirs()/mkdirsSync()` check for invalid win32 path chars. See: [#209][#209], [#237][#237]
- `mkdirs()/mkdirsSync()` if drive not mounted, error. See: [#93][#93]
0.27.0 / 2016-04-15
-------------------
- add `dereference` option to `copySync()`. [#235][#235]
0.26.7 / 2016-03-16
-------------------
- fixed `copy()` if source and dest are the same. [#230][#230]
0.26.6 / 2016-03-15
-------------------
- fixed if `emptyDir()` does not have a callback: [#229][#229]
0.26.5 / 2016-01-27
-------------------
- `copy()` with two arguments (w/o callback) was broken. See: [#215][#215]
0.26.4 / 2016-01-05
-------------------
- `copySync()` made `preserveTimestamps` default consistent with `copy()` which is `false`. See: [#208][#208]
0.26.3 / 2015-12-17
-------------------
- fixed `copy()` hangup in copying blockDevice / characterDevice / `/dev/null`. See: [#193][#193]
0.26.2 / 2015-11-02
-------------------
- fixed `outputJson{Sync}()` spacing adherence to `fs.spaces`
0.26.1 / 2015-11-02
-------------------
- fixed `copySync()` when `clogger=true` and the destination is read only. See: [#190][#190]
0.26.0 / 2015-10-25
-------------------
- extracted the `walk()` function into its own module [`klaw`](https://github.com/jprichardson/node-klaw).
0.25.0 / 2015-10-24
-------------------
- now has a file walker `walk()`
0.24.0 / 2015-08-28
-------------------
- removed alias `delete()` and `deleteSync()`. See: [#171][#171]
0.23.1 / 2015-08-07
-------------------
- Better handling of errors for `move()` when moving across devices. [#170][#170]
- `ensureSymlink()` and `ensureLink()` should not throw errors if link exists. [#169][#169]
0.23.0 / 2015-08-06
-------------------
- added `ensureLink{Sync}()` and `ensureSymlink{Sync}()`. See: [#165][#165]
0.22.1 / 2015-07-09
-------------------
- Prevent calling `hasMillisResSync()` on module load. See: [#149][#149].
Fixes regression that was introduced in `0.21.0`.
0.22.0 / 2015-07-09
-------------------
- preserve permissions / ownership in `copy()`. See: [#54][#54]
0.21.0 / 2015-07-04
-------------------
- add option to preserve timestamps in `copy()` and `copySync()`. See: [#141][#141]
- updated `graceful-fs@3.x` to `4.x`. This brings in features from `amazing-graceful-fs` (much cleaner code / less hacks)
0.20.1 / 2015-06-23
-------------------
- fixed regression caused by latest jsonfile update: See: https://github.com/jprichardson/node-jsonfile/issues/26
0.20.0 / 2015-06-19
-------------------
- removed `jsonfile` aliases with `File` in the name, they weren't documented and probably weren't in use e.g.
this package had both `fs.readJsonFile` and `fs.readJson` that were aliases to each other, now use `fs.readJson`.
- preliminary walker created. Intentionally not documented. If you use it, it will almost certainly change and break your code.
- started moving tests inline
- upgraded to `jsonfile@2.1.0`, can now pass JSON revivers/replacers to `readJson()`, `writeJson()`, `outputJson()`
0.19.0 / 2015-06-08
-------------------
- `fs.copy()` had support for Node v0.8, dropped support
0.18.4 / 2015-05-22
-------------------
- fixed license field according to this: [#136][#136] and https://github.com/npm/npm/releases/tag/v2.10.0
0.18.3 / 2015-05-08
-------------------
- bugfix: handle `EEXIST` when clobbering on some Linux systems. [#134][#134]
0.18.2 / 2015-04-17
-------------------
- bugfix: allow `F_OK` ([#120][#120])
0.18.1 / 2015-04-15
-------------------
- improved windows support for `move()` a bit. https://github.com/jprichardson/node-fs-extra/commit/92838980f25dc2ee4ec46b43ee14d3c4a1d30c1b
- fixed a lot of tests for Windows (appveyor)
0.18.0 / 2015-03-31
-------------------
- added `emptyDir()` and `emptyDirSync()`
0.17.0 / 2015-03-28
-------------------
- `copySync` added `clobber` option (before always would clobber, now if `clobber` is `false` it throws an error if the destination exists).
**Only works with files at the moment.**
- `createOutputStream()` added. See: [#118][#118]
0.16.5 / 2015-03-08
-------------------
- fixed `fs.move` when `clobber` is `true` and destination is a directory, it should clobber. [#114][#114]
0.16.4 / 2015-03-01
-------------------
- `fs.mkdirs` fix infinite loop on Windows. See: See https://github.com/substack/node-mkdirp/pull/74 and https://github.com/substack/node-mkdirp/issues/66
0.16.3 / 2015-01-28
-------------------
- reverted https://github.com/jprichardson/node-fs-extra/commit/1ee77c8a805eba5b99382a2591ff99667847c9c9
0.16.2 / 2015-01-28
-------------------
- fixed `fs.copy` for Node v0.8 (support is temporary and will be removed in the near future)
0.16.1 / 2015-01-28
-------------------
- if `setImmediate` is not available, fall back to `process.nextTick`
0.16.0 / 2015-01-28
-------------------
- bugfix `fs.move()` into itself. Closes [#104]
- bugfix `fs.move()` moving directory across device. Closes [#108]
- added coveralls support
- bugfix: nasty multiple callback `fs.copy()` bug. Closes [#98]
- misc fs.copy code cleanups
0.15.0 / 2015-01-21
-------------------
- dropped `ncp`, imported code in
- because of previous, now supports `io.js`
- `graceful-fs` is now a dependency
0.14.0 / 2015-01-05
-------------------
- changed `copy`/`copySync` from `fs.copy(src, dest, [filters], callback)` to `fs.copy(src, dest, [options], callback)` [#100][#100]
- removed mockfs tests for mkdirp (this may be temporary, but was getting in the way of other tests)
0.13.0 / 2014-12-10
-------------------
- removed `touch` and `touchSync` methods (they didn't handle permissions like UNIX touch)
- updated `"ncp": "^0.6.0"` to `"ncp": "^1.0.1"`
- imported `mkdirp` => `minimist` and `mkdirp` are no longer dependences, should now appease people who wanted `mkdirp` to be `--use_strict` safe. See [#59]([#59][#59])
0.12.0 / 2014-09-22
-------------------
- copy symlinks in `copySync()` [#85][#85]
0.11.1 / 2014-09-02
-------------------
- bugfix `copySync()` preserve file permissions [#80][#80]
0.11.0 / 2014-08-11
-------------------
- upgraded `"ncp": "^0.5.1"` to `"ncp": "^0.6.0"`
- upgrade `jsonfile": "^1.2.0"` to `jsonfile": "^2.0.0"` => on write, json files now have `\n` at end. Also adds `options.throws` to `readJsonSync()`
see https://github.com/jprichardson/node-jsonfile#readfilesyncfilename-options for more details.
0.10.0 / 2014-06-29
------------------
* bugfix: upgaded `"jsonfile": "~1.1.0"` to `"jsonfile": "^1.2.0"`, bumped minor because of `jsonfile` dep change
from `~` to `^`. [#67]
0.9.1 / 2014-05-22
------------------
* removed Node.js `0.8.x` support, `0.9.0` was published moments ago and should have been done there
0.9.0 / 2014-05-22
------------------
* upgraded `ncp` from `~0.4.2` to `^0.5.1`, [#58]
* upgraded `rimraf` from `~2.2.6` to `^2.2.8`
* upgraded `mkdirp` from `0.3.x` to `^0.5.0`
* added methods `ensureFile()`, `ensureFileSync()`
* added methods `ensureDir()`, `ensureDirSync()` [#31]
* added `move()` method. From: https://github.com/andrewrk/node-mv
0.8.1 / 2013-10-24
------------------
* copy failed to return an error to the callback if a file doesn't exist (ulikoehler [#38], [#39])
0.8.0 / 2013-10-14
------------------
* `filter` implemented on `copy()` and `copySync()`. (Srirangan / [#36])
0.7.1 / 2013-10-12
------------------
* `copySync()` implemented (Srirangan / [#33])
* updated to the latest `jsonfile` version `1.1.0` which gives `options` params for the JSON methods. Closes [#32]
0.7.0 / 2013-10-07
------------------
* update readme conventions
* `copy()` now works if destination directory does not exist. Closes [#29]
0.6.4 / 2013-09-05
------------------
* changed `homepage` field in package.json to remove NPM warning
0.6.3 / 2013-06-28
------------------
* changed JSON spacing default from `4` to `2` to follow Node conventions
* updated `jsonfile` dep
* updated `rimraf` dep
0.6.2 / 2013-06-28
------------------
* added .npmignore, [#25]
0.6.1 / 2013-05-14
------------------
* modified for `strict` mode, closes [#24]
* added `outputJson()/outputJsonSync()`, closes [#23]
0.6.0 / 2013-03-18
------------------
* removed node 0.6 support
* added node 0.10 support
* upgraded to latest `ncp` and `rimraf`.
* optional `graceful-fs` support. Closes [#17]
0.5.0 / 2013-02-03
------------------
* Removed `readTextFile`.
* Renamed `readJSONFile` to `readJSON` and `readJson`, same with write.
* Restructured documentation a bit. Added roadmap.
0.4.0 / 2013-01-28
------------------
* Set default spaces in `jsonfile` from 4 to 2.
* Updated `testutil` deps for tests.
* Renamed `touch()` to `createFile()`
* Added `outputFile()` and `outputFileSync()`
* Changed creation of testing diretories so the /tmp dir is not littered.
* Added `readTextFile()` and `readTextFileSync()`.
0.3.2 / 2012-11-01
------------------
* Added `touch()` and `touchSync()` methods.
0.3.1 / 2012-10-11
------------------
* Fixed some stray globals.
0.3.0 / 2012-10-09
------------------
* Removed all CoffeeScript from tests.
* Renamed `mkdir` to `mkdirs`/`mkdirp`.
0.2.1 / 2012-09-11
------------------
* Updated `rimraf` dep.
0.2.0 / 2012-09-10
------------------
* Rewrote module into JavaScript. (Must still rewrite tests into JavaScript)
* Added all methods of [jsonfile](https://github.com/jprichardson/node-jsonfile)
* Added Travis-CI.
0.1.3 / 2012-08-13
------------------
* Added method `readJSONFile`.
0.1.2 / 2012-06-15
------------------
* Bug fix: `deleteSync()` didn't exist.
* Verified Node v0.8 compatibility.
0.1.1 / 2012-06-15
------------------
* Fixed bug in `remove()`/`delete()` that wouldn't execute the function if a callback wasn't passed.
0.1.0 / 2012-05-31
------------------
* Renamed `copyFile()` to `copy()`. `copy()` can now copy directories (recursively) too.
* Renamed `rmrf()` to `remove()`.
* `remove()` aliased with `delete()`.
* Added `mkdirp` capabilities. Named: `mkdir()`. Hides Node.js native `mkdir()`.
* Instead of exporting the native `fs` module with new functions, I now copy over the native methods to a new object and export that instead.
0.0.4 / 2012-03-14
------------------
* Removed CoffeeScript dependency
0.0.3 / 2012-01-11
------------------
* Added methods rmrf and rmrfSync
* Moved tests from Jasmine to Mocha
[#344]: https://github.com/jprichardson/node-fs-extra/issues/344 "Licence Year"
[#343]: https://github.com/jprichardson/node-fs-extra/pull/343 "Add klaw-sync link to readme"
[#342]: https://github.com/jprichardson/node-fs-extra/pull/342 "allow preserveTimestamps when use move"
[#341]: https://github.com/jprichardson/node-fs-extra/issues/341 "mkdirp(path.dirname(dest) in move() logic needs cleaning up [question]"
[#340]: https://github.com/jprichardson/node-fs-extra/pull/340 "Move docs to seperate docs folder [documentation]"
[#339]: https://github.com/jprichardson/node-fs-extra/pull/339 "Remove walk() & walkSync() [feature-walk]"
[#338]: https://github.com/jprichardson/node-fs-extra/issues/338 "Remove walk() and walkSync() [feature-walk]"
[#337]: https://github.com/jprichardson/node-fs-extra/issues/337 "copy doesn't return a yieldable value"
[#336]: https://github.com/jprichardson/node-fs-extra/pull/336 "Docs enhanced walk sync [documentation, feature-walk]"
[#335]: https://github.com/jprichardson/node-fs-extra/pull/335 "Refactor move() tests [feature-move]"
[#334]: https://github.com/jprichardson/node-fs-extra/pull/334 "Cleanup lib/move/index.js [feature-move]"
[#333]: https://github.com/jprichardson/node-fs-extra/pull/333 "Rename clobber to overwrite [feature-copy, feature-move]"
[#332]: https://github.com/jprichardson/node-fs-extra/pull/332 "BREAKING: Drop Node v0.12 & io.js support"
[#331]: https://github.com/jprichardson/node-fs-extra/issues/331 "Add support for chmodr [enhancement, future]"
[#330]: https://github.com/jprichardson/node-fs-extra/pull/330 "BREAKING: Do not error when copy destination exists & clobber: false [feature-copy]"
[#329]: https://github.com/jprichardson/node-fs-extra/issues/329 "Does .walk() scale to large directories? [question]"
[#328]: https://github.com/jprichardson/node-fs-extra/issues/328 "Copying files corrupts [feature-copy, needs-confirmed]"
[#327]: https://github.com/jprichardson/node-fs-extra/pull/327 "Use writeStream 'finish' event instead of 'close' [bug, feature-copy]"
[#326]: https://github.com/jprichardson/node-fs-extra/issues/326 "fs.copy fails with chmod error when disk under heavy use [bug, feature-copy]"
[#325]: https://github.com/jprichardson/node-fs-extra/issues/325 "ensureDir is difficult to promisify [enhancement]"
[#324]: https://github.com/jprichardson/node-fs-extra/pull/324 "copySync() should apply filter to directories like copy() [bug, feature-copy]"
[#323]: https://github.com/jprichardson/node-fs-extra/issues/323 "Support for `dest` being a directory when using `copy*()`?"
[#322]: https://github.com/jprichardson/node-fs-extra/pull/322 "Add fs-promise as fs-extra-promise alternative"
[#321]: https://github.com/jprichardson/node-fs-extra/issues/321 "fs.copy() with clobber set to false return EEXIST error [feature-copy]"
[#320]: https://github.com/jprichardson/node-fs-extra/issues/320 "fs.copySync: Error: EPERM: operation not permitted, unlink "
[#319]: https://github.com/jprichardson/node-fs-extra/issues/319 "Create directory if not exists"
[#318]: https://github.com/jprichardson/node-fs-extra/issues/318 "Support glob patterns [enhancement, future]"
[#317]: https://github.com/jprichardson/node-fs-extra/pull/317 "Adding copy sync test for src file without write perms"
[#316]: https://github.com/jprichardson/node-fs-extra/pull/316 "Remove move()'s broken limit option [feature-move]"
[#315]: https://github.com/jprichardson/node-fs-extra/pull/315 "Fix move clobber tests to work around graceful-fs bug."
[#314]: https://github.com/jprichardson/node-fs-extra/issues/314 "move() limit option [documentation, enhancement, feature-move]"
[#313]: https://github.com/jprichardson/node-fs-extra/pull/313 "Test that remove() ignores glob characters."
[#312]: https://github.com/jprichardson/node-fs-extra/pull/312 "Enhance walkSync() to return items with path and stats [feature-walk]"
[#311]: https://github.com/jprichardson/node-fs-extra/issues/311 "move() not work when dest name not provided [feature-move]"
[#310]: https://github.com/jprichardson/node-fs-extra/issues/310 "Edit walkSync to return items like what walk emits [documentation, enhancement, feature-walk]"
[#309]: https://github.com/jprichardson/node-fs-extra/issues/309 "moveSync support [enhancement, feature-move]"
[#308]: https://github.com/jprichardson/node-fs-extra/pull/308 "Fix incorrect anchor link"
[#307]: https://github.com/jprichardson/node-fs-extra/pull/307 "Fix coverage"
[#306]: https://github.com/jprichardson/node-fs-extra/pull/306 "Update devDeps, fix lint error"
[#305]: https://github.com/jprichardson/node-fs-extra/pull/305 "Re-add Coveralls"
[#304]: https://github.com/jprichardson/node-fs-extra/pull/304 "Remove path-is-absolute [enhancement]"
[#303]: https://github.com/jprichardson/node-fs-extra/pull/303 "Document copySync filter inconsistency [documentation, feature-copy]"
[#302]: https://github.com/jprichardson/node-fs-extra/pull/302 "fix(console): depreciated -> deprecated"
[#301]: https://github.com/jprichardson/node-fs-extra/pull/301 "Remove chmod call from copySync [feature-copy]"
[#300]: https://github.com/jprichardson/node-fs-extra/pull/300 "Inline Rimraf [enhancement, feature-move, feature-remove]"
[#299]: https://github.com/jprichardson/node-fs-extra/pull/299 "Warn when filter is a RegExp [feature-copy]"
[#298]: https://github.com/jprichardson/node-fs-extra/issues/298 "API Docs [documentation]"
[#297]: https://github.com/jprichardson/node-fs-extra/pull/297 "Warn about using preserveTimestamps on 32-bit node"
[#296]: https://github.com/jprichardson/node-fs-extra/pull/296 "Improve EEXIST error message for copySync [enhancement]"
[#295]: https://github.com/jprichardson/node-fs-extra/pull/295 "Depreciate using regular expressions for copy's filter option [documentation]"
[#294]: https://github.com/jprichardson/node-fs-extra/pull/294 "BREAKING: Refactor lib/copy/ncp.js [feature-copy]"
[#293]: https://github.com/jprichardson/node-fs-extra/pull/293 "Update CI configs"
[#292]: https://github.com/jprichardson/node-fs-extra/issues/292 "Rewrite lib/copy/ncp.js [enhancement, feature-copy]"
[#291]: https://github.com/jprichardson/node-fs-extra/pull/291 "Escape '$' in replacement string for async file copying"
[#290]: https://github.com/jprichardson/node-fs-extra/issues/290 "Exclude files pattern while copying using copy.config.js [question]"
[#289]: https://github.com/jprichardson/node-fs-extra/pull/289 "(Closes #271) lib/util/utimes: properly close file descriptors in the event of an error"
[#288]: https://github.com/jprichardson/node-fs-extra/pull/288 "(Closes #271) lib/util/utimes: properly close file descriptors in the event of an error"
[#287]: https://github.com/jprichardson/node-fs-extra/issues/287 "emptyDir() callback arguments are inconsistent [enhancement, feature-remove]"
[#286]: https://github.com/jprichardson/node-fs-extra/pull/286 "Added walkSync function"
[#285]: https://github.com/jprichardson/node-fs-extra/issues/285 "CITGM test failing on s390"
[#284]: https://github.com/jprichardson/node-fs-extra/issues/284 "outputFile method is missing a check to determine if existing item is a folder or not"
[#283]: https://github.com/jprichardson/node-fs-extra/pull/283 "Apply filter also on directories and symlinks for copySync()"
[#282]: https://github.com/jprichardson/node-fs-extra/pull/282 "Apply filter also on directories and symlinks for copySync()"
[#281]: https://github.com/jprichardson/node-fs-extra/issues/281 "remove function executes 'successfully' but doesn't do anything?"
[#280]: https://github.com/jprichardson/node-fs-extra/pull/280 "Disable rimraf globbing"
[#279]: https://github.com/jprichardson/node-fs-extra/issues/279 "Some code is vendored instead of included [awaiting-reply]"
[#278]: https://github.com/jprichardson/node-fs-extra/issues/278 "copy() does not preserve file/directory ownership"
[#277]: https://github.com/jprichardson/node-fs-extra/pull/277 "Mention defaults for clobber and dereference options"
[#276]: https://github.com/jprichardson/node-fs-extra/issues/276 "Cannot connect to Shared Folder [awaiting-reply]"
[#275]: https://github.com/jprichardson/node-fs-extra/issues/275 "EMFILE, too many open files on Mac OS with JSON API"
[#274]: https://github.com/jprichardson/node-fs-extra/issues/274 "Use with memory-fs? [enhancement, future]"
[#273]: https://github.com/jprichardson/node-fs-extra/pull/273 "tests: rename `remote.test.js` to `remove.test.js`"
[#272]: https://github.com/jprichardson/node-fs-extra/issues/272 "Copy clobber flag never err even when true [bug, feature-copy]"
[#271]: https://github.com/jprichardson/node-fs-extra/issues/271 "Unclosed file handle on futimes error"
[#270]: https://github.com/jprichardson/node-fs-extra/issues/270 "copy not working as desired on Windows [feature-copy, platform-windows]"
[#269]: https://github.com/jprichardson/node-fs-extra/issues/269 "Copying with preserveTimeStamps: true is inaccurate using 32bit node [feature-copy]"
[#268]: https://github.com/jprichardson/node-fs-extra/pull/268 "port fix for mkdirp issue #111"
[#267]: https://github.com/jprichardson/node-fs-extra/issues/267 "WARN deprecated wrench@1.5.9: wrench.js is deprecated!"
[#266]: https://github.com/jprichardson/node-fs-extra/issues/266 "fs-extra"
[#265]: https://github.com/jprichardson/node-fs-extra/issues/265 "Link the `fs.stat fs.exists` etc. methods for replace the `fs` module forever?"
[#264]: https://github.com/jprichardson/node-fs-extra/issues/264 "Renaming a file using move fails when a file inside is open (at least on windows) [wont-fix]"
[#263]: https://github.com/jprichardson/node-fs-extra/issues/263 "ENOSYS: function not implemented, link [needs-confirmed]"
[#262]: https://github.com/jprichardson/node-fs-extra/issues/262 "Add .exists() and .existsSync()"
[#261]: https://github.com/jprichardson/node-fs-extra/issues/261 "Cannot read property 'prototype' of undefined"
[#260]: https://github.com/jprichardson/node-fs-extra/pull/260 "use more specific path for method require"
[#259]: https://github.com/jprichardson/node-fs-extra/issues/259 "Feature Request: isEmpty"
[#258]: https://github.com/jprichardson/node-fs-extra/issues/258 "copy files does not preserve file timestamp"
[#257]: https://github.com/jprichardson/node-fs-extra/issues/257 "Copying a file on windows fails"
[#256]: https://github.com/jprichardson/node-fs-extra/pull/256 "Updated Readme "
[#255]: https://github.com/jprichardson/node-fs-extra/issues/255 "Update rimraf required version"
[#254]: https://github.com/jprichardson/node-fs-extra/issues/254 "request for readTree, readTreeSync, walkSync method"
[#253]: https://github.com/jprichardson/node-fs-extra/issues/253 "outputFile does not touch mtime when file exists"
[#252]: https://github.com/jprichardson/node-fs-extra/pull/252 "Fixing problem when copying file with no write permission"
[#251]: https://github.com/jprichardson/node-fs-extra/issues/251 "Just wanted to say thank you"
[#250]: https://github.com/jprichardson/node-fs-extra/issues/250 "`fs.remove()` not removing files (works with `rm -rf`)"
[#249]: https://github.com/jprichardson/node-fs-extra/issues/249 "Just a Question ... Remove Servers"
[#248]: https://github.com/jprichardson/node-fs-extra/issues/248 "Allow option to not preserve permissions for copy"
[#247]: https://github.com/jprichardson/node-fs-extra/issues/247 "Add TypeScript typing directly in the fs-extra package"
[#246]: https://github.com/jprichardson/node-fs-extra/issues/246 "fse.remove() && fse.removeSync() don't throw error on ENOENT file"
[#245]: https://github.com/jprichardson/node-fs-extra/issues/245 "filter for empty dir [enhancement]"
[#244]: https://github.com/jprichardson/node-fs-extra/issues/244 "copySync doesn't apply the filter to directories"
[#243]: https://github.com/jprichardson/node-fs-extra/issues/243 "Can I request fs.walk() to be synchronous?"
[#242]: https://github.com/jprichardson/node-fs-extra/issues/242 "Accidentally truncates file names ending with $$ [bug, feature-copy]"
[#241]: https://github.com/jprichardson/node-fs-extra/pull/241 "Remove link to createOutputStream"
[#240]: https://github.com/jprichardson/node-fs-extra/issues/240 "walkSync request"
[#239]: https://github.com/jprichardson/node-fs-extra/issues/239 "Depreciate regular expressions for copy's filter [documentation, feature-copy]"
[#238]: https://github.com/jprichardson/node-fs-extra/issues/238 "Can't write to files while in a worker thread."
[#237]: https://github.com/jprichardson/node-fs-extra/issues/237 ".ensureDir(..) fails silently when passed an invalid path..."
[#236]: https://github.com/jprichardson/node-fs-extra/issues/236 "[Removed] Filed under wrong repo"
[#235]: https://github.com/jprichardson/node-fs-extra/pull/235 "Adds symlink dereference option to `fse.copySync` (#191)"
[#234]: https://github.com/jprichardson/node-fs-extra/issues/234 "ensureDirSync fails silent when EACCES: permission denied on travis-ci"
[#233]: https://github.com/jprichardson/node-fs-extra/issues/233 "please make sure the first argument in callback is error object [feature-copy]"
[#232]: https://github.com/jprichardson/node-fs-extra/issues/232 "Copy a folder content to its child folder. "
[#231]: https://github.com/jprichardson/node-fs-extra/issues/231 "Adding read/write/output functions for YAML"
[#230]: https://github.com/jprichardson/node-fs-extra/pull/230 "throw error if src and dest are the same to avoid zeroing out + test"
[#229]: https://github.com/jprichardson/node-fs-extra/pull/229 "fix 'TypeError: callback is not a function' in emptyDir"
[#228]: https://github.com/jprichardson/node-fs-extra/pull/228 "Throw error when target is empty so file is not accidentally zeroed out"
[#227]: https://github.com/jprichardson/node-fs-extra/issues/227 "Uncatchable errors when there are invalid arguments [feature-move]"
[#226]: https://github.com/jprichardson/node-fs-extra/issues/226 "Moving to the current directory"
[#225]: https://github.com/jprichardson/node-fs-extra/issues/225 "EBUSY: resource busy or locked, unlink"
[#224]: https://github.com/jprichardson/node-fs-extra/issues/224 "fse.copy ENOENT error"
[#223]: https://github.com/jprichardson/node-fs-extra/issues/223 "Suspicious behavior of fs.existsSync"
[#222]: https://github.com/jprichardson/node-fs-extra/pull/222 "A clearer description of emtpyDir function"
[#221]: https://github.com/jprichardson/node-fs-extra/pull/221 "Update README.md"
[#220]: https://github.com/jprichardson/node-fs-extra/pull/220 "Non-breaking feature: add option 'passStats' to copy methods."
[#219]: https://github.com/jprichardson/node-fs-extra/pull/219 "Add closing parenthesis in copySync example"
[#218]: https://github.com/jprichardson/node-fs-extra/pull/218 "fix #187 #70 options.filter bug"
[#217]: https://github.com/jprichardson/node-fs-extra/pull/217 "fix #187 #70 options.filter bug"
[#216]: https://github.com/jprichardson/node-fs-extra/pull/216 "fix #187 #70 options.filter bug"
[#215]: https://github.com/jprichardson/node-fs-extra/pull/215 "fse.copy throws error when only src and dest provided [bug, documentation, feature-copy]"
[#214]: https://github.com/jprichardson/node-fs-extra/pull/214 "Fixing copySync anchor tag"
[#213]: https://github.com/jprichardson/node-fs-extra/issues/213 "Merge extfs with this repo"
[#212]: https://github.com/jprichardson/node-fs-extra/pull/212 "Update year to 2016 in README.md and LICENSE"
[#211]: https://github.com/jprichardson/node-fs-extra/issues/211 "Not copying all files"
[#210]: https://github.com/jprichardson/node-fs-extra/issues/210 "copy/copySync behave differently when copying a symbolic file [bug, documentation, feature-copy]"
[#209]: https://github.com/jprichardson/node-fs-extra/issues/209 "In Windows invalid directory name causes infinite loop in ensureDir(). [bug]"
[#208]: https://github.com/jprichardson/node-fs-extra/pull/208 "fix options.preserveTimestamps to false in copy-sync by default [feature-copy]"
[#207]: https://github.com/jprichardson/node-fs-extra/issues/207 "Add `compare` suite of functions"
[#206]: https://github.com/jprichardson/node-fs-extra/issues/206 "outputFileSync"
[#205]: https://github.com/jprichardson/node-fs-extra/issues/205 "fix documents about copy/copySync [documentation, feature-copy]"
[#204]: https://github.com/jprichardson/node-fs-extra/pull/204 "allow copy of block and character device files"
[#203]: https://github.com/jprichardson/node-fs-extra/issues/203 "copy method's argument options couldn't be undefined [bug, feature-copy]"
[#202]: https://github.com/jprichardson/node-fs-extra/issues/202 "why there is not a walkSync method?"
[#201]: https://github.com/jprichardson/node-fs-extra/issues/201 "clobber for directories [feature-copy, future]"
[#200]: https://github.com/jprichardson/node-fs-extra/issues/200 "'copySync' doesn't work in sync"
[#199]: https://github.com/jprichardson/node-fs-extra/issues/199 "fs.copySync fails if user does not own file [bug, feature-copy]"
[#198]: https://github.com/jprichardson/node-fs-extra/issues/198 "handle copying between identical files [feature-copy]"
[#197]: https://github.com/jprichardson/node-fs-extra/issues/197 "Missing documentation for `outputFile` `options` 3rd parameter [documentation]"
[#196]: https://github.com/jprichardson/node-fs-extra/issues/196 "copy filter: async function and/or function called with `fs.stat` result [future]"
[#195]: https://github.com/jprichardson/node-fs-extra/issues/195 "How to override with outputFile?"
[#194]: https://github.com/jprichardson/node-fs-extra/pull/194 "allow ensureFile(Sync) to provide data to be written to created file"
[#193]: https://github.com/jprichardson/node-fs-extra/issues/193 "`fs.copy` fails silently if source file is /dev/null [bug, feature-copy]"
[#192]: https://github.com/jprichardson/node-fs-extra/issues/192 "Remove fs.createOutputStream()"
[#191]: https://github.com/jprichardson/node-fs-extra/issues/191 "How to copy symlinks to target as normal folders [feature-copy]"
[#190]: https://github.com/jprichardson/node-fs-extra/pull/190 "copySync to overwrite destination file if readonly and clobber true"
[#189]: https://github.com/jprichardson/node-fs-extra/pull/189 "move.test fix to support CRLF on Windows"
[#188]: https://github.com/jprichardson/node-fs-extra/issues/188 "move.test failing on windows platform"
[#187]: https://github.com/jprichardson/node-fs-extra/issues/187 "Not filter each file, stops on first false [feature-copy]"
[#186]: https://github.com/jprichardson/node-fs-extra/issues/186 "Do you need a .size() function in this module? [future]"
[#185]: https://github.com/jprichardson/node-fs-extra/issues/185 "Doesn't work on NodeJS v4.x"
[#184]: https://github.com/jprichardson/node-fs-extra/issues/184 "CLI equivalent for fs-extra"
[#183]: https://github.com/jprichardson/node-fs-extra/issues/183 "with clobber true, copy and copySync behave differently if destination file is read only [bug, feature-copy]"
[#182]: https://github.com/jprichardson/node-fs-extra/issues/182 "ensureDir(dir, callback) second callback parameter not specified"
[#181]: https://github.com/jprichardson/node-fs-extra/issues/181 "Add ability to remove file securely [enhancement, wont-fix]"
[#180]: https://github.com/jprichardson/node-fs-extra/issues/180 "Filter option doesn't work the same way in copy and copySync [bug, feature-copy]"
[#179]: https://github.com/jprichardson/node-fs-extra/issues/179 "Include opendir"
[#178]: https://github.com/jprichardson/node-fs-extra/issues/178 "ENOTEMPTY is thrown on removeSync "
[#177]: https://github.com/jprichardson/node-fs-extra/issues/177 "fix `remove()` wildcards (introduced by rimraf) [feature-remove]"
[#176]: https://github.com/jprichardson/node-fs-extra/issues/176 "createOutputStream doesn't emit 'end' event"
[#175]: https://github.com/jprichardson/node-fs-extra/issues/175 "[Feature Request].moveSync support [feature-move, future]"
[#174]: https://github.com/jprichardson/node-fs-extra/pull/174 "Fix copy formatting and document options.filter"
[#173]: https://github.com/jprichardson/node-fs-extra/issues/173 "Feature Request: writeJson should mkdirs"
[#172]: https://github.com/jprichardson/node-fs-extra/issues/172 "rename `clobber` flags to `overwrite`"
[#171]: https://github.com/jprichardson/node-fs-extra/issues/171 "remove unnecessary aliases"
[#170]: https://github.com/jprichardson/node-fs-extra/pull/170 "More robust handling of errors moving across virtual drives"
[#169]: https://github.com/jprichardson/node-fs-extra/pull/169 "suppress ensureLink & ensureSymlink dest exists error"
[#168]: https://github.com/jprichardson/node-fs-extra/pull/168 "suppress ensurelink dest exists error"
[#167]: https://github.com/jprichardson/node-fs-extra/pull/167 "Adds basic (string, buffer) support for ensureFile content [future]"
[#166]: https://github.com/jprichardson/node-fs-extra/pull/166 "Adds basic (string, buffer) support for ensureFile content"
[#165]: https://github.com/jprichardson/node-fs-extra/pull/165 "ensure for link & symlink"
[#164]: https://github.com/jprichardson/node-fs-extra/issues/164 "Feature Request: ensureFile to take optional argument for file content"
[#163]: https://github.com/jprichardson/node-fs-extra/issues/163 "ouputJson not formatted out of the box [bug]"
[#162]: https://github.com/jprichardson/node-fs-extra/pull/162 "ensure symlink & link"
[#161]: https://github.com/jprichardson/node-fs-extra/pull/161 "ensure symlink & link"
[#160]: https://github.com/jprichardson/node-fs-extra/pull/160 "ensure symlink & link"
[#159]: https://github.com/jprichardson/node-fs-extra/pull/159 "ensure symlink & link"
[#158]: https://github.com/jprichardson/node-fs-extra/issues/158 "Feature Request: ensureLink and ensureSymlink methods"
[#157]: https://github.com/jprichardson/node-fs-extra/issues/157 "writeJson isn't formatted"
[#156]: https://github.com/jprichardson/node-fs-extra/issues/156 "Promise.promisifyAll doesn't work for some methods"
[#155]: https://github.com/jprichardson/node-fs-extra/issues/155 "Readme"
[#154]: https://github.com/jprichardson/node-fs-extra/issues/154 "/tmp/millis-test-sync"
[#153]: https://github.com/jprichardson/node-fs-extra/pull/153 "Make preserveTimes also work on read-only files. Closes #152"
[#152]: https://github.com/jprichardson/node-fs-extra/issues/152 "fs.copy fails for read-only files with preserveTimestamp=true [feature-copy]"
[#151]: https://github.com/jprichardson/node-fs-extra/issues/151 "TOC does not work correctly on npm [documentation]"
[#150]: https://github.com/jprichardson/node-fs-extra/issues/150 "Remove test file fixtures, create with code."
[#149]: https://github.com/jprichardson/node-fs-extra/issues/149 "/tmp/millis-test-sync"
[#148]: https://github.com/jprichardson/node-fs-extra/issues/148 "split out `Sync` methods in documentation"
[#147]: https://github.com/jprichardson/node-fs-extra/issues/147 "Adding rmdirIfEmpty"
[#146]: https://github.com/jprichardson/node-fs-extra/pull/146 "ensure test.js works"
[#145]: https://github.com/jprichardson/node-fs-extra/issues/145 "Add `fs.exists` and `fs.existsSync` if it doesn't exist."
[#144]: https://github.com/jprichardson/node-fs-extra/issues/144 "tests failing"
[#143]: https://github.com/jprichardson/node-fs-extra/issues/143 "update graceful-fs"
[#142]: https://github.com/jprichardson/node-fs-extra/issues/142 "PrependFile Feature"
[#141]: https://github.com/jprichardson/node-fs-extra/pull/141 "Add option to preserve timestamps"
[#140]: https://github.com/jprichardson/node-fs-extra/issues/140 "Json file reading fails with 'utf8'"
[#139]: https://github.com/jprichardson/node-fs-extra/pull/139 "Preserve file timestamp on copy. Closes #138"
[#138]: https://github.com/jprichardson/node-fs-extra/issues/138 "Preserve timestamps on copying files"
[#137]: https://github.com/jprichardson/node-fs-extra/issues/137 "outputFile/outputJson: Unexpected end of input"
[#136]: https://github.com/jprichardson/node-fs-extra/pull/136 "Update license attribute"
[#135]: https://github.com/jprichardson/node-fs-extra/issues/135 "emptyDir throws Error if no callback is provided"
[#134]: https://github.com/jprichardson/node-fs-extra/pull/134 "Handle EEXIST error when clobbering dir"
[#133]: https://github.com/jprichardson/node-fs-extra/pull/133 "Travis runs with `sudo: false`"
[#132]: https://github.com/jprichardson/node-fs-extra/pull/132 "isDirectory method"
[#131]: https://github.com/jprichardson/node-fs-extra/issues/131 "copySync is not working iojs 1.8.4 on linux [feature-copy]"
[#130]: https://github.com/jprichardson/node-fs-extra/pull/130 "Please review additional features."
[#129]: https://github.com/jprichardson/node-fs-extra/pull/129 "can you review this feature?"
[#128]: https://github.com/jprichardson/node-fs-extra/issues/128 "fsExtra.move(filepath, newPath) broken;"
[#127]: https://github.com/jprichardson/node-fs-extra/issues/127 "consider using fs.access to remove deprecated warnings for fs.exists"
[#126]: https://github.com/jprichardson/node-fs-extra/issues/126 " TypeError: Object #<Object> has no method 'access'"
[#125]: https://github.com/jprichardson/node-fs-extra/issues/125 "Question: What do the *Sync function do different from non-sync"
[#124]: https://github.com/jprichardson/node-fs-extra/issues/124 "move with clobber option 'ENOTEMPTY'"
[#123]: https://github.com/jprichardson/node-fs-extra/issues/123 "Only copy the content of a directory"
[#122]: https://github.com/jprichardson/node-fs-extra/pull/122 "Update section links in README to match current section ids."
[#121]: https://github.com/jprichardson/node-fs-extra/issues/121 "emptyDir is undefined"
[#120]: https://github.com/jprichardson/node-fs-extra/issues/120 "usage bug caused by shallow cloning methods of 'graceful-fs'"
[#119]: https://github.com/jprichardson/node-fs-extra/issues/119 "mkdirs and ensureDir never invoke callback and consume CPU indefinitely if provided a path with invalid characters on Windows"
[#118]: https://github.com/jprichardson/node-fs-extra/pull/118 "createOutputStream"
[#117]: https://github.com/jprichardson/node-fs-extra/pull/117 "Fixed issue with slash separated paths on windows"
[#116]: https://github.com/jprichardson/node-fs-extra/issues/116 "copySync can only copy directories not files [documentation, feature-copy]"
[#115]: https://github.com/jprichardson/node-fs-extra/issues/115 ".Copy & .CopySync [feature-copy]"
[#114]: https://github.com/jprichardson/node-fs-extra/issues/114 "Fails to move (rename) directory to non-empty directory even with clobber: true"
[#113]: https://github.com/jprichardson/node-fs-extra/issues/113 "fs.copy seems to callback early if the destination file already exists"
[#112]: https://github.com/jprichardson/node-fs-extra/pull/112 "Copying a file into an existing directory"
[#111]: https://github.com/jprichardson/node-fs-extra/pull/111 "Moving a file into an existing directory "
[#110]: https://github.com/jprichardson/node-fs-extra/pull/110 "Moving a file into an existing directory"
[#109]: https://github.com/jprichardson/node-fs-extra/issues/109 "fs.move across windows drives fails"
[#108]: https://github.com/jprichardson/node-fs-extra/issues/108 "fse.move directories across multiple devices doesn't work"
[#107]: https://github.com/jprichardson/node-fs-extra/pull/107 "Check if dest path is an existing dir and copy or move source in it"
[#106]: https://github.com/jprichardson/node-fs-extra/issues/106 "fse.copySync crashes while copying across devices D: [feature-copy]"
[#105]: https://github.com/jprichardson/node-fs-extra/issues/105 "fs.copy hangs on iojs"
[#104]: https://github.com/jprichardson/node-fs-extra/issues/104 "fse.move deletes folders [bug]"
[#103]: https://github.com/jprichardson/node-fs-extra/issues/103 "Error: EMFILE with copy"
[#102]: https://github.com/jprichardson/node-fs-extra/issues/102 "touch / touchSync was removed ?"
[#101]: https://github.com/jprichardson/node-fs-extra/issues/101 "fs-extra promisified"
[#100]: https://github.com/jprichardson/node-fs-extra/pull/100 "copy: options object or filter to pass to ncp"
[#99]: https://github.com/jprichardson/node-fs-extra/issues/99 "ensureDir() modes [future]"
[#98]: https://github.com/jprichardson/node-fs-extra/issues/98 "fs.copy() incorrect async behavior [bug]"
[#97]: https://github.com/jprichardson/node-fs-extra/pull/97 "use path.join; fix copySync bug"
[#96]: https://github.com/jprichardson/node-fs-extra/issues/96 "destFolderExists in copySync is always undefined."
[#95]: https://github.com/jprichardson/node-fs-extra/pull/95 "Using graceful-ncp instead of ncp"
[#94]: https://github.com/jprichardson/node-fs-extra/issues/94 "Error: EEXIST, file already exists '../mkdirp/bin/cmd.js' on fs.copySync() [enhancement, feature-copy]"
[#93]: https://github.com/jprichardson/node-fs-extra/issues/93 "Confusing error if drive not mounted [enhancement]"
[#92]: https://github.com/jprichardson/node-fs-extra/issues/92 "Problems with Bluebird"
[#91]: https://github.com/jprichardson/node-fs-extra/issues/91 "fs.copySync('/test', '/haha') is different with 'cp -r /test /haha' [enhancement]"
[#90]: https://github.com/jprichardson/node-fs-extra/issues/90 "Folder creation and file copy is Happening in 64 bit machine but not in 32 bit machine"
[#89]: https://github.com/jprichardson/node-fs-extra/issues/89 "Error: EEXIST using fs-extra's fs.copy to copy a directory on Windows"
[#88]: https://github.com/jprichardson/node-fs-extra/issues/88 "Stacking those libraries"
[#87]: https://github.com/jprichardson/node-fs-extra/issues/87 "createWriteStream + outputFile = ?"
[#86]: https://github.com/jprichardson/node-fs-extra/issues/86 "no moveSync?"
[#85]: https://github.com/jprichardson/node-fs-extra/pull/85 "Copy symlinks in copySync"
[#84]: https://github.com/jprichardson/node-fs-extra/issues/84 "Push latest version to npm ?"
[#83]: https://github.com/jprichardson/node-fs-extra/issues/83 "Prevent copying a directory into itself [feature-copy]"
[#82]: https://github.com/jprichardson/node-fs-extra/pull/82 "README updates for move"
[#81]: https://github.com/jprichardson/node-fs-extra/issues/81 "fd leak after fs.move"
[#80]: https://github.com/jprichardson/node-fs-extra/pull/80 "Preserve file mode in copySync"
[#79]: https://github.com/jprichardson/node-fs-extra/issues/79 "fs.copy only .html file empty"
[#78]: https://github.com/jprichardson/node-fs-extra/pull/78 "copySync was not applying filters to directories"
[#77]: https://github.com/jprichardson/node-fs-extra/issues/77 "Create README reference to bluebird"
[#76]: https://github.com/jprichardson/node-fs-extra/issues/76 "Create README reference to typescript"
[#75]: https://github.com/jprichardson/node-fs-extra/issues/75 "add glob as a dep? [question]"
[#74]: https://github.com/jprichardson/node-fs-extra/pull/74 "including new emptydir module"
[#73]: https://github.com/jprichardson/node-fs-extra/pull/73 "add dependency status in readme"
[#72]: https://github.com/jprichardson/node-fs-extra/pull/72 "Use svg instead of png to get better image quality"
[#71]: https://github.com/jprichardson/node-fs-extra/issues/71 "fse.copy not working on Windows 7 x64 OS, but, copySync does work"
[#70]: https://github.com/jprichardson/node-fs-extra/issues/70 "Not filter each file, stops on first false [bug]"
[#69]: https://github.com/jprichardson/node-fs-extra/issues/69 "How to check if folder exist and read the folder name"
[#68]: https://github.com/jprichardson/node-fs-extra/issues/68 "consider flag to readJsonSync (throw false) [enhancement]"
[#67]: https://github.com/jprichardson/node-fs-extra/issues/67 "docs for readJson incorrectly states that is accepts options"
[#66]: https://github.com/jprichardson/node-fs-extra/issues/66 "ENAMETOOLONG"
[#65]: https://github.com/jprichardson/node-fs-extra/issues/65 "exclude filter in fs.copy"
[#64]: https://github.com/jprichardson/node-fs-extra/issues/64 "Announce: mfs - monitor your fs-extra calls"
[#63]: https://github.com/jprichardson/node-fs-extra/issues/63 "Walk"
[#62]: https://github.com/jprichardson/node-fs-extra/issues/62 "npm install fs-extra doesn't work"
[#61]: https://github.com/jprichardson/node-fs-extra/issues/61 "No longer supports node 0.8 due to use of `^` in package.json dependencies"
[#60]: https://github.com/jprichardson/node-fs-extra/issues/60 "chmod & chown for mkdirs"
[#59]: https://github.com/jprichardson/node-fs-extra/issues/59 "Consider including mkdirp and making fs-extra '--use_strict' safe [question]"
[#58]: https://github.com/jprichardson/node-fs-extra/issues/58 "Stack trace not included in fs.copy error"
[#57]: https://github.com/jprichardson/node-fs-extra/issues/57 "Possible to include wildcards in delete?"
[#56]: https://github.com/jprichardson/node-fs-extra/issues/56 "Crash when have no access to write to destination file in copy "
[#55]: https://github.com/jprichardson/node-fs-extra/issues/55 "Is it possible to have any console output similar to Grunt copy module?"
[#54]: https://github.com/jprichardson/node-fs-extra/issues/54 "`copy` does not preserve file ownership and permissons"
[#53]: https://github.com/jprichardson/node-fs-extra/issues/53 "outputFile() - ability to write data in appending mode"
[#52]: https://github.com/jprichardson/node-fs-extra/pull/52 "This fixes (what I think) is a bug in copySync"
[#51]: https://github.com/jprichardson/node-fs-extra/pull/51 "Add a Bitdeli Badge to README"
[#50]: https://github.com/jprichardson/node-fs-extra/issues/50 "Replace mechanism in createFile"
[#49]: https://github.com/jprichardson/node-fs-extra/pull/49 "update rimraf to v2.2.6"
[#48]: https://github.com/jprichardson/node-fs-extra/issues/48 "fs.copy issue [bug]"
[#47]: https://github.com/jprichardson/node-fs-extra/issues/47 "Bug in copy - callback called on readStream 'close' - Fixed in ncp 0.5.0"
[#46]: https://github.com/jprichardson/node-fs-extra/pull/46 "update copyright year"
[#45]: https://github.com/jprichardson/node-fs-extra/pull/45 "Added note about fse.outputFile() being the one that overwrites"
[#44]: https://github.com/jprichardson/node-fs-extra/pull/44 "Proposal: Stream support"
[#43]: https://github.com/jprichardson/node-fs-extra/issues/43 "Better error reporting "
[#42]: https://github.com/jprichardson/node-fs-extra/issues/42 "Performance issue?"
[#41]: https://github.com/jprichardson/node-fs-extra/pull/41 "There does seem to be a synchronous version now"
[#40]: https://github.com/jprichardson/node-fs-extra/issues/40 "fs.copy throw unexplained error ENOENT, utime "
[#39]: https://github.com/jprichardson/node-fs-extra/pull/39 "Added regression test for copy() return callback on error"
[#38]: https://github.com/jprichardson/node-fs-extra/pull/38 "Return err in copy() fstat cb, because stat could be undefined or null"
[#37]: https://github.com/jprichardson/node-fs-extra/issues/37 "Maybe include a line reader? [enhancement, question]"
[#36]: https://github.com/jprichardson/node-fs-extra/pull/36 "`filter` parameter `fs.copy` and `fs.copySync`"
[#35]: https://github.com/jprichardson/node-fs-extra/pull/35 "`filter` parameter `fs.copy` and `fs.copySync` "
[#34]: https://github.com/jprichardson/node-fs-extra/issues/34 "update docs to include options for JSON methods [enhancement]"
[#33]: https://github.com/jprichardson/node-fs-extra/pull/33 "fs_extra.copySync"
[#32]: https://github.com/jprichardson/node-fs-extra/issues/32 "update to latest jsonfile [enhancement]"
[#31]: https://github.com/jprichardson/node-fs-extra/issues/31 "Add ensure methods [enhancement]"
[#30]: https://github.com/jprichardson/node-fs-extra/issues/30 "update package.json optional dep `graceful-fs`"
[#29]: https://github.com/jprichardson/node-fs-extra/issues/29 "Copy failing if dest directory doesn't exist. Is this intended?"
[#28]: https://github.com/jprichardson/node-fs-extra/issues/28 "homepage field must be a string url. Deleted."
[#27]: https://github.com/jprichardson/node-fs-extra/issues/27 "Update Readme"
[#26]: https://github.com/jprichardson/node-fs-extra/issues/26 "Add readdir recursive method. [enhancement]"
[#25]: https://github.com/jprichardson/node-fs-extra/pull/25 "adding an `.npmignore` file"
[#24]: https://github.com/jprichardson/node-fs-extra/issues/24 "[bug] cannot run in strict mode [bug]"
[#23]: https://github.com/jprichardson/node-fs-extra/issues/23 "`writeJSON()` should create parent directories"
[#22]: https://github.com/jprichardson/node-fs-extra/pull/22 "Add a limit option to mkdirs()"
[#21]: https://github.com/jprichardson/node-fs-extra/issues/21 "touch() in 0.10.0"
[#20]: https://github.com/jprichardson/node-fs-extra/issues/20 "fs.remove yields callback before directory is really deleted"
[#19]: https://github.com/jprichardson/node-fs-extra/issues/19 "fs.copy err is empty array"
[#18]: https://github.com/jprichardson/node-fs-extra/pull/18 "Exposed copyFile Function"
[#17]: https://github.com/jprichardson/node-fs-extra/issues/17 "Use `require('graceful-fs')` if found instead of `require('fs')`"
[#16]: https://github.com/jprichardson/node-fs-extra/pull/16 "Update README.md"
[#15]: https://github.com/jprichardson/node-fs-extra/issues/15 "Implement cp -r but sync aka copySync. [enhancement]"
[#14]: https://github.com/jprichardson/node-fs-extra/issues/14 "fs.mkdirSync is broken in 0.3.1"
[#13]: https://github.com/jprichardson/node-fs-extra/issues/13 "Thoughts on including a directory tree / file watcher? [enhancement, question]"
[#12]: https://github.com/jprichardson/node-fs-extra/issues/12 "copyFile & copyFileSync are global"
[#11]: https://github.com/jprichardson/node-fs-extra/issues/11 "Thoughts on including a file walker? [enhancement, question]"
[#10]: https://github.com/jprichardson/node-fs-extra/issues/10 "move / moveFile API [enhancement]"
[#9]: https://github.com/jprichardson/node-fs-extra/issues/9 "don't import normal fs stuff into fs-extra"
[#8]: https://github.com/jprichardson/node-fs-extra/pull/8 "Update rimraf to latest version"
[#6]: https://github.com/jprichardson/node-fs-extra/issues/6 "Remove CoffeeScript development dependency"
[#5]: https://github.com/jprichardson/node-fs-extra/issues/5 "comments on naming"
[#4]: https://github.com/jprichardson/node-fs-extra/issues/4 "version bump to 0.2"
[#3]: https://github.com/jprichardson/node-fs-extra/pull/3 "Hi! I fixed some code for you!"
[#2]: https://github.com/jprichardson/node-fs-extra/issues/2 "Merge with fs.extra and mkdirp"
[#1]: https://github.com/jprichardson/node-fs-extra/issues/1 "file-extra npm !exist"
+15
View File
@@ -0,0 +1,15 @@
(The MIT License)
Copyright (c) 2011-2017 JP Richardson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+242
View File
@@ -0,0 +1,242 @@
Node.js: fs-extra
=================
`fs-extra` adds file system methods that aren't included in the native `fs` module and adds promise support to the `fs` methods. It should be a drop in replacement for `fs`.
[![npm Package](https://img.shields.io/npm/v/fs-extra.svg?style=flat-square)](https://www.npmjs.org/package/fs-extra)
[![build status](https://api.travis-ci.org/jprichardson/node-fs-extra.svg)](http://travis-ci.org/jprichardson/node-fs-extra)
[![windows Build status](https://img.shields.io/appveyor/ci/jprichardson/node-fs-extra/master.svg?label=windows%20build)](https://ci.appveyor.com/project/jprichardson/node-fs-extra/branch/master)
[![downloads per month](http://img.shields.io/npm/dm/fs-extra.svg)](https://www.npmjs.org/package/fs-extra)
[![Coverage Status](https://img.shields.io/coveralls/jprichardson/node-fs-extra.svg)](https://coveralls.io/r/jprichardson/node-fs-extra)
<a href="https://github.com/feross/standard"><img src="https://cdn.rawgit.com/feross/standard/master/sticker.svg" alt="Standard JavaScript" width="100"></a>
Why?
----
I got tired of including `mkdirp`, `rimraf`, and `ncp` in most of my projects.
Installation
------------
npm install --save fs-extra
Usage
-----
`fs-extra` is a drop in replacement for native `fs`. All methods in `fs` are attached to `fs-extra`. All `fs` methods return promises if the callback isn't passed.
You don't ever need to include the original `fs` module again:
```js
const fs = require('fs') // this is no longer necessary
```
you can now do this:
```js
const fs = require('fs-extra')
```
or if you prefer to make it clear that you're using `fs-extra` and not `fs`, you may want
to name your `fs` variable `fse` like so:
```js
const fse = require('fs-extra')
```
you can also keep both, but it's redundant:
```js
const fs = require('fs')
const fse = require('fs-extra')
```
Sync vs Async
-------------
Most methods are async by default. All async methods will return a promise if the callback isn't passed.
Sync methods on the other hand will throw if an error occurs.
Example:
```js
const fs = require('fs-extra')
// Async with promises:
fs.copy('/tmp/myfile', '/tmp/mynewfile')
.then(() => console.log('success!'))
.catch(err => console.error(err))
// Async with callbacks:
fs.copy('/tmp/myfile', '/tmp/mynewfile', err => {
if (err) return console.error(err)
console.log('success!')
})
// Sync:
try {
fs.copySync('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
```
Methods
-------
### Async
- [copy](docs/copy.md)
- [emptyDir](docs/emptyDir.md)
- [ensureFile](docs/ensureFile.md)
- [ensureDir](docs/ensureDir.md)
- [ensureLink](docs/ensureLink.md)
- [ensureSymlink](docs/ensureSymlink.md)
- [mkdirs](docs/ensureDir.md)
- [move](docs/move.md)
- [outputFile](docs/outputFile.md)
- [outputJson](docs/outputJson.md)
- [pathExists](docs/pathExists.md)
- [readJson](docs/readJson.md)
- [remove](docs/remove.md)
- [writeJson](docs/writeJson.md)
### Sync
- [copySync](docs/copy-sync.md)
- [emptyDirSync](docs/emptyDir-sync.md)
- [ensureFileSync](docs/ensureFile-sync.md)
- [ensureDirSync](docs/ensureDir-sync.md)
- [ensureLinkSync](docs/ensureLink-sync.md)
- [ensureSymlinkSync](docs/ensureSymlink-sync.md)
- [mkdirsSync](docs/ensureDir-sync.md)
- [moveSync](docs/move-sync.md)
- [outputFileSync](docs/outputFile-sync.md)
- [outputJsonSync](docs/outputJson-sync.md)
- [pathExistsSync](docs/pathExists-sync.md)
- [readJsonSync](docs/readJson-sync.md)
- [removeSync](docs/remove-sync.md)
- [writeJsonSync](docs/writeJson-sync.md)
**NOTE:** You can still use the native Node.js methods. They are promisified and copied over to `fs-extra`.
### What happened to `walk()` and `walkSync()`?
They were removed from `fs-extra` in v2.0.0. If you need the functionality, `walk` and `walkSync` are available as separate packages, [`klaw`](https://github.com/jprichardson/node-klaw) and [`klaw-sync`](https://github.com/manidlou/node-klaw-sync).
Third Party
-----------
### TypeScript
If you like TypeScript, you can use `fs-extra` with it: https://github.com/borisyankov/DefinitelyTyped/tree/master/fs-extra
### File / Directory Watching
If you want to watch for changes to files or directories, then you should use [chokidar](https://github.com/paulmillr/chokidar).
### Misc.
- [mfs](https://github.com/cadorn/mfs) - Monitor your fs-extra calls.
Hacking on fs-extra
-------------------
Wanna hack on `fs-extra`? Great! Your help is needed! [fs-extra is one of the most depended upon Node.js packages](http://nodei.co/npm/fs-extra.png?downloads=true&downloadRank=true&stars=true). This project
uses [JavaScript Standard Style](https://github.com/feross/standard) - if the name or style choices bother you,
you're gonna have to get over it :) If `standard` is good enough for `npm`, it's good enough for `fs-extra`.
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
What's needed?
- First, take a look at existing issues. Those are probably going to be where the priority lies.
- More tests for edge cases. Specifically on different platforms. There can never be enough tests.
- Improve test coverage. See coveralls output for more info.
Note: If you make any big changes, **you should definitely file an issue for discussion first.**
### Running the Test Suite
fs-extra contains hundreds of tests.
- `npm run lint`: runs the linter ([standard](http://standardjs.com/))
- `npm run unit`: runs the unit tests
- `npm test`: runs both the linter and the tests
### Windows
If you run the tests on the Windows and receive a lot of symbolic link `EPERM` permission errors, it's
because on Windows you need elevated privilege to create symbolic links. You can add this to your Windows's
account by following the instructions here: http://superuser.com/questions/104845/permission-to-make-symbolic-links-in-windows-7
However, I didn't have much luck doing this.
Since I develop on Mac OS X, I use VMWare Fusion for Windows testing. I create a shared folder that I map to a drive on Windows.
I open the `Node.js command prompt` and run as `Administrator`. I then map the network drive running the following command:
net use z: "\\vmware-host\Shared Folders"
I can then navigate to my `fs-extra` directory and run the tests.
Naming
------
I put a lot of thought into the naming of these functions. Inspired by @coolaj86's request. So he deserves much of the credit for raising the issue. See discussion(s) here:
* https://github.com/jprichardson/node-fs-extra/issues/2
* https://github.com/flatiron/utile/issues/11
* https://github.com/ryanmcgrath/wrench-js/issues/29
* https://github.com/substack/node-mkdirp/issues/17
First, I believe that in as many cases as possible, the [Node.js naming schemes](http://nodejs.org/api/fs.html) should be chosen. However, there are problems with the Node.js own naming schemes.
For example, `fs.readFile()` and `fs.readdir()`: the **F** is capitalized in *File* and the **d** is not capitalized in *dir*. Perhaps a bit pedantic, but they should still be consistent. Also, Node.js has chosen a lot of POSIX naming schemes, which I believe is great. See: `fs.mkdir()`, `fs.rmdir()`, `fs.chown()`, etc.
We have a dilemma though. How do you consistently name methods that perform the following POSIX commands: `cp`, `cp -r`, `mkdir -p`, and `rm -rf`?
My perspective: when in doubt, err on the side of simplicity. A directory is just a hierarchical grouping of directories and files. Consider that for a moment. So when you want to copy it or remove it, in most cases you'll want to copy or remove all of its contents. When you want to create a directory, if the directory that it's suppose to be contained in does not exist, then in most cases you'll want to create that too.
So, if you want to remove a file or a directory regardless of whether it has contents, just call `fs.remove(path)`. If you want to copy a file or a directory whether it has contents, just call `fs.copy(source, destination)`. If you want to create a directory regardless of whether its parent directories exist, just call `fs.mkdirs(path)` or `fs.mkdirp(path)`.
Credit
------
`fs-extra` wouldn't be possible without using the modules from the following authors:
- [Isaac Shlueter](https://github.com/isaacs)
- [Charlie McConnel](https://github.com/avianflu)
- [James Halliday](https://github.com/substack)
- [Andrew Kelley](https://github.com/andrewrk)
License
-------
Licensed under MIT
Copyright (c) 2011-2017 [JP Richardson](https://github.com/jprichardson)
[1]: http://nodejs.org/docs/latest/api/fs.html
[jsonfile]: https://github.com/jprichardson/node-jsonfile
+37
View File
@@ -0,0 +1,37 @@
# copySync(src, dest, [options])
Copy a file or directory. The directory can have contents. Like `cp -r`.
- `src` `<String>`
- `dest` `<String>`
- `options` `<Object>`
- `overwrite` `<boolean>`: overwrite existing file or directory, default is `true`. _Note that the copy operation will silently fail if you set this to `false` and the destination exists._ Use the `errorOnExist` option to change this behavior.
- `errorOnExist` `<boolean>`: when `overwrite` is `false` and the destination exists, throw an error. Default is `false`.
- `dereference` `<boolean>`: dereference symlinks, default is `false`.
- `preserveTimestamps` `<boolean>`: will set last modification and access times to the ones of the original source files, default is `false`.
- `filter` `<Function>`: Function to filter copied files. Return `true` to include, `false` to exclude. This can also be a RegExp, however this is deprecated (See [issue #239](https://github.com/jprichardson/node-fs-extra/issues/239) for background).
## Example:
```js
const fs = require('fs-extra')
// copy file
fs.copySync('/tmp/myfile', '/tmp/mynewfile')
// copy directory, even if it has subdirectories or files
fs.copySync('/tmp/mydir', '/tmp/mynewdir')
```
**Using filter function**
```js
const fs = require('fs-extra')
const filterFunc = (src, dest) => {
// your logic here
// it will be copied if return true
}
fs.copySync('/tmp/mydir', '/tmp/mynewdir', { filter: filterFunc })
```
+57
View File
@@ -0,0 +1,57 @@
# copy(src, dest, [options, callback])
Copy a file or directory. The directory can have contents. Like `cp -r`.
- `src` `<String>`
- `dest` `<String>`
- `options` `<Object>`
- `overwrite` `<boolean>`: overwrite existing file or directory, default is `true`. _Note that the copy operation will silently fail if you set this to `false` and the destination exists._ Use the `errorOnExist` option to change this behavior.
- `errorOnExist` `<boolean>`: when `overwrite` is `false` and the destination exists, throw an error. Default is `false`.
- `dereference` `<boolean>`: dereference symlinks, default is `false`.
- `preserveTimestamps` `<boolean>`: will set last modification and access times to the ones of the original source files, default is `false`.
- `filter` `<Function>`: Function to filter copied files. Return `true` to include, `false` to exclude. This can also be a RegExp, however this is deprecated (See [issue #239](https://github.com/jprichardson/node-fs-extra/issues/239) for background).
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
fs.copy('/tmp/myfile', '/tmp/mynewfile', err => {
if (err) return console.error(err)
console.log('success!')
}) // copies file
fs.copy('/tmp/mydir', '/tmp/mynewdir', err => {
if (err) return console.error(err)
console.log('success!')
}) // copies directory, even if it has subdirectories or files
// Promise usage:
fs.copy('/tmp/myfile', '/tmp/mynewfile')
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
**Using filter function**
```js
const fs = require('fs-extra')
const filterFunc = (src, dest) => {
// your logic here
// it will be copied if return true
}
fs.copy('/tmp/mydir', '/tmp/mynewdir', { filter: filterFunc }, err => {
if (err) return console.error(err)
console.log('success!')
})
```
+16
View File
@@ -0,0 +1,16 @@
# emptyDirSync(dir)
Ensures that a directory is empty. Deletes directory contents if the directory is not empty. If the directory does not exist, it is created. The directory itself is not deleted.
**Alias:** `emptydirSync()`
- `dir` `<String>`
## Example:
```js
const fs = require('fs-extra')
// assume this directory has a lot of files and folders
fs.emptyDirSync('/tmp/some/dir')
```
+30
View File
@@ -0,0 +1,30 @@
# emptyDir(dir, [callback])
Ensures that a directory is empty. Deletes directory contents if the directory is not empty. If the directory does not exist, it is created. The directory itself is not deleted.
**Alias:** `emptydir()`
- `dir` `<String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
// assume this directory has a lot of files and folders
fs.emptyDir('/tmp/some/dir', err => {
if (err) return console.error(err)
console.log('success!')
})
// With promises
fs.emptyDir('/tmp/some/dir')
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
+17
View File
@@ -0,0 +1,17 @@
# ensureDirSync(dir)
Ensures that the directory exists. If the directory structure does not exist, it is created. Like `mkdir -p`.
**Aliases:** `mkdirsSync()`, `mkdirpSync()`
- `dir` `<String>`
## Example:
```js
const fs = require('fs-extra')
const dir = '/tmp/this/path/does/not/exist'
fs.ensureDirSync(dir)
// dir has now been created, including the directory it is to be placed in
```
+29
View File
@@ -0,0 +1,29 @@
# ensureDir(dir, [callback])
Ensures that the directory exists. If the directory structure does not exist, it is created. Like `mkdir -p`.
**Aliases:** `mkdirs()`, `mkdirp()`
- `dir` `<String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
const dir = '/tmp/this/path/does/not/exist'
fs.ensureDir(dir, err => {
console.log(err) // => null
// dir has now been created, including the directory it is to be placed in
})
// With Promises:
fs.ensureDir(dir)
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
@@ -0,0 +1,17 @@
# ensureFileSync(file)
Ensures that the file exists. If the file that is requested to be created is in directories that do not exist, these directories are created. If the file already exists, it is **NOT MODIFIED**.
**Alias:** `createFileSync()`
- `file` `<String>`
## Example:
```js
const fs = require('fs-extra')
const file = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureFileSync(file)
// file has now been created, including the directory it is to be placed in
```
+29
View File
@@ -0,0 +1,29 @@
# ensureFile(file, [callback])
Ensures that the file exists. If the file that is requested to be created is in directories that do not exist, these directories are created. If the file already exists, it is **NOT MODIFIED**.
**Alias:** `createFile()`
- `file` `<String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
const file = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureFile(file, err => {
console.log(err) // => null
// file has now been created, including the directory it is to be placed in
})
// With Promises:
fs.ensureFile(file)
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
@@ -0,0 +1,17 @@
# ensureLinkSync(srcpath, dstpath)
Ensures that the link exists. If the directory structure does not exist, it is created.
- `srcpath` `<String>`
- `dstpath` `<String>`
## Example:
```js
const fs = require('fs-extra')
const srcpath = '/tmp/file.txt'
const dstpath = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureLinkSync(srcpath, dstpath)
// link has now been created, including the directory it is to be placed in
```
+29
View File
@@ -0,0 +1,29 @@
# ensureLink(srcpath, dstpath, [callback])
Ensures that the link exists. If the directory structure does not exist, it is created.
- `srcpath` `<String>`
- `dstpath` `<String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
const srcpath = '/tmp/file.txt'
const dstpath = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureLink(srcpath, dstpath, err => {
console.log(err) // => null
// link has now been created, including the directory it is to be placed in
})
// With Promises:
fs.ensureLink(srcpath, dstpath)
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
@@ -0,0 +1,18 @@
# ensureSymlinkSync(srcpath, dstpath, [type])
Ensures that the symlink exists. If the directory structure does not exist, it is created.
- `srcpath` `<String>`
- `dstpath` `<String>`
- `type` `<String>`
## Example:
```js
const fs = require('fs-extra')
const srcpath = '/tmp/file.txt'
const dstpath = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureSymlinkSync(srcpath, dstpath)
// symlink has now been created, including the directory it is to be placed in
```
+30
View File
@@ -0,0 +1,30 @@
# ensureSymlink(srcpath, dstpath, [type, callback])
Ensures that the symlink exists. If the directory structure does not exist, it is created.
- `srcpath` `<String>`
- `dstpath` `<String>`
- `type` `<String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
const srcpath = '/tmp/file.txt'
const dstpath = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureSymlink(srcpath, dstpath, err => {
console.log(err) // => null
// symlink has now been created, including the directory it is to be placed in
})
// With Promises:
fs.ensureSymlink(srcpath, dstpath)
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
+24
View File
@@ -0,0 +1,24 @@
# moveSync(src, dest, [options])
Moves a file or directory, even across devices.
- `src` `<String>`
- `dest` `<String>`
- `options` `<Object>`
- `overwrite` `<boolean>`: overwrite existing file or directory, default is `false`.
## Example:
```js
const fs = require('fs-extra')
fs.moveSync('/tmp/somefile', '/tmp/does/not/exist/yet/somefile')
```
**Using `overwrite` option**
```js
const fs = require('fs-extra')
fs.moveSync('/tmp/somedir', '/tmp/may/already/existed/somedir', { overwrite: true })
```
+41
View File
@@ -0,0 +1,41 @@
# move(src, dest, [options, callback])
Moves a file or directory, even across devices.
- `src` `<String>`
- `dest` `<String>`
- `options` `<Object>`
- `overwrite` `<boolean>`: overwrite existing file or directory, default is `false`.
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
fs.move('/tmp/somefile', '/tmp/does/not/exist/yet/somefile', err => {
if (err) return console.error(err)
console.log('success!')
})
fs.move('/tmp/somefile', '/tmp/does/not/exist/yet/somefile')
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
**Using `overwrite` option**
```js
const fs = require('fs-extra')
fs.move('/tmp/somedir', '/tmp/may/already/existed/somedir', { overwrite: true }, err => {
if (err) return console.error(err)
console.log('success!')
})
```
@@ -0,0 +1,19 @@
# outputFileSync(file, data, [options])
Almost the same as `writeFileSync` (i.e. it [overwrites](http://pages.citebite.com/v2o5n8l2f5reb)), except that if the parent directory does not exist, it's created. `file` must be a file path (a buffer or a file descriptor is not allowed). `options` are what you'd pass to [`fs.writeFileSync()`](https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options).
- `file` `<String>`
- `data` `<String> | <Buffer> | <Uint8Array>`
- `options` `<Object> | <String>`
## Example:
```js
const fs = require('fs-extra')
const file = '/tmp/this/path/does/not/exist/file.txt'
fs.outputFileSync(file, 'hello!')
const data = fs.readFileSync(file, 'utf8')
console.log(data) // => hello!
```
+34
View File
@@ -0,0 +1,34 @@
# outputFile(file, data, [options, callback])
Almost the same as `writeFile` (i.e. it [overwrites](http://pages.citebite.com/v2o5n8l2f5reb)), except that if the parent directory does not exist, it's created. `file` must be a file path (a buffer or a file descriptor is not allowed). `options` are what you'd pass to [`fs.writeFile()`](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback).
- `file` `<String>`
- `data` `<String> | <Buffer> | <Uint8Array>`
- `options` `<Object> | <String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
const file = '/tmp/this/path/does/not/exist/file.txt'
fs.outputFile(file, 'hello!', err => {
console.log(err) // => null
fs.readFile(file, 'utf8', (err, data) => {
if (err) return console.error(err)
console.log(data) // => hello!
})
})
// With Promises:
fs.outputFile(file, 'hello!')
.then(() => fs.readFile(file, 'utf8'))
.then(data => {
console.log(data) // => hello!
})
.catch(err => {
console.error(err)
})
```
@@ -0,0 +1,22 @@
# outputJsonSync(file, object, [options])
Almost the same as [`writeJsonSync`](writeJson-sync.md), except that if the directory does not exist, it's created.
`options` are what you'd pass to [`jsonFile.writeFileSync()`](https://github.com/jprichardson/node-jsonfile#writefilesyncfilename-obj-options).
**Alias:** `outputJSONSync()`
- `file` `<String>`
- `object` `<Object>`
- `options` `<Object>`
## Example:
```js
const fs = require('fs-extra')
const file = '/tmp/this/path/does/not/exist/file.json'
fs.outputJsonSync(file, {name: 'JP'})
const data = fs.readJsonSync(file)
console.log(data.name) // => JP
```
+37
View File
@@ -0,0 +1,37 @@
# outputJson(file, object, [options, callback])
Almost the same as [`writeJson`](writeJson.md), except that if the directory does not exist, it's created.
`options` are what you'd pass to [`jsonFile.writeFile()`](https://github.com/jprichardson/node-jsonfile#writefilefilename-options-callback).
**Alias:** `outputJSON()`
- `file` `<String>`
- `object` `<Object>`
- `options` `<Object>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
const file = '/tmp/this/path/does/not/exist/file.json'
fs.outputJson(file, {name: 'JP'}, err => {
console.log(err) // => null
fs.readJson(file, (err, data) => {
if (err) return console.error(err)
console.log(data.name) // => JP
})
})
// With Promises:
fs.outputJson(file, {name: 'JP'})
.then(() => fs.readJson(file))
.then(data => {
console.log(data.name) // => JP
})
.catch(err => {
console.error(err)
})
```
@@ -0,0 +1,3 @@
# pathExistsSync(file)
An alias for [`fs.existsSync()`](https://nodejs.org/api/fs.html#fs_fs_existssync_path), created for consistency with [`pathExists()`](pathExists.md).
+22
View File
@@ -0,0 +1,22 @@
# pathExists(file[, callback])
Test whether or not the given path exists by checking with the file system. Like [`fs.exists`](https://nodejs.org/api/fs.html#fs_fs_exists_path_callback), but with a normal callback signature (err, exists). Uses `fs.access` under the hood.
- `file` `<String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
const file = '/tmp/this/path/does/not/exist/file.txt'
// Promise usage:
fs.pathExists(file)
.then(exists => console.log(exists)) // => false
// Callback usage:
fs.pathExists(file, (err, exists) => {
console.log(err) // => null
console.log(exists) // => false
})
```
+33
View File
@@ -0,0 +1,33 @@
# readJsonSync(file, [options])
Reads a JSON file and then parses it into an object. `options` are the same
that you'd pass to [`jsonFile.readFileSync`](https://github.com/jprichardson/node-jsonfile#readfilesyncfilename-options).
**Alias:** `readJSONSync()`
- `file` `<String>`
- `options` `<Object>`
## Example:
```js
const fs = require('fs-extra')
const packageObj = fs.readJsonSync('./package.json')
console.log(packageObj.version) // => 2.0.0
```
---
`readJsonSync()` can take a `throws` option set to `false` and it won't throw if the JSON is invalid. Example:
```js
const fs = require('fs-extra')
const file = '/tmp/some-invalid.json'
const data = '{not valid JSON'
fs.writeFileSync(file, data)
const obj = fs.readJsonSync(file, { throws: false })
console.log(obj) // => null
```
+58
View File
@@ -0,0 +1,58 @@
# readJson(file, [options, callback])
Reads a JSON file and then parses it into an object. `options` are the same
that you'd pass to [`jsonFile.readFile`](https://github.com/jprichardson/node-jsonfile#readfilefilename-options-callback).
**Alias:** `readJSON()`
- `file` `<String>`
- `options` `<Object>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
fs.readJson('./package.json', (err, packageObj) => {
if (err) console.error(err)
console.log(packageObj.version) // => 0.1.3
})
// Promise Usage
fs.readJson('./package.json')
.then(packageObj => {
console.log(packageObj.version) // => 0.1.3
})
.catch(err => {
console.error(err)
})
```
---
`readJson()` can take a `throws` option set to `false` and it won't throw if the JSON is invalid. Example:
```js
const fs = require('fs-extra')
const file = '/tmp/some-invalid.json'
const data = '{not valid JSON'
fs.writeFileSync(file, data)
fs.readJson(file, { throws: false }, (err, obj) => {
if (err) console.error(err)
console.log(obj) // => null
})
// Promise Usage
fs.readJson(file, { throws: false })
.then(obj => {
console.log(obj) // => null
})
.catch(err => {
console.error(err) // Not called
})
```
+16
View File
@@ -0,0 +1,16 @@
# removeSync(path)
Removes a file or directory. The directory can have contents. Like `rm -rf`.
- `path` `<String>`
## Example:
```js
const fs = require('fs-extra')
// remove file
fs.removeSync('/tmp/myfile')
fs.removeSync('/home/jprichardson') // I just deleted my entire HOME directory.
```
+34
View File
@@ -0,0 +1,34 @@
# remove(path, [callback])
Removes a file or directory. The directory can have contents. Like `rm -rf`.
- `path` `<String>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
// remove file
fs.remove('/tmp/myfile', err => {
if (err) return console.error(err)
console.log('success!')
})
fs.remove('/home/jprichardson', err => {
if (err) return console.error(err)
console.log('success!') // I just deleted my entire HOME directory.
})
// Promise Usage
fs.remove('/tmp/myfile')
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
+21
View File
@@ -0,0 +1,21 @@
# writeJsonSync(file, object, [options])
Writes an object to a JSON file. `options` are the same that
you'd pass to [`jsonFile.writeFileSync()`](https://github.com/jprichardson/node-jsonfile#writefilesyncfilename-obj-options).
**Alias:** `writeJSONSync()`
- `file` `<String>`
- `object` `<Object>`
- `options` `<Object>`
## Example:
```js
const fs = require('fs-extra')
fs.writeJsonSync('./package.json', {name: 'fs-extra'})
```
---
**See also:** [`outputJsonSync()`](outputJson-sync.md)
+36
View File
@@ -0,0 +1,36 @@
# writeJson(file, object, [options, callback])
Writes an object to a JSON file. `options` are the same that
you'd pass to [`jsonFile.writeFile()`](https://github.com/jprichardson/node-jsonfile#writefilefilename-options-callback).
**Alias:** `writeJSON()`
- `file` `<String>`
- `object` `<Object>`
- `options` `<Object>`
- `callback` `<Function>`
## Example:
```js
const fs = require('fs-extra')
fs.writeJson('./package.json', {name: 'fs-extra'}, err => {
if (err) return console.error(err)
console.log('success!')
})
// With Promises
fs.writeJson('./package.json', {name: 'fs-extra'})
.then(() => {
console.log('success!')
})
.catch(err => {
console.error(err)
})
```
---
**See also:** [`outputJson()`](outputJson.md)
@@ -0,0 +1,41 @@
'use strict'
const fs = require('graceful-fs')
const BUF_LENGTH = 64 * 1024
const _buff = require('../util/buffer')(BUF_LENGTH)
function copyFileSync (srcFile, destFile, options) {
const overwrite = options.overwrite
const errorOnExist = options.errorOnExist
const preserveTimestamps = options.preserveTimestamps
if (fs.existsSync(destFile)) {
if (overwrite) {
fs.unlinkSync(destFile)
} else if (errorOnExist) {
throw new Error(`${destFile} already exists`)
} else return
}
const fdr = fs.openSync(srcFile, 'r')
const stat = fs.fstatSync(fdr)
const fdw = fs.openSync(destFile, 'w', stat.mode)
let bytesRead = 1
let pos = 0
while (bytesRead > 0) {
bytesRead = fs.readSync(fdr, _buff, 0, BUF_LENGTH, pos)
fs.writeSync(fdw, _buff, 0, bytesRead)
pos += bytesRead
}
if (preserveTimestamps) {
fs.futimesSync(fdw, stat.atime, stat.mtime)
}
fs.closeSync(fdr)
fs.closeSync(fdw)
}
module.exports = copyFileSync
@@ -0,0 +1,62 @@
'use strict'
const fs = require('graceful-fs')
const path = require('path')
const copyFileSync = require('./copy-file-sync')
const mkdir = require('../mkdirs')
function copySync (src, dest, options) {
if (typeof options === 'function' || options instanceof RegExp) {
options = {filter: options}
}
options = options || {}
options.recursive = !!options.recursive
// default to true for now
options.clobber = 'clobber' in options ? !!options.clobber : true
// overwrite falls back to clobber
options.overwrite = 'overwrite' in options ? !!options.overwrite : options.clobber
options.dereference = 'dereference' in options ? !!options.dereference : false
options.preserveTimestamps = 'preserveTimestamps' in options ? !!options.preserveTimestamps : false
options.filter = options.filter || function () { return true }
// Warn about using preserveTimestamps on 32-bit node:
if (options.preserveTimestamps && process.arch === 'ia32') {
console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n
see https://github.com/jprichardson/node-fs-extra/issues/269`)
}
const stats = (options.recursive && !options.dereference) ? fs.lstatSync(src) : fs.statSync(src)
const destFolder = path.dirname(dest)
const destFolderExists = fs.existsSync(destFolder)
let performCopy = false
if (options.filter instanceof RegExp) {
console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function')
performCopy = options.filter.test(src)
} else if (typeof options.filter === 'function') performCopy = options.filter(src, dest)
if (stats.isFile() && performCopy) {
if (!destFolderExists) mkdir.mkdirsSync(destFolder)
copyFileSync(src, dest, {
overwrite: options.overwrite,
errorOnExist: options.errorOnExist,
preserveTimestamps: options.preserveTimestamps
})
} else if (stats.isDirectory() && performCopy) {
if (!fs.existsSync(dest)) mkdir.mkdirsSync(dest)
const contents = fs.readdirSync(src)
contents.forEach(content => {
const opts = options
opts.recursive = true
copySync(path.join(src, content), path.join(dest, content), opts)
})
} else if (options.recursive && stats.isSymbolicLink() && performCopy) {
const srcPath = fs.readlinkSync(src)
fs.symlinkSync(srcPath, dest)
}
}
module.exports = copySync
@@ -0,0 +1,3 @@
module.exports = {
copySync: require('./copy-sync')
}

Some files were not shown because too many files have changed in this diff Show More