diff --git a/anaconda-project.yml b/anaconda-project.yml index b24928c0..6efefcbd 100644 --- a/anaconda-project.yml +++ b/anaconda-project.yml @@ -38,6 +38,7 @@ env_specs: wxyz-demo: packages: - bqplot + - cycler - dask - dask >=0.18.2 - dask-ml @@ -48,9 +49,11 @@ env_specs: - jsonpointer - jupyterlab >=0.35,<0.36 - lime + - matplotlib - nodejs >=8.11,<9 - pip - py-xgboost + - pygraphviz - pyld - python >=3.7,<3.8 - pyyaml @@ -60,10 +63,9 @@ env_specs: - skrebate - tornado <6 - tpot - - xgboost - - matplotlib + - transitions - umap-learn - - cycler + - xgboost - pip: - yellowbrick channels: diff --git a/ci/steps.conda.lint.yml b/ci/steps.conda.lint.yml index fbbe16ae..70c43b0b 100644 --- a/ci/steps.conda.lint.yml +++ b/ci/steps.conda.lint.yml @@ -23,3 +23,4 @@ steps: python>=3.7,<3.8.0a0 pyyaml traittypes + transitions diff --git a/ci/steps.conda.nbtest.yml b/ci/steps.conda.nbtest.yml index ed68214a..bf3af29b 100644 --- a/ci/steps.conda.nbtest.yml +++ b/ci/steps.conda.nbtest.yml @@ -39,6 +39,7 @@ steps: tornado<6 tpot traittypes + transitions umap-learn - script: python -m pip install --no-deps yellowbrick diff --git a/ci/steps.conda.robot.yml b/ci/steps.conda.robot.yml index 60e9363e..c63af74c 100644 --- a/ci/steps.conda.robot.yml +++ b/ci/steps.conda.robot.yml @@ -14,6 +14,7 @@ steps: dask dask-ml distributed + geckodriver importnb ipympl ipywidgets @@ -31,6 +32,8 @@ steps: pylint python>=3.7,<3.8.0a0 pyyaml + robotframework + robotframework-seleniumlibrary scikit-image scikit-learn scikit-mdr @@ -38,10 +41,8 @@ steps: tornado<6 tpot traittypes + transitions umap-learn - robotframework - robotframework-seleniumlibrary - geckodriver - ${{ if eq(parameters.name, 'Windows') }}: - script: python -m pip uninstall -y pyzmq && python -m pip install pyzmq diff --git a/environment.yml b/environment.yml index abec72f5..9a4e7d78 100644 --- a/environment.yml +++ b/environment.yml @@ -6,6 +6,7 @@ channels: dependencies: - bqplot + - cycler - dask - dask >=0.18.2 - dask-ml @@ -16,9 +17,11 @@ dependencies: - jsonpointer - jupyterlab >=0.35,<0.36 - lime + - matplotlib - nodejs >=8.11,<9 - pip - py-xgboost + - pygraphviz - pyld - python >=3.7,<3.8 - pyyaml @@ -28,9 +31,8 @@ dependencies: - skrebate - tornado <6 - tpot - - xgboost - - matplotlib + - transitions - umap-learn - - cycler + - xgboost - pip: - yellowbrick diff --git a/notebooks/Examples/StateMachine II.ipynb b/notebooks/Examples/StateMachine II.ipynb new file mode 100644 index 00000000..905acc06 --- /dev/null +++ b/notebooks/Examples/StateMachine II.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# StateMachine II" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from wxyz.stm.widget_stm import StateMachine, W\n", + "from wxyz.lab.widget_dock import DockBox\n", + "from yaml import safe_load" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with __import__(\"importnb\").Notebook():\n", + " from StateMachine import make_an_adventure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if __name__ == \"__main__\":\n", + " state_dict = safe_load(\"\"\"\n", + " jail:\n", + " _:\n", + " find_key: jail_has_key\n", + " sleep: jail\n", + " has_key:\n", + " _:\n", + " escape: town\n", + " town:\n", + " _:\n", + " steal_get_caught: jail\n", + " steal: town_has_coin\n", + " beg: town_has_coin\n", + " has_coin:\n", + " _:\n", + " buy_sword: town_has_gear_sword\n", + " buy_wand: town_has_gear_wand\n", + " has_gear:\n", + " _:\n", + " practice: town_has_gear\n", + " sword:\n", + " wand:\n", + " \"\"\")\n", + " display(state_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def state_dict_to_state_transitions(state_dict, prefix=\"\", parents=[]):\n", + " states = []\n", + " transitions = []\n", + " for state, children in state_dict.items():\n", + " if state == \"_\":\n", + " for tx, tgt in children.items():\n", + " if prefix:\n", + " transitions += [\n", + " [tx, parents[-1], tgt]\n", + " ]\n", + " else:\n", + " transitions += [\n", + " [tx, state, tgt]\n", + " ]\n", + " else:\n", + " cs, ct = [], []\n", + " if children:\n", + " cs, ct = state_dict_to_state_transitions(children, f\"{prefix}{state}_\", [*parents, f\"{prefix}{state}\"])\n", + " if cs:\n", + " states += [\n", + " {\"name\": state, \"children\": cs}\n", + " ]\n", + " else:\n", + " states += [state]\n", + " transitions += ct\n", + " return states, transitions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if __name__ == \"__main__\":\n", + " states, transitions = state_dict_to_state_transitions(state_dict)\n", + " display(states, transitions)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if __name__ == \"__main__\":\n", + " stm, view = make_an_adventure(states=states, transitions=transitions, initial=\"jail\")\n", + " dock = DockBox(view.children, layout=dict(height=\"600px\"))\n", + "\n", + " @dock.on_displayed\n", + " def _on_displayed(_):\n", + " dock.dock_layout = {\n", + " 'type': 'split-area',\n", + " 'orientation': 'horizontal',\n", + " 'children': [\n", + " {'type': 'tab-area', 'widgets': [0], 'currentIndex': 0},\n", + " {'type': 'tab-area', 'widgets': [1], 'currentIndex': 0}\n", + " ], 'sizes': [0.3, 0.7]}\n", + " display(dock)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/Examples/StateMachine.ipynb b/notebooks/Examples/StateMachine.ipynb new file mode 100644 index 00000000..b8c85618 --- /dev/null +++ b/notebooks/Examples/StateMachine.ipynb @@ -0,0 +1,137 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# StateMachine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from wxyz.stm.widget_stm import StateMachine, GraphMachine, W\n", + "from wxyz.svg.widget_svg import SVGBox" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def show_a_state_switch(\n", + " states=[\"magic\", \"more_magic\"], \n", + " transitions=[\n", + " [\"switch\", \"magic\", \"more_magic\"],\n", + " [\"switch\", \"more_magic\", \"magic\"]\n", + " ],\n", + " machine_class=StateMachine,\n", + " **kwargs\n", + "):\n", + " stm = machine_class(states=states, transitions=transitions, **kwargs)\n", + " current = W.Text(description=\"State\", disabled=True)\n", + " buttons = [W.Button(description=t) for t in set([t for t, u, v in transitions])]\n", + " for btn in buttons:\n", + " @btn.on_click\n", + " def switch_left(btn):\n", + " try:\n", + " getattr(stm.model, btn.description)()\n", + " stm.error = \"\"\n", + " except Exception as err:\n", + " stm.error = str(err)\n", + " err = W.HTML()\n", + " W.dlink((stm, \"state\"), (current, \"value\"))\n", + " W.dlink((stm, \"error\"), (err, \"value\"), \"
{}\".format)\n", + " view = W.VBox([current, *buttons, err])\n", + " return stm, view" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if __name__ == \"__main__\":\n", + " stm, stm_view = show_a_state_switch()\n", + " display(stm_view)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def make_an_adventure(\n", + " states=[\"Floor\", \"Pit\", \"Ladder\"],\n", + " transitions = [\n", + " [\"fall\", \"Floor\", \"Pit\"],\n", + " [\"sleep\", \"Pit\", \"Pit\"],\n", + " [\"sleep\", \"Floor\", \"Floor\"],\n", + " [\"climb\", \"Pit\", \"Ladder\"],\n", + " [\"climb\", \"Ladder\", \"Floor\"]\n", + " ],\n", + " machine_class=GraphMachine,\n", + " **kwargs\n", + "):\n", + " stm2, view = show_a_state_switch(states, transitions, machine_class=GraphMachine, **kwargs)\n", + " svg = SVGBox(show_svg=True, area_attr=\"id\", visible_areas=[\"*\"])\n", + " W.dlink((stm2, \"svg\"), (svg, \"svg\"))\n", + " prog = W.SelectionSlider(options=[\"dot\", \"neato\", \"circo\", \"fdp\"],\n", + " description=\"Layout\")\n", + " W.link([prog, \"value\"], [stm2, \"prog\"])\n", + " return stm2, W.HBox([\n", + " W.VBox([\n", + " view,\n", + " prog\n", + " ]),\n", + " svg\n", + " ])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if __name__ == \"__main__\":\n", + " stm2, view2 = make_an_adventure()\n", + " display(view2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/index.ipynb b/notebooks/index.ipynb index 406fa9e3..e5614cbb 100644 --- a/notebooks/index.ipynb +++ b/notebooks/index.ipynb @@ -34,7 +34,7 @@ "with importnb.Notebook():\n", " from Examples import (\n", " Hello_Worlds, JSON_LD_Playground, JSON_Tricks, Dock_Panel, Full_Screen, SVG, DataGrid, \n", - " StyleGrid_I, StyleGrid_II, SelectGrid\n", + " StyleGrid_I, StyleGrid_II, SelectGrid, StateMachine\n", " )" ] }, @@ -265,6 +265,28 @@ "pg3 = SelectGrid.make_select_grid()\n", "pg3" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [StateMachine](./Examples/StateMachine.ipynb)\n", + "Based on [transitions](https://github.com/pytransitions/transitions#basic-initialization)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "stm = StateMachine.make_an_adventure()\n", + "stm" + ] } ], "metadata": { diff --git a/src/py/wxyz_stm/LICENSE.txt b/src/py/wxyz_stm/LICENSE.txt new file mode 100644 index 00000000..8699a913 --- /dev/null +++ b/src/py/wxyz_stm/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2019 dead pixels collective +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/py/wxyz_stm/MANIFEST.in b/src/py/wxyz_stm/MANIFEST.in new file mode 100644 index 00000000..1372e018 --- /dev/null +++ b/src/py/wxyz_stm/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE.txt README.md diff --git a/src/py/wxyz_stm/README.md b/src/py/wxyz_stm/README.md new file mode 100644 index 00000000..e1f67c79 --- /dev/null +++ b/src/py/wxyz_stm/README.md @@ -0,0 +1,3 @@ +# ipywxyz + +> Experimental Jupyter widgets diff --git a/src/py/wxyz_stm/setup.cfg b/src/py/wxyz_stm/setup.cfg new file mode 100644 index 00000000..77a846fa --- /dev/null +++ b/src/py/wxyz_stm/setup.cfg @@ -0,0 +1,42 @@ +[metadata] +name = wxyz_stm +version = 0.1.0 +description = experimental Jupyter widgets +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/deathbeds/wxyz +author = dead pixels collective +author_email = ripxl@googlegroups.com +license = BSD-3-Clause +keywords = + Interactive +license_file = LICENSE.txt +classifiers = + Framework :: Jupyter + Intended Audience :: Developers + Intended Audience :: Information Technology + License :: OSI Approved :: BSD License + Programming Language :: Python + +[options] +install_requires = + ipywidgets + transitions +package_dir = + = src +packages = + wxyz.stm +include_package_data = True +zip_safe = False + +[options.extras_require] +tests = + pytest + +[options.packages.find] +where = + src + +[flake8] +exclude = .git,__pycache__,envs +max-line-length = 88 diff --git a/src/py/wxyz_stm/setup.py b/src/py/wxyz_stm/setup.py new file mode 100644 index 00000000..aefdf20d --- /dev/null +++ b/src/py/wxyz_stm/setup.py @@ -0,0 +1 @@ +__import__("setuptools").setup() diff --git a/src/py/wxyz_stm/src/wxyz/stm/__init__.py b/src/py/wxyz_stm/src/wxyz/stm/__init__.py new file mode 100644 index 00000000..f07bf83e --- /dev/null +++ b/src/py/wxyz_stm/src/wxyz/stm/__init__.py @@ -0,0 +1,4 @@ +""" Nothing to see here +""" + +from ._version import __version__ # noqa diff --git a/src/py/wxyz_stm/src/wxyz/stm/_version.py b/src/py/wxyz_stm/src/wxyz/stm/_version.py new file mode 100644 index 00000000..bb9ae17e --- /dev/null +++ b/src/py/wxyz_stm/src/wxyz/stm/_version.py @@ -0,0 +1,7 @@ +""" some constants +""" +# pylint: disable=invalid-name +version_info = (0, 1, 0, "dev") +__version__ = ".".join(map(str, version_info)) +module_name = "@deathbeds/wxyz-stm" +module_version = "^0.1.0" diff --git a/src/py/wxyz_stm/src/wxyz/stm/base.py b/src/py/wxyz_stm/src/wxyz/stm/base.py new file mode 100644 index 00000000..7d0e8997 --- /dev/null +++ b/src/py/wxyz_stm/src/wxyz/stm/base.py @@ -0,0 +1,20 @@ +""" Base classes for State Machine +""" +# pylint: disable=unused-import + +import ipywidgets as W # noqa +import traitlets as T + +from wxyz.core.base import Base + +from ._version import module_name, module_version + + +class StateMachineBase(Base): + """ Module metadata for StateMachine + """ + + _model_module = T.Unicode(module_name).tag(sync=True) + _model_module_version = T.Unicode(module_version).tag(sync=True) + _view_module = T.Unicode(module_name).tag(sync=True) + _view_module_version = T.Unicode(module_version).tag(sync=True) diff --git a/src/py/wxyz_stm/src/wxyz/stm/widget_stm.py b/src/py/wxyz_stm/src/wxyz/stm/widget_stm.py new file mode 100644 index 00000000..a083ab2e --- /dev/null +++ b/src/py/wxyz_stm/src/wxyz/stm/widget_stm.py @@ -0,0 +1,114 @@ +""" Widgets for working with State Machines +""" +# pylint: disable=fixme,no-member,broad-except +import ipywidgets.widgets.trait_types as TT +from transitions import Machine, State +from transitions.extensions import MachineFactory + +from .base import StateMachineBase, T, W + +NO_PYGRAPHVIZ = False + +try: + __import__("pygraphviz") +except ImportError: + NO_PYGRAPHVIZ = True + + +class _StateMachineModel(T.HasTraits): + # TODO: support the State object natively, especially on_* + state = T.Unicode(allow_none=True).tag(sync=True) + + +@W.register +class StateMachine(StateMachineBase): + """ A Widget that implements a state machine + + https://github.com/pytransitions/transitions#basic-initialization + """ + + _Machine = Machine + + _model_name = T.Unicode("StateMachineModel").tag(sync=True) + + states = T.List().tag(sync=True) + initial = T.Unicode().tag(sync=True) + transitions = T.List().tag(sync=True) + + machine = T.Instance(Machine, allow_none=True) + model = T.Instance(_StateMachineModel) + + state = T.Unicode(allow_none=True).tag(sync=True) + + def make_machine(self): + model = self.model = _StateMachineModel() + + machine = MachineFactory.get_predefined(**self.machine_factory_args())( + model=model, + states=self.states, + initial=self.initial or (self.states[0] if self.states else None), + transitions=self.transitions, + ) + + T.dlink((model, "state"), (self, "state")) + + def maybe_update(*_): + if machine == self.machine: + machine.set_state(self.state) + + self.observe(maybe_update, "state") + + return machine + + def machine_factory_args(self): + return dict(nested=True) + + @T.default("machine") + def _default_machine(self): + return self.make_machine() + + @T.observe("states", "transitions", "initial", "locked", "nested") + def on_configure(self, _): + """ make a Machine + """ + self.machine = self.make_machine() + + +@W.register +class GraphMachine(StateMachine): + """ A Widget that implements a state machine with SVG support + """ + + _model_name = T.Unicode("GraphMachineModel").tag(sync=True) + + svg = T.Unicode("").tag(sync=True) + prog = T.Unicode("dot").tag(sync=True) + + def _update_svg(self): + try: + self.svg = self.model.get_graph().draw(format="svg", prog=self.prog) + self.error = "" + except Exception as err: + self.error = str(err) + self.svg = "" + + @T.observe("state") + def _on_changing_state(self, _): + self._update_svg() + + def machine_factory_args(self): + args = dict(graph=True) + args.update(super().machine_factory_args()) + return args + + def make_machine(self): + machine = super().make_machine() + self._update_svg() + return machine + + @T.observe("prog") + def on_configure_prog(self, _): + """ make a Machine + """ + self.machine = self.make_machine() + self._update_svg() diff --git a/src/py/wxyz_svg/src/wxyz/svg/widget_svg.py b/src/py/wxyz_svg/src/wxyz/svg/widget_svg.py index 3a62585c..55b3ff44 100644 --- a/src/py/wxyz_svg/src/wxyz/svg/widget_svg.py +++ b/src/py/wxyz_svg/src/wxyz/svg/widget_svg.py @@ -34,8 +34,8 @@ class SVGBox(SVGBase, W.Box): "`area_attr` in SVG" ).tag(sync=True) - visible_areas = T.Tuple( - [None], + visible_areas = T.List( + default=[None], help="a list of `area_attrs`s of SVG `g`s to show. " "Accepts [None] for all", ).tag(sync=True) diff --git a/src/ts/wxyz-svg/src/widgets/svg.ts b/src/ts/wxyz-svg/src/widgets/svg.ts index bc495ea6..f1630f44 100644 --- a/src/ts/wxyz-svg/src/widgets/svg.ts +++ b/src/ts/wxyz-svg/src/widgets/svg.ts @@ -85,8 +85,8 @@ export class SVGBoxView extends BoxView { const areaAttr = this.model.get('area_attr'); this._lastSVG = this.model.get('svg'); + this._d3.selectAll('svg').remove(); const layout = this._d3.selectAll(CSS.LAYOUT).data([1]); - layout.remove(); this._zoom = null; layout.enter().call(function() { const xml = view._parser.parseFromString(view._lastSVG, 'image/svg+xml'); @@ -156,31 +156,31 @@ export class SVGBoxView extends BoxView { layout.call(this._zoom); } - const el = this.el.parentNode; - const doc = document.documentElement; - const aspectRatio = this._original.width / this._original.height; + // const el = this.el.parentNode; + // const doc = document.documentElement; + // const aspectRatio = this._original.width / this._original.height; const areaWidgets = view.model.get('area_widgets'); const visibleAreas = this.model .get('visible_areas') .map(this.patternToRegexp) .filter(Object); - let width = Math.min(el.clientWidth, doc.clientWidth); - let height = width / aspectRatio; - let scale = width / this._original.width; + // let width = Math.min(el.clientWidth, doc.clientWidth); + // let scale = width / this._original.width; + // let height = width / aspectRatio; let labelMap = {} as any; let areaAttr = this.model.get('area_attr'); - if (scale * this._original.height > doc.clientHeight) { - scale = doc.clientHeight / this._original.height; - height = doc.clientHeight; - width = height * aspectRatio; - } + // if (scale * this._original.height > doc.clientHeight) { + // scale = doc.clientHeight / this._original.height; + // height = doc.clientHeight; + // width = height * aspectRatio; + // } layout - .attr('width', width) - .attr('height', height) + // .attr('width', width) + // .attr('height', height) .style('opacity', this.model.get('show_svg') ? 1 : 0) - .select(`#${CSS.LAYOUT}-ROOT-${view.cid}`) - .attr('transform', `scale(${scale})`); + .select(`#${CSS.LAYOUT}-ROOT-${view.cid}`); + // .attr('transform', `scale(${scale})`); const area = layout.selectAll('g'); const named: any = area.filter(function() { // tslint:disable diff --git a/yarn.lock b/yarn.lock index 1e2a9dde..e98c8f35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2329,9 +2329,9 @@ fsevents@^1.2.7: nan "^2.9.2" node-pre-gyp "^0.10.0" -fstream@^1.0.0, fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" +fstream@^1.0.0, fstream@^1.0.12, fstream@^1.0.2: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" dependencies: graceful-fs "^4.1.2" inherits "~2.0.0"