diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..7721cb84 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,54 @@ +version: 2.1 +orbs: + codecov: codecov/codecov@1 +executors: + node: + docker: + - image: circleci/node:14.3 +jobs: + build: + executor: node + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + - run: npm ci + - save_cache: + paths: + - ~/.npm + key: v1-dependencies-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }} + - run: npm run build + - run: npm run test:prod + - codecov/upload + - store_test_results: + path: test-results + - store_artifacts: + path: test-results + publish: + executor: node + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + - run: npm ci + - run: npm run build + - run: npm run semantic-release +workflows: + version: 2 + build-and-publish: + jobs: + - build + - publish: + context: hypertrace-publishing + requires: + - build + filters: + branches: + only: + - master diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..92b9bb1e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +#root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 100 +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..8a479a56 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# Each line is a file pattern followed by one or more owners. + +# global +* @aaron-steinfeld \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..db9ba417 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b1b9d2c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules +coverage +.nyc_output +.DS_Store +*.log +.idea +dist +compiled +.awcache +.rpt2_cache +docs +test-results diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..b3500fc9 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@hypertrace:registry https://api.bintray.com/npm/hypertrace/npm diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..23f256ec --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "printWidth": 120, + "trailingComma": "none", + "arrowParens": "avoid" +} diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml new file mode 100644 index 00000000..ec094685 --- /dev/null +++ b/.semaphore/semaphore.yml @@ -0,0 +1,35 @@ +version: v1.0 +name: Dash POC +agent: + machine: + type: e1-standard-2 + os_image: ubuntu1804 + +blocks: + - name: Install dependencies + task: + prologue: + commands: + - sem-version node 11.1 + jobs: + - name: npm install and cache + commands: + - checkout + - cache restore node-modules-$(checksum package-lock.json) + - npm install + - cache store node-modules-$(checksum package-lock.json) node_modules + + - name: "Build" + task: + prologue: + commands: + - sem-version node 11.1 + - checkout + - cache restore node-modules-$(checksum package-lock.json) + jobs: + - name: npm run build + commands: + - npm run build + - name: npm run test:prod + commands: + - npm run test:prod diff --git a/.snyk b/.snyk new file mode 100644 index 00000000..80e6b060 --- /dev/null +++ b/.snyk @@ -0,0 +1,9 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.14.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JS-LODASH-567746: + - '*': + reason: no fix or usages of vulnerable method + expires: 2020-09-30T00:00:00.000Z +patch: {} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..02b469b7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug hyperdash", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--inspect-brk", + "${workspaceFolder}/node_modules/.bin/jest", + "--runInBand", + "--coverage", + "false" + ], + "console": "internalConsole", + "internalConsoleOptions": "openOnSessionStart", + "port": 9229, + "sourceMaps": true + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..c8102b28 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.preferences.quoteStyle": "single", + "typescript.preferences.importModuleSpecifier": "relative" +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..74c892ae --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,660 @@ +### GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public +License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for +the specific requirements. + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU AGPL, see . diff --git a/README.md b/README.md new file mode 100644 index 00000000..72a8e4b5 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Getting Started + +[![CircleCI](https://circleci.com/gh/hypertrace/hyperdash.svg?style=svg)](https://circleci.com/gh/hypertrace/hyperdash) +[![codecov](https://codecov.io/gh/hypertrace/hyperdash/branch/master/graph/badge.svg)](https://codecov.io/gh/hypertrace/hyperdash) + +## Prerequisites + +Install Node + +## Setup + +`npm install` + +## Build + +`npm run build` + +## Testing + +`npm run test:watch` + +## Commit + +`npm run commit` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..3e84e68f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,16284 @@ +{ + "name": "@hypertrace/hyperdash", + "version": "0.0.0-PLACEHOLDER", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", + "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.1" + } + }, + "@babel/compat-data": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.1.tgz", + "integrity": "sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/core": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.2.tgz", + "integrity": "sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.1", + "@babel/generator": "^7.10.2", + "@babel/helper-module-transforms": "^7.10.1", + "@babel/helpers": "^7.10.1", + "@babel/parser": "^7.10.2", + "@babel/template": "^7.10.1", + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", + "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", + "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz", + "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz", + "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.1", + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz", + "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.1", + "@babel/helper-member-expression-to-functions": "^7.10.1", + "@babel/helper-optimise-call-expression": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-replace-supers": "^7.10.1", + "@babel/helper-split-export-declaration": "^7.10.1" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz", + "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.1", + "@babel/helper-regex": "^7.10.1", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz", + "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.1", + "@babel/types": "^7.10.1", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz", + "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-function-name": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", + "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.1", + "@babel/template": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", + "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz", + "integrity": "sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz", + "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz", + "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz", + "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.1", + "@babel/helper-replace-supers": "^7.10.1", + "@babel/helper-simple-access": "^7.10.1", + "@babel/helper-split-export-declaration": "^7.10.1", + "@babel/template": "^7.10.1", + "@babel/types": "^7.10.1", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz", + "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz", + "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz", + "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==", + "dev": true, + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz", + "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.1", + "@babel/helper-wrap-function": "^7.10.1", + "@babel/template": "^7.10.1", + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz", + "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.1", + "@babel/helper-optimise-call-expression": "^7.10.1", + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz", + "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", + "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz", + "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.1", + "@babel/template": "^7.10.1", + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helpers": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.1.tgz", + "integrity": "sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.1", + "@babel/traverse": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/highlight": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", + "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", + "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz", + "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-remap-async-to-generator": "^7.10.1", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz", + "integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz", + "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz", + "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz", + "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz", + "integrity": "sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-numeric-separator": "^7.10.1" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz", + "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.1" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz", + "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz", + "integrity": "sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz", + "integrity": "sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz", + "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz", + "integrity": "sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.1.tgz", + "integrity": "sha512-XyHIFa9kdrgJS91CUH+ccPVTnJShr8nLGc5bG2IhGXv5p1Rd+8BleGE5yzIg2Nc1QZAdHDa0Qp4m6066OL96Iw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz", + "integrity": "sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz", + "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.1.tgz", + "integrity": "sha512-X/d8glkrAtra7CaQGMiGs/OGa6XgUzqPcBXCIGFCpCqnfGlT0Wfbzo/B89xHhnInTaItPK8LALblVXcUOEh95Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz", + "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz", + "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-remap-async-to-generator": "^7.10.1" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz", + "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz", + "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz", + "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.1", + "@babel/helper-define-map": "^7.10.1", + "@babel/helper-function-name": "^7.10.1", + "@babel/helper-optimise-call-expression": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-replace-supers": "^7.10.1", + "@babel/helper-split-export-declaration": "^7.10.1", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz", + "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz", + "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz", + "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz", + "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz", + "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", + "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz", + "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz", + "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz", + "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz", + "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz", + "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-simple-access": "^7.10.1", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz", + "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.10.1", + "@babel/helper-module-transforms": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz", + "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", + "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.8.3" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz", + "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz", + "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-replace-supers": "^7.10.1" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", + "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz", + "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz", + "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz", + "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz", + "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz", + "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz", + "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-regex": "^7.10.1" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz", + "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz", + "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.1.tgz", + "integrity": "sha512-v+QWKlmCnsaimLeqq9vyCsVRMViZG1k2SZTlcZvB+TqyH570Zsij8nvVUZzOASCRiQFUxkLrn9Wg/kH0zgy5OQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-syntax-typescript": "^7.10.1" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz", + "integrity": "sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz", + "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1" + } + }, + "@babel/preset-env": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.2.tgz", + "integrity": "sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.1", + "@babel/helper-compilation-targets": "^7.10.2", + "@babel/helper-module-imports": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-proposal-async-generator-functions": "^7.10.1", + "@babel/plugin-proposal-class-properties": "^7.10.1", + "@babel/plugin-proposal-dynamic-import": "^7.10.1", + "@babel/plugin-proposal-json-strings": "^7.10.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", + "@babel/plugin-proposal-numeric-separator": "^7.10.1", + "@babel/plugin-proposal-object-rest-spread": "^7.10.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.1", + "@babel/plugin-proposal-optional-chaining": "^7.10.1", + "@babel/plugin-proposal-private-methods": "^7.10.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.1", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.1", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.10.1", + "@babel/plugin-transform-arrow-functions": "^7.10.1", + "@babel/plugin-transform-async-to-generator": "^7.10.1", + "@babel/plugin-transform-block-scoped-functions": "^7.10.1", + "@babel/plugin-transform-block-scoping": "^7.10.1", + "@babel/plugin-transform-classes": "^7.10.1", + "@babel/plugin-transform-computed-properties": "^7.10.1", + "@babel/plugin-transform-destructuring": "^7.10.1", + "@babel/plugin-transform-dotall-regex": "^7.10.1", + "@babel/plugin-transform-duplicate-keys": "^7.10.1", + "@babel/plugin-transform-exponentiation-operator": "^7.10.1", + "@babel/plugin-transform-for-of": "^7.10.1", + "@babel/plugin-transform-function-name": "^7.10.1", + "@babel/plugin-transform-literals": "^7.10.1", + "@babel/plugin-transform-member-expression-literals": "^7.10.1", + "@babel/plugin-transform-modules-amd": "^7.10.1", + "@babel/plugin-transform-modules-commonjs": "^7.10.1", + "@babel/plugin-transform-modules-systemjs": "^7.10.1", + "@babel/plugin-transform-modules-umd": "^7.10.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", + "@babel/plugin-transform-new-target": "^7.10.1", + "@babel/plugin-transform-object-super": "^7.10.1", + "@babel/plugin-transform-parameters": "^7.10.1", + "@babel/plugin-transform-property-literals": "^7.10.1", + "@babel/plugin-transform-regenerator": "^7.10.1", + "@babel/plugin-transform-reserved-words": "^7.10.1", + "@babel/plugin-transform-shorthand-properties": "^7.10.1", + "@babel/plugin-transform-spread": "^7.10.1", + "@babel/plugin-transform-sticky-regex": "^7.10.1", + "@babel/plugin-transform-template-literals": "^7.10.1", + "@babel/plugin-transform-typeof-symbol": "^7.10.1", + "@babel/plugin-transform-unicode-escapes": "^7.10.1", + "@babel/plugin-transform-unicode-regex": "^7.10.1", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.10.2", + "browserslist": "^4.12.0", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", + "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-typescript": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.10.1.tgz", + "integrity": "sha512-m6GV3y1ShiqxnyQj10600ZVOFrSSAa8HQ3qIUk2r+gcGtHTIRw0dJnFLt1WNXpKjtVw7yw1DAPU/6ma2ZvgJuA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.1", + "@babel/plugin-transform-typescript": "^7.10.1" + } + }, + "@babel/runtime": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", + "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, + "@babel/template": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", + "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.1", + "@babel/parser": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/traverse": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", + "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.1", + "@babel/generator": "^7.10.1", + "@babel/helper-function-name": "^7.10.1", + "@babel/helper-split-export-declaration": "^7.10.1", + "@babel/parser": "^7.10.1", + "@babel/types": "^7.10.1", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", + "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@commitlint/cli": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-9.0.1.tgz", + "integrity": "sha512-BVOc/BY0FMmKTTH5oUVE0ukhPWDFf364FiYKk3GlXLOGTZPTXQ/9ncB2eMOaCF0PdcEVY4VoMjyoRSgcVapCMg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.9.6", + "@commitlint/format": "^9.0.1", + "@commitlint/lint": "^9.0.1", + "@commitlint/load": "^9.0.1", + "@commitlint/read": "^9.0.1", + "chalk": "3.0.0", + "core-js": "^3.6.1", + "get-stdin": "7.0.0", + "lodash": "^4.17.15", + "meow": "5.0.0", + "regenerator-runtime": "0.13.3", + "resolve-from": "5.0.0", + "resolve-global": "1.0.0" + }, + "dependencies": { + "@commitlint/execute-rule": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-9.0.1.tgz", + "integrity": "sha512-fxnLadXs59qOBE9dInfQjQ4DmbGToQ0NjfqqmN6N8qS+KsCecO6N0mMUrC95et9xTeimFRr+0l9UMfmRVHNS/w==", + "dev": true + }, + "@commitlint/load": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-9.0.1.tgz", + "integrity": "sha512-6ix/pUjVAggmDLTcnpyk0bgY3H9UBBTsEeFvTkHV+WQ6LNIxsQk8SwEOEZzWHUqt0pxqMQeiUgYeSZsSw2+uiw==", + "dev": true, + "requires": { + "@commitlint/execute-rule": "^9.0.1", + "@commitlint/resolve-extends": "^9.0.1", + "@commitlint/types": "^9.0.1", + "chalk": "3.0.0", + "cosmiconfig": "^6.0.0", + "lodash": "^4.17.15", + "resolve-from": "^5.0.0" + } + }, + "@commitlint/resolve-extends": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-9.0.1.tgz", + "integrity": "sha512-o6Lya2ILg1tEfWatS5x8w4ImvDzwb1whxsr2c/cxVCFqLF4hxHHHniZ0NJ+HFhYa1kBsYeKlD1qn9fHX5Y1+PQ==", + "dev": true, + "requires": { + "import-fresh": "^3.0.0", + "lodash": "^4.17.15", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@commitlint/config-conventional": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-9.0.1.tgz", + "integrity": "sha512-5rGu8aT4nRhWKrd5SpXqKJKLM07wXi4X5KVD9EEAuucAh2iZgfJJK9HKZNKGEKLKBQSWlnXE6UvkeEjJgi6TPQ==", + "dev": true, + "requires": { + "conventional-changelog-conventionalcommits": "4.2.3" + } + }, + "@commitlint/ensure": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-9.0.1.tgz", + "integrity": "sha512-z8SEkfbn0lMnAtt7Hp3A8hE3CRCDsg+Eu3Xj1UJakOyCPJgHE1/vEyM2DO2dxTXVKuttiHeLDnUSHCxklm78Ng==", + "dev": true, + "requires": { + "@commitlint/types": "^9.0.1", + "lodash": "^4.17.15" + } + }, + "@commitlint/execute-rule": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-8.3.4.tgz", + "integrity": "sha512-f4HigYjeIBn9f7OuNv5zh2y5vWaAhNFrfeul8CRJDy82l3Y+09lxOTGxfF3uMXKrZq4LmuK6qvvRCZ8mUrVvzQ==", + "dev": true, + "optional": true + }, + "@commitlint/format": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-9.0.1.tgz", + "integrity": "sha512-5oY7Jyve7Bfnx0CdbxFcpRKq92vUANFq3MVbz/ZTgvuYgUeMuYsSEwW6MJtOgOhHBQ2vZP/uPdxwmU+6pWZHcg==", + "dev": true, + "requires": { + "chalk": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@commitlint/is-ignored": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-9.0.1.tgz", + "integrity": "sha512-doGBfQgbsi48Hc48runGdN0TQFvf5XZizck8cylQdGG/3w+YwX9WkplEor7cvz8pmmuD6PpfpdukHSKlR8KmHQ==", + "dev": true, + "requires": { + "@commitlint/types": "^9.0.1", + "semver": "7.1.3" + }, + "dependencies": { + "semver": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "dev": true + } + } + }, + "@commitlint/lint": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-9.0.1.tgz", + "integrity": "sha512-EAn4E6aGWZ96Dg9LN28kdELqkyFOAUGlXWmanMdWxGFGdOf24ZHzlVsbr/Yb1oSBUE2KVvAF5W2Mzn2+Ge5rOg==", + "dev": true, + "requires": { + "@commitlint/is-ignored": "^9.0.1", + "@commitlint/parse": "^9.0.1", + "@commitlint/rules": "^9.0.1", + "@commitlint/types": "^9.0.1" + } + }, + "@commitlint/load": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-8.3.5.tgz", + "integrity": "sha512-poF7R1CtQvIXRmVIe63FjSQmN9KDqjRtU5A6hxqXBga87yB2VUJzic85TV6PcQc+wStk52cjrMI+g0zFx+Zxrw==", + "dev": true, + "optional": true, + "requires": { + "@commitlint/execute-rule": "^8.3.4", + "@commitlint/resolve-extends": "^8.3.5", + "babel-runtime": "^6.23.0", + "chalk": "2.4.2", + "cosmiconfig": "^5.2.0", + "lodash": "4.17.15", + "resolve-from": "^5.0.0" + } + }, + "@commitlint/message": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-9.0.1.tgz", + "integrity": "sha512-9rKnOeBV5s5hnV895aE3aMgciC27kAjkV9BYVQOWRjZdXHFZxa+OZ94mkMp+Hcr61W++fox1JJpPiTuCTDX3TQ==", + "dev": true + }, + "@commitlint/parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-9.0.1.tgz", + "integrity": "sha512-O39yMSMFdBtqwyM5Ld7RT6OGeI7jiXB9UUb09liIXIkltaZZo6CeoBD9hyfRWpaw81SiGL4OwHzp92mYVHLmow==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-parser": "^3.0.0" + } + }, + "@commitlint/read": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-9.0.1.tgz", + "integrity": "sha512-EYbel85mAiHb56bS5jBJ71lEaGjTnkSJLxTV1u6dpxdSBkRdmAn2DSPd6KQSbwYGUlPCR+pAZeZItT1y0Xk3hg==", + "dev": true, + "requires": { + "@commitlint/top-level": "^9.0.1", + "fs-extra": "^8.1.0", + "git-raw-commits": "^2.0.0" + } + }, + "@commitlint/resolve-extends": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-8.3.5.tgz", + "integrity": "sha512-nHhFAK29qiXNe6oH6uG5wqBnCR+BQnxlBW/q5fjtxIaQALgfoNLHwLS9exzbIRFqwJckpR6yMCfgMbmbAOtklQ==", + "dev": true, + "optional": true, + "requires": { + "import-fresh": "^3.0.0", + "lodash": "4.17.15", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + } + }, + "@commitlint/rules": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-9.0.1.tgz", + "integrity": "sha512-K9IiQzF/C2tP/0mQUPSkOtmAEUleRQhZK1NFLVbsd6r4uobaczjPSYvEH+cuSHlD9b3Ori7PRiTgVBAZTH5ORQ==", + "dev": true, + "requires": { + "@commitlint/ensure": "^9.0.1", + "@commitlint/message": "^9.0.1", + "@commitlint/to-lines": "^9.0.1", + "@commitlint/types": "^9.0.1" + } + }, + "@commitlint/to-lines": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-9.0.1.tgz", + "integrity": "sha512-FHiXPhFgGnvekF4rhyl1daHimEHkr81pxbHAmWG/0SOCehFr5THsWGoUYNNBMF7rdwUuVq4tXJpEOFiWBGKigg==", + "dev": true + }, + "@commitlint/top-level": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-9.0.1.tgz", + "integrity": "sha512-AjCah5y7wu9F/hOwMnqsujPRWlKerX79ZGf+UfBpOdAh+USdV7a/UfQaqjgCzkxy5GcNO9ER5A+2mWrUHxJ0hQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "@commitlint/types": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-9.0.1.tgz", + "integrity": "sha512-wo2rHprtDzTHf4tiSxavktJ52ntiwmg7eHNGFLH38G1of8OfGVwOc1sVbpM4jN/HK/rCMhYOi6xzoPqsv0537A==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, + "@jest/console": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.0.1.tgz", + "integrity": "sha512-9t1KUe/93coV1rBSxMmBAOIK3/HVpwxArCA1CxskKyRiv6o8J70V8C/V3OJminVCTa2M0hQI9AWRd5wxu2dAHw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "jest-message-util": "^26.0.1", + "jest-util": "^26.0.1", + "slash": "^3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.0.1.tgz", + "integrity": "sha512-Xq3eqYnxsG9SjDC+WLeIgf7/8KU6rddBxH+SCt18gEpOhAGYC/Mq+YbtlNcIdwjnnT+wDseXSbU0e5X84Y4jTQ==", + "dev": true, + "requires": { + "@jest/console": "^26.0.1", + "@jest/reporters": "^26.0.1", + "@jest/test-result": "^26.0.1", + "@jest/transform": "^26.0.1", + "@jest/types": "^26.0.1", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^26.0.1", + "jest-config": "^26.0.1", + "jest-haste-map": "^26.0.1", + "jest-message-util": "^26.0.1", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.0.1", + "jest-resolve-dependencies": "^26.0.1", + "jest-runner": "^26.0.1", + "jest-runtime": "^26.0.1", + "jest-snapshot": "^26.0.1", + "jest-util": "^26.0.1", + "jest-validate": "^26.0.1", + "jest-watcher": "^26.0.1", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "@jest/environment": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.0.1.tgz", + "integrity": "sha512-xBDxPe8/nx251u0VJ2dFAFz2H23Y98qdIaNwnMK6dFQr05jc+Ne/2np73lOAx+5mSBO/yuQldRrQOf6hP1h92g==", + "dev": true, + "requires": { + "@jest/fake-timers": "^26.0.1", + "@jest/types": "^26.0.1", + "jest-mock": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/fake-timers": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.0.1.tgz", + "integrity": "sha512-Oj/kCBnTKhm7CR+OJSjZty6N1bRDr9pgiYQr4wY221azLz5PHi08x/U+9+QpceAYOWheauLP8MhtSVFrqXQfhg==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "@sinonjs/fake-timers": "^6.0.1", + "jest-message-util": "^26.0.1", + "jest-mock": "^26.0.1", + "jest-util": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/globals": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.0.1.tgz", + "integrity": "sha512-iuucxOYB7BRCvT+TYBzUqUNuxFX1hqaR6G6IcGgEqkJ5x4htNKo1r7jk1ji9Zj8ZMiMw0oB5NaA7k5Tx6MVssA==", + "dev": true, + "requires": { + "@jest/environment": "^26.0.1", + "@jest/types": "^26.0.1", + "expect": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/reporters": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.0.1.tgz", + "integrity": "sha512-NWWy9KwRtE1iyG/m7huiFVF9YsYv/e+mbflKRV84WDoJfBqUrNRyDbL/vFxQcYLl8IRqI4P3MgPn386x76Gf2g==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^26.0.1", + "@jest/test-result": "^26.0.1", + "@jest/transform": "^26.0.1", + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^26.0.1", + "jest-resolve": "^26.0.1", + "jest-util": "^26.0.1", + "jest-worker": "^26.0.0", + "node-notifier": "^7.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^4.1.3" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "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 + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/source-map": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.0.0.tgz", + "integrity": "sha512-S2Z+Aj/7KOSU2TfW0dyzBze7xr95bkm5YXNUqqCek+HE0VbNNSNzrRwfIi5lf7wvzDTSS0/ib8XQ1krFNyYgbQ==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "@jest/test-result": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.0.1.tgz", + "integrity": "sha512-oKwHvOI73ICSYRPe8WwyYPTtiuOAkLSbY8/MfWF3qDEd/sa8EDyZzin3BaXTqufir/O/Gzea4E8Zl14XU4Mlyg==", + "dev": true, + "requires": { + "@jest/console": "^26.0.1", + "@jest/types": "^26.0.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/test-sequencer": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.0.1.tgz", + "integrity": "sha512-ssga8XlwfP8YjbDcmVhwNlrmblddMfgUeAkWIXts1V22equp2GMIHxm7cyeD5Q/B0ZgKPK/tngt45sH99yLLGg==", + "dev": true, + "requires": { + "@jest/test-result": "^26.0.1", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.0.1", + "jest-runner": "^26.0.1", + "jest-runtime": "^26.0.1" + } + }, + "@jest/transform": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.0.1.tgz", + "integrity": "sha512-pPRkVkAQ91drKGbzCfDOoHN838+FSbYaEAvBXvKuWeeRRUD8FjwXkqfUNUZL6Ke48aA/1cqq/Ni7kVMCoqagWA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.0.1", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.0.1", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.0.1", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "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 + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@octokit/auth-token": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.2.tgz", + "integrity": "sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ==", + "dev": true, + "requires": { + "@octokit/types": "^5.0.0" + } + }, + "@octokit/core": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-2.5.4.tgz", + "integrity": "sha512-HCp8yKQfTITYK+Nd09MHzAlP1v3Ii/oCohv0/TW9rhSLvzb98BOVs2QmVYuloE6a3l6LsfyGIwb6Pc4ycgWlIQ==", + "dev": true, + "requires": { + "@octokit/auth-token": "^2.4.0", + "@octokit/graphql": "^4.3.1", + "@octokit/request": "^5.4.0", + "@octokit/types": "^5.0.0", + "before-after-hook": "^2.1.0", + "universal-user-agent": "^5.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.3.tgz", + "integrity": "sha512-Y900+r0gIz+cWp6ytnkibbD95ucEzDSKzlEnaWS52hbCDNcCJYO5mRmWW7HRAnDc7am+N/5Lnd8MppSaTYx1Yg==", + "dev": true, + "requires": { + "@octokit/types": "^5.0.0", + "is-plain-object": "^3.0.0", + "universal-user-agent": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + } + } + }, + "@octokit/graphql": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.1.tgz", + "integrity": "sha512-qgMsROG9K2KxDs12CO3bySJaYoUu2aic90qpFrv7A8sEBzZ7UFGvdgPKiLw5gOPYEYbS0Xf8Tvf84tJutHPulQ==", + "dev": true, + "requires": { + "@octokit/request": "^5.3.0", + "@octokit/types": "^5.0.0", + "universal-user-agent": "^5.0.0" + } + }, + "@octokit/plugin-paginate-rest": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.2.tgz", + "integrity": "sha512-3OO/SjB5BChRTVRRQcZzpL0ZGcDGEB2dBzNhfqVqqMs6WDwo7cYW8cDwxqW8+VvA78mDK/abXgR/UrYg4HqrQg==", + "dev": true, + "requires": { + "@octokit/types": "^5.0.0" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz", + "integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==", + "dev": true + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.17.0.tgz", + "integrity": "sha512-NFV3vq7GgoO2TrkyBRUOwflkfTYkFKS0tLAPym7RNpkwLCttqShaEGjthOsPEEL+7LFcYv3mU24+F2yVd3npmg==", + "dev": true, + "requires": { + "@octokit/types": "^4.1.6", + "deprecation": "^2.3.1" + }, + "dependencies": { + "@octokit/types": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.1.10.tgz", + "integrity": "sha512-/wbFy1cUIE5eICcg0wTKGXMlKSbaAxEr00qaBXzscLXpqhcwgXeS6P8O0pkysBhRfyjkKjJaYrvR1ExMO5eOXQ==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, + "@octokit/request": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.5.tgz", + "integrity": "sha512-atAs5GAGbZedvJXXdjtKljin+e2SltEs48B3naJjqWupYl2IUBbB/CJisyjbNHcKpHzb3E+OYEZ46G8eakXgQg==", + "dev": true, + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.0.0", + "@octokit/types": "^5.0.0", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", + "node-fetch": "^2.3.0", + "once": "^1.4.0", + "universal-user-agent": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + } + } + }, + "@octokit/request-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.1.tgz", + "integrity": "sha512-5lqBDJ9/TOehK82VvomQ6zFiZjPeSom8fLkFVLuYL3sKiIb5RB8iN/lenLkY7oBmyQcGP7FBMGiIZTO8jufaRQ==", + "dev": true, + "requires": { + "@octokit/types": "^4.0.1", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "dependencies": { + "@octokit/types": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.1.10.tgz", + "integrity": "sha512-/wbFy1cUIE5eICcg0wTKGXMlKSbaAxEr00qaBXzscLXpqhcwgXeS6P8O0pkysBhRfyjkKjJaYrvR1ExMO5eOXQ==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, + "@octokit/rest": { + "version": "17.11.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.11.2.tgz", + "integrity": "sha512-4jTmn8WossTUaLfNDfXk4fVJgbz5JgZE8eCs4BvIb52lvIH8rpVMD1fgRCrHbSd6LRPE5JFZSfAEtszrOq3ZFQ==", + "dev": true, + "requires": { + "@octokit/core": "^2.4.3", + "@octokit/plugin-paginate-rest": "^2.2.0", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "3.17.0" + } + }, + "@octokit/types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.0.0.tgz", + "integrity": "sha512-3LVS+MbeqwSd5G4KS8123cZz+hWomsiGeMnQ/QJIBFDwL/YHX8kkr0FZXrgWEMO7Fgi2/VOrhbiFnk9sZ+s4qA==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + }, + "@rollup/plugin-json": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8" + } + }, + "@rollup/plugin-node-resolve": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.0.1.tgz", + "integrity": "sha512-KIeAmueDDaYMqMBnUngLVVZhURwxA12nq/YB6nGm5/JpVyOMwI1fCVU3oL/dAnnLBG7oiPXntO5LHOiMrfNXCA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "deep-freeze": "^0.0.1", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.14.2" + } + }, + "@rollup/plugin-typescript": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-4.1.2.tgz", + "integrity": "sha512-+7UlGat/99e2JbmGNnIauxwEhYLwrL7adO/tSJxUN57xrrS3Ps+ZzYpLCDGPZJ57j+ZJTZLLN89KXW9JMEB+jg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.1", + "resolve": "^1.14.1" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "@semantic-release/commit-analyzer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-8.0.1.tgz", + "integrity": "sha512-5bJma/oB7B4MtwUkZC2Bf7O1MHfi4gWe4mA+MIQ3lsEV0b422Bvl1z5HRpplDnMLHH3EXMoRdEng6Ds5wUqA3A==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.7", + "debug": "^4.0.0", + "import-from": "^3.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.2" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "conventional-changelog-angular": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", + "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@semantic-release/error": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", + "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", + "dev": true + }, + "@semantic-release/github": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.0.7.tgz", + "integrity": "sha512-Sai2UucYQ+5rJzKVEVJ4eiZNDdoo0/CzfpValBdeU5h97uJE7t4CoBTmUWkiXlPOx46CSw1+JhI+PHC1PUxVZw==", + "dev": true, + "requires": { + "@octokit/rest": "^17.0.0", + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^3.0.0", + "bottleneck": "^2.18.1", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "fs-extra": "^9.0.0", + "globby": "^11.0.0", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "issue-parser": "^6.0.0", + "lodash": "^4.17.4", + "mime": "^2.4.3", + "p-filter": "^2.0.0", + "p-retry": "^4.0.0", + "url-join": "^4.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + } + } + }, + "@semantic-release/npm": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.0.5.tgz", + "integrity": "sha512-D+oEmsx9aHE1q806NFQwSC9KdBO8ri/VO99eEz0wWbX2jyLqVyWr7t0IjKC8aSnkkQswg/4KN/ZjfF6iz1XOpw==", + "dev": true, + "requires": { + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^3.0.0", + "execa": "^4.0.0", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "nerf-dart": "^1.0.0", + "normalize-url": "^5.0.0", + "npm": "^6.10.3", + "rc": "^1.2.8", + "read-pkg": "^5.0.0", + "registry-auth-token": "^4.0.0", + "semver": "^7.1.2", + "tempy": "^0.5.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", + "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@semantic-release/release-notes-generator": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.1.tgz", + "integrity": "sha512-bOoTiH6SiiR0x2uywSNR7uZcRDl22IpZhj+Q5Bn0v+98MFtOMhCxFhbrKQjhbYoZw7vps1mvMRmFkp/g6R9cvQ==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^4.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.0", + "debug": "^4.0.0", + "get-stream": "^5.0.0", + "import-from": "^3.0.0", + "into-stream": "^5.0.0", + "lodash": "^4.17.4", + "read-pkg-up": "^7.0.0" + }, + "dependencies": { + "conventional-changelog-angular": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", + "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "@sinonjs/commons": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", + "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz", + "integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.8.tgz", + "integrity": "sha512-KXBiQG2OXvaPWFPDS1rD8yV9vO0OuWIqAEqLsbfX0oU2REN5KuoMnZ1gClWcBhO5I3n6oTVAmrMufOvRqdmFTQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.12.tgz", + "integrity": "sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/dateformat": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz", + "integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==", + "dev": true + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.0.tgz", + "integrity": "sha512-/yeMsH9HQ1RLORlXAwoLXe8S98xxvhNtUz3yrgrwbaxYjT+6SFPZZRksmRKRA6L5vsUtSHeN71viDOTTyYAD+g==", + "dev": true, + "requires": { + "jest-diff": "^25.2.1", + "pretty-format": "^25.2.1" + } + }, + "@types/lodash": { + "version": "4.14.155", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.155.tgz", + "integrity": "sha512-vEcX7S7aPhsBCivxMwAANQburHBtfN9RdyXFk84IJmu2Z4Hkg1tOFgaslRiEqqvoLtbCBi6ika1EMspE+NZ9Lg==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, + "@types/mkdirp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", + "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.0.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.13.tgz", + "integrity": "sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/prettier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.1.tgz", + "integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==", + "dev": true + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/sinon": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.2.tgz", + "integrity": "sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/yargs": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "dev": true + }, + "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.2.0", + "through": ">=2.2.7 <3" + } + }, + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "dev": true + }, + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-walk": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", + "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", + "dev": true + }, + "agent-base": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "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.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true + }, + "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": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "dev": true + }, + "babel-jest": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.0.1.tgz", + "integrity": "sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw==", + "dev": true, + "requires": { + "@jest/transform": "^26.0.1", + "@jest/types": "^26.0.1", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz", + "integrity": "sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz", + "integrity": "sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz", + "integrity": "sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^26.0.0", + "babel-preset-current-node-syntax": "^0.1.2" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "optional": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true, + "optional": 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 + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "before-after-hook": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz", + "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", + "dev": true + }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "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" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browserslist": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", + "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001043", + "electron-to-chromium": "^1.3.413", + "node-releases": "^1.1.53", + "pkg-up": "^2.0.0" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "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 + }, + "builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cachedir": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.2.0.tgz", + "integrity": "sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ==", + "dev": true + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "optional": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true, + "optional": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "optional": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30001081", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001081.tgz", + "integrity": "sha512-iZdh3lu09jsUtLE6Bp8NAbJskco4Y3UDtkR3GTCJGsbMowBU5IWDFF79sV2ws7lSqTzWyKazxam2thasHymENQ==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "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.3.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "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-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "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 + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commitizen": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.1.2.tgz", + "integrity": "sha512-LBxTQKHbVgroMz9ohpm86N+GfJobonGyvDc3zBGdZazbwCLz2tqLa48Rf2TnAdKx7/06W1i1R3SXUt5QW97qVQ==", + "dev": true, + "requires": { + "cachedir": "2.2.0", + "cz-conventional-changelog": "3.2.0", + "dedent": "0.7.0", + "detect-indent": "6.0.0", + "find-node-modules": "2.0.0", + "find-root": "1.1.0", + "fs-extra": "8.1.0", + "glob": "7.1.4", + "inquirer": "6.5.0", + "is-utf8": "^0.2.1", + "lodash": "4.17.15", + "minimist": "1.2.5", + "strip-bom": "4.0.0", + "strip-json-comments": "3.0.1" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + } + } + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-func": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.4.tgz", + "integrity": "sha512-sq2sWtrqKPkEXAC8tEJA1+BqAH9GbFkGBtUOqrUX57VSfwp8xyktctk+uLoRy5eccTdxzDcVIztlYDpKs3Jv1Q==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + } + }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "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 + }, + "conventional-changelog-angular": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", + "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "conventional-changelog-conventionalcommits": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.2.3.tgz", + "integrity": "sha512-atGa+R4vvEhb8N/8v3IoW59gCBJeeFiX6uIbPu876ENAmkMwsenyn0R21kdDHJFLQdy6zW4J6b4xN8KI3b9oww==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "lodash": "^4.17.15", + "q": "^1.5.1" + } + }, + "conventional-changelog-writer": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", + "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "conventional-commits-filter": "^2.0.6", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^7.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^3.0.0" + }, + "dependencies": { + "meow": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + } + } + } + }, + "conventional-commit-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", + "integrity": "sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==", + "dev": true + }, + "conventional-commits-filter": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.6.tgz", + "integrity": "sha512-4g+sw8+KA50/Qwzfr0hL5k5NWxqtrOVw4DDk3/h6L85a9Gz0/Eqp3oP+CWCNfesBvZZZEFHF7OTEbRe+yYSyKw==", + "dev": true, + "requires": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.1.0.tgz", + "integrity": "sha512-RSo5S0WIwXZiRxUGTPuYFbqvrR4vpJ1BDdTlthFgvHt5kEdnd1+pdvwWphWn57/oIl4V72NMmOocFqqJ8mFFhA==", + "dev": true, + "requires": { + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^7.0.0", + "split2": "^2.0.0", + "through2": "^3.0.0", + "trim-off-newlines": "^1.0.0" + }, + "dependencies": { + "meow": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + } + } + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true + }, + "core-js-compat": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "dev": true, + "requires": { + "browserslist": "^4.8.5", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "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 + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "optional": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "optional": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "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, + "optional": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true, + "optional": true + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "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.1" + } + }, + "cz-conventional-changelog": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.2.0.tgz", + "integrity": "sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg==", + "dev": true, + "requires": { + "@commitlint/load": ">6.1.1", + "chalk": "^2.4.1", + "commitizen": "^4.0.3", + "conventional-commit-types": "^3.0.0", + "lodash.map": "^4.5.1", + "longest": "^2.0.1", + "word-wrap": "^1.0.3" + } + }, + "dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "dateformat": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.2.tgz", + "integrity": "sha1-mk30v/FYrC80vGN6vbFUcWB+Flk=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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.1.0", + "map-obj": "^1.0.0" + }, + "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 + } + } + }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "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 + }, + "deep-freeze": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz", + "integrity": "sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "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.0" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "electron-to-chromium": { + "version": "1.3.467", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.467.tgz", + "integrity": "sha512-U+QgsL8TZDU/n+rDnYDa3hY5uy3C4iry9mrJS0PNBBGwnocuQ+aHSfgY44mdlaK9744X5YqrrGUvD9PxCLY1HA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-ci": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.2.tgz", + "integrity": "sha512-Xc41mKvjouTXD3Oy9AqySz1IeyvJvHZ20Twf5ZLYbNpPPIuCnL/qHCmNlD01LoNy0JTunw9HPYVptD19Ac7Mbw==", + "dev": true, + "requires": { + "execa": "^4.0.0", + "java-properties": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", + "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "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 + }, + "escodegen": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.2.tgz", + "integrity": "sha512-InuOIiKk8wwuOFg6x9BQXbzjrQhtyXh46K9bqVTPzSo2FnyMBaYGBMC6PhQy7yxxil9vIedFBweQBMK74/7o8A==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "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, + "optional": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "expect": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.0.1.tgz", + "integrity": "sha512-QcCy4nygHeqmbw564YxNbHTJlXh47dVID2BUP52cZFpLU9zHViMFK6h07cC1wf7GYCTIigTdAXhVua8Yl1FkKg==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.0.0", + "jest-matcher-utils": "^26.0.1", + "jest-message-util": "^26.0.1", + "jest-regex-util": "^26.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", + "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "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" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-node-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.0.0.tgz", + "integrity": "sha512-8MWIBRgJi/WpjjfVXumjPKCtmQ10B+fjx6zmSA+770GMJirLhWIzg8l763rhjl9xaeaHbnxPNRQKq2mgMhr+aw==", + "dev": true, + "requires": { + "findup-sync": "^3.0.0", + "merge": "^1.2.1" + } + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "requires": { + "semver-regex": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", + "dev": true, + "requires": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + }, + "dependencies": { + "split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", + "dev": true, + "requires": { + "through2": "~2.0.0" + } + }, + "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" + } + } + } + }, + "git-raw-commits": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.7.tgz", + "integrity": "sha512-SkwrTqrDxw8y0G1uGJ9Zw13F7qu3LF8V4BifyDeiJCxSnjRGZD9SaoMiMqUvvXMXh6S3sOQ1DsBN7L2fMUZW/g==", + "dev": true, + "requires": { + "dargs": "^7.0.0", + "lodash.template": "^4.0.2", + "meow": "^7.0.0", + "split2": "^2.0.0", + "through2": "^3.0.0" + }, + "dependencies": { + "meow": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + } + } + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true, + "optional": true + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "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 + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "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-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "highlight.js": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.0.3.tgz", + "integrity": "sha512-9FG7SSzv9yOY5CGGxfI6NDm7xLYtMOjKtPBxw7Zff3t5UcRcUNTGEeS8lNjhceL34KeetLMoGMFTGoaa83HwyQ==", + "dev": true + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hook-std": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", + "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "husky": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", + "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.6.0", + "cosmiconfig": "^6.0.0", + "find-versions": "^3.2.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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 < 3" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "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.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "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": "6.5.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "into-stream": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-5.1.1.tgz", + "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", + "dev": true, + "requires": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "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-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true, + "optional": true + }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "dev": true, + "optional": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "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-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-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "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-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, + "is-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.0.tgz", + "integrity": "sha512-ZVxq+5TkOx6GQdnoMm2aRdCKADdcrOWXLGzGT+vIA8DMpqEJaRk5AL1bS80zJ2bjHunVmjdzfCt0e4BymIEqKQ==", + "dev": true, + "requires": { + "@types/estree": "0.0.44" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.44", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.44.tgz", + "integrity": "sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==", + "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-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.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "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 + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "issue-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "dev": true, + "requires": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + } + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true + }, + "jest": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.0.1.tgz", + "integrity": "sha512-29Q54kn5Bm7ZGKIuH2JRmnKl85YRigp0o0asTc6Sb6l2ch1DCXIeZTLLFy9ultJvhkTqbswF5DEx4+RlkmCxWg==", + "dev": true, + "requires": { + "@jest/core": "^26.0.1", + "import-local": "^3.0.2", + "jest-cli": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-cli": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.0.1.tgz", + "integrity": "sha512-pFLfSOBcbG9iOZWaMK4Een+tTxi/Wcm34geqZEqrst9cZDkTQ1LZ2CnBrTlHWuYAiTMFr0EQeK52ScyFU8wK+w==", + "dev": true, + "requires": { + "@jest/core": "^26.0.1", + "@jest/test-result": "^26.0.1", + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.0.1", + "jest-util": "^26.0.1", + "jest-validate": "^26.0.1", + "prompts": "^2.0.1", + "yargs": "^15.3.1" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-changed-files": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.0.1.tgz", + "integrity": "sha512-q8LP9Sint17HaE2LjxQXL+oYWW/WeeXMPE2+Op9X3mY8IEGFVc14xRxFjUuXUbcPAlDLhtWdIEt59GdQbn76Hw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "execa": "^4.0.0", + "throat": "^5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", + "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "jest-config": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.0.1.tgz", + "integrity": "sha512-9mWKx2L1LFgOXlDsC4YSeavnblN6A4CPfXFiobq+YYLaBMymA/SczN7xYTSmLaEYHZOcB98UdoN4m5uNt6tztg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.0.1", + "@jest/types": "^26.0.1", + "babel-jest": "^26.0.1", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.0.1", + "jest-environment-node": "^26.0.1", + "jest-get-type": "^26.0.0", + "jest-jasmine2": "^26.0.1", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.0.1", + "jest-util": "^26.0.1", + "jest-validate": "^26.0.1", + "micromatch": "^4.0.2", + "pretty-format": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.0.1.tgz", + "integrity": "sha512-OTgJlwXCAR8NIWaXFL5DBbeS4QIYPuNASkzSwMCJO+ywo9BEa6TqkaSWsfR7VdbMLdgYJqSfQcIyjJCNwl5n4Q==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "jest-get-type": "^26.0.0", + "jest-util": "^26.0.1", + "pretty-format": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.0.1.tgz", + "integrity": "sha512-u88NJa3aptz2Xix2pFhihRBAatwZHWwSiRLBDBQE1cdJvDjPvv7ZGA0NQBxWwDDn7D0g1uHqxM8aGgfA9Bx49g==", + "dev": true, + "requires": { + "@jest/environment": "^26.0.1", + "@jest/fake-timers": "^26.0.1", + "@jest/types": "^26.0.1", + "jest-mock": "^26.0.1", + "jest-util": "^26.0.1", + "jsdom": "^16.2.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-node": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.0.1.tgz", + "integrity": "sha512-4FRBWcSn5yVo0KtNav7+5NH5Z/tEgDLp7VRQVS5tCouWORxj+nI+1tOLutM07Zb2Qi7ja+HEDoOUkjBSWZg/IQ==", + "dev": true, + "requires": { + "@jest/environment": "^26.0.1", + "@jest/fake-timers": "^26.0.1", + "@jest/types": "^26.0.1", + "jest-mock": "^26.0.1", + "jest-util": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true + }, + "jest-haste-map": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.0.1.tgz", + "integrity": "sha512-J9kBl/EdjmDsvyv7CiyKY5+DsTvVOScenprz/fGqfLg/pm1gdjbwwQ98nW0t+OIt+f+5nAVaElvn/6wP5KO7KA==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "@types/graceful-fs": "^4.1.2", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-serializer": "^26.0.0", + "jest-util": "^26.0.1", + "jest-worker": "^26.0.0", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7", + "which": "^2.0.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "jest-html-reporter": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/jest-html-reporter/-/jest-html-reporter-3.1.3.tgz", + "integrity": "sha512-OZFCCwH/N2X8nsCK11dKJy88DdziKwJOZvW4ZzczS+TP74bpGOxTFcJ876nShAMbiPbSQc9VOFuc1x+RejTSYQ==", + "dev": true, + "requires": { + "@babel/core": "^7.9.0", + "@babel/preset-env": "^7.8.7", + "@babel/preset-typescript": "^7.8.3", + "@jest/console": "^25.1.0", + "@jest/test-result": "^25.1.0", + "@jest/types": "^25.1.0", + "@types/dateformat": "^3.0.1", + "@types/jest": "^25.1.4", + "@types/mkdirp": "^1.0.0", + "@types/node": "^12.12.7", + "@types/sinon": "^7.5.2", + "dateformat": "3.0.2", + "mkdirp": "^1.0.3", + "sinon": "^9.0.1", + "strip-ansi": "6.0.0", + "xmlbuilder": "15.0.0" + }, + "dependencies": { + "@jest/console": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.5.0.tgz", + "integrity": "sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-message-util": "^25.5.0", + "jest-util": "^25.5.0", + "slash": "^3.0.0" + } + }, + "@jest/test-result": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.5.0.tgz", + "integrity": "sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/types": "^25.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@types/jest": { + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz", + "integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==", + "dev": true, + "requires": { + "jest-diff": "^25.2.1", + "pretty-format": "^25.2.1" + } + }, + "@types/node": { + "version": "12.12.47", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.47.tgz", + "integrity": "sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-message-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", + "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", + "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "make-dir": "^3.0.0" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-jasmine2": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.0.1.tgz", + "integrity": "sha512-ILaRyiWxiXOJ+RWTKupzQWwnPaeXPIoLS5uW41h18varJzd9/7I0QJGqg69fhTT1ev9JpSSo9QtalriUN0oqOg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.0.1", + "@jest/source-map": "^26.0.0", + "@jest/test-result": "^26.0.1", + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.0.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.0.1", + "jest-matcher-utils": "^26.0.1", + "jest-message-util": "^26.0.1", + "jest-runtime": "^26.0.1", + "jest-snapshot": "^26.0.1", + "jest-util": "^26.0.1", + "pretty-format": "^26.0.1", + "throat": "^5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-junit": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-11.0.1.tgz", + "integrity": "sha512-stgc0mBoiSg/F9qWd4KkmR3K7Nk2u+M/dc1oup7gxz9mrzGcEaU2YL9/0QscVqqg3IOaA1P5ZXtozG/XR6j6nw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^5.2.0", + "uuid": "^3.3.3", + "xml": "^1.0.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "jest-leak-detector": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.0.1.tgz", + "integrity": "sha512-93FR8tJhaYIWrWsbmVN1pQ9ZNlbgRpfvrnw5LmgLRX0ckOJ8ut/I35CL7awi2ecq6Ca4lL59bEK9hr7nqoHWPA==", + "dev": true, + "requires": { + "jest-get-type": "^26.0.0", + "pretty-format": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-matcher-utils": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.0.1.tgz", + "integrity": "sha512-PUMlsLth0Azen8Q2WFTwnSkGh2JZ8FYuwijC8NR47vXKpsrKmA1wWvgcj1CquuVfcYiDEdj985u5Wmg7COEARw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.0.1", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "diff-sequences": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", + "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-diff": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.0.1.tgz", + "integrity": "sha512-odTcHyl5X+U+QsczJmOjWw5tPvww+y9Yim5xzqxVl/R1j4z71+fHW4g8qu1ugMmKdFdxw+AtQgs5mupPnzcIBQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.0.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.0.1" + } + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.0.1.tgz", + "integrity": "sha512-CbK8uQREZ8umUfo8+zgIfEt+W7HAHjQCoRaNs4WxKGhAYBGwEyvxuK81FXa7VeB9pwDEXeeKOB2qcsNVCAvB7Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.0.1", + "@types/stack-utils": "^1.0.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-mock": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.0.1.tgz", + "integrity": "sha512-MpYTBqycuPYSY6xKJognV7Ja46/TeRbAZept987Zp+tuJvMN0YBWyyhG9mXyYQaU3SBI0TUlSaO5L3p49agw7Q==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true + }, + "jest-resolve": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.0.1.tgz", + "integrity": "sha512-6jWxk0IKZkPIVTvq6s72RH735P8f9eCJW3IM5CX/SJFeKq1p2cZx0U49wf/SdMlhaB/anann5J2nCJj6HrbezQ==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.1", + "jest-util": "^26.0.1", + "read-pkg-up": "^7.0.1", + "resolve": "^1.17.0", + "slash": "^3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.0.1.tgz", + "integrity": "sha512-9d5/RS/ft0vB/qy7jct/qAhzJsr6fRQJyGAFigK3XD4hf9kIbEH5gks4t4Z7kyMRhowU6HWm/o8ILqhaHdSqLw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runner": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.0.1.tgz", + "integrity": "sha512-CApm0g81b49Znm4cZekYQK67zY7kkB4umOlI2Dx5CwKAzdgw75EN+ozBHRvxBzwo1ZLYZ07TFxkaPm+1t4d8jA==", + "dev": true, + "requires": { + "@jest/console": "^26.0.1", + "@jest/environment": "^26.0.1", + "@jest/test-result": "^26.0.1", + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.0.1", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.0.1", + "jest-jasmine2": "^26.0.1", + "jest-leak-detector": "^26.0.1", + "jest-message-util": "^26.0.1", + "jest-resolve": "^26.0.1", + "jest-runtime": "^26.0.1", + "jest-util": "^26.0.1", + "jest-worker": "^26.0.0", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.0.1.tgz", + "integrity": "sha512-Ci2QhYFmANg5qaXWf78T2Pfo6GtmIBn2rRaLnklRyEucmPccmCKvS9JPljcmtVamsdMmkyNkVFb9pBTD6si9Lw==", + "dev": true, + "requires": { + "@jest/console": "^26.0.1", + "@jest/environment": "^26.0.1", + "@jest/fake-timers": "^26.0.1", + "@jest/globals": "^26.0.1", + "@jest/source-map": "^26.0.0", + "@jest/test-result": "^26.0.1", + "@jest/transform": "^26.0.1", + "@jest/types": "^26.0.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.0.1", + "jest-haste-map": "^26.0.1", + "jest-message-util": "^26.0.1", + "jest-mock": "^26.0.1", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.0.1", + "jest-snapshot": "^26.0.1", + "jest-util": "^26.0.1", + "jest-validate": "^26.0.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.3.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-serializer": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.0.0.tgz", + "integrity": "sha512-sQGXLdEGWFAE4wIJ2ZaIDb+ikETlUirEOBsLXdoBbeLhTHkZUJwgk3+M8eyFizhM6le43PDCCKPA1hzkSDo4cQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.0.1.tgz", + "integrity": "sha512-jxd+cF7+LL+a80qh6TAnTLUZHyQoWwEHSUFJjkw35u3Gx+BZUNuXhYvDqHXr62UQPnWo2P6fvQlLjsU93UKyxA==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.0.1", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.0.1", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.0.1", + "jest-get-type": "^26.0.0", + "jest-matcher-utils": "^26.0.1", + "jest-message-util": "^26.0.1", + "jest-resolve": "^26.0.1", + "make-dir": "^3.0.0", + "natural-compare": "^1.4.0", + "pretty-format": "^26.0.1", + "semver": "^7.3.2" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "diff-sequences": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", + "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-diff": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.0.1.tgz", + "integrity": "sha512-odTcHyl5X+U+QsczJmOjWw5tPvww+y9Yim5xzqxVl/R1j4z71+fHW4g8qu1ugMmKdFdxw+AtQgs5mupPnzcIBQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.0.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.0.1" + } + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.0.1.tgz", + "integrity": "sha512-byQ3n7ad1BO/WyFkYvlWQHTsomB6GIewBh8tlGtusiylAlaxQ1UpS0XYH0ngOyhZuHVLN79Qvl6/pMiDMSSG1g==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "make-dir": "^3.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.0.1.tgz", + "integrity": "sha512-u0xRc+rbmov/VqXnX3DlkxD74rHI/CfS5xaV2VpeaVySjbb1JioNVOyly5b56q2l9ZKe7bVG5qWmjfctkQb0bA==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.0.0", + "leven": "^3.1.0", + "pretty-format": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "dev": true + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.0.1.tgz", + "integrity": "sha512-pdZPydsS8475f89kGswaNsN3rhP6lnC3/QDCppP7bg1L9JQz7oU9Mb/5xPETk1RHDCWeqmVC47M4K5RR7ejxFw==", + "dev": true, + "requires": { + "@jest/test-result": "^26.0.1", + "@jest/types": "^26.0.1", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^26.0.1", + "string-length": "^4.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "jest-worker": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.0.0.tgz", + "integrity": "sha512-pPaYa2+JnwmiZjK9x7p9BoZht+47ecFCDFA/CJxspHzeDvQcfVBLWzCiWyo+EGrSiQMWZtCFo9iSvMZnAAo8vw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.2.tgz", + "integrity": "sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.0.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "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-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "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 + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "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.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-extend": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "dev": true, + "requires": { + "leven": "^3.1.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "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.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "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.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "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.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", + "dev": true + }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", + "dev": true + }, + "longest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-2.0.1.tgz", + "integrity": "sha1-eB4YMpaqlPbU2RbcM10NF676I/g=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.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.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "lunr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.8.tgz", + "integrity": "sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg==", + "dev": true + }, + "macos-release": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz", + "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==", + "dev": true + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.0.tgz", + "integrity": "sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==", + "dev": true + }, + "marked-terminal": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.0.tgz", + "integrity": "sha512-5KllfAOW02WS6hLRQ7cNvGOxvKW1BKuXELH4EtbWfyWgxQhROoMxEvuQ/3fTgkNjledR0J48F4HbapvYp1zWkQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.1", + "cardinal": "^2.1.1", + "chalk": "^4.0.0", + "cli-table": "^0.3.1", + "node-emoji": "^1.10.0", + "supports-hyperlinks": "^2.1.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "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.0.0" + } + }, + "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" + } + }, + "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 + }, + "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" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "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" + } + }, + "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.1.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 + }, + "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-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" + } + }, + "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 + }, + "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.3.2", + "path-type": "^3.0.0" + } + }, + "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.0.0", + "read-pkg": "^3.0.0" + } + }, + "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.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "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 + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.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 + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "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.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + } + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "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 + }, + "mri": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz", + "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multimatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nise": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz", + "integrity": "sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "dev": true, + "requires": { + "lodash.toarray": "^4.4.0" + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-7.0.1.tgz", + "integrity": "sha512-VkzhierE7DBmQEElhTGJIoiZa1oqRijOtgOlsXg32KrJRXsPy0NXFBqWGW/wTswnJlDCs5viRYaqWguqzsKcmg==", + "dev": true, + "optional": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^2.1.1", + "semver": "^7.2.1", + "shellwords": "^0.1.1", + "uuid": "^7.0.3", + "which": "^2.0.2" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true, + "optional": true + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "dev": true, + "optional": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "optional": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "node-releases": { + "version": "1.1.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", + "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==", + "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.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-5.0.0.tgz", + "integrity": "sha512-bAEm2fx8Dq/a35Z6PIRkkBBJvR56BbEJvhpNtvCZ4W9FyORSna77fn+xtYFjqk5JpBS+fMnAOG/wFgkQBmB7hw==", + "dev": true + }, + "npm": { + "version": "6.14.5", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.5.tgz", + "integrity": "sha512-CDwa3FJd0XJpKDbWCST484H+mCNjF26dPrU+xnREW+upR0UODjMEfXPl3bxWuAwZIX6c2ASg1plLO7jP8ehWeA==", + "dev": true, + "requires": { + "JSONStream": "^1.3.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "^2.0.0", + "archy": "~1.0.0", + "bin-links": "^1.1.7", + "bluebird": "^3.5.5", + "byte-size": "^5.0.1", + "cacache": "^12.0.3", + "call-limit": "^1.1.1", + "chownr": "^1.1.4", + "ci-info": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.1", + "cmd-shim": "^3.0.3", + "columnify": "~1.5.4", + "config-chain": "^1.1.12", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.3.0", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.8.8", + "iferr": "^1.0.2", + "imurmurhash": "*", + "infer-owner": "^1.0.4", + "inflight": "~1.0.6", + "inherits": "^2.0.4", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^3.0.0", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^4.0.7", + "libnpm": "^3.0.1", + "libnpmaccess": "^3.0.2", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "libnpx": "^10.2.2", + "lock-verify": "^2.1.0", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^5.1.1", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.5", + "move-concurrently": "^1.0.1", + "node-gyp": "^5.1.0", + "nopt": "^4.0.3", + "normalize-package-data": "^2.5.0", + "npm-audit-report": "^1.3.2", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "^3.0.2", + "npm-lifecycle": "^3.1.4", + "npm-package-arg": "^6.1.1", + "npm-packlist": "^1.4.8", + "npm-pick-manifest": "^3.0.2", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.4", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.1", + "osenv": "^0.1.5", + "pacote": "^9.5.12", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.8.2", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "^1.0.5", + "read-installed": "~4.0.3", + "read-package-json": "^2.1.1", + "read-package-tree": "^5.3.1", + "readable-stream": "^3.6.0", + "readdir-scoped-modules": "^1.1.0", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "^2.7.1", + "safe-buffer": "^5.1.2", + "semver": "^5.7.1", + "sha": "^3.0.0", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.1", + "stringify-package": "^1.0.1", + "tar": "^4.4.13", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "^1.1.1", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.3", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.7.0", + "write-file-atomic": "^2.4.3" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "agent-base": { + "version": "4.3.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "bundled": true, + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "asap": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true, + "dev": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.3.0", + "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "bluebird": { + "version": "3.5.5", + "bundled": true, + "dev": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "byline": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "byte-size": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "cacache": { + "version": "12.0.3", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cidr-regex": { + "version": "2.0.10", + "bundled": true, + "dev": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "cmd-shim": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "colors": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "dev": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "config-chain": { + "version": "1.1.12", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + } + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "err-code": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "bundled": true, + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "bundled": true, + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "figgy-pudding": { + "version": "3.5.1", + "bundled": true, + "dev": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.6.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "gentle-fs": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2", + "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "infer-owner": "^1.0.4", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "bundled": true, + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true, + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "ip": { + "version": "1.1.5", + "bundled": true, + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "is-ci": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^1.5.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "bundled": true, + "dev": true + } + } + }, + "is-cidr": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "cidr-regex": "^2.0.10" + } + }, + "is-date-object": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true, + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "libcipm": { + "version": "4.0.7", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpm": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" + } + }, + "libnpmaccess": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmconfig": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true, + "dev": true + } + } + }, + "libnpmhook": { + "version": "5.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmorg": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmpublish": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + } + }, + "libnpmsearch": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmteam": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpx": { + "version": "10.2.2", + "bundled": true, + "dev": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true, + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true, + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true, + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "5.0.2", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "4.3.0", + "bundled": true, + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "bundled": true, + "dev": true + } + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "bundled": true, + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true, + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + } + }, + "nopt": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "npm-audit-report": { + "version": "1.3.2", + "bundled": true, + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "npm-install-checks": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "3.1.4", + "bundled": true, + "dev": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "npm-package-arg": { + "version": "6.1.1", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.8", + "bundled": true, + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "4.0.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "4.0.4", + "bundled": true, + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-keys": { + "version": "1.0.12", + "bundled": true, + "dev": true + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "9.5.12", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true, + "dev": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true, + "dev": true + }, + "protoduck": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "psl": { + "version": "1.1.29", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "qs": { + "version": "6.5.2", + "bundled": true, + "dev": true + }, + "query-string": { + "version": "6.8.2", + "bundled": true, + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.3.1", + "bundled": true, + "dev": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "retry": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.7.1", + "bundled": true, + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "sha": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "smart-buffer": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "socks": { + "version": "2.3.3", + "bundled": true, + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "dev": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, + "dev": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "split-on-first": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } + } + }, + "stringify-package": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.13", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "through": { + "version": "2.3.8", + "bundled": true, + "dev": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "umask": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "util-promisify": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.3", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "bundled": true, + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.4.3", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.1.1", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "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.0" + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "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.0.0" + } + }, + "opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-name": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", + "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", + "dev": true, + "requires": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.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-each-series": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", + "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", + "dev": true + }, + "p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "requires": { + "p-map": "^2.0.0" + } + }, + "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-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true + }, + "p-retry": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.2.0.tgz", + "integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", + "dev": true, + "requires": { + "@types/retry": "^0.12.0", + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "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-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "dependencies": { + "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" + } + }, + "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" + } + }, + "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.1.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 + }, + "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 + } + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + }, + "dependencies": { + "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" + } + }, + "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" + } + }, + "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.1.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 + }, + "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 + } + } + }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "pretty-quick": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-2.0.1.tgz", + "integrity": "sha512-y7bJt77XadjUr+P1uKqZxFWLddvj3SKY6EU4BuQtMxmmEFSMpbN132pUWdSG1g1mtUfO0noBvn7wBf0BVeomHg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "execa": "^2.1.0", + "find-up": "^4.1.0", + "ignore": "^5.1.4", + "mri": "^1.1.4", + "multimatch": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", + "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^3.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", + "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prompts": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.4" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "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.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "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 + } + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, + "regenerate": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true, + "optional": true + }, + "regenerator-transform": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", + "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4", + "private": "^0.1.8" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "registry-auth-token": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", + "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "dev": true, + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "dev": true, + "requires": { + "global-dirs": "^0.1.1" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "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.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.18.0.tgz", + "integrity": "sha512-LhuQQp3WpnHo3HlKCRrdMXpB6jdLsGOoXXSfMjbv74s5VdV3WZhkYJT0Z6w/EH3UgPH+g/S9T4GJrKW/5iD8TA==", + "dev": true, + "requires": { + "fsevents": "~2.1.2" + } + }, + "rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, + "rollup-plugin-sourcemaps": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.2.tgz", + "integrity": "sha512-9AwTKg3yRykwzemfLt71ySe0LvrAci+bpsOL1LaTYFk5BX4HF6X7DQfpHa74ANfSja3hyjiQkXCR8goSOnW//Q==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.9", + "source-map-resolve": "^0.6.0" + }, + "dependencies": { + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + } + } + }, + "rollup-plugin-typescript2": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.1.tgz", + "integrity": "sha512-RJl77Bbj1EunAQDC3dK/O2HWuSUX3oJbRGzyLoS5o9W4Hs1Nix3Gavqj1Lzs5Y6Ff4H2xXfmZ1WWUQCYocSbzQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8", + "find-cache-dir": "^3.3.1", + "fs-extra": "8.1.0", + "resolve": "1.15.1", + "tslib": "1.11.2" + }, + "dependencies": { + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "tslib": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz", + "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==", + "dev": true + } + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "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 + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semantic-release": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.0.8.tgz", + "integrity": "sha512-9KcdidiJ4xchrJXxPdaDQVlybgX0xTeKyVjRySYk5u9GpjibXD7E5F8cB0BvFLMDmMyrkCwcem0kFiaLD2VNPg==", + "dev": true, + "requires": { + "@semantic-release/commit-analyzer": "^8.0.0", + "@semantic-release/error": "^2.2.0", + "@semantic-release/github": "^7.0.0", + "@semantic-release/npm": "^7.0.0", + "@semantic-release/release-notes-generator": "^9.0.0", + "aggregate-error": "^3.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.0.0", + "env-ci": "^5.0.0", + "execa": "^4.0.0", + "figures": "^3.0.0", + "find-versions": "^3.0.0", + "get-stream": "^5.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^2.0.0", + "hosted-git-info": "^3.0.0", + "lodash": "^4.17.15", + "marked": "^1.0.0", + "marked-terminal": "^4.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "p-reduce": "^2.0.0", + "read-pkg-up": "^7.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^3.1.1", + "signale": "^1.2.1", + "yargs": "^15.0.1" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", + "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "hosted-git-info": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", + "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + } + }, + "semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "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 + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "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 + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + } + }, + "sinon": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz", + "integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.2", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.0.3", + "diff": "^4.0.2", + "nise": "^4.0.1", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "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" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "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.2" + }, + "dependencies": { + "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" + } + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "string-length": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", + "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "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.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "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-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "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" + } + }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true + }, + "tempy": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.5.0.tgz", + "integrity": "sha512-VEY96x7gbIRfsxqsafy2l5yVxxp3PhwAGoWMyC2D2Zt5DmEv+2tGiPOrquNRpf21hhGnKLVEsuqleqiZmKG/qw==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.12.0", + "unique-string": "^2.0.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "dev": true + } + } + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "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 + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "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": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + }, + "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" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "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 + }, + "ts-jest": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.0.tgz", + "integrity": "sha512-JbhQdyDMYN5nfKXaAwCIyaWLGwevcT2/dbqRPsQeh6NZPUuXjZQZEfeLb75tz0ubCIgEELNm6xAzTe5NXs5Y4Q==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "json5": "2.x", + "lodash.memoize": "4.x", + "make-error": "1.x", + "micromatch": "4.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "18.x" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tslint": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.2.tgz", + "integrity": "sha512-UyNrLdK3E0fQG/xWNqAFAC5ugtFyPO4JJR1KyyfQAyzR8W0fTRrC91A8Wej4BntFzcvETdCSDa/4PnNYJQLYiA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.10.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "tslint-config-prettier": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", + "dev": true + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typedoc": { + "version": "0.17.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.17.7.tgz", + "integrity": "sha512-PEnzjwQAGjb0O8a6VDE0lxyLAadqNujN5LltsTUhZETolRMiIJv6Ox+Toa8h0XhKHqAOh8MOmB0eBVcWz6nuAw==", + "dev": true, + "requires": { + "fs-extra": "^8.1.0", + "handlebars": "^4.7.6", + "highlight.js": "^10.0.0", + "lodash": "^4.17.15", + "lunr": "^2.3.8", + "marked": "1.0.0", + "minimatch": "^3.0.0", + "progress": "^2.0.3", + "shelljs": "^0.8.4", + "typedoc-default-themes": "^0.10.1" + }, + "dependencies": { + "marked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", + "dev": true + } + } + }, + "typedoc-default-themes": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.10.1.tgz", + "integrity": "sha512-SuqAQI0CkwhqSJ2kaVTgl37cWs733uy9UGUqwtcds8pkFK8oRF4rZmCq+FXTGIb9hIUOu40rf5Kojg0Ha6akeg==", + "dev": true, + "requires": { + "lunr": "^2.3.8" + } + }, + "typescript": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", + "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "dev": true + }, + "uglify-js": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.4.tgz", + "integrity": "sha512-8RZBJq5smLOa7KslsNsVcSH+KOXf1uDU8yqLeNuVKwmT0T3FA0ZoXlinQfRad7SDcbZZRZE4ov+2v71EnxNyCA==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.3" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "universal-user-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "dev": true, + "requires": { + "os-name": "^3.1.0" + } + }, + "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 + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "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": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "v8-to-istanbul": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz", + "integrity": "sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "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.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.1.0.tgz", + "integrity": "sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "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 + }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "dev": true + }, + "windows-release": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.1.tgz", + "integrity": "sha512-Pngk/RDCaI/DkuHPlGTdIkDiTAnAkyMjoQMZqRsxydNl1qGXNIoZrB7RK8g53F2tEgQBMqQJHQdYZuQEEAu54A==", + "dev": true, + "requires": { + "execa": "^1.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "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": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", + "dev": true + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlbuilder": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.0.0.tgz", + "integrity": "sha512-KLu/G0DoWhkncQ9eHSI6s0/w+T4TM7rQaLhtCaL6tORv8jFlJPlnGumsgTcGfYeS1qZ/IHqrvDG7zJZ4d7e+nw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + }, + "yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..2c4a1704 --- /dev/null +++ b/package.json @@ -0,0 +1,131 @@ +{ + "name": "@hypertrace/hyperdash", + "version": "0.0.0-PLACEHOLDER", + "license": "AGPL-3.0", + "main": "dist/hyperdash.umd.js", + "module": "dist/hyperdash.es5.js", + "typings": "dist/types/hyperdash.d.ts", + "files": [ + "dist" + ], + "scripts": { + "lint": "npm run lint:src && npm run lint:test", + "lint:src": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' -e 'src/**/*.{test,spec}.ts'", + "lint:test": "tslint -c tslint.spec.json --project tsconfig.json -t codeFrame 'test/**/*.ts' 'src/**/*.{test,spec}.ts'", + "prebuild": "rimraf dist", + "build": "tsc --noEmit && rollup -c rollup.config.ts", + "start": "rollup -c rollup.config.ts -w", + "test": "jest", + "test:watch": "jest --watch", + "test:prod": "npm run lint && npm run test -- --coverage --no-cache -i", + "commit": "git-cz", + "semantic-release": "semantic-release", + "upload-coverage": "codecov" + }, + "config": { + "commitizen": { + "path": "node_modules/cz-conventional-changelog" + } + }, + "jest": { + "transform": { + ".(ts|tsx)": "ts-jest" + }, + "testEnvironment": "node", + "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts)$", + "moduleFileExtensions": [ + "ts", + "js" + ], + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/test/" + ], + "coverageThreshold": { + "global": { + "branches": 100, + "functions": 100, + "lines": 100, + "statements": 100 + } + }, + "collectCoverage": false, + "collectCoverageFrom": [ + "src/**/*.ts", + "!src/hyperdash.ts" + ], + "reporters": [ + "default", + [ + "jest-junit", + { + "outputDirectory": "test-results" + } + ], + [ + "jest-html-reporter", + { + "outputPath": "test-results/test-report.html" + } + ] + ] + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "husky": { + "hooks": { + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", + "pre-push": "npm run test:prod", + "pre-commit": "pretty-quick --staged" + } + }, + "devDependencies": { + "@commitlint/cli": "^9.0.1", + "@commitlint/config-conventional": "^9.0.1", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^8.0.1", + "@rollup/plugin-typescript": "^4.1.2", + "@types/jest": "^26.0.0", + "@types/lodash": "^4.14.155", + "@types/node": "^14.0.13", + "commitizen": "^4.1.2", + "core-js": "3.6.5", + "cz-conventional-changelog": "^3.2.0", + "husky": "^4.2.5", + "jest": "^26.0.1", + "jest-config": "^26.0.1", + "jest-html-reporter": "^3.1.3", + "jest-junit": "^11.0.1", + "prettier": "^2.0.5", + "pretty-quick": "^2.0.1", + "rimraf": "^3.0.2", + "rollup": "^2.18.0", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-sourcemaps": "^0.6.2", + "rollup-plugin-typescript2": "^0.27.1", + "rxjs": "6.5.5", + "semantic-release": "17.0.8", + "ts-jest": "^26.1.0", + "tslint": "^6.1.2", + "tslint-config-prettier": "^1.18.0", + "typedoc": "^0.17.7", + "typescript": "^3.9.5" + }, + "dependencies": { + "lodash": "^4.17.15" + }, + "peerDependencies": { + "rxjs": "^6.5.5", + "core-js": "^3.6.5" + }, + "repository": { + "type": "git", + "url": "git@github.com:hypertrace/hyperdash.git" + }, + "publishConfig": { + "registry": "https://api.bintray.com/npm/hypertrace/npm" + } +} diff --git a/rollup.config.ts b/rollup.config.ts new file mode 100644 index 00000000..149039f5 --- /dev/null +++ b/rollup.config.ts @@ -0,0 +1,52 @@ +// tslint:disable +import { camelCase } from 'lodash'; +import commonjs from 'rollup-plugin-commonjs'; +import json from '@rollup/plugin-json'; +import resolve from '@rollup/plugin-node-resolve'; +import sourceMaps from 'rollup-plugin-sourcemaps'; +import typescript from 'rollup-plugin-typescript2'; + +const pkg = require('./package.json'); + +const libraryName = 'hyperdash'; + +export default { + input: `src/${libraryName}.ts`, + output: [ + { + file: pkg.main, + name: camelCase(libraryName), + format: 'umd', + sourcemap: true, + globals: { + lodash: '_', + rxjs: 'rxjs', + 'rxjs/operators': 'rxjs.operators' + } + }, + { file: pkg.module, format: 'es', sourcemap: true } + ], + // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') + external: ['lodash', 'rxjs', 'rxjs/operators', 'core-js/es7/reflect'], + watch: { + include: 'src/**' + }, + plugins: [ + // Allow json resolution + json(), + // Compile TypeScript files + typescript({ + useTsconfigDeclarationDir: true, + exclude: ['*.d.ts', '**/*.d.ts', '**/test/**', '**/*.test.ts'] + }), + // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) + commonjs(), + // Allow node_modules resolution, so you can use 'external' to control + // Which external modules to include in the bundle + // Https://github.com/rollup/rollup-plugin-node-resolve#usage + resolve(), + + // Resolve source maps to the original source + sourceMaps() + ] +}; diff --git a/src/communication/dashboard-event-manager.test.ts b/src/communication/dashboard-event-manager.test.ts new file mode 100644 index 00000000..79b34bca --- /dev/null +++ b/src/communication/dashboard-event-manager.test.ts @@ -0,0 +1,54 @@ +import { ReplaySubject } from 'rxjs'; +import { getTestScheduler } from '../test/rxjs-jest-test-scheduler'; +import { DashboardEventManager } from './dashboard-event-manager'; + +describe('Dashboard event manager', () => { + let manager: DashboardEventManager; + const basicEvent = {}; + let replay: ReplaySubject; + + beforeEach(() => { + manager = new DashboardEventManager(); + replay = new ReplaySubject(); + manager.getObservableForEvent(basicEvent).subscribe(replay); + }); + + test('supports publishing to a new subject, then subscribing', () => { + getTestScheduler().run(({ expectObservable }) => { + // Use a different event def for this test, since we want to publish before the subscription starts + const otherEventDef = {}; + + // Pub/sub is hot, this should be ignored + manager.publishEvent(otherEventDef, 'before subscribe'); + + manager.getObservableForEvent(otherEventDef).subscribe(replay); + + manager.publishEvent(otherEventDef, 'after 1'); + manager.publishEvent(otherEventDef, 'after 2'); + + expectObservable(replay).toBe('(ab)', { a: 'after 1', b: 'after 2' }); + }); + }); + + test('supports subscribing to a new subject, then publishing', () => { + getTestScheduler().run(({ expectObservable }) => { + manager.publishEvent(basicEvent, 'after'); + + expectObservable(replay).toBe('a', { a: 'after' }); + }); + }); + + test('only subscribed to events are notified', () => { + getTestScheduler().run(({ expectObservable }) => { + const secondEvent = {}; + const secondReplay = new ReplaySubject(); + manager.getObservableForEvent(secondEvent).subscribe(secondReplay); + + manager.publishEvent(basicEvent, 'basic'); + manager.publishEvent(secondEvent, 'second'); + + expectObservable(replay).toBe('a', { a: 'basic' }); + expectObservable(secondReplay).toBe('a', { a: 'second' }); + }); + }); +}); diff --git a/src/communication/dashboard-event-manager.ts b/src/communication/dashboard-event-manager.ts new file mode 100644 index 00000000..7b0dff8f --- /dev/null +++ b/src/communication/dashboard-event-manager.ts @@ -0,0 +1,49 @@ +import { Observable, Subject } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; + +/** + * Orchestrates publishing and subscribing to events throughout the dashboarding system, + * provides weakly typed APIs for publishing and subscribing. Using these APIs directly is discouraged, + * instead use typed APIs provided by specific events. + */ +export class DashboardEventManager { + private readonly eventSubject: Subject = new Subject(); + + /** + * Returns an `Observable` notifying when the provided eventKey is published to. This must be + * manually disposed of, as events are an infinite stream and thus never terminate on their own. + */ + public getObservableForEvent(eventKey: DashboardEventKey): Observable { + return this.eventSubject.pipe( + filter(keyedEvent => keyedEvent.key === eventKey), + map(keyedEvent => keyedEvent.value as T) + ); + } + + /** + * Publishes the provided value to the provided eventKey. Any registered subscribers will + * be notified. + */ + public publishEvent(eventKey: DashboardEventKey, value: T): void { + this.eventSubject.next({ + key: eventKey, + value: value + }); + } +} + +/** + * An event key for a routeable dashboard event. This is used to publish and subscribe + */ +export type DashboardEventKey = object | symbol; + +interface KeyedDashboardEvent { + /** + * Dashboard event key + */ + key: DashboardEventKey; + /** + * Value of event + */ + value: unknown; +} diff --git a/src/communication/dashboard-event.ts b/src/communication/dashboard-event.ts new file mode 100644 index 00000000..06fae4de --- /dev/null +++ b/src/communication/dashboard-event.ts @@ -0,0 +1,40 @@ +import { Observable } from 'rxjs'; +import { DashboardEventKey, DashboardEventManager } from './dashboard-event-manager'; + +/** + * Dashboard event which supports observing and publishing itself. By default, the constructed instance is the key. + * This can be changed at construction by providing an optional second argument. + */ +export class DashboardEvent { + private readonly eventKey: DashboardEventKey = this; + + public constructor( + protected readonly dashboardEventManager: DashboardEventManager, + eventKeyToUse?: DashboardEventKey + ) { + if (eventKeyToUse) { + this.eventKey = eventKeyToUse; + } + } + + /** + * Gets an observable for this event which will be notified when anyone publishes to it + */ + public getObservable(): Observable { + return this.dashboardEventManager.getObservableForEvent(this.getKey()); + } + + /** + * Publishes `data` to this event + */ + public publish(data: TData): void { + this.dashboardEventManager.publishEvent(this.getKey(), data); + } + + /** + * Returns the event key being used for this event + */ + public getKey(): DashboardEventKey { + return this.eventKey; + } +} diff --git a/src/communication/model-scoped-dashboard-event.test.ts b/src/communication/model-scoped-dashboard-event.test.ts new file mode 100644 index 00000000..81f656e1 --- /dev/null +++ b/src/communication/model-scoped-dashboard-event.test.ts @@ -0,0 +1,49 @@ +import { EMPTY, Observable } from 'rxjs'; +import { modelDestroyedEventKey } from '../model/events/model-destroyed-event'; +import { PartialObjectMock } from '../test/partial-object-mock'; +import { getTestScheduler } from '../test/rxjs-jest-test-scheduler'; +import { DashboardEventManager } from './dashboard-event-manager'; +import { ModelScopedDashboardEvent, ModelScopedData } from './model-scoped-dashboard-event'; + +describe('Model scoped dashboard event', () => { + let mockDashboardEventManager: PartialObjectMock; + let mockModel: object; + let testDestroyObservable: Observable; + let testEventObservable: Observable>; + let modelScopedDashboardEvent: ModelScopedDashboardEvent; + + beforeEach(() => { + mockModel = {}; + testEventObservable = EMPTY; + testDestroyObservable = EMPTY; + + mockDashboardEventManager = { + publishEvent: jest.fn(), + getObservableForEvent: jest.fn(eventKey => { + if (eventKey === modelDestroyedEventKey) { + return testDestroyObservable; + } + + return testEventObservable; + }) + }; + + modelScopedDashboardEvent = new ModelScopedDashboardEvent(mockDashboardEventManager as DashboardEventManager); + }); + + test('provides observable for data scoped to a model, completing on model destruction', () => { + getTestScheduler().run(({ cold, expectObservable }) => { + testDestroyObservable = cold('---a', { a: mockModel }); + testEventObservable = cold('abc', { + a: { source: mockModel, data: 'a value' }, + b: { source: mockModel, data: 'b value' }, + c: { source: {}, data: 'c value' } + }); + + expectObservable(modelScopedDashboardEvent.getObservableForModel(mockModel)).toBe('ab-|', { + a: 'a value', + b: 'b value' + }); + }); + }); +}); diff --git a/src/communication/model-scoped-dashboard-event.ts b/src/communication/model-scoped-dashboard-event.ts new file mode 100644 index 00000000..e4d5e8e4 --- /dev/null +++ b/src/communication/model-scoped-dashboard-event.ts @@ -0,0 +1,55 @@ +import { Observable } from 'rxjs'; +import { filter, map, takeUntil } from 'rxjs/operators'; +import { modelDestroyedEventKey } from '../model/events/model-destroyed-event'; +import { DashboardEvent } from './dashboard-event'; +import { DashboardEventKey, DashboardEventManager } from './dashboard-event-manager'; + +/** + * An event which is sourced from and scoped to a model. By default, the scope is the model + * that generated the event. + * + * Decorated model event hooks will wrap and unwrap events of this type, respecting the provided scope. + */ +export class ModelScopedDashboardEvent extends DashboardEvent> { + public constructor(dashboardEventManager: DashboardEventManager, eventKeyToUse?: DashboardEventKey) { + super(dashboardEventManager, eventKeyToUse); + } + + /** + * Returns an observable that extracts the data from any events originating from a model + * that satisfies the `modelShouldReceiveEvent` predicate + */ + public getObservableForModel(model: object): Observable { + return this.getObservable().pipe( + filter(value => this.modelShouldReceiveEvent(model, value.source)), + map(value => value.data), + takeUntil( + this.dashboardEventManager + .getObservableForEvent(modelDestroyedEventKey) + .pipe(filter(destroyedModel => destroyedModel === model)) + ) + ); + } + + /** + * Returns true if an event originating from `eventSourceModel` should be propagated to a listener + * from `listenerModel` + */ + protected modelShouldReceiveEvent(listenerModel: object, eventSourceModel: object): boolean { + return listenerModel === eventSourceModel; + } +} + +/** + * An event occuring in the context of a model + */ +export interface ModelScopedData { + /** + * The model where the event ocurred + */ + source: object; + /** + * Any data for this event + */ + data: TData; +} diff --git a/src/dashboard/dashboard-manager.test.ts b/src/dashboard/dashboard-manager.test.ts new file mode 100644 index 00000000..eaa2869a --- /dev/null +++ b/src/dashboard/dashboard-manager.test.ts @@ -0,0 +1,178 @@ +// tslint:disable: completed-docs +import { EMPTY, Observable } from 'rxjs'; +import { DataSource, dataSourceMarker } from '../data/data-source/data-source'; +import { DataRefreshEvent } from '../data/data-source/events/data-refresh-event'; +import { DataSourceManager } from '../data/data-source/manager/data-source-manager'; +import { TimeRangeManager } from '../data/time-range/manager/time-range-manager'; +import { ModelManager } from '../model/manager/model-manager'; +import { DeserializationManager } from '../persistence/deserialization/deserialization-manager'; +import { SerializationManager } from '../persistence/serialization/serialization-manager'; +import { PartialObjectMock } from '../test/partial-object-mock'; +import { VariableManager } from '../variable/manager/variable-manager'; +import { DashboardManager } from './dashboard-manager'; + +describe('Dashboard manager', () => { + let dashboardManager: DashboardManager; + let mockDeserializationManager: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let mockVariableManager: PartialObjectMock; + let mockSerializationManager: PartialObjectMock; + let mockDataSourceManager: PartialObjectMock; + let mockRefreshEvent: PartialObjectMock; + let mockTimeRangeManager: PartialObjectMock; + let mockRoot: object; + + beforeEach(() => { + mockRoot = {}; + + mockDeserializationManager = { + deserialize: jest.fn().mockReturnValue(mockRoot) + }; + mockModelManager = { + destroy: jest.fn(), + create: jest.fn() + }; + mockVariableManager = { + set: jest.fn() + }; + mockDataSourceManager = { + setRootDataSource: jest.fn(), + getRootDataSource: jest.fn() + }; + + mockSerializationManager = {}; + + mockRefreshEvent = { + publishRefresh: jest.fn() + }; + + mockTimeRangeManager = { + setRootTimeRange: jest.fn() + }; + + dashboardManager = new DashboardManager( + mockDeserializationManager as DeserializationManager, + mockModelManager as ModelManager, + mockVariableManager as VariableManager, + mockSerializationManager as SerializationManager, + mockDataSourceManager as DataSourceManager, + mockRefreshEvent as DataRefreshEvent, + mockTimeRangeManager as TimeRangeManager + ); + }); + + test('can create dashboards', () => { + dashboardManager.create({ + type: 'serialized-model' + }); + + expect(mockDeserializationManager.deserialize).toHaveBeenCalledWith({ + type: 'serialized-model' + }); + }); + + test('can destroy dashboards', () => { + const dashboard = dashboardManager.create({ + type: 'serialized-model' + }); + + dashboard.destroy(); + expect(mockModelManager.destroy).toHaveBeenCalledWith(dashboard.root); + }); + + test('can set variables in a dashboard', () => { + const dashboard = dashboardManager.create({ + type: 'serialized-model' + }); + + dashboard.setVariable('key', 'value'); + + expect(mockVariableManager.set).toHaveBeenCalledWith('key', 'value', dashboard.root); + }); + + test('can serialize dashboards', () => { + const serializedResult = {}; + mockSerializationManager.serialize = jest.fn().mockReturnValue(serializedResult); + const dashboard = dashboardManager.create({ + type: 'serialized-model' + }); + + expect(dashboard.serialize()).toBe(serializedResult); + + expect(mockSerializationManager.serialize).toHaveBeenCalledWith(dashboard.root); + }); + + test('can create dashboards and set a root data source by class', () => { + const deserializedRoot = {}; + mockDeserializationManager.deserialize = jest.fn().mockReturnValue(deserializedRoot); + + const testDataSourceClass = class implements DataSource { + public dataSourceMarker: typeof dataSourceMarker = dataSourceMarker; + public getData(): Observable { + return EMPTY; + } + }; + mockModelManager.create = jest.fn(() => new testDataSourceClass()); + + dashboardManager.create({ type: 'serialized-model' }).createAndSetRootDataFromModelClass(testDataSourceClass); + + expect(mockModelManager.create).toHaveBeenCalledWith(testDataSourceClass, deserializedRoot); + + expect(mockDataSourceManager.setRootDataSource).toHaveBeenCalledWith( + expect.any(testDataSourceClass), + deserializedRoot + ); + }); + + test('can publish refresh events', () => { + const dashboard = dashboardManager.create({ + type: 'serialized-model' + }); + + dashboard.refresh(); + + expect(mockRefreshEvent.publishRefresh).toHaveBeenCalledWith(dashboard.root); + }); + + test('can change root time range', () => { + const dashboard = dashboardManager.create({ + type: 'serialized-model' + }); + + const timeRange = { startTime: new Date(), endTime: new Date() }; + + dashboard.setTimeRange(timeRange); + + expect(mockTimeRangeManager.setRootTimeRange).toHaveBeenCalledWith(dashboard.root, timeRange); + }); + + test('can retrieve root data source', () => { + const dashboard = dashboardManager.create({ + type: 'serialized-model' + }); + + const mockDataSource = {}; + + mockDataSourceManager.getRootDataSource = jest.fn().mockReturnValue(mockDataSource); + + expect(dashboard.getRootDataSource()).toBe(mockDataSource); + + expect(mockDataSourceManager.getRootDataSource).toHaveBeenCalledWith(dashboard.root); + }); + + test('destroys previous root data source when assigning a new one', () => { + const dashboard = dashboardManager.create({ + type: 'serialized-model' + }); + + const mockOriginalDataSource = {}; + const mockNewDataSource = {}; + + mockDataSourceManager.getRootDataSource = jest.fn().mockReturnValue(mockOriginalDataSource); + + dashboard.setRootDataSource(mockNewDataSource as DataSource); + + expect(mockModelManager.destroy).toHaveBeenCalledWith(mockOriginalDataSource); + expect(mockDataSourceManager.setRootDataSource).toHaveBeenCalledWith(mockNewDataSource, dashboard.root); + }); +}); diff --git a/src/dashboard/dashboard-manager.ts b/src/dashboard/dashboard-manager.ts new file mode 100644 index 00000000..7d012715 --- /dev/null +++ b/src/dashboard/dashboard-manager.ts @@ -0,0 +1,43 @@ +import { DataRefreshEvent } from '../data/data-source/events/data-refresh-event'; +import { DataSourceManager } from '../data/data-source/manager/data-source-manager'; +import { TimeRangeManager } from '../data/time-range/manager/time-range-manager'; +import { ModelManager } from '../model/manager/model-manager'; +import { DeserializationManager } from '../persistence/deserialization/deserialization-manager'; +import { ModelJson } from '../persistence/model-json'; +import { SerializationManager } from '../persistence/serialization/serialization-manager'; +import { VariableManager } from '../variable/manager/variable-manager'; +import { Dashboard } from './dashboard'; +import { DefaultDashboard } from './default-dashboard'; + +/** + * External API for managing dashboards + */ +export class DashboardManager { + public constructor( + private readonly deserializationManager: DeserializationManager, + private readonly modelManager: ModelManager, + private readonly variableManager: VariableManager, + private readonly serializationManager: SerializationManager, + private readonly dataSourceManager: DataSourceManager, + private readonly dataRefreshEvent: DataRefreshEvent, + private readonly timeRangeManager: TimeRangeManager + ) {} + + /** + * Transforms the provided JSON into an instantiated dashboard that can be rendered + */ + // tslint:disable-next-line: no-any + public create(json: ModelJson): Dashboard { + const root = this.deserializationManager.deserialize(json); + + return new DefaultDashboard( + root, + this.variableManager, + this.timeRangeManager, + this.serializationManager, + this.modelManager, + this.dataRefreshEvent, + this.dataSourceManager + ); + } +} diff --git a/src/dashboard/dashboard.ts b/src/dashboard/dashboard.ts new file mode 100644 index 00000000..534fcf00 --- /dev/null +++ b/src/dashboard/dashboard.ts @@ -0,0 +1,59 @@ +import { DataSource } from '../data/data-source/data-source'; +import { TimeRange } from '../data/time-range/time-range'; +import { Constructable } from '../util/constructable'; + +/** + * Represents an instantiated dashboard + */ +// tslint:disable-next-line: no-any want any data source to be assignable here, caller should be more specific +export interface Dashboard { + /** + * The root model of the dashboard + */ + readonly root: TRoot; + + /** + * Sets a variable scoped to the entire dashboard (the root) + */ + setVariable(variableName: string, value: unknown): this; + + /** + * Sets root time range for entire dashboard + */ + setTimeRange(timeRange: TimeRange): this; + + /** + * Returns a serialized from of this dashboard. Note this does not + * affect the dashboard in any way, it must be explicitly destroyed + * if it is no longer in use. + */ + serialize(): object; + + /** + * Destroys the dashboard, removing internal references from memory + */ + destroy(): void; + + /** + * Refreshes the dashboard, broadcasting an event indicating data should be refetched + */ + refresh(): this; + + /** + * Sets the root data source of the dashboard + */ + setRootDataSource(dataSource: DataSource): this; + + /** + * Creates the provided model class and sets it as the root data source of the dashboard. The + * model will be attached to the model tree as a child of root, and will be destroyed when + * the root is destroyed. + */ + createAndSetRootDataFromModelClass(dataSourceModelClass: Constructable>): this; + + /** + * Returns the root data source of the dashboard, if set + */ + // tslint:disable-next-line: no-any + getRootDataSource>(): T | undefined; +} diff --git a/src/dashboard/default-dashboard.ts b/src/dashboard/default-dashboard.ts new file mode 100644 index 00000000..6aed41b7 --- /dev/null +++ b/src/dashboard/default-dashboard.ts @@ -0,0 +1,89 @@ +import { DataSource } from '../data/data-source/data-source'; +import { DataRefreshEvent } from '../data/data-source/events/data-refresh-event'; +import { DataSourceManager } from '../data/data-source/manager/data-source-manager'; +import { TimeRangeManager } from '../data/time-range/manager/time-range-manager'; +import { TimeRange } from '../data/time-range/time-range'; +import { ModelManager } from '../model/manager/model-manager'; +import { SerializationManager } from '../persistence/serialization/serialization-manager'; +import { Constructable } from '../util/constructable'; +import { VariableManager } from '../variable/manager/variable-manager'; +import { Dashboard } from './dashboard'; + +// tslint:disable-next-line: completed-docs +export class DefaultDashboard implements Dashboard { + public constructor( + public readonly root: TRoot, + private readonly variableManager: VariableManager, + private readonly timeRangeManager: TimeRangeManager, + private readonly serializationManager: SerializationManager, + private readonly modelManager: ModelManager, + private readonly dataRefreshEvent: DataRefreshEvent, + private readonly dataSourceManager: DataSourceManager + ) {} + + /** + * @inheritdoc + */ + public setVariable(variableName: string, value: unknown): this { + this.variableManager.set(variableName, value, this.root); + + return this; + } + + /** + * @inheritdoc + */ + public setTimeRange(timeRange: TimeRange): this { + this.timeRangeManager.setRootTimeRange(this.root, timeRange); + + return this; + } + + /** + * @inheritdoc + */ + public serialize(): object { + return this.serializationManager.serialize(this.root); + } + + /** + * @inheritdoc + */ + public destroy(): void { + this.modelManager.destroy(this.root); + } + + /** + * @inheritdoc + */ + public refresh(): this { + this.dataRefreshEvent.publishRefresh(this.root); + + return this; + } + + /** + * @inheritdoc + */ + public setRootDataSource(rootDataSource: DataSource): this { + this.modelManager.destroy(this.getRootDataSource()); + this.dataSourceManager.setRootDataSource(rootDataSource, this.root); + + return this; + } + + /** + * @inheritdoc + */ + public createAndSetRootDataFromModelClass(dataSourceModelClass: Constructable>): this { + return this.setRootDataSource(this.modelManager.create(dataSourceModelClass, this.root)); + } + + /** + * @inheritdoc + */ + // tslint:disable-next-line: no-any + public getRootDataSource>(): T | undefined { + return this.dataSourceManager.getRootDataSource(this.root) as T | undefined; + } +} diff --git a/src/data/data-source/data-source.ts b/src/data/data-source/data-source.ts new file mode 100644 index 00000000..6f518967 --- /dev/null +++ b/src/data/data-source/data-source.ts @@ -0,0 +1,30 @@ +import { Observable } from 'rxjs'; +import { ModelJson } from '../../persistence/model-json'; + +export const dataSourceMarker = Symbol('Data Source Marker'); + +/** + * A model which can be used to retrieve data asynchronously + */ +export interface DataSource { + /** + * Retrieves data of type T in the form of an observable + */ + getData(): Observable; + + /** + * A marker property used to determine if the implementing class is + * actually a data source (since the interface itself is not reified) + */ + readonly dataSourceMarker: typeof dataSourceMarker; +} + +/** + * A fragment of serialized JSON representing a model with an attached data source + */ +export interface ModelJsonWithData extends ModelJson { + /** + * A fragment of serialized JSON representing a data source + */ + data: ModelJson; +} diff --git a/src/data/data-source/events/data-refresh-event.test.ts b/src/data/data-source/events/data-refresh-event.test.ts new file mode 100644 index 00000000..5ce9a8e2 --- /dev/null +++ b/src/data/data-source/events/data-refresh-event.test.ts @@ -0,0 +1,67 @@ +import { EMPTY, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { DashboardEventManager } from '../../../communication/dashboard-event-manager'; +import { modelDestroyedEventKey } from '../../../model/events/model-destroyed-event'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { getTestScheduler } from '../../../test/rxjs-jest-test-scheduler'; +import { DataRefreshEvent } from './data-refresh-event'; + +describe('Data refresh event', () => { + let mockDashboardEventManager: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let testEventObservable: Observable; + let testDestroyObservable: Observable; + let dataRefreshEvent: DataRefreshEvent; + + const parent = {}; + const child = {}; + const orphan = {}; + const marbleValues = { + p: parent, + c: child, + o: orphan + }; + + beforeEach(() => { + testEventObservable = EMPTY; + testDestroyObservable = EMPTY; + mockModelManager = {}; + mockDashboardEventManager = { + getObservableForEvent: jest.fn(eventKey => { + if (eventKey === modelDestroyedEventKey) { + return testDestroyObservable; + } + + return testEventObservable.pipe(map(refreshedModel => ({ source: refreshedModel, data: undefined }))); + }) + }; + dataRefreshEvent = new DataRefreshEvent( + mockDashboardEventManager as DashboardEventManager, + mockModelManager as ModelManager + ); + }); + + test('gets observable which only notifies for refreshes in parent or self', () => { + getTestScheduler().run(({ cold, expectObservable }) => { + mockModelManager.isAncestor = jest.fn( + (model: object, potentialAncestor: object) => model === child && potentialAncestor === parent + ); + + testEventObservable = cold(' poc', marbleValues); + testDestroyObservable = cold('---poc', marbleValues); + + expectObservable(dataRefreshEvent.getObservableForModel(child)).toBe('x-x --|', { x: undefined }); + expectObservable(dataRefreshEvent.getObservableForModel(orphan)).toBe('-x- -|', { x: undefined }); + expectObservable(dataRefreshEvent.getObservableForModel(parent)).toBe('x-- |', { x: undefined }); + }); + }); + + test('publishRefresh pass through calls publish', () => { + dataRefreshEvent.publish = jest.fn(); + dataRefreshEvent.publishRefresh(parent); + + expect(dataRefreshEvent.publish).toHaveBeenCalledTimes(1); + expect(dataRefreshEvent.publish).toHaveBeenCalledWith({ data: undefined, source: parent }); + }); +}); diff --git a/src/data/data-source/events/data-refresh-event.ts b/src/data/data-source/events/data-refresh-event.ts new file mode 100644 index 00000000..d9b94821 --- /dev/null +++ b/src/data/data-source/events/data-refresh-event.ts @@ -0,0 +1,30 @@ +import { DashboardEventManager } from '../../../communication/dashboard-event-manager'; +import { ModelScopedDashboardEvent } from '../../../communication/model-scoped-dashboard-event'; +import { ModelManager } from '../../../model/manager/model-manager'; + +export const dataRefreshEventKey = Symbol('Data refresh'); + +/** + * Fired for each model when a refresh is requested + */ +export class DataRefreshEvent extends ModelScopedDashboardEvent { + /* istanbul ignore next */ + public constructor(dashboardEventManager: DashboardEventManager, private readonly modelManager: ModelManager) { + super(dashboardEventManager); + } + + /** + * Shorthand method to call `publish` for a model + */ + public publishRefresh(model: object): void { + this.publish({ data: undefined, source: model }); + } + + /** + * @inheritdoc + */ + protected modelShouldReceiveEvent(listenerModel: object, eventSourceModel: object): boolean { + // Broadcast- all listening descendents should be notified of a change + return eventSourceModel === listenerModel || this.modelManager.isAncestor(listenerModel, eventSourceModel); + } +} diff --git a/src/data/data-source/manager/data-source-manager.test.ts b/src/data/data-source/manager/data-source-manager.test.ts new file mode 100644 index 00000000..aec7f0e2 --- /dev/null +++ b/src/data/data-source/manager/data-source-manager.test.ts @@ -0,0 +1,178 @@ +import { EMPTY } from 'rxjs'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { DataSource, dataSourceMarker } from '../data-source'; +import { DataSourceManager } from './data-source-manager'; + +describe('Data source manager', () => { + let dataSourceManager: DataSourceManager; + let mockModelManager: Partial; + + const mockModel = {}; + const mockRoot = {}; + const mockDataSource: Partial> = {}; + + beforeEach(() => { + mockModelManager = { + getRoot: jest.fn(() => mockRoot) + }; + + dataSourceManager = new DataSourceManager(mockModelManager as ModelManager); + }); + + test('supports attaching a data source', () => { + dataSourceManager.attach(mockDataSource as DataSource, mockModel); + + expect(dataSourceManager.get(mockModel)).toBe(mockDataSource); + }); + + test('attaching a data source overwrites a previous data source', () => { + const secondDataSource = {}; + dataSourceManager.attach(mockDataSource as DataSource, mockModel); + dataSourceManager.attach(secondDataSource as DataSource, mockModel); + + expect(dataSourceManager.get(mockModel)).toBe(secondDataSource); + }); + + test('supports detaching a data source', () => { + dataSourceManager.attach(mockDataSource as DataSource, mockModel); + dataSourceManager.detach(mockModel); + + expect(dataSourceManager.get(mockModel)).toBeUndefined(); + }); + + test('detaching a data source succeeds even if no data source was registered', () => { + dataSourceManager.detach(mockModel); + }); + + test('isDataSource can distinguish between data sources and other models', () => { + expect(dataSourceManager.isDataSource(mockDataSource)).toBeFalsy(); + expect(dataSourceManager.isDataSource({ getData: () => EMPTY })).toBeFalsy(); + // tslint:disable-next-line:no-any + expect(dataSourceManager.isDataSource({ dataSourceMarker: Symbol('test other symbol') } as any)).toBeFalsy(); + expect(dataSourceManager.isDataSource({ dataSourceMarker: dataSourceMarker })).toBeFalsy(); + + expect(dataSourceManager.isDataSource({ getData: () => EMPTY, dataSourceMarker: dataSourceMarker })).toBeTruthy(); + }); + + test('modelJsonHasData can correctly identify json containing data', () => { + expect(dataSourceManager.modelJsonHasData({ type: 'any' })).toBeFalsy(); + expect(dataSourceManager.modelJsonHasData({ type: 'any', data: 'any' })).toBeFalsy(); + // tslint:disable-next-line:no-null-keyword + expect(dataSourceManager.modelJsonHasData({ type: 'any', data: null })).toBeFalsy(); + expect(dataSourceManager.modelJsonHasData({ type: 'any', data: {} })).toBeTruthy(); + }); + + test('creates property location including getter and setter', () => { + spyOn(dataSourceManager, 'attach').and.callThrough(); + spyOn(dataSourceManager, 'detach').and.callThrough(); + + const location = dataSourceManager.getPropertyLocationForData(mockModel); + + expect(location.toString()).toBe('data'); + + location.setProperty(mockDataSource as DataSource); + expect(dataSourceManager.attach).toHaveBeenCalledWith(mockDataSource, mockModel); + + expect(location.getProperty()).toBe(mockDataSource); + + location.setProperty(undefined); + expect(dataSourceManager.detach).toHaveBeenCalledWith(mockModel); + + expect(location.getProperty()).toBeUndefined(); + }); + + test('getClosest returns closest data source for model', () => { + const mockParentDataSource: Partial> = {}; + + mockModelManager.getParent = jest.fn((providedModel: object) => + providedModel === mockModel ? mockRoot : undefined + ); + + dataSourceManager.attach(mockDataSource as DataSource, mockModel); + + expect(dataSourceManager.getClosest(mockModel)).toBe(mockDataSource); + expect(dataSourceManager.getClosest(mockRoot)).toBeUndefined(); + + dataSourceManager.attach(mockParentDataSource as DataSource, mockRoot); + + expect(dataSourceManager.getClosest(mockModel)).toBe(mockDataSource); + expect(dataSourceManager.getClosest(mockRoot)).toBe(mockParentDataSource); + + dataSourceManager.detach(mockModel); + + expect(dataSourceManager.getClosest(mockModel)).toBe(mockParentDataSource); + expect(dataSourceManager.getClosest(mockRoot)).toBe(mockParentDataSource); + + dataSourceManager.detach(mockRoot); + + expect(dataSourceManager.getClosest(mockModel)).toBeUndefined(); + expect(dataSourceManager.getClosest(mockRoot)).toBeUndefined(); + }); + + test('getClosest skips parent for data source model', () => { + const mockParentDataSource: Partial> = {}; + + mockModelManager.getParent = jest.fn((providedModel: object) => { + if (providedModel === mockDataSource) { + return mockModel; + } + if (providedModel === mockModel) { + return mockRoot; + } + if (providedModel === mockParentDataSource) { + return mockRoot; + } + + return undefined; + }); + + dataSourceManager.isDataSource = jest.fn( + model => model === mockDataSource || model === mockParentDataSource + // tslint:disable-next-line: no-any + ) as any; + + dataSourceManager.attach(mockDataSource as DataSource, mockModel); + dataSourceManager.attach(mockParentDataSource as DataSource, mockRoot); + + expect(dataSourceManager.getClosest(mockDataSource)).toBe(mockParentDataSource); + + expect(dataSourceManager.getClosest(mockParentDataSource)).toBeUndefined(); + }); + + test('getClosest treats root data sources as root of resolution tree', () => { + const mockRootDataSource: Partial> = {}; + mockModelManager.getParent = jest.fn((providedModel: object) => { + if (providedModel === mockDataSource) { + return mockRoot; + } + if (providedModel === mockModel) { + return mockRoot; + } + if (providedModel === mockRootDataSource) { + return mockRoot; + } + + return undefined; + }); + + mockModelManager.getRoot = jest.fn(() => mockRoot); + + dataSourceManager.isDataSource = jest.fn( + model => model === mockDataSource || model === mockRootDataSource + // tslint:disable-next-line: no-any + ) as any; + + dataSourceManager.setRootDataSource(mockRootDataSource as DataSource, mockRoot); + dataSourceManager.attach(mockDataSource as DataSource, mockRoot); + + expect(dataSourceManager.getClosest(mockModel)).toBe(mockDataSource); + expect(dataSourceManager.getClosest(mockRoot)).toBe(mockDataSource); + expect(dataSourceManager.getClosest(mockDataSource)).toBe(mockRootDataSource); + expect(dataSourceManager.getClosest(mockModel)).toBe(mockDataSource); + expect(dataSourceManager.getClosest(mockRootDataSource)).toBeUndefined(); + + dataSourceManager.detach(mockRoot); + expect(dataSourceManager.getClosest(mockModel)).toBe(mockRootDataSource); + expect(dataSourceManager.getClosest(mockModel)).toBe(mockRootDataSource); + }); +}); diff --git a/src/data/data-source/manager/data-source-manager.ts b/src/data/data-source/manager/data-source-manager.ts new file mode 100644 index 00000000..75c227ec --- /dev/null +++ b/src/data/data-source/manager/data-source-manager.ts @@ -0,0 +1,123 @@ +import { ModelManager } from '../../../hyperdash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { ModelJson } from '../../../persistence/model-json'; +import { DataSource, dataSourceMarker, ModelJsonWithData } from '../data-source'; + +/** + * Manages data sources and their associations with specific models + */ +export class DataSourceManager { + public constructor(private readonly modelManager: ModelManager) {} + + private readonly dataSourceByModelInstance: WeakMap> = new WeakMap(); + private readonly rootDataSourceByModelRoot: WeakMap> = new WeakMap(); + + /** + * Attaches a data source to the specified model. Overwrites any existing data source for + * the specified model. + */ + public attach(dataSource: DataSource, model: object): void { + this.dataSourceByModelInstance.set(model, dataSource); + } + + /** + * Removes the data source for the specified model. No action is taken if a data source is not defined + * for this specific model. + */ + public detach(model: object): void { + this.dataSourceByModelInstance.delete(model); + } + + /** + * Retrieves the data source for this model, if it exists. Returns undefined otherwise. + */ + public get(model: object): DataSource | undefined { + return this.dataSourceByModelInstance.get(model) as DataSource | undefined; + } + + /** + * Type predicate returning true if the provided model object is a data source + */ + public isDataSource> = {}>( + model: TModel + ): model is DataSource & TModel { + if (model.dataSourceMarker && model.getData) { + // tslint:disable-next-line:strict-type-predicates + return typeof model.getData === 'function' && model.dataSourceMarker === dataSourceMarker; + } + + return false; + } + + /** + * Returns true if the model JSON provided contains a data property + */ + public modelJsonHasData(modelJson: ModelJson): modelJson is ModelJsonWithData { + return 'data' in modelJson && typeof modelJson.data === 'object' && modelJson.data !== null; + } + + /** + * Returns a property location corresponding to the data attached to the provided model + */ + public getPropertyLocationForData(instance: object): PropertyLocation> { + return new PropertyLocation( + instance, + 'data', + (value: DataSource | undefined) => { + if (value === undefined) { + this.detach(instance); + } else { + this.attach(value, instance); + } + }, + () => this.get(instance) + ); + } + + /** + * Retrieves the data source attached to this model, if it exists. If not, it recursively checks for data sources + * attached to ancestors, returning the closest, or undefined if no ancestor has a data source. + * If the provided model is a data source, it will skip checking its parent, which would return the original + * data modelInstance, and start its search with a grandparent model, continuing upwards like a regular model. + */ + public getClosest(modelInstance: object): DataSource | undefined { + const attachedDataSource = this.get(modelInstance); + if (attachedDataSource) { + return attachedDataSource; + } + + let parent: object | undefined; + + if (this.isDataSource(modelInstance)) { + // For a data source, its parent would have the original data source attached, so skip a level + const attachedFromModel = this.modelManager.getParent(modelInstance); + parent = attachedFromModel && this.modelManager.getParent(attachedFromModel); + } else { + parent = this.modelManager.getParent(modelInstance); + } + + if (!parent) { + const rootDataSource = this.getRootDataSource(modelInstance); + + // If the root data source is the requestor, then don't give itself back, return undefined + return rootDataSource === modelInstance ? undefined : rootDataSource; + } + + return this.getClosest(parent); + } + + /** + * Sets the root data source for the provided model tree. This data source will be used at the root of the resolution + * tree. + */ + public setRootDataSource(dataSource: DataSource, rootModelInstance: object): void { + this.rootDataSourceByModelRoot.set(rootModelInstance, dataSource); + } + + /** + * Retrieves the root data source for the tree containing the provided model, or undefined if missing. + */ + public getRootDataSource(modelInstance: object): DataSource | undefined { + return this.rootDataSourceByModelRoot.get(this.modelManager.getRoot(modelInstance)) as DataSource | undefined; + } +} diff --git a/src/data/time-range/events/time-range-changed-event.test.ts b/src/data/time-range/events/time-range-changed-event.test.ts new file mode 100644 index 00000000..a56bfe32 --- /dev/null +++ b/src/data/time-range/events/time-range-changed-event.test.ts @@ -0,0 +1,77 @@ +import { EMPTY, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { DashboardEventManager } from '../../../communication/dashboard-event-manager'; +import { modelDestroyedEventKey } from '../../../model/events/model-destroyed-event'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { getTestScheduler } from '../../../test/rxjs-jest-test-scheduler'; +import { TimeRange } from '../time-range'; +import { TimeRangeChangedEvent } from './time-range-changed-event'; + +describe('Time range changed event', () => { + let mockDashboardEventManager: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let testTimeRangeChangeInfoObservable: Observable<[object, TimeRange]>; + let testDestroyObservable: Observable; + let timeRangeChange: TimeRangeChangedEvent; + const firstTimeRange = { startTime: new Date(), endTime: new Date() }; + const secondTimeRange = { ...firstTimeRange }; + + const parent = {}; + const child = {}; + const orphan = {}; + + beforeEach(() => { + testTimeRangeChangeInfoObservable = EMPTY; + testDestroyObservable = EMPTY; + mockModelManager = {}; + mockDashboardEventManager = { + getObservableForEvent: jest.fn(eventKey => { + if (eventKey === modelDestroyedEventKey) { + return testDestroyObservable; + } + + return testTimeRangeChangeInfoObservable.pipe( + map(([model, timeRange]) => ({ source: model, data: timeRange })) + ); + }) + }; + timeRangeChange = new TimeRangeChangedEvent( + mockDashboardEventManager as DashboardEventManager, + mockModelManager as ModelManager + ); + }); + + test('gets observable which only notifies for time ranges in parent or self', () => { + getTestScheduler().run(({ cold, expectObservable }) => { + mockModelManager.isAncestor = jest.fn( + (model: object, potentialAncestor: object) => model === child && potentialAncestor === parent + ); + + testTimeRangeChangeInfoObservable = cold<[object, TimeRange]>('abcd', { + a: [parent, firstTimeRange], + b: [child, secondTimeRange], + c: [parent, firstTimeRange], + d: [orphan, secondTimeRange] + }); + testDestroyObservable = cold('----poc', { + p: parent, + c: child, + o: orphan + }); + + const timeRangeMarbles = { f: firstTimeRange, s: secondTimeRange }; + expectObservable(timeRangeChange.getObservableForModel(child)).toBe('fsf- --|', timeRangeMarbles); + expectObservable(timeRangeChange.getObservableForModel(orphan)).toBe('---s -|', timeRangeMarbles); + expectObservable(timeRangeChange.getObservableForModel(parent)).toBe('f-f- |', timeRangeMarbles); + }); + }); + + test('publishTimeRangeChange pass through calls publish', () => { + timeRangeChange.publish = jest.fn(); + timeRangeChange.publishTimeRangeChange(parent, firstTimeRange); + + expect(timeRangeChange.publish).toHaveBeenCalledTimes(1); + expect(timeRangeChange.publish).toHaveBeenCalledWith({ data: firstTimeRange, source: parent }); + }); +}); diff --git a/src/data/time-range/events/time-range-changed-event.ts b/src/data/time-range/events/time-range-changed-event.ts new file mode 100644 index 00000000..645f96eb --- /dev/null +++ b/src/data/time-range/events/time-range-changed-event.ts @@ -0,0 +1,29 @@ +import { DashboardEventManager } from '../../../communication/dashboard-event-manager'; +import { ModelScopedDashboardEvent } from '../../../communication/model-scoped-dashboard-event'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { TimeRange } from '../time-range'; + +/** + * Fired for each model when the applicable time range is changed + */ +export class TimeRangeChangedEvent extends ModelScopedDashboardEvent { + /* istanbul ignore next */ + public constructor(dashboardEventManager: DashboardEventManager, private readonly modelManager: ModelManager) { + super(dashboardEventManager); + } + + /** + * Shorthand method to call `publish` for a model + */ + public publishTimeRangeChange(model: object, newTimeRange: TimeRange): void { + this.publish({ data: newTimeRange, source: model }); + } + + /** + * @inheritdoc + */ + protected modelShouldReceiveEvent(listenerModel: object, eventSourceModel: object): boolean { + // Broadcast- all listening descendents should be notified of a change + return eventSourceModel === listenerModel || this.modelManager.isAncestor(listenerModel, eventSourceModel); + } +} diff --git a/src/data/time-range/manager/time-range-manager.test.ts b/src/data/time-range/manager/time-range-manager.test.ts new file mode 100644 index 00000000..e86c56cb --- /dev/null +++ b/src/data/time-range/manager/time-range-manager.test.ts @@ -0,0 +1,39 @@ +import { ModelManager } from '../../../model/manager/model-manager'; +import { TimeRangeChangedEvent } from '../events/time-range-changed-event'; +import { TimeRangeManager } from './time-range-manager'; + +describe('Data source manager', () => { + let timeRangeManager: TimeRangeManager; + let mockModelManager: Partial; + let mockTimeRangeChangeEvent: Partial; + + const mockModel = {}; + const mockRoot = {}; + const timeRange = { startTime: new Date(), endTime: new Date() }; + + beforeEach(() => { + mockModelManager = { + getRoot: jest.fn(() => mockRoot) + }; + mockTimeRangeChangeEvent = { + publishTimeRangeChange: jest.fn() + }; + + timeRangeManager = new TimeRangeManager( + mockModelManager as ModelManager, + mockTimeRangeChangeEvent as TimeRangeChangedEvent + ); + }); + + test('supports setting and looking up time ranges', () => { + timeRangeManager.setRootTimeRange(mockRoot, timeRange); + + expect(timeRangeManager.getClosest(mockModel)).toBe(timeRange); + expect(timeRangeManager.getClosest(mockRoot)).toBe(timeRange); + }); + + test('publishes time range change when time range updated', () => { + timeRangeManager.setRootTimeRange(mockRoot, timeRange); + expect(mockTimeRangeChangeEvent.publishTimeRangeChange).toHaveBeenCalledWith(mockRoot, timeRange); + }); +}); diff --git a/src/data/time-range/manager/time-range-manager.ts b/src/data/time-range/manager/time-range-manager.ts new file mode 100644 index 00000000..c80d9f53 --- /dev/null +++ b/src/data/time-range/manager/time-range-manager.ts @@ -0,0 +1,32 @@ +import { ModelManager } from '../../../model/manager/model-manager'; +import { TimeRangeChangedEvent } from '../events/time-range-changed-event'; +import { TimeRange } from '../time-range'; + +/** + * Manages time ranges and their associations with specific models + */ +export class TimeRangeManager { + public constructor( + private readonly modelManager: ModelManager, + private readonly timeRangeChangedEvent: TimeRangeChangedEvent + ) {} + + private readonly rootTimeRangeByModelRoot: WeakMap = new WeakMap(); + + /** + * Sets the root time range for the provided model tree. + */ + public setRootTimeRange(rootModel: object, timeRange: TimeRange): void { + this.rootTimeRangeByModelRoot.set(rootModel, timeRange); + this.timeRangeChangedEvent.publishTimeRangeChange(rootModel, timeRange); + } + + // TODO - setting time range for child models, serialization/deserialization, relative TRs + /** + * Retrieves the time range attached to the closest model in the tree to the provided model, + * searching upwards. + */ + public getClosest(modelInstance: object): TimeRange | undefined { + return this.rootTimeRangeByModelRoot.get(this.modelManager.getRoot(modelInstance)); + } +} diff --git a/src/data/time-range/time-range.ts b/src/data/time-range/time-range.ts new file mode 100644 index 00000000..dbf1c1f9 --- /dev/null +++ b/src/data/time-range/time-range.ts @@ -0,0 +1,14 @@ +/** + * A range of time + */ +export interface TimeRange { + /** + * Start time for time range + */ + startTime: Date; + + /** + * End time for time range + */ + endTime: Date; +} diff --git a/src/hyperdash.ts b/src/hyperdash.ts new file mode 100644 index 00000000..d6eb7351 --- /dev/null +++ b/src/hyperdash.ts @@ -0,0 +1,53 @@ +export * from './communication/dashboard-event'; +export * from './communication/dashboard-event-manager'; +export * from './communication/model-scoped-dashboard-event'; +export * from './dashboard/dashboard'; +export * from './dashboard/dashboard-manager'; +export * from './data/data-source/data-source'; +export * from './data/data-source/events/data-refresh-event'; +export * from './data/data-source/manager/data-source-manager'; +export * from './data/time-range/events/time-range-changed-event'; +export * from './data/time-range/manager/time-range-manager'; +export * from './data/time-range/time-range'; +export * from './model/api/builder/default-model-api-builder'; +export * from './model/api/builder/model-api-builder'; +export * from './model/api/model-api'; +export * from './model/editor/api/editor-api'; +export * from './model/editor/api/editor-api-factory'; +export { ModelPropertyEditor } from './model/editor/editor-decorators'; +export * from './model/editor/editor-library'; +export * from './model/events/model-changed-event'; +export * from './model/events/model-created-event'; +export * from './model/events/model-destroyed-event'; +export * from './model/events/model-event-installer'; +export * from './model/manager/model-lifecycle-hooks'; +export * from './model/manager/model-manager'; +export * from './model/property/model-property-type-library'; +export * from './model/property/predefined/primitive-model-property-types'; +export * from './model/property/predefined/array-property-type'; +export * from './model/property/predefined/model-property-type'; +export * from './model/property/property-location'; +export * from './model/property/validation/model-property-validator'; +export { Model, ModelProperty } from './model/registration/model-decorators'; +export * from './model/registration/model-registration'; +export * from './persistence/deserialization/collection/array-deserializer'; +export * from './persistence/deserialization/collection/object-deserializer'; +export * from './persistence/deserialization/deserialization-manager'; +export * from './persistence/deserialization/model/model-deserializer'; +export * from './persistence/deserialization/primitive/primitive-deserializer'; +export * from './persistence/deserialization/variable/variable-deserializer'; +export * from './persistence/model-json'; +export * from './persistence/serialization/collection/array-serializer'; +export * from './persistence/serialization/collection/object-serializer'; +export * from './persistence/serialization/model/model-serializer'; +export * from './persistence/serialization/primitive/primitive-serializer'; +export * from './persistence/serialization/serialization-manager'; +export * from './persistence/serialization/variable/variable-serializer'; +export { Renderer } from './renderer/registration/renderer-decorators'; +export * from './renderer/registration/renderer-registration'; +export * from './theming/theme'; +export * from './theming/theme-manager'; +export * from './util/logging/log-level'; +export { LogMessage } from './util/logging/log-message'; +export * from './util/logging/logger'; +export * from './variable/manager/variable-manager'; diff --git a/src/model/api/builder/default-model-api-builder.test.ts b/src/model/api/builder/default-model-api-builder.test.ts new file mode 100644 index 00000000..a362e2d7 --- /dev/null +++ b/src/model/api/builder/default-model-api-builder.test.ts @@ -0,0 +1,61 @@ +import { DataSourceManager } from '../../../data/data-source/manager/data-source-manager'; +import { TimeRangeManager } from '../../../data/time-range/manager/time-range-manager'; +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { ThemeManager } from '../../../theming/theme-manager'; +import { Logger } from '../../../util/logging/logger'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { ModelChangedEvent } from '../../events/model-changed-event'; +import { ModelDestroyedEvent } from '../../events/model-destroyed-event'; +import { ModelManager } from '../../manager/model-manager'; +import { DefaultModelApi } from '../default-model-api'; +import { DefaultModelApiBuilder } from './default-model-api-builder'; + +describe('Default model api builder', () => { + let builder: DefaultModelApiBuilder; + let mockLogger: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let mockDataSourceManager: PartialObjectMock; + let mockModelChangedEvent: PartialObjectMock; + let mockModelDestroyedEvent: PartialObjectMock; + let mockThemeManager: PartialObjectMock; + let mockVariableManager: PartialObjectMock; + let mockDeserializationManager: PartialObjectMock; + let mockTimeRangeManager: PartialObjectMock; + + beforeEach(() => { + mockLogger = {}; + mockModelManager = {}; + mockDataSourceManager = {}; + mockModelChangedEvent = { + getObservableForModel: jest.fn().mockReturnValue({ pipe: jest.fn() }) + }; + mockModelDestroyedEvent = { + getDestructionObservable: jest.fn() + }; + mockThemeManager = {}; + mockVariableManager = {}; + mockDeserializationManager = {}; + mockTimeRangeManager = {}; + + builder = new DefaultModelApiBuilder( + mockLogger as Logger, + mockModelManager as ModelManager, + mockDataSourceManager as DataSourceManager, + mockModelChangedEvent as ModelChangedEvent, + mockModelDestroyedEvent as ModelDestroyedEvent, + mockThemeManager as ThemeManager, + mockVariableManager as VariableManager, + mockDeserializationManager as DeserializationManager, + mockTimeRangeManager as TimeRangeManager + ); + }); + + test('matches any model', () => { + expect(builder.matches()).toBe(true); + }); + + test('returns instance of Default Model API', () => { + expect(builder.build({})).toEqual(expect.any(DefaultModelApi)); + }); +}); diff --git a/src/model/api/builder/default-model-api-builder.ts b/src/model/api/builder/default-model-api-builder.ts new file mode 100644 index 00000000..91731d6a --- /dev/null +++ b/src/model/api/builder/default-model-api-builder.ts @@ -0,0 +1,53 @@ +import { DataSourceManager } from '../../../data/data-source/manager/data-source-manager'; +import { TimeRangeManager } from '../../../data/time-range/manager/time-range-manager'; +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { ThemeManager } from '../../../theming/theme-manager'; +import { Logger } from '../../../util/logging/logger'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { ModelChangedEvent } from '../../events/model-changed-event'; +import { ModelDestroyedEvent } from '../../events/model-destroyed-event'; +import { ModelManager } from '../../manager/model-manager'; +import { DefaultModelApi } from '../default-model-api'; +import { ModelApi } from '../model-api'; +import { ModelApiBuilder } from './model-api-builder'; + +/** + * Default implementation of `ModelApiBuilder` + */ +export class DefaultModelApiBuilder implements ModelApiBuilder { + public constructor( + private readonly logger: Logger, + private readonly modelManager: ModelManager, + private readonly dataSourceManager: DataSourceManager, + private readonly modelChangedEvent: ModelChangedEvent, + private readonly modelDestroyedEvent: ModelDestroyedEvent, + private readonly themeManager: ThemeManager, + private readonly variableManager: VariableManager, + private readonly deserializationManager: DeserializationManager, + private readonly timeRangeManager: TimeRangeManager + ) {} + /** + * @inheritdoc + */ + public matches(): boolean { + return true; + } + + /** + * @inheritdoc + */ + public build(model: object): ModelApi { + return new DefaultModelApi( + model, + this.logger, + this.modelManager, + this.dataSourceManager, + this.modelChangedEvent, + this.modelDestroyedEvent, + this.themeManager, + this.variableManager, + this.deserializationManager, + this.timeRangeManager + ); + } +} diff --git a/src/model/api/builder/model-api-builder.ts b/src/model/api/builder/model-api-builder.ts new file mode 100644 index 00000000..06aaab83 --- /dev/null +++ b/src/model/api/builder/model-api-builder.ts @@ -0,0 +1,17 @@ +import { ModelApi } from '../model-api'; + +/** + * Builds an API object for a provided model + */ +export interface ModelApiBuilder { + /** + * Returns true if this builder can build an API for the provided model + */ + matches(model: object): boolean; + + /** + * Builds an API object for the provided model. It does not install the API. + * Any models provided to this method are guaranteed to have passed the `matches` predicate + */ + build(model: object): T; +} diff --git a/src/model/api/default-model-api.test.ts b/src/model/api/default-model-api.test.ts new file mode 100644 index 00000000..bab3561f --- /dev/null +++ b/src/model/api/default-model-api.test.ts @@ -0,0 +1,186 @@ +import { EMPTY } from 'rxjs'; +import { DataSource } from '../../data/data-source/data-source'; +import { DataSourceManager } from '../../data/data-source/manager/data-source-manager'; +import { TimeRangeManager } from '../../data/time-range/manager/time-range-manager'; +import { DeserializationManager } from '../../persistence/deserialization/deserialization-manager'; +import { JsonPrimitive } from '../../persistence/model-json'; +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { getTestScheduler } from '../../test/rxjs-jest-test-scheduler'; +import { ThemeManager } from '../../theming/theme-manager'; +import { Logger } from '../../util/logging/logger'; +import { VariableManager } from '../../variable/manager/variable-manager'; +import { ModelChangedEvent } from '../events/model-changed-event'; +import { ModelDestroyedEvent } from '../events/model-destroyed-event'; +import { ModelManager } from '../manager/model-manager'; +import { PropertyLocation } from '../property/property-location'; +import { DefaultModelApi } from './default-model-api'; + +describe('Default Model API', () => { + let defaultModelApi: DefaultModelApi; + let model: { [key: string]: JsonPrimitive }; + let mockModelManager: PartialObjectMock; + let mockDataSourceManager: PartialObjectMock; + let mockLogger: PartialObjectMock; + let mockModelChangeEvent: PartialObjectMock; + let mockModelDestroyedEvent: PartialObjectMock; + let mockThemeManager: PartialObjectMock; + let mockVariableManager: PartialObjectMock; + let mockDeserializationManager: PartialObjectMock; + let mockTimeRangeManager: PartialObjectMock; + + const buildApi = () => { + defaultModelApi = new DefaultModelApi( + model, + mockLogger as Logger, + mockModelManager as ModelManager, + mockDataSourceManager as DataSourceManager, + mockModelChangeEvent as ModelChangedEvent, + mockModelDestroyedEvent as ModelDestroyedEvent, + mockThemeManager as ThemeManager, + mockVariableManager as VariableManager, + mockDeserializationManager as DeserializationManager, + mockTimeRangeManager as TimeRangeManager + ); + }; + + beforeEach(() => { + model = {}; + mockModelManager = { + create: jest.fn(), + destroy: jest.fn() + }; + mockLogger = {}; + + mockDataSourceManager = { + attach: jest.fn() + }; + + mockModelChangeEvent = { + getObservableForModel: () => EMPTY + }; + mockModelDestroyedEvent = { + getDestructionObservable: () => EMPTY + }; + + mockThemeManager = { + getThemeForModel: jest.fn() + }; + + mockVariableManager = { + set: jest.fn() + }; + mockDeserializationManager = { + deserialize: jest.fn() + }; + mockTimeRangeManager = {}; + buildApi(); + }); + + test('should support creating a child', () => { + const childClass = class ChildClass {}; + defaultModelApi.createChild(childClass); + + expect(mockModelManager.create).toHaveBeenCalledWith(childClass, model); + + const mockParent = {}; + defaultModelApi.createChild(childClass, mockParent); + + expect(mockModelManager.create).toHaveBeenCalledWith(childClass, mockParent); + }); + + test('should support destroying a child', () => { + const child = {}; + defaultModelApi.destroyChild(child); + expect(mockModelManager.destroy).toHaveBeenCalledWith(child); + }); + + test('get data invokes getData function of discovered data source for model', () => { + const returnVal = {}; + const mockDataSource = { + getData: jest.fn().mockReturnValue(returnVal) + }; + mockDataSourceManager.getClosest = jest.fn().mockReturnValue(mockDataSource); + + expect(defaultModelApi.getData()).toBe(returnVal); + }); + + test('get data logs and returns empty observable if no data source found', () => { + mockDataSourceManager.getClosest = jest.fn().mockReturnValue(undefined); + + mockLogger.warn = jest.fn(); + + expect(defaultModelApi.getData()).toEqual(EMPTY); + expect(mockLogger.warn).toHaveBeenCalledWith('No data source found when trying to retrieve data for model'); + }); + + test('should provide lifecycle observables that clean up after themselves', () => { + getTestScheduler().run(({ cold, expectObservable }) => { + const marbleValues = { a: model, b: {} }; + const modelChanges = cold('a-aa-|', marbleValues); + const modelDestroy = cold('-----(a|)', { a: undefined }); + mockModelChangeEvent = { + getObservableForModel: () => modelChanges + }; + mockModelDestroyedEvent = { + getDestructionObservable: () => modelDestroy + }; + + buildApi(); + + expectObservable(defaultModelApi.change$).toBe('a-aa-|', { a: undefined }); + expectObservable(defaultModelApi.destroyed$).toBe('-----(a|)', { a: undefined }); + }); + }); + + test('supports theme lookup', () => { + const mockTheme = {}; + mockThemeManager.getThemeForModel = jest.fn().mockReturnValue(mockTheme); + expect(defaultModelApi.getTheme()).toBe(mockTheme); + expect(mockThemeManager.getThemeForModel).toHaveBeenCalledWith(model); + }); + + test('supports setting variables for self by default', () => { + defaultModelApi.setVariable('key', 'value'); + expect(mockVariableManager.set).toHaveBeenCalledWith('key', 'value', model); + }); + + test('supports setting variables for child models', () => { + const child = {}; + defaultModelApi.setVariable('key', 'value', child); + expect(mockVariableManager.set).toHaveBeenCalledWith('key', 'value', child); + }); + + test('supports deserializing JSON as a child model', () => { + const modelJson = { type: 'model' }; + const expectedResult = {}; + const deserializationMock = jest.fn().mockReturnValue(expectedResult); + + mockDeserializationManager.deserialize = deserializationMock; + + expect(defaultModelApi.createChild(modelJson)).toBe(expectedResult); + + expect(deserializationMock).toHaveBeenCalledWith(modelJson, expect.any(PropertyLocation)); + expect((deserializationMock.mock.calls[0][1] as PropertyLocation).parentModel).toBe(model); + }); + + test('supports setting data source', () => { + const mockDataSource: PartialObjectMock> = {}; + const child = {}; + defaultModelApi.setDataSource(mockDataSource as DataSource, child); + + expect(mockDataSourceManager.attach).toHaveBeenLastCalledWith(mockDataSource, child); + + defaultModelApi.setDataSource(mockDataSource as DataSource); + + expect(mockDataSourceManager.attach).toHaveBeenLastCalledWith(mockDataSource, model); + }); + + test('supports getting time range', () => { + const mockTimeRange = {}; + const mockGetTimeRange = jest.fn().mockReturnValue(mockTimeRange); + mockTimeRangeManager.getClosest = mockGetTimeRange; + + expect(defaultModelApi.getTimeRange()).toBe(mockTimeRange); + expect(mockGetTimeRange).toHaveBeenCalledWith(model); + }); +}); diff --git a/src/model/api/default-model-api.ts b/src/model/api/default-model-api.ts new file mode 100644 index 00000000..de943b0a --- /dev/null +++ b/src/model/api/default-model-api.ts @@ -0,0 +1,111 @@ +import { EMPTY, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { DataSource } from '../../data/data-source/data-source'; +import { DataSourceManager } from '../../data/data-source/manager/data-source-manager'; +import { TimeRangeManager } from '../../data/time-range/manager/time-range-manager'; +import { TimeRange } from '../../data/time-range/time-range'; +import { DeserializationManager } from '../../persistence/deserialization/deserialization-manager'; +import { ModelJson } from '../../persistence/model-json'; +import { MergedTheme, Theme } from '../../theming/theme'; +import { ThemeManager } from '../../theming/theme-manager'; +import { Constructable } from '../../util/constructable'; +import { Logger } from '../../util/logging/logger'; +import { VariableManager } from '../../variable/manager/variable-manager'; +import { ModelChangedEvent } from '../events/model-changed-event'; +import { ModelDestroyedEvent } from '../events/model-destroyed-event'; +import { ModelManager } from '../manager/model-manager'; +import { PropertyLocation } from '../property/property-location'; +import { ModelApi } from './model-api'; + +/** + * Default implementation of Model API + */ +export class DefaultModelApi implements ModelApi { + public constructor( + private readonly model: object, + private readonly logger: Logger, + private readonly modelManager: ModelManager, + private readonly dataSourceManager: DataSourceManager, + private readonly modelChangedEvent: ModelChangedEvent, + private readonly modelDestroyedEvent: ModelDestroyedEvent, + private readonly themeManager: ThemeManager, + private readonly variableManager: VariableManager, + private readonly deserializationManager: DeserializationManager, + private readonly timeRangeManager: TimeRangeManager + ) { + this.destroyed$ = this.modelDestroyedEvent.getDestructionObservable(this.model); + + this.change$ = this.modelChangedEvent.getObservableForModel(this.model).pipe(map(_ => undefined)); + } + + /** + * @inheritdoc + */ + public readonly destroyed$: Observable; + + /** + * @inheritdoc + */ + public readonly change$: Observable; + + /** + * @inheritdoc + */ + public createChild(child: Constructable | ModelJson, parent: object = this.model): T { + if (typeof child === 'function') { + return this.modelManager.create(child, parent); + } + + return this.deserializationManager.deserialize(child, PropertyLocation.forUnassignedChildModel(parent)); + } + + /** + * @inheritdoc + */ + public destroyChild(child: object): void { + this.modelManager.destroy(child); + } + + /** + * @inheritdoc + */ + public getData(): Observable { + const dataSource = this.dataSourceManager.getClosest(this.model); + + if (!dataSource) { + this.logger.warn('No data source found when trying to retrieve data for model'); + + return EMPTY; + } + + return dataSource.getData(); + } + + /** + * @inheritdoc + */ + public getTheme(): MergedTheme { + return this.themeManager.getThemeForModel(this.model); + } + + /** + * @inheritdoc + */ + public setVariable(variableKey: string, value: unknown, modelScope: object = this.model): void { + this.variableManager.set(variableKey, value, modelScope); + } + + /** + * @inheritdoc + */ + public setDataSource(value: DataSource, modelScope: object = this.model): void { + this.dataSourceManager.attach(value, modelScope); + } + + /** + * @inheritdoc + */ + public getTimeRange(): TimeRange | undefined { + return this.timeRangeManager.getClosest(this.model); + } +} diff --git a/src/model/api/model-api.ts b/src/model/api/model-api.ts new file mode 100644 index 00000000..7573463f --- /dev/null +++ b/src/model/api/model-api.ts @@ -0,0 +1,71 @@ +import { Observable } from 'rxjs'; +import { DataSource } from '../../data/data-source/data-source'; +import { TimeRange } from '../../data/time-range/time-range'; +import { ModelJson } from '../../persistence/model-json'; +import { MergedTheme, Theme } from '../../theming/theme'; +import { Constructable } from '../../util/constructable'; + +/** + * Interface for models to interact with their ecosystem in + * a limited way. + */ +export interface ModelApi { + /** + * Instantiates the provided model constructor or JSON, adding it to the model tree + * as a child of the provided parent model. Parent defaults to current model if not specified. + */ + createChild(child: Constructable | ModelJson, parentModel?: object): T; + + /** + * Removes child model from the model tree. + */ + destroyChild(child: object): void; + + /** + * Retrieves closest data source as specified by searching along the model tree by the below argorithm, + * then retrieves its data returning as an observable. If the data source is not defined, + * instead we return an empty observable. + * + * The data returned is specified by + * - If this model has a data source registered to it, retrieve from that data source + * - If this model is a data source, recurse to grandparent (that is, the parent of the model the data source is + * attached to) + * - Else, recurse upwards to parent + */ + getData(): Observable; + + /** + * Retrieves the merged theme specified for this model + */ + getTheme(): MergedTheme; + + /** + * Sets a variable value to the provided key. The variable is scoped to the provided model, or the + * current model if no scope is provided. + */ + setVariable(variableKey: string, value: unknown, modelScope?: object): void; + + /** + * Sets a data source for the provided model. Defaults to the current model if not provided. + */ + setDataSource(value: DataSource, modelScope?: object): void; + + /** + * Gets the time range associated with the provided model, or its closest parent with a time range. + * If no time range is available, returns undefined. + */ + getTimeRange(): TimeRange | undefined; + + /** + * An observable which is notified when this model, or a child of this model is changed. + * It will be completed when the model is destroyed, and generally should not require + * manually unsubscribing. + */ + readonly change$: Observable; + + /** + * An observable which is notified when this model is destroyed, completing immediately after. + * It generally should not require manually unsubscribing. + */ + readonly destroyed$: Observable; +} diff --git a/src/model/editor/api/default-editor-api.test.ts b/src/model/editor/api/default-editor-api.test.ts new file mode 100644 index 00000000..2f5192fe --- /dev/null +++ b/src/model/editor/api/default-editor-api.test.ts @@ -0,0 +1,130 @@ +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { SerializationManager } from '../../../persistence/serialization/serialization-manager'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { ModelChangedEvent } from '../../events/model-changed-event'; +import { ModelManager } from '../../manager/model-manager'; +import { ModelPropertyTypeLibrary } from '../../property/model-property-type-library'; +import { PropertyLocation } from '../../property/property-location'; +import { DefaultEditorApi } from './default-editor-api'; + +describe('Default editor API', () => { + interface TestModel { + prop: string; + } + + let editorApi: DefaultEditorApi; + let model: TestModel; + let propertyLocation: PropertyLocation; + let mockModelChangedEvent: PartialObjectMock; + let mockSerializationManager: PartialObjectMock; + let mockDeserializationManager: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let mockModelPropertyTypeLibrary: PartialObjectMock; + let mockValidator: jest.Mock; + const buildApi = () => { + editorApi = new DefaultEditorApi( + 'Property editor', + { key: 'prop-type' }, + model, + mockValidator, + propertyLocation, + mockModelChangedEvent as ModelChangedEvent, + mockSerializationManager as SerializationManager, + mockDeserializationManager as DeserializationManager, + mockModelManager as ModelManager, + mockModelPropertyTypeLibrary as ModelPropertyTypeLibrary + ); + }; + + beforeEach(() => { + model = { + prop: 'initial value' + }; + + propertyLocation = PropertyLocation.forModelProperty(model, 'prop') as PropertyLocation; + + mockModelChangedEvent = { + publishChange: jest.fn() + }; + mockSerializationManager = { + serialize: jest.fn(val => `Serialized: ${String(val)}`) + }; + mockDeserializationManager = { + deserialize: jest.fn(val => `Deserialized: ${String(val)}`) + }; + mockModelManager = { + destroy: jest.fn() + }; + mockModelPropertyTypeLibrary = { + getPropertySerializer: jest.fn(), + getPropertyDeserializer: jest.fn() + }; + + mockValidator = jest.fn(value => (value === 'expected' ? undefined : 'Value was not expected')); + + buildApi(); + }); + + test('should provide the serialized value', () => { + expect(editorApi.value).toBe('Serialized: initial value'); + }); + + test('should provide the label for the property', () => { + expect(editorApi.label).toBe('Property editor'); + }); + + test('should give access to the validator from the editor data', () => { + expect(editorApi.validate('random')).toBe('Value was not expected'); + + expect(editorApi.validate('expected')).toBeUndefined(); + }); + + test('calls model change event and updates with deserialized value when the valueChange callback is invoked', () => { + editorApi.valueChange('expected'); + + expect(mockModelChangedEvent.publishChange).toHaveBeenCalledWith(model); + + expect(model.prop).toBe('Deserialized: expected'); + expect(editorApi.value).toBe('Serialized: Deserialized: expected'); + }); + + test('does not call model change or update value when valueChange is invoked if validation fails', () => { + editorApi.valueChange('not expected'); + + expect(mockModelChangedEvent.publishChange).not.toHaveBeenCalledWith(model); + + expect(model.prop).toBe('initial value'); + + expect(mockModelManager.destroy).not.toHaveBeenCalled(); + }); + + test('destroys values before overwriting them', () => { + editorApi.valueChange('expected'); + expect(mockModelManager.destroy).toHaveBeenCalledWith('initial value'); + }); + + test('delegates to custom serializer', () => { + mockModelPropertyTypeLibrary.getPropertySerializer = jest.fn(() => (val: unknown) => + `custom serializer: ${String(val)}` + ); + buildApi(); + expect(editorApi.value).toBe('custom serializer: initial value'); + + expect(editorApi.serialize('some value')).toBe('custom serializer: some value'); + + editorApi.valueChange('expected'); + expect(editorApi.value).toBe('custom serializer: Deserialized: expected'); + }); + + test('delegates to custom deserializer', () => { + mockModelPropertyTypeLibrary.getPropertyDeserializer = jest.fn(() => (val: unknown) => + `custom deserializer: ${String(val)}` + ); + buildApi(); + + editorApi.valueChange('expected'); + expect(editorApi.value).toBe('Serialized: custom deserializer: expected'); + + expect(editorApi.deserialize('some value')).toBe('custom deserializer: some value'); + }); +}); diff --git a/src/model/editor/api/default-editor-api.ts b/src/model/editor/api/default-editor-api.ts new file mode 100644 index 00000000..fa33af06 --- /dev/null +++ b/src/model/editor/api/default-editor-api.ts @@ -0,0 +1,93 @@ +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { JsonPrimitive } from '../../../persistence/model-json'; +import { SerializationManager } from '../../../persistence/serialization/serialization-manager'; +import { ModelChangedEvent } from '../../events/model-changed-event'; +import { ModelManager } from '../../manager/model-manager'; +import { ModelPropertyTypeInstance, ModelPropertyTypeLibrary } from '../../property/model-property-type-library'; +import { PropertyLocation } from '../../property/property-location'; +import { EditorApi } from './editor-api'; + +/** + * Default implementation of `EditorApi` + */ +export class DefaultEditorApi + implements EditorApi { + /** + * @inheritdoc + */ + public value: TSerialized; + + public constructor( + public readonly label: string, + public readonly propertyTypeInstance: ModelPropertyTypeInstance, + private readonly model: object, + private readonly validator: (value: unknown) => string | undefined, + private readonly propertyLocation: PropertyLocation, + private readonly modelChangedEvent: ModelChangedEvent, + private readonly serializer: SerializationManager, + private readonly deserializer: DeserializationManager, + private readonly modelManager: ModelManager, + private readonly modelPropertyTypeLibrary: ModelPropertyTypeLibrary + ) { + this.value = this.getValue(); + } + + /** + * @inheritdoc + */ + public validate(newSerializedValue: TSerialized): string | undefined { + return this.validator(newSerializedValue); + } + + /** + * @inheritdoc + */ + public valueChange(newSerializedValue: TSerialized): void { + this.setValue(newSerializedValue); + this.value = this.getValue(); + } + + private getValue(): TSerialized { + return this.serialize(this.propertyLocation.getProperty()!); + } + + private setValue(serializedValue: TSerialized): void { + // TODO variables + if (this.validator(serializedValue) !== undefined) { + return; // If non empty validation message, don't accept the change + } + this.modelManager.destroy(this.propertyLocation.getProperty()); + + this.propertyLocation.setProperty(this.deserialize(serializedValue)); + + this.modelChangedEvent.publishChange(this.model); + } + + /** + * @inheritdoc + */ + public serialize(value: TDeserialized): TSerialized { + const customSerializer = this.modelPropertyTypeLibrary.getPropertySerializer( + this.propertyTypeInstance + ); + if (customSerializer) { + return customSerializer(value, this.propertyLocation, this.propertyTypeInstance); + } + + return this.serializer.serialize(value, this.propertyLocation); + } + + /** + * @inheritdoc + */ + public deserialize(value: TSerialized): TDeserialized { + const customDeserializer = this.modelPropertyTypeLibrary.getPropertyDeserializer( + this.propertyTypeInstance + ); + if (customDeserializer) { + return customDeserializer(value, this.propertyLocation, this.propertyTypeInstance); + } + + return this.deserializer.deserialize(value, this.propertyLocation); + } +} diff --git a/src/model/editor/api/editor-api-factory.test.ts b/src/model/editor/api/editor-api-factory.test.ts new file mode 100644 index 00000000..0edbd348 --- /dev/null +++ b/src/model/editor/api/editor-api-factory.test.ts @@ -0,0 +1,123 @@ +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { SerializationManager } from '../../../persistence/serialization/serialization-manager'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { ModelChangedEvent } from '../../events/model-changed-event'; +import { ModelManager } from '../../manager/model-manager'; +import { ModelPropertyTypeLibrary } from '../../property/model-property-type-library'; +import { PropertyLocation } from '../../property/property-location'; +import { EditorKind, LeafEditorData, UnresolvedCompositeEditorData } from '../editor-library'; +import { DefaultEditorApi } from './default-editor-api'; +import { EditorApiFactory } from './editor-api-factory'; + +describe('Editor API factory', () => { + let editorApiFactory: EditorApiFactory; + let mockModelChangedEvent: PartialObjectMock; + let mockSerializationManager: PartialObjectMock; + let mockDeserializationManager: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let mockModelPropertyTypeLibrary: PartialObjectMock; + + beforeEach(() => { + mockModelChangedEvent = {}; + mockSerializationManager = { + serialize: jest.fn((val: unknown) => `Serialized: ${String(val)}`) + }; + mockDeserializationManager = {}; + mockModelManager = {}; + mockModelPropertyTypeLibrary = { + getPropertySerializer: jest.fn() + }; + + editorApiFactory = new EditorApiFactory( + mockModelChangedEvent as ModelChangedEvent, + mockSerializationManager as SerializationManager, + mockDeserializationManager as DeserializationManager, + mockModelManager as ModelManager, + mockModelPropertyTypeLibrary as ModelPropertyTypeLibrary + ); + }); + test('returns an editor API instance for leaf data', () => { + const model = { + prop: 'initial value' + }; + const editorData = { + propertyMetadata: { + runtimeKey: 'prop', + type: { + key: 'mock-key' + } + }, + title: 'mock title' + }; + const editorApi = editorApiFactory.buildLeafEditorApi(model, editorData as LeafEditorData); + + expect(editorApi).toBeInstanceOf(DefaultEditorApi); + + expect(editorApi.label).toBe('mock title'); + expect(editorApi.value).toBe('Serialized: initial value'); + expect(editorApi.propertyTypeInstance).toEqual({ key: 'mock-key' }); + }); + + test('returns an editor API instance for unresolved data', () => { + const modelClass = class { + public prop: string = 'initial value'; + }; + const model = new modelClass(); + + const editorData: UnresolvedCompositeEditorData = { + title: 'mock title', + getPropertyLocation: jest.fn().mockReturnValueOnce(PropertyLocation.forModelProperty(model, 'prop')), + modelClass: modelClass, + propertyTypeInstance: { + key: 'mock-key' + }, + kind: EditorKind.Unresolved + }; + + const editorApi = editorApiFactory.buildNestedEditorApi(model, editorData); + + expect(editorApi).toBeInstanceOf(DefaultEditorApi); + + expect(editorApi.label).toBe('mock title'); + expect(editorApi.value).toBe('Serialized: initial value'); + + expect(editorApi.propertyTypeInstance).toEqual({ key: 'mock-key' }); + + expect(editorApi.validate({ type: 'any' })).toBeUndefined(); // Should always work + }); + + test('sets empty model json if unresolved data corresponds to unset property', () => { + const modelClass = class { + public prop?: object; + }; + + const defaultValue = {}; + + const model = new modelClass(); + const childModel = new modelClass(); + (mockSerializationManager.serialize as jest.Mock).mockReturnValueOnce(undefined).mockReturnValueOnce(defaultValue); + + mockModelManager.create = jest.fn().mockReturnValue(childModel); + mockModelManager.destroy = jest.fn(); + + const editorData: UnresolvedCompositeEditorData = { + title: 'mock title', + getPropertyLocation: jest.fn().mockReturnValueOnce(PropertyLocation.forModelProperty(model, 'prop')), + modelClass: modelClass, + propertyTypeInstance: { + key: 'mock-key' + }, + kind: EditorKind.Unresolved + }; + + const editorApi = editorApiFactory.buildNestedEditorApi(model, editorData); + + expect(editorApi.value).toBe(defaultValue); + + expect(mockModelManager.create).toHaveBeenCalledWith(modelClass); + + expect(mockSerializationManager.serialize).toHaveBeenCalledWith(childModel); + + expect(mockModelManager.destroy).toHaveBeenCalledWith(childModel); + }); +}); diff --git a/src/model/editor/api/editor-api-factory.ts b/src/model/editor/api/editor-api-factory.ts new file mode 100644 index 00000000..30b4e706 --- /dev/null +++ b/src/model/editor/api/editor-api-factory.ts @@ -0,0 +1,81 @@ +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { JsonPrimitive, ModelJson } from '../../../persistence/model-json'; +import { SerializationManager } from '../../../persistence/serialization/serialization-manager'; +import { ModelChangedEvent } from '../../events/model-changed-event'; +import { ModelManager } from '../../manager/model-manager'; +import { ModelPropertyTypeLibrary } from '../../property/model-property-type-library'; +import { PropertyLocation } from '../../property/property-location'; +import { LeafEditorData, UnresolvedCompositeEditorData } from '../editor-library'; +import { DefaultEditorApi } from './default-editor-api'; +import { EditorApi } from './editor-api'; + +/** + * Factory for producing editor APIs + */ +export class EditorApiFactory { + public constructor( + private readonly modelChangedEvent: ModelChangedEvent, + private readonly serializationManager: SerializationManager, + private readonly deserializationManager: DeserializationManager, + private readonly modelManager: ModelManager, + private readonly modelPropertyTypeLibrary: ModelPropertyTypeLibrary + ) {} + + /** + * Produce a new editor API object for the provided model and leaf editor data + */ + public buildLeafEditorApi(model: object, editorData: LeafEditorData): EditorApi { + const propertyLocation = (PropertyLocation.forModelProperty( + model, + editorData.propertyMetadata.runtimeKey + ) as unknown) as PropertyLocation; + + return new DefaultEditorApi( + editorData.title, + editorData.propertyMetadata.type, + model, + editorData.validator, + propertyLocation, + this.modelChangedEvent, + this.serializationManager, + this.deserializationManager, + this.modelManager, + this.modelPropertyTypeLibrary + ); + } + + /** + * Produces a new editor API object for the provided model and composite editor data + */ + public buildNestedEditorApi(model: object, editorData: UnresolvedCompositeEditorData): EditorApi { + const noOpValidator = () => undefined; + const propertyLocation = editorData.getPropertyLocation(model); + + const api = new DefaultEditorApi( + editorData.title, + editorData.propertyTypeInstance, + model, + noOpValidator, + propertyLocation, + this.modelChangedEvent, + this.serializationManager, + this.deserializationManager, + this.modelManager, + this.modelPropertyTypeLibrary + ); + + if (api.value === undefined) { + api.value = this.buildDefaultJson(editorData); + } + + return api as EditorApi; + } + + private buildDefaultJson(editorData: UnresolvedCompositeEditorData): ModelJson { + const model = this.modelManager.create(editorData.modelClass); + const json = this.serializationManager.serialize(model); + this.modelManager.destroy(model); + + return json; + } +} diff --git a/src/model/editor/api/editor-api.ts b/src/model/editor/api/editor-api.ts new file mode 100644 index 00000000..67de2512 --- /dev/null +++ b/src/model/editor/api/editor-api.ts @@ -0,0 +1,42 @@ +import { JsonPrimitive } from '../../../persistence/model-json'; +import { ModelPropertyTypeInstance } from '../../property/model-property-type-library'; + +/** + * API object for editor renderers to display and update a model property. + * The editor works in serialized values. + */ +export interface EditorApi { + /** + * Current serialized value of the property. // TODO - may be a variable, which would allow something else here + */ + value: TSerialized; + /** + * Display label for this property + */ + label: string; + /** + * Property type instance for this property + */ + propertyTypeInstance: ModelPropertyTypeInstance; + /** + * Callback to be invoked to update the current value of this property. This will NOOP + * if the new value does not pass validation. @see EditorApi.validate + */ + valueChange(newValue: TSerialized): void; + /** + * Validation method to check if the new value is allowable. Will return error string if + * not, otherwise, undefined. + */ + validate(newValue: TSerialized): string | undefined; + /** + * Helper method to convert a potential value from its deserialized to serialized form, + * which is what is accepted by `valueChange` and `validate` + */ + serialize(deserializedValue: TDeserialized): TSerialized; + /** + * Helper method to convert a serialized from to its deserialized form, + * which may be easier to work with in the editor. It must be converted back via `serialize` + * before calling `validate` or `valueChange` + */ + deserialize(serializedValue: TSerialized): TDeserialized; +} diff --git a/src/model/editor/editor-decorators.test.ts b/src/model/editor/editor-decorators.test.ts new file mode 100644 index 00000000..c842a5f0 --- /dev/null +++ b/src/model/editor/editor-decorators.test.ts @@ -0,0 +1,14 @@ +import { ModelPropertyEditor, modelPropertyEditorRegistrations } from './editor-decorators'; + +describe('Editor decorator', () => { + test('should queue information to be retrieved later', () => { + @ModelPropertyEditor({ + propertyType: 'decorator-test' + }) + class DecoratorTestEditor {} + + expect(modelPropertyEditorRegistrations).toEqual([ + { info: { propertyType: 'decorator-test' }, editor: DecoratorTestEditor } + ]); + }); +}); diff --git a/src/model/editor/editor-decorators.ts b/src/model/editor/editor-decorators.ts new file mode 100644 index 00000000..c9b23fed --- /dev/null +++ b/src/model/editor/editor-decorators.ts @@ -0,0 +1,20 @@ +import { ObjectConstructable } from '../../util/constructable'; +import { EditorRegistrationInformation } from './editor-library'; + +export const modelPropertyEditorRegistrations: { + // tslint:disable-next-line:completed-docs + editor: ObjectConstructable; + // tslint:disable-next-line:completed-docs + info: EditorRegistrationInformation; +}[] = []; +/** + * Registers the decorated editor to the provided property type + */ +// tslint:disable-next-line:only-arrow-functions +export function ModelPropertyEditor( + registrationInfo: EditorRegistrationInformation +): (target: ObjectConstructable) => void { + return (editorClass: ObjectConstructable): void => { + modelPropertyEditorRegistrations.push({ editor: editorClass, info: registrationInfo }); + }; +} diff --git a/src/model/editor/editor-library.test.ts b/src/model/editor/editor-library.test.ts new file mode 100644 index 00000000..11555c61 --- /dev/null +++ b/src/model/editor/editor-library.test.ts @@ -0,0 +1,555 @@ +import { isNil } from 'lodash'; +import { DataSourceManager } from '../../data/data-source/manager/data-source-manager'; +import { RendererLibrary } from '../../renderer/registration/renderer-registration'; +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { Theme } from '../../theming/theme'; +import { ThemeManager } from '../../theming/theme-manager'; +import { ObjectConstructable } from '../../util/constructable'; +import { Logger } from '../../util/logging/logger'; +import { ModelPropertyTypeInstance, ModelPropertyTypeLibrary } from '../property/model-property-type-library'; +import { ModelPropertyType } from '../property/predefined/model-property-type'; +import { ModelLibrary, ModelPropertyMetadata, ModelRegistrationInformation } from '../registration/model-registration'; +import { ModelPropertyEditor, modelPropertyEditorRegistrations } from './editor-decorators'; +import { + EditorKind, + EditorLibrary, + LeafEditorData, + MultipleEditorData, + UnresolvedCompositeEditorData +} from './editor-library'; + +describe('Editor library', () => { + let editorLibrary: EditorLibrary; + let mockModelLibrary: PartialObjectMock; + let mockModelPropertyLibrary: PartialObjectMock; + let mockLogger: PartialObjectMock; + let mockRendererLibrary: PartialObjectMock; + let mockThemeManager: PartialObjectMock; + let mockDataSourceManager: PartialObjectMock; + + const modelClass = class {}; + + const mockPropertyMetadata: Partial>[] = [ + { + displayName: 'First Property Display Name', + type: { key: 'first-property' }, + required: true + }, + { + displayName: 'Second Property Display Name', + type: { key: 'second-property' }, + required: false + } + ]; + + beforeEach(() => { + mockModelLibrary = { + lookupModelMetadata: jest.fn((searchClass: ObjectConstructable) => { + if (searchClass === modelClass) { + return { + displayName: 'Model Display Name', + supportedDataSourceTypes: [] + }; + } + + return { + displayName: searchClass.name, + supportedDataSourceTypes: [] + }; + }), + lookupModelProperties: jest.fn(() => new Set(mockPropertyMetadata)), + getAllCompatibleModelClasses: jest.fn(input => [input]) + }; + mockModelPropertyLibrary = {}; + mockLogger = { + warn: jest.fn() + }; + mockRendererLibrary = { + hasRenderer: jest.fn(searchClass => searchClass !== Theme) + }; + + mockThemeManager = { + getPropertyLocationForTheme: jest.fn() + }; + + mockDataSourceManager = { + getPropertyLocationForData: jest.fn() + }; + + editorLibrary = new EditorLibrary( + mockModelLibrary as ModelLibrary, + mockModelPropertyLibrary as ModelPropertyTypeLibrary, + mockLogger as Logger, + mockRendererLibrary as RendererLibrary, + mockThemeManager as ThemeManager, + mockDataSourceManager as DataSourceManager + ); + }); + + test('should allow registering and retrieving editors', () => { + const propertyEditor1 = class {}; + const propertyEditor2 = class {}; + + editorLibrary.registerEditorRenderer(propertyEditor1, { propertyType: 'first-property' }); + editorLibrary.registerEditorRenderer(propertyEditor2, { propertyType: 'second-property' }); + + expect(editorLibrary.getEditorData(modelClass)).toEqual( + expect.objectContaining({ + subeditors: [ + expect.objectContaining({ + editor: propertyEditor1 + }), + expect.objectContaining({ + editor: propertyEditor2 + }) + ] + }) + ); + }); + + test('should return undefined if model class is not registered', () => { + mockModelLibrary.lookupModelMetadata = jest.fn(); + expect(editorLibrary.getEditorData(class TestUnregistered {})).toBeUndefined(); + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Attempted to lookup editor data for unregistered model class: TestUnregistered' + ); + }); + + test('should only return subeditors for properties with defined editors', () => { + editorLibrary.registerEditorRenderer(class {}, { propertyType: 'first-property' }); + + expect(editorLibrary.getEditorData(modelClass)).toEqual( + expect.objectContaining({ + subeditors: [ + expect.objectContaining({ + title: 'First Property Display Name' + }) + ] + }) + ); + }); + + test('should return appropriate validator for each editor', () => { + mockModelPropertyLibrary.getValidator = jest.fn( + (propertyType: ModelPropertyTypeInstance) => (value: string, allowUndefinedOrNull: boolean) => { + if (allowUndefinedOrNull && isNil(value)) { + return undefined; + } + + return value === `${propertyType.key} value` ? undefined : 'Failed'; + } + ); + + editorLibrary.registerEditorRenderer(class {}, { propertyType: 'first-property' }); + editorLibrary.registerEditorRenderer(class {}, { propertyType: 'second-property' }); + const editorMetadata = editorLibrary.getEditorData(modelClass)!; + + const firstPropertyValidator = (editorMetadata.subeditors[0] as LeafEditorData).validator; + + expect(firstPropertyValidator('first-property value')).toBeUndefined(); + expect(firstPropertyValidator('random value')).toBe('Failed'); + expect(firstPropertyValidator(undefined)).toBe('Failed'); + + const secondPropertyValidator = (editorMetadata.subeditors[1] as LeafEditorData).validator; + + expect(secondPropertyValidator('second-property value')).toBeUndefined(); + expect(secondPropertyValidator('random value')).toBe('Failed'); + expect(secondPropertyValidator(undefined)).toBeUndefined(); + }); + + test('should return correct title for each editor', () => { + editorLibrary.registerEditorRenderer(class {}, { propertyType: 'first-property' }); + + editorLibrary.registerEditorRenderer(class {}, { propertyType: 'second-property' }); + expect(editorLibrary.getEditorData(modelClass)).toEqual({ + title: 'Model Display Name', + kind: EditorKind.Composite, + subeditors: [ + expect.objectContaining({ + title: 'First Property Display Name' + }), + expect.objectContaining({ + title: 'Second Property Display Name' + }) + ], + themeEditor: expect.objectContaining({ + title: 'Theme' + }) + }); + }); + + test('should log an error an error and discard data if attempting to overwrite editor registration', () => { + mockLogger.error = jest.fn(); + const propertyEditor1 = class FirstEditor {}; + const propertyEditor2 = class SecondEditor {}; + editorLibrary.registerEditorRenderer(propertyEditor1, { propertyType: 'first-property' }); + editorLibrary.registerEditorRenderer(propertyEditor2, { propertyType: 'first-property' }); + + expect(editorLibrary.getEditorData(modelClass)).toEqual( + expect.objectContaining({ + subeditors: [ + expect.objectContaining({ + editor: propertyEditor1 + }) + ] + }) + ); + + expect(mockLogger.error) + // tslint:disable-next-line:max-line-length + .toHaveBeenCalledWith( + 'Property types may only have one editor. Attempted to register [SecondEditor] to [first-property], but already registered with [FirstEditor]' + ); + }); + + test('editors are returned in the order of model property registration', () => { + mockModelLibrary.lookupModelProperties = jest.fn(() => new Set([mockPropertyMetadata[1], mockPropertyMetadata[0]])); + editorLibrary.registerEditorRenderer(class {}, { propertyType: 'first-property' }); + + editorLibrary.registerEditorRenderer(class {}, { propertyType: 'second-property' }); + + expect(editorLibrary.getEditorData(modelClass)).toEqual( + expect.objectContaining({ + subeditors: [ + expect.objectContaining({ + title: 'Second Property Display Name' + }), + expect.objectContaining({ + title: 'First Property Display Name' + }) + ] + }) + ); + }); + + test('editors registered via decorators are available', () => { + @ModelPropertyEditor({ + propertyType: 'first-property' + }) + class DecoratorTestEditor {} + + expect(editorLibrary.getEditorData(modelClass)).toEqual( + expect.objectContaining({ + subeditors: [ + expect.objectContaining({ + editor: DecoratorTestEditor + }) + ] + }) + ); + + // Cleanup - decorator state is global, don't want test leaving state + modelPropertyEditorRegistrations.pop(); + }); + + test('handles nested editors', () => { + const propertyEditor = class {}; + + editorLibrary.registerEditorRenderer(propertyEditor, { propertyType: 'first-property' }); + + (mockModelLibrary.lookupModelProperties as jest.Mock).mockReturnValueOnce( + new Set([ + { + displayName: 'Parent Model Property', + type: { key: ModelPropertyType.TYPE }, + required: true, + runtimeType: modelClass, + runtimeKey: 'runtimeKey' + } + ]) + ); + + const editorData = editorLibrary.getEditorData(modelClass); + + expect(editorData).toEqual({ + title: 'Model Display Name', + kind: EditorKind.Composite, + subeditors: [ + { + title: 'Parent Model Property', + modelClass: modelClass, + kind: EditorKind.Unresolved, + propertyTypeInstance: { + key: ModelPropertyType.TYPE + }, + getPropertyLocation: expect.any(Function) + } + ], + themeEditor: expect.any(Object) + }); + + const unresolvedEditorData = editorData!.subeditors[0] as UnresolvedCompositeEditorData; + const childEditorData = editorLibrary.getEditorData(unresolvedEditorData.modelClass); + expect(childEditorData).toEqual({ + title: 'Model Display Name', + kind: EditorKind.Composite, + subeditors: [ + { + title: 'First Property Display Name', + editor: propertyEditor, + kind: EditorKind.Leaf, + propertyMetadata: mockPropertyMetadata[0], + validator: expect.any(Function) + } + ], + themeEditor: expect.any(Object) + }); + + const model = new modelClass(); + const childLocation = unresolvedEditorData.getPropertyLocation(model); + expect(childLocation.toString()).toBe('runtimeKey'); + expect(childLocation.parentModel).toBe(model); + }); + + test('handles no compatible nested editors', () => { + mockModelLibrary.getAllCompatibleModelClasses = jest.fn(input => (input === Theme ? [Theme] : [])); + mockModelLibrary.lookupModelProperties = jest.fn( + () => + new Set([ + { + displayName: 'Parent Model Property', + type: { key: ModelPropertyType.TYPE }, + required: true, + runtimeType: class Unregistered {} + } + ]) + ); + + expect(editorLibrary.getEditorData(modelClass)).toEqual({ + title: 'Model Display Name', + kind: EditorKind.Composite, + subeditors: [], + themeEditor: expect.any(Object) + }); + + mockModelLibrary.lookupModelProperties = jest.fn( + () => + new Set([ + { + displayName: 'Parent Model Property', + type: ModelPropertyType.TYPE, + required: true, + runtimeType: undefined + } + ]) + ); + + expect(editorLibrary.getEditorData(modelClass)).toEqual({ + title: 'Model Display Name', + kind: EditorKind.Composite, + subeditors: [], + themeEditor: expect.any(Object) + }); + }); + + test('handles multiple compatible nested editors', () => { + const childClass1 = class FirstChildClass extends modelClass {}; + const childClass2 = class SecondChildClass extends modelClass {}; + mockModelLibrary.getAllCompatibleModelClasses = jest.fn(input => + input === Theme ? [Theme] : [childClass1, childClass2] + ); + + (mockModelLibrary.lookupModelProperties as jest.Mock).mockReturnValueOnce( + new Set([ + { + displayName: 'Parent Model Property', + type: { key: ModelPropertyType.TYPE }, + required: true, + runtimeType: modelClass + } + ]) + ); + + const editorData = editorLibrary.getEditorData(modelClass); + + expect(editorData).toEqual({ + title: 'Model Display Name', + kind: EditorKind.Composite, + subeditors: [ + { + title: 'Parent Model Property', + kind: EditorKind.Multiple, + compatibleEditors: [ + { + title: 'FirstChildClass', + modelClass: childClass1, + getPropertyLocation: expect.any(Function), + propertyTypeInstance: { + key: ModelPropertyType.TYPE + }, + kind: EditorKind.Unresolved + }, + { + title: 'SecondChildClass', + modelClass: childClass2, + getPropertyLocation: expect.any(Function), + propertyTypeInstance: { + key: ModelPropertyType.TYPE + }, + kind: EditorKind.Unresolved + } + ] + } + ], + themeEditor: expect.any(Object) + }); + + const compatibleEditors = (editorData!.subeditors[0] as MultipleEditorData).compatibleEditors; + editorLibrary.getEditorData = jest.fn(); + + expect(compatibleEditors[0].modelClass).toBe(childClass1); + + expect(compatibleEditors[1].modelClass).toBe(childClass2); + }); + + test('provides theme editor for composite editor data', () => { + const editorData = editorLibrary.getEditorData(modelClass)!; + expect(editorData).toMatchObject({ + themeEditor: { + title: 'Theme', + modelClass: Theme, + kind: EditorKind.Unresolved, + getPropertyLocation: expect.any(Function) + } + }); + + mockModelLibrary.lookupModelMetadata = jest.fn< + Required | undefined, + [ObjectConstructable] + >(value => + value === Theme ? { type: 'theme', displayName: 'Theme Object', supportedDataSourceTypes: [] } : undefined + ); + + const themeEditor = editorData.themeEditor as UnresolvedCompositeEditorData; + expect(editorLibrary.getEditorData(themeEditor.modelClass)).toEqual({ + title: 'Theme Object', + subeditors: [], + kind: EditorKind.Composite + }); + + (mockThemeManager.getPropertyLocationForTheme as jest.Mock).mockReturnValueOnce('mock property location'); + const model = new modelClass(); + expect(themeEditor.getPropertyLocation(model)).toBe('mock property location'); + }); + + test('omits theme editor for non renderable model', () => { + mockRendererLibrary.hasRenderer = jest.fn().mockReturnValue(false); + const editorData = editorLibrary.getEditorData(modelClass)!; + expect(editorData).not.toMatchObject({ + themeEditor: expect.anything() + }); + }); + + test('provides multiple theme editor data if available', () => { + const themeClass1 = class FirstTheme extends Theme {}; + + const themeClass2 = class SecondTheme extends Theme {}; + mockModelLibrary.getAllCompatibleModelClasses = jest.fn(input => + input === Theme ? [themeClass1, themeClass2] : [input] + ); + + expect(editorLibrary.getEditorData(modelClass)).toMatchObject({ + themeEditor: { + title: 'Theme', + compatibleEditors: [ + { + title: 'FirstTheme', + modelClass: themeClass1, + kind: EditorKind.Unresolved + }, + { + title: 'SecondTheme', + modelClass: themeClass2, + kind: EditorKind.Unresolved + } + ], + kind: EditorKind.Multiple + } + }); + }); + + test('does not provides data editor if no data source classes registered', () => { + const editorData = editorLibrary.getEditorData(modelClass)!; + + expect(editorData).not.toMatchObject({ + dataEditor: expect.anything() + }); + }); + + test('provides data editor if single data source class registered', () => { + const mockDataSourceClass = class {}; + + mockModelLibrary.lookupModelMetadata = jest.fn((searchClass: ObjectConstructable) => { + if (searchClass === modelClass) { + return { + displayName: 'Model Display Name', + supportedDataSourceTypes: [mockDataSourceClass] + }; + } + if (searchClass === mockDataSourceClass) { + return { + displayName: 'Mock data source class', + supportedDataSourceTypes: [] + }; + } + + return { + displayName: searchClass.name, + supportedDataSourceTypes: [] + }; + }); + + const editorData = editorLibrary.getEditorData(modelClass)!; + expect(editorData).toMatchObject({ + dataEditor: { + title: 'Data', + modelClass: mockDataSourceClass, + kind: EditorKind.Unresolved, + getPropertyLocation: expect.any(Function) + } + }); + + (mockDataSourceManager.getPropertyLocationForData as jest.Mock).mockReturnValueOnce('mock data location'); + const model = new modelClass(); + expect((editorData.dataEditor as UnresolvedCompositeEditorData).getPropertyLocation(model)).toBe( + 'mock data location' + ); + }); + + test('provides data editor if multiple data source classes registered', () => { + const mockDataSourceClass1 = class {}; + const mockDataSourceClass2 = class {}; + + mockModelLibrary.lookupModelMetadata = jest.fn((searchClass: ObjectConstructable) => { + if (searchClass === modelClass) { + return { + displayName: 'Model Display Name', + supportedDataSourceTypes: [mockDataSourceClass1, mockDataSourceClass2] + }; + } + + return { + displayName: searchClass.name, + supportedDataSourceTypes: [] + }; + }); + + const editorData = editorLibrary.getEditorData(modelClass)!; + expect(editorData).toMatchObject({ + dataEditor: { + title: 'Data', + kind: EditorKind.Multiple, + compatibleEditors: [ + { + kind: EditorKind.Unresolved, + modelClass: mockDataSourceClass1 + }, + { + kind: EditorKind.Unresolved, + modelClass: mockDataSourceClass2 + } + ] + } + }); + }); +}); diff --git a/src/model/editor/editor-library.ts b/src/model/editor/editor-library.ts new file mode 100644 index 00000000..21d29fff --- /dev/null +++ b/src/model/editor/editor-library.ts @@ -0,0 +1,366 @@ +import { uniq } from 'lodash'; +import { DataSourceManager } from '../../data/data-source/manager/data-source-manager'; +import { RendererLibrary } from '../../renderer/registration/renderer-registration'; +import { Theme } from '../../theming/theme'; +import { ThemeManager } from '../../theming/theme-manager'; +import { ObjectConstructable, UnknownConstructable } from '../../util/constructable'; +import { Logger } from '../../util/logging/logger'; +import { ModelPropertyTypeInstance, ModelPropertyTypeLibrary } from '../property/model-property-type-library'; +import { ModelPropertyType } from '../property/predefined/model-property-type'; +import { PropertyLocation } from '../property/property-location'; +import { ModelLibrary, ModelPropertyMetadata } from '../registration/model-registration'; +import { modelPropertyEditorRegistrations } from './editor-decorators'; + +/** + * Thoughts: + * Create a new property type for each enum. That way, we can also do validation around it, localize + * and potentially reuse actual editors. + * + * Editor container should aggregate changes and send them in some standard format. + * Editor container should correctly nest. + * + */ +/** + * Editor library allows registering editor renderers to property types, and builds + * a tree of information for generating dynamic editors given a specific model constructor + */ +export class EditorLibrary { + private readonly editorMetadata: Map = new Map(); + private lastDecoratorIndexRead: number = 0; + + public constructor( + private readonly modelLibrary: ModelLibrary, + private readonly modelPropertyTypeLibrary: ModelPropertyTypeLibrary, + private readonly logger: Logger, + private readonly rendererLibrary: RendererLibrary, + private readonly themeManager: ThemeManager, + private readonly dataSourceManager: DataSourceManager + ) {} + + /** + * Registers the provided editor class to a given model property type. No action is taken if that + * model property already has an editor. + */ + public registerEditorRenderer( + editorRendererClass: UnknownConstructable, + registrationInformation: EditorRegistrationInformation + ): void { + this.processRegistrationQueue(); + this.registerEditorRendererInternal(editorRendererClass, registrationInformation); + } + + /** + * Gets data needed to build the editor for the provided model constructor, or undefined if the editor information + * cannot be found. + * TODO Also show meta properties like data (maybe, more likely first-class), or theme + */ + public getEditorData(modelConstructor: ObjectConstructable): CompositeEditorData | undefined { + this.processRegistrationQueue(); + + const metadata = this.modelLibrary.lookupModelMetadata(modelConstructor); + + if (!metadata) { + this.logger.warn(`Attempted to lookup editor data for unregistered model class: ${modelConstructor.name}`); + + return undefined; + } + + const modelPropertyEditors = Array.from(this.modelLibrary.lookupModelProperties(modelConstructor)) + .map(propertyMetadata => this.getModelPropertyEditorData(propertyMetadata)) + .filter((data): data is NestedEditorData => data !== undefined); + + const compositeData: CompositeEditorData = { + title: metadata.displayName, + subeditors: modelPropertyEditors, + kind: EditorKind.Composite + }; + const themeEditor = this.getThemeEditorForClass(modelConstructor); + if (themeEditor) { + compositeData.themeEditor = themeEditor; + } + const dataEditor = this.getDataEditorForClass(modelConstructor); + if (dataEditor) { + compositeData.dataEditor = dataEditor; + } + + return compositeData; + } + + private getModelPropertyEditorData( + modelPropertyMetadata: ModelPropertyMetadata + ): NestedEditorData | undefined { + const propertyTypeKey = modelPropertyMetadata.type.key; + // We have a registered editor, use it + if (this.editorMetadata.has(propertyTypeKey)) { + return { + title: modelPropertyMetadata.displayName, + editor: this.editorMetadata.get(propertyTypeKey)!.renderer, + validator: value => + this.modelPropertyTypeLibrary.getValidator(modelPropertyMetadata.type)( + value, + !modelPropertyMetadata.required, + modelPropertyMetadata.type + ), + propertyMetadata: modelPropertyMetadata, + kind: EditorKind.Leaf + }; + } + + if (propertyTypeKey === ModelPropertyType.TYPE && modelPropertyMetadata.runtimeType !== undefined) { + return this.getEditorMatchingModelClasses( + [modelPropertyMetadata.runtimeType as ObjectConstructable], + modelPropertyMetadata.displayName, + modelPropertyMetadata.type, + model => PropertyLocation.forModelProperty(model, modelPropertyMetadata.runtimeKey) + ); + } + + return undefined; + } + + /** + * Internal version does not clear queue before proceeding + */ + private registerEditorRendererInternal( + editorRendererClass: UnknownConstructable, + registrationInformation: EditorRegistrationInformation + ): void { + if (this.editorMetadata.has(registrationInformation.propertyType)) { + this.logger.error( + 'Property types may only have one editor. ' + + `Attempted to register [${editorRendererClass.name}] ` + + `to [${registrationInformation.propertyType}], but already registered with ` + + `[${this.editorMetadata.get(registrationInformation.propertyType)!.renderer.name}]` + ); + + return; + } + + this.editorMetadata.set(registrationInformation.propertyType, { renderer: editorRendererClass }); + } + + private processRegistrationQueue(): void { + // tslint:disable-next-line:max-line-length + for ( + this.lastDecoratorIndexRead; + this.lastDecoratorIndexRead < modelPropertyEditorRegistrations.length; + this.lastDecoratorIndexRead++ + ) { + const registration = modelPropertyEditorRegistrations[this.lastDecoratorIndexRead]; + this.registerEditorRendererInternal(registration.editor, registration.info); + } + } + + private getEditorMatchingModelClasses( + modelClasses: ObjectConstructable[], + displayName: string, + typeInstance: ModelPropertyTypeInstance, + getPropertyLocation: (model: object) => PropertyLocation + ): UnresolvedCompositeEditorData | MultipleEditorData | undefined { + const allMatchingEditors = this.getAllCompatibleModelClasses(modelClasses).map( + (compatibleConstructor): UnresolvedCompositeEditorData => ({ + title: this.modelLibrary.lookupModelMetadata(compatibleConstructor)!.displayName, + modelClass: compatibleConstructor, + propertyTypeInstance: typeInstance, + getPropertyLocation: getPropertyLocation as

() => PropertyLocation

, + kind: EditorKind.Unresolved + }) + ); + + if (allMatchingEditors.length === 0) { + return undefined; + } + if (allMatchingEditors.length === 1) { + return { + ...allMatchingEditors[0], + title: displayName + }; + } + + return { + title: displayName, + compatibleEditors: allMatchingEditors, + kind: EditorKind.Multiple + }; + } + + private getAllCompatibleModelClasses(modelClasses: ObjectConstructable[]): ObjectConstructable[] { + return uniq(modelClasses.flatMap(modeClass => this.modelLibrary.getAllCompatibleModelClasses(modeClass))); + } + + private getThemeEditorForClass( + modelClass: ObjectConstructable + ): UnresolvedCompositeEditorData | MultipleEditorData | undefined { + if (this.rendererLibrary.hasRenderer(modelClass)) { + return this.getEditorMatchingModelClasses([Theme], 'Theme', { key: '_theme' }, model => + this.themeManager.getPropertyLocationForTheme(model) + ); + } + + return undefined; + } + + private getDataEditorForClass( + modelClass: ObjectConstructable + ): UnresolvedCompositeEditorData | MultipleEditorData | undefined { + // Always defined, we've already done this lookup to get this far + const modelMetadata = this.modelLibrary.lookupModelMetadata(modelClass)!; + + return this.getEditorMatchingModelClasses(modelMetadata.supportedDataSourceTypes, 'Data', { key: '_data' }, model => + this.dataSourceManager.getPropertyLocationForData(model) + ); + } +} + +export type NestedEditorData = UnresolvedCompositeEditorData | LeafEditorData | MultipleEditorData; + +interface EditorMetadata { + /** + * Renderable object for editor + */ + renderer: UnknownConstructable; +} + +/** + * Discriminating enum for editor subtypes + */ +export enum EditorKind { + /** + * Indicates composite type + * @see CompositeEditorData + */ + Composite, + /** + * Indicates leaf type + * @see LeafEditorData + */ + Leaf, + /** + * Indicates multiple type + * @see MultipleEditorData + */ + Multiple, + /** + * Indicates unresolved type + * @see UnresolvedCompositeEditorData + */ + Unresolved +} + +/** + * Data representing an editor or editor fragment for the referenced `modelClass` + */ +export interface EditorData { + /** + * Display title for editor + */ + title: string; + + /** + * Discriminator + */ + kind: EditorKind; +} + +/** + * Data for a grouping of editors based on a model. This does not represent an editor itself, but should + * be composed of subeditors. + */ +export interface CompositeEditorData extends EditorData { + /** + * Editors for properties of this model + */ + subeditors: NestedEditorData[]; + + /** + * @inheritdoc + */ + kind: EditorKind.Composite; + + /** + * Editor for the theme of this model. Undefined if model is not themable. + */ + themeEditor?: UnresolvedCompositeEditorData | MultipleEditorData; + + /** + * Editor for the data source of this model. Undefined if model does not support data + */ + dataEditor?: UnresolvedCompositeEditorData | MultipleEditorData; +} + +/** + * Represents an editor for a leaf property - that is a primitive or primitive collection, + * as opposed to a model + */ +export interface LeafEditorData extends EditorData { + /** + * Validator for property, returning a string on validation failure or undefined otherwise + */ + validator(value: unknown): string | undefined; + + /** + * Renderable object for editor + */ + editor: UnknownConstructable; + + /** + * Metadata for the editable property + */ + propertyMetadata: ModelPropertyMetadata; + + /** + * @inheritdoc + */ + kind: EditorKind.Leaf; +} + +/** + * Represents a model inside another model. We don't use CompositeEditorData directly to prevent + * infinite recursive loops from self referencing models (or cycles). + */ +export interface UnresolvedCompositeEditorData extends EditorData { + /** + * Model class represented by this editor + */ + modelClass: ObjectConstructable; + + /** + * @inheritdoc + */ + kind: EditorKind.Unresolved; + + /** + * Retrieves Property Location for this field in the provided model + */ + getPropertyLocation(model: object): PropertyLocation; + + /** + * Property type instance for this property + */ + propertyTypeInstance: ModelPropertyTypeInstance; +} + +/** + * Represents a property that has multiple compatible models available. + */ +export interface MultipleEditorData extends EditorData { + /** + * Array of compatible model editor datas + */ + compatibleEditors: UnresolvedCompositeEditorData[]; + + /** + * @inheritdoc + */ + kind: EditorKind.Multiple; +} + +/** + * Metadata needed to register a new editor + */ +export interface EditorRegistrationInformation { + /** + * Property type associated with the editor being registered + */ + propertyType: string; +} diff --git a/src/model/events/model-changed-event.test.ts b/src/model/events/model-changed-event.test.ts new file mode 100644 index 00000000..79d2ad73 --- /dev/null +++ b/src/model/events/model-changed-event.test.ts @@ -0,0 +1,67 @@ +import { EMPTY, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { DashboardEventManager } from '../../communication/dashboard-event-manager'; +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { getTestScheduler } from '../../test/rxjs-jest-test-scheduler'; +import { ModelManager } from '../manager/model-manager'; +import { ModelChangedEvent } from './model-changed-event'; +import { modelDestroyedEventKey } from './model-destroyed-event'; + +describe('Model changed event', () => { + let mockDashboardEventManager: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let testEventObservable: Observable; + let testDestroyObservable: Observable; + let modelChangedEvent: ModelChangedEvent; + + const parent = {}; + const child = {}; + const orphan = {}; + const marbleValues = { + p: parent, + c: child, + o: orphan + }; + + beforeEach(() => { + testEventObservable = EMPTY; + testDestroyObservable = EMPTY; + mockModelManager = {}; + mockDashboardEventManager = { + getObservableForEvent: jest.fn(eventKey => { + if (eventKey === modelDestroyedEventKey) { + return testDestroyObservable; + } + + return testEventObservable.pipe(map(changedModel => ({ source: changedModel, data: changedModel }))); + }) + }; + modelChangedEvent = new ModelChangedEvent( + mockDashboardEventManager as DashboardEventManager, + mockModelManager as ModelManager + ); + }); + + test('gets observable which only notifies for changes in children or self', () => { + getTestScheduler().run(({ cold, expectObservable }) => { + mockModelManager.isAncestor = jest.fn( + (model: object, potentialAncestor: object) => model === child && potentialAncestor === parent + ); + + testEventObservable = cold(' poc', marbleValues); + testDestroyObservable = cold('---poc', marbleValues); + + expectObservable(modelChangedEvent.getObservableForModel(child)).toBe('--c --|', marbleValues); + expectObservable(modelChangedEvent.getObservableForModel(orphan)).toBe('-o- -|', marbleValues); + expectObservable(modelChangedEvent.getObservableForModel(parent)).toBe('p-c |', marbleValues); + }); + }); + + test('publishChange pass through calls publish', () => { + modelChangedEvent.publish = jest.fn(); + modelChangedEvent.publishChange(parent); + + expect(modelChangedEvent.publish).toHaveBeenCalledTimes(1); + expect(modelChangedEvent.publish).toHaveBeenCalledWith({ data: parent, source: parent }); + }); +}); diff --git a/src/model/events/model-changed-event.ts b/src/model/events/model-changed-event.ts new file mode 100644 index 00000000..34025521 --- /dev/null +++ b/src/model/events/model-changed-event.ts @@ -0,0 +1,28 @@ +import { DashboardEventManager } from '../../communication/dashboard-event-manager'; +import { ModelScopedDashboardEvent } from '../../communication/model-scoped-dashboard-event'; +import { ModelManager } from '../manager/model-manager'; + +/** + * Fired after a property of a model (or a child model) changes. + */ +export class ModelChangedEvent extends ModelScopedDashboardEvent { + /* istanbul ignore next */ + public constructor(dashboardEventManager: DashboardEventManager, private readonly modelManager: ModelManager) { + super(dashboardEventManager); + } + + /** + * Shorthand method to call `publish` for a model + */ + public publishChange(model: object): void { + this.publish({ data: model, source: model }); + } + + /** + * @inheritdoc + */ + protected modelShouldReceiveEvent(listenerModel: object, eventSourceModel: object): boolean { + // Bubble up - all listening ancestors should be notified of a change + return eventSourceModel === listenerModel || this.modelManager.isAncestor(eventSourceModel, listenerModel); + } +} diff --git a/src/model/events/model-created-event.test.ts b/src/model/events/model-created-event.test.ts new file mode 100644 index 00000000..5aec7696 --- /dev/null +++ b/src/model/events/model-created-event.test.ts @@ -0,0 +1,33 @@ +import { DashboardEventManager } from '../../communication/dashboard-event-manager'; +import { ModelCreatedEvent } from './model-created-event'; + +describe('Model created event', () => { + let mockEventManager: Partial; + let modelCreatedEvent: ModelCreatedEvent; + + beforeEach(() => { + mockEventManager = {}; + modelCreatedEvent = new ModelCreatedEvent(mockEventManager as DashboardEventManager); + }); + test('relays all publishes to manager', () => { + mockEventManager.publishEvent = jest.fn(); + const first = {}; + const second = {}; + modelCreatedEvent.publish(first); + modelCreatedEvent.publish(second); + + expect(mockEventManager.publishEvent).toHaveBeenCalledTimes(2); + + expect(mockEventManager.publishEvent).nthCalledWith(1, modelCreatedEvent.getKey(), first); + + expect(mockEventManager.publishEvent).nthCalledWith(2, modelCreatedEvent.getKey(), second); + }); + + test('gets observable from manager for this event', () => { + mockEventManager.getObservableForEvent = jest.fn(); + + modelCreatedEvent.getObservable(); + + expect(mockEventManager.getObservableForEvent).toHaveBeenCalledWith(modelCreatedEvent.getKey()); + }); +}); diff --git a/src/model/events/model-created-event.ts b/src/model/events/model-created-event.ts new file mode 100644 index 00000000..0671973d --- /dev/null +++ b/src/model/events/model-created-event.ts @@ -0,0 +1,13 @@ +import { DashboardEvent } from '../../communication/dashboard-event'; +import { DashboardEventManager } from '../../communication/dashboard-event-manager'; + +export const modelCreatedEventKey = Symbol('Model created'); + +/** + * Fired after a model is created, before its properties are set and its initialization hook is run + */ +export class ModelCreatedEvent extends DashboardEvent { + public constructor(dashboardEventManager: DashboardEventManager) { + super(dashboardEventManager, modelCreatedEventKey); + } +} diff --git a/src/model/events/model-destroyed-event.test.ts b/src/model/events/model-destroyed-event.test.ts new file mode 100644 index 00000000..d55326e1 --- /dev/null +++ b/src/model/events/model-destroyed-event.test.ts @@ -0,0 +1,48 @@ +import { DashboardEventManager } from '../../communication/dashboard-event-manager'; +import { getTestScheduler } from '../../test/rxjs-jest-test-scheduler'; +import { ModelDestroyedEvent } from './model-destroyed-event'; + +describe('Model destroyed event', () => { + let mockEventManager: Partial; + let modelDestroyedEvent: ModelDestroyedEvent; + + beforeEach(() => { + mockEventManager = {}; + modelDestroyedEvent = new ModelDestroyedEvent(mockEventManager as DashboardEventManager); + }); + test('relays all publishes to manager', () => { + mockEventManager.publishEvent = jest.fn(); + const first = {}; + const second = {}; + modelDestroyedEvent.publish(first); + modelDestroyedEvent.publish(second); + + expect(mockEventManager.publishEvent).toHaveBeenCalledTimes(2); + + expect(mockEventManager.publishEvent).nthCalledWith(1, modelDestroyedEvent.getKey(), first); + + expect(mockEventManager.publishEvent).nthCalledWith(2, modelDestroyedEvent.getKey(), second); + }); + + test('gets observable from manager for this event', () => { + mockEventManager.getObservableForEvent = jest.fn(); + + modelDestroyedEvent.getObservable(); + + expect(mockEventManager.getObservableForEvent).toHaveBeenCalledWith(modelDestroyedEvent.getKey()); + }); + + test('destruction observables notifies on the provided model then completes', () => { + getTestScheduler().run(({ cold, expectObservable }) => { + const mockModels = { + a: {}, + b: {} + }; + modelDestroyedEvent.getObservable = jest.fn().mockReturnValue(cold('a-b-', mockModels)); + + expectObservable(modelDestroyedEvent.getDestructionObservable(mockModels.a)).toBe('(a|)', { a: undefined }); + + expectObservable(modelDestroyedEvent.getDestructionObservable(mockModels.b)).toBe('--(b|)', { b: undefined }); + }); + }); +}); diff --git a/src/model/events/model-destroyed-event.ts b/src/model/events/model-destroyed-event.ts new file mode 100644 index 00000000..24eb1e21 --- /dev/null +++ b/src/model/events/model-destroyed-event.ts @@ -0,0 +1,27 @@ +import { Observable } from 'rxjs'; +import { filter, map, take } from 'rxjs/operators'; +import { DashboardEvent } from '../../communication/dashboard-event'; +import { DashboardEventManager } from '../../communication/dashboard-event-manager'; + +export const modelDestroyedEventKey = Symbol('Model destroyed'); + +/** + * Fired after a model is destroyed and any destroy hooks are called. + */ +export class ModelDestroyedEvent extends DashboardEvent { + public constructor(dashboardEventManager: DashboardEventManager) { + super(dashboardEventManager, modelDestroyedEventKey); + } + + /** + * Returns a void observable that will notify once when the provided model is + * destroyed, then complete. + */ + public getDestructionObservable(model: object): Observable { + return this.getObservable().pipe( + filter(destroyedModel => destroyedModel === model), + map(_ => undefined), + take(1) + ); + } +} diff --git a/src/model/events/model-event-installer.test.ts b/src/model/events/model-event-installer.test.ts new file mode 100644 index 00000000..4b06ec42 --- /dev/null +++ b/src/model/events/model-event-installer.test.ts @@ -0,0 +1,355 @@ +import { CompletionObserver, EMPTY, ErrorObserver, NextObserver, Observable, PartialObserver, Subject } from 'rxjs'; +import { DashboardEventManager } from '../../communication/dashboard-event-manager'; +import { ModelScopedDashboardEvent } from '../../communication/model-scoped-dashboard-event'; +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { getTestScheduler } from '../../test/rxjs-jest-test-scheduler'; +import { Logger } from '../../util/logging/logger'; +import { Model } from '../registration/model-decorators'; +import { ModelDestroyedEvent } from './model-destroyed-event'; +import { + ModelEventInstaller, + ModelEventMetadataType, + ModelEventPublisher, + ModelEventSubscriber +} from './model-event-installer'; + +describe('Model event installer', () => { + class MockModelClass { + public constructor( + public readonly onEvent: () => void = jest.fn(), + public event$: PartialObserver | Observable | undefined = new Subject() + ) {} + } + let modelEventInstaller: ModelEventInstaller; + let mockDashboardEventManager: PartialObjectMock; + let mockModelDestroyedEvent: PartialObjectMock; + let mockLogger: PartialObjectMock; + let mockModel: MockModelClass; + let eventObservable: Observable; + let destructionObservable: Observable; + + const eventKey = Symbol('event key'); + beforeEach(() => { + mockDashboardEventManager = { + getObservableForEvent: jest.fn(requestedKey => (requestedKey === eventKey ? eventObservable : EMPTY)), + publishEvent: jest.fn() + }; + mockModelDestroyedEvent = { + getDestructionObservable: jest.fn(() => destructionObservable) + }; + mockLogger = { + warn: jest.fn() + }; + mockModel = new MockModelClass(); + + modelEventInstaller = new ModelEventInstaller( + mockDashboardEventManager as DashboardEventManager, + mockModelDestroyedEvent as ModelDestroyedEvent, + mockLogger as Logger + ); + }); + + test('should install subscriber on registered methods', () => { + getTestScheduler().run(({ hot, flush }) => { + modelEventInstaller.registerModelEvent(MockModelClass, 'onEvent', eventKey, ModelEventMetadataType.Subscriber); + // Destroy before c should be passed along + eventObservable = hot(' a-b-c'); + destructionObservable = hot('---(a|)'); + + modelEventInstaller.decorate(mockModel); + + flush(); + + expect(mockModel.onEvent).toHaveBeenCalledTimes(2); + expect(mockModel.onEvent).nthCalledWith(1, 'a'); + expect(mockModel.onEvent).nthCalledWith(2, 'b'); + }); + }); + + test('should install subscriber on registered properties', () => { + getTestScheduler().run(({ hot, expectObservable }) => { + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Subscriber); + // Destroy before c should be passed along + eventObservable = hot(' a-b-c'); + destructionObservable = hot('---(a|)'); + + modelEventInstaller.decorate(mockModel); + + expectObservable(mockModel.event$ as Observable).toBe('a-b|'); + }); + }); + + test('should warn if subscriber property is not set', () => { + getTestScheduler().run(({ hot, flush }) => { + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Subscriber); + + eventObservable = hot('a'); + destructionObservable = hot('-a'); + + mockModel.event$ = undefined; + + modelEventInstaller.decorate(mockModel); + flush(); + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Cannot subscribe to property [event$] - must be function or Observer' + ); + }); + }); + + test('should warn if subscriber property is an object that is not a partial observer', () => { + getTestScheduler().run(({ hot, flush }) => { + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Subscriber); + + eventObservable = hot('a'); + destructionObservable = hot('-a'); + + mockModel.event$ = ({} as unknown) as Subject; + + modelEventInstaller.decorate(mockModel); + flush(); + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Cannot subscribe to property [event$] - must be function or Observer' + ); + }); + }); + + test('should detect different flavors of observers', () => { + getTestScheduler().run(({ hot, flush }) => { + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Subscriber); + + // NextObserver + eventObservable = hot('ab-c'); + destructionObservable = hot('--a'); + mockModel.event$ = { + next: jest.fn() + }; + + modelEventInstaller.decorate(mockModel); + flush(); + expect((mockModel.event$ as NextObserver).next).toHaveBeenCalledTimes(2); + expect((mockModel.event$ as NextObserver).next).nthCalledWith(1, 'a'); + expect((mockModel.event$ as NextObserver).next).nthCalledWith(2, 'b'); + + // CompletionObserver + eventObservable = hot('ab-|'); + destructionObservable = hot('--a'); + + mockModel.event$ = { + complete: jest.fn() + }; + + modelEventInstaller.decorate(mockModel); + flush(); + expect((mockModel.event$ as CompletionObserver).complete).toHaveBeenCalledTimes(1); + + // ErrorObserver + eventObservable = hot('ab#|'); + destructionObservable = hot('--a'); + + mockModel.event$ = { + error: jest.fn() + }; + + modelEventInstaller.decorate(mockModel); + flush(); + expect((mockModel.event$ as ErrorObserver).error).toHaveBeenCalledTimes(1); + }); + }); + + test('should install publisher on registered properties', () => { + getTestScheduler().run(({ hot, flush }) => { + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Publisher); + // Destroy before c should be passed along + destructionObservable = hot('a'); + + modelEventInstaller.decorate(mockModel); + (mockModel.event$ as NextObserver).next('a'); + (mockModel.event$ as NextObserver).next('b'); + + expect(mockDashboardEventManager.publishEvent).toHaveBeenCalledTimes(2); + expect(mockDashboardEventManager.publishEvent).nthCalledWith(1, eventKey, 'a'); + expect(mockDashboardEventManager.publishEvent).nthCalledWith(2, eventKey, 'b'); + + flush(); + // No more events should go through now that destruction is flushed + + (mockModel.event$ as NextObserver).next('c'); + + expect(mockDashboardEventManager.publishEvent).toHaveBeenCalledTimes(2); + + expect(mockLogger.warn).not.toHaveBeenCalled(); + }); + }); + + test('should warn if publisher is not an Observable', () => { + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Publisher); + mockModel.event$ = undefined; + + modelEventInstaller.decorate(mockModel); + + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Cannot publish from property [event$] - must be an instanceof Observable' + ); + }); + + test('should support registering two subscribers on the same property', () => { + getTestScheduler().run(({ hot, flush }) => { + const secondEventKey = Symbol('second event'); + + modelEventInstaller.registerModelEvent(MockModelClass, 'onEvent', eventKey, ModelEventMetadataType.Subscriber); + modelEventInstaller.registerModelEvent( + MockModelClass, + 'onEvent', + secondEventKey, + ModelEventMetadataType.Subscriber + ); + + mockDashboardEventManager.getObservableForEvent = jest.fn(providedEventKey => { + if (providedEventKey === secondEventKey) { + return hot('a-b-'); + } + + return hot('-x-y'); + }); + + modelEventInstaller.decorate(mockModel); + + flush(); + + expect(mockModel.onEvent).toHaveBeenCalledTimes(4); + expect(mockModel.onEvent).nthCalledWith(1, 'a'); + expect(mockModel.onEvent).nthCalledWith(2, 'x'); + expect(mockModel.onEvent).nthCalledWith(3, 'b'); + expect(mockModel.onEvent).nthCalledWith(4, 'y'); + }); + }); + + test('should support publishing and subscribing from the same property', () => { + getTestScheduler().run(({ hot, expectObservable }) => { + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Subscriber); + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Publisher); + eventObservable = hot('--xy'); + modelEventInstaller.decorate(mockModel); + (mockModel.event$ as NextObserver).next('a'); + (mockModel.event$ as NextObserver).next('b'); + + expect(mockDashboardEventManager.publishEvent).toHaveBeenCalledTimes(2); + expect(mockDashboardEventManager.publishEvent).nthCalledWith(1, eventKey, 'a'); + expect(mockDashboardEventManager.publishEvent).nthCalledWith(2, eventKey, 'b'); + + expectObservable(mockModel.event$ as Observable).toBe('--xy'); + }); + }); + + test('should support publishing with a resolved event key', () => { + const secondEventKey = Symbol('second key'); + modelEventInstaller = new (class extends ModelEventInstaller { + protected resolveEventKey: () => symbol = jest.fn().mockReturnValue(secondEventKey); + })( + mockDashboardEventManager as DashboardEventManager, + mockModelDestroyedEvent as ModelDestroyedEvent, + mockLogger as Logger + ); + + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Publisher); + + modelEventInstaller.decorate(mockModel); + (mockModel.event$ as NextObserver).next('z'); + + expect(mockDashboardEventManager.publishEvent).toHaveBeenCalledTimes(1); + expect(mockDashboardEventManager.publishEvent).nthCalledWith(1, secondEventKey, 'z'); + }); + + test('should support subscribing with a resolved event key', () => { + getTestScheduler().run(({ hot, expectObservable }) => { + const secondEventKey = Symbol('second key'); + modelEventInstaller = new (class extends ModelEventInstaller { + protected resolveEventKey: () => symbol = jest.fn().mockReturnValue(secondEventKey); + })( + mockDashboardEventManager as DashboardEventManager, + mockModelDestroyedEvent as ModelDestroyedEvent, + mockLogger as Logger + ); + mockDashboardEventManager.getObservableForEvent = jest.fn(requestedKey => + requestedKey === secondEventKey ? hot('q') : hot('a') + ); + + modelEventInstaller.registerModelEvent(MockModelClass, 'event$', eventKey, ModelEventMetadataType.Subscriber); + + modelEventInstaller.decorate(mockModel); + + expectObservable(mockModel.event$ as Observable).toBe('q'); + }); + }); + + test('published events are given model context if a model scoped event', () => { + const modelScopedEventKey = new ModelScopedDashboardEvent(mockDashboardEventManager as DashboardEventManager); + + modelEventInstaller.registerModelEvent( + MockModelClass, + 'event$', + modelScopedEventKey, + ModelEventMetadataType.Publisher + ); + + modelEventInstaller.decorate(mockModel); + (mockModel.event$ as NextObserver).next('z'); + + expect(mockDashboardEventManager.publishEvent).toHaveBeenCalledTimes(1); + expect(mockDashboardEventManager.publishEvent).toBeCalledWith(modelScopedEventKey, { + data: 'z', + source: mockModel + }); + }); + + test('subscribed events are unwrapped from model context if a model scoped event', () => { + getTestScheduler().run(({ hot, expectObservable }) => { + const modelScopedEventKey = new ModelScopedDashboardEvent(mockDashboardEventManager as DashboardEventManager); + modelEventInstaller.registerModelEvent( + MockModelClass, + 'event$', + modelScopedEventKey, + ModelEventMetadataType.Subscriber + ); + + mockDashboardEventManager.getObservableForEvent = jest.fn(requestedKey => { + if (requestedKey === modelScopedEventKey) { + return hot('a', { a: { data: 'foo', source: mockModel } }); + } + + return hot(''); + }); + modelEventInstaller.decorate(mockModel); + + expectObservable(mockModel.event$ as Observable).toBe('a', { a: 'foo' }); + }); + }); + + test('can register event subscribers and publishers from decorators', () => { + // tslint:disable-next-line: max-classes-per-file + @Model({ + type: 'test-event-decorators' + }) + class TestEventDecoratorsClass { + @ModelEventSubscriber(eventKey) + public onEvent: () => void = jest.fn(); + + @ModelEventPublisher(eventKey) + public event$: Subject = new Subject(); + } + + getTestScheduler().run(({ cold, flush }) => { + eventObservable = cold('a'); + const model = new TestEventDecoratorsClass(); + modelEventInstaller.decorate(model); + + flush(); + expect(model.onEvent).toHaveBeenCalledTimes(1); + expect(model.onEvent).toHaveBeenCalledWith('a'); + + model.event$.next('foo'); + expect(mockDashboardEventManager.publishEvent).toHaveBeenCalledTimes(1); + expect(mockDashboardEventManager.publishEvent).toHaveBeenCalledWith(eventKey, 'foo'); + }); + }); +}); diff --git a/src/model/events/model-event-installer.ts b/src/model/events/model-event-installer.ts new file mode 100644 index 00000000..62980b90 --- /dev/null +++ b/src/model/events/model-event-installer.ts @@ -0,0 +1,282 @@ +// tslint:disable:strict-type-predicates TODO - re-enable, does not work well with unknowns +import { CompletionObserver, ErrorObserver, NextObserver, Observable, PartialObserver } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DashboardEventKey, DashboardEventManager } from '../../communication/dashboard-event-manager'; +import { ModelScopedDashboardEvent } from '../../communication/model-scoped-dashboard-event'; +import { Constructable, ObjectConstructable } from '../../util/constructable'; +import { Logger } from '../../util/logging/logger'; +import { ModelDecorator } from '../manager/model-manager'; +import { ModelDestroyedEvent } from './model-destroyed-event'; + +/** + * Hooks up model event subscribers and publishers to the event system + */ +export class ModelEventInstaller implements ModelDecorator { + private readonly modelEventMetadata: Map = new Map(); + private lastDeferredIndexRead: number = 0; + + public constructor( + private readonly dashboardEventManager: DashboardEventManager, + private readonly modelDestroyedEvent: ModelDestroyedEvent, + private readonly logger: Logger + ) {} + + /** + * Hooks up model event subscribers and publishers properties in modelInstance based on + * those registered to modelInstance's model type + */ + public decorate(modelInstance: object): void { + const eventMetadata = this.lookupModelEvents(modelInstance.constructor as ObjectConstructable); + + eventMetadata.forEach(metadata => { + const propertyKey = metadata.propertyKey as keyof typeof modelInstance; + const eventKey = this.resolveEventKey(metadata.eventKey); + switch (metadata.type) { + case ModelEventMetadataType.Subscriber: + this.installEventSubscriber(modelInstance, eventKey, propertyKey); + break; + case ModelEventMetadataType.Publisher: + this.installEventPublisher(modelInstance, eventKey, propertyKey); + break; + /* istanbul ignore next */ + default: + } + }); + } + + /** + * Registers a model event. This property will be hooked into the event system as a publisher or subscriber + * of the specified event for each model instance instantiated of this type. + */ + public registerModelEvent( + modelClass: Constructable, + propertyKey: keyof T, + eventKey: DashboardEventKey, + type: ModelEventMetadataType + ): void { + if (!this.modelEventMetadata.has(modelClass)) { + this.modelEventMetadata.set(modelClass, []); + } + + const eventMetadataArray = this.modelEventMetadata.get(modelClass)!; + + eventMetadataArray.push({ + propertyKey: propertyKey, + eventKey: eventKey, + type: type + }); + } + + /** + * A hook to allow extended implementations to support other systems such as Dependency + * Injection + */ + protected resolveEventKey(providedKey: DashboardEventKey): DashboardEventKey { + return providedKey; + } + + private installEventSubscriber( + modelInstance: T, + eventKey: DashboardEventKey, + propertyKey: keyof T + ): void { + this.getObservableForModel(modelInstance, eventKey) + .pipe(takeUntil(this.modelDestroyedEvent.getDestructionObservable(modelInstance))) + .subscribe(this.getSubscriberAsObserver(modelInstance, propertyKey)); + } + + private installEventPublisher( + modelInstance: T, + eventKey: DashboardEventKey, + propertyKey: keyof T + ): void { + const publisherPropertyValue = modelInstance[propertyKey] as unknown; + + if (publisherPropertyValue instanceof Observable) { + publisherPropertyValue + .pipe(takeUntil(this.modelDestroyedEvent.getDestructionObservable(modelInstance))) + .subscribe((value: unknown) => this.getPublishFunctionForModel(modelInstance, eventKey)(value)); + } else { + this.logger + // tslint:disable-next-line:max-line-length + .warn(`Cannot publish from property [${String(propertyKey)}] - must be an instanceof Observable`); + } + } + + private getSubscriberAsObserver( + modelInstance: T, + subscriberKey: keyof T + ): PartialObserver { + const subscriberValue = modelInstance[subscriberKey]; + + if (typeof subscriberValue === 'function') { + return { + next: (subscriberValue as unknown) as (value: unknown) => void + }; + } + if (this.isObserver(subscriberValue)) { + return subscriberValue; + } + + this.logger.warn(`Cannot subscribe to property [${String(subscriberKey)}] - must be function or Observer`); + + return { + next: () => { + /*NOOP*/ + } + }; + } + + private isObserver(value: unknown): value is PartialObserver { + if (value === null || typeof value !== 'object') { + return false; + } + + if (typeof (value as NextObserver).next === 'function') { + return true; + } + + if (typeof (value as CompletionObserver).complete === 'function') { + return true; + } + + if (typeof (value as ErrorObserver).error === 'function') { + return true; + } + + return false; + } + + private getObservableForModel(model: object, eventKey: DashboardEventKey): Observable { + if (this.eventKeyIsModelScoped(eventKey)) { + return eventKey.getObservableForModel(model); + } + + return this.dashboardEventManager.getObservableForEvent(eventKey); + } + + private getPublishFunctionForModel(model: object, eventKey: DashboardEventKey): (data: unknown) => void { + if (this.eventKeyIsModelScoped(eventKey)) { + return data => + eventKey.publish({ + data: data, + source: model + }); + } + + return data => this.dashboardEventManager.publishEvent(eventKey, data); + } + + private eventKeyIsModelScoped(eventKey: DashboardEventKey): eventKey is ModelScopedDashboardEvent { + return eventKey instanceof ModelScopedDashboardEvent; + } + + private lookupModelEvents(modelClass: ObjectConstructable): ModelEventMetadata[] { + this.processRegistrationQueue(); + + return this.getConstructorChain(modelClass) + .reverse() + .reduce( + (metadata, constructor) => metadata.concat(this.modelEventMetadata.get(constructor) || []), + [] + ); + } + + private getConstructorChain(constructor: ObjectConstructable): ObjectConstructable[] { + let currentConstructor: ObjectConstructable | undefined = constructor; + const constructorChain: ObjectConstructable[] = []; + + while (currentConstructor) { + constructorChain.push(currentConstructor); + currentConstructor = Object.getPrototypeOf(currentConstructor) as ObjectConstructable | undefined; + } + + return constructorChain; + } + + private processRegistrationQueue(): void { + for ( + this.lastDeferredIndexRead; + this.lastDeferredIndexRead < deferredRegistrations.length; + this.lastDeferredIndexRead++ + ) { + const deferredRegistration = deferredRegistrations[this.lastDeferredIndexRead]; + deferredRegistration(this); + } + } +} + +type DeferredRegistration = (installer: ModelEventInstaller) => void; +const deferredRegistrations: DeferredRegistration[] = []; + +/** + * Registers the decorated property or method as a subscriber for the provided event key or event key provider. + * + * As a property, an event subscriber must be instantiated to an object that implements the RxJS `Observer` interface. + * As a method, an event subscriber will be invoked on each `Observer.next`, and provided as an argument any data + * included with the event. + */ +// tslint:disable-next-line:only-arrow-functions +export function ModelEventSubscriber(event: DashboardEventKey): MethodDecorator & PropertyDecorator { + return (modelPrototype: object, propertyKey: string | symbol): void => { + deferredRegistrations.push(installer => + installer.registerModelEvent( + modelPrototype.constructor as ObjectConstructable, + propertyKey as keyof object, + event, + ModelEventMetadataType.Subscriber + ) + ); + }; +} + +/** + * Registers the decorated property or method as a publisher for the provided event key or event key provider. + * + * The property must be insantiated to an object that extends the RxJS `Observable` class. + */ +// tslint:disable-next-line:only-arrow-functions +export function ModelEventPublisher(event: DashboardEventKey): PropertyDecorator { + return (modelPrototype: object, propertyKey: string | symbol): void => { + deferredRegistrations.push(installer => + installer.registerModelEvent( + modelPrototype.constructor as ObjectConstructable, + propertyKey as keyof object, + event, + ModelEventMetadataType.Publisher + ) + ); + }; +} + +/** + * Metadata describing an event system hook on a model property + */ +export interface ModelEventMetadata { + /** + * Key for the referenced event + */ + eventKey: DashboardEventKey; + /** + * Runtime key for the referencing model property + */ + propertyKey: string | symbol | number; + /** + * Type of event system hook + */ + type: ModelEventMetadataType; +} + +/** + * Indicates the type of event action metadata registered for a model + */ +export const enum ModelEventMetadataType { + /** + * An event subscriber + */ + Subscriber, + /** + * An event publisher + */ + Publisher +} diff --git a/src/model/manager/model-lifecycle-hooks.ts b/src/model/manager/model-lifecycle-hooks.ts new file mode 100644 index 00000000..f5afb21b --- /dev/null +++ b/src/model/manager/model-lifecycle-hooks.ts @@ -0,0 +1,24 @@ +/** + * A lifecycle hook that is invoked during model construction after all properties + * have been assigned and potentially deserialized and initialized. + */ +export interface ModelOnInit { + /** + * Callback method which is invoked during construction and should include any + * initialization logic. + */ + modelOnInit(): void; +} + +/** + * A lifecycle hook that is invoked during model destruction, before the model has + * been removed from all systems, and before the destruction event has been broadcast. + * All children are destroyed before this is invoked. + */ +export interface ModelOnDestroy { + /** + * Callback method which is invoked during destruction and should include any + * cleanup logic. + */ + modelOnDestroy(): void; +} diff --git a/src/model/manager/model-manager.test.ts b/src/model/manager/model-manager.test.ts new file mode 100644 index 00000000..a0c01e4c --- /dev/null +++ b/src/model/manager/model-manager.test.ts @@ -0,0 +1,352 @@ +// tslint:disable:completed-docs +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { Logger } from '../../util/logging/logger'; +import { ModelApiBuilder } from '../api/builder/model-api-builder'; +import { ModelApi } from '../api/model-api'; +import { ModelCreatedEvent } from '../events/model-created-event'; +import { ModelDestroyedEvent } from '../events/model-destroyed-event'; +import { ModelOnDestroy, ModelOnInit } from './model-lifecycle-hooks'; +import { ModelManager } from './model-manager'; + +describe('Model manager', () => { + const testClass = class TestClass {}; + let manager: ModelManager; + let mockLogger: PartialObjectMock; + let mockApiBuilder: PartialObjectMock>; + + let mockCreatedEvent: PartialObjectMock; + let mockDestroyedEvent: PartialObjectMock; + + beforeEach(() => { + mockCreatedEvent = { + publish: jest.fn() + }; + + mockDestroyedEvent = { + publish: jest.fn() + }; + + mockLogger = { + warn: jest.fn((message: string) => ({ + throw: jest.fn(() => { + throw Error(message); + }) + })) + }; + + mockApiBuilder = { + matches: () => true, + // tslint:disable-next-line: no-object-literal-type-assertion + build: () => ({} as ModelApi) + }; + + manager = new ModelManager( + mockLogger as Logger, + mockCreatedEvent as ModelCreatedEvent, + mockDestroyedEvent as ModelDestroyedEvent + ); + + manager.registerModelApiBuilder(mockApiBuilder as ModelApiBuilder); + }); + + test('allows constructing new models', () => { + const instance = manager.construct(testClass); + expect(instance.constructor).toBe(testClass); + }); + + test('allows constructing a model with a parent', () => { + const parent = manager.construct(testClass); + const child = manager.construct(testClass, parent); + expect(manager.getChildren(parent)).toEqual([child]); + expect(manager.getParent(child)).toEqual(parent); + }); + + test('throws error constructing if provided parent is not tracked', () => { + expect(() => manager.construct(testClass, new testClass())).toThrow( + 'Could not retrieve data for provided instance, it has not been registered' + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Could not retrieve data for provided instance, it has not been registered' + ); + }); + + test('get children returns empty array if no children', () => { + const instance = manager.construct(testClass); + expect(manager.getChildren(instance)).toEqual([]); + }); + + test('throws error retrieving children if not tracked', () => { + expect(() => manager.getChildren(new testClass())).toThrow( + 'Could not retrieve data for provided instance, it has not been registered' + ); + + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Could not retrieve data for provided instance, it has not been registered' + ); + }); + + test('returns undefined for an instance with no parent', () => { + const root = manager.construct(testClass); + expect(manager.getParent(root)).toBeUndefined(); + }); + + test('protects data from mutation', () => { + const parent = manager.construct(testClass); + const child = manager.construct(testClass, parent); + manager.getChildren(parent).push(new testClass()); + expect(manager.getChildren(parent)).toEqual([child]); + }); + + test('throws error retrieving parent if not tracked', () => { + expect(() => manager.getParent(new testClass())).toThrow( + 'Could not retrieve data for provided instance, it has not been registered' + ); + + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Could not retrieve data for provided instance, it has not been registered' + ); + }); + + test('allows destroying models', () => { + const instance = manager.construct(testClass); + manager.destroy(instance); + expect(() => manager.getChildren(instance)).toThrow( + 'Could not retrieve data for provided instance, it has not been registered' + ); + + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Could not retrieve data for provided instance, it has not been registered' + ); + }); + + test('destroy succeeds if not registered or primitive', () => { + expect(() => manager.destroy(new testClass())).not.toThrow(); + + expect(() => manager.destroy(15)).not.toThrow(); + }); + + test('destroy finds nested models', () => { + const parent = manager.construct(testClass); + + const instance1 = manager.construct(testClass, parent); + const arrayWithInstance1 = [instance1]; + const instance2 = manager.construct(testClass, parent); + const objectWithInstance2 = { key: instance2 }; + const instance3 = manager.construct(testClass, parent); + const nestedArrayWithInstance3 = { key: [instance3] }; + const instance4 = manager.construct(testClass, parent); + const nestedObjectWithInstance4 = [{ key: instance4 }]; + + expect(manager.getChildren(parent).length).toBe(4); + + manager.destroy(arrayWithInstance1); + expect(manager.getChildren(parent).length).toBe(3); + expect(manager.getChildren(parent)).toEqual(expect.arrayContaining([instance2, instance3, instance4])); + + manager.destroy(objectWithInstance2); + expect(manager.getChildren(parent).length).toBe(2); + expect(manager.getChildren(parent)).toEqual(expect.arrayContaining([instance3, instance4])); + + manager.destroy(nestedArrayWithInstance3); + expect(manager.getChildren(parent).length).toBe(1); + expect(manager.getChildren(parent)).toEqual(expect.arrayContaining([instance4])); + + manager.destroy(nestedObjectWithInstance4); + expect(manager.getChildren(parent).length).toBe(0); + }); + + test('recursively destroys children', () => { + const root = manager.construct(testClass); + const firstChild = manager.construct(testClass, root); + const secondChild = manager.construct(testClass, root); + const firstChildOfFirstChild = manager.construct(testClass, firstChild); + manager.destroy(root); + + expect(() => manager.getChildren(root)).toThrow(); + expect(() => manager.getChildren(firstChild)).toThrow(); + expect(() => manager.getChildren(secondChild)).toThrow(); + expect(() => manager.getChildren(firstChildOfFirstChild)).toThrow(); + }); + + test('cleans up parent data after deleting child', () => { + const root = manager.construct(testClass); + const firstChild = manager.construct(testClass, root); + const secondChild = manager.construct(testClass, root); + + manager.destroy(firstChild); + + expect(manager.getChildren(root)).toEqual([secondChild]); + }); + + test('fires init lifecycle hook', () => { + const initSpy = jest.fn(); + const initModelClass = class ModelClass implements ModelOnInit { + public modelOnInit: () => void = initSpy; + }; + + const model = manager.construct(initModelClass); + + expect(initSpy).not.toHaveBeenCalled(); // Doesn't init during construct + + manager.initialize(model); + + expect(initSpy).toHaveBeenCalledTimes(1); + }); + + test('safely skips initialize if no init hook', () => { + expect(manager.initialize(new testClass())).toEqual(expect.any(testClass)); + }); + + test('fires destroy lifecycle hook', () => { + const destroyModelClass = class ModelClass implements ModelOnDestroy { + public modelOnDestroy(): void { + throw Error('Method not implemented.'); + } + }; + + const destroySpy = spyOn(destroyModelClass.prototype, 'modelOnDestroy'); + + const destroyModelInstance = manager.construct(destroyModelClass); + expect(destroySpy).not.toHaveBeenCalled(); + + manager.destroy(destroyModelInstance); + expect(destroySpy).toHaveBeenCalledTimes(1); + }); + + test('create calls construct then init', () => { + const mockConstructResult = {}; + manager.construct = jest.fn().mockReturnValue(mockConstructResult); + manager.initialize = jest.fn(); + + manager.create(testClass); + + expect(manager.construct).toHaveBeenCalled(); + + expect(manager.initialize).toHaveBeenCalledWith(mockConstructResult); + }); + + test('fires lifecycle events', () => { + const root = manager.construct(testClass); + const firstChild = manager.construct(testClass, root); + + expect(mockCreatedEvent.publish).toHaveBeenCalledTimes(2); + expect(mockCreatedEvent.publish).toHaveBeenNthCalledWith(1, root); + expect(mockCreatedEvent.publish).toHaveBeenNthCalledWith(2, firstChild); + + manager.destroy(root); + + expect(mockDestroyedEvent.publish).toHaveBeenCalledTimes(2); + expect(mockDestroyedEvent.publish).toHaveBeenNthCalledWith(1, firstChild); + expect(mockDestroyedEvent.publish).toHaveBeenNthCalledWith(2, root); + }); + + test('can find root model', () => { + const root = manager.construct(testClass); + const firstChild = manager.construct(testClass, root); + const secondChild = manager.construct(testClass, root); + const firstChildOfFirstChild = manager.construct(testClass, firstChild); + + expect(manager.getRoot(firstChild)).toBe(root); + expect(manager.getRoot(secondChild)).toBe(root); + expect(manager.getRoot(firstChildOfFirstChild)).toBe(root); + expect(manager.getRoot(root)).toBe(root); + }); + + test('logs and throws error if attempting to find root of untracked model', () => { + expect(() => manager.getRoot({})).toThrow( + 'Could not retrieve data for provided instance, it has not been registered' + ); + expect(mockLogger.warn).toHaveBeenCalled(); + }); + + test('can determine if ancestor', () => { + const root = manager.construct(testClass); + const firstChild = manager.construct(testClass, root); + const secondChild = manager.construct(testClass, root); + const firstChildOfFirstChild = manager.construct(testClass, firstChild); + + expect(manager.isAncestor(firstChild, root)).toBe(true); + expect(manager.isAncestor(secondChild, root)).toBe(true); + expect(manager.isAncestor(firstChildOfFirstChild, root)).toBe(true); + expect(manager.isAncestor(firstChildOfFirstChild, firstChild)).toBe(true); + expect(manager.isAncestor(firstChildOfFirstChild, secondChild)).toBe(false); + expect(manager.isAncestor(firstChild, secondChild)).toBe(false); + expect(manager.isAncestor(secondChild, firstChild)).toBe(false); + + // Not ancestor of self + expect(manager.isAncestor(firstChild, firstChild)).toBe(false); + // Should handle if potential ancestor is not registered + expect(manager.isAncestor(firstChild, {})).toBe(false); + }); + + test('logs and throws error if attempting to find ancestor of untracked node', () => { + const root = manager.construct(testClass); + + expect(() => manager.isAncestor({}, root)).toThrow( + 'Could not retrieve data for provided instance, it has not been registered' + ); + expect(mockLogger.warn).toHaveBeenCalled(); + }); + + test('delegates to correct model api builder', () => { + manager = new ModelManager( + mockLogger as Logger, + mockCreatedEvent as ModelCreatedEvent, + mockDestroyedEvent as ModelDestroyedEvent + ); // Rebuild so we don't use the mock api build from other tests + + const modelAClass = class {}; + const modelBClass = class {}; + + const builderForA = { + matches: (model: object) => model instanceof modelAClass, + build: jest.fn() + }; + + const builderForB = { + matches: (model: object) => model instanceof modelBClass, + build: jest.fn() + }; + + mockLogger.error = jest.fn((error: string) => ({ + throw: () => { + throw Error(error); + } + })); + + manager.registerModelApiBuilder(builderForA); + manager.registerModelApiBuilder(builderForB); + + manager.construct(modelAClass); + expect(builderForA.build).toHaveBeenCalledTimes(1); + + manager.construct(modelBClass); + expect(builderForB.build).toHaveBeenCalledTimes(1); + + expect(() => manager.construct(class {})).toThrow(); + expect(mockLogger.error).toHaveBeenCalledWith('No model API builder registered matching provided model'); + }); + + test('invokes any registered decorators on model creation', () => { + const decorator = { + decorate: jest.fn() + }; + + manager.registerDecorator(decorator); + const instance = manager.construct(testClass); + expect(decorator.decorate).toHaveBeenCalledWith(instance, mockApiBuilder.build!(instance)); + }); + + test('determines if a provided value is tracked or not', () => { + const instance = manager.construct(testClass); + expect(manager.isTrackedModel(instance)).toBe(true); + + expect(manager.isTrackedModel(undefined)).toBe(false); + // tslint:disable-next-line:no-null-keyword + expect(manager.isTrackedModel(null)).toBe(false); + expect(manager.isTrackedModel([])).toBe(false); + expect(manager.isTrackedModel(new testClass())).toBe(false); + expect(manager.isTrackedModel({})).toBe(false); + expect(manager.isTrackedModel('string')).toBe(false); + }); +}); diff --git a/src/model/manager/model-manager.ts b/src/model/manager/model-manager.ts new file mode 100644 index 00000000..98aaae7b --- /dev/null +++ b/src/model/manager/model-manager.ts @@ -0,0 +1,269 @@ +import { cloneDeepWith, without } from 'lodash'; +import { Constructable } from '../../util/constructable'; +import { Logger } from '../../util/logging/logger'; +import { ModelApiBuilder } from '../api/builder/model-api-builder'; +import { ModelApi } from '../api/model-api'; +import { ModelCreatedEvent } from '../events/model-created-event'; +import { ModelDestroyedEvent } from '../events/model-destroyed-event'; +import { ModelOnDestroy, ModelOnInit } from './model-lifecycle-hooks'; + +/** + * Model Manager creates, destroys and tracks existing models. It is used to maintain relationships between + * models. + */ +export class ModelManager { + private readonly modelInstanceMap: WeakMap = new WeakMap(); + private readonly apiBuilders: ModelApiBuilder[] = []; + private readonly decorators: ModelDecorator[] = []; + + public constructor( + private readonly logger: Logger, + private readonly modelCreatedEvent: ModelCreatedEvent, + private readonly modelDestroyedEvent: ModelDestroyedEvent + ) {} + + /** + * Constructs (@see `ModelManager.construct`) then initializes (@see `ModelManager.initialize`) it + * + * Throws Error if a parent is provided which is not tracked + */ + public create(modelClass: Constructable, parent?: object): T { + return this.initialize(this.construct(modelClass, parent)); + } + + /** + * Initializes the provided model instance, calling appropriate lifecycle hooks and marking it + * ready. + */ + public initialize(modelInstance: T): T { + if (this.modelHasInitHook(modelInstance)) { + modelInstance.modelOnInit(); + } + + return modelInstance; + } + + /** + * Constructs the provided class, tracking its relationships to other models based on the provided + * parent. + * + * Models must be created through this method and cannot take constructor parameters. + * + * This does not initialize the model, which must be done separately. @see `ModelManager.initialize` + * + * Throws Error if a parent is provided which is not tracked + */ + public construct(modelClass: Constructable, parent?: object): T { + const instance = new modelClass(); + + this.modelInstanceMap.set(instance, { + parent: parent, + children: [] + }); + + if (parent) { + this.trackNewChild(parent, instance); + } + + const modelApi = this.buildApiForModel(instance); + + this.decorators.forEach(decorator => decorator.decorate(instance, modelApi)); + + this.modelCreatedEvent.publish(instance); + + return instance; + } + + /** + * Untracks any model instances descending from the provided value. + * + * If `value` is a model, it will be untracked along with its descendents, starting from the leaf of the model tree. + * That is, a child will always be destroyed before its parent. + * + * If `value` is an array, each of its object-typed values will be recursively passed to this function. + * + * If `value` is a non-model, non-array object, each of its object-typed values will be recursively passed to this + * function. + * + * If the value is a primitve or no model is found, no action is taken. + */ + public destroy(value: unknown): void { + if (typeof value !== 'object' || !value) { + return; + } + if (this.modelInstanceMap.has(value)) { + this.destroyModel(value); + } else if (Array.isArray(value)) { + value.forEach((arrayValue: unknown) => this.destroy(arrayValue)); + } else { + Object.values(value).forEach((objectValue: unknown) => this.destroy(objectValue)); + } + } + + /** + * Returns a copy of the children registered to the provided model. + * + * Throws Error if the provided instance is not tracked + */ + public getChildren(modelInstance: object): object[] { + return this.getInstanceDataOrThrow(modelInstance).children; + } + + /** + * Returns the parent registered to the provided model, or undefined if + * no parent is registered. + * + * Throws Error if the provided instance is not tracked + */ + public getParent(modelInstance: object): object | undefined { + return this.getInstanceDataOrThrow(modelInstance).parent; + } + + /** + * Returns the root node in the model tree to which the provided instance + * belongs. Returns itself if the provided node is a root. + * + * Throws Error if the provided instance is not tracked + */ + public getRoot(modelInstance: object): object { + let currentModel = modelInstance; + let currentModelParent = this.getParent(currentModel); + while (currentModelParent) { + currentModel = currentModelParent; + currentModelParent = this.getParent(currentModel); + } + + return currentModel; + } + + /** + * Returns true if `potentialAncestor` is an ancestor of `model`. + * Returns false otherwise, including if `model === potentialAncestor`. + * Throws Error if `model` is not tracked + */ + public isAncestor(model: object, potentialAncestor: object): boolean { + let currentAncestor: object | undefined = model; + while (currentAncestor) { + currentAncestor = this.getParent(currentAncestor); + if (currentAncestor === potentialAncestor) { + return true; + } + } + + return false; + } + + /** + * Adds the provided API builder to the search list. The first builder that matches a given model, + * in the order registered, will be used. + */ + public registerModelApiBuilder(modelApiBuilder: ModelApiBuilder): void { + this.apiBuilders.push(modelApiBuilder); + } + + /** + * Returns true if the provided value is a tracked model, false otherwise + */ + public isTrackedModel(value: unknown): boolean { + if (typeof value !== 'object' || value === null) { + return false; + } + + return this.modelInstanceMap.has(value); + } + + /** + * Registeres a ModelDecorator which will be called when creating all future + * model instances. @see `ModelDecorator` + */ + public registerDecorator(decorator: ModelDecorator): void { + this.decorators.push(decorator); + } + + private removeChildFromParent(parent: object, childToRemove: object): void { + const originalParentData = this.getInstanceDataOrThrow(parent); + const newParentData = { + ...originalParentData, + children: without(originalParentData.children, childToRemove) + }; + this.modelInstanceMap.set(parent, newParentData); + } + + private trackNewChild(parent: object, newChild: object): void { + const originalParentData = this.getInstanceDataOrThrow(parent); + const newParentData = { + ...originalParentData, + children: originalParentData.children.concat(newChild) + }; + this.modelInstanceMap.set(parent, newParentData); + } + + private getInstanceDataOrThrow(instance: object): ModelInstanceData { + if (!this.modelInstanceMap.has(instance)) { + this.logger.warn('Could not retrieve data for provided instance, it has not been registered').throw(); + } + + // Make sure this isn't mutated by always returning a copy, only leaving actual models in tact + const cloneFunction = (value: object) => (this.modelInstanceMap.has(value) ? value : undefined); + + return cloneDeepWith(this.modelInstanceMap.get(instance)!, cloneFunction) as ModelInstanceData; + } + + private modelHasInitHook(model: T & Partial): model is T & ModelOnInit { + return typeof model.modelOnInit === 'function'; + } + + private modelHasDestroyHook(model: T & Partial): model is T & ModelOnDestroy { + return typeof model.modelOnDestroy === 'function'; + } + + private buildApiForModel(model: object): ModelApi { + const matchingBuilder = this.apiBuilders.find(builder => builder.matches(model)); + + if (!matchingBuilder) { + return this.logger.error('No model API builder registered matching provided model').throw(); + } + + return matchingBuilder.build(model); + } + + private destroyModel(modelInstance: object): void { + const instanceData = this.getInstanceDataOrThrow(modelInstance); + + // Depth first, destroy children before self + instanceData.children.forEach(child => this.destroy(child)); + + if (this.modelHasDestroyHook(modelInstance)) { + modelInstance.modelOnDestroy(); + } + + if (instanceData.parent) { + this.removeChildFromParent(instanceData.parent, modelInstance); + } + this.modelInstanceMap.delete(modelInstance); + + this.modelDestroyedEvent.publish(modelInstance); + } +} + +interface ModelInstanceData { + /** + * Parent of tracked model + */ + readonly parent?: object; + /** + * Children of tracked model + */ + readonly children: object[]; // No mutation! +} + +/** + * A decorator class that can optionally decorate created models. + */ +export interface ModelDecorator { + /** + * Will be invoked for each created model object before it is initialized and before the + * creation event is published. + */ + decorate(modelInstance: object, api: ModelApi): void; +} diff --git a/src/model/property/model-property-type-library.test.ts b/src/model/property/model-property-type-library.test.ts new file mode 100644 index 00000000..d06e35e7 --- /dev/null +++ b/src/model/property/model-property-type-library.test.ts @@ -0,0 +1,142 @@ +import { Logger } from '../../util/logging/logger'; +import { + ModelPropertyTypeLibrary, + ModelPropertyTypeRegistrationInformation, + PropertyValidatorFunction +} from './model-property-type-library'; +import { BOOLEAN_PROPERTY } from './predefined/primitive-model-property-types'; +import { PropertyLocation } from './property-location'; + +describe('Model property type library', () => { + let library: ModelPropertyTypeLibrary; + let mockLogger: Partial; + + beforeEach(() => { + mockLogger = {}; + library = new ModelPropertyTypeLibrary(mockLogger as Logger); + }); + + const expectIsDefaultValidator = (validator: unknown) => { + expect(typeof validator).toBe('function'); + + expect( + // tslint:disable-next-line: no-null-keyword + ['t', true, 0, Symbol(''), undefined, null, [], {}] + .map(value => (validator as PropertyValidatorFunction)(value, false, { key: 'any' })) + .every(val => val === undefined) + ).toBeTruthy(); + }; + + test('can register properties', () => { + const propertyType = { + type: 'test', + validator: jest.fn() + }; + + library.registerPropertyType(propertyType); + expect(library.getValidator('test')).toEqual(expect.any(Function)); + }); + + test('can retrieve bound validator', () => { + const propertyType = new (class implements ModelPropertyTypeRegistrationInformation { + public type: string = 'test'; + private readonly internalProperty: string = 'internalValidator'; + public validator(): string { + return this.internalProperty; + } + })(); + + library.registerPropertyType(propertyType); + const validator = library.getValidator('test'); + expect(validator('value', true, { key: 'test' })).toBe('internalValidator'); + }); + + test('defaults validator if undefined', () => { + library.registerPropertyType({ + type: 'test' + }); + + expectIsDefaultValidator(library.getValidator('test')); + }); + + test('logs error if registering property twice', () => { + mockLogger.error = jest.fn(); + library.registerPropertyType(BOOLEAN_PROPERTY); + library.registerPropertyType(BOOLEAN_PROPERTY); + expect(mockLogger.error).toHaveBeenCalledWith('Property type has already been registered: [boolean]'); + }); + + test('logs and returns default on lookup if property type not registered', () => { + mockLogger.warn = jest.fn(); + + expectIsDefaultValidator(library.getValidator('fake')); + + expect(mockLogger.warn).toHaveBeenCalledWith('Requested property type has not been registered: fake'); + }); + + test('can retrieve bound serializer', () => { + const propertyType = new (class implements ModelPropertyTypeRegistrationInformation { + public type: string = 'test'; + private readonly internalProperty: string = 'internalSerializer'; + public serializer(): string { + return this.internalProperty; + } + })(); + + library.registerPropertyType(propertyType); + const location = PropertyLocation.forModelProperty({ exampleProp: undefined as unknown }, 'exampleProp'); + const serializer = library.getPropertySerializer('test'); + expect(serializer!('value', location, { key: 'test' })).toBe('internalSerializer'); + }); + + test('retrieves undefined for serializer if not defined or not registered', () => { + mockLogger.warn = jest.fn(); + expect(library.getPropertySerializer('test')).toBeUndefined(); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + + const propertyType = { + type: 'test' + }; + library.registerPropertyType(propertyType); + expect(library.getPropertySerializer('test')).toBeUndefined(); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); // Not called again + }); + + test('can retrieve bound deserializer', () => { + const propertyType = new (class implements ModelPropertyTypeRegistrationInformation { + public type: string = 'test'; + private readonly internalProperty: string = 'internalDeserializer'; + public deserializer(): string { + return this.internalProperty; + } + })(); + + library.registerPropertyType(propertyType); + const location = PropertyLocation.forModelProperty({ exampleProp: undefined as unknown }, 'exampleProp'); + const deserializer = library.getPropertyDeserializer('test'); + expect(deserializer!('value', location, { key: 'test' })).toBe('internalDeserializer'); + }); + + test('retrieves undefined for deserializer if not defined or not registered', () => { + mockLogger.warn = jest.fn(); + expect(library.getPropertyDeserializer('test')).toBeUndefined(); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + + const propertyType = { + type: 'test' + }; + library.registerPropertyType(propertyType); + expect(library.getPropertyDeserializer('test')).toBeUndefined(); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); // Not called again + }); + + test('can retrieve by model type instance', () => { + const propertyType = { + type: 'test', + validator: () => undefined + }; + + library.registerPropertyType(propertyType); + expect(library.getValidator({ key: 'test' })).toEqual(expect.any(Function)); + }); +}); diff --git a/src/model/property/model-property-type-library.ts b/src/model/property/model-property-type-library.ts new file mode 100644 index 00000000..3068ef74 --- /dev/null +++ b/src/model/property/model-property-type-library.ts @@ -0,0 +1,166 @@ +import { DeserializationFunction } from '../../persistence/deserialization/deserializer'; +import { JsonPrimitive } from '../../persistence/model-json'; +import { SerializationFunction } from '../../persistence/serialization/serializer'; +import { Logger } from '../../util/logging/logger'; + +/** + * Store of metadata information about supported property types + */ +export class ModelPropertyTypeLibrary { + private static readonly NO_OP_VALIDATOR: () => undefined = () => undefined; + + private readonly propertyTypeMap: Map = new Map(); + + public constructor(private readonly logger: Logger) {} + + /** + * Registers the provided property type. No action is taken if the property type has + * already been registered + */ + public registerPropertyType(propertyTypeData: ModelPropertyTypeRegistrationInformation): void { + if (this.propertyTypeMap.has(propertyTypeData.type)) { + this.logger.error(`Property type has already been registered: [${propertyTypeData.type}]`); + + return; + } + this.propertyTypeMap.set( + propertyTypeData.type, + this.convertPropertyTypeRegistrationInfoToMetadata(propertyTypeData) + ); + } + + /** + * Retrieves the validator function for the provided property type. Returns NO-OP validator + * if the property type has not been registered. + */ + public getValidator(type: string | ModelPropertyTypeInstance): PropertyValidatorFunction { + const metadata = this.getMetadataOrLog(type); + + return metadata ? metadata.validator : ModelPropertyTypeLibrary.NO_OP_VALIDATOR; + } + + /** + * Retrieves the customer serializer function for the provided property type. Returns undefined if + * the property type has not been registered, or if no serializer exists. + */ + public getPropertySerializer( + type: string | ModelPropertyTypeInstance + ): SerializationFunction | undefined { + const metadata = this.getMetadataOrLog(type); + + return metadata && (metadata.serializer as SerializationFunction); + } + + /** + * Retrieves the customer deserializer function for the provided property type. Returns undefined if + * the property type has not been registered, or if no deserializer exists. + */ + public getPropertyDeserializer( + type: string | ModelPropertyTypeInstance + ): DeserializationFunction | undefined { + const metadata = this.getMetadataOrLog(type); + + return metadata && (metadata.deserializer as DeserializationFunction); + } + + private convertPropertyTypeRegistrationInfoToMetadata( + registrationInfo: ModelPropertyTypeRegistrationInformation + ): ModelPropertyTypeMetadata { + return { + validator: this.bindPotentialFunction(registrationInfo, 'validator') || ModelPropertyTypeLibrary.NO_OP_VALIDATOR, + serializer: this.bindPotentialFunction(registrationInfo, 'serializer'), + deserializer: this.bindPotentialFunction(registrationInfo, 'deserializer') + }; + } + + private getMetadataOrLog(type: string | ModelPropertyTypeInstance): ModelPropertyTypeMetadata | undefined { + const typeKey = this.typeToKey(type); + if (this.propertyTypeMap.has(typeKey)) { + return this.propertyTypeMap.get(typeKey)!; + } + this.logger.warn(`Requested property type has not been registered: ${typeKey}`); + } + + private typeToKey(type: string | ModelPropertyTypeInstance): string { + return typeof type === 'string' ? type : type.key; + } + + // tslint:disable-next-line: ban-types + private bindPotentialFunction

>( + object: ModelPropertyTypeRegistrationInformation, + key: P + ): ModelPropertyTypeRegistrationInformation[P] { + const potentialFunction = object[key]; + // tslint:disable-next-line: strict-type-predicates + if (typeof potentialFunction === 'function') { + return potentialFunction.bind(object) as ModelPropertyTypeRegistrationInformation[P]; + } + + return potentialFunction; + } +} + +/** + * Registration information for a Model Property Type + */ +// tslint:disable-next-line: max-line-length +export interface ModelPropertyTypeRegistrationInformation< + TDeserialized = unknown, + TSerialized extends JsonPrimitive = JsonPrimitive +> { + /** + * The type key + */ + type: string; + /** + * A potential validator for properties of this type + */ + validator?: PropertyValidatorFunction; + /** + * A custom serializer for this property type. If defined, this will take precedence over the general + * serialization search + */ + serializer?: SerializationFunction; + /** + * A custom deserializer for this property type. If defined, this will take precedence over the general + * deserialization search + */ + deserializer?: DeserializationFunction; +} + +/** + * Represents an instance of a model property type + */ +export interface ModelPropertyTypeInstance { + /** + * The type key represented + */ + key: string; +} + +/** + * A validator function that returns a `string` with an error message if validation fails, else `undefined` + * Accepts the value to check, which should be in its serialized form, and a flag indicating whether + * null or undefined values are allowed. + * + */ +export type PropertyValidatorFunction = ( + serializedValue: unknown, + allowUndefinedOrNull: boolean, + propertyTypeInstance: ModelPropertyTypeInstance +) => string | undefined; + +interface ModelPropertyTypeMetadata { + /** + * A validator for this property type or potentially a default + */ + validator: PropertyValidatorFunction; + /** + * An optional serializations function for this property type + */ + serializer: SerializationFunction | undefined; + /** + * An optional deserialization function for this property type + */ + deserializer: DeserializationFunction | undefined; +} diff --git a/src/model/property/predefined/array-property-type.test.ts b/src/model/property/predefined/array-property-type.test.ts new file mode 100644 index 00000000..10503166 --- /dev/null +++ b/src/model/property/predefined/array-property-type.test.ts @@ -0,0 +1,33 @@ +// tslint:disable: no-null-keyword +import { ARRAY_PROPERTY } from './array-property-type'; + +describe('Predefined Model Property array type', () => { + test('validator works for null/undefined', () => { + expect(ARRAY_PROPERTY.validator(null, true)).toBeUndefined(); + + expect(ARRAY_PROPERTY.validator(undefined, true)).toBeUndefined(); + + expect(ARRAY_PROPERTY.validator(null, false)).toBe('Required property got null value'); + + expect(ARRAY_PROPERTY.validator(undefined, false)).toBe('Required property got undefined value'); + }); + + test('validator works for legitimate values', () => { + const goodValue = ['test']; + + expect(ARRAY_PROPERTY.validator(goodValue, false)).toBeUndefined(); + expect(ARRAY_PROPERTY.validator(goodValue, false)).toBeUndefined(); + + expect(ARRAY_PROPERTY.validator(goodValue, true)).toBeUndefined(); + expect(ARRAY_PROPERTY.validator(goodValue, true)).toBeUndefined(); + }); + + test('validator rejects bad values', () => { + expect(ARRAY_PROPERTY.validator(5, true)).toBe('Provided value is not an Array, detected: number'); + expect(ARRAY_PROPERTY.validator('true', true)).toBe('Provided value is not an Array, detected: string'); + expect(ARRAY_PROPERTY.validator(Symbol('test'), true)).toBe('Provided value is not an Array, detected: symbol'); + expect(ARRAY_PROPERTY.validator({ [1]: 'value', length: 1 }, true)).toBe( + 'Provided value is not an Array, detected: object' + ); + }); +}); diff --git a/src/model/property/predefined/array-property-type.ts b/src/model/property/predefined/array-property-type.ts new file mode 100644 index 00000000..accbfd0b --- /dev/null +++ b/src/model/property/predefined/array-property-type.ts @@ -0,0 +1,18 @@ +import { isNil } from 'lodash'; + +export const ARRAY_PROPERTY = Object.freeze({ + type: 'array', + validator: (value: unknown, allowUndefinedOrNull: boolean) => { + if (allowUndefinedOrNull && isNil(value)) { + return undefined; + } + if (isNil(value)) { + return `Required property got ${value} value`; + } + if (Array.isArray(value)) { + return undefined; + } + + return `Provided value is not an Array, detected: ${typeof value}`; + } +}); diff --git a/src/model/property/predefined/model-property-type.test.ts b/src/model/property/predefined/model-property-type.test.ts new file mode 100644 index 00000000..092282b0 --- /dev/null +++ b/src/model/property/predefined/model-property-type.test.ts @@ -0,0 +1,84 @@ +// tslint:disable: no-null-keyword +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { ModelManager } from '../../manager/model-manager'; +import { PropertyLocation } from '../property-location'; +import { ModelPropertyType } from './model-property-type'; + +describe('Model property type validator', () => { + let mockDeserializationManager: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let propertyType: ModelPropertyType; + const model = { prop: {} }; + const propertyLocation = PropertyLocation.forModelProperty(model, 'prop'); + + beforeEach(() => { + mockDeserializationManager = { + deserialize: jest.fn() + }; + mockModelManager = { + create: jest.fn() + }; + + propertyType = new ModelPropertyType( + mockDeserializationManager as DeserializationManager, + mockModelManager as ModelManager + ); + }); + test('validator works for null/undefined', () => { + expect(propertyType.validator(null, true)).toBeUndefined(); + + expect(propertyType.validator(undefined, true)).toBeUndefined(); + + expect(propertyType.validator(null, false)).toBe('Required property got null value'); + + expect(propertyType.validator(undefined, false)).toBe('Required property got undefined value'); + }); + + test('validator works for legitimate values', () => { + const goodValue = { + type: 'test' + }; + expect(propertyType.validator(goodValue, false)).toBeUndefined(); + expect(propertyType.validator(goodValue, false)).toBeUndefined(); + + expect(propertyType.validator(goodValue, true)).toBeUndefined(); + expect(propertyType.validator(goodValue, true)).toBeUndefined(); + }); + + test('validator rejects bad values', () => { + expect(propertyType.validator(5, true)).toBe('Provided value is not model JSON, detected type: number'); + expect(propertyType.validator('true', true)).toBe('Provided value is not model JSON, detected type: string'); + expect(propertyType.validator(Symbol('test'), true)).toBe( + 'Provided value is not model JSON, detected type: symbol' + ); + expect(propertyType.validator([], true)).toBe('Provided value is missing model JSON required type field'); + expect(propertyType.validator({}, true)).toBe('Provided value is missing model JSON required type field'); + }); + + test('deserializes as normal if no default or value provided', () => { + const value = { + type: 'test' + }; + // Value provided + propertyType.deserializer(value, propertyLocation, { key: ModelPropertyType.TYPE, defaultModelClass: class {} }); + expect(mockDeserializationManager.deserialize).toHaveBeenCalledWith(value, propertyLocation); + expect(mockModelManager.create).not.toHaveBeenCalled(); + + // No default + propertyType.deserializer(undefined, propertyLocation, { key: ModelPropertyType.TYPE }); + expect(mockDeserializationManager.deserialize).toHaveBeenLastCalledWith(undefined, propertyLocation); + expect(mockModelManager.create).not.toHaveBeenCalled(); + }); + + test('defaults to instance of requested class if undefined', () => { + const defaultClass = class {}; + // Value provided + propertyType.deserializer(undefined, propertyLocation, { + key: ModelPropertyType.TYPE, + defaultModelClass: defaultClass + }); + expect(mockDeserializationManager.deserialize).not.toHaveBeenCalled(); + expect(mockModelManager.create).toHaveBeenCalledWith(defaultClass, model); + }); +}); diff --git a/src/model/property/predefined/model-property-type.ts b/src/model/property/predefined/model-property-type.ts new file mode 100644 index 00000000..a55307c0 --- /dev/null +++ b/src/model/property/predefined/model-property-type.ts @@ -0,0 +1,72 @@ +import { isNil } from 'lodash'; +import { DeserializationManager } from '../../../persistence/deserialization/deserialization-manager'; +import { ModelJson } from '../../../persistence/model-json'; +import { Constructable } from '../../../util/constructable'; +import { ModelManager } from '../../manager/model-manager'; +import { ModelPropertyTypeInstance, ModelPropertyTypeRegistrationInformation } from '../model-property-type-library'; +import { PropertyLocation } from '../property-location'; + +/** + * Model properties representing a nested model + */ +export class ModelPropertyType implements ModelPropertyTypeRegistrationInformation { + /** + * Type key for model properties + */ + public static readonly TYPE: string = 'model'; + + /** + * @inheritdoc + */ + public readonly type: string = ModelPropertyType.TYPE; + + public constructor( + private readonly deserializationManager: DeserializationManager, + private readonly modelManager: ModelManager + ) {} + /** + * @inheritdoc + */ + public validator(value: unknown, allowUndefinedOrNull: boolean): string | undefined { + if (allowUndefinedOrNull && isNil(value)) { + return undefined; + } + if (isNil(value)) { + return `Required property got ${value} value`; + } + if (typeof value !== 'object') { + return `Provided value is not model JSON, detected type: ${typeof value}`; + } + if (!('type' in value!)) { + return 'Provided value is missing model JSON required type field'; + } + + return undefined; // Can't detect if type is registered without access to library + } + + /** + * @inheritdoc + */ + public deserializer( + json: ModelJson | undefined, + location: PropertyLocation, + propertyInstance: ModelModelPropertyTypeInstance + ): object { + const defaultModelClass = propertyInstance.defaultModelClass; + if (!json && defaultModelClass) { + return this.modelManager.create(defaultModelClass, location.parentModel); + } + + return this.deserializationManager.deserialize(json, location); + } +} + +/** + * A ModelPropertyType representing another model... yeah. + */ +export interface ModelModelPropertyTypeInstance extends ModelPropertyTypeInstance { + /** + * If the property is undefined, by default, use this class + */ + defaultModelClass?: Constructable; +} diff --git a/src/model/property/predefined/primitive-model-property-types.test.ts b/src/model/property/predefined/primitive-model-property-types.test.ts new file mode 100644 index 00000000..7f8d4f83 --- /dev/null +++ b/src/model/property/predefined/primitive-model-property-types.test.ts @@ -0,0 +1,124 @@ +// tslint:disable:no-null-keyword +import { + BOOLEAN_PROPERTY, + NUMBER_PROPERTY, + PLAIN_OBJECT_PROPERTY, + STRING_PROPERTY, + UNKNOWN_PROPERTY +} from './primitive-model-property-types'; + +describe('Predefined Model Property primitive types', () => { + const propertyTypeInstance = { key: 'any' }; // Not used by predefined validators + + test('boolean has correct type', () => { + expect(BOOLEAN_PROPERTY.type).toBe('boolean'); + }); + + test('number has correct type', () => { + expect(NUMBER_PROPERTY.type).toBe('number'); + }); + + test('string has correct type', () => { + expect(STRING_PROPERTY.type).toBe('string'); + }); + + // Shared implementation, only testing via boolean + test('validator works for null/undefined', () => { + expect(BOOLEAN_PROPERTY.validator(null, true, propertyTypeInstance)).toBeUndefined(); + + expect(BOOLEAN_PROPERTY.validator(undefined, true, propertyTypeInstance)).toBeUndefined(); + + expect(BOOLEAN_PROPERTY.validator(null, false, propertyTypeInstance)).toBe('Required property got null value'); + + expect(BOOLEAN_PROPERTY.validator(undefined, false, propertyTypeInstance)).toBe( + 'Required property got undefined value' + ); + }); + + test('validator works for legitimate values', () => { + expect(BOOLEAN_PROPERTY.validator(true, false, propertyTypeInstance)).toBeUndefined(); + expect(BOOLEAN_PROPERTY.validator(false, false, propertyTypeInstance)).toBeUndefined(); + + expect(BOOLEAN_PROPERTY.validator(true, true, propertyTypeInstance)).toBeUndefined(); + expect(BOOLEAN_PROPERTY.validator(false, true, propertyTypeInstance)).toBeUndefined(); + }); + + test('validator rejects bad values', () => { + expect(BOOLEAN_PROPERTY.validator(5, true, propertyTypeInstance)).toBe( + 'Provided value is not a boolean, detected: number' + ); + expect(BOOLEAN_PROPERTY.validator('true', true, propertyTypeInstance)).toBe( + 'Provided value is not a boolean, detected: string' + ); + expect(BOOLEAN_PROPERTY.validator(Symbol('test'), true, propertyTypeInstance)).toBe( + 'Provided value is not a boolean, detected: symbol' + ); + expect(BOOLEAN_PROPERTY.validator([], true, propertyTypeInstance)).toBe( + 'Provided value is not a boolean, detected: object' + ); + expect(BOOLEAN_PROPERTY.validator({}, true, propertyTypeInstance)).toBe( + 'Provided value is not a boolean, detected: object' + ); + }); +}); + +describe('Predefined Model Property plain object type', () => { + const propertyTypeInstance = { key: 'any' }; // Not used by predefined validators + test('validator works for null/undefined', () => { + expect(PLAIN_OBJECT_PROPERTY.validator(null, true, propertyTypeInstance)).toBeUndefined(); + + expect(PLAIN_OBJECT_PROPERTY.validator(undefined, true, propertyTypeInstance)).toBeUndefined(); + + expect(PLAIN_OBJECT_PROPERTY.validator(null, false, propertyTypeInstance)).toBe('Required property got null value'); + + expect(PLAIN_OBJECT_PROPERTY.validator(undefined, false, propertyTypeInstance)).toBe( + 'Required property got undefined value' + ); + }); + + test('validator works for legitimate values', () => { + const goodValue = { test: 5 }; + + expect(PLAIN_OBJECT_PROPERTY.validator(goodValue, false, propertyTypeInstance)).toBeUndefined(); + expect(PLAIN_OBJECT_PROPERTY.validator(goodValue, false, propertyTypeInstance)).toBeUndefined(); + + expect(PLAIN_OBJECT_PROPERTY.validator(goodValue, true, propertyTypeInstance)).toBeUndefined(); + expect(PLAIN_OBJECT_PROPERTY.validator(goodValue, true, propertyTypeInstance)).toBeUndefined(); + }); + + test('validator rejects bad values', () => { + expect(PLAIN_OBJECT_PROPERTY.validator(5, true, propertyTypeInstance)).toBe( + 'Provided value is not an object, detected: number' + ); + expect(PLAIN_OBJECT_PROPERTY.validator('true', true, propertyTypeInstance)).toBe( + 'Provided value is not an object, detected: string' + ); + expect(PLAIN_OBJECT_PROPERTY.validator(Symbol('test'), true, propertyTypeInstance)).toBe( + 'Provided value is not an object, detected: symbol' + ); + expect(PLAIN_OBJECT_PROPERTY.validator([], true, propertyTypeInstance)).toBe( + 'Provided value is not a plain object, detected: Array' + ); + }); +}); + +describe('Predefined Model Property unknown type', () => { + test('validator works for null/undefined', () => { + expect(UNKNOWN_PROPERTY.validator(null, true)).toBeUndefined(); + + expect(UNKNOWN_PROPERTY.validator(undefined, true)).toBeUndefined(); + + expect(UNKNOWN_PROPERTY.validator(null, false)).toBe('Required property got null value'); + + expect(UNKNOWN_PROPERTY.validator(undefined, false)).toBe('Required property got undefined value'); + }); + + test('validator works for all other values', () => { + expect(UNKNOWN_PROPERTY.validator(true, false)).toBeUndefined(); + expect(UNKNOWN_PROPERTY.validator(false, false)).toBeUndefined(); + expect(UNKNOWN_PROPERTY.validator('string', true)).toBeUndefined(); + expect(UNKNOWN_PROPERTY.validator(10, true)).toBeUndefined(); + expect(UNKNOWN_PROPERTY.validator([], true)).toBeUndefined(); + expect(UNKNOWN_PROPERTY.validator({}, true)).toBeUndefined(); + }); +}); diff --git a/src/model/property/predefined/primitive-model-property-types.ts b/src/model/property/predefined/primitive-model-property-types.ts new file mode 100644 index 00000000..7e2c5b92 --- /dev/null +++ b/src/model/property/predefined/primitive-model-property-types.ts @@ -0,0 +1,61 @@ +import { isNil } from 'lodash'; +import { ModelPropertyTypeInstance, PropertyValidatorFunction } from '../model-property-type-library'; + +const typeofValidator = (type: 'string' | 'boolean' | 'number' | 'object'): PropertyValidatorFunction => ( + value: unknown, + allowUndefinedOrNull: boolean +) => { + if (allowUndefinedOrNull && isNil(value)) { + return undefined; + } + if (isNil(value)) { + return `Required property got ${value} value`; + } + + if (typeof value === type) { + return undefined; + } + + if (type === 'object') { + return `Provided value is not an ${type}, detected: ${typeof value}`; + } + + return `Provided value is not a ${type}, detected: ${typeof value}`; +}; + +export const STRING_PROPERTY = Object.freeze({ + type: 'string', + validator: typeofValidator('string') +}); + +export const NUMBER_PROPERTY = Object.freeze({ + type: 'number', + validator: typeofValidator('number') +}); + +export const BOOLEAN_PROPERTY = Object.freeze({ + type: 'boolean', + validator: typeofValidator('boolean') +}); + +export const PLAIN_OBJECT_PROPERTY = Object.freeze({ + type: 'plain-object', + validator: (value: unknown, allowUndefinedOrNull: boolean, propertyType: ModelPropertyTypeInstance) => { + if (Array.isArray(value)) { + return 'Provided value is not a plain object, detected: Array'; + } + + return typeofValidator('object')(value, allowUndefinedOrNull, propertyType); + } +}); + +export const UNKNOWN_PROPERTY = Object.freeze({ + type: 'unknown', + validator: (value: unknown, allowUndefinedOrNull: boolean) => { + if (isNil(value) && !allowUndefinedOrNull) { + return `Required property got ${value} value`; + } + + return undefined; + } +}); diff --git a/src/model/property/property-location.test.ts b/src/model/property/property-location.test.ts new file mode 100644 index 00000000..0bfaaf85 --- /dev/null +++ b/src/model/property/property-location.test.ts @@ -0,0 +1,87 @@ +import { PropertyLocation } from '../../model/property/property-location'; + +describe('Property location', () => { + let mockSetter: jest.Mock; + let mockGetter: jest.Mock; + let parentModel: { [key: string]: unknown }; + + beforeEach(() => { + parentModel = {}; + mockSetter = jest.fn(); + mockGetter = jest.fn().mockReturnValue('mock getter'); + }); + + test('returns property name from to string', () => { + expect(new PropertyLocation(parentModel, 'propertyName', mockSetter, mockGetter).toString()).toEqual( + 'propertyName' + ); + }); + + test('calls provided setter', () => { + new PropertyLocation(parentModel, 'propertyName', mockSetter, mockGetter).setProperty('hi'); + expect(mockSetter).toHaveBeenCalledWith('hi'); + }); + + test('can build child with concatenated key and setter', () => { + const original = new PropertyLocation<{ [key: string]: unknown }>( + parentModel, + 'propertyName', + mockSetter, + mockGetter as jest.Mock + ); + const objectToSet = {}; + const childLocation = original.buildChildFromObjectAndKey(objectToSet, 'prop'); + expect(childLocation.toString()).toBe('propertyName:prop'); + expect(childLocation.parentModel).toBe(parentModel); + childLocation.setProperty(15); + expect(objectToSet).toEqual({ prop: 15 }); + expect(childLocation.getProperty()).toBe(15); + }); + + test('can build location for model property', () => { + const modelLocation = PropertyLocation.forModelProperty(parentModel, 'parentProp'); + expect(modelLocation.toString()).toBe('parentProp'); + expect(modelLocation.parentModel).toBe(parentModel); + modelLocation.setProperty(16); + expect(parentModel).toEqual({ parentProp: 16 }); + expect(modelLocation.getProperty()).toBe(16); + }); + + test('calls validator if defined', () => { + const validator = jest.fn(val => { + if (val === 'valid') { + return; + } + throw Error('invalid'); + }); + + const modelLocation = new PropertyLocation(parentModel, 'propertyName', mockSetter, mockGetter).withValidator( + validator + ); + + modelLocation.setProperty('valid'); + + expect(validator).toHaveBeenCalledWith('valid'); + expect(mockSetter).toHaveBeenCalledWith('valid'); + + expect(() => modelLocation.setProperty('invalid')).toThrow(); + + expect(validator).toHaveBeenCalledWith('invalid'); + expect(mockSetter).not.toHaveBeenCalledWith('invalid'); + }); + + test('calls provided getter', () => { + expect(new PropertyLocation(parentModel, 'propertyName', mockSetter, mockGetter).getProperty()).toBe('mock getter'); + expect(mockGetter).toHaveBeenCalled(); + }); + + test('can build location for unassigned child models', () => { + const parent = {}; + const location = PropertyLocation.forUnassignedChildModel(parent); + expect(location.parentModel).toBe(parent); + expect(location.toString()).toBe('UNASSIGNED'); + + expect(() => location.setProperty(undefined)).toThrow(); + expect(() => location.getProperty()).toThrow(); + }); +}); diff --git a/src/model/property/property-location.ts b/src/model/property/property-location.ts new file mode 100644 index 00000000..467c73d0 --- /dev/null +++ b/src/model/property/property-location.ts @@ -0,0 +1,93 @@ +/** + * A property location represents a position in the model tree. + */ +export class PropertyLocation { + /** + * Creates a property location for a direct property of a model + */ + public static forModelProperty(model: T, property: P): PropertyLocation { + return new PropertyLocation( + model, + property, + val => (model[property] = val!), + () => model[property] + ); + } + + /** + * Creates a property location for a newly created child model with no location assignment. This location will convey + * the parent model, but will not allow setting or getting. + */ + public static forUnassignedChildModel(parentModel: object): PropertyLocation { + return new PropertyLocation( + parentModel, + PropertyLocation.UNASSIGNED_LOCATION, + () => { + throw Error('Setter not supported for Unassigned child'); + }, + () => { + throw Error('Getter not supported for Unassigned child'); + } + ); + } + + private static readonly UNASSIGNED_LOCATION: string = 'UNASSIGNED'; + + private validator?: (value: T | undefined) => void; + + public constructor( + public readonly parentModel: object, + private readonly propertyKey: string | number | symbol, + private readonly setter: (value: T | undefined) => void, + private readonly getter: () => T | undefined + ) {} + + /** + * Adds validation function that will be run each time before invoking the setter + */ + public withValidator(validator: (value: unknown) => void): this { + this.validator = validator; + + return this; + } + + /** + * Sets the location with the provided value, first validating it if provided with a validator + */ + public setProperty(value: T | undefined): void { + if (this.validator) { + this.validator(value); + } + this.setter(value); + } + + /** + * Gets the value from the provided location + */ + public getProperty(): T | undefined { + return this.getter(); + } + + /** + * Converts the location to a string representation. All + * locations with the same parentModel will have a unique string + * representation. + */ + public toString(): string { + return String(this.propertyKey); + } + + /** + * Creates a property location nested from the current one. object represents the object in the current + * location, `propertyKey` is the path from that object to the new location. The parent model is retained. + * If object is a model, it should use `PropertyLocation.forModelProperty` instead. + */ + public buildChildFromObjectAndKey(object: T, propertyKey: TKey): PropertyLocation { + return new PropertyLocation( + this.parentModel, + `${this.toString()}:${String(propertyKey)}`, + value => (object[propertyKey] = value!), + () => object[propertyKey] + ); + } +} diff --git a/src/model/property/validation/model-property-validator.test.ts b/src/model/property/validation/model-property-validator.test.ts new file mode 100644 index 00000000..9c258d1e --- /dev/null +++ b/src/model/property/validation/model-property-validator.test.ts @@ -0,0 +1,97 @@ +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { Logger } from '../../../util/logging/logger'; +import { ModelPropertyTypeLibrary, PropertyValidatorFunction } from '../model-property-type-library'; +import { ModelPropertyValidator } from './model-property-validator'; + +describe('Model property validator', () => { + let validator: ModelPropertyValidator; + let mockModelPropertyTypeLibrary: Partial; + let mockLogger: PartialObjectMock; + let mockValidatorFunction: jest.Mock; + + beforeEach(() => { + mockValidatorFunction = jest.fn().mockReturnValue('mock error'); + mockLogger = { + warn: jest.fn(), + error: jest.fn((message: string) => ({ + throw: () => { + throw Error(message); + } + })) + }; + mockModelPropertyTypeLibrary = { + getValidator: jest.fn().mockReturnValue(mockValidatorFunction) + }; + validator = new ModelPropertyValidator( + mockModelPropertyTypeLibrary as ModelPropertyTypeLibrary, + mockLogger as Logger + ); + }); + + test('throws and logs error for validation errors', () => { + mockModelPropertyTypeLibrary.getValidator = jest.fn().mockReturnValue((val: unknown) => { + if (typeof val === 'string' && /[aeiou]/.test(val)) { + return 'string properties should not contain vowels'; + } + }); + + expect(() => + validator.validate('vowels aplenty', { + type: { key: 'string' }, + runtimeKey: 'vowelProp', + key: 'vowelKey', + displayName: 'Vowel Property', + required: true, + runtimeType: String + }) + ).toThrow('Validation error for property [vowelProp]: string properties should not contain vowels'); + + expect(mockLogger.error).toHaveBeenCalled(); + (mockLogger.error as jest.Mock).mockClear(); + expect(() => + validator.validate('n0 v0w3ls', { + type: { key: 'string' }, + runtimeKey: 'noVowelProp', + key: 'noVowelKey', + displayName: 'No Vowel Property', + required: true, + runtimeType: String + }) + ).not.toThrow(); + }); + + test('validation only logs warning if validation is not strict', () => { + validator.setStrictSchema(false); + expect(() => + validator.validate(42, { + type: { key: 'number' }, + runtimeKey: 'numberRuntimeKey', + key: 'numberKey', + displayName: 'Number', + required: true, + runtimeType: Number + }) + ).not.toThrow(); + + expect(mockLogger.warn).toHaveBeenLastCalledWith('Validation error for property [numberRuntimeKey]: mock error'); + expect(mockLogger.error).not.toHaveBeenCalled(); + + (mockLogger.warn as jest.Mock).mockClear(); + + validator.setStrictSchema(true); + + expect(() => + validator.validate(42, { + type: { key: 'number' }, + runtimeKey: 'numberRuntimeKey', + key: 'numberKey', + displayName: 'Number', + required: true, + runtimeType: Number + }) + ).toThrow(); + + expect(mockLogger.error).toHaveBeenLastCalledWith('Validation error for property [numberRuntimeKey]: mock error'); + expect(mockLogger.warn).not.toHaveBeenCalled(); + }); +}); diff --git a/src/model/property/validation/model-property-validator.ts b/src/model/property/validation/model-property-validator.ts new file mode 100644 index 00000000..b162a3af --- /dev/null +++ b/src/model/property/validation/model-property-validator.ts @@ -0,0 +1,43 @@ +import { isEmpty } from 'lodash'; +import { Logger } from '../../../util/logging/logger'; +import { ModelPropertyMetadata } from '../../registration/model-registration'; +import { ModelPropertyTypeLibrary } from '../model-property-type-library'; + +/** + * Performs validation of a single value, warning or throwing an error based on configuration + * if validation does not pass. + */ +export class ModelPropertyValidator { + public constructor( + private readonly modelPropertyTypeLibrary: ModelPropertyTypeLibrary, + private readonly logger: Logger + ) {} + + private strictSchema: boolean = true; + + /** + * Performs the validation, throwing an error in strict mode or logging a warning otherwise + */ + public validate(value: unknown, propertyMetadata: ModelPropertyMetadata): void { + const validator = this.modelPropertyTypeLibrary.getValidator(propertyMetadata.type); + const error = validator(value, !propertyMetadata.required, propertyMetadata.type); + + if (isEmpty(error)) { + return; + } + const errorMessage = `Validation error for property [${String(propertyMetadata.runtimeKey)}]: ${error}`; + + if (this.strictSchema) { + return this.logger.error(errorMessage).throw(); + } + + this.logger.warn(errorMessage); + } + + /** + * If true, any validation errors are thrown as runtime errors + */ + public setStrictSchema(checkSchema: boolean): void { + this.strictSchema = checkSchema; + } +} diff --git a/src/model/registration/model-decorators.test.ts b/src/model/registration/model-decorators.test.ts new file mode 100644 index 00000000..0932315f --- /dev/null +++ b/src/model/registration/model-decorators.test.ts @@ -0,0 +1,58 @@ +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { BOOLEAN_PROPERTY, NUMBER_PROPERTY } from '../property/predefined/primitive-model-property-types'; +import { deferredModelDecoratorRegistrations, Model, ModelProperty } from './model-decorators'; +import { ModelLibrary } from './model-registration'; + +describe('Model decorators', () => { + const symbolKey: unique symbol = Symbol('symbol'); + let mockModelLibrary: PartialObjectMock; + + @Model({ + type: 'test-model-decorator', + displayName: 'Test Model Decorator' + }) + class TestModelDecoratorClass { + @ModelProperty({ + key: 'string', + type: NUMBER_PROPERTY.type, + displayName: 'String Key' + }) + public stringKey!: number; + + @ModelProperty({ + key: 'symbol', + type: BOOLEAN_PROPERTY.type, + required: false + }) + public [symbolKey]: boolean; + } + + beforeEach(() => { + mockModelLibrary = { + registerModelClass: jest.fn(), + registerModelProperty: jest.fn() + }; + + deferredModelDecoratorRegistrations.forEach(deferred => deferred(mockModelLibrary as ModelLibrary)); + }); + + test('can be used to register classes', () => { + expect(mockModelLibrary.registerModelClass).toHaveBeenCalledWith(TestModelDecoratorClass, { + type: 'test-model-decorator', + displayName: 'Test Model Decorator' + }); + }); + + test('can be used to register properties', () => { + expect(mockModelLibrary.registerModelProperty).toHaveBeenCalledWith(TestModelDecoratorClass, 'stringKey', { + key: 'string', + type: NUMBER_PROPERTY.type, + displayName: 'String Key' + }); + expect(mockModelLibrary.registerModelProperty).toHaveBeenCalledWith(TestModelDecoratorClass, symbolKey, { + key: 'symbol', + type: BOOLEAN_PROPERTY.type, + required: false + }); + }); +}); diff --git a/src/model/registration/model-decorators.ts b/src/model/registration/model-decorators.ts new file mode 100644 index 00000000..55db376e --- /dev/null +++ b/src/model/registration/model-decorators.ts @@ -0,0 +1,33 @@ +import { ObjectConstructable } from '../../util/constructable'; +import { ModelLibrary, ModelPropertyRegistrationInformation, ModelRegistrationInformation } from './model-registration'; + +export type DeferredModelDecoratorRegistration = (modelLibrary: ModelLibrary) => void; +export const deferredModelDecoratorRegistrations: DeferredModelDecoratorRegistration[] = []; + +/** + * Registers the decorated model with the provided information + */ +// tslint:disable-next-line:only-arrow-functions +export function Model(registrationInfo: ModelRegistrationInformation): (target: ObjectConstructable) => void { + return (modelClass: ObjectConstructable): void => { + deferredModelDecoratorRegistrations.push(modelLibrary => + modelLibrary.registerModelClass(modelClass, registrationInfo) + ); + }; +} + +/** + * Registers the decorated property with the containing model + */ +// tslint:disable-next-line:only-arrow-functions +export function ModelProperty(registrationInfo: ModelPropertyRegistrationInformation): PropertyDecorator { + return (modelPrototype: object, propertyKey: string | symbol): void => { + deferredModelDecoratorRegistrations.push(modelLibrary => + modelLibrary.registerModelProperty( + modelPrototype.constructor as ObjectConstructable, + propertyKey as keyof object, + registrationInfo + ) + ); + }; +} diff --git a/src/model/registration/model-registration.test.ts b/src/model/registration/model-registration.test.ts new file mode 100644 index 00000000..19a36dc7 --- /dev/null +++ b/src/model/registration/model-registration.test.ts @@ -0,0 +1,243 @@ +// tslint:disable:completed-docs +import { Logger } from '../../util/logging/logger'; +import { BOOLEAN_PROPERTY, STRING_PROPERTY } from '../property/predefined/primitive-model-property-types'; +import { deferredModelDecoratorRegistrations } from './model-decorators'; +import { ModelLibrary } from './model-registration'; + +describe('Model', () => { + const testClass = class TestClass {}; + let library: ModelLibrary; + let mockLogger: Partial; + + beforeEach(() => { + mockLogger = {}; + library = new ModelLibrary(mockLogger as Logger); + }); + + test('can be registered', () => { + library.registerModelClass(testClass, { type: 'test-model', displayName: 'Test Model Display' }); + expect(library.lookupModelMetadata(testClass)).toEqual({ + type: 'test-model', + displayName: 'Test Model Display', + supportedDataSourceTypes: [] + }); + + expect(library.lookupModelClass('test-model')).toBe(testClass); + }); + + test('defaults display name on registration', () => { + library.registerModelClass(testClass, { type: 'test-model' }); + expect(library.lookupModelMetadata(testClass)).toEqual({ + type: 'test-model', + displayName: 'Test Model', + supportedDataSourceTypes: [] + }); + + expect(library.lookupModelClass('test-model')).toBe(testClass); + }); + + test('types can not be re-registered', () => { + mockLogger.error = jest.fn(); + library.registerModelClass(testClass, { type: 'test-model' }); + library.registerModelClass(class OtherClass {}, { type: 'test-model' }); + + expect(mockLogger.error).toHaveBeenCalledWith('Model types may not be registered more than once: test-model'); + expect(library.lookupModelClass('test-model')).toBe(testClass); + }); + + test('classes can not be re-registered', () => { + mockLogger.error = jest.fn(); + library.registerModelClass(testClass, { type: 'test-model' }); + library.registerModelClass(testClass, { type: 'other-test-model' }); + + expect(library.lookupModelMetadata(testClass)).toEqual(expect.objectContaining({ type: 'test-model' })); + expect(mockLogger.error).toHaveBeenCalledWith('Model classes may not be registered more than once: TestClass'); + }); + + test('lookup fails for unregistered models', () => { + mockLogger.info = jest.fn(); + + expect(library.lookupModelMetadata(testClass)).toBeUndefined(); + expect(mockLogger.info).toHaveBeenLastCalledWith('No type registered matching class: TestClass'); + + expect(library.lookupModelClass('test-model')).toBeUndefined(); + + expect(mockLogger.info).toHaveBeenLastCalledWith('No class registered matching type: test-model'); + }); + + test('can return compatible models', () => { + const childClass1 = class extends testClass {}; + const childClass2 = class extends testClass {}; + const childOfChildClass1 = class extends childClass1 {}; + + library.registerModelClass(testClass, { type: 'test-class' }); + library.registerModelClass(childClass1, { type: 'child-class-1' }); + library.registerModelClass(childClass2, { type: 'child-class-2' }); + library.registerModelClass(childOfChildClass1, { type: 'child-of-child-class-1' }); + + expect(library.getAllCompatibleModelClasses(testClass)).toEqual( + expect.arrayContaining([testClass, childClass1, childClass2, childOfChildClass1]) + ); + + expect(library.getAllCompatibleModelClasses(childClass1)).toEqual( + expect.arrayContaining([childClass1, childOfChildClass1]) + ); + + expect(library.getAllCompatibleModelClasses(childClass2)).toEqual(expect.arrayContaining([childClass2])); + + expect(library.getAllCompatibleModelClasses(childOfChildClass1)).toEqual( + expect.arrayContaining([childOfChildClass1]) + ); + }); + + test('returns empty array if no compatible models', () => { + expect(library.getAllCompatibleModelClasses(class {})).toEqual([]); + }); +}); + +describe('Model properties', () => { + const symbolKey = Symbol('symbol key'); + const testClass = class TestClass { + public readonly runtimeKey?: string; + public readonly [symbolKey]: boolean = false; + }; + let library: ModelLibrary; + let mockLogger: Partial; + + beforeEach(() => { + mockLogger = {}; + library = new ModelLibrary(mockLogger as Logger); + }); + + test('can be registered and properly defaulted', () => { + library.registerModelProperty(testClass, 'runtimeKey', { + key: 'stringKey', + type: STRING_PROPERTY.type, + required: false, + displayName: 'My fancy string' + }); + // tslint:disable-next-line: no-any https://github.com/microsoft/TypeScript/issues/38009 + library.registerModelProperty(testClass, symbolKey as any, { + key: 'symbolKey', + type: { key: BOOLEAN_PROPERTY.type } + }); + + expect(library.lookupModelProperties(testClass)).toEqual([ + { + key: 'stringKey', + type: { key: STRING_PROPERTY.type }, + required: false, + displayName: 'My fancy string', + runtimeKey: 'runtimeKey' + }, + { + key: 'symbolKey', + type: { key: BOOLEAN_PROPERTY.type }, + required: false, + displayName: 'Symbol Key', + runtimeKey: symbolKey + } + ]); + }); + + test('metadata cannot be changed externally', () => { + library.registerModelProperty(testClass, 'runtimeKey', { type: STRING_PROPERTY.type, key: 'runtimeKey' }); + const metadata = library.lookupModelProperties(testClass); + metadata.forEach(val => (val.key = 'overwritten')); + + expect(library.lookupModelProperties(testClass)).toEqual([expect.objectContaining({ key: 'runtimeKey' })]); + }); + + test('properties can not be re-registered', () => { + mockLogger.error = jest.fn(); + library.registerModelProperty(testClass, 'runtimeKey', { type: STRING_PROPERTY.type, key: 'runtimeKey' }); + library.registerModelProperty(testClass, 'runtimeKey', { type: BOOLEAN_PROPERTY.type, key: 'runtimeKey' }); + + expect(mockLogger.error).toHaveBeenCalledWith( + 'Model properties may not be registered more than once: TestClass.runtimeKey' + ); + expect(library.lookupModelProperties(testClass)).toEqual([ + expect.objectContaining({ type: { key: STRING_PROPERTY.type } }) + ]); + }); + + test('lookup always succeeds for registered classes', () => { + // A model class can be registered without properties - lookup should not fail + library.registerModelClass(testClass, { type: 'test-model' }); + expect(library.lookupModelProperties(testClass)).toEqual([]); + }); + + test('lookup returns empty array for unregistered model classes', () => { + mockLogger.info = jest.fn(); + + expect(library.lookupModelProperties(class OtherClass {})).toEqual([]); + }); + + test('are inherited from super classes', () => { + const childTestClass = class ChildTestClass extends testClass { + public readonly childProp!: string; + }; + const grandChildTestClass = class GrandChildTestClass extends childTestClass { + public readonly grandChildProp!: string; + }; + library.registerModelProperty(testClass, 'runtimeKey', { key: 'runtimeKey', type: STRING_PROPERTY.type }); + library.registerModelProperty(childTestClass, 'childProp', { key: 'childProp', type: STRING_PROPERTY.type }); + library.registerModelProperty(grandChildTestClass, 'grandChildProp', { + key: 'grandChildProp', + type: STRING_PROPERTY.type + }); + + expect(library.lookupModelProperties(testClass)).toEqual([expect.objectContaining({ key: 'runtimeKey' })]); + expect(library.lookupModelProperties(childTestClass)).toEqual([ + expect.objectContaining({ key: 'runtimeKey' }), + expect.objectContaining({ key: 'childProp' }) + ]); + expect(library.lookupModelProperties(grandChildTestClass)).toEqual([ + expect.objectContaining({ key: 'runtimeKey' }), + expect.objectContaining({ key: 'childProp' }), + expect.objectContaining({ key: 'grandChildProp' }) + ]); + }); +}); + +describe('Model Library', () => { + const testClass = class TestClass { + public readonly key!: string; + }; + let library: ModelLibrary; + let mockLogger: Partial; + + beforeEach(() => { + mockLogger = {}; + library = new ModelLibrary(mockLogger as Logger); + }); + + test('supports decorator information added after construction', () => { + deferredModelDecoratorRegistrations.length = 0; + deferredModelDecoratorRegistrations.push( + providedLib => providedLib.registerModelClass(testClass, { type: 'test-model' }), + providedLib => providedLib.registerModelProperty(testClass, 'key', { key: 'key', type: STRING_PROPERTY.type }) + ); + + expect(library.lookupModelMetadata(testClass)).toEqual(expect.objectContaining({ type: 'test-model' })); + + expect(library.lookupModelClass('test-model')).toBe(testClass); + + expect(library.lookupModelProperties(testClass)).toEqual([expect.objectContaining({ key: 'key' })]); + }); + + test('supports deferred registrations added before construction', () => { + deferredModelDecoratorRegistrations.length = 0; + deferredModelDecoratorRegistrations.push( + providedLib => providedLib.registerModelClass(testClass, { type: 'test-model' }), + providedLib => providedLib.registerModelProperty(testClass, 'key', { key: 'key', type: STRING_PROPERTY.type }) + ); + library = new ModelLibrary(mockLogger as Logger); + + expect(library.lookupModelMetadata(testClass)).toEqual(expect.objectContaining({ type: 'test-model' })); + + expect(library.lookupModelClass('test-model')).toBe(testClass); + + expect(library.lookupModelProperties(testClass)).toEqual([expect.objectContaining({ key: 'key' })]); + }); +}); diff --git a/src/model/registration/model-registration.ts b/src/model/registration/model-registration.ts new file mode 100644 index 00000000..4c5a70a7 --- /dev/null +++ b/src/model/registration/model-registration.ts @@ -0,0 +1,286 @@ +import { cloneDeep, defaults, startCase } from 'lodash'; +import { Constructable, ObjectConstructable, UnknownConstructable } from '../../util/constructable'; +import { Logger } from '../../util/logging/logger'; +import { getReflectedPropertyType } from '../../util/reflection/reflection-utilities'; +import { ModelPropertyTypeInstance } from '../property/model-property-type-library'; +import { deferredModelDecoratorRegistrations } from './model-decorators'; + +/** + * Model Library represents a story of metadata information describing the models in the system and their properties. + */ +export class ModelLibrary { + private readonly modelConstructorByType: Map = new Map(); + private readonly modelClassMetadata: Map = new Map(); + private readonly modelPropertyMetadata: Map< + ObjectConstructable, + Map> + > = new Map(); + private lastDeferredIndexRead: number = 0; + private currentlyProcessingDeferred: boolean = false; + + public constructor(private readonly logger: Logger) {} + + /** + * Registers a model class with the provieded information + * + * No action is taken if the model class or type has already been registered + */ + public registerModelClass( + modelClass: ObjectConstructable, + registrationInformation: ModelRegistrationInformation + ): void { + this.processRegistrationQueue(); + this.trackNewModelType(modelClass, registrationInformation.type); + + if (this.modelClassMetadata.has(modelClass)) { + this.logger.error(`Model classes may not be registered more than once: ${modelClass.name}`); + + return; + } + + this.modelClassMetadata.set(modelClass, this.convertModelRegistrationInfoToMetadata(registrationInformation)); + } + + /** + * Associates a model property identified by the provided key with a model class. + * + * No action is taken if the property key has already been registered + */ + public registerModelProperty( + modelClass: Constructable, + runtimeKey: keyof T, + registrationInfo: ModelPropertyRegistrationInformation + ): void { + this.processRegistrationQueue(); + const propertyKey = registrationInfo.key; + if (!this.modelPropertyMetadata.has(modelClass)) { + this.modelPropertyMetadata.set(modelClass, new Map()); + } + const propertyMetadataSet = this.modelPropertyMetadata.get(modelClass)!; + + if (propertyMetadataSet.has(propertyKey)) { + this.logger.error(`Model properties may not be registered more than once: ${modelClass.name}.${propertyKey}`); + + return; + } + + propertyMetadataSet.set( + propertyKey, + this.convertPropertyRegistrationToMetadata(runtimeKey, registrationInfo, modelClass) as ModelPropertyMetadata< + object + > + ); + } + + /** + * Looks up and returns the constructor of the model class associated with the provided string. + * Returns undefined if no class is found. + */ + public lookupModelClass(modelType: string): ObjectConstructable | undefined { + this.processRegistrationQueue(); + if (this.modelConstructorByType.has(modelType)) { + return this.modelConstructorByType.get(modelType)!; + } + this.logger.info(`No class registered matching type: ${modelType}`); + + return undefined; + } + + /** + * Looks up and returns the model metadata for the provided model class. + * Returns undefined if no type is found. + */ + public lookupModelMetadata(modelClass: ObjectConstructable): ModelClassMetadata | undefined { + this.processRegistrationQueue(); + if (this.modelClassMetadata.has(modelClass)) { + return this.modelClassMetadata.get(modelClass)!; + } + this.logger.info(`No type registered matching class: ${modelClass.name}`); + + return undefined; + } + + /** + * Returns an array of properties registered to the provided model class. + * + * Returns empty array if class is not found. + */ + public lookupModelProperties(modelClass: Constructable): ModelPropertyMetadata[] { + this.processRegistrationQueue(); + const modelProperties: ModelPropertyMetadata[] = []; + + this.getMetadataChain(modelClass, this.modelPropertyMetadata).forEach(propertyMetadataMap => { + Array.from(propertyMetadataMap.values()) + .map(cloneDeep) + .forEach(propertyMetadata => modelProperties.push(propertyMetadata)); + }); + + return modelProperties; + } + + /** + * Returns all model classes that contain `modelClass` on their prototype chain, including + * the provided class if registered. + */ + public getAllCompatibleModelClasses(modelClass: ObjectConstructable): ObjectConstructable[] { + this.processRegistrationQueue(); + + return Array.from(this.modelClassMetadata.keys()).filter( + registeredClass => registeredClass === modelClass || registeredClass.prototype instanceof modelClass + ); + } + + private trackNewModelType(modelClass: ObjectConstructable, modelType: string): void { + if (this.modelConstructorByType.has(modelType)) { + this.logger.error(`Model types may not be registered more than once: ${modelType}`); + + return; + } + + if (!this.modelPropertyMetadata.has(modelClass)) { + // Model property should be empty in case no properties were registered + this.modelPropertyMetadata.set(modelClass, new Map()); + } + + this.modelConstructorByType.set(modelType, modelClass); + } + + private convertModelRegistrationInfoToMetadata(registrationInfo: ModelRegistrationInformation): ModelClassMetadata { + return { + type: registrationInfo.type, + displayName: registrationInfo.displayName || this.formatAsDisplayName(registrationInfo.type), + supportedDataSourceTypes: registrationInfo.supportedDataSourceTypes || [] + }; + } + + /** + * Each discovered metadata object, in descending order (i.e. `[modelClassParent, modelClass]`) + */ + private getMetadataChain(modelClass: ObjectConstructable, metadataMap: Map): T[] { + const metadataChain = []; + let constructor: ObjectConstructable | undefined = modelClass; + while (constructor) { + if (metadataMap.has(constructor)) { + metadataChain.unshift(metadataMap.get(constructor)!); + } + constructor = Object.getPrototypeOf(constructor) as ObjectConstructable | undefined; + } + + return metadataChain; + } + + private convertPropertyRegistrationToMetadata( + propertyKey: keyof V, + registrationInfo: ModelPropertyRegistrationInformation, + modelClass: Constructable + ): ModelPropertyMetadata { + const registrationInfoWithConvertedType = { + ...registrationInfo, + type: typeof registrationInfo.type === 'string' ? { key: registrationInfo.type } : { ...registrationInfo.type } + }; + + return defaults(registrationInfoWithConvertedType, { + displayName: this.formatAsDisplayName(registrationInfo.key), + required: false, + runtimeKey: propertyKey, + runtimeType: getReflectedPropertyType(modelClass, propertyKey) + }); + } + + private processRegistrationQueue(): void { + if (this.currentlyProcessingDeferred) { + return; // Lazy shortcut lock to prevent infinitely looping on this + } + this.currentlyProcessingDeferred = true; + // tslint:disable-next-line:max-line-length + for ( + this.lastDeferredIndexRead; + this.lastDeferredIndexRead < deferredModelDecoratorRegistrations.length; + this.lastDeferredIndexRead++ + ) { + const deferredRegistration = deferredModelDecoratorRegistrations[this.lastDeferredIndexRead]; + deferredRegistration(this); + } + this.currentlyProcessingDeferred = false; + } + + private formatAsDisplayName(input: string): string { + return startCase(input); + } +} + +/** + * Registration information for a model property + */ +export interface ModelPropertyRegistrationInformation { + /** + * Key used for serializing/deserializing this model property + */ + key: string; + + /** + * When displaying in an editor, use this name. + * Defaults to key value converted to Start Case. + * Underscores, dashes and camel casing is all converted to spaces. Trailing and leading spaces + * are removed, and the first letter of each word is capitalized. + * i.e. my-special_key => My Special Key + */ + displayName?: string; + + /** + * See `ModelPropertyTypeLibrary` + * An extensible string or object value that dictates the type of this property (for editing and validation) + */ + type: string | ModelPropertyTypeInstance; + + /** + * Is this property required to be set? If so, the editor will require it and validation will fail if missing + * Defaults to false + */ + required?: boolean; +} +/** + * Registration information for a model class + */ +export interface ModelRegistrationInformation { + /** + * Key used for serializing/deserializing this model class + */ + readonly type: string; + + /** + * When displaying in an editor, use this name. + * Defaults to type value converted to Start Case. + * Underscores, dashes and camel casing is all converted to spaces. Trailing and leading spaces + * are removed, and the first letter of each word is capitalized. + * i.e. my-special_type => My Special Type + */ + displayName?: string; + + /** + * Model type for data source expected by this model + */ + supportedDataSourceTypes?: ObjectConstructable[]; +} + +interface ModelClassMetadata extends Required {} + +/** + * Model Property metadata after all defaults have been applied + */ +export interface ModelPropertyMetadata extends Required { + /** + * Name of the property key at runtime + */ + runtimeKey: keyof T; + + /** + * Constructor of type at runtime + */ + runtimeType: UnknownConstructable | undefined; + + /** + * An object containing property type metadata for this property. + */ + type: ModelPropertyTypeInstance; +} diff --git a/src/persistence/deserialization/collection/array-deserializer.test.ts b/src/persistence/deserialization/collection/array-deserializer.test.ts new file mode 100644 index 00000000..09036ed3 --- /dev/null +++ b/src/persistence/deserialization/collection/array-deserializer.test.ts @@ -0,0 +1,71 @@ +// tslint:disable:completed-docs +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { DeserializationManager } from '../deserialization-manager'; +import { ArrayDeserializer } from './array-deserializer'; + +describe('Array deserializer', () => { + let deserializer: ArrayDeserializer; + let mockDeserializationManager: DeserializationManager; + const testClass = class { + public array!: unknown[]; + }; + let parent: typeof testClass.prototype; + let location: PropertyLocation; + + const valuesToTest = { + string: 'string', + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + literalArray: ['test'], + emptyArray: [], + constructedArray: new Array(1), + instance: new (class ExampleClass {})() + }; + beforeEach(() => { + mockDeserializationManager = { + deserialize: jest.fn() + // tslint:disable-next-line:no-any + } as any; + deserializer = new ArrayDeserializer(mockDeserializationManager); + parent = new testClass(); + location = PropertyLocation.forModelProperty(parent, 'array'); + }); + + test('should support arrays only', () => { + expect(mapValues(valuesToTest, value => deserializer.canDeserialize(value))).toEqual({ + string: false, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: false, + literalArray: true, + emptyArray: true, + constructedArray: true, + instance: false + }); + }); + + test('should pass array values to the deserialization manager', () => { + deserializer.deserialize(['test', 5, []], location); + expect(mockDeserializationManager.deserialize).toHaveBeenCalledTimes(3); + expect(mockDeserializationManager.deserialize).toHaveBeenNthCalledWith(1, 'test', expect.any(PropertyLocation)); + expect(mockDeserializationManager.deserialize).toHaveBeenNthCalledWith(2, 5, expect.any(PropertyLocation)); + expect(mockDeserializationManager.deserialize).toHaveBeenNthCalledWith(3, [], expect.any(PropertyLocation)); + }); + + test('should pass model context for child deserialization', () => { + deserializer.deserialize(['test'], location); + expect(mockDeserializationManager.deserialize).toHaveBeenCalledWith( + 'test', + expect.objectContaining({ parentModel: parent, propertyKey: 'array:0' }) + ); + }); +}); diff --git a/src/persistence/deserialization/collection/array-deserializer.ts b/src/persistence/deserialization/collection/array-deserializer.ts new file mode 100644 index 00000000..618a642c --- /dev/null +++ b/src/persistence/deserialization/collection/array-deserializer.ts @@ -0,0 +1,32 @@ +import { PropertyLocation } from '../../../model/property/property-location'; +import { JsonPrimitive, JsonPrimitiveArray } from '../../model-json'; +import { DeserializationManager } from '../deserialization-manager'; +import { Deserializer } from '../deserializer'; + +/** + * Handles deserialization of an array type, recursing back to the manager for each value + */ +export class ArrayDeserializer implements Deserializer { + public constructor(private readonly deserializationManager: DeserializationManager) {} + + /** + * @inheritdoc + */ + public canDeserialize(json: JsonPrimitive): json is JsonPrimitiveArray { + return Array.isArray(json); + } + + /** + * @inheritdoc + */ + public deserialize(array: JsonPrimitiveArray, location?: PropertyLocation): unknown[] { + const newArray: unknown[] = []; + + const deserializeModelValue = (value: JsonPrimitive, index: number) => + this.deserializationManager.deserialize(value, location!.buildChildFromObjectAndKey(newArray, index)); + + newArray.push(...array.map(deserializeModelValue)); + + return newArray; + } +} diff --git a/src/persistence/deserialization/collection/object-deserializer.test.ts b/src/persistence/deserialization/collection/object-deserializer.test.ts new file mode 100644 index 00000000..fe07053b --- /dev/null +++ b/src/persistence/deserialization/collection/object-deserializer.test.ts @@ -0,0 +1,81 @@ +// tslint:disable:completed-docs +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { DeserializationManager } from '../deserialization-manager'; +import { ObjectDeserializer } from './object-deserializer'; + +describe('Object deserializer', () => { + let deserializer: ObjectDeserializer; + let mockDeserializationManager: DeserializationManager; + const testClass = class { + public obj!: object; + }; + let parent: typeof testClass.prototype; + let location: PropertyLocation; + + const valuesToTest = { + string: 'string', + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + array: [], + instance: new (class ExampleClass {})() + }; + beforeEach(() => { + mockDeserializationManager = { + deserialize: jest.fn() + // tslint:disable-next-line:no-any + } as any; + deserializer = new ObjectDeserializer(mockDeserializationManager); + parent = new testClass(); + location = PropertyLocation.forModelProperty(parent, 'obj'); + }); + + test('should support objects only', () => { + expect(mapValues(valuesToTest, value => deserializer.canDeserialize(value))).toEqual({ + string: false, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: true, + array: false, + instance: false + }); + }); + + test('should pass object values to the deserialization manager', () => { + deserializer.deserialize( + { + string: 'string', + undefined: undefined, + object: {}, + array: [] + }, + location + ); + expect(mockDeserializationManager.deserialize).toHaveBeenCalledTimes(4); + expect(mockDeserializationManager.deserialize).toHaveBeenNthCalledWith(1, 'string', expect.any(PropertyLocation)); + expect(mockDeserializationManager.deserialize).toHaveBeenNthCalledWith(2, undefined, expect.any(PropertyLocation)); + expect(mockDeserializationManager.deserialize).toHaveBeenNthCalledWith(3, {}, expect.any(PropertyLocation)); + expect(mockDeserializationManager.deserialize).toHaveBeenNthCalledWith(4, [], expect.any(PropertyLocation)); + }); + + test('should pass location context for child deserialization', () => { + deserializer.deserialize( + { + stringKey: 'stringValue' + }, + location + ); + expect(mockDeserializationManager.deserialize).toHaveBeenCalledWith( + 'stringValue', + expect.objectContaining({ parentModel: parent, propertyKey: 'obj:stringKey' }) + ); + }); +}); diff --git a/src/persistence/deserialization/collection/object-deserializer.ts b/src/persistence/deserialization/collection/object-deserializer.ts new file mode 100644 index 00000000..288a5487 --- /dev/null +++ b/src/persistence/deserialization/collection/object-deserializer.ts @@ -0,0 +1,32 @@ +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { JsonPrimitive } from '../../model-json'; +import { DeserializationManager } from '../deserialization-manager'; +import { Deserializer } from '../deserializer'; + +/** + * Handles deserialization of a primitive JSON object, recursing back to the manager for each value + */ +export class ObjectDeserializer implements Deserializer { + public constructor(private readonly deserializationManager: DeserializationManager) {} + + /** + * @inheritdoc + */ + public canDeserialize(json: JsonPrimitive): json is object { + return typeof json === 'object' && json !== null && Object.getPrototypeOf(json) === Object.prototype; + } + + /** + * @inheritdoc + */ + public deserialize(object: T, location: PropertyLocation): object { + const newObject = {}; + + const deserializeModelValue = (value: JsonPrimitive, key: keyof T) => + this.deserializationManager.deserialize(value, location.buildChildFromObjectAndKey(newObject as T, key)); + + // tslint:disable-next-line:prefer-object-spread seems like a questionable rule, breaks behavior here + return Object.assign(newObject, mapValues(object, deserializeModelValue)); + } +} diff --git a/src/persistence/deserialization/deserialization-manager.test.ts b/src/persistence/deserialization/deserialization-manager.test.ts new file mode 100644 index 00000000..9ac01747 --- /dev/null +++ b/src/persistence/deserialization/deserialization-manager.test.ts @@ -0,0 +1,62 @@ +import { Logger } from '../../util/logging/logger'; +import { DeserializationManager } from './deserialization-manager'; +import { Deserializer } from './deserializer'; + +describe('Deserialization manager', () => { + let manager: DeserializationManager; + let mockLogger: Partial; + + const createDeserializerMatchingNumber = (valueToMatch: T): Deserializer => { + const deserializer: Deserializer = { + canDeserialize: (value): value is T => value === valueToMatch, + deserialize: value => value + }; + spyOn(deserializer, 'deserialize'); + + return deserializer; + }; + + beforeEach(() => { + mockLogger = {}; + manager = new DeserializationManager(mockLogger as Logger); + }); + + test('allows registration of deserializers', () => { + const deserializer = createDeserializerMatchingNumber(5); + manager.registerDeserializer(deserializer); + manager.deserialize(5); + + expect(deserializer.deserialize).toHaveBeenCalledTimes(1); + }); + + test('uses first matching deserializer', () => { + const firstDeserializer = createDeserializerMatchingNumber(5); + const secondDeserializer = createDeserializerMatchingNumber(6); + const thirdDeserializer = createDeserializerMatchingNumber(6); + + manager.registerDeserializer(firstDeserializer); + manager.registerDeserializer(secondDeserializer); + manager.registerDeserializer(thirdDeserializer); + manager.deserialize(6); + + expect(firstDeserializer.deserialize).not.toHaveBeenCalled(); + + expect(secondDeserializer.deserialize).toHaveBeenCalledTimes(1); + + expect(thirdDeserializer.deserialize).not.toHaveBeenCalled(); + }); + + test('throws error if no matching deserializer found', () => { + mockLogger.error = jest.fn((message: string) => ({ + throw: jest.fn(() => { + throw Error(message); + }) + })) as jest.Mock; + + const deserializer = createDeserializerMatchingNumber(5); + manager.registerDeserializer(deserializer); + + expect(() => manager.deserialize(6)).toThrow('No deserializer registered matching provided json value'); + expect(mockLogger.error).toHaveBeenCalledWith('No deserializer registered matching provided json value'); + }); +}); diff --git a/src/persistence/deserialization/deserialization-manager.ts b/src/persistence/deserialization/deserialization-manager.ts new file mode 100644 index 00000000..ef6a2299 --- /dev/null +++ b/src/persistence/deserialization/deserialization-manager.ts @@ -0,0 +1,42 @@ +import { PropertyLocation } from '../../model/property/property-location'; +import { Logger } from '../../util/logging/logger'; +import { JsonPrimitive } from '../model-json'; +import { Deserializer } from './deserializer'; + +/** + * Allows dynamic registration of deserializers, delegating to the first matching + * Deserializer, by order of registration, for deserialization + */ +export class DeserializationManager { + private readonly deserializers: Deserializer[] = []; + + public constructor(private readonly logger: Logger) {} + + /** + * Adds a new deserialier to the lookup path for deserialization + */ + public registerDeserializer( + deserializer: Deserializer + ): void { + this.deserializers.push(deserializer as Deserializer); + } + + /** + * Searches for the first matching deserializer and delegates to it + * + * Throws Error if no deserializer can be determined or deserialization fails + */ + public deserialize(json: JsonPrimitive, location?: PropertyLocation): T { + return this.getMatchingDeserializer(json).deserialize(json, location); + } + + private getMatchingDeserializer(json: JsonPrimitive): Deserializer { + const deserializer = this.deserializers.find(potentialDeserializer => potentialDeserializer.canDeserialize(json)); + + if (deserializer) { + return deserializer as Deserializer; + } + + return this.logger.error('No deserializer registered matching provided json value').throw(); + } +} diff --git a/src/persistence/deserialization/deserializer.ts b/src/persistence/deserialization/deserializer.ts new file mode 100644 index 00000000..c58cc324 --- /dev/null +++ b/src/persistence/deserialization/deserializer.ts @@ -0,0 +1,27 @@ +import { ModelPropertyTypeInstance } from '../../model/property/model-property-type-library'; +import { PropertyLocation } from '../../model/property/property-location'; +import { JsonPrimitive } from '../model-json'; + +/** + * A deserialzation class which accepts JSON or some JSON fragment and returns a deserialized version + */ +export interface Deserializer { + /** + * Returns true of the provided value can be deserialized by this deserializer + */ + canDeserialize(json: JsonPrimitive): json is TSerialized; + + /** + * Performs deserialiazation, potentially recursively. Values provided to this method are assumed to be + * valid as determined by the canDeserialize method. + * + * Throws Error if deserialization fails for any reason + */ + deserialize(json: TSerialized, location?: PropertyLocation): TDeserialized; +} + +export type DeserializationFunction = ( + json: TSerialized, + location: PropertyLocation, + propertyType: ModelPropertyTypeInstance +) => TDeserialized; diff --git a/src/persistence/deserialization/model/model-deserializer.test.ts b/src/persistence/deserialization/model/model-deserializer.test.ts new file mode 100644 index 00000000..4580001f --- /dev/null +++ b/src/persistence/deserialization/model/model-deserializer.test.ts @@ -0,0 +1,401 @@ +import { mapValues } from 'lodash'; +import { DataSourceManager } from '../../../data/data-source/manager/data-source-manager'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { + ModelPropertyTypeInstance, + ModelPropertyTypeLibrary +} from '../../../model/property/model-property-type-library'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { ModelPropertyValidator } from '../../../model/property/validation/model-property-validator'; +import { ModelLibrary } from '../../../model/registration/model-registration'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { ThemeManager } from '../../../theming/theme-manager'; +import { Logger } from '../../../util/logging/logger'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { DeserializationManager } from '../deserialization-manager'; +import { ModelDeserializer } from './model-deserializer'; + +describe('Model deserializer', () => { + let deserializer: ModelDeserializer; + let mockDeserializationManager: PartialObjectMock; + let mockModelLibrary: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let mockModelPropertyValidator: PartialObjectMock; + let mockLogger: PartialObjectMock; + let mockDataSourceManager: PartialObjectMock; + let mockVariableManager: PartialObjectMock; + let mockThemeManager: PartialObjectMock; + let mockModelPropertyTypeLibrary: PartialObjectMock; + + const valuesToTest = { + string: 'string', + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + array: [], + instance: new (class ExampleClass {})(), + registeredModel: { + type: 'registered', + first: 'first', + second: 'second' + }, + unregisteredModel: { + type: 'unregistered' + } + }; + + const testClass = class { + public constructor(public readonly runtimeProp?: string) {} + }; + + const parent = new (class { + public childProp!: object; + })(); + const parentLocation = PropertyLocation.forModelProperty(parent, 'childProp'); + + beforeEach(() => { + mockDeserializationManager = { + deserialize: jest.fn().mockReturnValue('deserialized') + }; + mockModelLibrary = { + lookupModelClass: jest.fn().mockReturnValue(testClass), + lookupModelProperties: jest.fn().mockReturnValue( + new Set([ + { + key: 'prop', + runtimeKey: 'runtimeProp', + type: { key: 'runtime-prop-type' } + } + ]) + ) + }; + mockModelManager = { + construct: jest.fn().mockImplementation(() => new testClass()), + initialize: jest.fn().mockImplementation((model: unknown) => model) + }; + + mockModelPropertyValidator = { + validate: jest.fn() + }; + + mockLogger = {}; + + mockDataSourceManager = { + attach: jest.fn(), + detach: jest.fn(), + modelJsonHasData: jest.fn() + }; + + mockVariableManager = { + isVariableExpression: () => false + }; + + mockThemeManager = { + modelJsonHasTheme: jest.fn() + }; + + mockModelPropertyTypeLibrary = { + getPropertyDeserializer: jest.fn() + }; + + deserializer = new ModelDeserializer( + mockDeserializationManager as DeserializationManager, + mockModelLibrary as ModelLibrary, + mockModelManager as ModelManager, + mockModelPropertyValidator as ModelPropertyValidator, + mockLogger as Logger, + mockDataSourceManager as DataSourceManager, + mockVariableManager as VariableManager, + mockThemeManager as ThemeManager, + mockModelPropertyTypeLibrary as ModelPropertyTypeLibrary + ); + }); + + test('should support model json only', () => { + mockModelLibrary.lookupModelClass = jest.fn().mockImplementation(type => { + if (type === 'registered') { + return class {}; + } + + return undefined; + }); + + expect(mapValues(valuesToTest, value => deserializer.canDeserialize(value))).toEqual({ + string: false, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: false, + array: false, + instance: false, + registeredModel: true, + unregisteredModel: false + }); + }); + + test('should deserialize into specified model class', () => { + const result = deserializer.deserialize( + { + type: 'registered', + prop: 'serialized', + random: 'should not be deserialized' + }, + parentLocation + ); + + expect(result).toEqual(new testClass('deserialized')); + + expect(mockModelManager.construct).toHaveBeenCalledTimes(1); + + expect(mockModelManager.construct).toHaveBeenCalledWith(testClass, parent); + + expect(mockDeserializationManager.deserialize).toHaveBeenCalledTimes(1); + + expect(mockDeserializationManager.deserialize).toHaveBeenCalledWith( + 'serialized', + expect.objectContaining({ + propertyKey: 'runtimeProp', + parentModel: result + }), + { key: 'runtime-prop-type' } + ); + }); + + test('populates properties before init has been called', () => { + mockModelManager.initialize = jest + .fn() + .mockImplementation((model: unknown) => expect(model).toEqual(new testClass('deserialized'))); + + deserializer.deserialize({ + type: 'registered', + prop: 'serialized' + }); + + expect(mockModelManager.initialize).toHaveBeenCalledTimes(1); + }); + + test('deserializes data property', () => { + const dataObj = {}; + const mockDataLocation = { + setProperty: jest.fn() + }; + mockDeserializationManager.deserialize = jest.fn().mockImplementation((serialized: unknown) => { + if (serialized === dataObj) { + return dataObj; + } + + return 'deserialized'; + }); + + mockDataSourceManager.modelJsonHasData = jest + .fn() + // tslint:disable-next-line:no-any + .mockReturnValueOnce(true) as any; + + mockDataSourceManager.getPropertyLocationForData = jest.fn().mockReturnValueOnce(mockDataLocation); + + const result = deserializer.deserialize({ + type: 'registered', + prop: 'serialized', + data: dataObj + }); + + expect(result).toEqual(new testClass('deserialized')); + + expect(mockDeserializationManager.deserialize).toHaveBeenCalledWith(dataObj, mockDataLocation); + expect(mockDataLocation.setProperty).toHaveBeenCalledWith(dataObj); + }); + + test('deserializes theme property', () => { + const themeObj = {}; + const mockThemeLocation = { + setProperty: jest.fn() + }; + + mockDeserializationManager.deserialize = jest.fn().mockImplementation((serialized: unknown) => { + if (serialized === themeObj) { + return themeObj; + } + + return 'deserialized'; + }); + mockThemeManager.modelJsonHasTheme = jest + .fn() + // tslint:disable-next-line:no-any + .mockReturnValueOnce(true) as any; + + mockThemeManager.getPropertyLocationForTheme = jest.fn().mockReturnValueOnce(mockThemeLocation); + + const result = deserializer.deserialize({ + type: 'registered', + prop: 'serialized', + theme: themeObj + }); + + expect(result).toEqual(new testClass('deserialized')); + + expect(mockDeserializationManager.deserialize).toHaveBeenCalledWith(themeObj, mockThemeLocation); + expect(mockThemeLocation.setProperty).toHaveBeenCalledWith(themeObj); + }); + + test('does not overwrite undefined properties', () => { + const defaultedClass = class { + public constructor(public readonly runtimeProp?: string, public other: string = 'default') {} + }; + + mockModelLibrary.lookupModelClass = jest.fn().mockReturnValue(defaultedClass); + + mockModelLibrary.lookupModelProperties = jest.fn().mockReturnValue( + new Set([ + { + key: 'prop', + runtimeKey: 'runtimeProp', + required: true + }, + { + key: 'other', + runtimeKey: 'other' + } + ]) + ); + + mockModelManager.construct = jest.fn().mockImplementation(() => new defaultedClass()); + + mockDeserializationManager.deserialize = jest.fn().mockImplementation((serialized: unknown) => serialized); + + expect( + deserializer.deserialize({ + type: 'registered', + prop: 'value' + }) + ).toEqual(new defaultedClass('value')); + + expect( + deserializer.deserialize({ + type: 'registered', + prop: 'value', + other: 'not default' + }) + ).toEqual(new defaultedClass('value', 'not default')); + }); + + test('skips validation for variables', () => { + mockVariableManager.isVariableExpression = jest.fn().mockReturnValue((val: string) => val.startsWith('$')); + deserializer.deserialize({ + type: 'registered', + // tslint:disable-next-line:no-invalid-template-strings + prop: '${test}', + random: 'other' + }); + + expect(mockModelPropertyValidator.validate).not.toHaveBeenCalled(); + }); + + test('requests validation for regular properties', () => { + deserializer.deserialize({ + type: 'registered', + prop: 'serialized', + random: 'should not be deserialized' + }); + + expect(mockModelPropertyValidator.validate).toHaveBeenCalledTimes(1); + expect(mockModelPropertyValidator.validate).nthCalledWith( + 1, + 'serialized', + expect.objectContaining({ + key: 'prop', + runtimeKey: 'runtimeProp' + }) + ); + }); + + test('creates valid locations for deserialized properties', () => { + const result = deserializer.deserialize({ + type: 'registered', + prop: 'serialized' + }) as { [key: string]: string }; + + const location = (mockDeserializationManager.deserialize as jest.Mock).mock.calls[0][1] as PropertyLocation; + + // Setter should assign to runtimeProp, not prop + location.setProperty('newVal'); + + expect(result.runtimeProp).toBe('newVal'); + }); + + test('adds validation to property locations', () => { + expect(() => + deserializer.deserialize({ + type: 'registered', + prop: 'serialized' + }) + ).not.toThrow(); + + const location = (mockDeserializationManager.deserialize as jest.Mock).mock.calls[0][1] as PropertyLocation; + + location.setProperty('unvalidated'); + + expect(mockModelPropertyValidator.validate).toHaveBeenCalledWith( + 'unvalidated', + expect.objectContaining({ + key: 'prop', + runtimeKey: 'runtimeProp' + }) + ); + }); + + test('thows error for nested validation errors', () => { + mockLogger.error = jest.fn((message: string) => ({ + throw: () => { + throw Error(message); + } + })); + + mockModelPropertyValidator.validate = jest.fn().mockImplementation((val: unknown) => { + if (typeof val === 'string') { + throw Error('no strings'); + } + }); + + mockDeserializationManager.deserialize = jest + .fn() + .mockImplementationOnce(deserializer.deserialize.bind(deserializer)); + + expect(() => + deserializer.deserialize({ + type: 'registered', + prop: { + type: 'registered', + prop: 'string value' + } + }) + ).toThrow('Error deserializing property [prop]'); + }); + + test('delegates to custom model property deserializers', () => { + const mockPropertyDeserializer = jest.fn(() => 'mock custom deserialized property') as jest.Mock; + mockModelPropertyTypeLibrary.getPropertyDeserializer = jest.fn((type: ModelPropertyTypeInstance) => + type.key === 'runtime-prop-type' ? mockPropertyDeserializer : undefined + ); + + expect( + deserializer.deserialize( + { + type: 'registered', + prop: 'serialized' + }, + parentLocation + ) + ).toEqual(new testClass('mock custom deserialized property')); + + expect(mockPropertyDeserializer).toHaveBeenCalledWith('serialized', expect.any(PropertyLocation), { + key: 'runtime-prop-type' + }); + }); +}); diff --git a/src/persistence/deserialization/model/model-deserializer.ts b/src/persistence/deserialization/model/model-deserializer.ts new file mode 100644 index 00000000..ae6f71ad --- /dev/null +++ b/src/persistence/deserialization/model/model-deserializer.ts @@ -0,0 +1,110 @@ +import { DataSourceManager } from '../../../data/data-source/manager/data-source-manager'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { ModelPropertyTypeLibrary } from '../../../model/property/model-property-type-library'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { ModelPropertyValidator } from '../../../model/property/validation/model-property-validator'; +import { ModelLibrary, ModelPropertyMetadata } from '../../../model/registration/model-registration'; +import { ThemeManager } from '../../../theming/theme-manager'; +import { Constructable } from '../../../util/constructable'; +import { Logger } from '../../../util/logging/logger'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { JsonPrimitive, ModelJson } from '../../model-json'; +import { DeserializationManager } from '../deserialization-manager'; +import { DeserializationFunction, Deserializer } from '../deserializer'; + +/** + * Handles deserialization of a JSON object with a registered type property, + * instantiating the associated model class + */ +export class ModelDeserializer implements Deserializer { + public constructor( + private readonly deserializationManager: DeserializationManager, + private readonly modelLibrary: ModelLibrary, + private readonly modelManager: ModelManager, + private readonly modelPropertyValidator: ModelPropertyValidator, + private readonly logger: Logger, + private readonly dataSourceManager: DataSourceManager, + private readonly variableManager: VariableManager, + private readonly themeManager: ThemeManager, + private readonly modelPropertyTypeLibrary: ModelPropertyTypeLibrary + ) {} + + /** + * @inheritdoc + */ + public canDeserialize(json: JsonPrimitive): json is ModelJson { + return this.isObjectWithTypeProperty(json) && this.isRegisteredModelType(json.type); + } + + /** + * @inheritdoc + */ + public deserialize(json: ModelJson, location?: PropertyLocation): object { + const modelClass = this.modelLibrary.lookupModelClass(json.type)!; + const parentModel = location && location.parentModel; + + const instance = this.modelManager.construct(modelClass as Constructable<{ [key: string]: unknown }>, parentModel); + + this.modelLibrary.lookupModelProperties(modelClass).forEach(propMetdata => { + const serializedValue = json[propMetdata.key]; + this.validateProperty(serializedValue, propMetdata); + try { + const deserializedValue = this.getDeserializationFunctionForProperty(propMetdata)( + serializedValue, + PropertyLocation.forModelProperty(instance, propMetdata.runtimeKey).withValidator(valueToValidate => + // Currently variables are allowed to be undefined. May change this in the future + this.modelPropertyValidator.validate(valueToValidate, { ...propMetdata, required: false }) + ), + propMetdata.type + ); + if (deserializedValue !== undefined) { + instance[propMetdata.runtimeKey] = deserializedValue; + } + } catch (untypedErr) { + return this.logger.error(`Error deserializing property [${propMetdata.key}]`, untypedErr as Error).throw(); + } + }); + + if (this.dataSourceManager.modelJsonHasData(json)) { + const dataLocation = this.dataSourceManager.getPropertyLocationForData(instance); + dataLocation.setProperty(this.deserializationManager.deserialize(json.data, dataLocation)); + } + + if (this.themeManager.modelJsonHasTheme(json)) { + const themeLocation = this.themeManager.getPropertyLocationForTheme(instance); + themeLocation.setProperty(this.deserializationManager.deserialize(json.theme, themeLocation)); + } + + this.modelManager.initialize(instance); + + return instance; + } + + private getDeserializationFunctionForProperty(metadata: ModelPropertyMetadata): DeserializationFunction { + const propertyTypeOverride = this.modelPropertyTypeLibrary.getPropertyDeserializer(metadata.type); + const defaultDeserialize = this.deserializationManager.deserialize.bind(this.deserializationManager); + + return propertyTypeOverride || (defaultDeserialize as DeserializationFunction); + } + + private isObjectWithTypeProperty(json?: JsonPrimitive): json is ModelJson { + return ( + typeof json === 'object' && + json !== null && + Object.getPrototypeOf(json) === Object.prototype && + 'type' in json && + (typeof json.type as unknown) === 'string' + ); + } + + private isRegisteredModelType(modelType: string): boolean { + return !!this.modelLibrary.lookupModelClass(modelType); + } + + private validateProperty(value: unknown, propertyMetadata: ModelPropertyMetadata): void { + if (typeof value === 'string' && this.variableManager.isVariableExpression(value)) { + return; // Variable expressions are validated at resolve time + } + this.modelPropertyValidator.validate(value, propertyMetadata); + } +} diff --git a/src/persistence/deserialization/primitive/primitive-deserialize.test.ts b/src/persistence/deserialization/primitive/primitive-deserialize.test.ts new file mode 100644 index 00000000..90173f2c --- /dev/null +++ b/src/persistence/deserialization/primitive/primitive-deserialize.test.ts @@ -0,0 +1,40 @@ +import { mapValues } from 'lodash'; +import { PrimitiveDeserializer } from './primitive-deserializer'; + +describe('Primitive deserializer', () => { + let deserializer: PrimitiveDeserializer; + + const valuesToTest = { + string: 'string', + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + array: ['test'], + instance: new (class ExampleClass {})() + }; + beforeEach(() => { + deserializer = new PrimitiveDeserializer(); + }); + + test('should support string, number, boolean, undefined, null', () => { + expect(mapValues(valuesToTest, value => deserializer.canDeserialize(value))).toEqual({ + string: true, + number: true, + boolean: true, + date: false, + null: true, + undefined: true, + object: false, + array: false, + instance: false + }); + }); + + test('should return the value given', () => { + expect(mapValues(valuesToTest, value => deserializer.deserialize(value))).toEqual(valuesToTest); + }); +}); diff --git a/src/persistence/deserialization/primitive/primitive-deserializer.ts b/src/persistence/deserialization/primitive/primitive-deserializer.ts new file mode 100644 index 00000000..501d6aeb --- /dev/null +++ b/src/persistence/deserialization/primitive/primitive-deserializer.ts @@ -0,0 +1,24 @@ +import { includes } from 'lodash'; +import { JsonPrimitive } from '../../model-json'; +import { Deserializer } from '../deserializer'; + +/** + * Handles deserialization of basic primitives: string, number, boolean, undefined and null. + */ +export class PrimitiveDeserializer implements Deserializer { + private static readonly ALLOWED_PRIMITIVE_TYPES: string[] = ['string', 'number', 'boolean', 'undefined']; + + /** + * @inheritdoc + */ + public canDeserialize(json: JsonPrimitive): json is JsonPrimitive { + return json === null || includes(PrimitiveDeserializer.ALLOWED_PRIMITIVE_TYPES, typeof json); + } + + /** + * @inheritdoc + */ + public deserialize(json: JsonPrimitive): JsonPrimitive { + return json; + } +} diff --git a/src/persistence/deserialization/variable/variable-deserializer.test.ts b/src/persistence/deserialization/variable/variable-deserializer.test.ts new file mode 100644 index 00000000..296ea162 --- /dev/null +++ b/src/persistence/deserialization/variable/variable-deserializer.test.ts @@ -0,0 +1,51 @@ +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { VariableDeserializer } from './variable-deserializer'; + +describe('Variable deserializer', () => { + let deserializer: VariableDeserializer; + let mockVariableManager: Partial; + + const valuesToTest = { + string: 'string', + variable: 'VARIABLE', + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'VARIABLE' }, + array: ['VARIABLE'], + instance: new (class ExampleClass {})() + }; + beforeEach(() => { + mockVariableManager = { + isVariableExpression: jest.fn(value => value === 'VARIABLE'), + registerReference: jest.fn() + }; + deserializer = new VariableDeserializer(mockVariableManager as VariableManager); + }); + + test('should support only variable strings', () => { + expect(mapValues(valuesToTest, value => deserializer.canDeserialize(value))).toEqual({ + string: false, + variable: true, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: false, + array: false, + instance: false + }); + }); + + test('should register the value and location given', () => { + const fakeLocation = {}; + deserializer.deserialize('VARIABLE', fakeLocation as PropertyLocation); + expect(mockVariableManager.registerReference).toHaveBeenCalledWith(fakeLocation, 'VARIABLE'); + }); +}); diff --git a/src/persistence/deserialization/variable/variable-deserializer.ts b/src/persistence/deserialization/variable/variable-deserializer.ts new file mode 100644 index 00000000..7e8b4ebc --- /dev/null +++ b/src/persistence/deserialization/variable/variable-deserializer.ts @@ -0,0 +1,24 @@ +import { PropertyLocation } from '../../../model/property/property-location'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { JsonPrimitive } from '../../model-json'; +import { Deserializer } from '../deserializer'; + +/** + * Handles deserialization for variable strings representing any type of value + */ +export class VariableDeserializer implements Deserializer { + public constructor(private readonly variableManager: VariableManager) {} + /** + * @inheritdoc + */ + public canDeserialize(json: JsonPrimitive): json is string { + return typeof json === 'string' && this.variableManager.isVariableExpression(json); + } + + /** + * @inheritdoc + */ + public deserialize(json: string, location: PropertyLocation): unknown { + return this.variableManager.registerReference(location, json); + } +} diff --git a/src/persistence/model-json.ts b/src/persistence/model-json.ts new file mode 100644 index 00000000..b098af5e --- /dev/null +++ b/src/persistence/model-json.ts @@ -0,0 +1,17 @@ +/** + * JSON representing a model class + */ +export interface ModelJson { + [key: string]: JsonPrimitive; + /** + * Model type - the serialization key used to find the model class + */ + type: string; +} + +/** + * An array of `JsonPrimitive` + */ +export interface JsonPrimitiveArray extends Array {} + +export type JsonPrimitive = string | number | boolean | object | undefined | null | JsonPrimitiveArray | ModelJson; diff --git a/src/persistence/serialization/collection/array-serializer.test.ts b/src/persistence/serialization/collection/array-serializer.test.ts new file mode 100644 index 00000000..4ab3ca0d --- /dev/null +++ b/src/persistence/serialization/collection/array-serializer.test.ts @@ -0,0 +1,71 @@ +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { SerializationManager } from '../serialization-manager'; +import { ArraySerializer } from './array-serializer'; + +describe('Array serializer', () => { + let serializer: ArraySerializer; + let mockSerializationManager: PartialObjectMock; + const parent = new (class { + public array!: unknown[]; + })(); + let location: PropertyLocation; + + const valuesToTest = { + string: 'string', + symbol: Symbol('symbol'), + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + literalArray: ['test'], + emptyArray: [], + constructedArray: new Array(1), + instance: new (class ExampleClass {})() + }; + + beforeEach(() => { + mockSerializationManager = { + serialize: jest.fn() + }; + serializer = new ArraySerializer(mockSerializationManager as SerializationManager); + location = PropertyLocation.forModelProperty(parent, 'array'); + }); + + test('should support arrays only', () => { + expect(mapValues(valuesToTest, value => serializer.canSerialize(value))).toEqual({ + string: false, + symbol: false, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: false, + literalArray: true, + emptyArray: true, + constructedArray: true, + instance: false + }); + }); + + test('should pass array values to the serialization manager', () => { + serializer.serialize(['test', 5, []], location); + expect(mockSerializationManager.serialize).toHaveBeenCalledTimes(3); + expect(mockSerializationManager.serialize).nthCalledWith(1, 'test', expect.any(PropertyLocation)); + expect(mockSerializationManager.serialize).nthCalledWith(2, 5, expect.any(PropertyLocation)); + expect(mockSerializationManager.serialize).nthCalledWith(3, [], expect.any(PropertyLocation)); + }); + + test('should pass model context for child serialization', () => { + serializer.serialize(['test'], location); + expect(mockSerializationManager.serialize).toHaveBeenCalledWith( + 'test', + expect.objectContaining({ parentModel: parent, propertyKey: 'array:0' }) + ); + }); +}); diff --git a/src/persistence/serialization/collection/array-serializer.ts b/src/persistence/serialization/collection/array-serializer.ts new file mode 100644 index 00000000..19ebca33 --- /dev/null +++ b/src/persistence/serialization/collection/array-serializer.ts @@ -0,0 +1,32 @@ +import { PropertyLocation } from '../../../model/property/property-location'; +import { JsonPrimitiveArray } from '../../model-json'; +import { SerializationManager } from '../serialization-manager'; +import { Serializer } from '../serializer'; + +/** + * Handles serialization of an array type, recursing back to the manager for each value + */ +export class ArraySerializer implements Serializer { + public constructor(private readonly serializationManager: SerializationManager) {} + + /** + * @inheritdoc + */ + public canSerialize(value: unknown): value is unknown[] { + return Array.isArray(value); + } + + /** + * @inheritdoc + */ + public serialize(array: unknown[], location?: PropertyLocation): JsonPrimitiveArray { + const newArray: JsonPrimitiveArray = []; + + const serializeModelValue = (value: unknown, index: number) => + this.serializationManager.serialize(value, location!.buildChildFromObjectAndKey(array, index)); + + newArray.push(...array.map(serializeModelValue)); + + return newArray; + } +} diff --git a/src/persistence/serialization/collection/object-serializer.test.ts b/src/persistence/serialization/collection/object-serializer.test.ts new file mode 100644 index 00000000..d0a52e5c --- /dev/null +++ b/src/persistence/serialization/collection/object-serializer.test.ts @@ -0,0 +1,81 @@ +// tslint:disable:completed-docs +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { SerializationManager } from '../serialization-manager'; +import { ObjectSerializer } from './object-serializer'; + +describe('Object serializer', () => { + let serializer: ObjectSerializer; + let mockSerializationManager: PartialObjectMock; + const parent = new (class { + public obj!: object; + })(); + let location: PropertyLocation; + + const valuesToTest = { + string: 'string', + symbol: Symbol('symbol'), + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + array: [], + instance: new (class {})() + }; + beforeEach(() => { + mockSerializationManager = { + serialize: jest.fn() + }; + serializer = new ObjectSerializer(mockSerializationManager as SerializationManager); + location = PropertyLocation.forModelProperty(parent, 'obj'); + }); + + test('should support plain objects only', () => { + expect(mapValues(valuesToTest, value => serializer.canSerialize(value))).toEqual({ + string: false, + symbol: false, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: true, + array: false, + instance: false + }); + }); + + test('should pass object values to the serialization manager', () => { + serializer.serialize( + { + string: 'string', + undefined: undefined, + object: {}, + array: [] + }, + location + ); + expect(mockSerializationManager.serialize).toHaveBeenCalledTimes(4); + expect(mockSerializationManager.serialize).nthCalledWith(1, 'string', expect.any(PropertyLocation)); + expect(mockSerializationManager.serialize).nthCalledWith(2, undefined, expect.any(PropertyLocation)); + expect(mockSerializationManager.serialize).nthCalledWith(3, {}, expect.any(PropertyLocation)); + expect(mockSerializationManager.serialize).nthCalledWith(4, [], expect.any(PropertyLocation)); + }); + + test('should pass location context for child serialization', () => { + serializer.serialize( + { + stringKey: 'stringValue' + }, + location + ); + expect(mockSerializationManager.serialize).toHaveBeenCalledWith( + 'stringValue', + expect.objectContaining({ parentModel: parent, propertyKey: 'obj:stringKey' }) + ); + }); +}); diff --git a/src/persistence/serialization/collection/object-serializer.ts b/src/persistence/serialization/collection/object-serializer.ts new file mode 100644 index 00000000..33e96202 --- /dev/null +++ b/src/persistence/serialization/collection/object-serializer.ts @@ -0,0 +1,31 @@ +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { SerializationManager } from '../serialization-manager'; +import { Serializer } from '../serializer'; + +/** + * Handles serialization of a primitive JSON object, recursing back to the manager for each value + */ +export class ObjectSerializer implements Serializer { + public constructor(private readonly serializationManager: SerializationManager) {} + + /** + * @inheritdoc + */ + public canSerialize(value: unknown): value is object { + return typeof value === 'object' && value !== null && Object.getPrototypeOf(value) === Object.prototype; + } + + /** + * @inheritdoc + */ + public serialize(sourceObject: T, location?: PropertyLocation): object { + const newObject = {}; + + const serializeModelValue = (value: T[keyof T], key: keyof T) => + this.serializationManager.serialize(value, location!.buildChildFromObjectAndKey(sourceObject, key)); + + // tslint:disable-next-line:prefer-object-spread seems like a questionable rule, breaks behavior here + return Object.assign(newObject, mapValues(sourceObject, serializeModelValue)); + } +} diff --git a/src/persistence/serialization/model/model-serializer.test.ts b/src/persistence/serialization/model/model-serializer.test.ts new file mode 100644 index 00000000..99ecbced --- /dev/null +++ b/src/persistence/serialization/model/model-serializer.test.ts @@ -0,0 +1,194 @@ +import { mapValues } from 'lodash'; +import { DataSourceManager } from '../../../data/data-source/manager/data-source-manager'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { + ModelPropertyTypeInstance, + ModelPropertyTypeLibrary +} from '../../../model/property/model-property-type-library'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { ModelLibrary } from '../../../model/registration/model-registration'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { ThemeManager } from '../../../theming/theme-manager'; +import { SerializationManager } from '../serialization-manager'; +import { ModelSerializer } from './model-serializer'; + +describe('Model serializer', () => { + let serializer: ModelSerializer; + let mockModelManager: PartialObjectMock; + let mockModelLibrary: PartialObjectMock; + let mockSerializationManager: PartialObjectMock; + let mockDataSourceManager: PartialObjectMock; + let mockThemeManager: PartialObjectMock; + let mockModelPropertyTypeLibrary: PartialObjectMock; + + const modelClass = class { + public constructor(public runtimeProp?: string) {} + }; + + let modelObject: typeof modelClass.prototype; + + beforeEach(() => { + modelObject = new modelClass('deserialized'); + mockModelManager = { + isTrackedModel: jest.fn((model: object) => model === modelObject) + }; + mockModelLibrary = { + lookupModelMetadata: jest.fn().mockReturnValue({ type: 'model-type' }), + lookupModelProperties: jest.fn().mockReturnValue( + new Set([ + { + key: 'prop', + runtimeKey: 'runtimeProp', + type: { key: 'runtime-prop-type' } + } + ]) + ) + }; + mockSerializationManager = { + serialize: jest.fn().mockReturnValue('serialized') + }; + mockDataSourceManager = { + get: jest.fn() + }; + mockThemeManager = { + getThemeOverrideObjectProvidedByModel: jest.fn() + }; + mockModelPropertyTypeLibrary = { + getPropertySerializer: jest.fn() + }; + + serializer = new ModelSerializer( + mockModelManager as ModelManager, + mockModelLibrary as ModelLibrary, + mockSerializationManager as SerializationManager, + mockDataSourceManager as DataSourceManager, + mockThemeManager as ThemeManager, + mockModelPropertyTypeLibrary as ModelPropertyTypeLibrary + ); + }); + + test('should support model objects only', () => { + const valuesToTest = { + string: 'string', + symbol: Symbol('symbol'), + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + array: [], + nonModelClassInstance: new (class ExampleClass {})(), + unregisteredModelInstance: new modelClass(), + modelInstance: modelObject + }; + + expect(mapValues(valuesToTest, value => serializer.canSerialize(value))).toEqual({ + string: false, + symbol: false, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: false, + array: false, + nonModelClassInstance: false, + unregisteredModelInstance: false, + modelInstance: true + }); + }); + + test('should serialize model class', () => { + const result = serializer.serialize(modelObject); + + expect(result).toEqual({ + type: 'model-type', + prop: 'serialized' + }); + }); + + test('does not write undefined properties to JSON', () => { + mockSerializationManager.serialize = jest.fn(); + + expect(serializer.serialize(modelObject)).toEqual({ + type: 'model-type' + }); + }); + + test('serializes any attached data source', () => { + const dataObj = {}; + const mockDataLocation = {}; + + mockSerializationManager.serialize = jest.fn().mockImplementation((deserialized: unknown) => { + if (deserialized === dataObj) { + return { type: 'serialized-data' }; + } + + return 'serialized'; + }); + + mockDataSourceManager.get = jest.fn().mockReturnValueOnce(dataObj); + + mockDataSourceManager.getPropertyLocationForData = jest.fn().mockReturnValueOnce(mockDataLocation); + + expect(serializer.serialize(modelObject)).toEqual({ + type: 'model-type', + prop: 'serialized', + data: { + type: 'serialized-data' + } + }); + expect(mockSerializationManager.serialize).toHaveBeenCalledWith(dataObj, mockDataLocation); + }); + + test('serializes any associated theme', () => { + const themeObj = {}; + const mockThemeLocation = {}; + + mockSerializationManager.serialize = jest.fn().mockImplementation((deserialized: unknown) => { + if (deserialized === themeObj) { + return { type: 'serialized-theme' }; + } + + return 'serialized'; + }); + + mockThemeManager.getThemeOverrideObjectProvidedByModel = jest.fn().mockReturnValueOnce(themeObj); + + mockThemeManager.getPropertyLocationForTheme = jest.fn().mockReturnValueOnce(mockThemeLocation); + + expect(serializer.serialize(modelObject)).toEqual({ + type: 'model-type', + prop: 'serialized', + theme: { + type: 'serialized-theme' + } + }); + expect(mockSerializationManager.serialize).toHaveBeenCalledWith(themeObj, mockThemeLocation); + }); + + test('creates valid locations for serialized properties', () => { + spyOn(PropertyLocation, 'forModelProperty'); // Use spy for autoreset + serializer.serialize(modelObject); + + expect(PropertyLocation.forModelProperty).toHaveBeenCalledWith(modelObject, 'runtimeProp'); + }); + + test('delegates to custom model property serializers', () => { + const mockPropertySerializer = jest.fn(() => 'mock custom serialized property'); + mockModelPropertyTypeLibrary.getPropertySerializer = jest.fn((type: ModelPropertyTypeInstance) => + type.key === 'runtime-prop-type' ? mockPropertySerializer : undefined + ) as jest.Mock; + + expect(serializer.serialize(modelObject)).toEqual({ + type: 'model-type', + prop: 'mock custom serialized property' + }); + + expect(mockPropertySerializer).toHaveBeenCalledWith('deserialized', expect.any(PropertyLocation), { + key: 'runtime-prop-type' + }); + }); +}); diff --git a/src/persistence/serialization/model/model-serializer.ts b/src/persistence/serialization/model/model-serializer.ts new file mode 100644 index 00000000..6ef69946 --- /dev/null +++ b/src/persistence/serialization/model/model-serializer.ts @@ -0,0 +1,93 @@ +import { DataSource, ModelJsonWithData } from '../../../data/data-source/data-source'; +import { DataSourceManager } from '../../../data/data-source/manager/data-source-manager'; +import { ModelManager } from '../../../model/manager/model-manager'; +import { ModelPropertyTypeLibrary } from '../../../model/property/model-property-type-library'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { ModelLibrary, ModelPropertyMetadata } from '../../../model/registration/model-registration'; +import { ModelJsonWithTheme, Theme } from '../../../theming/theme'; +import { ThemeManager } from '../../../theming/theme-manager'; +import { ObjectConstructable } from '../../../util/constructable'; +import { ModelJson } from '../../model-json'; +import { SerializationManager } from '../serialization-manager'; +import { SerializationFunction, Serializer } from '../serializer'; + +/** + * Handles serializaation of a model object into a dehydrated + * JSON representation + */ +export class ModelSerializer implements Serializer { + public constructor( + private readonly modelManager: ModelManager, + private readonly modelLibrary: ModelLibrary, + private readonly serializationManager: SerializationManager, + private readonly dataSourceManager: DataSourceManager, + private readonly themeManager: ThemeManager, + private readonly modelPropertyTypeLibrary: ModelPropertyTypeLibrary + ) {} + + /** + * @inheritdoc + */ + public canSerialize(value: unknown): value is object { + return this.modelManager.isTrackedModel(value); + } + + /** + * @inheritdoc + */ + public serialize(modelObject: T): ModelJson { + const modelClass = modelObject.constructor as ObjectConstructable; + const modelJson: ModelJson = { + type: this.modelLibrary.lookupModelMetadata(modelClass)!.type + }; + + this.modelLibrary.lookupModelProperties(modelClass).forEach((propMetdata: ModelPropertyMetadata) => { + const serializedValue = this.getSerializationFunctionForProperty(propMetdata)( + modelObject[propMetdata.runtimeKey], + PropertyLocation.forModelProperty(modelObject, propMetdata.runtimeKey), + propMetdata.type + ); + if (serializedValue !== undefined) { + modelJson[propMetdata.key] = serializedValue; + } + }); + + this.serializeThemeIfExists(modelJson, modelObject); + this.serializeDataIfExists(modelJson, modelObject); + + return modelJson; + } + + private serializeThemeIfExists(modelJson: ModelJson, modelObject: object): void { + const theme = this.themeManager.getThemeOverrideObjectProvidedByModel(modelObject); + if (theme) { + const serializedTheme = this.serializationManager.serialize( + theme, + this.themeManager.getPropertyLocationForTheme(modelObject) + ); + + (modelJson as ModelJsonWithTheme).theme = serializedTheme; + } + } + + private serializeDataIfExists(modelJson: ModelJson, modelObject: object): void { + const dataSource = this.dataSourceManager.get(modelObject); + if (dataSource) { + const serializedDataSource = this.serializationManager.serialize, ModelJson>( + dataSource, + this.dataSourceManager.getPropertyLocationForData(modelObject) + ); + (modelJson as ModelJsonWithData).data = serializedDataSource; + } + } + + // tslint:disable-next-line: max-line-length + private getSerializationFunctionForProperty( + metadata: ModelPropertyMetadata + ): SerializationFunction { + const propertyTypeOverride = this.modelPropertyTypeLibrary.getPropertySerializer(metadata.type); + const defaultSerialization = this.serializationManager.serialize.bind(this.serializationManager); + + return propertyTypeOverride || defaultSerialization; + } +} diff --git a/src/persistence/serialization/primitive/primitive-serializer.test.ts b/src/persistence/serialization/primitive/primitive-serializer.test.ts new file mode 100644 index 00000000..84722b94 --- /dev/null +++ b/src/persistence/serialization/primitive/primitive-serializer.test.ts @@ -0,0 +1,40 @@ +import { mapValues } from 'lodash'; +import { PrimitiveSerializer } from './primitive-serializer'; + +describe('Primitive serializer', () => { + let serializer: PrimitiveSerializer; + + const valuesToTest = { + string: 'string', + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'test' }, + array: ['test'], + instance: new (class {})() + }; + beforeEach(() => { + serializer = new PrimitiveSerializer(); + }); + + test('should support string, number, boolean, undefined, null', () => { + expect(mapValues(valuesToTest, value => serializer.canSerialize(value))).toEqual({ + string: true, + number: true, + boolean: true, + date: false, + null: true, + undefined: true, + object: false, + array: false, + instance: false + }); + }); + + test('should return the value given', () => { + expect(mapValues(valuesToTest, value => serializer.serialize(value))).toEqual(valuesToTest); + }); +}); diff --git a/src/persistence/serialization/primitive/primitive-serializer.ts b/src/persistence/serialization/primitive/primitive-serializer.ts new file mode 100644 index 00000000..6bb8c9dc --- /dev/null +++ b/src/persistence/serialization/primitive/primitive-serializer.ts @@ -0,0 +1,24 @@ +import { includes } from 'lodash'; +import { JsonPrimitive } from '../../model-json'; +import { Serializer } from '../serializer'; + +/** + * Handles serialization of basic primitives: string, number, boolean, undefined and null. + */ +export class PrimitiveSerializer implements Serializer { + private static readonly ALLOWED_PRIMITIVE_TYPES: string[] = ['string', 'number', 'boolean', 'undefined']; + + /** + * @inheritdoc + */ + public canSerialize(value: unknown): value is JsonPrimitive { + return value === null || includes(PrimitiveSerializer.ALLOWED_PRIMITIVE_TYPES, typeof value); + } + + /** + * @inheritdoc + */ + public serialize(value: JsonPrimitive): JsonPrimitive { + return value; + } +} diff --git a/src/persistence/serialization/serialization-manager.test.ts b/src/persistence/serialization/serialization-manager.test.ts new file mode 100644 index 00000000..09ad44ba --- /dev/null +++ b/src/persistence/serialization/serialization-manager.test.ts @@ -0,0 +1,62 @@ +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { Logger } from '../../util/logging/logger'; +import { SerializationManager } from './serialization-manager'; +import { Serializer } from './serializer'; + +describe('Serialization manager', () => { + let manager: SerializationManager; + let mockLogger: PartialObjectMock; + + const createSerializerMatchingNumber = (valueToMatch: T): Serializer => { + const serializer: Serializer = { + canSerialize: (value): value is T => value === valueToMatch, + serialize: jest.fn((val: unknown) => val) as jest.Mock + }; + + return serializer; + }; + + beforeEach(() => { + mockLogger = {}; + manager = new SerializationManager(mockLogger as Logger); + }); + + test('allows registration of serializers', () => { + const serializer = createSerializerMatchingNumber(5); + manager.registerSerializer(serializer); + manager.serialize(5); + + expect(serializer.serialize).toHaveBeenCalledTimes(1); + }); + + test('uses first matching serializer', () => { + const firstSerializer = createSerializerMatchingNumber(5); + const secondSerializer = createSerializerMatchingNumber(6); + const thirdSerializer = createSerializerMatchingNumber(6); + + manager.registerSerializer(firstSerializer); + manager.registerSerializer(secondSerializer); + manager.registerSerializer(thirdSerializer); + manager.serialize(6); + + expect(firstSerializer.serialize).not.toHaveBeenCalled(); + + expect(secondSerializer.serialize).toHaveBeenCalledTimes(1); + + expect(thirdSerializer.serialize).not.toHaveBeenCalled(); + }); + + test('throws error if no matching serializer found', () => { + mockLogger.error = jest.fn((message: string) => ({ + throw: jest.fn(() => { + throw Error(message); + }) + })); + + const serializer = createSerializerMatchingNumber(5); + manager.registerSerializer(serializer); + + expect(() => manager.serialize(6)).toThrow('No serializer registered matching provided value'); + expect(mockLogger.error).toHaveBeenCalledWith('No serializer registered matching provided value'); + }); +}); diff --git a/src/persistence/serialization/serialization-manager.ts b/src/persistence/serialization/serialization-manager.ts new file mode 100644 index 00000000..0748bc63 --- /dev/null +++ b/src/persistence/serialization/serialization-manager.ts @@ -0,0 +1,47 @@ +import { PropertyLocation } from '../../model/property/property-location'; +import { Logger } from '../../util/logging/logger'; +import { JsonPrimitive } from '../model-json'; +import { Serializer } from './serializer'; + +/** + * Allows dynamic registration of serializers, delegating to the first matching + * serializer, by order of registration, for serialization + */ +export class SerializationManager { + private readonly serializers: Serializer[] = []; + + public constructor(private readonly logger: Logger) {} + + /** + * Adds a new serializer to the lookup path for serialization + */ + public registerSerializer( + serializer: Serializer + ): void { + this.serializers.push(serializer as Serializer); + } + + /** + * Searches for the first matching serializer and delegates to it + * + * Throws Error if no serializer can be determined or serialization fails + */ + public serialize(value: S, location?: PropertyLocation): T { + return this.getMatchingSerializer(value, location).serialize(value, location); + } + + private getMatchingSerializer( + value: S, + location?: PropertyLocation + ): Serializer { + const serializer = this.serializers.find(potentialSerializer => + potentialSerializer.canSerialize(value, location as PropertyLocation) + ); + + if (serializer) { + return serializer as Serializer; + } + + return this.logger.error('No serializer registered matching provided value').throw(); + } +} diff --git a/src/persistence/serialization/serializer.ts b/src/persistence/serialization/serializer.ts new file mode 100644 index 00000000..f7b53c52 --- /dev/null +++ b/src/persistence/serialization/serializer.ts @@ -0,0 +1,27 @@ +import { ModelPropertyTypeInstance } from '../../model/property/model-property-type-library'; +import { PropertyLocation } from '../../model/property/property-location'; +import { JsonPrimitive } from '../model-json'; + +/** + * A deserialzation class which accepts a deserialized value or object and converts it to JSON form + */ +export interface Serializer { + /** + * Returns true of the provided value can be serialized by this serializer + */ + canSerialize(value: unknown, location?: PropertyLocation): value is TDeserialized; + + /** + * Performs serialization, potentially recursively. Values provided to this method are assumed to be + * valid as determined by the canSerialize method. + * + * Throws Error if serialization fails for any reason + */ + serialize(value: TDeserialized, location?: PropertyLocation): TSerialized; +} + +export type SerializationFunction = ( + value: TDeserialized, + location: PropertyLocation, + propertyType: ModelPropertyTypeInstance +) => TSerialized; diff --git a/src/persistence/serialization/variable/variable-serializer.test.ts b/src/persistence/serialization/variable/variable-serializer.test.ts new file mode 100644 index 00000000..fb5bf627 --- /dev/null +++ b/src/persistence/serialization/variable/variable-serializer.test.ts @@ -0,0 +1,71 @@ +import { mapValues } from 'lodash'; +import { PropertyLocation } from '../../../model/property/property-location'; +import { PartialObjectMock } from '../../../test/partial-object-mock'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { VariableSerializer } from './variable-serializer'; + +describe('Variable serializer', () => { + let serializer: VariableSerializer; + let mockVariableManager: PartialObjectMock; + + const valuesToTest = { + string: 'string', + unrealizedVariable: 'VARIABLE', + symbol: Symbol('symbol'), + number: 15, + boolean: true, + date: new Date(), + // tslint:disable-next-line:no-null-keyword + null: null, + undefined: undefined, + object: { test: 'VARIABLE' }, + array: ['VARIABLE'], + instance: new (class {})(), + objectFromVariable: new (class {})(), + stringFromVariable: 'magic variable string', + numberFromVariable: 42 + }; + beforeEach(() => { + mockVariableManager = { + isVariableReference: jest.fn((location: PropertyLocation) => location.toString().endsWith('FromVariable')), + getVariableExpressionFromLocation: jest.fn() + }; + serializer = new VariableSerializer(mockVariableManager as VariableManager); + }); + + test('should support only variable references', () => { + const propLocationForKey = (key: string) => + PropertyLocation.forModelProperty<{ [key: string]: unknown }, string>(valuesToTest, key); + + const mappedValues = mapValues(valuesToTest, (value, key) => + serializer.canSerialize(value, propLocationForKey(key)) + ); + + expect(mappedValues).toEqual({ + string: false, + unrealizedVariable: false, + symbol: false, + number: false, + boolean: false, + date: false, + null: false, + undefined: false, + object: false, + array: false, + instance: false, + objectFromVariable: true, + stringFromVariable: true, + numberFromVariable: true + }); + }); + + test('should not accept serialization without a location', () => { + expect(serializer.canSerialize('value')).toBe(false); + }); + + test('should retrieve the variable expression from given location', () => { + const fakeLocation = {}; + serializer.serialize(undefined, fakeLocation as PropertyLocation); + expect(mockVariableManager.getVariableExpressionFromLocation).toHaveBeenCalledWith(fakeLocation); + }); +}); diff --git a/src/persistence/serialization/variable/variable-serializer.ts b/src/persistence/serialization/variable/variable-serializer.ts new file mode 100644 index 00000000..98be2b16 --- /dev/null +++ b/src/persistence/serialization/variable/variable-serializer.ts @@ -0,0 +1,23 @@ +import { PropertyLocation } from '../../../model/property/property-location'; +import { VariableManager } from '../../../variable/manager/variable-manager'; +import { Serializer } from '../serializer'; + +/** + * Handles deserialization for variable strings representing any type of value + */ +export class VariableSerializer implements Serializer { + public constructor(private readonly variableManager: VariableManager) {} + /** + * @inheritdoc + */ + public canSerialize(_value: unknown, location?: PropertyLocation): _value is unknown { + return location ? this.variableManager.isVariableReference(location) : false; + } + + /** + * @inheritdoc + */ + public serialize(_value: unknown, location: PropertyLocation): string { + return this.variableManager.getVariableExpressionFromLocation(location); + } +} diff --git a/src/renderer/registration/renderer-decorators.test.ts b/src/renderer/registration/renderer-decorators.test.ts new file mode 100644 index 00000000..43a560c0 --- /dev/null +++ b/src/renderer/registration/renderer-decorators.test.ts @@ -0,0 +1,25 @@ +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { deferredRendererDecoratorRegistrations, Renderer } from './renderer-decorators'; +import { RendererLibrary } from './renderer-registration'; + +describe('Renderer decorators', () => { + let mockRendererLibrary: PartialObjectMock; + const testModelClass = class {}; + + @Renderer({ modelClass: testModelClass }) + class RendererClass {} + + beforeEach(() => { + mockRendererLibrary = { + registerRendererClass: jest.fn() + }; + + deferredRendererDecoratorRegistrations.forEach(deferred => deferred(mockRendererLibrary as RendererLibrary)); + }); + + test('can be used to register decorator class', () => { + expect(mockRendererLibrary.registerRendererClass).toHaveBeenCalledWith(RendererClass, { + modelClass: testModelClass + }); + }); +}); diff --git a/src/renderer/registration/renderer-decorators.ts b/src/renderer/registration/renderer-decorators.ts new file mode 100644 index 00000000..a0381abb --- /dev/null +++ b/src/renderer/registration/renderer-decorators.ts @@ -0,0 +1,17 @@ +import { UnknownConstructable } from '../../util/constructable'; +import { RendererLibrary, RendererRegistrationInformation } from './renderer-registration'; + +export type DeferredRendererDecoratorRegistration = (rendererLibrary: RendererLibrary) => void; +export const deferredRendererDecoratorRegistrations: DeferredRendererDecoratorRegistration[] = []; + +/** + * Registers the decorated renderer with the provided information + */ +// tslint:disable-next-line:only-arrow-functions +export function Renderer(registrationInfo: RendererRegistrationInformation): (target: UnknownConstructable) => void { + return (rendererClass: UnknownConstructable): void => { + deferredRendererDecoratorRegistrations.push(rendererLibrary => + rendererLibrary.registerRendererClass(rendererClass, registrationInfo) + ); + }; +} diff --git a/src/renderer/registration/renderer-registration.test.ts b/src/renderer/registration/renderer-registration.test.ts new file mode 100644 index 00000000..24a25fd5 --- /dev/null +++ b/src/renderer/registration/renderer-registration.test.ts @@ -0,0 +1,66 @@ +import { Logger } from '../../util/logging/logger'; +import { deferredRendererDecoratorRegistrations } from './renderer-decorators'; +import { RendererLibrary } from './renderer-registration'; + +describe('Renderer', () => { + const testModelClass = class TestModelClass {}; + const testRendererClass = class TestRendererClass {}; + let library: RendererLibrary; + let mockLogger: Partial; + + beforeEach(() => { + mockLogger = {}; + library = new RendererLibrary(mockLogger as Logger); + }); + + test('can be registered to a model', () => { + library.registerRendererClass(testRendererClass, { + modelClass: testModelClass + }); + + expect(library.lookupRenderer(testModelClass)).toBe(testRendererClass); + }); + + test('can not register more than one renderer to a model', () => { + mockLogger.error = jest.fn(); + + library.registerRendererClass(testRendererClass, { + modelClass: testModelClass + }); + + library.registerRendererClass(class SecondRendererClass {}, { + modelClass: testModelClass + }); + + expect(mockLogger.error).toHaveBeenCalledWith( + 'Model classes may only have one renderer. Attempted to register [SecondRendererClass] ' + + 'to [TestModelClass], but model already registered with [TestRendererClass]' + ); + }); + + test('warn and return undefined trying to lookup model without a renderer', () => { + mockLogger.warn = jest.fn(); + + expect(library.lookupRenderer(testModelClass)).toBeUndefined(); + expect(mockLogger.warn).toHaveBeenCalledWith('No renderer registered for model: [TestModelClass]'); + }); + + test('supports registrations before renderer library construction', () => { + deferredRendererDecoratorRegistrations.length = 0; + deferredRendererDecoratorRegistrations.push(providedLib => + providedLib.registerRendererClass(testRendererClass, { modelClass: testModelClass }) + ); + library = new RendererLibrary(mockLogger as Logger); + + expect(library.lookupRenderer(testModelClass)).toBe(testRendererClass); + }); + + test('supports registrations after renderer library construction', () => { + deferredRendererDecoratorRegistrations.length = 0; + deferredRendererDecoratorRegistrations.push(providedLib => + providedLib.registerRendererClass(testRendererClass, { modelClass: testModelClass }) + ); + + expect(library.lookupRenderer(testModelClass)).toBe(testRendererClass); + }); +}); diff --git a/src/renderer/registration/renderer-registration.ts b/src/renderer/registration/renderer-registration.ts new file mode 100644 index 00000000..ae00377f --- /dev/null +++ b/src/renderer/registration/renderer-registration.ts @@ -0,0 +1,84 @@ +import { UnknownConstructable } from '../../util/constructable'; +import { Logger } from '../../util/logging/logger'; +import { deferredRendererDecoratorRegistrations } from './renderer-decorators'; + +/** + * Renderer Library allows rendererer classes to be associated with model classes + */ +export class RendererLibrary { + private readonly rendererMetadata: Map = new Map(); + private lastDeferredIndexRead: number = 0; + private currentlyProcessingDeferred: boolean = false; + + public constructor(private readonly logger: Logger) {} + + /** + * Registers the provided render class to a given model class. No action is taken if that + * model already has a renderer. + */ + public registerRendererClass( + rendererClass: UnknownConstructable, + registrationInformation: RendererRegistrationInformation + ): void { + if (this.hasRenderer(registrationInformation.modelClass)) { + this.logger.error( + `Model classes may only have one renderer. Attempted to register [${rendererClass.name}] ` + + `to [${registrationInformation.modelClass.name}], but model already registered with ` + + `[${this.rendererMetadata.get(registrationInformation.modelClass)!.name}]` + ); + + return; + } + + this.rendererMetadata.set(registrationInformation.modelClass, rendererClass); + } + + /** + * Retrieves the renderer class associated with the provided model class. Returns + * undefined if the model class has not been registered to a renderer. + */ + public lookupRenderer(modelClass: UnknownConstructable): UnknownConstructable | undefined { + if (this.hasRenderer(modelClass)) { + return this.rendererMetadata.get(modelClass)!; + } + this.logger.warn(`No renderer registered for model: [${modelClass.name}]`); + + return undefined; + } + + /** + * Returns true if `modelClass` has a renderer, false otherwise. + */ + public hasRenderer(modelClass: UnknownConstructable): boolean { + this.processRegistrationQueue(); + + return this.rendererMetadata.has(modelClass); + } + + private processRegistrationQueue(): void { + if (this.currentlyProcessingDeferred) { + return; // Lazy shortcut lock to prevent infinitely looping on this + } + this.currentlyProcessingDeferred = true; + // tslint:disable-next-line:max-line-length + for ( + this.lastDeferredIndexRead; + this.lastDeferredIndexRead < deferredRendererDecoratorRegistrations.length; + this.lastDeferredIndexRead++ + ) { + const deferredRegistration = deferredRendererDecoratorRegistrations[this.lastDeferredIndexRead]; + deferredRegistration(this); + } + this.currentlyProcessingDeferred = false; + } +} + +/** + * Describes a model renderer + */ +export interface RendererRegistrationInformation { + /** + * The model class this renderer is associated with + */ + modelClass: UnknownConstructable; +} diff --git a/src/test/partial-object-mock.ts b/src/test/partial-object-mock.ts new file mode 100644 index 00000000..54876df6 --- /dev/null +++ b/src/test/partial-object-mock.ts @@ -0,0 +1 @@ +export type PartialObjectMock = { [P in keyof T]?: jest.Mock> | T[P] }; diff --git a/src/test/rxjs-jest-test-scheduler.ts b/src/test/rxjs-jest-test-scheduler.ts new file mode 100644 index 00000000..a2d62e1e --- /dev/null +++ b/src/test/rxjs-jest-test-scheduler.ts @@ -0,0 +1,6 @@ +import { TestScheduler } from 'rxjs/testing'; + +export const getTestScheduler = () => + new TestScheduler((actual: unknown, expected: unknown) => { + expect(actual).toEqual(expected); + }); diff --git a/src/theming/theme-manager.test.ts b/src/theming/theme-manager.test.ts new file mode 100644 index 00000000..5ccafece --- /dev/null +++ b/src/theming/theme-manager.test.ts @@ -0,0 +1,225 @@ +import { ModelManager } from '../model/manager/model-manager'; +import { ModelLibrary } from '../model/registration/model-registration'; +import { PartialObjectMock } from '../test/partial-object-mock'; +import { MergedTheme, Theme } from './theme'; +import { ThemeManager } from './theme-manager'; + +describe('Theme manager', () => { + let themeManager: ThemeManager; + let mockModelManager: PartialObjectMock; + let mockModelLibrary: PartialObjectMock; + + const modelClass = class {}; + let mockModel: object; + const modelType = 'test-model'; + const parentModelClass = class {}; + let mockParentModel: object; + const parentModelType = 'test-parent-model'; + + class TestTheme extends Theme { + public constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } + } + + // tslint:disable-next-line:max-classes-per-file + class TestGlobalTheme extends TestTheme implements Required { + public backgroundColor!: string; + public textColor!: string; + public constructor(fullTheme: MergedTheme) { + super(fullTheme); + } + } + + const createThemeManager = () => { + themeManager = new ThemeManager( + mockModelManager as ModelManager, + mockModelLibrary as ModelLibrary, + new TestGlobalTheme({ + backgroundColor: 'white', + textColor: 'black' + }) + ); + }; + + beforeEach(() => { + mockModel = new modelClass(); + mockParentModel = new parentModelClass(); + mockModelLibrary = { + lookupModelMetadata: jest.fn(lookupClass => { + if (lookupClass === modelClass) { + return { type: modelType }; + } + if (lookupClass === parentModelClass) { + return { type: parentModelType }; + } + + return undefined; + }) as jest.Mock + }; + mockModelManager = { + getParent: jest.fn(model => (model === mockModel ? mockParentModel : undefined)) + }; + + createThemeManager(); + }); + + test('allows basic theme setting and retrieval, defaulting to global theme', () => { + const themeToSet = new TestTheme({ textColor: 'orange' }); + themeManager.setThemeForModel(themeToSet, mockModel); + + expect(themeManager.getThemeForModel(mockModel)).toEqual({ + backgroundColor: 'white', + textColor: 'orange' + }); + + expect(themeManager.getThemeForModel(mockParentModel)).toEqual({ + backgroundColor: 'white', + textColor: 'black' + }); + }); + + test('allows removing themes', () => { + const themeToSet = new TestTheme({ textColor: 'orange' }); + themeManager.setThemeForModel(themeToSet, mockModel); + + expect(themeManager.getThemeForModel(mockModel)).toEqual({ + backgroundColor: 'white', + textColor: 'orange' + }); + + themeManager.removeThemeForModel(mockModel); + + expect(themeManager.getThemeForModel(mockModel)).toEqual({ + backgroundColor: 'white', + textColor: 'black' + }); + }); + + test('returns new object every time', () => { + const themeToSet = new TestTheme({ + backgroundColor: 'orange', + textColor: 'red' + }); + themeManager.setThemeForModel(themeToSet, mockModel); + const firstGetResult = themeManager.getThemeForModel(mockModel); + expect(firstGetResult).not.toBe(themeToSet); + + expect(themeManager.getThemeForModel(mockModel)).not.toBe(firstGetResult); + }); + + test('supports changing global theme', () => { + const themeToSet = new TestTheme({ textColor: 'orange' }); + themeManager.setThemeForModel(themeToSet, mockModel); + + themeManager.setGlobalTheme( + new TestGlobalTheme({ + backgroundColor: 'lightgray', + textColor: 'green' + }) + ); + + expect(themeManager.getThemeForModel(mockModel)).toEqual({ + backgroundColor: 'lightgray', + textColor: 'orange' + }); + }); + + test('merges themes based on model hierarchy', () => { + themeManager.setThemeForModel( + new TestTheme({ + backgroundColor: 'crimson', + textColor: 'orange' + }), + mockParentModel + ); + + themeManager.setThemeForModel( + new TestTheme({ + textColor: 'cornflowerblue' + }), + mockModel + ); + + expect(themeManager.getThemeForModel(mockParentModel)).toEqual({ + backgroundColor: 'crimson', + textColor: 'orange' + }); + + expect(themeManager.getThemeForModel(mockModel)).toEqual({ + backgroundColor: 'crimson', + textColor: 'cornflowerblue' + }); + }); + + test('modelJsonHasTheme can correctly identify json containing theme', () => { + expect(themeManager.modelJsonHasTheme({ type: 'any' })).toBe(false); + expect(themeManager.modelJsonHasTheme({ type: 'any', theme: 'any' })).toBe(false); + // tslint:disable-next-line:no-null-keyword + expect(themeManager.modelJsonHasTheme({ type: 'any', theme: null })).toBe(false); + expect(themeManager.modelJsonHasTheme({ type: 'any', theme: {} })).toBe(true); + }); + + test('creates property location including a setter that correctly gets or sets themes', () => { + spyOn(themeManager, 'setThemeForModel').and.callThrough(); + + spyOn(themeManager, 'removeThemeForModel').and.callThrough(); + + const location = themeManager.getPropertyLocationForTheme(mockModel); + const theme = new TestTheme({}); + + expect(location.toString()).toBe('theme'); + + location.setProperty(theme); + expect(themeManager.setThemeForModel).toHaveBeenCalledWith(theme, mockModel); + expect(location.getProperty()).toBe(theme); + + location.setProperty(undefined); + expect(themeManager.removeThemeForModel).toHaveBeenCalledWith(mockModel); + expect(location.getProperty()).toBeUndefined(); + }); + + test('allows retrieval of a single theme property based on property key', () => { + const themeToSet = new TestTheme({ textColor: 'orange' }); + themeManager.setThemeForModel(themeToSet, mockModel); + + mockModelLibrary.lookupModelProperties = jest.fn().mockReturnValue( + new Set([ + { + key: 'text-color', + runtimeKey: 'textColor' + }, + { + key: 'background-color', + runtimeKey: 'backgroundColor' + } + ]) + ); + + expect(themeManager.getThemePropertyForModel(mockModel, 'text-color')).toBe('orange'); + expect(themeManager.getThemePropertyForModel(mockModel, 'background-color')).toBe('white'); + expect(themeManager.getThemePropertyForModel(mockModel, "doesn't exist")).toBeUndefined(); + + // Should go to global for unregistered model + expect(themeManager.getThemePropertyForModel(mockParentModel, 'text-color')).toBe('black'); + }); + + test('returns undefined for theme properties if themes have not been registered', () => { + const themeToSet = new TestTheme({ textColor: 'orange' }); + themeManager.setThemeForModel(themeToSet, mockModel); + + mockModelLibrary.lookupModelProperties = jest.fn(() => []); + expect(themeManager.getThemePropertyForModel(mockModel, 'text-color')).toBeUndefined(); + }); + + test('returns original theme object for serialization', () => { + const themeToSet = new TestTheme({ textColor: 'orange' }); + themeManager.setThemeForModel(themeToSet, mockModel); + + expect(themeManager.getThemeOverrideObjectProvidedByModel(mockModel)).toBe(themeToSet); + + // Should be merged, not the original + expect(themeManager.getThemeForModel(mockModel)).not.toBe(themeToSet); + }); +}); diff --git a/src/theming/theme-manager.ts b/src/theming/theme-manager.ts new file mode 100644 index 00000000..80dd5375 --- /dev/null +++ b/src/theming/theme-manager.ts @@ -0,0 +1,127 @@ +import { ModelManager } from '../model/manager/model-manager'; +import { PropertyLocation } from '../model/property/property-location'; +import { ModelLibrary } from '../model/registration/model-registration'; +import { ModelJson } from '../persistence/model-json'; +import { Constructable } from '../util/constructable'; +import { MergedTheme, ModelJsonWithTheme, Theme } from './theme'; + +/** + * Manages themes for dashboards, allowing assigning retrieving hierarchical themes + * tied to specific model instances. + */ +export class ThemeManager { + private readonly themeByModel: WeakMap = new WeakMap(); + private globalTheme!: Required; + public constructor( + private readonly modelManager: ModelManager, + private readonly modelLibrary: ModelLibrary, + globalTheme: Required + ) { + this.setGlobalTheme(globalTheme); + } + + /** + * Sets the global theme + */ + public setGlobalTheme(theme: Required): void { + this.globalTheme = theme; + } + + /** + * Sets specific overrides for the given model instance + */ + public setThemeForModel(theme: Theme, model: object): void { + this.themeByModel.set(model, theme); + } + + /** + * Removes theme overrides for provided model + */ + public removeThemeForModel(model: object): void { + this.themeByModel.delete(model); + } + + /** + * Retrieves a merged theme for the provided model applying + * overrides in order of specificity. + */ + public getThemeForModel(model: object): MergedTheme { + return Object.assign({}, ...this.getThemeHierarchy(model)) as Required; + } + + /** + * Returns true if the model JSON provided contains a theme property + */ + public modelJsonHasTheme(modelJson: ModelJson): modelJson is ModelJsonWithTheme { + return 'theme' in modelJson && typeof modelJson.theme === 'object' && modelJson.theme !== null; + } + + /** + * Returns a property location corresponding to the theme attached to the provided model + */ + public getPropertyLocationForTheme(instance: object): PropertyLocation { + return new PropertyLocation( + instance, + 'theme', + (value: Theme | undefined) => { + if (value === undefined) { + this.removeThemeForModel(instance); + } else { + this.setThemeForModel(value, instance); + } + }, + () => this.getThemeOverrideObjectProvidedByModel(instance) + ); + } + + /** + * Retrieves the value of the theme property associated with the provided key for this model. + * Note: the propertyKey is not necessarily the same as the runtime key. It is the serialization key. + */ + public getThemePropertyForModel(model: object, propertyKey: string): T | undefined { + const matchedTheme = this.getThemeHierarchy(model) + .reverse() + .find(theme => { + if (!theme) { + return false; + } + const runtimeKeyForTheme = this.getThemeRuntimeKey(theme, propertyKey); + if (!runtimeKeyForTheme) { + return false; + } + + return runtimeKeyForTheme in theme; + }); + + return matchedTheme && ((matchedTheme[this.getThemeRuntimeKey(matchedTheme, propertyKey)!] as unknown) as T); + } + /** + * Returns the original Theme object provided for this model, if any. This does not include + * any resolved theme properties from parents or globals. + */ + public getThemeOverrideObjectProvidedByModel(model: object): Theme | undefined { + return this.themeByModel.get(model); + } + + private getThemeRuntimeKey(theme: T, propertyKey: string): keyof T | undefined { + const properties = this.modelLibrary.lookupModelProperties(theme.constructor as Constructable); + + const matchingMetadata = Array.from(properties).find(themePropMetadata => themePropMetadata.key === propertyKey); + + return matchingMetadata && matchingMetadata.runtimeKey; + } + + private getThemeHierarchy(model: object): (Theme | undefined)[] { + const themeHierarchy = []; + let currModel: object | undefined = model; + while (currModel) { + themeHierarchy.unshift(this.themeByModel.get(currModel)); + currModel = this.modelManager.getParent(currModel); + } + + // Always add global theme as first in hierarchy + themeHierarchy.unshift(this.globalTheme); + + return themeHierarchy; + } +} diff --git a/src/theming/theme.ts b/src/theming/theme.ts new file mode 100644 index 00000000..0278f56e --- /dev/null +++ b/src/theming/theme.ts @@ -0,0 +1,44 @@ +import { STRING_PROPERTY } from '../model/property/predefined/primitive-model-property-types'; +import { Model, ModelProperty } from '../model/registration/model-decorators'; +import { ModelJson } from '../persistence/model-json'; + +/** + * A theme describing how to style a specific model. + */ +@Model({ + type: 'theme', + displayName: 'Theme' +}) +export class Theme { + /** + * Background color. Accessible via key `background-color`. + */ + @ModelProperty({ + key: 'background-color', + type: STRING_PROPERTY.type, + required: false + }) + public backgroundColor?: string; + + /** + * Text color. Accessible via key `text-color`. + */ + @ModelProperty({ + key: 'text-color', + type: STRING_PROPERTY.type, + required: false + }) + public textColor?: string; +} + +/** + * A JSON object representing a model with associated theme JSON + */ +export interface ModelJsonWithTheme extends ModelJson { + /** + * Theme JSON + */ + theme: ModelJson; +} + +export type MergedTheme = Required; diff --git a/src/util/constructable.ts b/src/util/constructable.ts new file mode 100644 index 00000000..a1081822 --- /dev/null +++ b/src/util/constructable.ts @@ -0,0 +1,15 @@ +/** + * A function representing a constructor for some class + */ +// tslint:disable-next-line:no-any +export type Constructable = new (...args: any[]) => T; + +/** + * A function representing a constructor for an unknown class + */ +export interface UnknownConstructable extends Constructable {} + +/** + * A function representing a constructor for any object + */ +export interface ObjectConstructable extends Constructable {} diff --git a/src/util/logging/log-level.ts b/src/util/logging/log-level.ts new file mode 100644 index 00000000..8a8e705b --- /dev/null +++ b/src/util/logging/log-level.ts @@ -0,0 +1,21 @@ +/** + * Indicates the level a log message should be emitted at. + */ +export const enum LogLevel { + /** + * Info level + */ + Info = 'INFO', + /** + * Debug level + */ + Debug = 'DEBUG', + /** + * Warn level + */ + Warn = 'WARN', + /** + * Error level + */ + Error = 'ERROR' +} diff --git a/src/util/logging/log-message.test.ts b/src/util/logging/log-message.test.ts new file mode 100644 index 00000000..8dc1fd95 --- /dev/null +++ b/src/util/logging/log-message.test.ts @@ -0,0 +1,72 @@ +import { LogLevel } from './log-level'; +import { DefaultLogMessage } from './log-message'; + +describe('Default Log message', () => { + const messageText = 'test message'; + test('info level', () => { + const message = new DefaultLogMessage(LogLevel.Info, messageText); + + expect(message.toString()).toEqual('INFO: test message'); + + const infoSpy = spyOn(console, 'info'); + + message.log(); + expect(infoSpy).toHaveBeenCalledWith('INFO: test message'); + }); + + test('info level', () => { + const message = new DefaultLogMessage(LogLevel.Info, messageText); + + expect(message.toString()).toEqual('INFO: test message'); + + const infoSpy = spyOn(console, 'info'); + + message.log(); + expect(infoSpy).toHaveBeenCalledWith('INFO: test message'); + }); + + test('warn level', () => { + const message = new DefaultLogMessage(LogLevel.Warn, messageText); + + expect(message.toString()).toEqual('WARN: test message'); + + const warnSpy = spyOn(console, 'warn'); + + message.log(); + expect(warnSpy).toHaveBeenCalledWith('WARN: test message'); + }); + + test('debug level', () => { + const message = new DefaultLogMessage(LogLevel.Debug, messageText); + + expect(message.toString()).toEqual('DEBUG: test message'); + + const debugSpy = spyOn(console, 'debug'); + + message.log(); + expect(debugSpy).toHaveBeenCalledWith('DEBUG: test message'); + }); + + test('error level', () => { + const message = new DefaultLogMessage(LogLevel.Error, messageText); + + expect(message.toString()).toEqual('ERROR: test message'); + + const errorSpy = spyOn(console, 'error'); + + message.log(); + expect(errorSpy).toHaveBeenCalledWith('ERROR: test message'); + }); + + test('source messages are printed out', () => { + const rootSource = new DefaultLogMessage(LogLevel.Info, 'root'); + const intermediateSource = new DefaultLogMessage(LogLevel.Info, 'intermediate', rootSource); + const message = new DefaultLogMessage(LogLevel.Warn, messageText, intermediateSource); + + expect(message.toString()).toBe('WARN: test message\n ↳ intermediate\n ↳ root'); + }); + + test('throws errors if requested', () => { + expect(() => new DefaultLogMessage(LogLevel.Warn, messageText).throw()).toThrow('test message'); + }); +}); diff --git a/src/util/logging/log-message.ts b/src/util/logging/log-message.ts new file mode 100644 index 00000000..f4d99473 --- /dev/null +++ b/src/util/logging/log-message.ts @@ -0,0 +1,111 @@ +import { LogLevel } from './log-level'; + +/** + * Represents a log message emitted at a specific level. + * Potentially has a source of other log messages. + */ +export interface LogMessage { + /** + * Log level for this message + */ + level: LogLevel; + /** + * Log message + */ + message: string; + /** + * Any potential source message for this message + */ + source?: LogMessage; + /** + * Perform the actual logging + */ + log(): void; + /** + * Throws the log message as an error + */ + throw(): never; +} + +/** + * Default log message outputting to console + */ +export class DefaultLogMessage implements LogMessage { + private static readonly DEFAULT_INDENT: string = ' '; + + public constructor( + public readonly level: LogLevel, + public readonly message: string, + public readonly source?: Readonly + ) {} + + /** + * Convert the message, and any sources to a stack string + */ + public toString(): string { + return `${this.level}: ${this.getMessageWithStack()}`; + } + + /** + * Perform the actual logging, sending the result of toString to console. + */ + public log(): void { + this.getLogMethod()(this.toString()); + } + + /** + * Throws the log message as an error + */ + public throw(): never { + throw Error(this.getMessageWithStack()); + } + + private getMessageWithStack(): string { + const stack = this.getFormattedSourceStack(); + const lineSeparatorIfStack = stack.length === 0 ? '' : '\n'; + + return `${this.message}${lineSeparatorIfStack}${stack}`; + } + + private getFormattedSourceStack(): string { + if (!this.source) { + return ''; + } + + return this.getSourceMessages() + .map((message: string, index: number) => this.getIndent(index + 1) + message) + .join('\n'); + } + + private getSourceMessages(): string[] { + const sourceMessages = []; + let currentSourceObject: Readonly | undefined = this.source; + + while (currentSourceObject) { + sourceMessages.push(currentSourceObject.message); + currentSourceObject = currentSourceObject.source; + } + + return sourceMessages; + } + + private getIndent(count: number): string { + return DefaultLogMessage.DEFAULT_INDENT.repeat(count).replace(/ $/, '↳ '); + } + + private getLogMethod(): (message: string) => void { + /* tslint:disable:no-console */ + switch (this.level) { + case LogLevel.Warn: + return console.warn; + case LogLevel.Error: + return console.error; + case LogLevel.Debug: + return console.debug; + case LogLevel.Info: + default: + return console.info; + } + /* tslint:enable:no-console */ + } +} diff --git a/src/util/logging/logger.test.ts b/src/util/logging/logger.test.ts new file mode 100644 index 00000000..aac650b5 --- /dev/null +++ b/src/util/logging/logger.test.ts @@ -0,0 +1,95 @@ +import { LogLevel } from './log-level'; +import { DefaultLogMessage, LogMessage } from './log-message'; +import { Logger } from './logger'; + +jest.mock('./log-message'); + +describe('Logger', () => { + let logger: Logger; + let messageBuilder: jest.Mock; + let mockMessage: Partial; + const mockedLogMessageConstructor = (DefaultLogMessage as unknown) as jest.Mock; + + beforeEach(() => { + logger = new Logger(); + mockedLogMessageConstructor.mockClear(); + messageBuilder = jest.fn((logLevel: LogLevel) => { + mockMessage = { + level: logLevel, + log: jest.fn() + }; + + return mockMessage as LogMessage; + }); + logger.setLogMessageBuilder(messageBuilder); + }); + + test('log methods map to correct level', () => { + logger.log = jest.fn(); + + logger.info('test info'); + expect(logger.log).toHaveBeenLastCalledWith(LogLevel.Info, 'test info', undefined); + + logger.warn('test warn'); + expect(logger.log).toHaveBeenLastCalledWith(LogLevel.Warn, 'test warn', undefined); + + logger.error('test error'); + expect(logger.log).toHaveBeenLastCalledWith(LogLevel.Error, 'test error', undefined); + + logger.debug('test debug'); + expect(logger.log).toHaveBeenLastCalledWith(LogLevel.Debug, 'test debug', undefined); + }); + + test('log message includes logger name if defined', () => { + logger.debug('my message'); + + expect(messageBuilder).toHaveBeenCalledWith(LogLevel.Debug, 'my message', undefined); + + messageBuilder.mockClear(); + const namedLogger = new Logger('my name'); + namedLogger.setLogMessageBuilder(messageBuilder); + + namedLogger.debug('my message'); + + expect(messageBuilder).toHaveBeenCalledWith(LogLevel.Debug, '[my name] my message', undefined); + }); + + test('respects log level', () => { + logger.setLogLevel(LogLevel.Warn); + logger.info('my message'); + expect(mockMessage.log).not.toHaveBeenCalled(); + + logger.warn('my warn message'); + + expect(mockMessage.log).toHaveBeenCalled(); + }); + + test('passes source log message', () => { + const sourceLogMessage = {}; + + logger.debug('root message', sourceLogMessage as LogMessage); + + expect(messageBuilder).toHaveBeenCalledWith(LogLevel.Debug, 'root message', sourceLogMessage); + }); + + test('uses DefaultLogMessage by default', () => { + logger = new Logger(); + + logger.debug('my message'); + expect(DefaultLogMessage).toHaveBeenCalledWith(LogLevel.Debug, 'my message', undefined); + }); + + test('treats error source as a nested log message', () => { + logger = new Logger(); + logger.debug('my message', Error('source error')); + + expect(mockedLogMessageConstructor).toHaveBeenCalledTimes(2); + expect(mockedLogMessageConstructor).toHaveBeenNthCalledWith(1, LogLevel.Error, 'source error', undefined); + expect(mockedLogMessageConstructor).toHaveBeenNthCalledWith( + 2, + LogLevel.Debug, + 'my message', + mockedLogMessageConstructor.mock.instances[0] + ); + }); +}); diff --git a/src/util/logging/logger.ts b/src/util/logging/logger.ts new file mode 100644 index 00000000..67abdd93 --- /dev/null +++ b/src/util/logging/logger.ts @@ -0,0 +1,98 @@ +import { LogLevel } from './log-level'; +import { DefaultLogMessage, LogMessage } from './log-message'; + +/** + * Logging utility + */ +export class Logger { + private static readonly LOG_PRIORITY: ReadonlyArray = [ + LogLevel.Debug, + LogLevel.Info, + LogLevel.Warn, + LogLevel.Error + ]; + + private static readonly DEFAULT_LOG_MESSAGE_BUILDER: LogMessageBuilder = ( + level: LogLevel, + message: string, + source?: LogMessage + ) => new DefaultLogMessage(level, message, source); + + private logLevel: LogLevel = LogLevel.Info; + private logMessageBuilder: LogMessageBuilder = Logger.DEFAULT_LOG_MESSAGE_BUILDER; + + public constructor(private readonly loggerName: string = '') {} + + /** + * Log provided message at info level + */ + public info(message: string, source?: LogMessage | Error): LogMessage { + return this.log(LogLevel.Info, message, source); + } + + /** + * Log provided message at debug level + */ + public debug(message: string, source?: LogMessage | Error): LogMessage { + return this.log(LogLevel.Debug, message, source); + } + + /** + * Log provided message at error level + */ + public error(message: string, source?: LogMessage | Error): LogMessage { + return this.log(LogLevel.Error, message, source); + } + /** + * Log provided message at warn level + */ + public warn(message: string, source?: LogMessage | Error): LogMessage { + return this.log(LogLevel.Warn, message, source); + } + + /** + * Log provided message at requested level + */ + public log(logLevel: LogLevel, message: string, source?: LogMessage | Error): LogMessage { + const loggerNamePrefix = this.loggerName.length > 0 ? `[${this.loggerName}] ` : ''; + + const logMessage = this.logMessageBuilder( + logLevel, + `${loggerNamePrefix}${message}`, + this.convertSourceToLogMessageOrUndefined(source) + ); + if (this.shouldLogMessage(this.logLevel, logMessage)) { + logMessage.log(); + } + + return logMessage; + } + + /** + * Set the minimum log level. Any log statements that are at a more verbose level are not logged.. + */ + public setLogLevel(level: LogLevel): void { + this.logLevel = level; + } + + /** + * Provides a log message builder to implement custom logging behavior + */ + public setLogMessageBuilder(builder: LogMessageBuilder): void { + this.logMessageBuilder = builder; + } + + private shouldLogMessage(minimumLevel: LogLevel, logMessage: LogMessage): boolean { + return Logger.LOG_PRIORITY.indexOf(logMessage.level) >= Logger.LOG_PRIORITY.indexOf(minimumLevel); + } + + private convertSourceToLogMessageOrUndefined(source?: LogMessage | Error): LogMessage | undefined { + if (source && source instanceof Error) { + return this.logMessageBuilder(LogLevel.Error, source.message); + } + + return source; + } +} + +export type LogMessageBuilder = (level: LogLevel, message: string, source?: LogMessage) => LogMessage; diff --git a/src/util/omit.ts b/src/util/omit.ts new file mode 100644 index 00000000..7b0a421e --- /dev/null +++ b/src/util/omit.ts @@ -0,0 +1 @@ +export type Omit = Pick>; diff --git a/src/util/reflection/reflection-utilities.test.ts b/src/util/reflection/reflection-utilities.test.ts new file mode 100644 index 00000000..f7bcb2eb --- /dev/null +++ b/src/util/reflection/reflection-utilities.test.ts @@ -0,0 +1,54 @@ +import { getReflectedPropertyType } from './reflection-utilities'; + +const testDecorator = (_proto: unknown, _propertyKey: string) => { + /*NOOP*/ +}; + +interface TestInterface { + property: string; +} + +class DecoratedClass { + public undecorated?: string; + + @testDecorator + public decoratedPrimitive?: number; + + @testDecorator + public decoratedComplex?: DecoratedClass; + + @testDecorator + public decoratedUnion?: string | number; + + @testDecorator + public decoratedIntersection?: string & number; + + @testDecorator + public decoratedInterface?: TestInterface; +} + +describe('Reflection utilities', () => { + test('can detect property type from a decorated primitive property', () => { + expect(getReflectedPropertyType(DecoratedClass, 'decoratedPrimitive')).toBe(Number); + }); + + test('can detect property type from a decorated complex property', () => { + expect(getReflectedPropertyType(DecoratedClass, 'decoratedComplex')).toBe(DecoratedClass); + }); + + test('returns undefined for an undecorated property type', () => { + expect(getReflectedPropertyType(DecoratedClass, 'undecorated')).toBeUndefined(); + }); + + test('returns Object for a union property type', () => { + expect(getReflectedPropertyType(DecoratedClass, 'decoratedUnion')).toBe(Object); + }); + + test('returns Object for a intersection property type', () => { + expect(getReflectedPropertyType(DecoratedClass, 'decoratedIntersection')).toBe(Object); + }); + + test('returns Object for a interface type', () => { + expect(getReflectedPropertyType(DecoratedClass, 'decoratedInterface')).toBe(Object); + }); +}); diff --git a/src/util/reflection/reflection-utilities.ts b/src/util/reflection/reflection-utilities.ts new file mode 100644 index 00000000..77ffe260 --- /dev/null +++ b/src/util/reflection/reflection-utilities.ts @@ -0,0 +1,34 @@ +// tslint:disable-next-line:no-import-side-effect no-submodule-imports +import 'core-js/proposals/reflect-metadata'; +import { Constructable, UnknownConstructable } from '../constructable'; + +// Augment Reflect object for core-js polyfill. Would prefer to drop this but core-js is missing good types +// tslint:disable-next-line:no-namespace +declare namespace Reflect { + // tslint:disable-next-line:only-arrow-functions + function getMetadata( + metadataKey: unknown, + target: object, + targetKey?: string | symbol | number + ): T | undefined; +} + +const PROPERTY_TYPE_METADATA_KEY = 'design:type'; + +/** + * Returns any design type metadata for the requested property. Returns undefined if + * type cannot be resolved. Certain types, like intersections, unions and interfaces + * will always return Object, as they do not have a runtime representation. + */ +export const getReflectedPropertyType = ( + classConstructor: Constructable, + propertyKey: keyof T +): UnknownConstructable | undefined => { + const reflectedResult = Reflect.getMetadata( + PROPERTY_TYPE_METADATA_KEY, + classConstructor.prototype as T, + propertyKey as string | symbol + ); + + return reflectedResult; +}; diff --git a/src/variable/evaluator/variable-evaluator.test.ts b/src/variable/evaluator/variable-evaluator.test.ts new file mode 100644 index 00000000..428d08d5 --- /dev/null +++ b/src/variable/evaluator/variable-evaluator.test.ts @@ -0,0 +1,161 @@ +// tslint:disable:no-invalid-template-strings + +import { VariableEvaluator } from './variable-evaluator'; + +describe('Variable evaluator', () => { + test('works for basic properties', () => { + const evaluator = new VariableEvaluator('${test}'); + + expect(evaluator.evaluate({ test: 15 })).toEqual({ + value: 15, + variableNamesAdded: ['test'], + variableNamesRemoved: [] + }); + expect(evaluator.evaluate({ test: 'foo' })).toEqual({ + value: 'foo', + variableNamesAdded: [], + variableNamesRemoved: [] + }); + }); + + test('works for interpolated strings', () => { + const evaluator = new VariableEvaluator("I'm ${ test }!"); + + expect(evaluator.evaluate({ test: 5 })).toEqual({ + value: "I'm 5!", + variableNamesAdded: ['test'], + variableNamesRemoved: [] + }); + expect(evaluator.evaluate({ test: 'foo' })).toEqual({ + value: "I'm foo!", + variableNamesAdded: [], + variableNamesRemoved: [] + }); + }); + + test('returns error if lookup fails', () => { + expect(new VariableEvaluator("I'm ${ test }!").evaluate({ not: 5 })).toEqual({ + variableNamesAdded: ['test'], + variableNamesRemoved: [], + error: 'Could not lookup variable value: test' + }); + expect(new VariableEvaluator('${test}').evaluate({ not: 5 })).toEqual({ + variableNamesAdded: ['test'], + variableNamesRemoved: [], + error: 'Could not lookup variable value: test' + }); + }); + + test('returns undefined if bad parse', () => { + expect(new VariableEvaluator("I'm ${!").evaluate({ not: 5 })).toEqual({ + variableNamesAdded: [], + variableNamesRemoved: [], + error: 'Parse error in child node' + }); + }); + + test('works for escaped strings', () => { + expect(new VariableEvaluator("I'm ${\\$pecial}!").evaluate({ ['$pecial']: 5 })).toEqual({ + value: "I'm 5!", + variableNamesAdded: ['$pecial'], + variableNamesRemoved: [] + }); + }); + + test('works for nested props', () => { + expect(new VariableEvaluator('${${a}${b}}').evaluate({ a: 'foo', b: 'bar', foobar: 'baz' })).toEqual({ + value: 'baz', + variableNamesAdded: ['a', 'b', 'foobar'], + variableNamesRemoved: [] + }); + }); + + test('returns correct diff if undefined and multiple variables', () => { + expect(new VariableEvaluator('${${a}${b}}').evaluate({})).toEqual({ + error: 'Could not lookup variable value: a; Could not lookup variable value: b', + variableNamesAdded: ['a', 'b'], + variableNamesRemoved: [] + }); + + expect(new VariableEvaluator('${a} and ${b}').evaluate({})).toEqual({ + error: 'Could not lookup variable value: a; Could not lookup variable value: b', + variableNamesAdded: ['a', 'b'], + variableNamesRemoved: [] + }); + + expect(new VariableEvaluator('${${a}${b}} and ${${c}${d}}').evaluate({})).toEqual({ + // tslint:disable-next-line:max-line-length + error: + 'Could not lookup variable value: a; Could not lookup variable value: b; Could not lookup variable value: c; Could not lookup variable value: d', + variableNamesAdded: ['a', 'b', 'c', 'd'], + variableNamesRemoved: [] + }); + }); + + test('variable name diff works for nested props', () => { + const evaluator = new VariableEvaluator('${${a}${b}}'); + + evaluator.evaluate({ a: 'foo', b: 'bar', foobar: 'baz', food: 'stuff' }); // Tested above + + expect(evaluator.evaluate({ a: 'foo', b: 'd', foobar: 'baz', food: 'stuff' })).toEqual({ + value: 'stuff', + variableNamesAdded: ['food'], + variableNamesRemoved: ['foobar'] + }); + + expect(evaluator.evaluate({ a: 'foo', foobar: 'baz', food: 'stuff' })).toEqual({ + error: 'Could not lookup variable value: b', + variableNamesAdded: [], + variableNamesRemoved: ['food'] + }); + }); + + test('variable name diff works for self-referencing props', () => { + const evaluator = new VariableEvaluator('${${foo}}'); + + expect(evaluator.evaluate({ foo: 'foo' })).toEqual({ + value: 'foo', + variableNamesAdded: ['foo'], + variableNamesRemoved: [] + }); + + expect(evaluator.evaluate({ foo: 'bar' })).toEqual({ + error: 'Could not lookup variable value: bar', + variableNamesAdded: ['bar'], + variableNamesRemoved: [] + }); + }); + + test('returns error for undefined lookup', () => { + expect(new VariableEvaluator('${test}').evaluate({ test: undefined })).toEqual({ + error: 'Could not lookup variable value: test', + variableNamesAdded: ['test'], + variableNamesRemoved: [] + }); + + expect(new VariableEvaluator('${test} and ${other}').evaluate({ test: 'fine' })).toEqual({ + error: 'Could not lookup variable value: other', + variableNamesAdded: ['test', 'other'], + variableNamesRemoved: [] + }); + }); + + test('unevaluate returns original string', () => { + const evaluator = new VariableEvaluator('${test}'); + + // Even if not evaluated + expect(evaluator.unevaluate()).toEqual({ + value: '${test}', + variableNamesAdded: [], + variableNamesRemoved: [] + }); + + evaluator.evaluate({}); + + expect(evaluator.unevaluate()).toEqual({ + value: '${test}', + variableNamesAdded: [], + variableNamesRemoved: ['test'] + }); + }); +}); diff --git a/src/variable/evaluator/variable-evaluator.ts b/src/variable/evaluator/variable-evaluator.ts new file mode 100644 index 00000000..4231d32a --- /dev/null +++ b/src/variable/evaluator/variable-evaluator.ts @@ -0,0 +1,145 @@ +import { difference, get } from 'lodash'; +import { ExpressionParser } from '../parser/expression-parser'; +import { ParseNode, ParseNodeType } from '../parser/parse-node'; +import { ResolveDictionary } from '../variable-dictionary'; + +/** + * Provides an evaluator for re-evaluating the same variable string + * against different variable values. + */ +export class VariableEvaluator { + private readonly parser: ExpressionParser; + private readonly variableNamesFromLastEvaluate: Set = new Set(); + + public constructor(private readonly variableString: string) { + this.parser = new ExpressionParser(variableString); + } + + /** + * Does the evaluation, using the provided dictionary to perform any variable lookups + */ + public evaluate(dictionary: ResolveDictionary): EvaluationResult { + const variablesBeforeEvaluate = [...this.variableNamesFromLastEvaluate]; + this.variableNamesFromLastEvaluate.clear(); + const result: EvaluationResult = { + variableNamesAdded: [], + variableNamesRemoved: [] + }; + + try { + const value = this.convertNodeToValue(this.parser.parse(), dictionary) as T; + result.value = value; + } catch (e) { + result.error = e && (e as Error).message; + } + + const variablesAfterEvaluate = [...this.variableNamesFromLastEvaluate]; + result.variableNamesRemoved = difference(variablesBeforeEvaluate, variablesAfterEvaluate); + result.variableNamesAdded = difference(variablesAfterEvaluate, variablesBeforeEvaluate); + + return result; + } + + /** + * Returns result indicating state before evaluation ocurred + */ + public unevaluate(): EvaluationResult { + const variableNamesFromLastEvaluate = [...this.variableNamesFromLastEvaluate]; + this.variableNamesFromLastEvaluate.clear(); + + return { + variableNamesAdded: [], + variableNamesRemoved: variableNamesFromLastEvaluate, + value: this.variableString + }; + } + + private convertNodeToValue(node: ParseNode, dictionary: ResolveDictionary): unknown { + if (node.error) { + throw new Error(node.error); + } + // tslint:disable-next-line:switch-default https://github.com/palantir/tslint/issues/2104 + switch (node.type) { + case ParseNodeType.Root: + return this.convertRootNodeToValue(node, dictionary); + case ParseNodeType.EscapedCharacter: + return this.convertEscapedCharacterToValue(node); + case ParseNodeType.Text: + return this.convertTextToValue(node); + case ParseNodeType.Expression: + return this.convertExpressionToValue(node, dictionary); + } + } + + private convertRootNodeToValue(node: ParseNode, dictionary: ResolveDictionary): unknown { + if (node.children.length === 1) { + return this.convertNodeToValue(node.children[0], dictionary); + } + + return this.mapAndJoinChildren(node, dictionary); + } + + private convertEscapedCharacterToValue(node: ParseNode): string { + return this.variableString.charAt(node.start + node.length - 1); // Last char of node + } + + private convertTextToValue(node: ParseNode): string { + return this.variableString.substr(node.start, node.length); // Last char of node + } + + private convertExpressionToValue(node: ParseNode, dictionary: ResolveDictionary): unknown { + const propertyPath = this.mapAndJoinChildren(node, dictionary).trim(); + + this.variableNamesFromLastEvaluate.add(propertyPath.split('.')[0]); + + const result = get(dictionary, propertyPath); + if (result !== undefined) { + // Treat undefined as unable to look up. We don't allow assignment of undefined. + return result; + } + throw new Error(`Could not lookup variable value: ${propertyPath}`); + } + + private mapAndJoinChildren(node: ParseNode, dictionary: ResolveDictionary): string { + const caughtErrors: Error[] = []; + const mappedValues = node.children.map(child => { + try { + return this.convertNodeToValue(child, dictionary); + } catch (e) { + caughtErrors.push(e as Error); + } + }); + + if (caughtErrors.length > 0) { + throw this.combineErrors(caughtErrors); + } + + return mappedValues.join(''); + } + + private combineErrors(errorArray: Error[]): Error { + return Error(errorArray.map(error => error.message).join('; ')); + } +} + +/** + * The results of evaluating a particular expression + */ +export interface EvaluationResult { + /** + * The resolved value of the evaluation + */ + value?: T; + /** + * Any errors resulting from evaluation + */ + error?: string; + /** + * Variable names used that were not used in the previous evaluation + */ + variableNamesAdded: string[]; + /** + * Variable names which were not used in this evaluation, but were on the previous evaluation + */ + variableNamesRemoved: string[]; +} diff --git a/src/variable/manager/variable-manager.test.ts b/src/variable/manager/variable-manager.test.ts new file mode 100644 index 00000000..2ef70e10 --- /dev/null +++ b/src/variable/manager/variable-manager.test.ts @@ -0,0 +1,326 @@ +// tslint:disable:no-invalid-template-strings +import { ModelChangedEvent } from '../../model/events/model-changed-event'; +import { ModelManager } from '../../model/manager/model-manager'; +import { PropertyLocation } from '../../model/property/property-location'; +import { PartialObjectMock } from '../../test/partial-object-mock'; +import { Logger } from '../../util/logging/logger'; +import { VariableManager } from './variable-manager'; + +describe('Variable manager', () => { + let manager: VariableManager; + let mockLogger: Partial; + let mockModelManager: Partial; + let mockModelChangedEvent: Partial; + const model = {}; + const parent = {}; + const root = {}; + + beforeEach(() => { + mockLogger = { + warn: jest.fn() + }; + mockModelChangedEvent = { + publishChange: jest.fn() + }; + mockModelManager = { + getParent: jest.fn().mockImplementation(inputModel => { + if (inputModel === model) { + return parent; + } + if (inputModel === parent) { + return root; + } + + return undefined; + }) + }; + + manager = new VariableManager( + mockLogger as Logger, + mockModelManager as ModelManager, + mockModelChangedEvent as ModelChangedEvent + ); + }); + + test('should support basic read/write', () => { + manager.set('a', 'a value', model); + manager.set('b', 'b value', model); + manager.set('c', 'c value', model); + + expect(manager.get('a', model)).toBe('a value'); + expect(manager.get('b', model)).toBe('b value'); + expect(manager.get('c', model)).toBe('c value'); + }); + + test('should return undefined and warn for an unknown key', () => { + expect(manager.get('a', model)).toBe(undefined); + expect(mockLogger.warn).toHaveBeenCalledWith('Attempting to lookup unassigned variable: a'); + }); + + test('should allow overwrite of existing variable', () => { + manager.set('a', 'a value', model); + manager.set('a', 'new a value', model); + + expect(manager.get('a', model)).toBe('new a value'); + }); + + test('should resolve to higher scopes', () => { + manager.set('a', 'a value', model); + manager.set('b', 'b value', parent); + manager.set('c', 'c value', root); + + expect(manager.get('a', model)).toBe('a value'); + expect(manager.get('b', model)).toBe('b value'); + expect(manager.get('c', model)).toBe('c value'); + }); + + test('should support variable shadowing', () => { + manager.set('a', 'a value', model); + manager.set('a', "parent's a value", parent); + + expect(manager.get('a', model)).toBe('a value'); + expect(manager.get('a', parent)).toBe("parent's a value"); + expect(manager.get('a', root)).toBeUndefined(); + }); + + test('should determine if a key is contained in the provided model scope', () => { + manager.set('a', 'a value', root); + manager.set('b', "parent's b value", parent); + expect(manager.has('a', root)).toBe(true); + expect(manager.has('a', model)).toBe(true); + expect(manager.has('b', root)).toBe(false); + expect(manager.has('b', parent)).toBe(true); + expect(manager.has('b', model)).toBe(true); + expect(manager.has('c', model)).toBe(false); + }); + + test('should treat an undefined variable the same as none at all', () => { + manager.set('a', undefined, root); + expect(manager.has('a', root)).toBe(false); + }); +}); + +describe('Variable manager reference tracking', () => { + // Not mocking variable reference, we want to test a little more integration here + let manager: VariableManager; + let mockLogger: PartialObjectMock; + let mockModelManager: PartialObjectMock; + let mockModelChangedEvent: PartialObjectMock; + + let mockParentLocation: PartialObjectMock; + const parent = {}; + + let mockModelLocation: PartialObjectMock; + const model = {}; + + beforeEach(() => { + mockModelChangedEvent = { publishChange: jest.fn() }; + mockLogger = { + error: jest.fn() + }; + mockModelManager = { + getParent: jest.fn().mockImplementation(inputModel => { + if (inputModel === model) { + return parent; + } + + return undefined; + }), + getRoot: jest.fn().mockReturnValue(parent), + isAncestor: jest.fn( + (amodel: object, potentialAncestor: object) => amodel === model && potentialAncestor === parent + ) + }; + + mockModelLocation = { + parentModel: model, + setProperty: jest.fn(), + toString: () => 'modelProp' + }; + + mockParentLocation = { + parentModel: parent, + setProperty: jest.fn(), + toString: () => 'parentProp' + }; + + manager = new VariableManager( + mockLogger as Logger, + mockModelManager as ModelManager, + mockModelChangedEvent as ModelChangedEvent + ); + }); + + test('should be able to register references', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${test}'); + expect(manager.isVariableReference(mockModelLocation as PropertyLocation)).toBe(true); + + expect(manager.isVariableReference(mockParentLocation as PropertyLocation)).toBe(false); + manager.registerReference(mockParentLocation as PropertyLocation, '${test}'); + expect(manager.isVariableReference(mockParentLocation as PropertyLocation)).toBe(true); + }); + + test('should log error if registering twice to the same location', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${test}'); + manager.registerReference(mockModelLocation as PropertyLocation, '${test}'); + expect(mockLogger.error).toHaveBeenCalledWith( + 'Attempting to register reference which has already been declared at modelProp' + ); + }); + + test('should update existing references when setting a variable', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${test}'); + expect(mockModelChangedEvent.publishChange).nthCalledWith(1, model); + manager.set('test', 'foo', model); + expect(mockModelLocation.setProperty).toHaveBeenLastCalledWith('foo'); + expect(mockModelChangedEvent.publishChange).nthCalledWith(2, model); + manager.set('test', 'baz', model); + expect(mockModelLocation.setProperty).toHaveBeenLastCalledWith('baz'); + expect(mockModelChangedEvent.publishChange).nthCalledWith(3, model); + expect(mockModelChangedEvent.publishChange).toHaveBeenCalledTimes(3); + }); + + test('should shadow existing variables when setting at a more specific scope', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${test}'); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(1, undefined); + + manager.set('test', 'foo', parent); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(2, 'foo'); + + manager.set('test', 'baz', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(3, 'baz'); + + manager.set('test', 'more baz', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(4, 'more baz'); + + manager.set('test', 'bar', parent); + // Not called again + expect(mockModelLocation.setProperty).toHaveBeenCalledTimes(4); + }); + + test('should not shadow references for parent models', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${test}'); + manager.registerReference(mockParentLocation as PropertyLocation, '${test}'); + + manager.set('test', 'baz', model); + expect(mockModelLocation.setProperty).toHaveBeenLastCalledWith('baz'); + expect(mockParentLocation.setProperty).toHaveBeenLastCalledWith(undefined); + + manager.set('test', 'foo', parent); + expect(mockModelLocation.setProperty).toHaveBeenLastCalledWith('baz'); + expect(mockParentLocation.setProperty).toHaveBeenLastCalledWith('foo'); + }); + + test('should track references in complex variables correctly', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${${a}${b}}'); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(1, undefined); + + manager.set('foobar', 'baz', model); + manager.set('b', 'bar', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(2, undefined); + manager.set('a', 'foo', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(3, 'baz'); + + manager.set('b', 'c', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(4, undefined); + + manager.set('b', 'd', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(5, undefined); + + manager.set('food', 'ta da', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(6, 'ta da'); + }); + + test('should track references in interpolated variable strings correctly', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${a} and ${b}'); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(1, undefined); + + manager.set('b', 'bar', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(2, undefined); + manager.set('a', 'foo', model); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(3, 'foo and bar'); + }); + + test('should handle deregistration of references', () => { + manager.registerReference(mockModelLocation as PropertyLocation, '${test}'); + expect(mockModelLocation.setProperty).toHaveBeenNthCalledWith(1, undefined); + + expect(manager.isVariableReference(mockModelLocation as PropertyLocation)).toBe(true); + + expect(manager.deregisterReference(mockModelLocation as PropertyLocation)).toBe('${test}'); + + expect(manager.isVariableReference(mockModelLocation as PropertyLocation)).toBe(false); + + manager.set('test', 'foo', model); + expect(mockModelLocation.setProperty).toHaveBeenCalledTimes(1); + }); + + test('deregister throws error if location does contain reference', () => { + mockLogger.error = jest.fn(() => ({ + throw: () => { + throw Error(); + } + })); + + expect(() => manager.deregisterReference(mockModelLocation as PropertyLocation)).toThrow(); + + expect(mockLogger.error) + // tslint:disable-next-line:max-line-length + .toHaveBeenCalledWith( + 'Attempted to deregister reference at modelProp which does not contain a registered reference' + ); + }); + + test('can determine strings containing a variable expression', () => { + expect(manager.isVariableExpression('test')).toBe(false); + expect(manager.isVariableExpression('${test}')).toBe(true); + expect(manager.isVariableExpression('test ${test}')).toBe(true); + expect(manager.isVariableExpression('{}')).toBe(false); + expect(manager.isVariableExpression('')).toBe(false); + expect(manager.isVariableExpression('${${nes}${ted}}')).toBe(true); + expect(manager.isVariableExpression('${}')).toBe(true); + expect(manager.isVariableExpression('some ${bad')).toBe(true); + expect(manager.isVariableExpression('${${nested}${bad}')).toBe(true); + }); + + test('registering a reference returns the resolved value', () => { + expect(manager.registerReference(mockModelLocation as PropertyLocation, '${test}')).toBeUndefined(); + manager.set('test', 'foo', parent); + expect(manager.registerReference(mockParentLocation as PropertyLocation, '${test}')).toBe('foo'); + + manager.deregisterReference(mockParentLocation as PropertyLocation); + + manager.set('bar', 'baz', parent); + expect(manager.registerReference(mockParentLocation as PropertyLocation, '${test} + ${bar}')).toBe('foo + baz'); + }); + + test('allows retrieving a variable expression without deregistering', () => { + manager.registerReference(mockModelLocation as PropertyLocation, 'Hi: ${test}'); + + manager.set('test', 'foo', model); + + expect(mockModelLocation.setProperty).lastCalledWith('Hi: foo'); + + expect(manager.getVariableExpressionFromLocation(mockModelLocation as PropertyLocation)).toBe('Hi: ${test}'); + + manager.set('test', 'bar', model); + + expect(mockModelLocation.setProperty).lastCalledWith('Hi: bar'); + }); + + test('getVariableExpressionFromLocation throws error if location does contain reference', () => { + mockLogger.error = jest.fn(() => ({ + throw: () => { + throw Error(); + } + })); + + expect(() => manager.getVariableExpressionFromLocation(mockModelLocation as PropertyLocation)).toThrow(); + + expect(mockLogger.error) + // tslint:disable-next-line:max-line-length + .toHaveBeenCalledWith( + 'Attempted to resolve reference at modelProp which does not contain a registered reference' + ); + }); +}); diff --git a/src/variable/manager/variable-manager.ts b/src/variable/manager/variable-manager.ts new file mode 100644 index 00000000..a2e1f570 --- /dev/null +++ b/src/variable/manager/variable-manager.ts @@ -0,0 +1,283 @@ +import { fromPairs } from 'lodash'; +import { ModelChangedEvent } from '../../model/events/model-changed-event'; +import { ModelManager } from '../../model/manager/model-manager'; +import { PropertyLocation } from '../../model/property/property-location'; +import { Logger } from '../../util/logging/logger'; +import { EvaluationResult } from '../evaluator/variable-evaluator'; +import { ExpressionParser } from '../parser/expression-parser'; +import { ParseNodeType } from '../parser/parse-node'; +import { VariableReference } from '../reference/variable-reference'; +import { VariableValue } from '../value/variable-value'; +import { ResolveDictionary, VariableDictionary } from '../variable-dictionary'; + +/** + * Variable manager handles read, write and update of variable values, + * supporting serialization and deserialization to convert variables into + * values and back. + */ +export class VariableManager { + private readonly variableDictionaries: WeakMap = new WeakMap(); + private readonly variableReferences: WeakMap> = new WeakMap(); + + public constructor( + private readonly logger: Logger, + private readonly modelManager: ModelManager, + private readonly modelChangedEvent: ModelChangedEvent + ) {} + + /** + * Assign a value to the given key and scope. Scope should be a model object. + */ + public set(key: string, value: unknown, modelScope: object): void { + if (!this.variableDictionaries.has(modelScope)) { + this.variableDictionaries.set(modelScope, new Map()); + } + const variableDictionary = this.variableDictionaries.get(modelScope)!; + + if (variableDictionary.has(key)) { + variableDictionary.get(key)!.currentValue = value; + } else { + const newValue = this.createVariableValue(key, value); + this.shadowExistingReferencesIfNeeded(modelScope, newValue); + variableDictionary.set(key, newValue); + } + + this.updateAllReferences(variableDictionary.get(key)!); + } + + /** + * Retrieves a value for the given key. If that value has been assigned in this scope, + * it will be returned. Otherwise, scopes will be searched upwards in the model tree + * returning undefined if no match is found. + */ + public get(key: string, modelScope: object): T | undefined { + const variableValue = this.getVariableValue(key, modelScope); + if (!variableValue) { + this.logger.warn(`Attempting to lookup unassigned variable: ${key}`); + } + + return variableValue && variableValue.currentValue; + } + + /** + * Indicates whether the provided key is registered, accessible at the given scope + * and returns a defined value. + */ + public has(key: string, modelScope: object): boolean { + const variableValue = this.getVariableValue(key, modelScope); + + return variableValue ? variableValue.currentValue !== undefined : false; + } + + /** + * Begin tracking the provided expression at `location`. The value will be set based on + * variables, and updated as variables changed. + * + * Throws Error if the provided location is already being tracked + */ + public registerReference(location: PropertyLocation, variableExpression: string): unknown { + let reference = new VariableReference(variableExpression, location); + + const referenceMap = this.getOrCreateReferenceMapForModelContainingLocation(location); + + if (referenceMap.has(location.toString())) { + this.logger.error(`Attempting to register reference which has already been declared at ${location.toString()}`); + reference = referenceMap.get(location.toString())!; + } else { + referenceMap.set(location.toString(), reference); + } + + return this.updateReference(reference); + } + + /** + * Indicates whether the value at `location` is currently being tracked as a variable reference + */ + public isVariableReference(location: PropertyLocation): boolean { + return !!this.getReferenceAtLocation(location); + } + + /** + * Indicates whether the provided string should be treated as a variable expression + */ + public isVariableExpression(potentialExpression: string): boolean { + const parsed = new ExpressionParser(potentialExpression).parse(); + + return parsed.children.some(child => child.type === ParseNodeType.Expression); + } + + /** + * Ends tracking for the variable at `location`. Returns the original variable string. + * The value at `location` is left as is. + * + * Throws Error if the provided location is not being tracked + */ + public deregisterReference(location: PropertyLocation): string { + const reference = this.getReferenceAtLocation(location); + + if (!reference) { + return this.logger + .error( + `Attempted to deregister reference at ${location.toString()} which does not contain a registered reference` + ) + .throw(); + } + + this.getOrCreateReferenceMapForModelContainingLocation(location).delete(reference.location.toString()); + + const result = reference.unresolve(); + this.updateValueReferenceTrackingFromEvaluationResult(reference, result); + + return result.value!; + } + + /** + * Retrieves the original variable expression from `location`. This value will continue + * to be tracked. + * + * Throws Error if the provided location is not being tracked + */ + public getVariableExpressionFromLocation(location: PropertyLocation): string { + const reference = this.getReferenceAtLocation(location); + + if (!reference) { + return this.logger + .error(`Attempted to resolve reference at ${location.toString()} which does not contain a registered reference`) + .throw(); + } + /* Unresolve is stateful, but it *should* be OK. on the following resolution, it will think new variables are being + used, but we're using sets so the extra references should be deduped + */ + const expression = reference.unresolve().value!; + + return expression; + } + + private getParentModelScope(modelScope: object): object | undefined { + const parentModel = this.modelManager.getParent(modelScope); + if (!parentModel) { + return undefined; + } + + return this.variableDictionaries.has(parentModel) ? parentModel : this.getParentModelScope(parentModel); + } + + private createVariableValue(key: string, value: T): VariableValue { + return { + key: key, + currentValue: value, + references: new Set() + }; + } + + private updateAllReferences(value: VariableValue): void { + value.references.forEach(reference => this.updateReference(reference)); + } + + private updateReference(reference: VariableReference): unknown { + const modelScope = reference.location.parentModel; + const result = reference.resolve(this.getResolveDictionaryForModel(modelScope)); + this.updateValueReferenceTrackingFromEvaluationResult(reference, result); + this.modelChangedEvent.publishChange(reference.location.parentModel); + + return result.value; + } + + private getReferenceAtLocation(location: PropertyLocation): VariableReference | undefined { + const referenceMapForModel = this.variableReferences.get(location.parentModel); + if (referenceMapForModel) { + return referenceMapForModel.get(location.toString()); + } + + return undefined; + } + + private getDictionaryContainingKey(key: string, modelScope: object): VariableDictionary | undefined { + const dictionaryWithRequestedScope = this.variableDictionaries.get(modelScope); + if (dictionaryWithRequestedScope && dictionaryWithRequestedScope.has(key)) { + return dictionaryWithRequestedScope; + } + + const parent = this.getParentModelScope(modelScope); + + return parent ? this.getDictionaryContainingKey(key, parent) : undefined; + } + + private getOrCreateReferenceMapForModelContainingLocation( + location: PropertyLocation + ): Map { + if (!this.variableReferences.has(location.parentModel)) { + this.variableReferences.set(location.parentModel, new Map()); + } + + return this.variableReferences.get(location.parentModel)!; + } + + private getResolveDictionaryForModel(modelScope: object): ResolveDictionary { + const variablePairs: [string, VariableValue][] = []; + let nextModelScope = this.variableDictionaries.has(modelScope) ? modelScope : this.getParentModelScope(modelScope); + + while (nextModelScope) { + // Later takes precedence, so we always unshift on to beginning + variablePairs.unshift(...this.variableDictionaries.get(nextModelScope)!); + nextModelScope = this.getParentModelScope(nextModelScope); + } + + return fromPairs(variablePairs.map(([key, value]) => [key, value.currentValue])); + } + + private getVariableValue(key: string, modelScope: object): VariableValue | undefined { + const dictionaryWithKey = this.getDictionaryContainingKey(key, modelScope); + + return dictionaryWithKey && (dictionaryWithKey.get(key) as VariableValue | undefined); + } + + private updateValueReferenceTrackingFromEvaluationResult( + reference: VariableReference, + evaluationResult: EvaluationResult + ): void { + const modelScope = reference.location.parentModel; + evaluationResult.variableNamesRemoved.forEach(name => { + // Every variable name previously referenced should have a placeholder + this.getVariableValue(name, modelScope)!.references.delete(reference); + }); + + evaluationResult.variableNamesAdded.forEach(name => { + if (!this.has(name, modelScope)) { + this.addPlaceholderVariable(name, modelScope); + } + this.getVariableValue(name, modelScope)!.references.add(reference); + }); + } + + private shadowExistingReferencesIfNeeded(modelScope: object, newVariableValue: VariableValue): void { + // References registered before this new value may be inside this scope and should be switched over + const parentModelScope = this.getParentModelScope(modelScope); + const parentDictionary = + parentModelScope && this.getDictionaryContainingKey(newVariableValue.key, parentModelScope); + if (!parentDictionary) { + return; // This variable is not shadowing any other + } + const existingReferences = parentDictionary.get(newVariableValue.key)!.references; + + const referencesToUpdate: VariableReference[] = []; + + existingReferences.forEach(reference => { + if ( + reference.location.parentModel === modelScope || + this.modelManager.isAncestor(reference.location.parentModel, modelScope) + ) { + referencesToUpdate.push(reference); + } + }); + + referencesToUpdate.forEach(referenence => { + existingReferences.delete(referenence); + newVariableValue.references.add(referenence); + }); + } + + private addPlaceholderVariable(variableName: string, modelScope: object): void { + this.set(variableName, undefined, this.modelManager.getRoot(modelScope)); + } +} diff --git a/src/variable/parser/expression-parser.test.ts b/src/variable/parser/expression-parser.test.ts new file mode 100644 index 00000000..97426294 --- /dev/null +++ b/src/variable/parser/expression-parser.test.ts @@ -0,0 +1,588 @@ +// tslint:disable:no-invalid-template-strings max-file-line-count + +import { ExpressionParser } from './expression-parser'; +import { ParseNodeType } from './parse-node'; + +describe('Expression parser', () => { + test('should cache results', () => { + // Kinda hard to test this as it's intentionally internal + const parser = new ExpressionParser('interpolated ${something\\}'); + + expect(parser.parse()).toBe(parser.parse()); + }); + + test('should identify normal strings', () => { + expect(new ExpressionParser('').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 0, + children: [] + }) + ); + + expect(new ExpressionParser('test string').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 11, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 11 + }) + ] + }) + ); + + expect(new ExpressionParser('test {}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 7 + }) + ] + }) + ); + + expect(new ExpressionParser('test {').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 6, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 6 + }) + ] + }) + ); + + expect(new ExpressionParser('test {').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 6, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 6 + }) + ] + }) + ); + + expect(new ExpressionParser('test $').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 6, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 6 + }) + ] + }) + ); + + expect(new ExpressionParser('{}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 2, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 2 + }) + ] + }) + ); + + expect(new ExpressionParser('test {}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 7 + }) + ] + }) + ); + + expect(new ExpressionParser('{}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 2, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 2 + }) + ] + }) + ); + + expect(new ExpressionParser('}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 1, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 1 + }) + ] + }) + ); + + expect(new ExpressionParser('{').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 1, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 1 + }) + ] + }) + ); + + expect(new ExpressionParser('$').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 1, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 1 + }) + ] + }) + ); + + expect(new ExpressionParser('$ {test}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 8 + }) + ] + }) + ); + }); + + test('escaped expressions', () => { + expect(new ExpressionParser('\\').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 1, + error: expect.any(String), + children: [ + expect.objectContaining({ + type: ParseNodeType.EscapedCharacter, + length: 1, + error: expect.any(String) + }) + ] + }) + ); + + expect(new ExpressionParser('\\${test}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.EscapedCharacter, + length: 2 + }), + expect.objectContaining({ + type: ParseNodeType.Text, + start: 2, + length: 6 + }) + ] + }) + ); + + expect(new ExpressionParser('$\\{test}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 1 + }), + expect.objectContaining({ + type: ParseNodeType.EscapedCharacter, + start: 1, + length: 2 + }), + expect.objectContaining({ + type: ParseNodeType.Text, + start: 3, + length: 5 + }) + ] + }) + ); + }); + + test('should identify strings containing expressions', () => { + expect(new ExpressionParser('${test}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Expression, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 4, + start: 2 + }) + ] + }) + ] + }) + ); + + expect(new ExpressionParser('${ test}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.Expression, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 5, + start: 2 + }) + ] + }) + ] + }) + ); + + expect(new ExpressionParser('${test }').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.Expression, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 5, + start: 2 + }) + ] + }) + ] + }) + ); + + expect(new ExpressionParser('interpolated ${test}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 20, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 13 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 13, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 4, + start: 15 + }) + ] + }) + ] + }) + ); + + expect(new ExpressionParser('interpolated ${test} $}{').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 24, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 13 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 13, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 4, + start: 15 + }) + ] + }), + expect.objectContaining({ + type: ParseNodeType.Text, + start: 20, + length: 4 + }) + ] + }) + ); + + expect(new ExpressionParser('interpolated ${ test }').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 24, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 13 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 13, + length: 11, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 8, + start: 15 + }) + ] + }) + ] + }) + ); + + expect(new ExpressionParser('test \\$${test}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 14, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 5 + }), + expect.objectContaining({ + type: ParseNodeType.EscapedCharacter, + length: 2, + start: 5 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 7, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 4, + start: 9 + }) + ] + }) + ] + }) + ); + + expect(new ExpressionParser('${}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 3, + children: [ + expect.objectContaining({ + type: ParseNodeType.Expression, + length: 3, + // This one is invalid in template strings, but seems safe enough to just remove if interpolatd + children: [] + }) + ] + }) + ); + }); + + test('should identify strings with invalid expressions', () => { + expect(new ExpressionParser('interpolated ${test} ${').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 23, + error: expect.any(String), + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 13 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 13, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 4, + start: 15 + }) + ] + }), + expect.objectContaining({ + type: ParseNodeType.Text, + start: 20, + length: 1 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 21, + length: 2, + error: expect.any(String) + }) + ] + }) + ); + + expect(new ExpressionParser('interpolated ${').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 15, + error: expect.any(String), + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 13 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 13, + length: 2, + error: expect.any(String) + }) + ] + }) + ); + + expect(new ExpressionParser('interpolated ${something').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 24, + error: expect.any(String), + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 13 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 13, + length: 11, + error: expect.any(String), + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + start: 15, + length: 9 + }) + ] + }) + ] + }) + ); + + expect(new ExpressionParser('interpolated ${something\\}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 26, + error: expect.any(String), + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + length: 13 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 13, + length: 13, + error: expect.any(String), + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + start: 15, + length: 9 + }), + expect.objectContaining({ + type: ParseNodeType.EscapedCharacter, + start: 24, + length: 2 + }) + ] + }) + ] + }) + ); + }); + + test('should support sub expressions', () => { + expect(new ExpressionParser('${${first}\\$${last}}').parse()).toEqual( + expect.objectContaining({ + type: ParseNodeType.Root, + length: 20, + children: [ + expect.objectContaining({ + type: ParseNodeType.Expression, + length: 20, + children: [ + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 2, + length: 8, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + start: 4, + length: 5 + }) + ] + }), + expect.objectContaining({ + type: ParseNodeType.EscapedCharacter, + start: 10, + length: 2 + }), + expect.objectContaining({ + type: ParseNodeType.Expression, + start: 12, + length: 7, + children: [ + expect.objectContaining({ + type: ParseNodeType.Text, + start: 14, + length: 4 + }) + ] + }) + ] + }) + ] + }) + ); + }); +}); diff --git a/src/variable/parser/expression-parser.ts b/src/variable/parser/expression-parser.ts new file mode 100644 index 00000000..7d5f7f1f --- /dev/null +++ b/src/variable/parser/expression-parser.ts @@ -0,0 +1,180 @@ +import { min } from 'lodash'; +import { ParseNode, ParseNodeType } from './parse-node'; + +/** + * Represents a parsed expression which can detect and replace variables. + * Parsing is done lazily. + * TODO: revisit this... + */ +export class ExpressionParser { + private static readonly ESCAPE: string = '\\'; + private static readonly START: string = '$'; + private static readonly OPEN: string = '{'; + private static readonly CLOSE: string = '}'; + private static readonly EXPRESSION_OPEN_LENGTH: number = ExpressionParser.START.length + ExpressionParser.OPEN.length; + private static readonly EXPRESSION_CLOSE_LENGTH: number = ExpressionParser.CLOSE.length; + + private readonly rootRule: Partial = { + // Partial because a root rule doesn't have a start condition + type: ParseNodeType.Root, + endBefore: () => this.expression.length === 0, + endAfter: index => index === this.lastIndexOfLength(this.expression.length), + parsedUntil: () => this.lastIndexOfLength(this.expression.length) + }; + + private readonly parseRules: ReadonlyArray = [ + { + type: ParseNodeType.EscapedCharacter, + startWith: this.isEscapeSequence.bind(this), + beginParseFrom: startingIndex => startingIndex + ExpressionParser.ESCAPE.length, + endBefore: () => true, // Immediately end after following character + parsedUntil: endingIndex => min([endingIndex, this.lastIndexOfLength(this.expression.length)])!, + errorOn: index => + index > this.lastIndexOfLength(this.expression.length) ? 'Cannot end with escape character' : undefined + }, + { + type: ParseNodeType.Expression, + startWith: this.isExpressionOpen.bind(this), + beginParseFrom: startingIndex => startingIndex + ExpressionParser.EXPRESSION_OPEN_LENGTH, + endBefore: this.isExpressionClose.bind(this), + parsedUntil: index => this.lastIndexOfLength(ExpressionParser.EXPRESSION_CLOSE_LENGTH, index) + }, + { + type: ParseNodeType.Text, + // "default" state - always start a text node if not in one + startWith: (_index: number, currentNodeType: ParseNodeType) => currentNodeType !== ParseNodeType.Text, + endBefore: (index: number, parentNodeType: ParseNodeType) => + this.isEscapeSequence(index) || + this.isExpressionOpen(index) || + (parentNodeType === ParseNodeType.Expression && this.isExpressionClose(index)), + endAfter: (index: number) => index === this.lastIndexOfLength(this.expression.length), + parsedUntil: (endingIndex: number, parentNodeType: ParseNodeType) => + this.isEscapeSequence(endingIndex) || + this.isExpressionOpen(endingIndex) || + (parentNodeType === ParseNodeType.Expression && this.isExpressionClose(endingIndex)) + ? endingIndex - 1 + : endingIndex + } + ]; + + private parsed?: ParseNode; + + public constructor(private readonly expression: string) {} + + /** + * Transform source string into parse tree + */ + public parse(): ParseNode { + if (!this.parsed) { + this.parsed = this.parseByRule(0, this.rootRule as ParseRule); + } + + return this.parsed; + } + + private isEscapeSequence(index: number): boolean { + return this.expression.startsWith(ExpressionParser.ESCAPE, index); + } + + private isExpressionOpen(index: number): boolean { + return this.expression.startsWith(ExpressionParser.START + ExpressionParser.OPEN, index); + } + + private isExpressionClose(index: number): boolean { + return this.expression.startsWith(ExpressionParser.CLOSE, index); + } + + private lengthBetween(startIndex: number, endIndex: number): number { + return endIndex - startIndex + 1; + } + + private lastIndexOfLength(length: number, startIndex: number = 0): number { + return startIndex + length - 1; + } + + private parseByRule(startingFrom: number, currentRule: ParseRule, parentRule?: ParseRule): ParseNode { + let currentIndex = currentRule.beginParseFrom ? currentRule.beginParseFrom(startingFrom) : startingFrom; + const result = { + type: currentRule.type, + start: startingFrom, + children: [] as ParseNode[] + }; + do { + const error = currentRule.errorOn && currentRule.errorOn(currentIndex); + if (error) { + return { + ...result, + length: this.lengthBetween( + startingFrom, + currentRule.parsedUntil(currentIndex, parentRule && parentRule.type) + ), + error: error + }; + } + if (currentRule.endBefore && currentRule.endBefore(currentIndex, parentRule && parentRule.type)) { + return { + ...result, + length: this.lengthBetween(startingFrom, currentRule.parsedUntil(currentIndex, parentRule && parentRule.type)) + }; + } + const newRule = this.parseRules.find(rule => rule.startWith(currentIndex, currentRule.type)); + if (newRule) { + const child = this.parseByRule(currentIndex, newRule, currentRule); + result.children.push(child); + currentIndex = this.lastIndexOfLength(child.length, child.start); + if (child.error) { + return { + ...result, + length: this.lengthBetween(startingFrom, currentIndex), + error: 'Parse error in child node' + }; + } + } + + if (currentRule.endAfter && currentRule.endAfter(currentIndex, parentRule && parentRule.type)) { + return { + ...result, + length: this.lengthBetween(startingFrom, currentRule.parsedUntil(currentIndex, parentRule && parentRule.type)) + }; + } + currentIndex += 1; + } while (currentIndex < this.expression.length); + + return { + ...result, + length: this.expression.length - startingFrom, + error: 'Reached end of expression without completing parsing' + }; + } +} + +interface ParseRule { + /** + * Applicable node type + */ + type: ParseNodeType; + /** + * Returns true if the rule applies starting at the provided index + */ + startWith(index: number, currentNodeType: ParseNodeType): boolean; + /** + * Returns an error string if an error is detected at the provided index + */ + errorOn?(index: number): string | undefined; + /** + * Returns true if the rule ends before the provided index + */ + endBefore?(index: number, parentNodeType?: ParseNodeType): boolean; + /** + * Returns true if the rule ends after the provided index + */ + endAfter?(index: number, parentNodeType?: ParseNodeType): boolean; + /** + * Returns the final parsed index after the rule has completed + */ + parsedUntil(endingIndex: number, parentNodeType?: ParseNodeType): number; + /** + * Returns the index to begin parsing after the rule was triggered + */ + beginParseFrom?(startingIndex: number): number; +} diff --git a/src/variable/parser/parse-node.ts b/src/variable/parser/parse-node.ts new file mode 100644 index 00000000..6eeefcc9 --- /dev/null +++ b/src/variable/parser/parse-node.ts @@ -0,0 +1,47 @@ +/** + * A Node in the prase tree representing some part of the variable expression + */ +export interface ParseNode { + /** + * Child nodes + */ + children: ParseNode[]; + /** + * The start index relative to the entire expression + */ + start: number; + /** + * The length of the expression contained in this, including its children + */ + length: number; + /** + * The type of parse node + */ + type: ParseNodeType; + /** + * Errors parsing this node + */ + error?: string; +} + +/** + * Represents the type of information represented by a `ParseNode` + */ +export const enum ParseNodeType { + /** + * A parse tree root node + */ + Root = 'root', + /** + * An expression containing a variable + */ + Expression = 'expression', + /** + * Text, potentially inside another node + */ + Text = 'text', + /** + * An escaped character + */ + EscapedCharacter = 'escape' +} diff --git a/src/variable/reference/variable-reference.test.ts b/src/variable/reference/variable-reference.test.ts new file mode 100644 index 00000000..735c7010 --- /dev/null +++ b/src/variable/reference/variable-reference.test.ts @@ -0,0 +1,45 @@ +// tslint:disable:no-invalid-template-strings +import { PropertyLocation } from '../../model/property/property-location'; +import { EvaluationResult, VariableEvaluator } from '../evaluator/variable-evaluator'; +import { VariableReference } from './variable-reference'; + +jest.mock('../evaluator/variable-evaluator'); +const mockedVariableEvaluatorConstructor = VariableEvaluator as jest.Mock; + +describe('Variable reference', () => { + let mockLocation: Partial; + let mockEvaluationResult: Partial>; + let mockUnevaluationResult: Partial>; + + beforeEach(() => { + mockedVariableEvaluatorConstructor.mockReset(); + mockEvaluationResult = { + value: Symbol('eval') + }; + mockUnevaluationResult = { + value: Symbol('uneval') + }; + mockedVariableEvaluatorConstructor.mockImplementation( + () => + (({ + evaluate: jest.fn().mockReturnValue(mockEvaluationResult), + unevaluate: jest.fn().mockReturnValue(mockUnevaluationResult) + } as unknown) as VariableEvaluator) + ); + mockLocation = { + setProperty: jest.fn() + }; + }); + + test('resolve sets the property to the evaluation result', () => { + const ref = new VariableReference('${test}', mockLocation as PropertyLocation); + + expect(ref.resolve({})).toBe(mockEvaluationResult); + expect(mockLocation.setProperty).toHaveBeenCalledWith(mockEvaluationResult.value); + }); + + test('unresolve returns the unevaluate result', () => { + const ref = new VariableReference('${test}', mockLocation as PropertyLocation); + expect(ref.unresolve()).toBe(mockUnevaluationResult); + }); +}); diff --git a/src/variable/reference/variable-reference.ts b/src/variable/reference/variable-reference.ts new file mode 100644 index 00000000..663859a1 --- /dev/null +++ b/src/variable/reference/variable-reference.ts @@ -0,0 +1,31 @@ +import { PropertyLocation } from '../../model/property/property-location'; +import { EvaluationResult, VariableEvaluator } from '../evaluator/variable-evaluator'; +import { ResolveDictionary } from '../variable-dictionary'; + +/** + * A reference to one or more variables at a specific location. + */ +export class VariableReference { + private readonly evaluator: VariableEvaluator; + + public constructor(variableString: string, public readonly location: PropertyLocation) { + this.evaluator = new VariableEvaluator(variableString); + } + + /** + * Using the provided dictionary, assigns the location with the resolved variable(s). + */ + public resolve(dictionary: ResolveDictionary): EvaluationResult { + const result = this.evaluator.evaluate(dictionary); + this.location.setProperty(result.value); + + return result; + } + + /** + * Returns the original variable expression value. Does not assign it. + */ + public unresolve(): EvaluationResult { + return this.evaluator.unevaluate(); + } +} diff --git a/src/variable/value/variable-value.ts b/src/variable/value/variable-value.ts new file mode 100644 index 00000000..444075c0 --- /dev/null +++ b/src/variable/value/variable-value.ts @@ -0,0 +1,19 @@ +import { VariableReference } from '../reference/variable-reference'; + +/** + * A variable in a dashboard which may have multiple references + */ +export interface VariableValue { + /** + * The variable key - i.e 'a' for ${a} + */ + key: string; + /** + * The current value of the variable + */ + currentValue: T; + /** + * References to this variable + */ + references: Set; +} diff --git a/src/variable/variable-dictionary.ts b/src/variable/variable-dictionary.ts new file mode 100644 index 00000000..14c4fa52 --- /dev/null +++ b/src/variable/variable-dictionary.ts @@ -0,0 +1,10 @@ +import { VariableValue } from './value/variable-value'; + +export type VariableDictionary = Map>; + +/** + * A dictionary used for resolving a variable + */ +export interface ResolveDictionary { + [key: string]: unknown; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..f9c6a14e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "es5", + "module": "es2015", + "lib": ["es2015", "es2016", "es2017", "esnext.array", "dom"], + "strict": true, + "sourceMap": true, + "declaration": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "noEmitOnError": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "downlevelIteration": true, + "declarationDir": "dist/types", + "outDir": "dist/lib", + "types": ["jest"] + }, + "include": ["src"] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..cd7ecf4e --- /dev/null +++ b/tslint.json @@ -0,0 +1,81 @@ +{ + "extends": ["tslint:all", "tslint-config-prettier"], + "rules": { + "no-inferrable-types": [true, "ignore-properties", "ignore-params"], + "typedef": [true, "parameter", "call-signature", "property-declaration", "member-variable-declaration"], + "max-file-line-count": [true, 500], + "file-name-casing": [true, "kebab-case"], + "prefer-function-over-method": false, + "interface-name": false, + "array-type": [true, "array"], + "quotemark": [true, "single", "avoid-escape", "avoid-template"], + "no-non-null-assertion": false, + "member-ordering": false, + "no-unused-variable": false, + "completed-docs": [ + true, + { + "enums": true, + "classes": { + "visibilities": ["exported"] + }, + "functions": { + "visibilities": ["exported"] + }, + "methods": { + "privacies": ["public", "protected"], + "tags": { + "existence": ["inheritdoc"] + } + }, + "properties": { + "privacies": ["public", "protected"], + "tags": { + "existence": ["inheritdoc"] + } + }, + "interfaces": { + "visibilities": ["exported"] + }, + "enum-members": { + "visibilities": ["exported"] + } + } + ], + "max-classes-per-file": [true, 1, "exclude-class-expressions"], + "trailing-comma": [ + true, + { + "multiline": "never", + "singleline": "never" + } + ], + "object-literal-sort-keys": false, + "no-void-expression": [true, "ignore-arrow-function-shorthand"], + "strict-boolean-expressions": [ + true, + "allow-undefined-union", + "allow-null-union", + "ignore-rhs", + "allow-string", + "allow-mix" + ], + "object-literal-shorthand": [true, "never"], + "arrow-parens": false, + "no-magic-numbers": false, + "no-unbound-method": false, + "no-parameter-properties": false, + "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"], + "no-submodule-imports": [true, "rxjs/testing", "rxjs/operators"], + "increment-decrement": false, + "no-use-before-declare": false, + "ordered-imports": [ + true, + { + "grouped-imports": false + } + ], + "no-null-undefined-union": false, + "strict-comparisons": false + } +} diff --git a/tslint.spec.json b/tslint.spec.json new file mode 100644 index 00000000..f2260972 --- /dev/null +++ b/tslint.spec.json @@ -0,0 +1,8 @@ +{ + "extends": "./tslint.json", + "rules": { + "completed-docs": false, + "max-file-line-count": false, + "no-unsafe-any": false + } +}