├── .gitignore ├── LICENSE ├── README.md ├── config.ini.sample ├── hijack.js ├── imes └── remapper ├── package-lock.json ├── package.json ├── remapper ├── engine.js ├── keymap.js ├── main.js ├── manifest.json └── preamble.js └── wscript /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /build/ 3 | /config.ini 4 | /imes/* 5 | !/imes/remapper 6 | .lock* 7 | /node_modules/ 8 | /waf 9 | /waf-* 10 | .waf* 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Marica Odagaki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chromeos-key-remapper 2 | 3 | This repo contains: 4 | 5 | 1. An unpublished Chrome OS IME that lets you use emacs-like cursor movement 6 | keys with a US English keyboard. `C-a` and `C-k`, to name a few. 7 | 2. Tooling to build a custom IME that combines the remapper engine and other 3rd party IMEs. 8 | 9 | ## Limitations 10 | 11 | - All the combined IMEs share the same JavaScript scope. Name collision 12 | can happen. 13 | - The options page of the custom IME can only display the options page of a 14 | single IME. 15 | - Keys can be only remapped to other key combinations. i.e. can't do things 16 | that the OS already supports through exiting key combos. 17 | - Only a single key combo can be mapped: no support for sequence of keys. 18 | 19 | ## Prerequisites / assumptions 20 | 21 | For the premade IME: 22 | 23 | - You don't want to remap when you're in crosh window 24 | 25 | Additionally, for the make-your-own route: 26 | 27 | - python (I use 3.x; 2.x _probably_ works) 28 | - [`waf` command](https://waf.io/book/#_download_and_installation) 29 | - `waf*` is gitignored in this repo; I have it downloaded to the root of my local clone 30 | - [`jscodeshift` command](https://github.com/facebook/jscodeshift) 31 | - `npm install` in this repo and add `./node_module/.bin` to `$PATH` when invoking `waf` 32 | - or: `npm install -g jscodeshift` 33 | 34 | ## How to install 35 | 36 | First, download this repo as a zip file and unpack it or clone the repo. 37 | Chrome OS needs to have access to the file system where your local copy resides. 38 | 39 | Go to chrome://extensions and enable developer mode. 40 | 41 | ### Using the premade IME 42 | 43 | In chrome://extensions, click the "Load unpacked extension..." button 44 | and pick the `remapper` directory in your local copy of the repo. 45 | 46 | Open Settings, then search for "manage input methods." Click the highlighted row 47 | with that label. There should be a row named "US x emacs"; check to enable it. 48 | 49 | Now, pressing Ctrl-Shift-Space will cycle through all the 50 | IMEs that are enabled. Hit Ctrl-Space to switch back and forth 51 | between the previously selected IME. 52 | 53 | There's an indicator next to the notification indicator that shows the active 54 | IME: make sure you've activated the IME you just installed and try out 55 | a few bindings like `C-f`, `C-b` in a text field. 56 | 57 | See [`remapper/keymap.js`](./remapper/keymap.js) for the keybindings. 58 | If you want different bindings, edit this file and reload the extension. 59 | 60 | ### Making your own with a different language and/or layout 61 | 62 | You need to create a `config.ini` file. `config.ini.sample` is a good 63 | starting point: 64 | 65 | ```sh 66 | cp config.ini.sample config.ini 67 | ``` 68 | 69 | The config file can contain multiple sections. Each section, when 70 | built, will result in a directory under `./build` that contains an 71 | IME extension. An IME extension can potentially hold multiple 72 | IMEs; however, this tooling only supports a single IME per extension. 73 | 74 | Here's what a section looks like: 75 | 76 | ```ini 77 | [us_emacs] 78 | name = EN x emacs 79 | description = US keyboard with emacs-like cursor movement 80 | language = en-US 81 | layout = us 82 | fallback_imes = 83 | options_page = 84 | ``` 85 | 86 | `[us_emacs]` is the section name, which will become the name of the directory. 87 | 88 | `name` and `description` will become the name and description of both the extension 89 | and the IME you see in Settings. 90 | 91 | `language` and `layout` dictate which language and layout the IME will be for. 92 | Change these to make an IME for your preferred language and layout. I'm not sure 93 | what values are accepted here. The [extra keyboards repo][extra-keyboard] may 94 | be a good resource. 95 | 96 | `fallback_imes` and `options_page` are irrelevant to what we're doing now; we'll 97 | come back to it later. 98 | 99 | To build an IME out of this config file, run `waf`: 100 | 101 | ```sh 102 | python waf configure build 103 | ``` 104 | 105 | This should create a directory `./build/us_emacs/`, which you can install 106 | like the premade one above. 107 | 108 | ### Making your own by combining other IMEs 109 | 110 | The basic steps are the same as the above: create `config.ini` and run `waf`. 111 | 112 | This time, we actually make use of `fallback_imes` and `options_page`. 113 | 114 | `fallback_imes` is a comma-separated list of directory names you put 115 | in `./imes`. Said directories must contain an IME extension. All 116 | files in the directory will be copied to 117 | `build/[config_section_name]/[fallback_ime_name]/`, with `.js` files 118 | getting special treatment to make all this work. 119 | 120 | `options_page` should be the path to the options page to use, relative 121 | to the built extension's root: `[fallback_ime_name]/options.html`, for example. 122 | 123 | How you place an IME extension under `./imes` is up to you. You could 124 | clone a repo there, or symlink to a directory outside the repo. 125 | 126 | Let's see an example of how all this fits together. 127 | 128 | I want to use my emacs keybindings on top of [Chrome SKK][skk], a Japanese 129 | IME, so here's what I do: 130 | 131 | ``` 132 | chrome-skk/ 133 | chrome-key-remapper/ 134 | config.ini 135 | imes/ 136 | skk -> ../../chrome-skk/extension 137 | remapper/ 138 | .. 139 | ``` 140 | 141 | With a config like: 142 | 143 | ``` 144 | [skk_remapped] 145 | name = SKK x emacs 146 | description = SKK with emacs-like cursor movement 147 | language = ja 148 | layout = us 149 | fallback_imes = skk 150 | options_page = skk/options.html 151 | ``` 152 | 153 | When I invoke `python waf configure build`, an IME extension gets built in `./build/`: 154 | 155 | ``` 156 | chrome-key-remapper/ 157 | build/ 158 | skk_remapped/ 159 | manifest.json 160 | remapper/ 161 | main.js 162 | .. 163 | skk/ 164 | main.js 165 | .. 166 | ``` 167 | 168 | With this hybrid IME enabled and active, any key event not handled by 169 | the remapper will get passed onto SKK, enabling me to input Japanese 170 | text while enjoying the familiar emacs-like cursor movement keys. 171 | 172 | [extra-keyboard]: https://github.com/google/extra-keyboards-for-chrome-os 173 | [skk]: https://github.com/jmuk/chrome-skk 174 | -------------------------------------------------------------------------------- /config.ini.sample: -------------------------------------------------------------------------------- 1 | [us_emacs] 2 | name = US x emacs 3 | description = US keyboard with emacs-like cursor movement 4 | language = en-US 5 | layout = us 6 | fallback_imes = 7 | options_page = 8 | -------------------------------------------------------------------------------- /hijack.js: -------------------------------------------------------------------------------- 1 | /* Rewrite calls to chrome.input.ime.onX.addListener as 2 | Remapper.hijack.onX.addListener. 3 | */ 4 | 5 | var fs = require('fs'); 6 | const jscodeshift = require('jscodeshift'); 7 | 8 | function baseFilter() { 9 | return { 10 | callee: { 11 | property: {name: "addListener"}, 12 | object: { 13 | object: { 14 | property: {name: "ime"}, 15 | object: { 16 | property: {name: "input"}, 17 | object: { 18 | name: "chrome" 19 | } 20 | } 21 | } 22 | } 23 | } 24 | }; 25 | } 26 | 27 | // filter for calls like `ime.onKeyData` 28 | function propertyMemberFilter() { 29 | const filter = baseFilter(); 30 | filter.callee.object.computed = false; // ignore dynamic member lookup like `ime[eventName]` 31 | filter.callee.object.property = {type: "Identifier"}; 32 | return filter; 33 | } 34 | 35 | function propertyMemberEvent(path) { 36 | return path.node.callee.object.property.name; 37 | } 38 | 39 | // filter for calls like `ime["onKeyData"]` 40 | function literalMemberFilter() { 41 | const filter = baseFilter(); 42 | filter.callee.object.computed = true; 43 | filter.callee.object.property = {type: "Literal"}; 44 | return filter; 45 | } 46 | 47 | function literalMemberEvent(path) { 48 | return path.node.callee.object.property.value; 49 | } 50 | 51 | function rewrite(j, getEventName) { 52 | return function(path) { 53 | { 54 | j(path).replaceWith( 55 | j.callExpression(j.memberExpression( 56 | j.memberExpression( 57 | j.memberExpression( 58 | j.identifier("Remapper"), 59 | j.identifier("hijack"), // onX 60 | false), 61 | j.identifier(getEventName(path)), // onX 62 | false), 63 | j.identifier(path.node.callee.property.name), // addListener 64 | false), 65 | path.node.arguments) 66 | ); 67 | } 68 | }; 69 | } 70 | 71 | function transformer(file, api) { 72 | const j = api.jscodeshift; 73 | const root = j(file.source); 74 | root.find(j.CallExpression, propertyMemberFilter()) 75 | .forEach(rewrite(j, propertyMemberEvent)); 76 | root.find(j.CallExpression, literalMemberFilter()) 77 | .forEach(rewrite(j, literalMemberEvent)) 78 | return root.toSource(); 79 | } 80 | 81 | if (process.argv.length < 4) { 82 | console.error("you should be invoking waf instead of this script, but if you really need to know:"); 83 | console.error('usage: node hijack.js input_path output_path'); 84 | process.exit(1); 85 | } 86 | 87 | // the jscodeshift command rewrites files in place, and waf doesn't 88 | // like it when the input and output are the same file. so this is a 89 | // simple wrapper around the jscodeshift transformer function that 90 | // reads JS code from the specified input path, transforms it, and 91 | // writes to the specified output path. 92 | fs.readFile(process.argv[2], {encoding: 'utf8'}, function(err, source) { 93 | if (err) { 94 | console.error(err); 95 | process.exit(1); 96 | } 97 | var result = transformer({source: source}, {jscodeshift: jscodeshift}); 98 | fs.writeFile(process.argv[3], result, function(err) { 99 | if (err) { 100 | console.error(err); 101 | process.exit(1); 102 | } 103 | }); 104 | }) 105 | -------------------------------------------------------------------------------- /imes/remapper: -------------------------------------------------------------------------------- 1 | ../remapper -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chromeos-key-remapper", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@ampproject/remapping": { 8 | "version": "2.2.0", 9 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", 10 | "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", 11 | "requires": { 12 | "@jridgewell/gen-mapping": "^0.1.0", 13 | "@jridgewell/trace-mapping": "^0.3.9" 14 | } 15 | }, 16 | "@babel/code-frame": { 17 | "version": "7.21.4", 18 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", 19 | "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", 20 | "requires": { 21 | "@babel/highlight": "^7.18.6" 22 | } 23 | }, 24 | "@babel/compat-data": { 25 | "version": "7.21.4", 26 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", 27 | "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==" 28 | }, 29 | "@babel/core": { 30 | "version": "7.21.4", 31 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", 32 | "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", 33 | "requires": { 34 | "@ampproject/remapping": "^2.2.0", 35 | "@babel/code-frame": "^7.21.4", 36 | "@babel/generator": "^7.21.4", 37 | "@babel/helper-compilation-targets": "^7.21.4", 38 | "@babel/helper-module-transforms": "^7.21.2", 39 | "@babel/helpers": "^7.21.0", 40 | "@babel/parser": "^7.21.4", 41 | "@babel/template": "^7.20.7", 42 | "@babel/traverse": "^7.21.4", 43 | "@babel/types": "^7.21.4", 44 | "convert-source-map": "^1.7.0", 45 | "debug": "^4.1.0", 46 | "gensync": "^1.0.0-beta.2", 47 | "json5": "^2.2.2", 48 | "semver": "^6.3.0" 49 | } 50 | }, 51 | "@babel/generator": { 52 | "version": "7.21.4", 53 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", 54 | "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", 55 | "requires": { 56 | "@babel/types": "^7.21.4", 57 | "@jridgewell/gen-mapping": "^0.3.2", 58 | "@jridgewell/trace-mapping": "^0.3.17", 59 | "jsesc": "^2.5.1" 60 | }, 61 | "dependencies": { 62 | "@jridgewell/gen-mapping": { 63 | "version": "0.3.2", 64 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", 65 | "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", 66 | "requires": { 67 | "@jridgewell/set-array": "^1.0.1", 68 | "@jridgewell/sourcemap-codec": "^1.4.10", 69 | "@jridgewell/trace-mapping": "^0.3.9" 70 | } 71 | } 72 | } 73 | }, 74 | "@babel/helper-annotate-as-pure": { 75 | "version": "7.18.6", 76 | "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", 77 | "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", 78 | "requires": { 79 | "@babel/types": "^7.18.6" 80 | } 81 | }, 82 | "@babel/helper-compilation-targets": { 83 | "version": "7.21.4", 84 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", 85 | "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", 86 | "requires": { 87 | "@babel/compat-data": "^7.21.4", 88 | "@babel/helper-validator-option": "^7.21.0", 89 | "browserslist": "^4.21.3", 90 | "lru-cache": "^5.1.1", 91 | "semver": "^6.3.0" 92 | } 93 | }, 94 | "@babel/helper-create-class-features-plugin": { 95 | "version": "7.21.4", 96 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz", 97 | "integrity": "sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==", 98 | "requires": { 99 | "@babel/helper-annotate-as-pure": "^7.18.6", 100 | "@babel/helper-environment-visitor": "^7.18.9", 101 | "@babel/helper-function-name": "^7.21.0", 102 | "@babel/helper-member-expression-to-functions": "^7.21.0", 103 | "@babel/helper-optimise-call-expression": "^7.18.6", 104 | "@babel/helper-replace-supers": "^7.20.7", 105 | "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", 106 | "@babel/helper-split-export-declaration": "^7.18.6" 107 | } 108 | }, 109 | "@babel/helper-environment-visitor": { 110 | "version": "7.18.9", 111 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", 112 | "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" 113 | }, 114 | "@babel/helper-function-name": { 115 | "version": "7.21.0", 116 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", 117 | "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", 118 | "requires": { 119 | "@babel/template": "^7.20.7", 120 | "@babel/types": "^7.21.0" 121 | } 122 | }, 123 | "@babel/helper-hoist-variables": { 124 | "version": "7.18.6", 125 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", 126 | "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", 127 | "requires": { 128 | "@babel/types": "^7.18.6" 129 | } 130 | }, 131 | "@babel/helper-member-expression-to-functions": { 132 | "version": "7.21.0", 133 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", 134 | "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", 135 | "requires": { 136 | "@babel/types": "^7.21.0" 137 | } 138 | }, 139 | "@babel/helper-module-imports": { 140 | "version": "7.21.4", 141 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", 142 | "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", 143 | "requires": { 144 | "@babel/types": "^7.21.4" 145 | } 146 | }, 147 | "@babel/helper-module-transforms": { 148 | "version": "7.21.2", 149 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", 150 | "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", 151 | "requires": { 152 | "@babel/helper-environment-visitor": "^7.18.9", 153 | "@babel/helper-module-imports": "^7.18.6", 154 | "@babel/helper-simple-access": "^7.20.2", 155 | "@babel/helper-split-export-declaration": "^7.18.6", 156 | "@babel/helper-validator-identifier": "^7.19.1", 157 | "@babel/template": "^7.20.7", 158 | "@babel/traverse": "^7.21.2", 159 | "@babel/types": "^7.21.2" 160 | } 161 | }, 162 | "@babel/helper-optimise-call-expression": { 163 | "version": "7.18.6", 164 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", 165 | "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", 166 | "requires": { 167 | "@babel/types": "^7.18.6" 168 | } 169 | }, 170 | "@babel/helper-plugin-utils": { 171 | "version": "7.20.2", 172 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", 173 | "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" 174 | }, 175 | "@babel/helper-replace-supers": { 176 | "version": "7.20.7", 177 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", 178 | "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", 179 | "requires": { 180 | "@babel/helper-environment-visitor": "^7.18.9", 181 | "@babel/helper-member-expression-to-functions": "^7.20.7", 182 | "@babel/helper-optimise-call-expression": "^7.18.6", 183 | "@babel/template": "^7.20.7", 184 | "@babel/traverse": "^7.20.7", 185 | "@babel/types": "^7.20.7" 186 | } 187 | }, 188 | "@babel/helper-simple-access": { 189 | "version": "7.20.2", 190 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", 191 | "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", 192 | "requires": { 193 | "@babel/types": "^7.20.2" 194 | } 195 | }, 196 | "@babel/helper-skip-transparent-expression-wrappers": { 197 | "version": "7.20.0", 198 | "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", 199 | "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", 200 | "requires": { 201 | "@babel/types": "^7.20.0" 202 | } 203 | }, 204 | "@babel/helper-split-export-declaration": { 205 | "version": "7.18.6", 206 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", 207 | "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", 208 | "requires": { 209 | "@babel/types": "^7.18.6" 210 | } 211 | }, 212 | "@babel/helper-string-parser": { 213 | "version": "7.19.4", 214 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", 215 | "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" 216 | }, 217 | "@babel/helper-validator-identifier": { 218 | "version": "7.19.1", 219 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", 220 | "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" 221 | }, 222 | "@babel/helper-validator-option": { 223 | "version": "7.21.0", 224 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", 225 | "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==" 226 | }, 227 | "@babel/helpers": { 228 | "version": "7.21.0", 229 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", 230 | "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", 231 | "requires": { 232 | "@babel/template": "^7.20.7", 233 | "@babel/traverse": "^7.21.0", 234 | "@babel/types": "^7.21.0" 235 | } 236 | }, 237 | "@babel/highlight": { 238 | "version": "7.18.6", 239 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 240 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 241 | "requires": { 242 | "@babel/helper-validator-identifier": "^7.18.6", 243 | "chalk": "^2.0.0", 244 | "js-tokens": "^4.0.0" 245 | }, 246 | "dependencies": { 247 | "chalk": { 248 | "version": "2.4.2", 249 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 250 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 251 | "requires": { 252 | "ansi-styles": "^3.2.1", 253 | "escape-string-regexp": "^1.0.5", 254 | "supports-color": "^5.3.0" 255 | } 256 | } 257 | } 258 | }, 259 | "@babel/parser": { 260 | "version": "7.21.4", 261 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", 262 | "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==" 263 | }, 264 | "@babel/plugin-proposal-class-properties": { 265 | "version": "7.18.6", 266 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", 267 | "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", 268 | "requires": { 269 | "@babel/helper-create-class-features-plugin": "^7.18.6", 270 | "@babel/helper-plugin-utils": "^7.18.6" 271 | } 272 | }, 273 | "@babel/plugin-proposal-nullish-coalescing-operator": { 274 | "version": "7.18.6", 275 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", 276 | "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", 277 | "requires": { 278 | "@babel/helper-plugin-utils": "^7.18.6", 279 | "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" 280 | } 281 | }, 282 | "@babel/plugin-proposal-optional-chaining": { 283 | "version": "7.21.0", 284 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", 285 | "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", 286 | "requires": { 287 | "@babel/helper-plugin-utils": "^7.20.2", 288 | "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", 289 | "@babel/plugin-syntax-optional-chaining": "^7.8.3" 290 | } 291 | }, 292 | "@babel/plugin-syntax-flow": { 293 | "version": "7.21.4", 294 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz", 295 | "integrity": "sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==", 296 | "requires": { 297 | "@babel/helper-plugin-utils": "^7.20.2" 298 | } 299 | }, 300 | "@babel/plugin-syntax-jsx": { 301 | "version": "7.21.4", 302 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", 303 | "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", 304 | "requires": { 305 | "@babel/helper-plugin-utils": "^7.20.2" 306 | } 307 | }, 308 | "@babel/plugin-syntax-nullish-coalescing-operator": { 309 | "version": "7.8.3", 310 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", 311 | "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", 312 | "requires": { 313 | "@babel/helper-plugin-utils": "^7.8.0" 314 | } 315 | }, 316 | "@babel/plugin-syntax-optional-chaining": { 317 | "version": "7.8.3", 318 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", 319 | "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", 320 | "requires": { 321 | "@babel/helper-plugin-utils": "^7.8.0" 322 | } 323 | }, 324 | "@babel/plugin-syntax-typescript": { 325 | "version": "7.21.4", 326 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", 327 | "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", 328 | "requires": { 329 | "@babel/helper-plugin-utils": "^7.20.2" 330 | } 331 | }, 332 | "@babel/plugin-transform-flow-strip-types": { 333 | "version": "7.21.0", 334 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.21.0.tgz", 335 | "integrity": "sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==", 336 | "requires": { 337 | "@babel/helper-plugin-utils": "^7.20.2", 338 | "@babel/plugin-syntax-flow": "^7.18.6" 339 | } 340 | }, 341 | "@babel/plugin-transform-modules-commonjs": { 342 | "version": "7.21.2", 343 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", 344 | "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", 345 | "requires": { 346 | "@babel/helper-module-transforms": "^7.21.2", 347 | "@babel/helper-plugin-utils": "^7.20.2", 348 | "@babel/helper-simple-access": "^7.20.2" 349 | } 350 | }, 351 | "@babel/plugin-transform-typescript": { 352 | "version": "7.21.3", 353 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz", 354 | "integrity": "sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==", 355 | "requires": { 356 | "@babel/helper-annotate-as-pure": "^7.18.6", 357 | "@babel/helper-create-class-features-plugin": "^7.21.0", 358 | "@babel/helper-plugin-utils": "^7.20.2", 359 | "@babel/plugin-syntax-typescript": "^7.20.0" 360 | } 361 | }, 362 | "@babel/preset-flow": { 363 | "version": "7.21.4", 364 | "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.21.4.tgz", 365 | "integrity": "sha512-F24cSq4DIBmhq4OzK3dE63NHagb27OPE3eWR+HLekt4Z3Y5MzIIUGF3LlLgV0gN8vzbDViSY7HnrReNVCJXTeA==", 366 | "requires": { 367 | "@babel/helper-plugin-utils": "^7.20.2", 368 | "@babel/helper-validator-option": "^7.21.0", 369 | "@babel/plugin-transform-flow-strip-types": "^7.21.0" 370 | } 371 | }, 372 | "@babel/preset-typescript": { 373 | "version": "7.21.4", 374 | "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.4.tgz", 375 | "integrity": "sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A==", 376 | "requires": { 377 | "@babel/helper-plugin-utils": "^7.20.2", 378 | "@babel/helper-validator-option": "^7.21.0", 379 | "@babel/plugin-syntax-jsx": "^7.21.4", 380 | "@babel/plugin-transform-modules-commonjs": "^7.21.2", 381 | "@babel/plugin-transform-typescript": "^7.21.3" 382 | } 383 | }, 384 | "@babel/register": { 385 | "version": "7.21.0", 386 | "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.21.0.tgz", 387 | "integrity": "sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==", 388 | "requires": { 389 | "clone-deep": "^4.0.1", 390 | "find-cache-dir": "^2.0.0", 391 | "make-dir": "^2.1.0", 392 | "pirates": "^4.0.5", 393 | "source-map-support": "^0.5.16" 394 | } 395 | }, 396 | "@babel/template": { 397 | "version": "7.20.7", 398 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", 399 | "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", 400 | "requires": { 401 | "@babel/code-frame": "^7.18.6", 402 | "@babel/parser": "^7.20.7", 403 | "@babel/types": "^7.20.7" 404 | } 405 | }, 406 | "@babel/traverse": { 407 | "version": "7.21.4", 408 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", 409 | "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", 410 | "requires": { 411 | "@babel/code-frame": "^7.21.4", 412 | "@babel/generator": "^7.21.4", 413 | "@babel/helper-environment-visitor": "^7.18.9", 414 | "@babel/helper-function-name": "^7.21.0", 415 | "@babel/helper-hoist-variables": "^7.18.6", 416 | "@babel/helper-split-export-declaration": "^7.18.6", 417 | "@babel/parser": "^7.21.4", 418 | "@babel/types": "^7.21.4", 419 | "debug": "^4.1.0", 420 | "globals": "^11.1.0" 421 | } 422 | }, 423 | "@babel/types": { 424 | "version": "7.21.4", 425 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", 426 | "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", 427 | "requires": { 428 | "@babel/helper-string-parser": "^7.19.4", 429 | "@babel/helper-validator-identifier": "^7.19.1", 430 | "to-fast-properties": "^2.0.0" 431 | } 432 | }, 433 | "@jridgewell/gen-mapping": { 434 | "version": "0.1.1", 435 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", 436 | "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", 437 | "requires": { 438 | "@jridgewell/set-array": "^1.0.0", 439 | "@jridgewell/sourcemap-codec": "^1.4.10" 440 | } 441 | }, 442 | "@jridgewell/resolve-uri": { 443 | "version": "3.1.0", 444 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 445 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" 446 | }, 447 | "@jridgewell/set-array": { 448 | "version": "1.1.2", 449 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 450 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" 451 | }, 452 | "@jridgewell/sourcemap-codec": { 453 | "version": "1.4.14", 454 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 455 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" 456 | }, 457 | "@jridgewell/trace-mapping": { 458 | "version": "0.3.17", 459 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", 460 | "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", 461 | "requires": { 462 | "@jridgewell/resolve-uri": "3.1.0", 463 | "@jridgewell/sourcemap-codec": "1.4.14" 464 | } 465 | }, 466 | "ansi-styles": { 467 | "version": "3.2.1", 468 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 469 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 470 | "requires": { 471 | "color-convert": "^1.9.0" 472 | } 473 | }, 474 | "ast-types": { 475 | "version": "0.15.2", 476 | "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", 477 | "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", 478 | "requires": { 479 | "tslib": "^2.0.1" 480 | } 481 | }, 482 | "babel-core": { 483 | "version": "7.0.0-bridge.0", 484 | "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", 485 | "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==" 486 | }, 487 | "balanced-match": { 488 | "version": "1.0.2", 489 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 490 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 491 | }, 492 | "brace-expansion": { 493 | "version": "1.1.11", 494 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 495 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 496 | "requires": { 497 | "balanced-match": "^1.0.0", 498 | "concat-map": "0.0.1" 499 | } 500 | }, 501 | "braces": { 502 | "version": "3.0.2", 503 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 504 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 505 | "requires": { 506 | "fill-range": "^7.0.1" 507 | } 508 | }, 509 | "browserslist": { 510 | "version": "4.21.5", 511 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", 512 | "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", 513 | "requires": { 514 | "caniuse-lite": "^1.0.30001449", 515 | "electron-to-chromium": "^1.4.284", 516 | "node-releases": "^2.0.8", 517 | "update-browserslist-db": "^1.0.10" 518 | } 519 | }, 520 | "buffer-from": { 521 | "version": "1.1.2", 522 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 523 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 524 | }, 525 | "caniuse-lite": { 526 | "version": "1.0.30001474", 527 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001474.tgz", 528 | "integrity": "sha512-iaIZ8gVrWfemh5DG3T9/YqarVZoYf0r188IjaGwx68j4Pf0SGY6CQkmJUIE+NZHkkecQGohzXmBGEwWDr9aM3Q==" 529 | }, 530 | "chalk": { 531 | "version": "4.1.2", 532 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 533 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 534 | "requires": { 535 | "ansi-styles": "^4.1.0", 536 | "supports-color": "^7.1.0" 537 | }, 538 | "dependencies": { 539 | "ansi-styles": { 540 | "version": "4.3.0", 541 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 542 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 543 | "requires": { 544 | "color-convert": "^2.0.1" 545 | } 546 | }, 547 | "color-convert": { 548 | "version": "2.0.1", 549 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 550 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 551 | "requires": { 552 | "color-name": "~1.1.4" 553 | } 554 | }, 555 | "color-name": { 556 | "version": "1.1.4", 557 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 558 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 559 | }, 560 | "has-flag": { 561 | "version": "4.0.0", 562 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 563 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 564 | }, 565 | "supports-color": { 566 | "version": "7.2.0", 567 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 568 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 569 | "requires": { 570 | "has-flag": "^4.0.0" 571 | } 572 | } 573 | } 574 | }, 575 | "clone-deep": { 576 | "version": "4.0.1", 577 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", 578 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", 579 | "requires": { 580 | "is-plain-object": "^2.0.4", 581 | "kind-of": "^6.0.2", 582 | "shallow-clone": "^3.0.0" 583 | } 584 | }, 585 | "color-convert": { 586 | "version": "1.9.3", 587 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 588 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 589 | "requires": { 590 | "color-name": "1.1.3" 591 | } 592 | }, 593 | "color-name": { 594 | "version": "1.1.3", 595 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 596 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 597 | }, 598 | "commondir": { 599 | "version": "1.0.1", 600 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 601 | "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" 602 | }, 603 | "concat-map": { 604 | "version": "0.0.1", 605 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 606 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 607 | }, 608 | "convert-source-map": { 609 | "version": "1.9.0", 610 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", 611 | "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" 612 | }, 613 | "debug": { 614 | "version": "4.3.4", 615 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 616 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 617 | "requires": { 618 | "ms": "2.1.2" 619 | } 620 | }, 621 | "electron-to-chromium": { 622 | "version": "1.4.350", 623 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.350.tgz", 624 | "integrity": "sha512-XnXcWpVnOfHZ4C3NPiL+SubeoGV8zc/pg8GEubRtc1dPA/9jKS2vsOPmtClJHhWxUb2RSGC1OBLCbgNUJMtZPw==" 625 | }, 626 | "escalade": { 627 | "version": "3.1.1", 628 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 629 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 630 | }, 631 | "escape-string-regexp": { 632 | "version": "1.0.5", 633 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 634 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" 635 | }, 636 | "esprima": { 637 | "version": "4.0.1", 638 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 639 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 640 | }, 641 | "fill-range": { 642 | "version": "7.0.1", 643 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 644 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 645 | "requires": { 646 | "to-regex-range": "^5.0.1" 647 | } 648 | }, 649 | "find-cache-dir": { 650 | "version": "2.1.0", 651 | "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", 652 | "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", 653 | "requires": { 654 | "commondir": "^1.0.1", 655 | "make-dir": "^2.0.0", 656 | "pkg-dir": "^3.0.0" 657 | } 658 | }, 659 | "find-up": { 660 | "version": "3.0.0", 661 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 662 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 663 | "requires": { 664 | "locate-path": "^3.0.0" 665 | } 666 | }, 667 | "flow-parser": { 668 | "version": "0.203.1", 669 | "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.203.1.tgz", 670 | "integrity": "sha512-Nw2M8MPP/Zb+yhvmPDEjzkCXLtgyWGKXZjAYOVftm+wIf3xd4FKa7nRI9v67rODs0WzxMbPc8IPs/7o/dyxo/Q==" 671 | }, 672 | "fs.realpath": { 673 | "version": "1.0.0", 674 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 675 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 676 | }, 677 | "gensync": { 678 | "version": "1.0.0-beta.2", 679 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 680 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" 681 | }, 682 | "glob": { 683 | "version": "7.2.3", 684 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 685 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 686 | "requires": { 687 | "fs.realpath": "^1.0.0", 688 | "inflight": "^1.0.4", 689 | "inherits": "2", 690 | "minimatch": "^3.1.1", 691 | "once": "^1.3.0", 692 | "path-is-absolute": "^1.0.0" 693 | } 694 | }, 695 | "globals": { 696 | "version": "11.12.0", 697 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 698 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" 699 | }, 700 | "graceful-fs": { 701 | "version": "4.2.11", 702 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 703 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" 704 | }, 705 | "has-flag": { 706 | "version": "3.0.0", 707 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 708 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" 709 | }, 710 | "imurmurhash": { 711 | "version": "0.1.4", 712 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 713 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" 714 | }, 715 | "inflight": { 716 | "version": "1.0.6", 717 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 718 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 719 | "requires": { 720 | "once": "^1.3.0", 721 | "wrappy": "1" 722 | } 723 | }, 724 | "inherits": { 725 | "version": "2.0.4", 726 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 727 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 728 | }, 729 | "is-number": { 730 | "version": "7.0.0", 731 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 732 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 733 | }, 734 | "is-plain-object": { 735 | "version": "2.0.4", 736 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 737 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 738 | "requires": { 739 | "isobject": "^3.0.1" 740 | } 741 | }, 742 | "isobject": { 743 | "version": "3.0.1", 744 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 745 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" 746 | }, 747 | "js-tokens": { 748 | "version": "4.0.0", 749 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 750 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 751 | }, 752 | "jscodeshift": { 753 | "version": "0.14.0", 754 | "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", 755 | "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", 756 | "requires": { 757 | "@babel/core": "^7.13.16", 758 | "@babel/parser": "^7.13.16", 759 | "@babel/plugin-proposal-class-properties": "^7.13.0", 760 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", 761 | "@babel/plugin-proposal-optional-chaining": "^7.13.12", 762 | "@babel/plugin-transform-modules-commonjs": "^7.13.8", 763 | "@babel/preset-flow": "^7.13.13", 764 | "@babel/preset-typescript": "^7.13.0", 765 | "@babel/register": "^7.13.16", 766 | "babel-core": "^7.0.0-bridge.0", 767 | "chalk": "^4.1.2", 768 | "flow-parser": "0.*", 769 | "graceful-fs": "^4.2.4", 770 | "micromatch": "^4.0.4", 771 | "neo-async": "^2.5.0", 772 | "node-dir": "^0.1.17", 773 | "recast": "^0.21.0", 774 | "temp": "^0.8.4", 775 | "write-file-atomic": "^2.3.0" 776 | } 777 | }, 778 | "jsesc": { 779 | "version": "2.5.2", 780 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 781 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" 782 | }, 783 | "json5": { 784 | "version": "2.2.3", 785 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 786 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" 787 | }, 788 | "kind-of": { 789 | "version": "6.0.3", 790 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 791 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" 792 | }, 793 | "locate-path": { 794 | "version": "3.0.0", 795 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 796 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 797 | "requires": { 798 | "p-locate": "^3.0.0", 799 | "path-exists": "^3.0.0" 800 | } 801 | }, 802 | "lru-cache": { 803 | "version": "5.1.1", 804 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 805 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 806 | "requires": { 807 | "yallist": "^3.0.2" 808 | } 809 | }, 810 | "make-dir": { 811 | "version": "2.1.0", 812 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", 813 | "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", 814 | "requires": { 815 | "pify": "^4.0.1", 816 | "semver": "^5.6.0" 817 | }, 818 | "dependencies": { 819 | "semver": { 820 | "version": "5.7.1", 821 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 822 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 823 | } 824 | } 825 | }, 826 | "micromatch": { 827 | "version": "4.0.5", 828 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 829 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 830 | "requires": { 831 | "braces": "^3.0.2", 832 | "picomatch": "^2.3.1" 833 | } 834 | }, 835 | "minimatch": { 836 | "version": "3.1.2", 837 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 838 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 839 | "requires": { 840 | "brace-expansion": "^1.1.7" 841 | } 842 | }, 843 | "ms": { 844 | "version": "2.1.2", 845 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 846 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 847 | }, 848 | "neo-async": { 849 | "version": "2.6.2", 850 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", 851 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" 852 | }, 853 | "node-dir": { 854 | "version": "0.1.17", 855 | "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", 856 | "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", 857 | "requires": { 858 | "minimatch": "^3.0.2" 859 | } 860 | }, 861 | "node-releases": { 862 | "version": "2.0.10", 863 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", 864 | "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" 865 | }, 866 | "once": { 867 | "version": "1.4.0", 868 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 869 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 870 | "requires": { 871 | "wrappy": "1" 872 | } 873 | }, 874 | "p-limit": { 875 | "version": "2.3.0", 876 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 877 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 878 | "requires": { 879 | "p-try": "^2.0.0" 880 | } 881 | }, 882 | "p-locate": { 883 | "version": "3.0.0", 884 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 885 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 886 | "requires": { 887 | "p-limit": "^2.0.0" 888 | } 889 | }, 890 | "p-try": { 891 | "version": "2.2.0", 892 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 893 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" 894 | }, 895 | "path-exists": { 896 | "version": "3.0.0", 897 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 898 | "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" 899 | }, 900 | "path-is-absolute": { 901 | "version": "1.0.1", 902 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 903 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 904 | }, 905 | "picocolors": { 906 | "version": "1.0.0", 907 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 908 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 909 | }, 910 | "picomatch": { 911 | "version": "2.3.1", 912 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 913 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" 914 | }, 915 | "pify": { 916 | "version": "4.0.1", 917 | "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", 918 | "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" 919 | }, 920 | "pirates": { 921 | "version": "4.0.5", 922 | "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", 923 | "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" 924 | }, 925 | "pkg-dir": { 926 | "version": "3.0.0", 927 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", 928 | "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", 929 | "requires": { 930 | "find-up": "^3.0.0" 931 | } 932 | }, 933 | "recast": { 934 | "version": "0.21.5", 935 | "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", 936 | "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", 937 | "requires": { 938 | "ast-types": "0.15.2", 939 | "esprima": "~4.0.0", 940 | "source-map": "~0.6.1", 941 | "tslib": "^2.0.1" 942 | } 943 | }, 944 | "rimraf": { 945 | "version": "2.6.3", 946 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", 947 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", 948 | "requires": { 949 | "glob": "^7.1.3" 950 | } 951 | }, 952 | "semver": { 953 | "version": "6.3.0", 954 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 955 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 956 | }, 957 | "shallow-clone": { 958 | "version": "3.0.1", 959 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", 960 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", 961 | "requires": { 962 | "kind-of": "^6.0.2" 963 | } 964 | }, 965 | "signal-exit": { 966 | "version": "3.0.7", 967 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 968 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" 969 | }, 970 | "source-map": { 971 | "version": "0.6.1", 972 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 973 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 974 | }, 975 | "source-map-support": { 976 | "version": "0.5.21", 977 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 978 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 979 | "requires": { 980 | "buffer-from": "^1.0.0", 981 | "source-map": "^0.6.0" 982 | } 983 | }, 984 | "supports-color": { 985 | "version": "5.5.0", 986 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 987 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 988 | "requires": { 989 | "has-flag": "^3.0.0" 990 | } 991 | }, 992 | "temp": { 993 | "version": "0.8.4", 994 | "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", 995 | "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", 996 | "requires": { 997 | "rimraf": "~2.6.2" 998 | } 999 | }, 1000 | "to-fast-properties": { 1001 | "version": "2.0.0", 1002 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1003 | "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" 1004 | }, 1005 | "to-regex-range": { 1006 | "version": "5.0.1", 1007 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1008 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1009 | "requires": { 1010 | "is-number": "^7.0.0" 1011 | } 1012 | }, 1013 | "tslib": { 1014 | "version": "2.5.0", 1015 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", 1016 | "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" 1017 | }, 1018 | "update-browserslist-db": { 1019 | "version": "1.0.10", 1020 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", 1021 | "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", 1022 | "requires": { 1023 | "escalade": "^3.1.1", 1024 | "picocolors": "^1.0.0" 1025 | } 1026 | }, 1027 | "wrappy": { 1028 | "version": "1.0.2", 1029 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1030 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1031 | }, 1032 | "write-file-atomic": { 1033 | "version": "2.4.3", 1034 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", 1035 | "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", 1036 | "requires": { 1037 | "graceful-fs": "^4.1.11", 1038 | "imurmurhash": "^0.1.4", 1039 | "signal-exit": "^3.0.2" 1040 | } 1041 | }, 1042 | "yallist": { 1043 | "version": "3.1.1", 1044 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1045 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1046 | } 1047 | } 1048 | } 1049 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chromeos-key-remapper", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "background.js", 6 | "dependencies": { 7 | "jscodeshift": "^0.14.0" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/ento/chromeos-key-remapper.git" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/ento/chromeos-key-remapper/issues" 21 | }, 22 | "homepage": "https://github.com/ento/chromeos-key-remapper#readme" 23 | } 24 | -------------------------------------------------------------------------------- /remapper/engine.js: -------------------------------------------------------------------------------- 1 | Remapper.Engine = function (keymap) { 2 | var contextId = -1; 3 | var lastFocusedWindowUrl = null; 4 | const debug = false; 5 | 6 | const urlBlacklist = [ 7 | 'chrome-extension://pnhechapfaindjhompbnflcldabbghjo/html/crosh.html' 8 | ]; 9 | 10 | const nullKeyData = { 11 | 'altKey': false, 12 | 'ctrlKey': false, 13 | 'shiftKey': false, 14 | 'key': '', 15 | 'code': '' 16 | }; 17 | 18 | const sequencePrefixToKeyDataAttribute = { 19 | 'C-': 'ctrlKey', 20 | 'S-': 'shiftKey', 21 | 'M-': 'altKey' 22 | } 23 | 24 | function keyDataToSequenceString(keyData) { 25 | var sequence = ''; 26 | if (keyData.ctrlKey) { 27 | sequence += 'C-'; 28 | } 29 | if (keyData.shiftKey) { 30 | sequence += 'S-'; 31 | } 32 | if (keyData.altKey) { 33 | sequence += 'M-'; 34 | } 35 | sequence += keyData.key; 36 | return sequence; 37 | } 38 | 39 | function sequenceStringToKeyData(sequence) { 40 | var keyData = {}; 41 | sequence.split(/(C-|M-|S-)/).forEach(function(part) { 42 | if (part.length == 0) { 43 | return; 44 | } 45 | var booleanAttribute = sequencePrefixToKeyDataAttribute[part]; 46 | if (booleanAttribute) { 47 | keyData[booleanAttribute] = true; 48 | return; 49 | } 50 | // TODO: validate part is valid as code 51 | // Note: allegedly, only the `code` matters when using the `sendKeyEvents` API. 52 | keyData.code = part; 53 | }); 54 | return keyData; 55 | } 56 | 57 | // grab the last focused window's URL for blacklisting. note that there will 58 | // be a delay due to the API being async. 59 | this.handleFocus = function(context) { 60 | contextId = context.contextID; 61 | chrome.windows.getLastFocused({ 62 | populate: true, 63 | windowTypes: ['popup', 'normal', 'panel', 'app', 'devtools'] 64 | }, function(window) { 65 | if (window && window.tabs.length > 0) { 66 | lastFocusedWindowUrl = window.tabs[0].url; 67 | } 68 | }); 69 | } 70 | 71 | this.handleKeyEvent = function(engineID, keyData) { 72 | if (keyData.type === "keydown") { 73 | if (debug) { 74 | console.log(keyData.type, keyData.key, keyData.code, keyData); 75 | } 76 | } 77 | 78 | if (keyData.extensionId && (keyData.extensionId === chrome.runtime.id)) { 79 | // already remapped, pass it through 80 | return false; 81 | } 82 | 83 | if (lastFocusedWindowUrl && urlBlacklist.indexOf(lastFocusedWindowUrl) !== -1) { 84 | // don't remap in blacklisted windows 85 | return false; 86 | } 87 | 88 | var handled = false; 89 | 90 | if (keyData.type === "keydown") { 91 | var encodedSequence = keyDataToSequenceString(keyData); 92 | 93 | // TODO: convert keymap to an object of {match: decodedSequences} for speed 94 | var activeMapping = keymap.find(function(candidate) { 95 | return encodedSequence === candidate.match; 96 | }); 97 | 98 | if (activeMapping) { 99 | var newKeyData = activeMapping.emit.map(function(sequence) { 100 | var mappedKeyData = sequenceStringToKeyData(sequence); 101 | return Object.assign({}, keyData, nullKeyData, mappedKeyData); 102 | }); 103 | chrome.input.ime.sendKeyEvents({"contextID": contextId, "keyData": newKeyData}); 104 | handled = true; 105 | } 106 | } 107 | 108 | return handled; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /remapper/keymap.js: -------------------------------------------------------------------------------- 1 | // bindings for emacs-like cursor movements. 2 | // variable name must match what's referenced in main.js. 3 | // TODO: better documentation on what values are accepted. 4 | const keymap = [ 5 | {'match': 'C-a', 'emit': ['Home']}, // cursor: beginning of line 6 | {'match': 'C-e', 'emit': ['End']}, // cursor: end of line 7 | {'match': 'C-f', 'emit': ['ArrowRight']}, // cursor: forward one character 8 | {'match': 'C-b', 'emit': ['ArrowLeft']}, // cursor: back one character 9 | {'match': 'C-p', 'emit': ['ArrowUp']}, // cursor: previous line 10 | {'match': 'C-n', 'emit': ['ArrowDown']}, // cursor: next line 11 | {'match': 'C-k', 'emit': ['S-End', 'Backspace']}, // cursor: cut to end of line 12 | {'match': 'C-h', 'emit': ['Backspace']}, // cursor: backspace 13 | {'match': 'C-d', 'emit': ['Delete']}, // cursor: delete one char 14 | {'match': 'M-a', 'emit': ['C-KeyA']}, // C-a replacement: for select all 15 | {'match': 'M-b', 'emit': ['C-KeyB']}, // C-b replacement: for boldening text on paper 16 | {'match': 'M-n', 'emit': ['C-KeyN']}, // C-n replacement: for opening a new window 17 | {'match': 'M-k', 'emit': ['C-KeyK']} // C-k replacement: for Slack channel switcher 18 | ]; 19 | -------------------------------------------------------------------------------- /remapper/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var remapper = new Remapper.Engine(keymap); 3 | Remapper.hijack.onFocus.addListener(remapper.handleFocus.bind(remapper)); 4 | Remapper.hijack.onKeyEvent.addListener(remapper.handleKeyEvent.bind(remapper)); 5 | })(); 6 | -------------------------------------------------------------------------------- /remapper/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "US keyboard x emacs", 3 | "version": "1.0", 4 | "manifest_version": 2, 5 | "description": "US keyboard with emacs-like cursor movement", 6 | "background": { 7 | "scripts": [ 8 | "preamble.js", 9 | "engine.js", 10 | "keymap.js", 11 | "main.js" 12 | ] 13 | }, 14 | "permissions": [ 15 | "input", "tabs" 16 | ], 17 | "input_components": [ 18 | { 19 | "name": "US x emacs", 20 | "type": "ime", 21 | "id": "io.github.ento.cros_key_remapper", 22 | "description": "US keyboard with emacs-like cursor movement", 23 | "language": "en-US", 24 | "layouts": ["us"] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /remapper/preamble.js: -------------------------------------------------------------------------------- 1 | const Remapper = {}; 2 | 3 | // Acts as a middleman that accepts an chrome.input.ime event and 4 | // feeds it to registered event listeners. First listener to register 5 | // gets to handle the event first. 6 | Remapper.EventHandler = function EventHandler() { 7 | this.listeners = [] 8 | }; 9 | 10 | Remapper.EventHandler.prototype.addListener = function(fn) { 11 | this.listeners.push(fn) 12 | }; 13 | 14 | Remapper.EventHandler.prototype.handleEvent = function() { 15 | let handled = false; 16 | for (let listener of this.listeners) { 17 | handled = listener.apply(null, arguments); 18 | if (handled) break; 19 | } 20 | return handled; 21 | }; 22 | 23 | // Array of events to hijack. 24 | // see: https://developer.chrome.com/extensions/input_ime 25 | Remapper.events = [ 26 | 'onActivate', 27 | 'onDeactivated', 28 | 'onFocus', 29 | 'onBlur', 30 | 'onInputContextUpdate', 31 | 'onKeyEvent', 32 | 'onCandidateClicked', 33 | 'onMenuItemActivated', 34 | 'onSurroundingTextChanged', 35 | 'onReset' 36 | // 'onCompositionBoundsChanged' // appears to be private 37 | ]; 38 | 39 | // Name must match what's in hijack.js 40 | Remapper.hijack = {}; 41 | 42 | Remapper.events.forEach(function(event) { 43 | const handler = new Remapper.EventHandler() 44 | Remapper.hijack[event] = handler; 45 | // The entire plot hinges on this `addListener` call not being 46 | // picked up by hijack.js, because this extension is just another 47 | // ime to be composed together with fallback imes. 48 | chrome.input.ime[event].addListener(handler.handleEvent.bind(handler)); 49 | }) 50 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | import configparser 2 | from collections import OrderedDict 3 | 4 | from waflib import Task 5 | from waflib.TaskGen import feature, after_method 6 | 7 | 8 | top = '.' 9 | out = 'build' 10 | 11 | required_configs = [ 12 | 'name', 13 | 'description', 14 | 'language', 15 | 'layout', 16 | 'fallback_imes', 17 | 'options_page', 18 | ] 19 | 20 | 21 | def configure(ctx): 22 | ctx.find_program('jscodeshift') 23 | 24 | 25 | def build(ctx): 26 | config = _read_config() 27 | for name in config: 28 | _build_ime(ctx, name, config[name]) 29 | 30 | 31 | def _read_config(): 32 | parser = configparser.ConfigParser() 33 | parser.read('config.ini') 34 | config = OrderedDict() 35 | for name in parser.sections(): 36 | spec = {} 37 | for prop in required_configs: 38 | # TODO: more human-friendly error 39 | spec[prop] = parser[name][prop] 40 | spec['fallback_imes'] = [ime_name.strip() 41 | for ime_name in spec['fallback_imes'].split(',') 42 | if len(ime_name.strip()) > 0] 43 | config[name] = spec 44 | return config 45 | 46 | 47 | def _build_ime(ctx, name, spec): 48 | out = ctx.bldnode.find_or_declare(name) 49 | 50 | # copy / transform sources 51 | 52 | imes_root = ctx.path.find_dir('imes') 53 | transformer = ctx.path.find_node('hijack.js') 54 | 55 | # remapper must be the first in stack in order for hijacking to work. 56 | ime_stack = ['remapper'] + spec['fallback_imes'] 57 | for ime_name in ime_stack: 58 | extension_root = imes_root.find_dir(ime_name) 59 | for extension_file in extension_root.ant_glob('**/*'): 60 | is_javascript = extension_file.suffix() == '.js' 61 | source = extension_file 62 | target = out.find_or_declare(extension_file.path_from(imes_root)) 63 | if is_javascript: 64 | jscodeshift_env = ctx.env.derive() 65 | jscodeshift_env.TRANSFORMER = transformer.abspath() 66 | jscodeshift_task = jscodeshift(env=jscodeshift_env) 67 | jscodeshift_task.set_inputs(source) 68 | jscodeshift_task.set_outputs(target) 69 | ctx.add_to_group(jscodeshift_task) 70 | ctx.add_manual_dependency(source, transformer) 71 | else: 72 | ctx(features='subst', 73 | is_copy=True, 74 | source=source, 75 | target=target) 76 | 77 | # build manifest 78 | 79 | manifests = [out.find_or_declare(ime_name).find_or_declare('manifest.json') 80 | for ime_name in ime_stack] 81 | manifest_env = ctx.env.derive() 82 | manifest_env.identifier = name 83 | manifest_env.name = spec['name'] 84 | manifest_env.description = spec['description'] 85 | manifest_env.language = spec['language'] 86 | manifest_env.layout = spec['layout'] 87 | manifest_env.options_page = spec['options_page'] 88 | manifest_task = manifest(env=manifest_env) 89 | manifest_task.set_inputs(manifests) 90 | manifest_task.set_outputs(out.find_or_declare('manifest.json')) 91 | ctx.add_to_group(manifest_task) 92 | 93 | 94 | class jscodeshift(Task.Task): 95 | run_str = 'node ${TRANSFORMER} ${SRC} ${TGT}' 96 | 97 | 98 | class manifest(Task.Task): 99 | ''' 100 | Builds a manifest JSON by collecting background scripts and permissions 101 | from input files and reading the name, description, language, and layout 102 | from `self.env`. 103 | ''' 104 | 105 | def run(self): 106 | target = self.outputs[0] 107 | out = target.parent 108 | submanifests = OrderedDict([(path, path.read_json()) for path in self.inputs]) 109 | 110 | scripts = [path.parent.find_or_declare(script).path_from(out) 111 | for path, submanifest in submanifests.items() 112 | for script in submanifest['background']['scripts']] 113 | 114 | permissions = [permission 115 | for path, submanifest in submanifests.items() 116 | for permission in submanifest['permissions']] 117 | 118 | input_component = { 119 | "name": self.env.name, 120 | "type": "ime", 121 | "id": "io.github.ento.cros_key_remapper." + self.env.identifier, 122 | "description": self.env.description, 123 | "language": self.env.language, 124 | "layouts": [self.env.layout], 125 | } 126 | manifest = { 127 | "name": self.env.name, 128 | "version": "1.0", 129 | "manifest_version": 2, 130 | "description": self.env.description, 131 | "background": { 132 | "scripts": scripts, 133 | }, 134 | "permissions": list(set(permissions)), 135 | "input_components": [ 136 | input_component 137 | ] 138 | } 139 | if self.env.options_page: 140 | manifest['options_page'] = self.env.options_page 141 | target.write_json(manifest) 142 | 143 | 144 | def imes(ctx): 145 | imes_root = ctx.path.find_dir('imes') 146 | for extension_root in imes.ant_glob('*', src=False, dir=True): 147 | print(extension_root.path_from(imes_root)) 148 | --------------------------------------------------------------------------------