This issue is meant to track the discussion and implementation of Real Time Collaboration (RTC) in JupyterLab.
Broadly, we wish to enable RTC for state across the application. Specifically, document models such as text and notebook models should be readily shareable.
This sharing of state should scale (Jupyter is regularly deployed to 1k-10k's of users in enterprise or academic settings).
We will move toward a server-side representation of model state, allowing us to handle kernel messages when there is no active front end, and then render an accurate reflection of the document when the UI is loaded.
Additionally, save and load of the document should be done via the RTC server, to prevent conflict with the on-disk representation.
Additional design goals and constraints:
To that end, we have been working on an implementation of the conflict-free replicated data type (CRDT) algorithm that meets our design goals here, with a prototype server implementation here. There is also discussion around the impact to the notebook model (as a representative complex model) here.
Many people, including myself are interested in helping out with this feature. Where can we find some more info on the progress and todo’s of this feature?
It seems that the refactoring of the notebook model is an inhibiting factor on the progress. Is it perhaps possible to work on a prototype that supports text documents in JupyterLab? This would enable us to implement other parts of the feature.
Your assessment is right on - that the refactoring of the notebook model to use a real-time data store is the blocking point. Most of the core work is complete in this branch:
https://github.com/phosphorjs/phosphor/tree/feature-tables3/packages/datastore
This month, we are developing a plan that will enable others to jump in and help us finish the last few pieces. We will be posting updates here as we get to that point. We are hoping that the work can be done in parallel by a number of folks, so your help would be greatly appreciated. This last year has been rough for the team with a number of our upstream funding sources shifting priorities for reasons unrelated to JupyterLab.
In the meantime, beginning to look through the code in that branch would be a good way to get going.
Just an update: I'm currently putting in some work on that phosphor branch, and I will be adding code for lab to synchronize RTC data (webscoket based exchange of transactions). I'll be linking relevant PRs here as they come along. I guess adding a text editor RTC proof of concept is a decent start there, before transitioning to the notebook model refactor.
Some parallel tasks that are up for grabs:
If anybody has the time to sprint on these things, I'd be happy to set up video calls to get people up to speed quickly.
Me, @ian-r-rose and @afshin had a short talk about the refactoring that needs to happen to best support the phosphor datastore backend. These were some of the main points (cleaned up after the fact by me):
IModelDB has different change event arguments than the phosphor datastore.IModelDB is replaced/redefined with something that more closely matches the phosphor datastore change arguments. It should also take a schema on initialization.Other inputs to any of these points are very welcome!
IModelDB abstraction, as they are at the same level.I think it would be nice to have a call to slough through some of the main issues for RTC. E.g. about the permissions things that you mentioned with @Zsailer, but also several other points. If I said early next week, late EU / early US time, who would potentially be interested/available? Add a 👍 emoji to this post if interested.
who would potentially be interested/available?
I would be interested. How about we signal interest in being included by +1'ing your comment above?
Current status:
IModelDB. ~Status: Partial refactor such that the RTC fits as closely as possible with the current ModelDB is in place.~ Refactor in progress in multiple PRs.More? I'll try to keep this updated. Current lab work is here: https://github.com/vidartf/jupyterlab/tree/rtc
From a discussion in #6003, I wanted to summarize the UX design and usage cases that are driving the CRDT based real-time approach in JupyterLab. As a number of other groups are working on real time collaboration, I think it is important to clarify the UX design questions and usage cases. Here are some of them:
Commands to try out the current notebook RTC (WIP):
feature-tables3-extras.rtcOnce you have the git branches checked out in a common root (phoshpor and jupyterlab directory as siblings), you can set up the build like normal:
cd phosphor
npm install
npm run build
cd ../jupyterlab
pip install -e .
jlpm install
jlpm run build
jupyter lab --dev-mode --dev --no-browse
Thanks @vidartf
On the npm run build stage on JupyterLab I get an error:
Error: Cannot find module 'sort-package-json'
Thoughts?
@ellisonbg It is supposed to be jlpm install and jlpm run build, not npm for lab. That might be it?
Yep, brain got rewired typing the phosphor commands...
OK, got it built, but now see this when I go to open or create a notebook:

@vidartf got a successful build and jupyter lab launch but creating a new notebook gives
[I 17:25:11.539 LabApp] Creating new notebook in /
[I 17:25:12.003 LabApp] Initializing datastore connection /lab/api/datastore/Untitledipynb
c:\dev\jupyter\rtc-jupyterlab\jupyterlab\jupyterlab\datastore\handler.py:96: RuntimeWarning: coroutine 'WebSocketHandler.get' was never awaited
super(WSBaseHandler, self).get(*args, **kwargs)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
[I 17:25:13.064 LabApp] Initializing datastore connection /lab/api/datastore/Untitledipynb
[I 17:25:15.069 LabApp] Initializing datastore connection /lab/api/datastore/Untitledipynb
[I 17:25:19.077 LabApp] Initializing datastore connection /lab/api/datastore/Untitledipynb
...
but just following the error and changing the line to include yield, yield super(WSBaseHandler, self).get(*args, **kwargs) fixed it and things are going smooth! Not sure what I am looking for but I see all the transaction logs so I'm super excited.
I am getting the same error when trying to create new notebook:
```
Launcher Error
collections_1.BPlusTree is not a constructor
Hmm, I did a complete git clean -xdf and am still getting the same error.
In case it helps, here is the version of everything I can think of that might be involved in the build:
tool versions
(env) C:\dev\Jupyter\rtc-jupyterlab>python --version
Python 3.7.2
(env) C:\dev\Jupyter\rtc-jupyterlab>yarn --version
1.6.0
(env) C:\dev\Jupyter\rtc-jupyterlab>npm --version
5.4.2
(env) C:\dev\Jupyter\rtc-jupyterlab>node --version
v8.15.0
(env) C:\dev\Jupyter\rtc-jupyterlab>jlpm --version
1.9.4
(env) C:\dev\Jupyter\rtc-jupyterlab>jupyter --version
4.4.0
(env) C:\dev\Jupyter\rtc-jupyterlab>jupyter lab --version
1.0.0a1
(env) C:\dev\Jupyter\rtc-jupyterlab>pip --version
pip 19.0.3 from c:\dev\jupyter\rtc-jupyterlab\env\lib\site-packages\pip (python 3.7)
pip freeze
attrs==19.1.0
backcall==0.1.0
bleach==3.1.0
colorama==0.4.1
decorator==4.3.2
defusedxml==0.5.0
entrypoints==0.3
ipykernel==5.1.0
ipython==7.3.0
ipython-genutils==0.2.0
ipywidgets==7.4.2
jedi==0.13.3
Jinja2==2.10
jsonschema==3.0.1
jupyter==1.0.0
jupyter-client==5.2.4
jupyter-console==6.0.0
jupyter-core==4.4.0
-e git+https://github.com/vidartf/jupyterlab.git@6722a28fb07e18ab82bf5cd5e94e0b611fd0820e#egg=jupyterlab
jupyterlab-server==0.2.0
MarkupSafe==1.1.1
mistune==0.8.4
nbconvert==5.4.1
nbformat==4.4.0
notebook==5.7.6
pandocfilters==1.4.2
parso==0.3.4
pickleshare==0.7.5
prometheus-client==0.6.0
prompt-toolkit==2.0.9
Pygments==2.3.1
pyrsistent==0.14.11
python-dateutil==2.8.0
pywinpty==0.5.5
pyzmq==18.0.1
qtconsole==4.4.3
Send2Trash==1.5.0
six==1.12.0
terminado==0.8.1
testpath==0.4.2
tornado==6.0.1
traitlets==4.3.2
wcwidth==0.1.7
webencodings==0.5.1
widgetsnbextension==3.4.2
phosphor generated
package-lock.json
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"JSONStream": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
"integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
"dev": true,
"requires": {
"jsonparse": "1.3.1",
"through": "2.3.8"
}
},
"add-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz",
"integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=",
"dev": true
},
"ansi-escapes": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"dev": true
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.3"
}
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
},
"are-we-there-yet": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"dev": true,
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.3.6"
}
},
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
"dev": true
},
"array-ify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
"integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=",
"dev": true
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
"dev": true,
"requires": {
"array-uniq": "1.0.3"
}
},
"array-uniq": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
"dev": true
},
"arrify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
"dev": true
},
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"byline": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
"integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=",
"dev": true
},
"camelcase": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
},
"camelcase-keys": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz",
"integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=",
"dev": true,
"requires": {
"camelcase": "4.1.0",
"map-obj": "2.0.0",
"quick-lru": "1.1.0"
}
},
"capture-stack-trace": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
"integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
}
},
"chardet": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
"ci-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"dev": true,
"requires": {
"restore-cursor": "2.0.0"
}
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
"dev": true
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"dev": true,
"requires": {
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wrap-ansi": "2.1.0"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
}
}
},
"clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
"dev": true
},
"cmd-shim": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz",
"integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"mkdirp": "0.5.1"
}
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"columnify": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz",
"integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
"dev": true,
"requires": {
"strip-ansi": "3.0.1",
"wcwidth": "1.0.1"
}
},
"command-join": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/command-join/-/command-join-2.0.0.tgz",
"integrity": "sha1-Uui5hPSHLZUv8b3IuYOX0nxxRM8=",
"dev": true
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
"dev": true,
"optional": true
},
"compare-func": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz",
"integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=",
"dev": true,
"requires": {
"array-ify": "1.0.0",
"dot-prop": "3.0.0"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"dev": true,
"requires": {
"buffer-from": "1.1.1",
"inherits": "2.0.3",
"readable-stream": "2.3.6",
"typedarray": "0.0.6"
}
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
},
"conventional-changelog": {
"version": "1.1.24",
"resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-1.1.24.tgz",
"integrity": "sha512-2WcSUst4Y3Z4hHvoMTWXMJr/DmgVdLiMOVY1Kak2LfFz+GIz2KDp5naqbFesYbfXPmaZ5p491dO0FWZIJoJw1Q==",
"dev": true,
"requires": {
"conventional-changelog-angular": "1.6.6",
"conventional-changelog-atom": "0.2.8",
"conventional-changelog-codemirror": "0.3.8",
"conventional-changelog-core": "2.0.11",
"conventional-changelog-ember": "0.3.12",
"conventional-changelog-eslint": "1.0.9",
"conventional-changelog-express": "0.3.6",
"conventional-changelog-jquery": "0.1.0",
"conventional-changelog-jscs": "0.1.0",
"conventional-changelog-jshint": "0.3.8",
"conventional-changelog-preset-loader": "1.1.8"
}
},
"conventional-changelog-angular": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz",
"integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==",
"dev": true,
"requires": {
"compare-func": "1.3.2",
"q": "1.5.1"
}
},
"conventional-changelog-atom": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-0.2.8.tgz",
"integrity": "sha512-8pPZqhMbrnltNBizjoDCb/Sz85KyUXNDQxuAEYAU5V/eHn0okMBVjqc8aHWYpHrytyZWvMGbayOlDv7i8kEf6g==",
"dev": true,
"requires": {
"q": "1.5.1"
}
},
"conventional-changelog-cli": {
"version": "1.3.22",
"resolved": "https://registry.npmjs.org/conventional-changelog-cli/-/conventional-changelog-cli-1.3.22.tgz",
"integrity": "sha512-pnjdIJbxjkZ5VdAX/H1wndr1G10CY8MuZgnXuJhIHglOXfIrXygb7KZC836GW9uo1u8PjEIvIw/bKX0lOmOzZg==",
"dev": true,
"requires": {
"add-stream": "1.0.0",
"conventional-changelog": "1.1.24",
"lodash": "4.17.11",
"meow": "4.0.1",
"tempfile": "1.1.1"
}
},
"conventional-changelog-codemirror": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.3.8.tgz",
"integrity": "sha512-3HFZKtBXTaUCHvz7ai6nk2+psRIkldDoNzCsom0egDtVmPsvvHZkzjynhdQyULfacRSsBTaiQ0ol6nBOL4dDiQ==",
"dev": true,
"requires": {
"q": "1.5.1"
}
},
"conventional-changelog-core": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz",
"integrity": "sha512-HvTE6RlqeEZ/NFPtQeFLsIDOLrGP3bXYr7lFLMhCVsbduF1MXIe8OODkwMFyo1i9ku9NWBwVnVn0jDmIFXjDRg==",
"dev": true,
"requires": {
"conventional-changelog-writer": "3.0.9",
"conventional-commits-parser": "2.1.7",
"dateformat": "3.0.3",
"get-pkg-repo": "1.4.0",
"git-raw-commits": "1.3.6",
"git-remote-origin-url": "2.0.0",
"git-semver-tags": "1.3.6",
"lodash": "4.17.11",
"normalize-package-data": "2.5.0",
"q": "1.5.1",
"read-pkg": "1.1.0",
"read-pkg-up": "1.0.1",
"through2": "2.0.5"
},
"dependencies": {
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"parse-json": "2.2.0",
"pify": "2.3.0",
"pinkie-promise": "2.0.1",
"strip-bom": "2.0.0"
}
},
"parse-json": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
"dev": true,
"requires": {
"error-ex": "1.3.2"
}
},
"path-type": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"pify": "2.3.0",
"pinkie-promise": "2.0.1"
}
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true,
"requires": {
"load-json-file": "1.1.0",
"normalize-package-data": "2.5.0",
"path-type": "1.1.0"
}
},
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true,
"requires": {
"is-utf8": "0.2.1"
}
}
}
},
"conventional-changelog-ember": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-0.3.12.tgz",
"integrity": "sha512-mmJzA7uzbrOqeF89dMMi6z17O07ORTXlTMArnLG9ZTX4oLaKNolUlxFUFlFm9JUoVWajVpaHQWjxH1EOQ+ARoQ==",
"dev": true,
"requires": {
"q": "1.5.1"
}
},
"conventional-changelog-eslint": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-1.0.9.tgz",
"integrity": "sha512-h87nfVh2fdk9fJIvz26wCBsbDC/KxqCc5wSlNMZbXcARtbgNbNDIF7Y7ctokFdnxkzVdaHsbINkh548T9eBA7Q==",
"dev": true,
"requires": {
"q": "1.5.1"
}
},
"conventional-changelog-express": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-0.3.6.tgz",
"integrity": "sha512-3iWVtBJZ9RnRnZveNDzOD8QRn6g6vUif0qVTWWyi5nUIAbuN1FfPVyKdAlJJfp5Im+dE8Kiy/d2SpaX/0X678Q==",
"dev": true,
"requires": {
"q": "1.5.1"
}
},
"conventional-changelog-jquery": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-0.1.0.tgz",
"integrity": "sha1-Agg5cWLjhGmG5xJztsecW1+A9RA=",
"dev": true,
"requires": {
"q": "1.5.1"
}
},
"conventional-changelog-jscs": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/conventional-changelog-jscs/-/conventional-changelog-jscs-0.1.0.tgz",
"integrity": "sha1-BHnrRDzH1yxYvwvPDvHURKkvDlw=",
"dev": true,
"requires": {
"q": "1.5.1"
}
},
"conventional-changelog-jshint": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-0.3.8.tgz",
"integrity": "sha512-hn9QU4ZI/5V50wKPJNPGT4gEWgiBFpV6adieILW4MaUFynuDYOvQ71EMSj3EznJyKi/KzuXpc9dGmX8njZMjig==",
"dev": true,
"requires": {
"compare-func": "1.3.2",
"q": "1.5.1"
}
},
"conventional-changelog-preset-loader": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz",
"integrity": "sha512-MkksM4G4YdrMlT2MbTsV2F6LXu/hZR0Tc/yenRrDIKRwBl/SP7ER4ZDlglqJsCzLJi4UonBc52Bkm5hzrOVCcw==",
"dev": true
},
"conventional-changelog-writer": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz",
"integrity": "sha512-n9KbsxlJxRQsUnK6wIBRnARacvNnN4C/nxnxCkH+B/R1JS2Fa+DiP1dU4I59mEDEjgnFaN2+9wr1P1s7GYB5/Q==",
"dev": true,
"requires": {
"compare-func": "1.3.2",
"conventional-commits-filter": "1.1.6",
"dateformat": "3.0.3",
"handlebars": "4.1.0",
"json-stringify-safe": "5.0.1",
"lodash": "4.17.11",
"meow": "4.0.1",
"semver": "5.6.0",
"split": "1.0.1",
"through2": "2.0.5"
}
},
"conventional-commits-filter": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz",
"integrity": "sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q==",
"dev": true,
"requires": {
"is-subset": "0.1.1",
"modify-values": "1.0.1"
}
},
"conventional-commits-parser": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz",
"integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==",
"dev": true,
"requires": {
"JSONStream": "1.3.5",
"is-text-path": "1.0.1",
"lodash": "4.17.11",
"meow": "4.0.1",
"split2": "2.2.0",
"through2": "2.0.5",
"trim-off-newlines": "1.0.1"
}
},
"conventional-recommended-bump": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-1.2.1.tgz",
"integrity": "sha512-oJjG6DkRgtnr/t/VrPdzmf4XZv8c4xKVJrVT4zrSHd92KEL+EYxSbYoKq8lQ7U5yLMw7130wrcQTLRjM/T+d4w==",
"dev": true,
"requires": {
"concat-stream": "1.6.2",
"conventional-commits-filter": "1.1.6",
"conventional-commits-parser": "2.1.7",
"git-raw-commits": "1.3.6",
"git-semver-tags": "1.3.6",
"meow": "3.7.0",
"object-assign": "4.1.1"
},
"dependencies": {
"camelcase": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
"integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
"dev": true
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true,
"requires": {
"camelcase": "2.1.1",
"map-obj": "1.0.1"
}
},
"indent-string": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
"dev": true,
"requires": {
"repeating": "2.0.1"
}
},
"map-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true
},
"meow": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true,
"requires": {
"camelcase-keys": "2.1.0",
"decamelize": "1.2.0",
"loud-rejection": "1.6.0",
"map-obj": "1.0.1",
"minimist": "1.2.0",
"normalize-package-data": "2.5.0",
"object-assign": "4.1.1",
"read-pkg-up": "1.0.1",
"redent": "1.0.0",
"trim-newlines": "1.0.0"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
"dev": true,
"requires": {
"indent-string": "2.1.0",
"strip-indent": "1.0.1"
}
},
"strip-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
"dev": true,
"requires": {
"get-stdin": "4.0.1"
}
},
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
"dev": true
}
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"create-error-class": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
"integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
"dev": true,
"requires": {
"capture-stack-trace": "1.0.1"
}
},
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"dev": true,
"requires": {
"lru-cache": "4.1.5",
"shebang-command": "1.2.0",
"which": "1.3.1"
}
},
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
"integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
"dev": true,
"requires": {
"array-find-index": "1.0.2"
}
},
"dargs": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz",
"integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"dateformat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
"integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==",
"dev": true
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"decamelize-keys": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz",
"integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=",
"dev": true,
"requires": {
"decamelize": "1.2.0",
"map-obj": "1.0.1"
},
"dependencies": {
"map-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true
}
}
},
"dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
"dev": true
},
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true
},
"defaults": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"dev": true,
"requires": {
"clone": "1.0.4"
}
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true
},
"detect-indent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
"integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=",
"dev": true
},
"dot-prop": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz",
"integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
"dev": true,
"requires": {
"is-obj": "1.0.1"
}
},
"duplexer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
"dev": true
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
"dev": true
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "0.2.1"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"execa": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
"integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=",
"dev": true,
"requires": {
"cross-spawn": "5.1.0",
"get-stream": "3.0.0",
"is-stream": "1.1.0",
"npm-run-path": "2.0.2",
"p-finally": "1.0.0",
"signal-exit": "3.0.2",
"strip-eof": "1.0.0"
}
},
"external-editor": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
"dev": true,
"requires": {
"chardet": "0.4.2",
"iconv-lite": "0.4.24",
"tmp": "0.0.33"
}
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"dev": true,
"requires": {
"escape-string-regexp": "1.0.5"
}
},
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
"locate-path": "2.0.0"
}
},
"fs-extra": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
"integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"jsonfile": "4.0.0",
"universalify": "0.1.2"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"requires": {
"aproba": "1.2.0",
"console-control-strings": "1.1.0",
"has-unicode": "2.0.1",
"object-assign": "4.1.1",
"signal-exit": "3.0.2",
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wide-align": "1.1.3"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
}
}
},
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"dev": true
},
"get-pkg-repo": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz",
"integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=",
"dev": true,
"requires": {
"hosted-git-info": "2.7.1",
"meow": "3.7.0",
"normalize-package-data": "2.5.0",
"parse-github-repo-url": "1.4.1",
"through2": "2.0.5"
},
"dependencies": {
"camelcase": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
"integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
"dev": true
},
"camelcase-keys": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"dev": true,
"requires": {
"camelcase": "2.1.1",
"map-obj": "1.0.1"
}
},
"indent-string": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
"integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
"dev": true,
"requires": {
"repeating": "2.0.1"
}
},
"map-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
"dev": true
},
"meow": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"dev": true,
"requires": {
"camelcase-keys": "2.1.0",
"decamelize": "1.2.0",
"loud-rejection": "1.6.0",
"map-obj": "1.0.1",
"minimist": "1.2.0",
"normalize-package-data": "2.5.0",
"object-assign": "4.1.1",
"read-pkg-up": "1.0.1",
"redent": "1.0.0",
"trim-newlines": "1.0.0"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
"integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
"dev": true,
"requires": {
"indent-string": "2.1.0",
"strip-indent": "1.0.1"
}
},
"strip-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
"integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
"dev": true,
"requires": {
"get-stdin": "4.0.1"
}
},
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
"dev": true
}
}
},
"get-port": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
"integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=",
"dev": true
},
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
},
"git-raw-commits": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz",
"integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==",
"dev": true,
"requires": {
"dargs": "4.1.0",
"lodash.template": "4.4.0",
"meow": "4.0.1",
"split2": "2.2.0",
"through2": "2.0.5"
}
},
"git-remote-origin-url": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz",
"integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=",
"dev": true,
"requires": {
"gitconfiglocal": "1.0.0",
"pify": "2.3.0"
},
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
}
},
"git-semver-tags": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.3.6.tgz",
"integrity": "sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig==",
"dev": true,
"requires": {
"meow": "4.0.1",
"semver": "5.6.0"
}
},
"gitconfiglocal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz",
"integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=",
"dev": true,
"requires": {
"ini": "1.3.5"
}
},
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
"dev": true,
"requires": {
"is-glob": "3.1.0",
"path-dirname": "1.0.2"
}
},
"globby": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true,
"requires": {
"array-union": "1.0.2",
"glob": "7.1.3",
"object-assign": "4.1.1",
"pify": "2.3.0",
"pinkie-promise": "2.0.1"
},
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
}
},
"got": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
"integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
"dev": true,
"requires": {
"create-error-class": "3.0.2",
"duplexer3": "0.1.4",
"get-stream": "3.0.0",
"is-redirect": "1.0.0",
"is-retry-allowed": "1.1.0",
"is-stream": "1.1.0",
"lowercase-keys": "1.0.1",
"safe-buffer": "5.1.2",
"timed-out": "4.0.1",
"unzip-response": "2.0.1",
"url-parse-lax": "1.0.0"
}
},
"graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
"dev": true
},
"handlebars": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz",
"integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==",
"dev": true,
"requires": {
"async": "2.6.2",
"optimist": "0.6.1",
"source-map": "0.6.1",
"uglify-js": "3.4.9"
},
"dependencies": {
"async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
"integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
"dev": true,
"requires": {
"lodash": "4.17.11"
}
}
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true
},
"hosted-git-info": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
"dev": true
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": {
"safer-buffer": "2.1.2"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"indent-string": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
"integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"inquirer": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
"integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==",
"dev": true,
"requires": {
"ansi-escapes": "3.2.0",
"chalk": "2.4.2",
"cli-cursor": "2.1.0",
"cli-width": "2.2.0",
"external-editor": "2.2.0",
"figures": "2.0.0",
"lodash": "4.17.11",
"mute-stream": "0.0.7",
"run-async": "2.3.0",
"rx-lite": "4.0.8",
"rx-lite-aggregates": "4.0.8",
"string-width": "2.1.1",
"strip-ansi": "4.0.0",
"through": "2.3.8"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "3.0.0"
}
}
}
},
"invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
"dev": true
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-ci": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
"integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
"dev": true,
"requires": {
"ci-info": "1.6.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-finite": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
"integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"dev": true,
"requires": {
"is-extglob": "2.1.1"
}
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true
},
"is-plain-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
"dev": true
},
"is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
"dev": true
},
"is-redirect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
"dev": true
},
"is-retry-allowed": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
"integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=",
"dev": true
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
},
"is-subset": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
"integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=",
"dev": true
},
"is-text-path": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz",
"integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=",
"dev": true,
"requires": {
"text-extensions": "1.9.0"
}
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15"
}
},
"jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"dev": true
},
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
"integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
"dev": true,
"requires": {
"invert-kv": "1.0.0"
}
},
"lerna": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/lerna/-/lerna-2.11.0.tgz",
"integrity": "sha512-kgM6zwe2P2tR30MYvgiLLW+9buFCm6E7o8HnRlhTgm70WVBvXVhydqv+q/MF2HrVZkCawfVtCfetyQmtd4oHhQ==",
"dev": true,
"requires": {
"async": "1.5.2",
"chalk": "2.4.2",
"cmd-shim": "2.0.2",
"columnify": "1.5.4",
"command-join": "2.0.0",
"conventional-changelog-cli": "1.3.22",
"conventional-recommended-bump": "1.2.1",
"dedent": "0.7.0",
"execa": "0.8.0",
"find-up": "2.1.0",
"fs-extra": "4.0.3",
"get-port": "3.2.0",
"glob": "7.1.3",
"glob-parent": "3.1.0",
"globby": "6.1.0",
"graceful-fs": "4.1.15",
"hosted-git-info": "2.7.1",
"inquirer": "3.3.0",
"is-ci": "1.2.1",
"load-json-file": "4.0.0",
"lodash": "4.17.11",
"minimatch": "3.0.4",
"npmlog": "4.1.2",
"p-finally": "1.0.0",
"package-json": "4.0.1",
"path-exists": "3.0.0",
"read-cmd-shim": "1.0.1",
"read-pkg": "3.0.0",
"rimraf": "2.6.3",
"safe-buffer": "5.1.2",
"semver": "5.6.0",
"signal-exit": "3.0.2",
"slash": "1.0.0",
"strong-log-transformer": "1.0.6",
"temp-write": "3.4.0",
"write-file-atomic": "2.4.2",
"write-json-file": "2.3.0",
"write-pkg": "3.2.0",
"yargs": "8.0.2"
}
},
"load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"parse-json": "4.0.0",
"pify": "3.0.0",
"strip-bom": "3.0.0"
}
},
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
"dev": true,
"requires": {
"p-locate": "2.0.0",
"path-exists": "3.0.0"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
},
"lodash._reinterpolate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
"dev": true
},
"lodash.template": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz",
"integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=",
"dev": true,
"requires": {
"lodash._reinterpolate": "3.0.0",
"lodash.templatesettings": "4.1.0"
}
},
"lodash.templatesettings": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz",
"integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=",
"dev": true,
"requires": {
"lodash._reinterpolate": "3.0.0"
}
},
"loud-rejection": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
"integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
"dev": true,
"requires": {
"currently-unhandled": "0.4.1",
"signal-exit": "3.0.2"
}
},
"lowercase-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"dev": true,
"requires": {
"pseudomap": "1.0.2",
"yallist": "2.1.2"
}
},
"make-dir": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
"integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
"dev": true,
"requires": {
"pify": "3.0.0"
}
},
"map-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz",
"integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=",
"dev": true
},
"mem": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
"dev": true,
"requires": {
"mimic-fn": "1.2.0"
}
},
"meow": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz",
"integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==",
"dev": true,
"requires": {
"camelcase-keys": "4.2.0",
"decamelize-keys": "1.1.0",
"loud-rejection": "1.6.0",
"minimist": "1.2.0",
"minimist-options": "3.0.2",
"normalize-package-data": "2.5.0",
"read-pkg-up": "3.0.0",
"redent": "2.0.0",
"trim-newlines": "2.0.0"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"read-pkg-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
"integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
"dev": true,
"requires": {
"find-up": "2.1.0",
"read-pkg": "3.0.0"
}
}
}
},
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.11"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"minimist-options": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz",
"integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==",
"dev": true,
"requires": {
"arrify": "1.0.1",
"is-plain-obj": "1.1.0"
}
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"modify-values": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz",
"integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==",
"dev": true
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
"dev": true
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"requires": {
"hosted-git-info": "2.7.1",
"resolve": "1.10.0",
"semver": "5.6.0",
"validate-npm-package-license": "3.0.4"
}
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
"dev": true,
"requires": {
"path-key": "2.0.1"
}
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"requires": {
"are-we-there-yet": "1.1.5",
"console-control-strings": "1.1.0",
"gauge": "2.7.4",
"set-blocking": "2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1.0.2"
}
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"dev": true,
"requires": {
"mimic-fn": "1.2.0"
}
},
"optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
"dev": true,
"requires": {
"minimist": "0.0.8",
"wordwrap": "0.0.3"
}
},
"os-locale": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
"integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
"dev": true,
"requires": {
"execa": "0.7.0",
"lcid": "1.0.0",
"mem": "1.1.0"
},
"dependencies": {
"execa": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"dev": true,
"requires": {
"cross-spawn": "5.1.0",
"get-stream": "3.0.0",
"is-stream": "1.1.0",
"npm-run-path": "2.0.2",
"p-finally": "1.0.0",
"signal-exit": "3.0.2",
"strip-eof": "1.0.0"
}
}
}
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
},
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
"dev": true,
"requires": {
"p-try": "1.0.0"
}
},
"p-locate": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
"dev": true,
"requires": {
"p-limit": "1.3.0"
}
},
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
"dev": true
},
"package-json": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
"integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
"dev": true,
"requires": {
"got": "6.7.1",
"registry-auth-token": "3.3.2",
"registry-url": "3.1.0",
"semver": "5.6.0"
}
},
"parse-github-repo-url": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz",
"integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=",
"dev": true
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"dev": true,
"requires": {
"error-ex": "1.3.2",
"json-parse-better-errors": "1.0.2"
}
},
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
"dev": true
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"dev": true,
"requires": {
"pify": "3.0.0"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
"dev": true
},
"pinkie-promise": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
"dev": true,
"requires": {
"pinkie": "2.0.4"
}
},
"prepend-http": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"dev": true
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
"dev": true
},
"quick-lru": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz",
"integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=",
"dev": true
},
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true,
"requires": {
"deep-extend": "0.6.0",
"ini": "1.3.5",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
}
}
},
"read-cmd-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz",
"integrity": "sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15"
}
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
"integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
"dev": true,
"requires": {
"load-json-file": "4.0.0",
"normalize-package-data": "2.5.0",
"path-type": "3.0.0"
}
},
"read-pkg-up": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
"dev": true,
"requires": {
"find-up": "1.1.2",
"read-pkg": "1.1.0"
},
"dependencies": {
"find-up": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
"dev": true,
"requires": {
"path-exists": "2.1.0",
"pinkie-promise": "2.0.1"
}
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"parse-json": "2.2.0",
"pify": "2.3.0",
"pinkie-promise": "2.0.1",
"strip-bom": "2.0.0"
}
},
"parse-json": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
"dev": true,
"requires": {
"error-ex": "1.3.2"
}
},
"path-exists": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"dev": true,
"requires": {
"pinkie-promise": "2.0.1"
}
},
"path-type": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"pify": "2.3.0",
"pinkie-promise": "2.0.1"
}
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true,
"requires": {
"load-json-file": "1.1.0",
"normalize-package-data": "2.5.0",
"path-type": "1.1.0"
}
},
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true,
"requires": {
"is-utf8": "0.2.1"
}
}
}
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "2.0.0",
"safe-buffer": "5.1.2",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
},
"redent": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz",
"integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=",
"dev": true,
"requires": {
"indent-string": "3.2.0",
"strip-indent": "2.0.0"
}
},
"registry-auth-token": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
"integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
"dev": true,
"requires": {
"rc": "1.2.8",
"safe-buffer": "5.1.2"
}
},
"registry-url": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
"integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
"dev": true,
"requires": {
"rc": "1.2.8"
}
},
"repeating": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
"integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
"dev": true,
"requires": {
"is-finite": "1.0.2"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"dev": true
},
"resolve": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
"integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
"dev": true,
"requires": {
"path-parse": "1.0.6"
}
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"dev": true,
"requires": {
"onetime": "2.0.1",
"signal-exit": "3.0.2"
}
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "7.1.3"
}
},
"run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
"dev": true,
"requires": {
"is-promise": "2.1.0"
}
},
"rx-lite": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
"integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=",
"dev": true
},
"rx-lite-aggregates": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz",
"integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=",
"dev": true,
"requires": {
"rx-lite": "4.0.8"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"dev": true
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"dev": true,
"requires": {
"shebang-regex": "1.0.0"
}
},
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"slash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
"dev": true
},
"sort-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
"integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
"dev": true,
"requires": {
"is-plain-obj": "1.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"spdx-correct": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
"integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
"dev": true,
"requires": {
"spdx-expression-parse": "3.0.0",
"spdx-license-ids": "3.0.3"
}
},
"spdx-exceptions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
"dev": true
},
"spdx-expression-parse": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
"integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
"dev": true,
"requires": {
"spdx-exceptions": "2.2.0",
"spdx-license-ids": "3.0.3"
}
},
"spdx-license-ids": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
"integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==",
"dev": true
},
"split": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
"integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
"dev": true,
"requires": {
"through": "2.3.8"
}
},
"split2": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
"integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"dev": true,
"requires": {
"through2": "2.0.5"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "2.0.0",
"strip-ansi": "4.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "3.0.0"
}
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "5.1.2"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
"strip-indent": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
"integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
"dev": true
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true
},
"strong-log-transformer": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-1.0.6.tgz",
"integrity": "sha1-9/uTdYpppXEUAYEnfuoMLrEwH6M=",
"dev": true,
"requires": {
"byline": "5.0.0",
"duplexer": "0.1.1",
"minimist": "0.1.0",
"moment": "2.24.0",
"through": "2.3.8"
},
"dependencies": {
"minimist": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz",
"integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=",
"dev": true
}
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
},
"temp-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
"integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=",
"dev": true
},
"temp-write": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz",
"integrity": "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"is-stream": "1.1.0",
"make-dir": "1.3.0",
"pify": "3.0.0",
"temp-dir": "1.0.0",
"uuid": "3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
}
}
},
"tempfile": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/tempfile/-/tempfile-1.1.1.tgz",
"integrity": "sha1-W8xOrsxKsscH2LwR2ZzMmiyyh/I=",
"dev": true,
"requires": {
"os-tmpdir": "1.0.2",
"uuid": "2.0.3"
}
},
"text-extensions": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
"integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
"dev": true
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
"dev": true,
"requires": {
"readable-stream": "2.3.6",
"xtend": "4.0.1"
}
},
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
"integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
"dev": true
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"requires": {
"os-tmpdir": "1.0.2"
}
},
"trim-newlines": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz",
"integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=",
"dev": true
},
"trim-off-newlines": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz",
"integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=",
"dev": true
},
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
"uglify-js": {
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
"dev": true,
"optional": true,
"requires": {
"commander": "2.17.1",
"source-map": "0.6.1"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
},
"unzip-response": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
"integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=",
"dev": true
},
"url-parse-lax": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
"integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
"dev": true,
"requires": {
"prepend-http": "1.0.4"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
"uuid": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz",
"integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"requires": {
"spdx-correct": "3.1.0",
"spdx-expression-parse": "3.0.0"
}
},
"wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"dev": true,
"requires": {
"defaults": "1.0.3"
}
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
"isexe": "2.0.0"
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"dev": true,
"requires": {
"string-width": "2.1.1"
}
},
"wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
"dev": true
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
"string-width": "1.0.2",
"strip-ansi": "3.0.1"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
}
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write-file-atomic": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz",
"integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"imurmurhash": "0.1.4",
"signal-exit": "3.0.2"
}
},
"write-json-file": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz",
"integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=",
"dev": true,
"requires": {
"detect-indent": "5.0.0",
"graceful-fs": "4.1.15",
"make-dir": "1.3.0",
"pify": "3.0.0",
"sort-keys": "2.0.0",
"write-file-atomic": "2.4.2"
}
},
"write-pkg": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz",
"integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==",
"dev": true,
"requires": {
"sort-keys": "2.0.0",
"write-json-file": "2.3.0"
}
},
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
"dev": true
},
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"yargs": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz",
"integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=",
"dev": true,
"requires": {
"camelcase": "4.1.0",
"cliui": "3.2.0",
"decamelize": "1.2.0",
"get-caller-file": "1.0.3",
"os-locale": "2.1.0",
"read-pkg-up": "2.0.0",
"require-directory": "2.1.1",
"require-main-filename": "1.0.1",
"set-blocking": "2.0.0",
"string-width": "2.1.1",
"which-module": "2.0.0",
"y18n": "3.2.1",
"yargs-parser": "7.0.0"
},
"dependencies": {
"load-json-file": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
"dev": true,
"requires": {
"graceful-fs": "4.1.15",
"parse-json": "2.2.0",
"pify": "2.3.0",
"strip-bom": "3.0.0"
}
},
"parse-json": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
"dev": true,
"requires": {
"error-ex": "1.3.2"
}
},
"path-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
"integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
"dev": true,
"requires": {
"pify": "2.3.0"
}
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
"integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
"dev": true,
"requires": {
"load-json-file": "2.0.0",
"normalize-package-data": "2.5.0",
"path-type": "2.0.0"
}
},
"read-pkg-up": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
"integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
"dev": true,
"requires": {
"find-up": "2.1.0",
"read-pkg": "2.0.0"
}
}
}
},
"yargs-parser": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz",
"integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=",
"dev": true,
"requires": {
"camelcase": "4.1.0"
}
}
}
}
phosphor datastore generated
package-lock.json
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"typedoc": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.12.0.tgz",
"integrity": "sha512-dsdlaYZ7Je8JC+jQ3j2Iroe4uyD0GhqzADNUVyBRgLuytQDP/g0dPkAw5PdM/4drnmmJjRzSWW97FkKo+ITqQg==",
"requires": {
"@types/fs-extra": "5.0.5",
"@types/handlebars": "4.1.0",
"@types/highlight.js": "9.12.3",
"@types/lodash": "4.14.123",
"@types/marked": "0.4.2",
"@types/minimatch": "3.0.3",
"@types/shelljs": "0.8.3",
"fs-extra": "7.0.1",
"handlebars": "4.1.0",
"highlight.js": "9.15.6",
"lodash": "4.17.11",
"marked": "0.4.0",
"minimatch": "3.0.4",
"progress": "2.0.3",
"shelljs": "0.8.3",
"typedoc-default-themes": "0.5.0",
"typescript": "3.0.3"
},
"dependencies": {
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
},
"@types/fs-extra": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.5.tgz",
"integrity": "sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A==",
"requires": {
"@types/node": "11.11.3"
}
},
"@types/glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
"integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
"requires": {
"@types/events": "3.0.0",
"@types/minimatch": "3.0.3",
"@types/node": "11.11.3"
}
},
"@types/handlebars": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.1.0.tgz",
"integrity": "sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA==",
"requires": {
"handlebars": "4.1.0"
}
},
"@types/highlight.js": {
"version": "9.12.3",
"resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.3.tgz",
"integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ=="
},
"@types/lodash": {
"version": "4.14.123",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz",
"integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q=="
},
"@types/marked": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.4.2.tgz",
"integrity": "sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg=="
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
},
"@types/node": {
"version": "11.11.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz",
"integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg=="
},
"@types/shelljs": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.3.tgz",
"integrity": "sha512-miY41hqc5SkRlsZDod3heDa4OS9xv8G77EMBQuSpqq86HBn66l7F+f8y9YKm+1PIuwC8QEZVwN8YxOOG7Y67fA==",
"requires": {
"@types/glob": "7.1.1",
"@types/node": "11.11.3"
}
},
"async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
"integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
"requires": {
"lodash": "4.17.11"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
"optional": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"requires": {
"graceful-fs": "4.1.15",
"jsonfile": "4.0.0",
"universalify": "0.1.2"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"handlebars": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz",
"integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==",
"requires": {
"async": "2.6.2",
"optimist": "0.6.1",
"source-map": "0.6.1",
"uglify-js": "3.4.9"
}
},
"highlight.js": {
"version": "9.15.6",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.6.tgz",
"integrity": "sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"interpret": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
"integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "4.1.15"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"marked": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.4.0.tgz",
"integrity": "sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
}
},
"minimist": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
"requires": {
"minimist": "0.0.10",
"wordwrap": "0.0.3"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
},
"rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
"requires": {
"resolve": "1.10.0"
}
},
"resolve": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
"integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
"requires": {
"path-parse": "1.0.6"
}
},
"shelljs": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz",
"integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==",
"requires": {
"glob": "7.1.3",
"interpret": "1.2.0",
"rechoir": "0.6.2"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"typedoc-default-themes": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.5.0.tgz",
"integrity": "sha1-bcJDPnjti+qOiHo6zeLzF4W9Yic="
},
"uglify-js": {
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
"optional": true,
"requires": {
"commander": "2.17.1",
"source-map": "0.6.1"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
},
"wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
},
"typescript": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.3.tgz",
"integrity": "sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg=="
}
}
}
This was my first time building jupyter lab from source so it might have picked up some newer packages?
The issue @ellisonbg had was solved by using jlpm link:
cd phosphor/packages/collections
jlpm link
cd ../algorithm
jlpm link
cd ../coreutils
jlpm link
cd ../../jupyterlab
jlpm link @phosphor/collections
jlpm link @phosphor/algorithm
jlpm link @phosphor/coreutils
jlpm run build
@vidartf could you please point to any relevant documentation for this feature. I am looking for an architecture/design diagram or such which will be super helpful to understand the implementation details. Also Jupyterlab persists the notebooks on disk, so will this feature introduce a different persistence layer? I see you mention about LFS etc. Any pointers will be very helpful.
I could bring up a jupyterlab instance with the below commands (assuming docker is already installed):
docker run -it -p 8888:8888 continuumio/miniconda /bin/bash
conda create -n jupyterlab -c conda-forge --override-channels nodejs jupyterlab cookiecutter git
conda activate jupyterlab
git clone https://github.com/vidartf/phosphor.git
cd phosphor/
git checkout feature-tables3-extras
npm install --dev typescript@latest
npm install --unsafe-perm
npm run build --unsafe-perm
cd packages/collections
jlpm link
cd ../algorithm
jlpm link
cd ../coreutils
jlpm link
cd /
git clone https://github.com/vidartf/jupyterlab.git
cd jupyterlab/
git checkout rtc
pip install -e .
jlpm install
jlpm link @phosphor/collections
jlpm link @phosphor/algorithm
jlpm link @phosphor/coreutils
jlpm run build
jupyter lab --ip 0.0.0.0 --allow-root --dev-mode --dev --no-browse --NotebookApp.token=''
I too faced the same error which @SpencerPark faced. Editing /jupyterlab/jupyterlab/datastore/handler.py line 96 and prefixing the yield keyword to the super call fixed it.
Jupyterlab is up and running. When i launch jupyterlab in two different browser tabs (note that the second tab uses a different workspace), i can see the edits done in one tab being seen in the other tab, real time :) Super excited to have this feature as GA.
@balamuruganvg This is very much still a work in progress, and can be considered more of a proof-of-concept. A more detailed write up about the suggested network protocol for the RTC would be included in a PR. Other than that, this is based on the phosphor datastore implementation of CRDTs (commutative replicated data types). See the phosphor repo for details on that.
Super excited to have this feature as GA.
What does GA mean? 🙃
@balamuruganvg This is very much still a work in progress, and can be considered more of a proof-of-concept. A more detailed write up about the suggested network protocol for the RTC would be included in a PR. Other than that, this is based on the phosphor datastore implementation of CRDTs (commutative replicated data types). See the phosphor repo for details on that.
Thanks @vidartf for the pointers.
Super excited to have this feature as GA.
What does GA mean? 🙃
Generally Available :)
FWIW, I have to run npm rather than jlpm for the link commands.
Fix for the L96 issue:
Here is a pre-built docker container with all of this built:
https://cloud.docker.com/u/ellisonbg/repository/docker/ellisonbg/jupyterlab-rtc
GitHub repo with directions on how to run:
Any updates on the RTC work?
@ian-r-rose @saulshanabrook @gnestor @SylvainCorlay @vidartf
We're slowly starting back up, and I think this will be a decent priority after 1.0 + fire marshal service after the release. Hopefully we will find a good way to split the work of the notebook refactor as well 👍
I think accessibility and rtc work will both be big focuses post 1.0, and probably will need to be coordinated.
As we are very close to an initial publishing of the @phosphor/datastore package, I'm beginning to think about the actual document model refactor that will need to happen as part of the switch to the Datastore. This comment is a big brain-dump of what I think it might look like:
An interactive document within JupyterLab, broadly speaking, is made up of four components, each with different responsibilities. These are document models, document widgets, document contexts, and client sessions. What are the goals for the overall division of responsibility between these items? Are we meeting these goals? How should we refactor things to better keep the intended abstracitons?
What is the purpose of the document model? It holds the document content. View-related data should not be on the document model. A possible exception to this principle: information about collaborators (name, cursor position, etc). Responsibilities of the document model include:
fromJSON, toJSON, fromString, toString).A document widget renders a document model. It may also render some aspects of a client session or a context, such as kernel status. It updates its view state due to changes from the model and client sessions. Users can effect a change in the client session or document models by interacting with the document widget (e.g., entering text, executing a cell, sliding a widget). Responsibilities of the widget include:
Client sessions associate a kernel with a document. Responsibilities of a client session include:
A context represents the bridge between the document model and the server. Document models may exist only in memory, and without computational resources attached, but eventually you will want to execute some code and persist the document somewhere. This is the purpose of the context. Its responsibilities include:
With the above summarized, I want to make some specific proposals for how to refactor it. For the most part, the document models should be as dumb as possible.
It should not need to know anything about its location on disk (or in a database),
nor should it need to know anything about associated kernels.
With that in mind, here are some proposed changes:
contentChanged should be removed in favor of the related datastore signal.stateChanged is confusing. It only is fired when the readOnly or dirty state changes. If it is kept, it should be kept where those attributes are, and documented better.dirty and readOnly seem like they are more associated with the document's state in relation to disk. Perhaps they should be moved to the context.defaultKernelName and defaultKernelLanguage are largely redundant with similar functionality on client sessions, and shouldn't really be the responsibility of document models. Can we get away with removing them?modelDB will be removed in favor of Datastore.initialize is mostly used to set up undo/redo stacks. This will be completely changed by the datastore. Let's try to get rid of it.With these proposals, the document model becomes much simpler, and looks something like this:
namespace DocumentRegistry {
/**
* The interface for a document model.
*/
export interface IModel extends IDisposable {
/**
* The datastore for the model.
*/
readonly datastore: Datastore;
/**
* Serialize the model to a string.
*/
toString(): string;
/**
* Deserialize the model from a string.
*/
fromString(value: string): void;
/**
* Serialize the model to JSON.
*/
toJSON(): JSONValue;
/**
* Deserialize the model from JSON.
*/
fromJSON(value: JSONValue): void;
}
}
Mostly, items have been removed. The major new item is the Datastore, which holds the data in the model in tables with type-safe schemas.
We may want to make the type of the IModel generic, specialized by a collection of schemas that are added to the datastore, something like
type SchemaCollection = Schema[];
export interface IModel<S extends SchemaCollection> {
but I'm not sure exactly what that would look like.
With this interface, document models are mostly specialized by the schemas used in their datastore, rather than by any inheritance structure. We currently have the following inheritance structure:
DocumentModel implements DocumentRegistry.IModel and CodeEditor.IModel.
The notebook extends DocumentModel, thereby gaining weird attributes value, mimeType, etc that don't really make sense.
These inheritances should be broken, and replaced with a collection of schemas that describe the data that needs to be stored
We may not want to pay the CRDT overhead for these documents.
How would we indicate that in the document model interface.
No datastore? An isCollaborative boolean? I don't know, but we should talk about it!
Instead of having a complex inheritance structure, we would like to compose our document models out of well-typed schemas. The following is one such set of schemas:
export interface CodeEditorSchema {
id: 'jp.CodeEditor.v1',
fields: CodeEditorFields
}
export interface NotebookSchema {
id: 'jp.Notebook.v1',
fields: NotebookFields
}
export interface CellSchema {
id: 'jp.Cell.v1',
fields: CellFields
}
export interface OutputSchema {
id: 'jp.Output.v1',
fields: OutputFields
}
export interface CodeEditorFields {
mimeType: StringField,
value: TextField,
selections: MapField<ITextSelection[]>
}
export interface CellFields extends CodeEditorFields {
type: RegisterField<nbformat.CellType>,
trusted: BooleanField,
metadata: MapField<JSONValue>
}
export interface AttachmentCellFields extends CellFields {
attachment: RegisterField<JSONObject>
}
export interface RawCellFields extends AttachmentCellFields {}
export interface MarkdownCellFields extends AttachmentCellFields {}
export interface CodeCellFields extends CellFields {
executionCount: RegisterField<nbformat.ExecutionCount>,
outputs: ListField<string>
}
export interface OutputFields {
type: StringField,
executionCount: RegisterField<nbformat.ExecutionCount>,
trusted: BooleanField,
}
export interface NotebookFields {
nbformat: NumberField,
nbformatMinor: NumberField,
metadata: MapField<JSONObject>
cells: ListField<string>
}
It's possible that the cells could be stored in a single table with some superfluous fields, or in three different kinds of tables, one each for raw, code, and markdown, in which case, the schemas would be somewhat different.
Thanks for laying all this out @ian-r-rose, I look forward to more discussion!
Some other points worth thinking about in relation to the refactor:
Thanks @vidartf, a few thoughts on your points:
Some other points worth thinking about in relation to the refactor:
1. Does the change signals need any extra info / differentiation to avoid signal ping/pong (change guards)? Will all collaborators share the same context end-point (e.g. same file at disk), or will we have one "master" node that has the context to disk? Are all collaborators connected to the same Jupyter kernel? How do we deduplicate changes coming from such shared sources?
I had been thinking that any required change guards (as I expect the codemirror instances will need) would be happening in the view widgets. Datastore change events, at least, get a storeId field, so we can filter those that were already applied by the current user based upon that, yes?
That's a good point about resources coming in from the kernel. Eventually we likely will want a superpeer, as you have suggested. For the moment, it seems the most important thing would be to avoid duplicated outputs (though I'm not sure what that would look like). Maybe only the user who executes the cell should be the one to apply the output coming from it?
2. How do we deal with hierarchical models (e.g. notebooks which has cell models, which has output models)?
My hope was to make it maximally flat, so there aren't really hierarchies, so much as tables that have references to other tables (which is what I've tried to do in the proposed schemas above). So the notebook model just has a list of cell IDs, which are then able to be looked up in a cells table.
1. How does views depend on models in such hierarchies? Which parts are extensible?
¯_(ツ)_/¯
2. How do we ensure correct transaction grouping happens? E.g. if I write a command in an extension that should (a) move a cell and (b) change some metadata in both that cell and another cell (and this only makes sense as an atomic operation): How do I initialize/end the transaction from outside?
My hope was to give extension authors raw access to the datastore and its update/changed methods.
So long as they use the public APIs (and not the private or semi-private ones), then I think it should be equally safe as the way they do things now with the Observables. I did like your withTransaction method, and think we should consider promoting it to the datastore API.
3. Since a transaction can span multiple models, how does that affect which parts are responsible for undo/redo, and how they are managed/displayed? Does views need to know about the datastore? Or can that be an internal detail of the models?
A transaction might span multiple models, but it also might not, depending upon how we set things up. If each document gets its own datastore, then I think they are fairly well isolated from each other, and all transactions happen within the same model. If there is a document-type-level or application-level datastore, then that wouldn't necessarily be the case. This may be a point in favor of the former route.
3. How do we deal with large immutable objects (e.g. image outputs in notebooks)? We don't want them clogging up the synchronization channels when transferring (i.e. no text edits are synced while a 50MB picture is transferred).
Goooood question.
4. In a multi-collaborator setting, how is saving of documents handled? And how is the dirty flag handled? Related to point 1. above, but that focuses on deduplicating inherently identical changes from shared sources, while this focuses on persistence and state tracking related to that.
Also unsure about these. In the IModelDB universe, we explicitly don't worry about other users having also saved to the same location on disk: https://github.com/jupyterlab/jupyterlab/blob/4e913e665ecc462651a7ffc6a05303de62a57ad8/packages/docregistry/src/context.ts#L485-L487 . Also, see my above discussion of where to put the isDirty flag.
My hope was to make it maximally flat, so there aren't really hierarchies, so much as tables that have references to other tables
and
A transaction might span multiple models, but it also might not, depending upon how we set things up.
To clarify: Are you thinking of going away from having "cell model" and "output model" classes? If not, my question here was how to deal with transactions across such "models" (i.e. not document models, but document models spread across multiple objects).
My hope was to make it maximally flat, so there aren't really hierarchies, so much as tables that have references to other tables
and
A transaction might span multiple models, but it also might not, depending upon how we set things up.
To clarify: Are you thinking of going away from having "cell model" and "output model" classes? If not, my question here was how to deal with transactions across such "models" (i.e. not document models, but document models spread across multiple objects).
Yeah, I was thinking of getting rid of CellModel and OutputModel, and just have the cell widget and output widget listen for changes to the appropriate records. We could still have in-memory models (for use in consoles, for instance), which have purely in-memory datastores.
Can we meet at 8am Pacific time Wednesday on the JLab zoom channel to discuss this (one hour before the dev meeting on the same channel this week)? @ian-r-rose says that would be helpful, and he could make that time.
I'm in.
I'd love to join this call as well. @saulshanabrook and I were discussing this work last Friday.
@ian-r-rose - in addition to reading the recent discussion above, do you have any other suggestions for preparing for Wednesday's meeting to come up to speed on the current state of things?
@jasongrout It may be helpful to glance through this minimal working example (which is still a significant amount of code): https://github.com/phosphorjs/phosphor/pull/410
The above-mentioned video conference meeting about real-time collaboration is starting now on
https://calpoly.zoom.us/my/jupyter. We'll post back here with notes too.
Notes from our meeting:
re: https://github.com/jupyterlab/jupyterlab/issues/5382
Current datastructures implemented (called Fields):
ListFieldMapFieldTextField (mutable string)Undo/redo: not implemented currently, but we should be at a place where it could be implemented
Two public APIs:
API is:
Heroku example app from Ian: http://vast-wildwood-86294.herokuapp.com/ - from https://github.com/phosphorjs/phosphor/pull/410
What about all the other types of state that isn't collaborative? Kernel state?
What are we storing on the server:
Things are going to need some notion of identity. Perhaps JLab has some identity provider, where the system can retrieve the user's identity.
Let's initially get collaborative text editing working.
How do we incrementally experiment?
A second document registry, working on a different drive, that talks directly to the patch server instead of the file api?
eventually, perhaps a graphql or some similar interface for people to query the normalized datastore and get back an object graph.
Explore graphql querying of the data store
Meet with William?
Saul's Qs for integration:
So we are building a decentralized relational database.
For question 2, can we reuse any popular models in the community? One option is graphql.
https://github.com/phosphorjs/phosphor/tree/master/packages/datastore/src
https://github.com/phosphorjs/phosphor/pull/410
https://github.com/jupyterlab/jupyterlab/pull/6871
Hey, as requested on Gitter:
I’m curious about the state of reconnecting to a running notebook session in JupyterLab. I’ve found jupyter/notebook#1150 for original jupyter, and wondered if there was a plan to fix this in JupyterLab?
Basically, the behaviour I’m seeing right now is that if I leave a long-running job in Jupyter, disconnect the browser (e.g. close laptop lid overnight), and later reconnect, I lose some or all of the “state” of the notebook e.g. which cell is currently running, and I intermittently see some, but not all of the output printed from a long-running cell. This is problematic when you ran a long job in JupyterLab overnight, and really care about the result in the morning!
Another use case is connecting to the same notebook from multiple browsers on multiple machines simultaneously, would really like to see the outputs (again, of long-running cells) synchronized between the multiple viewers. Could be different people, could just be me on my office computer and my laptop at home.
NB: also seen jupyterlab/jupyterlab#2833, understand it's dependent on the backend implementation changing.
IMHO: this is an essential capability to bring JupyterLab on par with, say, Databricks notebooks, and make it feel more serious/capable for production-grade data science. Full disclosure, we are developing an AI platform which embeds JupyterLab. Let us know if we can assist (with time or money) to speed up development of these use cases!
Thanks for engaging @lukemarsden!
Here are the notes we took at today's meeting:
re: https://github.com/jupyterlab/jupyterlab/issues/5382
Current datastructures implemented (called Fields):
ListFieldMapFieldTextField (mutable string)Undo/redo: not implemented currently, but we should be at a place where it could be implemented
Two public APIs:
API is:
Heroku example app from Ian: http://vast-wildwood-86294.herokuapp.com/ - from https://github.com/phosphorjs/phosphor/pull/410
What about all the other types of state that isn't collaborative? Kernel state?
What are we storing on the server:
Things are going to need some notion of identity. Perhaps JLab has some identity provider, where the system can retrieve the user's identity.
Let's initially get collaborative text editing working.
How do we incrementally experiment?
A second document registry, working on a different drive, that talks directly to the patch server instead of the file api?
eventually, perhaps a graphql or some similar interface for people to query the normalized datastore and get back an object graph.
Explore graphql querying of the data store
Meet with William?
Saul's Qs for integration:
So we are building a decentralized relational database.
For question 2, can we reuse any popular models in the community? One option is graphql.
https://github.com/phosphorjs/phosphor/tree/master/packages/datastore/src
https://github.com/phosphorjs/phosphor/pull/410
https://github.com/jupyterlab/jupyterlab/pull/6871
Attendees
https://github.com/jupyterlab/jupyterlab/compare/datastore...ian-r-rose:datastore-codeeditor
https://github.com/ian-r-rose/jupyterlab/tree/datastore-codeeditor

Each pageload has a unique store id, a number, given by the server in PageConfig. It uses this store id for all datastores created in the application.
We should make datastore creation asynchronous, which may not be strictly necessary right now,
but may be necessary for certain client-server relationships.
There is a datastore per thing that the client wants to collaborate on. The collaboration id is generally pluginid:uniqueid, where the uniqueid is unique in a plugin. For example, pluginid:path could be a collaboration id for datastores per document.
We should separate things into different datastores based on whether we want all the contents to have one undo/redo stack and to download all at once. So each file needs its own datastore, so that if two people are working on separate files, they each only need to keep updated on the changesets for the ones they have open. In the future, we could implement some transaction filtering that could filter a transaction for tables that a client cares about, if we wanted datastores to span multiple documents, etc.
Ian showed us his signal change filter that makes it very easy to listen to changes in a typesafe way on single records or single fields of a record: (link)
Saul shows type safe way of having datastore object with multiple schemas:
export class Manager<T extends {[label: string]: Schema}> {
constructor(public manager: DatastoreManager, private schemas: T) {}
getTable<V extends keyof T>(label: V): Table<T[V]> {
return this.manager.datastore.get(this.schemas[label])
}
}
export class DatastoreCreator {
async createDatastore<T extends {[label: string]: Schema}>(
collaboratorID: string,
schemas: T
): Promise<Manager<T>> {
const manager = new DatastoreManager(collaboratorID, Object.values(schemas), true);
await manager.connected;
return new Manager(manager, schemas);
}
}
async function t(creator: DatastoreCreator) {
const datastore = await creator.createDatastore("datastore-collaboration-id", {
cells: {
id: 'cells',
fields: {
description: Fields.Text(),
show: Fields.Boolean({ value: true })
}
},
metadata: {
id: 'metadata',
fields: {
otherFIeld: Fields.Boolean({ value: true })
}
}
})
const metadata = datastore.getTable("metadata")
metadata.get("df").otherFIeld
}
Plans moving forward:
createDatastore(collaborationId, schemas) functionEach pageload has a unique store id, a number, given by the server in PageConfig. It uses this store id for all datastores created in the application.
This is probably smart 👍 Note that the transaction server code should probably get an update to receive the store_id from the client in order to enable network reconnection.
Interesting observation: When two registers are written to concurrently, the write from the lowest store id wins. Previously this meant that the "originator" of the collaboration would always win. Now it means whoever has been connected to the server the longest wins. 🚜
Working on extension for datastore creator: https://github.com/jupyterlab/jupyterlab/pull/7009
Involves change to jupyterlab_server to add unique store id per request https://github.com/jupyterlab/jupyterlab_server/pull/74
Can we remove the DatastoreManager eventually?
DatastoreManager was about sync connection to a document. So it was an in memory datastore and then switch to a remote one. Let's say you are connecting. You don't know yet whether it's a fresh one or it's an existing one.
Ian is trying to rewrite the notebook model using these tables.
If we have a table of cells, which have an execution count, a value, a trusted state... We need to manage the lifecycle for this table. Currently we have cell widgets that reference cell models. What if a cell is created by a remote user?
The hope is that the datastore becomes the model. So in many cases we can get rid of this model. That would clean up some of the lifecycle issues around these models.
Jason: What state is stored in the model that isn't store in the datastore?
Ian: Yeah there is some. We are removing this now.
Who handles overwriting characters, like from that message from the kernel?
Either the person who owns the message in some way, or have a global kernel client on the server to handle these events.
Ian's proposal: Remove all model obejcts and just interface with the datastore directly, with helper functions. One exception is the rendermime interface, where we still need to create the model.
Widgets will be passed in a location to a record in a datastore, and they will fetch it. They could either do a react style listen to changes, or mutate the dom manually, for better performance.
(we give thumbs up to this idea)
Ian: we could get this going in a week or so, for the notebook. The text editor model is simpler and works currently.
Relationship with workspaces?
Well we could stop sending people to a new workspace, instead we could let people on the same workspace share their view. Should this be the mental model? We need to think a bit about how much users want to synchronize.
I came across this project again today: https://github.com/automerge/automerge. The author of it, @ept, is actively doing research into CRDTs and distributed systems and has written a book on the subject. It also seems to have some community uptake already and has documentation.
There was some original conversation about using automerge on this issue: https://github.com/jupyterlab/jupyterlab/issues/3824
Brian said that we need these things that automerge was not designed for:
- Has to scale to very large numbers of users and very large documents (Jupyter is regularly deployed to 1k-10k's of users). This is a difficult constraint that strongly limits what parts of the design decision tree you can explore.
- Strongly typed interfaces, data models, table schemas.
- Mutable data structures (we are fully aware of of the benefits of immutability, but its adds additional constraints that make the other goals more difficult to achieve.)
I know we have already gone pretty far done this road of building our own implementation, but it's possible that we could move some of that work we have already done into the existing automerge project. I don't have any knowledge about the technical differences, but community wise it would be preferable to reuse existing work that already has authors working on it. Maybe if @ept is interested, we could set up a call to chat?
Ian is working on #7045. He added some helper functions to deal with getting data from tables. Most of the modeldb abstractions are gone.
Q: What about state that isn't shared between users?
A: I have drived to move those onto the widgets.
Brian's "hunch" is to store per user state in the datastore as well.
Should we store every UI related thing in the datastore?
Like if we have a comment we haven't published, should that be synced?
Yes, because if you want to reload you still want it there.
Is there a reason to do more than just using one store and putting everyones information in there?
Let's say not for now.
Back to ian's work, what hasn't been done is the collapsed state and the top level questions of where the datastore object comes from.
Q: At what point is it useful to have others help you?
A: The PR is about ready to be merged into datastore. Now is a good time for people to take a look at it.
Just finished up https://github.com/jupyterlab/jupyterlab/pull/7009.
We had a discussion on whether files should be identified by their path, some metadata on the system file object, or with another table that maps file IDs to paths.
Hi @saulshanabrook, in reply to your comment, thanks for tagging me!
I came across this project again today: https://github.com/automerge/automerge. The author of it, @ept, is actively doing research into CRDTs and distributed systems and has written a book on the subject. It also seems to have some community uptake already and has documentation.
Yes, Automerge has been under active development for a few years now, and it's shaping up to being a really solid CRDT implementation. A lot of research and engineering is going into making it high-performance (naively implemented CRDTs end up being quite inefficient), so perhaps you can benefit from that too.
Brian said that we need these things that automerge was not designed for:
- Has to scale to very large numbers of users and very large documents (Jupyter is regularly deployed to 1k-10k's of users). This is a difficult constraint that strongly limits what parts of the design decision tree you can explore.
Generally with these sorts of things it doesn't matter how many users have access to a document; what matters is how many users actually contribute edits to a document. Do you really have 1k-10k users all making changes to the same document? Even Google Docs limits the number of users who can edit and comment on a doc to 100.
In Automerge, the cost of read-only users is zero, and there is a small cost proportional to the number of users who have edited the same document, which might be noticeable if thousands of distinct users edit the same document. But there's no hard limit.
- Strongly typed interfaces, data models, table schemas.
In this regard, Automerge currently takes a different approach — it has a dynamically typed JSON interface (though if you're using it from TypeScript, you can define types for the document structure, which are checked at compile-time).
I am actually not aware of any CRDT implementation that has good support for schemas. The problem is that different collaborators may be concurrently using different versions of the app, which may be built with different schema definitions. Resolving those kinds of schema version differences is an open research problem. If you've figured out how to do this, I'd love to hear more.
- Mutable data structures (we are fully aware of of the benefits of immutability, but its adds additional constraints that make the other goals more difficult to achieve.)
I'd be interested to hear what these constraints are. Automerge is based on immutable data structures, which has a small performance cost (more object copying needs to happen compared to a mutable implementation), but I think is overall a win for application development.
Maybe if @ept is interested, we could set up a call to chat?
Certainly, I'd love to compare notes! Feel free to email me (firstname at lastname dot com) to set up a call.
I found this old issue that might be useful to think about now: https://github.com/jupyterlab/jupyterlab/issues/1922
Current status from Ian:
Any updates on this?
I think many people are looking forward to https://github.com/jupyterlab/jupyterlab/issues/2833 being fixed, which if I understand correctly depends on this.
We have a plan here and it is moving forward. We are looking for more funding to support this work and are looking at applying for a CZI grant.
we should have a patreon, I would give monthly if i knew the money was being used for development.
I really want collab :)
Does JupyterLab accepts donations?
Here are a couple of ways to contribute financial resources toward JupyterLab development:
For this particular project, real-time collaboration, there are likely others and other companies that may be interested in helping fund the effort.
Why is that nessecary?
How come this has been shelved for 2 years? Why can't you get this done by the community? It doesn't look to me like this is fundamental research, rather just an engineering challenge.
Or am I too shortsighted here?
Edit: perhaps this post was a bit polarizing (unintentionally). Please read this before downvoting.
Edit2: Ok, I have read a little bit about the current effort and past achievements. "Shelved" was definitely too a too strong word. My apologies.
How come this has been shelved for 2 years?
People that at one time or another that were working on it ran out of time to work on and moved on to other things. Also priorities shifted (like actually shipping 1.0, etc.).
"shelved" is too strong of a word. For example, Ian and Vidar both made good progress on this last year, and Brian did some work on it too. Others have worked on it too in the last 6-8 months at some level.
Why can't you get this done by the community?
People in the community have been working on it (off and on as time and priorities permitted). It's a tricky problem.
It doesn't look to me like this is fundamental research, rather just an engineering challenge.
Or an I too shortsighted here?
I think you are underestimating the time it takes to solve some of the trickier problems and the amount of refactoring in JupyterLab needed to accommodate this work. (Efficient) RTC algorithms are an active research area, and applying them to a system like JupyterLab is also an engineering problem.
We welcome help if you want to start contributing! If you find it easy and have expertise in this area, you're doubly welcome!
I appreciate all your hard work, and you have created something awesome. But if you want me to donate I need to ask some questions about the timeline first.
I'm just trying to open up the discussion here. I apologize if the tone of my previous message offended anybody.
I do not have the proper expertise to understand the full complexity of this, but I have been following this thread and the ones before this one for over 2 years. I remember there was a great idea that lead to a great PoC that died because of Drive's API changes. Then there was a great plan to do this the right way. Problems were identified, papers describing solutions for the RTC complexities were found and shared. Then came the great refactor. Now I ask myself, how will this money help to achieve RTC in JupyterLab?
I'll defer here to @saulshanabrook who most recently put together a game plan to put us over the finish line. I totally agree that there should be a concrete plan to ask for financial donations.
@jasongrout Thank you for the information! I made a tiny donation to the Jupyter project on NumFOCUS. Even though it would better if NumFOCUS can allow specifying that I'm donating to JupyterLab specifically.
The guys behind CoCalc implemented collaborative editing for notebooks. Possibly the Jupyter developers working on this issue could tap into that knowledge. William Stein is a really friendly and responsive guy, so I'd suspect he'd be willing to help.
Hey @EWouters thanks for chiming in! I feel your pain... It's a good reminder to try to communicate publicly as soon as there is a plan, even if it isn't 100%.
I am beginning to ramp up on this work currently. There has been actually a lot of work put into getting that into a mostly working state, it's just been not well advertised or publicized.
My next step, in the next week or two, is to get an example running, in the jupyterlab/rtc repo, of a "supernode" based approach, where multiple clients can talk to this one node sitting on the server, and it takes care of interacting with the underlying Jupyter Server. There is a diagram I made in the README of that repo of that architecture roughly.
However, it takes a lot of time to draft up a really comprehensive plan! And a lot of things are still unknown. So my step after creating the proof of concept demo, is to write up a plan with all the knowns and unknowns, and try to break it up into manageable chunks.
We are also going to restart the bi-weekly RTC calls as well.
I think we proposed mondays at noon EST, on the off weeks when there is not a Voila call. But does that conflict with the ipywidgets meeting? I couldn't find that publicly listed anywhere.
@saulshanabrook please invite me to the RTC call when you schedule it ([email protected]). Thanks!
@williamstein will do! Looking forward to chatting with you.
I should be able to do a sprint on this soon (especially if you are ramping up as well @saulshanabrook ). Let's have that call sooner rather than later: I have some ideas for how to split it up into manageable chunks.
@saulshanabrook can you invite me to the RTC meeting as well? There are some people at UMich doing RTC on Jupyter stuff and they want to collaborate and being connected. And I am also interested in this topic :wink:
Thanks!
I am also very interested. Can we share the meeting details here when it's finalized? Unless there is already another place I should be subscribing to, in which case I would appreciate a link.
Same interest here.
Will you keep this updated with the status, or is there another source of information for those interested but not actively working on development ?
@devnull-mr Yeah good idea, I will keep that issue updated with status updates!
I have scheduled a meeting for a week from Monday, repeating every other week. I added it to the the Jupyter Community Calendar. Here is a link with some more details. Please feel free to add to the agenda before the meeting on points you would like to bring up! I plan on coming with a tentative plan.
I saw this project https://convergence.io/
I don't know if it can help but it seems interesting.
I have scheduled a meeting for a week from Monday, repeating every other week.
I'm not available then; I won't be at the meetings only because of a scheduling conflict, and not because I don't want to support development of realtime collab in Jupyter. I'll try to comment on online notes.
I moved the meeting forward one week, repeating every other week after that, to accommodate for the holidays in the US and the UK next monday.
@williamstein Sounds good. We can set up a time to chat as well when you do have availability as well, I definitely want to get your perspective since cocalc seems like the most fully featured collaborative Jupyter environment at the moment!
We can set up a time to chat as well when you do have availability as well,
My only never-available time is the same time as that meeting, so pretty much any other time is likely to work for a separate discussion.
Regarding collaboration, there are a million decisions and choices to be made, and certainly no right answers across the board. For CoCalc, probably the most painful decision was having to rewrite the entire Jupyter stack (frontend and backend, except kernels) from scratch in order to deal with the edge 1% of user complaints that just couldn't be fixed any other way. We put that decision off for years. Hopefully JupyterLab will have a much easier path since it's a more shiny modern codebase than Jupyter classic. :-).
In the spirit of collecting literature on other attempts, here is an interesting retrospective on how CRDT did not work out as envisioned in the xi editor: https://github.com/xi-editor/xi-editor/issues/1187#issuecomment-491473599. This comment also prompted a HackerNews discussion.
Another interesting article on OT vs CRDT from Joseph Gentle, who worked on Google Wave and is the author of ShareJS: https://josephg.com/blog/crdts-are-the-future/ (hn discussion). Joseph recommended watching Martin Kleppmann’s recent talk.
After reading the article, it seems to me that OT is much simpler and easier to make it work as expected. CRDT can be useful if you need to handle a lot of concurrency or if you don't want to depend on a central server, but I don't think this applies to the jupyter use case. The author thinks that the theoretical problems with CRDT were solved and we are just missing an implementation, but if that's the case, I would choose between waiting an unknown time for such an implementation, or using OT.
Hi @noamraph ,
I debunked the notion that CRDTs are too slow. https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/
Seph used some unfortunate phrasing in some places. But he really meant it when he said that Yjs kicks the competition. The bottom line of the above article is that it is literally impossible for a human to write a document that Yjs can't handle. While the document size really grows indefinitely, we can now compress everything so neatly that it really doesn't matter how much you write, you still end up with a small document.
If you are interested, you can have a look at a Yjs walkthrough with me and Seph https://www.youtube.com/watch?v=0l5XgnQ6rB4 (FYI I'm the author of Yjs). It has some interesting optimizations that make it performant.
I'm currently working on a Rust port of Yjs that will allow you to have concurrent data structures in Python or any other language (either using language bindings or wasm). While Yjs certainly can do more than Jupyter ever needs. The concurrent data structures have a lot of advantages aside from being "peer-to-peer". They are also really handy to work with. And just perfectly suited for text editing.
Thx for putting those links here. I have added them on https://jupyter-rtc.readthedocs.io/en/latest/about-rtc/algorithms.html where we curate information around the RTC algorithms.
I am giving a talk at Jupytercon Jupyter Real Time Collaboration https://cfp.jupytercon.com/2020/schedule/presentation/116/jupyter-real-time-collaboration. This is one of the slides I will use

For now, we are more testing, exploring, experimenting the options in the https://github.com/jupyterlab/rtc repository. See e.g. work on GraphQL https://github.com/jupyterlab/rtc/pull/73 and Fluid https://github.com/jupyterlab/rtc/issues/80
In terms of pure JupyterLab/Lumino, the work done in 2019 JupyterLab/Lumino has been updated to August master branches, read more on https://jupyter-rtc.readthedocs.io/en/latest/developer/integrations.html#jupyterlab
We have. biweekly calls open to all https://github.com/jupyterlab/rtc/blob/main/README.md, feel free to join and discuss.
Sure, I'll join next Monday if you don't mind me making a case for Yjs. Yjs is really about connecting different projects and providing a shared editing framework that works for all.
FYI: I was really impressed by the Lumino CRDT. Even though it is a relatively unknown project, it beat all other CRDT implementations (except Yjs, but I invested much more time in this project) and has some other nifty features.
Looking forward to your presentation!
Sure, I'll join next Monday if you don't mind me making a case for Yjs. Yjs is really about connecting different projects and providing a shared editing framework that works for all.
You are more than welcome. Talk to you soon.
Looking forward to your presentation!
You, as core Y.js developer, will not learn a lot on CRDT..., but maybe useful to identify potential specific needs and how others see RTC world from a library consumer standpoint.
I'm fascinated by this issue. From what I understand, I would personally
choose OT when its limitations fit my use case, and try CRDT otherwise. The
main reason I would prefer OT is that it's easy for me to understand it -
it basically works like I work with git, when if someone else pushed their
changes before I did, I need to rebase my changes. I just need to decide on
a rebase algorithm. However, I understand that CRDT has significant
advantages for some use cases. In a way, I see the fact that CRDT is
fascinating to computer scientists as a disadvantage - I want a boring
backend that is easy for me to understand.
I wonder if it would be possible for Jupyter to have an abstraction layer,
allowing the user to choose between a OT and CRDT backend. Then, instead of
having to decide up front what RTC algorithm would be best, it would be
easy to find the backend that is actually a better fit for the requirements
as they arise. I would think that from the library using the OT/CRDT
backend, the interface should be the same.
Cheers,
Noam
>
@noamraph Funny you should mention an abstraction layer, @echarles wrote something interesting about that the other day:
It turns out that having a target architecture is needed. I was thinking we could deliver a solution where the user is free to choose between a few RTC implementations (Luminio, Y.js, Fluid, Automerge) but this sounds really like over-architectured for the time being. Prolly we should stick for a monolith stack and the abstract when we have something that works.
_Originally posted by @echarles in https://github.com/jupyterlab/rtc/issues/80#issuecomment-697797775_
So yeah, concrete POC implementation first, then abstraction
Thx @telamonian for the link, I was going to add also Layers Definition https://github.com/jupyterlab/rtc/issues/61.
We have today a jupyterlab/lumino (work done in 2019 has been updated to august master, 2019 issues still need to be solved) + some react components that use Lumino with examples in https://github.com/jupyterlab/rtc/tree/main/examples
I believe the concrete next step is to add more usage of Y.js, Fluid... in those examples and that the ambitious target could be jupyter-frontent <-> jupyter-rtc <-> rtc-libs.
So what's the progress?
@GF-Huang We are progressing and converging. The work is being done in https://github.com/jupyterlab/rtc. As soon as a usable deliverable is produced, we will post here.
Most helpful comment
We have a plan here and it is moving forward. We are looking for more funding to support this work and are looking at applying for a CZI grant.