├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmignore ├── .npmrc ├── .nvmrc ├── .prettierrc ├── LICENSE ├── README.md ├── build.js ├── docs ├── lambda-analyzer.png ├── sample-screenshot.png └── sample-with-compare.png ├── package-lock.json ├── package.json ├── public ├── index.html ├── index.js ├── index.js.map ├── lib │ ├── Chart.min.js │ └── base64.js ├── style.css └── style.css.map ├── serve.js ├── src ├── app.js ├── encode.js ├── hashStore.js ├── index.js └── style.scss └── tailwind.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor Config - generated by Confit. This file will NOT be re-overwritten by Confit 2 | # Feel free to customise it further. 3 | # http://editorconfig.org 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Common folders to ignore 2 | node_modules/* 3 | 4 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2022, // Required to support top-level await in build script, and private class syntax (e.g. #foo() {}) 5 | sourceType: 'module', 6 | }, 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node:true, 11 | }, 12 | 13 | extends: [ 14 | 'eslint:recommended', 15 | 'plugin:prettier/recommended', 16 | ], 17 | rules: { 18 | 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | node_modules/ 3 | 4 | reports/ 5 | /.semverDetails 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/*.spec.js 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.19.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "tabWidth": 2, 6 | "semi": true 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Lambda Power Tuning UI 2 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/matteo-ronchetti/aws-lambda-power-tuning-ui/graphs/commit-activity) 3 | [![GitHub license](https://img.shields.io/github/license/matteo-ronchetti/aws-lambda-power-tuning-ui.svg)](https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui/blob/master/LICENSE) 4 | [![Open Source Love svg2](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/) 5 | 6 | This project provides a simple UI to visualize the results of [AWS Lambda Power Tuning](https://github.com/alexcasalboni/aws-lambda-power-tuning). 7 | The UI is a static HTML page that reads data from URL hash. 8 | 9 | ![Sample Screenshot](docs/sample-screenshot.png?raw=true) 10 | 11 | - [Basic Demo](https://lambda-power-tuning.show/#gAEAAgAEAAYACA==;Go9qRqDLVUbymEpGgC0jRn/iMkY=;41bGOHAK8Th8bWQ5mvyJObyvyTk=;) 12 | - [Comparison Demo](https://lambda-power-tuning.show/#gAEAAgAEAAYACA==;Go9qRqDLVUbymEpGgC0jRn/iMkY=;41bGOHAK8Th8bWQ5mvyJObyvyTk=;gAEAAgAEAAYACA==;0+hrRh7TR0YDoxBG/mQCRjclCkY=;sX2hOBZhtji8AgQ5ZIcyOV8vfDk=;JS%20x86;JS%20ARM64) 13 | 14 | ## Local building and execution 15 | First you need to clone the source and install the [bundler](https://gitlab.com/matteo-ronchetti/rosh-bundler) 16 | by running 17 | ```bash 18 | git clone https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui.git 19 | cd aws-lambda-power-tuning-ui 20 | npm install 21 | ``` 22 | Then run 23 | ```bash 24 | npm start 25 | ``` 26 | to build and serve at [localhost:3000](http://localhost:3000/). 27 | 28 | ## URL format 29 | The URL hash is formatted as `;;` 30 | where each parameter `` is a list encoded in base64 with proper data type 31 | (int16 for size, float32 for time and cost). 32 | 33 | This can be achieved using the `encode` function defined [here](https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui/blob/master/src/encode.js#L1): 34 | ```javascript 35 | let sizes = [128, 256, 512, 1024, 1536]; 36 | let times = [16.0, 8.0, 4.0, 2.8, 2.1]; 37 | let costs = [0.01, 0.008, 0.005, 0.009, 0.012]; 38 | 39 | window.location.hash = encode(sizes, Int16Array) + ";" + encode(times) + ";" + encode(costs) 40 | ``` 41 | 42 | # Contributing 43 | 44 | ```bash 45 | # Lint the code 46 | npm run lint 47 | 48 | # Build the website 49 | npm run build 50 | 51 | # Serve the built-website in a standalone server on port 4000 52 | npm run serve 53 | ``` 54 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import esbuild from 'esbuild'; 3 | import { sassPlugin } from 'esbuild-sass-plugin'; 4 | import postcss from 'postcss'; 5 | import autoprefixer from 'autoprefixer'; 6 | import tailwindcss from 'tailwindcss'; 7 | import tailwindConfig from './tailwind.config.js'; 8 | 9 | export const config = { 10 | entryPoints: ['src/index.js', 'src/style.scss'], 11 | platform: 'browser', 12 | bundle: true, 13 | minify: true, 14 | sourcemap: true, 15 | target: ['es2020'], 16 | outdir: 'public', 17 | plugins: [ 18 | sassPlugin({ 19 | async transform(source) { 20 | const { css } = await postcss(autoprefixer, tailwindcss(tailwindConfig)).process(source, { from: undefined }); 21 | return css; 22 | }, 23 | }), 24 | ], 25 | }; 26 | 27 | if (getLaunchFile() === 'build.js') { 28 | // I am the main launch file 29 | await build(); 30 | } 31 | 32 | function build() { 33 | return esbuild.build(config); 34 | } 35 | 36 | function getLaunchFile() { 37 | return path.basename(process.argv[1]); 38 | } 39 | -------------------------------------------------------------------------------- /docs/lambda-analyzer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matteo-ronchetti/aws-lambda-power-tuning-ui/7317e149edf173fba3c7d99aab6fd0aa49a899a5/docs/lambda-analyzer.png -------------------------------------------------------------------------------- /docs/sample-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matteo-ronchetti/aws-lambda-power-tuning-ui/7317e149edf173fba3c7d99aab6fd0aa49a899a5/docs/sample-screenshot.png -------------------------------------------------------------------------------- /docs/sample-with-compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matteo-ronchetti/aws-lambda-power-tuning-ui/7317e149edf173fba3c7d99aab6fd0aa49a899a5/docs/sample-with-compare.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-lambda-power-tuning-ui", 3 | "version": "0.0.0-development", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@eslint/eslintrc": { 8 | "version": "1.2.3", 9 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", 10 | "integrity": "sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==", 11 | "dev": true, 12 | "requires": { 13 | "ajv": "^6.12.4", 14 | "debug": "^4.3.2", 15 | "espree": "^9.3.2", 16 | "globals": "^13.9.0", 17 | "ignore": "^5.2.0", 18 | "import-fresh": "^3.2.1", 19 | "js-yaml": "^4.1.0", 20 | "minimatch": "^3.1.2", 21 | "strip-json-comments": "^3.1.1" 22 | } 23 | }, 24 | "@humanwhocodes/config-array": { 25 | "version": "0.9.5", 26 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", 27 | "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", 28 | "dev": true, 29 | "requires": { 30 | "@humanwhocodes/object-schema": "^1.2.1", 31 | "debug": "^4.1.1", 32 | "minimatch": "^3.0.4" 33 | } 34 | }, 35 | "@humanwhocodes/object-schema": { 36 | "version": "1.2.1", 37 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", 38 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", 39 | "dev": true 40 | }, 41 | "@nodelib/fs.scandir": { 42 | "version": "2.1.5", 43 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 44 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 45 | "dev": true, 46 | "requires": { 47 | "@nodelib/fs.stat": "2.0.5", 48 | "run-parallel": "^1.1.9" 49 | } 50 | }, 51 | "@nodelib/fs.stat": { 52 | "version": "2.0.5", 53 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 54 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 55 | "dev": true 56 | }, 57 | "@nodelib/fs.walk": { 58 | "version": "1.2.8", 59 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 60 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 61 | "dev": true, 62 | "requires": { 63 | "@nodelib/fs.scandir": "2.1.5", 64 | "fastq": "^1.6.0" 65 | } 66 | }, 67 | "@polka/url": { 68 | "version": "1.0.0-next.21", 69 | "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", 70 | "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", 71 | "dev": true 72 | }, 73 | "acorn": { 74 | "version": "8.7.1", 75 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 76 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 77 | "dev": true 78 | }, 79 | "acorn-jsx": { 80 | "version": "5.3.2", 81 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 82 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 83 | "dev": true 84 | }, 85 | "acorn-node": { 86 | "version": "1.8.2", 87 | "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", 88 | "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", 89 | "dev": true, 90 | "requires": { 91 | "acorn": "^7.0.0", 92 | "acorn-walk": "^7.0.0", 93 | "xtend": "^4.0.2" 94 | }, 95 | "dependencies": { 96 | "acorn": { 97 | "version": "7.4.1", 98 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 99 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 100 | "dev": true 101 | } 102 | } 103 | }, 104 | "acorn-walk": { 105 | "version": "7.2.0", 106 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", 107 | "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", 108 | "dev": true 109 | }, 110 | "ajv": { 111 | "version": "6.12.6", 112 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 113 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 114 | "dev": true, 115 | "requires": { 116 | "fast-deep-equal": "^3.1.1", 117 | "fast-json-stable-stringify": "^2.0.0", 118 | "json-schema-traverse": "^0.4.1", 119 | "uri-js": "^4.2.2" 120 | } 121 | }, 122 | "ansi-regex": { 123 | "version": "5.0.1", 124 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 125 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 126 | "dev": true 127 | }, 128 | "ansi-styles": { 129 | "version": "4.3.0", 130 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 131 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 132 | "dev": true, 133 | "requires": { 134 | "color-convert": "^2.0.1" 135 | } 136 | }, 137 | "anymatch": { 138 | "version": "3.1.2", 139 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 140 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 141 | "dev": true, 142 | "requires": { 143 | "normalize-path": "^3.0.0", 144 | "picomatch": "^2.0.4" 145 | } 146 | }, 147 | "arg": { 148 | "version": "5.0.1", 149 | "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", 150 | "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", 151 | "dev": true 152 | }, 153 | "argparse": { 154 | "version": "2.0.1", 155 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 156 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 157 | "dev": true 158 | }, 159 | "autoprefixer": { 160 | "version": "10.4.7", 161 | "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", 162 | "integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==", 163 | "dev": true, 164 | "requires": { 165 | "browserslist": "^4.20.3", 166 | "caniuse-lite": "^1.0.30001335", 167 | "fraction.js": "^4.2.0", 168 | "normalize-range": "^0.1.2", 169 | "picocolors": "^1.0.0", 170 | "postcss-value-parser": "^4.2.0" 171 | } 172 | }, 173 | "balanced-match": { 174 | "version": "1.0.2", 175 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 176 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 177 | "dev": true 178 | }, 179 | "binary-extensions": { 180 | "version": "2.2.0", 181 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 182 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 183 | "dev": true 184 | }, 185 | "brace-expansion": { 186 | "version": "1.1.11", 187 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 188 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 189 | "dev": true, 190 | "requires": { 191 | "balanced-match": "^1.0.0", 192 | "concat-map": "0.0.1" 193 | } 194 | }, 195 | "braces": { 196 | "version": "3.0.2", 197 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 198 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 199 | "dev": true, 200 | "requires": { 201 | "fill-range": "^7.0.1" 202 | } 203 | }, 204 | "browserslist": { 205 | "version": "4.20.3", 206 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", 207 | "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", 208 | "dev": true, 209 | "requires": { 210 | "caniuse-lite": "^1.0.30001332", 211 | "electron-to-chromium": "^1.4.118", 212 | "escalade": "^3.1.1", 213 | "node-releases": "^2.0.3", 214 | "picocolors": "^1.0.0" 215 | } 216 | }, 217 | "callsites": { 218 | "version": "3.1.0", 219 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 220 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 221 | "dev": true 222 | }, 223 | "camelcase-css": { 224 | "version": "2.0.1", 225 | "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", 226 | "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", 227 | "dev": true 228 | }, 229 | "caniuse-lite": { 230 | "version": "1.0.30001341", 231 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz", 232 | "integrity": "sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==", 233 | "dev": true 234 | }, 235 | "chalk": { 236 | "version": "4.1.2", 237 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 238 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 239 | "dev": true, 240 | "requires": { 241 | "ansi-styles": "^4.1.0", 242 | "supports-color": "^7.1.0" 243 | } 244 | }, 245 | "chokidar": { 246 | "version": "3.5.3", 247 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 248 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 249 | "dev": true, 250 | "requires": { 251 | "anymatch": "~3.1.2", 252 | "braces": "~3.0.2", 253 | "fsevents": "~2.3.2", 254 | "glob-parent": "~5.1.2", 255 | "is-binary-path": "~2.1.0", 256 | "is-glob": "~4.0.1", 257 | "normalize-path": "~3.0.0", 258 | "readdirp": "~3.6.0" 259 | } 260 | }, 261 | "color-convert": { 262 | "version": "2.0.1", 263 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 264 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 265 | "dev": true, 266 | "requires": { 267 | "color-name": "~1.1.4" 268 | } 269 | }, 270 | "color-name": { 271 | "version": "1.1.4", 272 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 273 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 274 | "dev": true 275 | }, 276 | "concat-map": { 277 | "version": "0.0.1", 278 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 279 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 280 | "dev": true 281 | }, 282 | "console-clear": { 283 | "version": "1.1.1", 284 | "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz", 285 | "integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==", 286 | "dev": true 287 | }, 288 | "cross-spawn": { 289 | "version": "7.0.3", 290 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 291 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 292 | "dev": true, 293 | "requires": { 294 | "path-key": "^3.1.0", 295 | "shebang-command": "^2.0.0", 296 | "which": "^2.0.1" 297 | } 298 | }, 299 | "cssesc": { 300 | "version": "3.0.0", 301 | "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 302 | "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", 303 | "dev": true 304 | }, 305 | "debug": { 306 | "version": "4.3.4", 307 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 308 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 309 | "dev": true, 310 | "requires": { 311 | "ms": "2.1.2" 312 | } 313 | }, 314 | "deep-is": { 315 | "version": "0.1.4", 316 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 317 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 318 | "dev": true 319 | }, 320 | "defined": { 321 | "version": "1.0.0", 322 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 323 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 324 | "dev": true 325 | }, 326 | "detective": { 327 | "version": "5.2.0", 328 | "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", 329 | "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", 330 | "dev": true, 331 | "requires": { 332 | "acorn-node": "^1.6.1", 333 | "defined": "^1.0.0", 334 | "minimist": "^1.1.1" 335 | } 336 | }, 337 | "didyoumean": { 338 | "version": "1.2.2", 339 | "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", 340 | "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", 341 | "dev": true 342 | }, 343 | "dlv": { 344 | "version": "1.1.3", 345 | "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", 346 | "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", 347 | "dev": true 348 | }, 349 | "doctrine": { 350 | "version": "3.0.0", 351 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 352 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 353 | "dev": true, 354 | "requires": { 355 | "esutils": "^2.0.2" 356 | } 357 | }, 358 | "electron-to-chromium": { 359 | "version": "1.4.137", 360 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", 361 | "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==", 362 | "dev": true 363 | }, 364 | "esbuild": { 365 | "version": "0.14.38", 366 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.38.tgz", 367 | "integrity": "sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==", 368 | "dev": true, 369 | "requires": { 370 | "esbuild-android-64": "0.14.38", 371 | "esbuild-android-arm64": "0.14.38", 372 | "esbuild-darwin-64": "0.14.38", 373 | "esbuild-darwin-arm64": "0.14.38", 374 | "esbuild-freebsd-64": "0.14.38", 375 | "esbuild-freebsd-arm64": "0.14.38", 376 | "esbuild-linux-32": "0.14.38", 377 | "esbuild-linux-64": "0.14.38", 378 | "esbuild-linux-arm": "0.14.38", 379 | "esbuild-linux-arm64": "0.14.38", 380 | "esbuild-linux-mips64le": "0.14.38", 381 | "esbuild-linux-ppc64le": "0.14.38", 382 | "esbuild-linux-riscv64": "0.14.38", 383 | "esbuild-linux-s390x": "0.14.38", 384 | "esbuild-netbsd-64": "0.14.38", 385 | "esbuild-openbsd-64": "0.14.38", 386 | "esbuild-sunos-64": "0.14.38", 387 | "esbuild-windows-32": "0.14.38", 388 | "esbuild-windows-64": "0.14.38", 389 | "esbuild-windows-arm64": "0.14.38" 390 | } 391 | }, 392 | "esbuild-android-64": { 393 | "version": "0.14.38", 394 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz", 395 | "integrity": "sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==", 396 | "dev": true, 397 | "optional": true 398 | }, 399 | "esbuild-android-arm64": { 400 | "version": "0.14.38", 401 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz", 402 | "integrity": "sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==", 403 | "dev": true, 404 | "optional": true 405 | }, 406 | "esbuild-darwin-64": { 407 | "version": "0.14.38", 408 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz", 409 | "integrity": "sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==", 410 | "dev": true, 411 | "optional": true 412 | }, 413 | "esbuild-darwin-arm64": { 414 | "version": "0.14.38", 415 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz", 416 | "integrity": "sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==", 417 | "dev": true, 418 | "optional": true 419 | }, 420 | "esbuild-freebsd-64": { 421 | "version": "0.14.38", 422 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz", 423 | "integrity": "sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==", 424 | "dev": true, 425 | "optional": true 426 | }, 427 | "esbuild-freebsd-arm64": { 428 | "version": "0.14.38", 429 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz", 430 | "integrity": "sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==", 431 | "dev": true, 432 | "optional": true 433 | }, 434 | "esbuild-linux-32": { 435 | "version": "0.14.38", 436 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz", 437 | "integrity": "sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==", 438 | "dev": true, 439 | "optional": true 440 | }, 441 | "esbuild-linux-64": { 442 | "version": "0.14.38", 443 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz", 444 | "integrity": "sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==", 445 | "dev": true, 446 | "optional": true 447 | }, 448 | "esbuild-linux-arm": { 449 | "version": "0.14.38", 450 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz", 451 | "integrity": "sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==", 452 | "dev": true, 453 | "optional": true 454 | }, 455 | "esbuild-linux-arm64": { 456 | "version": "0.14.38", 457 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz", 458 | "integrity": "sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==", 459 | "dev": true, 460 | "optional": true 461 | }, 462 | "esbuild-linux-mips64le": { 463 | "version": "0.14.38", 464 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz", 465 | "integrity": "sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==", 466 | "dev": true, 467 | "optional": true 468 | }, 469 | "esbuild-linux-ppc64le": { 470 | "version": "0.14.38", 471 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz", 472 | "integrity": "sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==", 473 | "dev": true, 474 | "optional": true 475 | }, 476 | "esbuild-linux-riscv64": { 477 | "version": "0.14.38", 478 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz", 479 | "integrity": "sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==", 480 | "dev": true, 481 | "optional": true 482 | }, 483 | "esbuild-linux-s390x": { 484 | "version": "0.14.38", 485 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz", 486 | "integrity": "sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==", 487 | "dev": true, 488 | "optional": true 489 | }, 490 | "esbuild-netbsd-64": { 491 | "version": "0.14.38", 492 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz", 493 | "integrity": "sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==", 494 | "dev": true, 495 | "optional": true 496 | }, 497 | "esbuild-openbsd-64": { 498 | "version": "0.14.38", 499 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz", 500 | "integrity": "sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==", 501 | "dev": true, 502 | "optional": true 503 | }, 504 | "esbuild-sass-plugin": { 505 | "version": "2.2.6", 506 | "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.2.6.tgz", 507 | "integrity": "sha512-WVREJhOS6UlZNoS2FhkOA5980VVKjS6ocUK7YFghJt/94rWDNXxPI+XfkOKlSMbJF/n5wAotr37P8/9KhgkgPQ==", 508 | "dev": true, 509 | "requires": { 510 | "esbuild": "^0.14.13", 511 | "sass": "^1.49.0" 512 | } 513 | }, 514 | "esbuild-sunos-64": { 515 | "version": "0.14.38", 516 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz", 517 | "integrity": "sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==", 518 | "dev": true, 519 | "optional": true 520 | }, 521 | "esbuild-windows-32": { 522 | "version": "0.14.38", 523 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz", 524 | "integrity": "sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==", 525 | "dev": true, 526 | "optional": true 527 | }, 528 | "esbuild-windows-64": { 529 | "version": "0.14.38", 530 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz", 531 | "integrity": "sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==", 532 | "dev": true, 533 | "optional": true 534 | }, 535 | "esbuild-windows-arm64": { 536 | "version": "0.14.38", 537 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz", 538 | "integrity": "sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==", 539 | "dev": true, 540 | "optional": true 541 | }, 542 | "escalade": { 543 | "version": "3.1.1", 544 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 545 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 546 | "dev": true 547 | }, 548 | "escape-string-regexp": { 549 | "version": "4.0.0", 550 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 551 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 552 | "dev": true 553 | }, 554 | "eslint": { 555 | "version": "8.15.0", 556 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.15.0.tgz", 557 | "integrity": "sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==", 558 | "dev": true, 559 | "requires": { 560 | "@eslint/eslintrc": "^1.2.3", 561 | "@humanwhocodes/config-array": "^0.9.2", 562 | "ajv": "^6.10.0", 563 | "chalk": "^4.0.0", 564 | "cross-spawn": "^7.0.2", 565 | "debug": "^4.3.2", 566 | "doctrine": "^3.0.0", 567 | "escape-string-regexp": "^4.0.0", 568 | "eslint-scope": "^7.1.1", 569 | "eslint-utils": "^3.0.0", 570 | "eslint-visitor-keys": "^3.3.0", 571 | "espree": "^9.3.2", 572 | "esquery": "^1.4.0", 573 | "esutils": "^2.0.2", 574 | "fast-deep-equal": "^3.1.3", 575 | "file-entry-cache": "^6.0.1", 576 | "functional-red-black-tree": "^1.0.1", 577 | "glob-parent": "^6.0.1", 578 | "globals": "^13.6.0", 579 | "ignore": "^5.2.0", 580 | "import-fresh": "^3.0.0", 581 | "imurmurhash": "^0.1.4", 582 | "is-glob": "^4.0.0", 583 | "js-yaml": "^4.1.0", 584 | "json-stable-stringify-without-jsonify": "^1.0.1", 585 | "levn": "^0.4.1", 586 | "lodash.merge": "^4.6.2", 587 | "minimatch": "^3.1.2", 588 | "natural-compare": "^1.4.0", 589 | "optionator": "^0.9.1", 590 | "regexpp": "^3.2.0", 591 | "strip-ansi": "^6.0.1", 592 | "strip-json-comments": "^3.1.0", 593 | "text-table": "^0.2.0", 594 | "v8-compile-cache": "^2.0.3" 595 | }, 596 | "dependencies": { 597 | "glob-parent": { 598 | "version": "6.0.2", 599 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 600 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 601 | "dev": true, 602 | "requires": { 603 | "is-glob": "^4.0.3" 604 | } 605 | } 606 | } 607 | }, 608 | "eslint-config-prettier": { 609 | "version": "8.5.0", 610 | "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", 611 | "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", 612 | "dev": true 613 | }, 614 | "eslint-plugin-prettier": { 615 | "version": "4.0.0", 616 | "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", 617 | "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", 618 | "dev": true, 619 | "requires": { 620 | "prettier-linter-helpers": "^1.0.0" 621 | } 622 | }, 623 | "eslint-scope": { 624 | "version": "7.1.1", 625 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", 626 | "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", 627 | "dev": true, 628 | "requires": { 629 | "esrecurse": "^4.3.0", 630 | "estraverse": "^5.2.0" 631 | } 632 | }, 633 | "eslint-utils": { 634 | "version": "3.0.0", 635 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", 636 | "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", 637 | "dev": true, 638 | "requires": { 639 | "eslint-visitor-keys": "^2.0.0" 640 | }, 641 | "dependencies": { 642 | "eslint-visitor-keys": { 643 | "version": "2.1.0", 644 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", 645 | "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", 646 | "dev": true 647 | } 648 | } 649 | }, 650 | "eslint-visitor-keys": { 651 | "version": "3.3.0", 652 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", 653 | "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", 654 | "dev": true 655 | }, 656 | "espree": { 657 | "version": "9.3.2", 658 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", 659 | "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", 660 | "dev": true, 661 | "requires": { 662 | "acorn": "^8.7.1", 663 | "acorn-jsx": "^5.3.2", 664 | "eslint-visitor-keys": "^3.3.0" 665 | } 666 | }, 667 | "esquery": { 668 | "version": "1.4.0", 669 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", 670 | "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", 671 | "dev": true, 672 | "requires": { 673 | "estraverse": "^5.1.0" 674 | } 675 | }, 676 | "esrecurse": { 677 | "version": "4.3.0", 678 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 679 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 680 | "dev": true, 681 | "requires": { 682 | "estraverse": "^5.2.0" 683 | } 684 | }, 685 | "estraverse": { 686 | "version": "5.3.0", 687 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 688 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 689 | "dev": true 690 | }, 691 | "esutils": { 692 | "version": "2.0.3", 693 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 694 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 695 | "dev": true 696 | }, 697 | "fast-deep-equal": { 698 | "version": "3.1.3", 699 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 700 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 701 | "dev": true 702 | }, 703 | "fast-diff": { 704 | "version": "1.2.0", 705 | "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", 706 | "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", 707 | "dev": true 708 | }, 709 | "fast-glob": { 710 | "version": "3.2.11", 711 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", 712 | "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", 713 | "dev": true, 714 | "requires": { 715 | "@nodelib/fs.stat": "^2.0.2", 716 | "@nodelib/fs.walk": "^1.2.3", 717 | "glob-parent": "^5.1.2", 718 | "merge2": "^1.3.0", 719 | "micromatch": "^4.0.4" 720 | } 721 | }, 722 | "fast-json-stable-stringify": { 723 | "version": "2.1.0", 724 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 725 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 726 | "dev": true 727 | }, 728 | "fast-levenshtein": { 729 | "version": "2.0.6", 730 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 731 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 732 | "dev": true 733 | }, 734 | "fastq": { 735 | "version": "1.13.0", 736 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", 737 | "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", 738 | "dev": true, 739 | "requires": { 740 | "reusify": "^1.0.4" 741 | } 742 | }, 743 | "file-entry-cache": { 744 | "version": "6.0.1", 745 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 746 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 747 | "dev": true, 748 | "requires": { 749 | "flat-cache": "^3.0.4" 750 | } 751 | }, 752 | "fill-range": { 753 | "version": "7.0.1", 754 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 755 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 756 | "dev": true, 757 | "requires": { 758 | "to-regex-range": "^5.0.1" 759 | } 760 | }, 761 | "flat-cache": { 762 | "version": "3.0.4", 763 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 764 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 765 | "dev": true, 766 | "requires": { 767 | "flatted": "^3.1.0", 768 | "rimraf": "^3.0.2" 769 | } 770 | }, 771 | "flatted": { 772 | "version": "3.2.5", 773 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", 774 | "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", 775 | "dev": true 776 | }, 777 | "fraction.js": { 778 | "version": "4.2.0", 779 | "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", 780 | "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", 781 | "dev": true 782 | }, 783 | "fs.realpath": { 784 | "version": "1.0.0", 785 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 786 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 787 | "dev": true 788 | }, 789 | "fsevents": { 790 | "version": "2.3.2", 791 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 792 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 793 | "dev": true, 794 | "optional": true 795 | }, 796 | "function-bind": { 797 | "version": "1.1.1", 798 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 799 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 800 | "dev": true 801 | }, 802 | "functional-red-black-tree": { 803 | "version": "1.0.1", 804 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 805 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 806 | "dev": true 807 | }, 808 | "get-port": { 809 | "version": "3.2.0", 810 | "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", 811 | "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", 812 | "dev": true 813 | }, 814 | "glob": { 815 | "version": "7.2.2", 816 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.2.tgz", 817 | "integrity": "sha512-NzDgHDiJwKYByLrL5lONmQFpK/2G78SMMfo+E9CuGlX4IkvfKDsiQSNPwAYxEy+e6p7ZQ3uslSLlwlJcqezBmQ==", 818 | "dev": true, 819 | "requires": { 820 | "fs.realpath": "^1.0.0", 821 | "inflight": "^1.0.4", 822 | "inherits": "2", 823 | "minimatch": "^3.1.1", 824 | "once": "^1.3.0", 825 | "path-is-absolute": "^1.0.0" 826 | } 827 | }, 828 | "glob-parent": { 829 | "version": "5.1.2", 830 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 831 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 832 | "dev": true, 833 | "requires": { 834 | "is-glob": "^4.0.1" 835 | } 836 | }, 837 | "globals": { 838 | "version": "13.15.0", 839 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", 840 | "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", 841 | "dev": true, 842 | "requires": { 843 | "type-fest": "^0.20.2" 844 | } 845 | }, 846 | "has": { 847 | "version": "1.0.3", 848 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 849 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 850 | "dev": true, 851 | "requires": { 852 | "function-bind": "^1.1.1" 853 | } 854 | }, 855 | "has-flag": { 856 | "version": "4.0.0", 857 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 858 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 859 | "dev": true 860 | }, 861 | "ignore": { 862 | "version": "5.2.0", 863 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", 864 | "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", 865 | "dev": true 866 | }, 867 | "immutable": { 868 | "version": "4.0.0", 869 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", 870 | "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", 871 | "dev": true 872 | }, 873 | "import-fresh": { 874 | "version": "3.3.0", 875 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 876 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 877 | "dev": true, 878 | "requires": { 879 | "parent-module": "^1.0.0", 880 | "resolve-from": "^4.0.0" 881 | } 882 | }, 883 | "imurmurhash": { 884 | "version": "0.1.4", 885 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 886 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 887 | "dev": true 888 | }, 889 | "inflight": { 890 | "version": "1.0.6", 891 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 892 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 893 | "dev": true, 894 | "requires": { 895 | "once": "^1.3.0", 896 | "wrappy": "1" 897 | } 898 | }, 899 | "inherits": { 900 | "version": "2.0.4", 901 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 902 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 903 | "dev": true 904 | }, 905 | "is-binary-path": { 906 | "version": "2.1.0", 907 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 908 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 909 | "dev": true, 910 | "requires": { 911 | "binary-extensions": "^2.0.0" 912 | } 913 | }, 914 | "is-core-module": { 915 | "version": "2.9.0", 916 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", 917 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", 918 | "dev": true, 919 | "requires": { 920 | "has": "^1.0.3" 921 | } 922 | }, 923 | "is-extglob": { 924 | "version": "2.1.1", 925 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 926 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 927 | "dev": true 928 | }, 929 | "is-glob": { 930 | "version": "4.0.3", 931 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 932 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 933 | "dev": true, 934 | "requires": { 935 | "is-extglob": "^2.1.1" 936 | } 937 | }, 938 | "is-number": { 939 | "version": "7.0.0", 940 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 941 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 942 | "dev": true 943 | }, 944 | "isexe": { 945 | "version": "2.0.0", 946 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 947 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 948 | "dev": true 949 | }, 950 | "js-yaml": { 951 | "version": "4.1.0", 952 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 953 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 954 | "dev": true, 955 | "requires": { 956 | "argparse": "^2.0.1" 957 | } 958 | }, 959 | "json-schema-traverse": { 960 | "version": "0.4.1", 961 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 962 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 963 | "dev": true 964 | }, 965 | "json-stable-stringify-without-jsonify": { 966 | "version": "1.0.1", 967 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 968 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 969 | "dev": true 970 | }, 971 | "kleur": { 972 | "version": "4.1.4", 973 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", 974 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", 975 | "dev": true 976 | }, 977 | "levn": { 978 | "version": "0.4.1", 979 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 980 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 981 | "dev": true, 982 | "requires": { 983 | "prelude-ls": "^1.2.1", 984 | "type-check": "~0.4.0" 985 | } 986 | }, 987 | "lilconfig": { 988 | "version": "2.0.5", 989 | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", 990 | "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==", 991 | "dev": true 992 | }, 993 | "local-access": { 994 | "version": "1.1.0", 995 | "resolved": "https://registry.npmjs.org/local-access/-/local-access-1.1.0.tgz", 996 | "integrity": "sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw==", 997 | "dev": true 998 | }, 999 | "lodash.merge": { 1000 | "version": "4.6.2", 1001 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1002 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1003 | "dev": true 1004 | }, 1005 | "merge2": { 1006 | "version": "1.4.1", 1007 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1008 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1009 | "dev": true 1010 | }, 1011 | "micromatch": { 1012 | "version": "4.0.5", 1013 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 1014 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 1015 | "dev": true, 1016 | "requires": { 1017 | "braces": "^3.0.2", 1018 | "picomatch": "^2.3.1" 1019 | } 1020 | }, 1021 | "minimatch": { 1022 | "version": "3.1.2", 1023 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1024 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1025 | "dev": true, 1026 | "requires": { 1027 | "brace-expansion": "^1.1.7" 1028 | } 1029 | }, 1030 | "minimist": { 1031 | "version": "1.2.6", 1032 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 1033 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", 1034 | "dev": true 1035 | }, 1036 | "mri": { 1037 | "version": "1.2.0", 1038 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", 1039 | "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", 1040 | "dev": true 1041 | }, 1042 | "mrmime": { 1043 | "version": "1.0.0", 1044 | "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", 1045 | "integrity": "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==", 1046 | "dev": true 1047 | }, 1048 | "ms": { 1049 | "version": "2.1.2", 1050 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1051 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1052 | "dev": true 1053 | }, 1054 | "nanoid": { 1055 | "version": "3.3.4", 1056 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 1057 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", 1058 | "dev": true 1059 | }, 1060 | "natural-compare": { 1061 | "version": "1.4.0", 1062 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1063 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1064 | "dev": true 1065 | }, 1066 | "node-releases": { 1067 | "version": "2.0.4", 1068 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", 1069 | "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", 1070 | "dev": true 1071 | }, 1072 | "normalize-path": { 1073 | "version": "3.0.0", 1074 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1075 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1076 | "dev": true 1077 | }, 1078 | "normalize-range": { 1079 | "version": "0.1.2", 1080 | "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", 1081 | "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", 1082 | "dev": true 1083 | }, 1084 | "object-hash": { 1085 | "version": "3.0.0", 1086 | "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", 1087 | "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", 1088 | "dev": true 1089 | }, 1090 | "once": { 1091 | "version": "1.4.0", 1092 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1093 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1094 | "dev": true, 1095 | "requires": { 1096 | "wrappy": "1" 1097 | } 1098 | }, 1099 | "optionator": { 1100 | "version": "0.9.1", 1101 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 1102 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 1103 | "dev": true, 1104 | "requires": { 1105 | "deep-is": "^0.1.3", 1106 | "fast-levenshtein": "^2.0.6", 1107 | "levn": "^0.4.1", 1108 | "prelude-ls": "^1.2.1", 1109 | "type-check": "^0.4.0", 1110 | "word-wrap": "^1.2.3" 1111 | } 1112 | }, 1113 | "parent-module": { 1114 | "version": "1.0.1", 1115 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1116 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1117 | "dev": true, 1118 | "requires": { 1119 | "callsites": "^3.0.0" 1120 | } 1121 | }, 1122 | "path-is-absolute": { 1123 | "version": "1.0.1", 1124 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1125 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1126 | "dev": true 1127 | }, 1128 | "path-key": { 1129 | "version": "3.1.1", 1130 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1131 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1132 | "dev": true 1133 | }, 1134 | "path-parse": { 1135 | "version": "1.0.7", 1136 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1137 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1138 | "dev": true 1139 | }, 1140 | "picocolors": { 1141 | "version": "1.0.0", 1142 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1143 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1144 | "dev": true 1145 | }, 1146 | "picomatch": { 1147 | "version": "2.3.1", 1148 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1149 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1150 | "dev": true 1151 | }, 1152 | "postcss": { 1153 | "version": "8.4.13", 1154 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz", 1155 | "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", 1156 | "dev": true, 1157 | "requires": { 1158 | "nanoid": "^3.3.3", 1159 | "picocolors": "^1.0.0", 1160 | "source-map-js": "^1.0.2" 1161 | } 1162 | }, 1163 | "postcss-js": { 1164 | "version": "4.0.0", 1165 | "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz", 1166 | "integrity": "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==", 1167 | "dev": true, 1168 | "requires": { 1169 | "camelcase-css": "^2.0.1" 1170 | } 1171 | }, 1172 | "postcss-load-config": { 1173 | "version": "3.1.4", 1174 | "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", 1175 | "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", 1176 | "dev": true, 1177 | "requires": { 1178 | "lilconfig": "^2.0.5", 1179 | "yaml": "^1.10.2" 1180 | } 1181 | }, 1182 | "postcss-nested": { 1183 | "version": "5.0.6", 1184 | "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", 1185 | "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", 1186 | "dev": true, 1187 | "requires": { 1188 | "postcss-selector-parser": "^6.0.6" 1189 | } 1190 | }, 1191 | "postcss-selector-parser": { 1192 | "version": "6.0.10", 1193 | "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", 1194 | "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", 1195 | "dev": true, 1196 | "requires": { 1197 | "cssesc": "^3.0.0", 1198 | "util-deprecate": "^1.0.2" 1199 | } 1200 | }, 1201 | "postcss-value-parser": { 1202 | "version": "4.2.0", 1203 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 1204 | "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 1205 | "dev": true 1206 | }, 1207 | "prelude-ls": { 1208 | "version": "1.2.1", 1209 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1210 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1211 | "dev": true 1212 | }, 1213 | "prettier": { 1214 | "version": "2.6.2", 1215 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", 1216 | "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", 1217 | "dev": true 1218 | }, 1219 | "prettier-linter-helpers": { 1220 | "version": "1.0.0", 1221 | "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", 1222 | "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", 1223 | "dev": true, 1224 | "requires": { 1225 | "fast-diff": "^1.1.2" 1226 | } 1227 | }, 1228 | "punycode": { 1229 | "version": "2.1.1", 1230 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1231 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1232 | "dev": true 1233 | }, 1234 | "queue-microtask": { 1235 | "version": "1.2.3", 1236 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1237 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1238 | "dev": true 1239 | }, 1240 | "quick-lru": { 1241 | "version": "5.1.1", 1242 | "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", 1243 | "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", 1244 | "dev": true 1245 | }, 1246 | "readdirp": { 1247 | "version": "3.6.0", 1248 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1249 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1250 | "dev": true, 1251 | "requires": { 1252 | "picomatch": "^2.2.1" 1253 | } 1254 | }, 1255 | "regexpp": { 1256 | "version": "3.2.0", 1257 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", 1258 | "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", 1259 | "dev": true 1260 | }, 1261 | "resolve": { 1262 | "version": "1.22.0", 1263 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", 1264 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", 1265 | "dev": true, 1266 | "requires": { 1267 | "is-core-module": "^2.8.1", 1268 | "path-parse": "^1.0.7", 1269 | "supports-preserve-symlinks-flag": "^1.0.0" 1270 | } 1271 | }, 1272 | "resolve-from": { 1273 | "version": "4.0.0", 1274 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1275 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1276 | "dev": true 1277 | }, 1278 | "reusify": { 1279 | "version": "1.0.4", 1280 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1281 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1282 | "dev": true 1283 | }, 1284 | "rimraf": { 1285 | "version": "3.0.2", 1286 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1287 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1288 | "dev": true, 1289 | "requires": { 1290 | "glob": "^7.1.3" 1291 | } 1292 | }, 1293 | "run-parallel": { 1294 | "version": "1.2.0", 1295 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1296 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1297 | "dev": true, 1298 | "requires": { 1299 | "queue-microtask": "^1.2.2" 1300 | } 1301 | }, 1302 | "sade": { 1303 | "version": "1.8.1", 1304 | "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", 1305 | "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", 1306 | "dev": true, 1307 | "requires": { 1308 | "mri": "^1.1.0" 1309 | } 1310 | }, 1311 | "sass": { 1312 | "version": "1.51.0", 1313 | "resolved": "https://registry.npmjs.org/sass/-/sass-1.51.0.tgz", 1314 | "integrity": "sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==", 1315 | "dev": true, 1316 | "requires": { 1317 | "chokidar": ">=3.0.0 <4.0.0", 1318 | "immutable": "^4.0.0", 1319 | "source-map-js": ">=0.6.2 <2.0.0" 1320 | } 1321 | }, 1322 | "semiver": { 1323 | "version": "1.1.0", 1324 | "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", 1325 | "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", 1326 | "dev": true 1327 | }, 1328 | "shebang-command": { 1329 | "version": "2.0.0", 1330 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1331 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1332 | "dev": true, 1333 | "requires": { 1334 | "shebang-regex": "^3.0.0" 1335 | } 1336 | }, 1337 | "shebang-regex": { 1338 | "version": "3.0.0", 1339 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1340 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1341 | "dev": true 1342 | }, 1343 | "sirv": { 1344 | "version": "2.0.2", 1345 | "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.2.tgz", 1346 | "integrity": "sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==", 1347 | "dev": true, 1348 | "requires": { 1349 | "@polka/url": "^1.0.0-next.20", 1350 | "mrmime": "^1.0.0", 1351 | "totalist": "^3.0.0" 1352 | } 1353 | }, 1354 | "sirv-cli": { 1355 | "version": "2.0.2", 1356 | "resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-2.0.2.tgz", 1357 | "integrity": "sha512-OtSJDwxsF1NWHc7ps3Sa0s+dPtP15iQNJzfKVz+MxkEo3z72mCD+yu30ct79rPr0CaV1HXSOBp+MIY5uIhHZ1A==", 1358 | "dev": true, 1359 | "requires": { 1360 | "console-clear": "^1.1.0", 1361 | "get-port": "^3.2.0", 1362 | "kleur": "^4.1.4", 1363 | "local-access": "^1.0.1", 1364 | "sade": "^1.6.0", 1365 | "semiver": "^1.0.0", 1366 | "sirv": "^2.0.0", 1367 | "tinydate": "^1.0.0" 1368 | } 1369 | }, 1370 | "source-map-js": { 1371 | "version": "1.0.2", 1372 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1373 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 1374 | "dev": true 1375 | }, 1376 | "strip-ansi": { 1377 | "version": "6.0.1", 1378 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1379 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1380 | "dev": true, 1381 | "requires": { 1382 | "ansi-regex": "^5.0.1" 1383 | } 1384 | }, 1385 | "strip-json-comments": { 1386 | "version": "3.1.1", 1387 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1388 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1389 | "dev": true 1390 | }, 1391 | "supports-color": { 1392 | "version": "7.2.0", 1393 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1394 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1395 | "dev": true, 1396 | "requires": { 1397 | "has-flag": "^4.0.0" 1398 | } 1399 | }, 1400 | "supports-preserve-symlinks-flag": { 1401 | "version": "1.0.0", 1402 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1403 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1404 | "dev": true 1405 | }, 1406 | "tailwindcss": { 1407 | "version": "3.0.24", 1408 | "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.24.tgz", 1409 | "integrity": "sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig==", 1410 | "dev": true, 1411 | "requires": { 1412 | "arg": "^5.0.1", 1413 | "chokidar": "^3.5.3", 1414 | "color-name": "^1.1.4", 1415 | "detective": "^5.2.0", 1416 | "didyoumean": "^1.2.2", 1417 | "dlv": "^1.1.3", 1418 | "fast-glob": "^3.2.11", 1419 | "glob-parent": "^6.0.2", 1420 | "is-glob": "^4.0.3", 1421 | "lilconfig": "^2.0.5", 1422 | "normalize-path": "^3.0.0", 1423 | "object-hash": "^3.0.0", 1424 | "picocolors": "^1.0.0", 1425 | "postcss": "^8.4.12", 1426 | "postcss-js": "^4.0.0", 1427 | "postcss-load-config": "^3.1.4", 1428 | "postcss-nested": "5.0.6", 1429 | "postcss-selector-parser": "^6.0.10", 1430 | "postcss-value-parser": "^4.2.0", 1431 | "quick-lru": "^5.1.1", 1432 | "resolve": "^1.22.0" 1433 | }, 1434 | "dependencies": { 1435 | "glob-parent": { 1436 | "version": "6.0.2", 1437 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1438 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1439 | "dev": true, 1440 | "requires": { 1441 | "is-glob": "^4.0.3" 1442 | } 1443 | } 1444 | } 1445 | }, 1446 | "text-table": { 1447 | "version": "0.2.0", 1448 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1449 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1450 | "dev": true 1451 | }, 1452 | "tinydate": { 1453 | "version": "1.3.0", 1454 | "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz", 1455 | "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==", 1456 | "dev": true 1457 | }, 1458 | "to-regex-range": { 1459 | "version": "5.0.1", 1460 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1461 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1462 | "dev": true, 1463 | "requires": { 1464 | "is-number": "^7.0.0" 1465 | } 1466 | }, 1467 | "totalist": { 1468 | "version": "3.0.0", 1469 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz", 1470 | "integrity": "sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==", 1471 | "dev": true 1472 | }, 1473 | "type-check": { 1474 | "version": "0.4.0", 1475 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1476 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1477 | "dev": true, 1478 | "requires": { 1479 | "prelude-ls": "^1.2.1" 1480 | } 1481 | }, 1482 | "type-fest": { 1483 | "version": "0.20.2", 1484 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 1485 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 1486 | "dev": true 1487 | }, 1488 | "uri-js": { 1489 | "version": "4.4.1", 1490 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1491 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1492 | "dev": true, 1493 | "requires": { 1494 | "punycode": "^2.1.0" 1495 | } 1496 | }, 1497 | "util-deprecate": { 1498 | "version": "1.0.2", 1499 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1500 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 1501 | "dev": true 1502 | }, 1503 | "v8-compile-cache": { 1504 | "version": "2.3.0", 1505 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", 1506 | "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", 1507 | "dev": true 1508 | }, 1509 | "which": { 1510 | "version": "2.0.2", 1511 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1512 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1513 | "dev": true, 1514 | "requires": { 1515 | "isexe": "^2.0.0" 1516 | } 1517 | }, 1518 | "word-wrap": { 1519 | "version": "1.2.3", 1520 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1521 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1522 | "dev": true 1523 | }, 1524 | "wrappy": { 1525 | "version": "1.0.2", 1526 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1527 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1528 | "dev": true 1529 | }, 1530 | "xtend": { 1531 | "version": "4.0.2", 1532 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1533 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 1534 | "dev": true 1535 | }, 1536 | "yaml": { 1537 | "version": "1.10.2", 1538 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 1539 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", 1540 | "dev": true 1541 | } 1542 | } 1543 | } 1544 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-lambda-power-tuning-ui", 3 | "version": "0.0.0-development", 4 | "description": "aws-lambda-power-tuning-ui", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui.git" 9 | }, 10 | "keywords": [ 11 | "aws", 12 | "lambda", 13 | "tuning" 14 | ], 15 | "license": "Apache-2.0", 16 | "bugs": { 17 | "url": "https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui/issues" 18 | }, 19 | "homepage": "https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui#readme", 20 | "scripts": { 21 | "build": "node build.js", 22 | "start": "node serve.js", 23 | "test": "echo \"Error: no test specified\" && exit 1", 24 | "lint": "eslint --fix src/**/*.js *.js", 25 | "verify": "eslint --max-warnings=0 src/**/*.js *.js", 26 | "serve": "sirv public --port 4000" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "10.4.7", 30 | "esbuild": "0.14.38", 31 | "esbuild-sass-plugin": "2.2.6", 32 | "eslint": "8.15.0", 33 | "eslint-config-prettier": "8.5.0", 34 | "eslint-plugin-prettier": "4.0.0", 35 | "postcss": "8.4.13", 36 | "prettier": "2.6.2", 37 | "sirv-cli": "2.0.2", 38 | "tailwindcss": "3.0.24" 39 | }, 40 | "engines": { 41 | "node": ">=14.13.1 || >16" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
AWS Lambda Power Tuning Results
10 |
11 | 19 |
20 |
21 |
22 | 23 |
Shown results are averages over multiple runs
24 |
25 |
26 |
27 |

Best Cost

28 |
XXXMB
29 |
30 |
31 |

Worst Cost

32 |
XXXMB
33 |
34 | 38 |
39 | 42 |
43 |
44 |
45 |
46 |

Best Time

47 |
XXXMB
48 |
49 |
50 |

Worst Time

51 |
XXXMB
52 |
53 |
54 |
55 |
56 |

Size: 1024

57 |
Time: 0ms
58 |
Cost: 0.000$
59 |
60 |
Time: 0ms
61 |
Cost: 0.000$
62 |
63 | 135 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | (()=>{var P=(t,e,s)=>{if(!e.has(t))throw TypeError("Cannot "+s)};var R=(t,e,s)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,s)};var $=(t,e,s)=>(P(t,e,"access private method"),s);function L(t,e=Float32Array){return t=new e(t),t instanceof Uint8Array||(t=new Uint8Array(t.buffer)),window.base64js.fromByteArray(t)}function b(t,e=Float32Array){return Array.from(new e(window.base64js.toByteArray(t).buffer))}var H="https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui",m=";",F=3,M=8,c={red:"rgb(255, 99, 132)",blue:"rgb(54, 162, 235)",orange:"rgb(255, 165, 00)",green:"rgb(34, 139, 34)"},S={responsive:!0,hoverMode:"index",tooltips:{mode:"index",intersect:!1,enabled:!1,custom:D},stacked:!1,title:{display:!1},scales:{xAxes:[{scaleLabel:{display:!0,labelString:"Memory / Power (MB)"}}],yAxes:[{ticks:{beginAtZero:!0,callback:t=>`${d(t)} ms`},type:"linear",display:!0,scaleLabel:{display:!0,labelString:"Invocation Time"},position:"left",id:"time-axis"},{ticks:{beginAtZero:!0,callback:t=>`${d(t)} $`},type:"linear",display:!0,position:"right",id:"cost-axis",scaleLabel:{display:!0,labelString:"Invocation Cost"},gridLines:{drawOnChartArea:!1}}]}},w=class{constructor(e){this.store=e,this.canvas=document.querySelector("canvas"),this.reportElem=document.getElementById("report"),this.errorElem=document.getElementById("error"),this.data1=null,this.data1URL="",this.data2=null,this.data2URL="",this.legend1="",this.legend2="",this.store.addListener(this.show.bind(this),this.error.bind(this))}setData1(e){this.data1=T(e),this.data1URL=x(e.join(m))}setData2(e){this.data2=T(e),this.data2URL=x(e.join(m))}error(e){switch(this.reportElem.style.display="none",this.errorElem.style.display="flex",e){case"empty":let s=x(L([128,256,512,1024],Int16Array)+m+L([36555.39,18441.58,8539.34,3870.76])+m+L([7612e-8,7696e-8,7155e-8,6489e-8]));document.getElementById("error-title").innerHTML="Error: URL must contain data in the hash!",document.getElementById("error-msg").innerHTML=`Your URL is ${window.location} and should be something like ${s}
Please refer to the documentation.`;break;case"malformed":document.getElementById("error-title").innerHTML="Error: malformed URL parameters!",document.getElementById("error-msg").innerHTML=`Please check your URL parameters, refer to the documentation.`;break;default:document.getElementById("error-title").innerHTML="Error",document.getElementById("error-msg").innerHTML="Please check your URL parameters"}}show(e){let s=e.split(m),i=s.length;this.legend1="",this.legend2="",i!==F&&i!==M&&this.error("malformed");try{this.setData1(s.slice(0,3))}catch(n){console.error(n),this.error("malformed");return}if(i===M){try{this.setData2(s.slice(3,6))}catch(n){console.error(n),this.error("malformed");return}this.legend1=decodeURIComponent(s[6]),this.legend2=decodeURIComponent(s[7])}else this.data2=null,this.data2URL="",this.legend1="",this.legend2="";this.reportElem.style.display="flex",this.errorElem.style.display="none",this.showCards(this.data1);let a=this.data1.powers;if(this.data2){a=this.data1.powers.concat(this.data2.powers),a=a.filter((n,l)=>a.indexOf(n)===l),a.sort((n,l)=>n-l);for(let[n,l]of a.entries())this.data1.powers.includes(l)||(this.data1.times.splice(n,0,null),this.data1.costs.splice(n,0,null)),this.data2.powers.includes(l)||(this.data2.times.splice(n,0,null),this.data2.costs.splice(n,0,null))}let o={labels:a,datasets:[{label:`Invocation Time ${this.legend1?this.legend1+" ":""}(ms)`,borderColor:c.red,backgroundColor:c.red,fill:!1,data:this.data1.times,yAxisID:"time-axis"},{label:`Invocation Cost ${this.legend1?this.legend1+" ":""}(USD)`,borderColor:c.blue,backgroundColor:c.blue,fill:!1,data:this.data1.costs,yAxisID:"cost-axis"}]};this.data2&&o.datasets.push({label:`Invocation Time ${this.legend2?this.legend2+" ":""}(ms)`,borderColor:c.orange,backgroundColor:c.orange,fill:!1,data:this.data2.times,yAxisID:"time-axis"},{label:`Invocation Cost ${this.legend2?this.legend2+" ":""}(USD)`,borderColor:c.green,backgroundColor:c.green,fill:!1,data:this.data2.costs,yAxisID:"cost-axis"}),this.myLine&&this.myLine.destroy();let r=this.canvas.getContext("2d");this.myLine=window.Chart.Line(r,{data:o,options:S})}showCards(e){let i=Math.max(...e.costs),a=Math.max(...e.times),o=e.powers.map((r,n)=>({size:r,time:e.times[n],cost:e.costs[n],value:.5*e.costs[n]/i+(1-.5)*e.times[n]/a}));o.sort((r,n)=>r.time-n.time),document.getElementById("min-time").innerHTML=`${o[0].size}MB`,document.getElementById("max-time").innerHTML=`${o[o.length-1].size}MB`,o.sort((r,n)=>r.cost-n.cost),document.getElementById("min-cost").innerHTML=`${o[0].size}MB`,document.getElementById("max-cost").innerHTML=`${o[o.length-1].size}MB`,o.sort((r,n)=>r.value-n.value),document.getElementById("balanced").innerHTML=`${o[0].size}MB`}compare(e,s,i,a){this.store.setState([e,s,encodeURIComponent(i),encodeURIComponent(a)].join(m))}};function T(t){let e=b(t[0],Int16Array),s=b(t[1]),i=b(t[2]);return{powers:e,times:s,costs:i}}function x(t){return`${window.location.origin}${window.location.pathname}#${t}`}function D(t){let e=document.getElementById("tooltip"),s=document.getElementById("tooltip-power"),i=document.getElementById("tooltip-time-1"),a=document.getElementById("tooltip-cost-1"),o=document.getElementById("tooltip-time-2"),r=document.getElementById("tooltip-cost-2"),n=document.getElementById("tooltip-separator");if(t.opacity===0){e.style.display="none";return}let l=t.dataPoints[0].label,p=d(parseFloat(t.dataPoints[0].value)),h=d(parseFloat(t.dataPoints[1].value),2);if(s.innerHTML=`Power: ${l}`,i.innerHTML=`Time: ${p}ms`,a.innerHTML=`Cost: ${h}$`,t.dataPoints[2]&&t.dataPoints[2].value){let f=d(parseFloat(t.dataPoints[2].value)),C=d(parseFloat(t.dataPoints[3].value),2),v=d(f*100/p)-100,B=d(C*100/h)-100,k=v<0?"faster":"slower",A=B>0?"more expensive":"cheaper";o.innerHTML=`Time: ${f}ms (${Math.sign(v)*v}% ${k})`,r.innerHTML=`Cost: ${C}$ (${Math.sign(B)*B}% ${A})`,o.style.display="block",r.style.display="block",n.style.display="block"}else o.style.display="none",r.style.display="none",n.style.display="none";let g=this._chart.canvas.getBoundingClientRect();e.style.display="block",e.style.left=g.left+window.pageXOffset+t.caretX+"px",e.style.top=g.top+window.pageYOffset+t.caretY+"px",e.style.fontFamily=t._bodyFontFamily,e.style.fontSize=t.bodyFontSize+"px",e.style.fontStyle=t._bodyFontStyle,e.style.pointerEvents="none"}function d(t,e=1){if(t<1e-12)return t.toFixed(e).replace(/(\.0*[1-9]+)[0]+$|\.[0]+$/,"$1");let s=Math.max(-Math.round(Math.log10(t))+e,0);return t.toFixed(s).replace(/(\.0*[1-9]+)[0]+$|\.[0]+$/,"$1")}var u,U,I=class{constructor(){R(this,u);this.listeners=[],window.addEventListener("hashchange",$(this,u,U).bind(this))}addListener(e,s){this.listeners.push([e,s]),$(this,u,U).call(this)}setState(e){window.location.hash=e}};u=new WeakSet,U=function(){window.location.hash?this.listeners.forEach(([e])=>e(window.location.hash.slice(1))):this.listeners.forEach(([,e])=>e("empty"))};var y=new w(new I),E=document.getElementById("modal-compare");z();function z(){window.addEventListener("click",t=>{t.target===E&&(E.style.display="none")}),O()}function O(){let t=document.getElementById("compare"),e=document.getElementById("confirm-compare"),s=document.getElementById("compare-f1-name"),i=document.getElementById("compare-f1-url"),a=document.getElementById("compare-f2-name"),o=document.getElementById("compare-f2-url");t.addEventListener("click",function(){E.style.display="block",s.value=y.legend1,i.value=y.data1URL,a.value=y.legend2,o.value=y.data2URL}),e.addEventListener("click",function(r){let n=i.value||"",[,l]=n.split("#");if(!l){alert("Invalid URL for function 1. Please try again with a valid visualization URL.");return}let p=o.value||"",[,h]=p.split("#");if(!h){alert("Invalid URL for function 2. Please try again with a valid visualization URL.");return}let g=s.value||"",f=a.value||"";return E.style.display="none",r.stopPropagation(),y.compare(l,h,g,f),!1})}})(); 2 | //# sourceMappingURL=index.js.map 3 | -------------------------------------------------------------------------------- /public/index.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/encode.js", "../src/app.js", "../src/hashStore.js", "../src/index.js"], 4 | "sourcesContent": ["export function encode(input, cls = Float32Array) {\n input = new cls(input);\n if (!(input instanceof Uint8Array)) {\n input = new Uint8Array(input.buffer);\n }\n\n return window.base64js.fromByteArray(input); // base64 is on the window (global-scope) object\n}\n\nexport function decode(x, cls = Float32Array) {\n return Array.from(new cls(window.base64js.toByteArray(x).buffer));\n}\n", "import { encode, decode } from './encode';\n\nconst DOC_URL = 'https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui';\nconst HASH_DELIM = ';';\nconst NORMAL_HASH_LEN = 3;\nconst COMPARISON_HASH_LEN = 8;\n\nconst chartColors = {\n red: 'rgb(255, 99, 132)',\n blue: 'rgb(54, 162, 235)',\n orange: 'rgb(255, 165, 00)',\n green: 'rgb(34, 139, 34)',\n};\n\nconst chartOptions = {\n responsive: true,\n hoverMode: 'index',\n tooltips: {\n mode: 'index',\n intersect: false,\n enabled: false,\n custom: customTooltip,\n },\n stacked: false,\n title: {\n display: false,\n },\n scales: {\n xAxes: [\n {\n scaleLabel: {\n display: true,\n labelString: 'Memory / Power (MB)',\n },\n },\n ],\n yAxes: [\n {\n ticks: {\n beginAtZero: true,\n callback: (value /*, index, values*/) => `${smartRound(value)} ms`,\n },\n type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance\n display: true,\n scaleLabel: {\n display: true,\n labelString: 'Invocation Time',\n },\n position: 'left',\n id: 'time-axis',\n },\n {\n ticks: {\n beginAtZero: true,\n callback: (value /*, index, values*/) => `${smartRound(value)} $`,\n },\n type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance\n display: true,\n position: 'right',\n id: 'cost-axis',\n scaleLabel: {\n display: true,\n labelString: 'Invocation Cost',\n },\n // grid line settings\n gridLines: {\n drawOnChartArea: false, // only want the grid lines for one axis to show up\n },\n },\n ],\n },\n};\n\nexport class App {\n constructor(store) {\n this.store = store;\n this.canvas = document.querySelector('canvas');\n this.reportElem = document.getElementById('report');\n this.errorElem = document.getElementById('error');\n\n // App State\n this.data1 = null;\n this.data1URL = '';\n this.data2 = null;\n this.data2URL = '';\n this.legend1 = '';\n this.legend2 = '';\n\n this.store.addListener(this.show.bind(this), this.error.bind(this));\n }\n\n setData1(parts) {\n this.data1 = parseHash(parts);\n this.data1URL = getURLForHash(parts.join(HASH_DELIM));\n }\n\n setData2(parts) {\n this.data2 = parseHash(parts);\n this.data2URL = getURLForHash(parts.join(HASH_DELIM));\n }\n\n error(err) {\n this.reportElem.style.display = 'none';\n this.errorElem.style.display = 'flex';\n\n switch (err) {\n case 'empty':\n // eslint-disable-next-line no-case-declarations\n const sampleURL = getURLForHash(\n encode([128, 256, 512, 1024], Int16Array) +\n HASH_DELIM +\n encode([36555.39, 18441.58, 8539.34, 3870.76]) +\n HASH_DELIM +\n encode([0.00007612, 0.00007696, 0.00007155, 0.00006489]),\n );\n document.getElementById('error-title').innerHTML = 'Error: URL must contain data in the hash!';\n document.getElementById(\n 'error-msg',\n ).innerHTML = `Your URL is ${window.location} and should be something like ${sampleURL}
Please refer to the documentation.`;\n break;\n\n case 'malformed':\n document.getElementById('error-title').innerHTML = 'Error: malformed URL parameters!';\n document.getElementById(\n 'error-msg',\n ).innerHTML = `Please check your URL parameters, refer to the documentation.`;\n break;\n\n default:\n // general error\n document.getElementById('error-title').innerHTML = 'Error';\n document.getElementById('error-msg').innerHTML = 'Please check your URL parameters';\n }\n }\n\n show(hash) {\n const hashParts = hash.split(HASH_DELIM);\n const hashPartsLen = hashParts.length;\n this.legend1 = '';\n this.legend2 = '';\n\n // There must be a single set of values (3 parts), OR, the comparison-set-of-values (8)\n if (hashPartsLen !== NORMAL_HASH_LEN && hashPartsLen !== COMPARISON_HASH_LEN) {\n this.error('malformed');\n }\n\n try {\n this.setData1(hashParts.slice(0, 3));\n } catch (e) {\n console.error(e);\n this.error('malformed');\n return;\n }\n\n if (hashPartsLen === COMPARISON_HASH_LEN) {\n try {\n this.setData2(hashParts.slice(3, 6));\n } catch (e) {\n console.error(e);\n this.error('malformed');\n return;\n }\n\n this.legend1 = decodeURIComponent(hashParts[6]);\n this.legend2 = decodeURIComponent(hashParts[7]);\n } else {\n // If we don't have the comparison data, reset the comparison-data fields\n this.data2 = null;\n this.data2URL = '';\n this.legend1 = '';\n this.legend2 = '';\n }\n\n this.reportElem.style.display = 'flex';\n this.errorElem.style.display = 'none';\n\n this.showCards(this.data1);\n\n // will be used for the x-axis\n let powerValues = this.data1.powers;\n\n if (this.data2) {\n // let's make sure you can compare two results with different power values\n // union of power values\n powerValues = this.data1.powers.concat(this.data2.powers);\n // remove duplicates\n powerValues = powerValues.filter((item, pos) => powerValues.indexOf(item) === pos);\n // sort numerically\n powerValues.sort((a, b) => a - b);\n // inject null for missing power values (if any)\n for (const [i, value] of powerValues.entries()) {\n if (!this.data1.powers.includes(value)) {\n this.data1.times.splice(i, 0, null);\n this.data1.costs.splice(i, 0, null);\n }\n if (!this.data2.powers.includes(value)) {\n this.data2.times.splice(i, 0, null);\n this.data2.costs.splice(i, 0, null);\n }\n }\n }\n\n const chartData = {\n labels: powerValues,\n datasets: [\n {\n label: `Invocation Time ${this.legend1 ? this.legend1 + ' ' : ''}(ms)`,\n borderColor: chartColors.red,\n backgroundColor: chartColors.red,\n fill: false,\n data: this.data1.times,\n yAxisID: 'time-axis',\n },\n {\n label: `Invocation Cost ${this.legend1 ? this.legend1 + ' ' : ''}(USD)`,\n borderColor: chartColors.blue,\n backgroundColor: chartColors.blue,\n fill: false,\n data: this.data1.costs,\n yAxisID: 'cost-axis',\n },\n ],\n };\n\n if (this.data2) {\n // add two more datasets with different legend & colors (but same axis ID)\n chartData.datasets.push(\n ...[\n {\n label: `Invocation Time ${this.legend2 ? this.legend2 + ' ' : ''}(ms)`,\n borderColor: chartColors.orange,\n backgroundColor: chartColors.orange,\n fill: false,\n data: this.data2.times,\n yAxisID: 'time-axis',\n },\n {\n label: `Invocation Cost ${this.legend2 ? this.legend2 + ' ' : ''}(USD)`,\n borderColor: chartColors.green,\n backgroundColor: chartColors.green,\n fill: false,\n data: this.data2.costs,\n yAxisID: 'cost-axis',\n },\n ],\n );\n }\n\n // clear existing charts to avoid rendering conflicts\n if (this.myLine) {\n this.myLine.destroy();\n }\n\n const ctx = this.canvas.getContext('2d');\n this.myLine = window.Chart.Line(ctx, {\n data: chartData,\n options: chartOptions,\n });\n }\n\n showCards(data) {\n const alpha = 0.5;\n const mc = Math.max(...data.costs);\n const mt = Math.max(...data.times);\n\n const configurations = data.powers.map((x, i) => {\n return {\n size: x,\n time: data.times[i],\n cost: data.costs[i],\n value: (alpha * data.costs[i]) / mc + ((1 - alpha) * data.times[i]) / mt,\n };\n });\n\n configurations.sort((x, y) => x.time - y.time);\n document.getElementById('min-time').innerHTML = `${configurations[0].size}MB`;\n document.getElementById('max-time').innerHTML = `${configurations[configurations.length - 1].size}MB`;\n configurations.sort((x, y) => x.cost - y.cost);\n document.getElementById('min-cost').innerHTML = `${configurations[0].size}MB`;\n document.getElementById('max-cost').innerHTML = `${configurations[configurations.length - 1].size}MB`;\n configurations.sort((x, y) => x.value - y.value);\n document.getElementById('balanced').innerHTML = `${configurations[0].size}MB`;\n }\n\n compare(hash1, hash2, legend1, legend2) {\n // This is really like a persistState() call.\n // This should trigger a call to app.show() via the hashwatcher's callback\n this.store.setState([hash1, hash2, encodeURIComponent(legend1), encodeURIComponent(legend2)].join(HASH_DELIM));\n }\n}\n\nfunction parseHash(parts) {\n const powers = decode(parts[0], Int16Array);\n const times = decode(parts[1]);\n const costs = decode(parts[2]);\n // console.log(powers.toString(), times.toString(), costs.toString());\n return { powers, times, costs };\n}\n\nfunction getURLForHash(hash) {\n return `${window.location.origin}${window.location.pathname}#${hash}`;\n}\n\nfunction customTooltip(tooltipModel) {\n // Tooltip Element\n const el = document.getElementById('tooltip');\n const elPower = document.getElementById('tooltip-power');\n const elTime1 = document.getElementById('tooltip-time-1');\n const elCost1 = document.getElementById('tooltip-cost-1');\n const elTime2 = document.getElementById('tooltip-time-2');\n const elCost2 = document.getElementById('tooltip-cost-2');\n const elSeparator = document.getElementById('tooltip-separator');\n\n // Hide if no tooltip\n if (tooltipModel.opacity === 0) {\n el.style.display = 'none';\n return;\n }\n\n const power = tooltipModel.dataPoints[0].label;\n const time = smartRound(parseFloat(tooltipModel.dataPoints[0].value));\n const cost = smartRound(parseFloat(tooltipModel.dataPoints[1].value), 2);\n\n elPower.innerHTML = `Power: ${power}`;\n elTime1.innerHTML = `Time: ${time}ms`;\n elCost1.innerHTML = `Cost: ${cost}$`;\n\n if (tooltipModel.dataPoints[2] && tooltipModel.dataPoints[2].value) {\n const time2 = smartRound(parseFloat(tooltipModel.dataPoints[2].value));\n const cost2 = smartRound(parseFloat(tooltipModel.dataPoints[3].value), 2);\n const percTime = smartRound((time2 * 100) / time) - 100; // faster if <0, slower if >0\n const percCost = smartRound((cost2 * 100) / cost) - 100; // more expensive if >0, cheaper if <0\n\n const timeLabel = percTime < 0 ? 'faster' : 'slower';\n const costLabel = percCost > 0 ? 'more expensive' : 'cheaper';\n\n elTime2.innerHTML = `Time: ${time2}ms (${Math.sign(percTime) * percTime}% ${timeLabel})`;\n elCost2.innerHTML = `Cost: ${cost2}$ (${Math.sign(percCost) * percCost}% ${costLabel})`;\n\n elTime2.style.display = 'block';\n elCost2.style.display = 'block';\n elSeparator.style.display = 'block';\n } else {\n elTime2.style.display = 'none';\n elCost2.style.display = 'none';\n elSeparator.style.display = 'none';\n }\n\n const position = this._chart.canvas.getBoundingClientRect();\n\n el.style.display = 'block';\n el.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px';\n el.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px';\n el.style.fontFamily = tooltipModel._bodyFontFamily;\n el.style.fontSize = tooltipModel.bodyFontSize + 'px';\n el.style.fontStyle = tooltipModel._bodyFontStyle;\n // el.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';\n el.style.pointerEvents = 'none';\n}\n\nfunction smartRound(x, s = 1) {\n if (x < 1e-12) {\n return x.toFixed(s).replace(/(\\.0*[1-9]+)[0]+$|\\.[0]+$/, '$1');\n }\n\n let digits = Math.max(-Math.round(Math.log10(x)) + s, 0);\n let string = x.toFixed(digits);\n return string.replace(/(\\.0*[1-9]+)[0]+$|\\.[0]+$/, '$1');\n}\n", "export class HashStore {\n constructor() {\n this.listeners = [];\n window.addEventListener('hashchange', this.#onHashChange.bind(this));\n }\n\n addListener(onChange, onError) {\n this.listeners.push([onChange, onError]);\n this.#onHashChange();\n }\n\n setState(hash) {\n window.location.hash = hash;\n }\n\n #onHashChange() {\n if (window.location.hash) {\n this.listeners.forEach(([onChange]) => onChange(window.location.hash.slice(1))); // remove the '#' char\n } else {\n this.listeners.forEach(([, onError]) => onError('empty'));\n }\n }\n}\n", "import { App } from './app.js';\nimport { HashStore } from './hashStore';\n\nconst app = new App(new HashStore());\nconst compareModal = document.getElementById('modal-compare');\n\nsetupUI();\n\nfunction setupUI() {\n window.addEventListener('click', (event) => {\n if (event.target === compareModal) {\n compareModal.style.display = 'none';\n }\n });\n setupModal();\n}\n\nfunction setupModal() {\n const compareModalButton = document.getElementById('compare');\n const compareConfirmButton = document.getElementById('confirm-compare');\n\n const compareF1Name = document.getElementById('compare-f1-name');\n const compareF1URL = document.getElementById('compare-f1-url');\n const compareF2Name = document.getElementById('compare-f2-name');\n const compareF2URL = document.getElementById('compare-f2-url');\n\n compareModalButton.addEventListener('click', function () {\n compareModal.style.display = 'block';\n\n // Update the form values when we show the dialog\n compareF1Name.value = app.legend1;\n compareF1URL.value = app.data1URL;\n compareF2Name.value = app.legend2;\n compareF2URL.value = app.data2URL;\n });\n\n compareConfirmButton.addEventListener('click', function (e) {\n const URL1 = compareF1URL.value || '';\n const [, hash1] = URL1.split('#');\n\n if (!hash1) {\n alert('Invalid URL for function 1. Please try again with a valid visualization URL.');\n return;\n }\n\n const URL2 = compareF2URL.value || '';\n const [, hash2] = URL2.split('#');\n if (!hash2) {\n alert('Invalid URL for function 2. Please try again with a valid visualization URL.');\n return;\n }\n\n const legend1 = compareF1Name.value || '';\n const legend2 = compareF2Name.value || '';\n\n compareModal.style.display = 'none';\n e.stopPropagation();\n\n app.compare(hash1, hash2, legend1, legend2);\n\n return false;\n });\n}\n"], 5 | "mappings": "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", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /public/lib/base64.js: -------------------------------------------------------------------------------- 1 | (function(r){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=r()}else if(typeof define==="function"&&define.amd){define([],r)}else{var e;if(typeof window!=="undefined"){e=window}else if(typeof global!=="undefined"){e=global}else if(typeof self!=="undefined"){e=self}else{e=this}e.base64js=r()}})(function(){var r,e,n;return function(){function d(a,f,i){function u(n,r){if(!f[n]){if(!a[n]){var e="function"==typeof require&&require;if(!r&&e)return e(n,!0);if(v)return v(n,!0);var t=new Error("Cannot find module '"+n+"'");throw t.code="MODULE_NOT_FOUND",t}var o=f[n]={exports:{}};a[n][0].call(o.exports,function(r){var e=a[n][1][r];return u(e||r)},o,o.exports,d,a,f,i)}return f[n].exports}for(var v="function"==typeof require&&require,r=0;r0){throw new Error("Invalid string. Length must be a multiple of 4")}var n=r.indexOf("=");if(n===-1)n=e;var t=n===e?0:4-n%4;return[n,t]}function f(r){var e=c(r);var n=e[0];var t=e[1];return(n+t)*3/4-t}function h(r,e,n){return(e+n)*3/4-n}function i(r){var e;var n=c(r);var t=n[0];var o=n[1];var a=new d(h(r,t,o));var f=0;var i=o>0?t-4:t;var u;for(u=0;u>16&255;a[f++]=e>>8&255;a[f++]=e&255}if(o===2){e=v[r.charCodeAt(u)]<<2|v[r.charCodeAt(u+1)]>>4;a[f++]=e&255}if(o===1){e=v[r.charCodeAt(u)]<<10|v[r.charCodeAt(u+1)]<<4|v[r.charCodeAt(u+2)]>>2;a[f++]=e>>8&255;a[f++]=e&255}return a}function s(r){return u[r>>18&63]+u[r>>12&63]+u[r>>6&63]+u[r&63]}function l(r,e,n){var t;var o=[];for(var a=e;ai?i:f+a))}if(t===1){e=r[n-1];o.push(u[e>>2]+u[e<<4&63]+"==")}else if(t===2){e=(r[n-2]<<8)+r[n-1];o.push(u[e>>10]+u[e>>4&63]+u[e<<2&63]+"=")}return o.join("")}},{}]},{},[])("/")}); 2 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | *,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input:-ms-input-placeholder,textarea:-ms-input-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.fixed{position:fixed}.relative{position:relative}.inset-0{inset:0}.right-0{right:0}.bottom-0{bottom:0}.m-4{margin:1rem}.m-2{margin:.5rem}.m-1{margin:.25rem}.mx-6{margin-left:1.5rem;margin-right:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mt-10{margin-top:2.5rem}.mt-3{margin-top:.75rem}.mb-8{margin-bottom:2rem}.mb-6{margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mr-6{margin-right:1.5rem}.block{display:block}.flex{display:flex}.hidden{display:none}.h-full{height:100%}.w-full{width:100%}.w-64{width:16rem}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.border{border-width:1px}.border-t-0{border-top-width:0px}.border-red-400{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity))}.bg-gray-300{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-opacity-50{--tw-bg-opacity: .5}.p-4{padding:1rem}.p-2{padding:.5rem}.p-5{padding:1.25rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pr-4{padding-right:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-xs{font-size:.75rem;line-height:1rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-light{font-weight:300}.font-medium{font-weight:500}.leading-6{line-height:1.5rem}.leading-tight{line-height:1.25}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}a{text-decoration:underline}.card{float:left;overflow:hidden;border-radius:.5rem;--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity));--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}#tooltip{position:absolute;display:none;border-radius:.5rem;padding:.5rem .75rem;--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity));background:rgba(5,5,10,.7)}.dont-break-out{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-all;word-break:break-word;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity))}.hover\:bg-green-600:hover{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-green-300:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(134 239 172 / var(--tw-ring-opacity))}@media (min-width: 768px){.md\:mb-0{margin-bottom:0}.md\:flex{display:flex}.md\:w-1\/2{width:50%}.md\:items-center{align-items:center}.md\:text-right{text-align:right}}@media (min-width: 1024px){.lg\:w-1\/2{width:50%}.lg\:flex-1{flex:1 1 0%}.lg\:items-start{align-items:flex-start}.lg\:justify-center{justify-content:center}}@media (min-width: 1280px){.xl\:px-24{padding-left:6rem;padding-right:6rem}} 2 | /*# sourceMappingURL=style.css.map */ 3 | -------------------------------------------------------------------------------- /public/style.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/Users/brettuglow/code/digio/aws-lambda-power-tuning-ui/node_modules/tailwindcss/base.css", "../src/Users/brettuglow/code/digio/aws-lambda-power-tuning-ui/node_modules/tailwindcss/utilities.css", "../src/Users/brettuglow/code/digio/aws-lambda-power-tuning-ui/src/style.scss", "../src/%3Cinput%20css%20iyNhin%3E", "../src/"], 4 | "sourcesContent": [null, null, null, null, null], 5 | "mappings": "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", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /serve.js: -------------------------------------------------------------------------------- 1 | import { config } from './build.js'; 2 | import esbuild from 'esbuild'; 3 | 4 | esbuild.serve({ port: 3000, servedir: 'public' }, config).then((/* server */) => { 5 | // Call "stop" on the web server to stop serving 6 | //server.stop(); 7 | console.log('watching on port 3000...'); 8 | }); 9 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import { encode, decode } from './encode'; 2 | 3 | const DOC_URL = 'https://github.com/matteo-ronchetti/aws-lambda-power-tuning-ui'; 4 | const HASH_DELIM = ';'; 5 | const NORMAL_HASH_LEN = 3; 6 | const COMPARISON_HASH_LEN = 8; 7 | 8 | const chartColors = { 9 | red: 'rgb(255, 99, 132)', 10 | blue: 'rgb(54, 162, 235)', 11 | orange: 'rgb(255, 165, 00)', 12 | green: 'rgb(34, 139, 34)', 13 | }; 14 | 15 | const chartOptions = { 16 | responsive: true, 17 | hoverMode: 'index', 18 | tooltips: { 19 | mode: 'index', 20 | intersect: false, 21 | enabled: false, 22 | custom: customTooltip, 23 | }, 24 | stacked: false, 25 | title: { 26 | display: false, 27 | }, 28 | scales: { 29 | xAxes: [ 30 | { 31 | scaleLabel: { 32 | display: true, 33 | labelString: 'Memory / Power (MB)', 34 | }, 35 | }, 36 | ], 37 | yAxes: [ 38 | { 39 | ticks: { 40 | beginAtZero: true, 41 | callback: (value /*, index, values*/) => `${smartRound(value)} ms`, 42 | }, 43 | type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance 44 | display: true, 45 | scaleLabel: { 46 | display: true, 47 | labelString: 'Invocation Time', 48 | }, 49 | position: 'left', 50 | id: 'time-axis', 51 | }, 52 | { 53 | ticks: { 54 | beginAtZero: true, 55 | callback: (value /*, index, values*/) => `${smartRound(value)} $`, 56 | }, 57 | type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance 58 | display: true, 59 | position: 'right', 60 | id: 'cost-axis', 61 | scaleLabel: { 62 | display: true, 63 | labelString: 'Invocation Cost', 64 | }, 65 | // grid line settings 66 | gridLines: { 67 | drawOnChartArea: false, // only want the grid lines for one axis to show up 68 | }, 69 | }, 70 | ], 71 | }, 72 | }; 73 | 74 | export class App { 75 | constructor(store) { 76 | this.store = store; 77 | this.canvas = document.querySelector('canvas'); 78 | this.reportElem = document.getElementById('report'); 79 | this.errorElem = document.getElementById('error'); 80 | 81 | // App State 82 | this.data1 = null; 83 | this.data1URL = ''; 84 | this.data2 = null; 85 | this.data2URL = ''; 86 | this.legend1 = ''; 87 | this.legend2 = ''; 88 | 89 | this.store.addListener(this.show.bind(this), this.error.bind(this)); 90 | } 91 | 92 | setData1(parts) { 93 | this.data1 = parseHash(parts); 94 | this.data1URL = getURLForHash(parts.join(HASH_DELIM)); 95 | } 96 | 97 | setData2(parts) { 98 | this.data2 = parseHash(parts); 99 | this.data2URL = getURLForHash(parts.join(HASH_DELIM)); 100 | } 101 | 102 | error(err) { 103 | this.reportElem.style.display = 'none'; 104 | this.errorElem.style.display = 'flex'; 105 | 106 | switch (err) { 107 | case 'empty': 108 | // eslint-disable-next-line no-case-declarations 109 | const sampleURL = getURLForHash( 110 | encode([128, 256, 512, 1024], Int16Array) + 111 | HASH_DELIM + 112 | encode([36555.39, 18441.58, 8539.34, 3870.76]) + 113 | HASH_DELIM + 114 | encode([0.00007612, 0.00007696, 0.00007155, 0.00006489]), 115 | ); 116 | document.getElementById('error-title').innerHTML = 'Error: URL must contain data in the hash!'; 117 | document.getElementById( 118 | 'error-msg', 119 | ).innerHTML = `Your URL is ${window.location} and should be something like ${sampleURL}
Please refer to the documentation.`; 120 | break; 121 | 122 | case 'malformed': 123 | document.getElementById('error-title').innerHTML = 'Error: malformed URL parameters!'; 124 | document.getElementById( 125 | 'error-msg', 126 | ).innerHTML = `Please check your URL parameters, refer to the documentation.`; 127 | break; 128 | 129 | default: 130 | // general error 131 | document.getElementById('error-title').innerHTML = 'Error'; 132 | document.getElementById('error-msg').innerHTML = 'Please check your URL parameters'; 133 | } 134 | } 135 | 136 | show(hash) { 137 | const hashParts = hash.split(HASH_DELIM); 138 | const hashPartsLen = hashParts.length; 139 | this.legend1 = ''; 140 | this.legend2 = ''; 141 | 142 | // There must be a single set of values (3 parts), OR, the comparison-set-of-values (8) 143 | if (hashPartsLen !== NORMAL_HASH_LEN && hashPartsLen !== COMPARISON_HASH_LEN) { 144 | this.error('malformed'); 145 | } 146 | 147 | try { 148 | this.setData1(hashParts.slice(0, 3)); 149 | } catch (e) { 150 | console.error(e); 151 | this.error('malformed'); 152 | return; 153 | } 154 | 155 | if (hashPartsLen === COMPARISON_HASH_LEN) { 156 | try { 157 | this.setData2(hashParts.slice(3, 6)); 158 | } catch (e) { 159 | console.error(e); 160 | this.error('malformed'); 161 | return; 162 | } 163 | 164 | this.legend1 = decodeURIComponent(hashParts[6]); 165 | this.legend2 = decodeURIComponent(hashParts[7]); 166 | } else { 167 | // If we don't have the comparison data, reset the comparison-data fields 168 | this.data2 = null; 169 | this.data2URL = ''; 170 | this.legend1 = ''; 171 | this.legend2 = ''; 172 | } 173 | 174 | this.reportElem.style.display = 'flex'; 175 | this.errorElem.style.display = 'none'; 176 | 177 | this.showCards(this.data1); 178 | 179 | // will be used for the x-axis 180 | let powerValues = this.data1.powers; 181 | 182 | if (this.data2) { 183 | // let's make sure you can compare two results with different power values 184 | // union of power values 185 | powerValues = this.data1.powers.concat(this.data2.powers); 186 | // remove duplicates 187 | powerValues = powerValues.filter((item, pos) => powerValues.indexOf(item) === pos); 188 | // sort numerically 189 | powerValues.sort((a, b) => a - b); 190 | // inject null for missing power values (if any) 191 | for (const [i, value] of powerValues.entries()) { 192 | if (!this.data1.powers.includes(value)) { 193 | this.data1.times.splice(i, 0, null); 194 | this.data1.costs.splice(i, 0, null); 195 | } 196 | if (!this.data2.powers.includes(value)) { 197 | this.data2.times.splice(i, 0, null); 198 | this.data2.costs.splice(i, 0, null); 199 | } 200 | } 201 | } 202 | 203 | const chartData = { 204 | labels: powerValues, 205 | datasets: [ 206 | { 207 | label: `Invocation Time ${this.legend1 ? this.legend1 + ' ' : ''}(ms)`, 208 | borderColor: chartColors.red, 209 | backgroundColor: chartColors.red, 210 | fill: false, 211 | data: this.data1.times, 212 | yAxisID: 'time-axis', 213 | }, 214 | { 215 | label: `Invocation Cost ${this.legend1 ? this.legend1 + ' ' : ''}(USD)`, 216 | borderColor: chartColors.blue, 217 | backgroundColor: chartColors.blue, 218 | fill: false, 219 | data: this.data1.costs, 220 | yAxisID: 'cost-axis', 221 | }, 222 | ], 223 | }; 224 | 225 | if (this.data2) { 226 | // add two more datasets with different legend & colors (but same axis ID) 227 | chartData.datasets.push( 228 | ...[ 229 | { 230 | label: `Invocation Time ${this.legend2 ? this.legend2 + ' ' : ''}(ms)`, 231 | borderColor: chartColors.orange, 232 | backgroundColor: chartColors.orange, 233 | fill: false, 234 | data: this.data2.times, 235 | yAxisID: 'time-axis', 236 | }, 237 | { 238 | label: `Invocation Cost ${this.legend2 ? this.legend2 + ' ' : ''}(USD)`, 239 | borderColor: chartColors.green, 240 | backgroundColor: chartColors.green, 241 | fill: false, 242 | data: this.data2.costs, 243 | yAxisID: 'cost-axis', 244 | }, 245 | ], 246 | ); 247 | } 248 | 249 | // clear existing charts to avoid rendering conflicts 250 | if (this.myLine) { 251 | this.myLine.destroy(); 252 | } 253 | 254 | const ctx = this.canvas.getContext('2d'); 255 | this.myLine = window.Chart.Line(ctx, { 256 | data: chartData, 257 | options: chartOptions, 258 | }); 259 | } 260 | 261 | showCards(data) { 262 | const alpha = 0.5; 263 | const mc = Math.max(...data.costs); 264 | const mt = Math.max(...data.times); 265 | 266 | const configurations = data.powers.map((x, i) => { 267 | return { 268 | size: x, 269 | time: data.times[i], 270 | cost: data.costs[i], 271 | value: (alpha * data.costs[i]) / mc + ((1 - alpha) * data.times[i]) / mt, 272 | }; 273 | }); 274 | 275 | configurations.sort((x, y) => x.time - y.time); 276 | document.getElementById('min-time').innerHTML = `${configurations[0].size}MB`; 277 | document.getElementById('max-time').innerHTML = `${configurations[configurations.length - 1].size}MB`; 278 | configurations.sort((x, y) => x.cost - y.cost); 279 | document.getElementById('min-cost').innerHTML = `${configurations[0].size}MB`; 280 | document.getElementById('max-cost').innerHTML = `${configurations[configurations.length - 1].size}MB`; 281 | configurations.sort((x, y) => x.value - y.value); 282 | document.getElementById('balanced').innerHTML = `${configurations[0].size}MB`; 283 | } 284 | 285 | compare(hash1, hash2, legend1, legend2) { 286 | // This is really like a persistState() call. 287 | // This should trigger a call to app.show() via the hashwatcher's callback 288 | this.store.setState([hash1, hash2, encodeURIComponent(legend1), encodeURIComponent(legend2)].join(HASH_DELIM)); 289 | } 290 | } 291 | 292 | function parseHash(parts) { 293 | const powers = decode(parts[0], Int16Array); 294 | const times = decode(parts[1]); 295 | const costs = decode(parts[2]); 296 | // console.log(powers.toString(), times.toString(), costs.toString()); 297 | return { powers, times, costs }; 298 | } 299 | 300 | function getURLForHash(hash) { 301 | return `${window.location.origin}${window.location.pathname}#${hash}`; 302 | } 303 | 304 | function customTooltip(tooltipModel) { 305 | // Tooltip Element 306 | const el = document.getElementById('tooltip'); 307 | const elPower = document.getElementById('tooltip-power'); 308 | const elTime1 = document.getElementById('tooltip-time-1'); 309 | const elCost1 = document.getElementById('tooltip-cost-1'); 310 | const elTime2 = document.getElementById('tooltip-time-2'); 311 | const elCost2 = document.getElementById('tooltip-cost-2'); 312 | const elSeparator = document.getElementById('tooltip-separator'); 313 | 314 | // Hide if no tooltip 315 | if (tooltipModel.opacity === 0) { 316 | el.style.display = 'none'; 317 | return; 318 | } 319 | 320 | const power = tooltipModel.dataPoints[0].label; 321 | const time = smartRound(parseFloat(tooltipModel.dataPoints[0].value)); 322 | const cost = smartRound(parseFloat(tooltipModel.dataPoints[1].value), 2); 323 | 324 | elPower.innerHTML = `Power: ${power}`; 325 | elTime1.innerHTML = `Time: ${time}ms`; 326 | elCost1.innerHTML = `Cost: ${cost}$`; 327 | 328 | if (tooltipModel.dataPoints[2] && tooltipModel.dataPoints[2].value) { 329 | const time2 = smartRound(parseFloat(tooltipModel.dataPoints[2].value)); 330 | const cost2 = smartRound(parseFloat(tooltipModel.dataPoints[3].value), 2); 331 | const percTime = smartRound((time2 * 100) / time) - 100; // faster if <0, slower if >0 332 | const percCost = smartRound((cost2 * 100) / cost) - 100; // more expensive if >0, cheaper if <0 333 | 334 | const timeLabel = percTime < 0 ? 'faster' : 'slower'; 335 | const costLabel = percCost > 0 ? 'more expensive' : 'cheaper'; 336 | 337 | elTime2.innerHTML = `Time: ${time2}ms (${Math.sign(percTime) * percTime}% ${timeLabel})`; 338 | elCost2.innerHTML = `Cost: ${cost2}$ (${Math.sign(percCost) * percCost}% ${costLabel})`; 339 | 340 | elTime2.style.display = 'block'; 341 | elCost2.style.display = 'block'; 342 | elSeparator.style.display = 'block'; 343 | } else { 344 | elTime2.style.display = 'none'; 345 | elCost2.style.display = 'none'; 346 | elSeparator.style.display = 'none'; 347 | } 348 | 349 | const position = this._chart.canvas.getBoundingClientRect(); 350 | 351 | el.style.display = 'block'; 352 | el.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px'; 353 | el.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px'; 354 | el.style.fontFamily = tooltipModel._bodyFontFamily; 355 | el.style.fontSize = tooltipModel.bodyFontSize + 'px'; 356 | el.style.fontStyle = tooltipModel._bodyFontStyle; 357 | // el.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px'; 358 | el.style.pointerEvents = 'none'; 359 | } 360 | 361 | function smartRound(x, s = 1) { 362 | if (x < 1e-12) { 363 | return x.toFixed(s).replace(/(\.0*[1-9]+)[0]+$|\.[0]+$/, '$1'); 364 | } 365 | 366 | let digits = Math.max(-Math.round(Math.log10(x)) + s, 0); 367 | let string = x.toFixed(digits); 368 | return string.replace(/(\.0*[1-9]+)[0]+$|\.[0]+$/, '$1'); 369 | } 370 | -------------------------------------------------------------------------------- /src/encode.js: -------------------------------------------------------------------------------- 1 | export function encode(input, cls = Float32Array) { 2 | input = new cls(input); 3 | if (!(input instanceof Uint8Array)) { 4 | input = new Uint8Array(input.buffer); 5 | } 6 | 7 | return window.base64js.fromByteArray(input); // base64 is on the window (global-scope) object 8 | } 9 | 10 | export function decode(x, cls = Float32Array) { 11 | return Array.from(new cls(window.base64js.toByteArray(x).buffer)); 12 | } 13 | -------------------------------------------------------------------------------- /src/hashStore.js: -------------------------------------------------------------------------------- 1 | export class HashStore { 2 | constructor() { 3 | this.listeners = []; 4 | window.addEventListener('hashchange', this.#onHashChange.bind(this)); 5 | } 6 | 7 | addListener(onChange, onError) { 8 | this.listeners.push([onChange, onError]); 9 | this.#onHashChange(); 10 | } 11 | 12 | setState(hash) { 13 | window.location.hash = hash; 14 | } 15 | 16 | #onHashChange() { 17 | if (window.location.hash) { 18 | this.listeners.forEach(([onChange]) => onChange(window.location.hash.slice(1))); // remove the '#' char 19 | } else { 20 | this.listeners.forEach(([, onError]) => onError('empty')); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { App } from './app.js'; 2 | import { HashStore } from './hashStore'; 3 | 4 | const app = new App(new HashStore()); 5 | const compareModal = document.getElementById('modal-compare'); 6 | 7 | setupUI(); 8 | 9 | function setupUI() { 10 | window.addEventListener('click', (event) => { 11 | if (event.target === compareModal) { 12 | compareModal.style.display = 'none'; 13 | } 14 | }); 15 | setupModal(); 16 | } 17 | 18 | function setupModal() { 19 | const compareModalButton = document.getElementById('compare'); 20 | const compareConfirmButton = document.getElementById('confirm-compare'); 21 | 22 | const compareF1Name = document.getElementById('compare-f1-name'); 23 | const compareF1URL = document.getElementById('compare-f1-url'); 24 | const compareF2Name = document.getElementById('compare-f2-name'); 25 | const compareF2URL = document.getElementById('compare-f2-url'); 26 | 27 | compareModalButton.addEventListener('click', function () { 28 | compareModal.style.display = 'block'; 29 | 30 | // Update the form values when we show the dialog 31 | compareF1Name.value = app.legend1; 32 | compareF1URL.value = app.data1URL; 33 | compareF2Name.value = app.legend2; 34 | compareF2URL.value = app.data2URL; 35 | }); 36 | 37 | compareConfirmButton.addEventListener('click', function (e) { 38 | const URL1 = compareF1URL.value || ''; 39 | const [, hash1] = URL1.split('#'); 40 | 41 | if (!hash1) { 42 | alert('Invalid URL for function 1. Please try again with a valid visualization URL.'); 43 | return; 44 | } 45 | 46 | const URL2 = compareF2URL.value || ''; 47 | const [, hash2] = URL2.split('#'); 48 | if (!hash2) { 49 | alert('Invalid URL for function 2. Please try again with a valid visualization URL.'); 50 | return; 51 | } 52 | 53 | const legend1 = compareF1Name.value || ''; 54 | const legend2 = compareF2Name.value || ''; 55 | 56 | compareModal.style.display = 'none'; 57 | e.stopPropagation(); 58 | 59 | app.compare(hash1, hash2, legend1, legend2); 60 | 61 | return false; 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/style.scss: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | a { 6 | text-decoration: underline; 7 | } 8 | 9 | .card { 10 | float: left; 11 | @apply rounded-lg overflow-hidden shadow-md bg-white text-gray-700; 12 | } 13 | 14 | #tooltip { 15 | @apply py-2 px-3 absolute rounded-lg text-gray-200 hidden; 16 | background: rgba(5, 5, 10, 0.7); 17 | } 18 | 19 | .dont-break-out { 20 | /* These are technically the same, but use both */ 21 | overflow-wrap: break-word; 22 | word-wrap: break-word; 23 | 24 | -ms-word-break: break-all; 25 | /* This is the dangerous one in WebKit, as it breaks things wherever */ 26 | word-break: break-all; 27 | /* Instead use this non-standard one: */ 28 | word-break: break-word; 29 | 30 | /* Adds a hyphen where the word breaks, if supported (No Blink) */ 31 | -ms-hyphens: auto; 32 | -moz-hyphens: auto; 33 | -webkit-hyphens: auto; 34 | hyphens: auto; 35 | } 36 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mode: 'jit', 3 | content: ['./public/index.html'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | --------------------------------------------------------------------------------