├── .gitignore ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── bin ├── react-bitwig.mjs ├── tasks │ └── init.mjs └── template │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ └── {{scriptName}}.control.js │ ├── tsconfig.json │ └── webpack.config.js ├── docs └── assets │ └── react-bitwig-header.png ├── hooks.d.ts ├── hooks.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── controller-script.tsx │ └── midi.tsx ├── controls │ ├── base.ts │ └── midi │ │ ├── index.ts │ │ ├── midi-connector.ts │ │ ├── midi-instance.ts │ │ ├── midi-message.ts │ │ ├── midi-node.ts │ │ ├── midi-out-proxy.ts │ │ ├── note-input.ts │ │ ├── sysex-message.ts │ │ └── utils.ts ├── env │ ├── delayed-task.ts │ ├── index.ts │ └── logger.ts ├── index.ts ├── init-helpers.ts ├── lib │ └── hooks.ts ├── reconciler.ts ├── session │ ├── event-emitter.ts │ ├── index.ts │ └── session.ts └── types.d.ts ├── tsconfig.build.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | /.vscode 4 | /lib 5 | /dist 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .prettierrc 4 | /.vscode 5 | /tsconfig.* 6 | /src 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true, 4 | "useTabs": false, 5 | "semi": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Joseph Larson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![React Bitwig](./docs/assets/react-bitwig-header.png) 2 | 3 | ⚛️ Build Bitwig Studio controller scripts in React! 4 | 5 | ReactBitwig is React based JavaScript framework for building controller scripts in Bitwig Studio. At its core is a custom React renderer for MIDI that enables declarative component based control of your devices via the provided `Midi` component. Built around and on top of this foundation is a suite of smart tools and helpers designed to improve the experience of working in Bitwig's unique JavaScript environment: 6 | 7 | - Custom state management solution that takes Bitwig's init phase into consideration. 8 | - Highly detailed TypeScript [type definitions](https://github.com/joslarson/typed-bitwig-api) for Bitwig APIs 9 | - A growing library of useful components, hooks, and other helpers 10 | - Built in debug tooling for Midi I/O logging and log filtering 11 | - [Custom Webpack plugin](https://github.com/joslarson/bitwig-webpack-plugin) enabling use of ES Modules and bundling of NPM packages with project 12 | - Polyfills for relevant missing browser API's (console, setTimeout/setInterval, etc.) 13 | 14 | ## Quick Start 15 | 16 | First make sure you have the following: 17 | 18 | - Node.js v16 or newer 19 | - Bitwig Studio v4 or newer 20 | 21 | ### Project Setup 22 | 23 | With that out of the way, let's initialize a new project using ReactBitwig's CLI tool. We'll call our project `getting-started`. 24 | 25 | ```bash 26 | npx react-bitwig init getting-started 27 | # pass --typescript flag to initialize in TypeScript mode 28 | # run npx react-bitwig --help for detailed usage 29 | ``` 30 | 31 | This command will ask you some questions then generate a working project skeleton based on your responses: 32 | 33 | ```bash 34 | # answers are optional where a default is provided in parentheses 35 | [react-bitwig] begin project initialization... 36 | Display Name: Getting Started 37 | Vendor/Category: Custom 38 | Author: Joseph Larson 39 | Version (1.0.0): 40 | API Version (17): 41 | [react-bitwig] project initialization complete. 42 | ``` 43 | 44 | If you answered the prompts as shown above your new project will be created within a new `getting-started` directory, relative to your current working directory, and will have the following structure: 45 | 46 | ```bash 47 | getting-started/ 48 | ├── dist/ -> ... # symlinked into default Bitwig control surface scripts directory 49 | ├── src/ 50 | │ └── getting-started.control.js 51 | ├── README.md 52 | ├── package.json 53 | ├── tsconfig.json 54 | └── webpack.config.js 55 | ``` 56 | 57 | `cd` into your new project directory and install the initial dependencies using npm. 58 | 59 | ```bash 60 | cd getting-started 61 | npm install 62 | ``` 63 | 64 | Now, we'll run the `build` command, to generate your project's initial build. 65 | 66 | ```bash 67 | npm run build 68 | ``` 69 | 70 | At this point your newly built project files should have been picked up by Bitwig and your script should be listed under `Custom > Getting Started` in Bitwig's controller selection menu. 71 | 72 | Activate your new controller script by selecting it from the script selection menu and assigning your Midi controller's corresponding MIDI I/O. 73 | 74 | > **Note**: The default project template is setup assuming a single midi input/output pair, but if you need more than that, more can be defined in the your project's entry file (`src/getting-started.control.js` in this case). 75 | 76 | Now, before we start editing files, let's run the build command in dev/watch mode so that our project will rebuild whenever we modify and save a source file. 77 | 78 | ```bash 79 | npm run dev # run from the project root 80 | # exit the build command with ctrl/cmd+c 81 | ``` 82 | 83 | With that, everything should be in place to start coding your control surface script. Let's get into it :) 84 | 85 | ### Creating Your First Component 86 | 87 | With our bare bones project, our entry file should currently look something like this: 88 | 89 | ```jsx 90 | // src/getting-started.control.js 91 | 92 | import ReactBitwig, { ControllerScript } from 'react-bitwig'; 93 | 94 | ReactBitwig.render( 95 | 104 | {/* ...add components to render here */} 105 | 106 | ); 107 | ``` 108 | 109 | Not very useful yet... Let's jump in and create some init phase values/state and create and wire up our first component, the `PlayToggle`: 110 | 111 | ```tsx 112 | // src/components/play-toggle.js 113 | 114 | import React from 'react'; 115 | import ReactBitwig, { Midi } from 'react-bitwig'; 116 | 117 | // `createInitValue` defines a gettable value that needs to be setup during the init phase 118 | const TransportValue = ReactBitwig.createInitValue(() => 119 | host.createTransport() 120 | ); 121 | 122 | // The createInitState helper assists in wiring up init-time-only 123 | // subscriptions and provides a `.use()` hook for subscribing to 124 | // changes from within React components. 125 | const IsPlayingState = ReactBitwig.createInitState(() => { 126 | // You can safely access other init states and values inside this initializer 127 | const transport = TransportValue.get(); 128 | transport 129 | .isPlaying() 130 | .addValueObserver((isPlaying) => IsPlayingState.set(isPlaying)); 131 | return false; // return initial state 132 | }); 133 | 134 | // define our component using TransportValue and IsPlayingState form above 135 | export const PlayToggle = () => { 136 | // keep track of button pressed state so we can keep the button illuminated 137 | // while the button is continues to be pressed when the transport is stopped 138 | const [isPressed, setIsPressed] = React.useState(false); 139 | 140 | // get the transport 141 | const transport = TransportValue.get(); 142 | // subscribe to isPlaying transport state 143 | const isPlaying = IsPlayingState.use(); 144 | 145 | return ( 146 | { 154 | // when pressing the button (data2 > 0), toggle transport.isPlaying 155 | if (data2 > 0) transport.isPlaying().toggle(); 156 | // update local isPressed state so we can use it in determining the value below 157 | setIsPressed(data2 > 0); 158 | }} 159 | // when setting the value you only have to specify the non-stable parts of 160 | // the message. We want the button light on if it is pressed or if the 161 | // transport is playing. 162 | value={{ data2: isPressed || isPlaying ? 127 : 0 }} 163 | // caching modes teach ReactBitwig how to keep your hardware state in sync with your 164 | // script and help optimize to avoid sending redundant MIDI messages 165 | cacheOnInput // assume MIDI messages sent from the hardware to Bitwig represent a change in hardware state 166 | cacheOnOutput // assume MIDI messages sent from Bitwig to the hardware represent a change in hardware state 167 | /> 168 | ); 169 | }; 170 | ``` 171 | 172 | > **Note:** Generally speaking, when this documentation mentions MIDI input or output, it is speaking from the perspective of the script, not the controller. So "input" is referring to MIDI messages traveling from the controller to the script, and "output" is referencing messages moving from the script to the controller. 173 | 174 | Once you've finished building your `PlayToggle` component, import it into `getting-started.control.js` and render it as a child of your ControllerScript component: 175 | 176 | ```tsx 177 | // src/getting-started.control.js 178 | 179 | import ReactBitwig, { ControllerScript } from 'react-bitwig'; 180 | import { PlayToggle } from './components/play-toggle'; 181 | 182 | // render our controller script 183 | ReactBitwig.render( 184 | 193 | 194 | 195 | ); 196 | ``` 197 | 198 | ## The Midi Component 199 | 200 | The Midi component allows you to declaratively send and/or receive MIDI messages to synchronize Midi hardware with your controller script state. The Midi component takes the following props: 201 | 202 | ```ts 203 | type MidiProps = { 204 | // Name your control (helpful for logging/debugging) 205 | label?: string; 206 | // MIDI port messages are sent/received on, defaults to 0 207 | port?: number; 208 | // defines initial value to send on mount 209 | defaultValue?: MidiObjectPattern; 210 | // defines value to send on render 211 | value?: MidiObjectPattern; 212 | // defines value to send on on unmount 213 | unmountValue?: MidiObjectPattern; 214 | // defines the stable parts of MIDI messages associated with the control to subscribe to 215 | pattern?: MidiHexPattern | MidiObjectPattern; 216 | // called anytime a message is received matching the port and pattern defined above 217 | onInput?: (message: MidiObjectPattern) => void; 218 | // similar to onInput, but is only called when input represents a change to hardware state 219 | onChange?: (message: MidiObjectPattern) => void; 220 | // assume messages sent from hardware to script represent a change in hardware state 221 | cacheOnInput?: boolean; 222 | // assume messages sent from script to hardware represent a change in hardware state 223 | cacheOnOutput?: boolean; 224 | // sets the priority of a message, defaulting to false (messages sent during Bitwig's flush phase) 225 | urgent?: boolean; 226 | }; 227 | ``` 228 | 229 | ### Sending MIDI Messages 230 | 231 | The simplest use case for the Midi component is one where you just want to output a Midi message on mount/render/unmount. This can be accomplished using the `value`, `defaultValue`, and `unmountValue` props. 232 | 233 | ```tsx 234 | // message sent on mount and render 235 | 236 | // message sent on mount and render if value has changed 237 | 238 | ``` 239 | 240 | ```tsx 241 | // message sent on mount only 242 | 243 | ``` 244 | 245 | ```tsx 246 | // message sent on unmount only (helpful to zero out a control display when it is no longer rendered) 247 | 248 | ``` 249 | 250 | ### Responding to MIDI Input 251 | 252 | The `pattern` prop of the Midi component defines which MIDI messages the component instance is subscribed to. The `onInput` props allows you to register a callback to that subscription. 253 | 254 | ```tsx 255 | // callback passed to onInput will be called for all messages matching the provided pattern 256 | {...}} /> 257 | ``` 258 | 259 | The `onChange` prop can similarly be used to subscribe **only to changes** in the control's hardware state. For example, if the script receives a matching MIDI message that is identical to the current cached state, the onChange handler will not be called. However, this behavior only be leveraged when a component instance has configured its caching mode. 260 | 261 | ```tsx 262 | {...}} // called only when virtual hardware state cache changes 265 | cacheOnOutput // assume MIDI messages sent from Bitwig to the hardware represent a change in hardware state 266 | /> 267 | ``` 268 | 269 | #### Midi Patterns 270 | 271 | Patterns can be defined in string or object literal form. 272 | 273 | - **Object literal form** allows you to define MIDI patterns by assuming undefined MIDI byte values to be wild cards (e.g. `{ status: 0xb0, data1: 0x18 }` where any MIDI message matching the provided values will be passed through). Though slightly less powerful and more verbose than string form patterns, you may find object literal form to be more readable while still covering the vast majority of use cases. 274 | 275 | - **String form** patterns are the most expressive and concise, representing the MIDI port and each MIDI byte as consecutive two character hexadecimal values, with question marks used for wildcard matching (e.g. `'B018??'` where `B0`, `18`, and `??` represent the status, data1, and data2 values). 276 | 277 | > **Note**: The values for the status and data1 arguments above (e.g. `0xb0`) are just plain numbers written in JavaScript's hexadecimal (base 16) form. This makes it easier to to see the type of message and which channel the control maps to, but you can use regular base 10 integers 0-127 here if you want to. 278 | 279 | ### Caching Modes 280 | 281 | The `pattern`, `cacheOnInput`, and `cacheOnOutput` props work together to help ReactBitwig understand how to cache MIDI input/output messages such that redundant messages—those that would not change your MIDI controller's state—are not sent. 282 | 283 | The caching mode configuration you choose will depending on your MIDI hardware (or virtual device). Passing the `cacheOnInput` prop tells ReactBitwig to assume that messages sent from hardware to script represent a change in hardware state. Similarly, the `cacheOnOutput` prop hints that messages sent from script to hardware represent a change in hardware state. 284 | 285 | As an example, pretend you have a hardware button with and integrated light. It's a gate style button, which sends a `data2` of 127 when pressed and 0 when released. Now consider when you press this button (and a the 127 value input is received by your script), does the button light up while it is pressed? And what about when the script outputs a `data2` of 127 back to the hardware, does the button light up then? 286 | 287 | Different hardware implementations answer these questions in different ways. Some hardware will update the hardware state both when sending and receiving data, while others will only do so when receiving data, and still others allow you configure such behavior. 288 | 289 | By default, the Midi component assumes no caching behavior at all, so it's important to understand how your hardware answers these questions and to configure Midi component instances accordingly. 290 | 291 | ### Logging/Debugging Midi I/O & the `label` Prop 292 | 293 | Bitwig provides a "Controller Script Console" for logging messages to debug your controller scripts. Leveraging this, ReactBitwig provides some extra built-in tooling to make debugging easier. In your controller script settings (for controller scripts built using ReactBitwig), you can turn on automatic logging of MIDI input, output, or both. MIDI I/O is logged in hexadecimal format and is tagged with the label provided to a matching Midi component instance if any. Sample log output: 294 | 295 | ``` 296 | // Format: [MIDI] IN/OUT PORT ==>/<== XXXXXX "Control Label" 297 | [MIDI] IN 0 ==> 91087F "PLAY" 298 | [MIDI] OUT 0 <== 91087F "PLAY" 299 | [MIDI] IN 0 ==> 910800 "PLAY" 300 | ``` 301 | 302 | ## State Management 303 | 304 | Bitwig has a unique runtime environment, and as part of that, you can only access some features during the "init" phase (which runs only once when your script first boots up). Basically any Bitwig data/event you would like to subscribe to (channel count, selected track, transport state, etc) has to be wired up during this phase, making things a bit awkward for a paradigm like React where it is common to initialize such subscriptions on mount and tear them down on unmount. 305 | 306 | To improve the developer experience in relation to the restrictions placed on us by this "init" phase, ReactBitwig provides some "init" helpers to assist in defining init phase data and subscriptions/state. 307 | 308 | ### `createInitValue` 309 | 310 | Define a getter for an unchanging value, that is run only once during the init phase, but whose value can be requested any time thereafter (in our React components or elsewhere). 311 | 312 | ```ts 313 | // `createInitValue` defines a gettable value that needs to be setup during the init phase 314 | const TransportValue = ReactBitwig.createInitValue(() => 315 | host.createTransport() 316 | ); 317 | 318 | // Access value (after init) 319 | const SomeComponent = () => { 320 | const transport = TransportValue.get(); 321 | ... 322 | } 323 | ``` 324 | 325 | ### `createInitObject` 326 | 327 | Similar to `createInitValue`, but returns an object where key access is defined as getters underneath, so that it can be used like a regular object literal, without throwing errors before the init phase begins. 328 | 329 | ```ts 330 | const bitwig = ReactBitwig.createInitObject(() => { 331 | const arrangerCursorClip = host.createArrangerCursorClip(4, 128); 332 | const launcherCursorClip = host.createLauncherCursorClip(4, 128); 333 | 334 | // transport 335 | const transport = host.createTransport(); 336 | transport.subscribe(); 337 | transport.tempo().markInterested(); 338 | transport.getPosition().markInterested(); 339 | transport.isPlaying().markInterested(); 340 | transport.timeSignature().markInterested(); 341 | 342 | // application 343 | const application = host.createApplication(); 344 | application.panelLayout().markInterested(); 345 | 346 | // cursorTrack 347 | const cursorTrack = host.createCursorTrack(0, 16); 348 | cursorTrack.isGroup().markInterested(); 349 | cursorTrack.color().markInterested(); 350 | cursorTrack.position().markInterested(); 351 | 352 | // trackBank 353 | const trackBank = host.createMainTrackBank(8, 0, 0); 354 | trackBank.cursorIndex().markInterested(); 355 | trackBank.channelCount().markInterested(); 356 | trackBank.setChannelScrollStepSize(8); 357 | 358 | 359 | return { 360 | arrangerCursorClip, 361 | launcherCursorClip, 362 | transport, 363 | application, 364 | cursorTrack, 365 | trackBank, 366 | }; 367 | }); 368 | 369 | 370 | // use it in a component as if it's a regular object literal 371 | const SomeComponent = () => { 372 | // note: destructuring must happen in the component 373 | // (it will error if you destructure at the module level) 374 | const { application, transport } = bitwig; 375 | ... 376 | } 377 | ``` 378 | 379 | ### `createInitState` 380 | 381 | Creates an atomic piece of global state with an initializing function that is called during Bitwig's init phase. The resulting `InitState` instance provides a `.set(...)` method for updating the state, a `.use()` hook method for subscribing React components to the state, and `get/subscribe/unsubscribe` methods for accessing and subscribing to state outside of React components. 382 | 383 | ```ts 384 | // The createInitState helper assists in wiring up init-time-only 385 | // subscriptions and provides a `.use()` hook for subscribing to 386 | // changes from within React components. 387 | const IsPlayingState = ReactBitwig.createInitState(() => { 388 | // You can safely access other init states and values inside this initializer 389 | const transport = TransportValue.get(); 390 | transport 391 | .isPlaying() 392 | .addValueObserver((isPlaying) => IsPlayingState.set(isPlaying)); 393 | 394 | return false; // return initial state 395 | }); 396 | ``` 397 | -------------------------------------------------------------------------------- /bin/react-bitwig.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { program } from 'commander'; 3 | import { v4 as uuid } from 'uuid'; 4 | import colors from 'colors/safe.js'; 5 | 6 | import init from './tasks/init.mjs'; 7 | 8 | // init command 9 | program 10 | .command('init [dirname]') 11 | .description( 12 | 'Initialize a new project with dirname (defaults to current directory).' 13 | ) 14 | .option('-t, --typescript', 'setup project to use TypeScript') 15 | .action((dirname, options) => init(dirname, options.typescript)); 16 | 17 | // uuid generator 18 | program 19 | .command('uuid') 20 | .description('Generate a UUID') 21 | .action(() => console.log(colors.green(uuid()))); 22 | 23 | // parse args and run commands 24 | program.parse(process.argv); 25 | 26 | // no command? print help 27 | if (!process.argv.slice(2).length) program.outputHelp(); 28 | -------------------------------------------------------------------------------- /bin/tasks/init.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import os from 'os'; 3 | import path from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | import colors from 'colors/safe.js'; 7 | import { globSync as glob } from 'glob'; 8 | import nunjucks from 'nunjucks'; 9 | import promptSync from 'prompt-sync'; 10 | import { v4 as uuid } from 'uuid'; 11 | 12 | const __filename = fileURLToPath(import.meta.url); 13 | const __dirname = path.dirname(__filename); 14 | 15 | const pkgjson = JSON.parse( 16 | fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8') 17 | ); 18 | 19 | const prompt = promptSync({ sigint: true }); 20 | 21 | const IS_WINDOWS = os.platform() === 'win32'; 22 | const IS_MACOS = os.platform() === 'darwin'; 23 | const IS_LINUX = !IS_WINDOWS && !IS_MACOS; 24 | const TEMPLATE_ROOT = path.join(__dirname, '..', 'template'); 25 | 26 | const scriptsDirs = { 27 | linux: path.join(os.homedir(), 'Bitwig Studio', 'Controller Scripts'), 28 | other: path.join( 29 | os.homedir(), 30 | 'Documents', 31 | 'Bitwig Studio', 32 | 'Controller Scripts' 33 | ), 34 | }; 35 | 36 | const taskName = colors.bold(colors.magenta(`[react-bitwig]`)); 37 | const scriptsDir = IS_LINUX ? scriptsDirs.linux : scriptsDirs.other; 38 | 39 | const typescriptVersion = pkgjson.devDependencies['typescript']; 40 | const apiTypesVersion = pkgjson.devDependencies['typed-bitwig-api']; 41 | const defaultApiVersion = apiTypesVersion.split('.')[0].slice(1); 42 | 43 | function rprompt(question, defaultValue, test, badTestResponse) { 44 | test = test === undefined ? (val) => val.trim().length > 0 : test; 45 | badTestResponse = 46 | badTestResponse !== undefined 47 | ? badTestResponse 48 | : 'Invalid input, try again...'; 49 | 50 | let result; 51 | while (true) { 52 | result = prompt(question, defaultValue); 53 | if (test(result)) break; 54 | console.log(badTestResponse); 55 | } 56 | 57 | return result; 58 | } 59 | 60 | export default (dirname = process.cwd, typescript = false) => { 61 | console.log(`${taskName} begin project initialization...`); 62 | 63 | const scriptName = path.basename(path.resolve(dirname)).trim(); 64 | 65 | const name = rprompt(colors.bold(colors.blue('Display Name: ')), '').trim(); 66 | const vendor = rprompt( 67 | colors.bold(colors.blue('Vendor/Category: ')), 68 | '' 69 | ).trim(); 70 | const author = prompt(colors.bold(colors.blue('Author: ')), '').trim(); 71 | const version = prompt( 72 | colors.bold(colors.blue('Version (1.0.0): ')), 73 | '1.0.0' 74 | ).trim(); 75 | const apiVersion = rprompt( 76 | colors.bold(colors.blue(`API Version (${defaultApiVersion}): `)), 77 | `${defaultApiVersion}` 78 | ).trim(); 79 | 80 | const context = { 81 | typescript, 82 | packageVersion: pkgjson.version, 83 | scriptName, 84 | name, 85 | vendor, 86 | version, 87 | uuid: uuid(), 88 | author, 89 | apiVersion, 90 | apiTypesVersion, 91 | typescriptVersion, 92 | }; 93 | 94 | const filePaths = glob('**/*', { 95 | dot: true, 96 | cwd: TEMPLATE_ROOT, 97 | nodir: true, 98 | }); 99 | 100 | /** @type {{ path: string, contents: string }[]} */ 101 | const templates = filePaths.reduce((acc, filePath) => { 102 | const fullPath = path.join(TEMPLATE_ROOT, filePath); 103 | acc.push({ path: filePath, contents: fs.readFileSync(fullPath, 'utf-8') }); 104 | return acc; 105 | }, []); 106 | 107 | templates.forEach((template) => { 108 | // generate file content from template & context 109 | const contents = nunjucks.renderString(template.contents, context); 110 | 111 | // generate file path from template & context 112 | let destPath = nunjucks.renderString(template.path, context); 113 | if (typescript && destPath.startsWith('src/') && destPath.endsWith('.js')) { 114 | destPath = `${destPath.slice(0, -3)}.tsx`; 115 | } 116 | 117 | const fullDestPath = path.join(dirname, destPath); 118 | const fullDestDir = path.dirname(fullDestPath); 119 | // create dir if it doesn't exist 120 | if (!fs.existsSync(fullDestDir)) { 121 | fs.mkdirSync(fullDestDir, { recursive: true }); 122 | } 123 | // save file 124 | fs.writeFileSync(fullDestPath, contents, 'utf-8'); 125 | }); 126 | 127 | if (!IS_WINDOWS) { 128 | // symlink on mac and linux 129 | // create build directory 130 | try { 131 | fs.mkdirSync(path.join(scriptsDir, scriptName)); 132 | } catch (e) { 133 | // already exists... pass 134 | } 135 | // symlink build dir to project root 136 | fs.symlinkSync( 137 | path.join(scriptsDir, scriptName), 138 | path.join(dirname, 'dist') 139 | ); 140 | } 141 | 142 | console.log(`${taskName} project initialization complete.`); 143 | }; 144 | -------------------------------------------------------------------------------- /bin/template/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/react", { "runtime": "automatic" }], 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": ["@babel/plugin-proposal-class-properties"] 7 | } 8 | -------------------------------------------------------------------------------- /bin/template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | ./dist 4 | -------------------------------------------------------------------------------- /bin/template/README.md: -------------------------------------------------------------------------------- 1 | # {{ name }} 2 | -------------------------------------------------------------------------------- /bin/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "webpack --mode production", 5 | "dev": "webpack --mode development --watch" 6 | }, 7 | "dependencies": { 8 | "react": "^18.2.0", 9 | "react-bitwig": "^{{ packageVersion }}" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.21.0", 13 | "@babel/plugin-proposal-class-properties": "^7.18.6", 14 | "@babel/preset-react": "^7.18.6", 15 | "@babel/preset-typescript": "^7.21.0", 16 | "@types/react": "^18.0.28", 17 | "babel-loader": "^9.1.2", 18 | "bitwig-webpack-plugin": "^2.0.0", 19 | "copy-webpack-plugin": "^11.0.0", 20 | "glob": "^9.2.1", 21 | "typed-bitwig-api": "{{ apiTypesVersion }}", 22 | "typescript": "{{ typescriptVersion }}", 23 | "webpack-cli": "^5.0.1", 24 | "webpack": "^5.76.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bin/template/src/{{scriptName}}.control.js: -------------------------------------------------------------------------------- 1 | import ReactBitwig, { ControllerScript } from 'react-bitwig'; 2 | 3 | ReactBitwig.render( 4 | 13 | {/* ...add components to render here */} 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /bin/template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "es2015", 5 | "rootDir": "src", 6 | "baseUrl": "./src", 7 | "jsx": "react-jsx", 8 | "lib": ["DOM", "ESNext", "ScriptHost"], 9 | "types": ["typed-bitwig-api"], 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "moduleResolution": "node", 15 | "paths": { 16 | "@/*": ["src/*"], 17 | "*": ["node_modules/*"] 18 | } 19 | }, 20 | "include": ["src/**/*"] 21 | } 22 | -------------------------------------------------------------------------------- /bin/template/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const BitwigWebpackPlugin = require('bitwig-webpack-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const tsconfig = require('./tsconfig.json'); 6 | 7 | const glob = require('glob').globSync; 8 | 9 | const entries = glob('./src/*.control.+(tsx|ts|js|jsx)', { dotRelative: true }) 10 | .map((filePath) => filePath.split('.').slice(0, -1).join('.')) 11 | .reduce((acc, filePath) => { 12 | acc[path.basename(filePath)] = filePath; 13 | return acc; 14 | }, {}); 15 | 16 | const paths = Array.from( 17 | new Set([].concat(...tsconfig.include.map((p) => glob(p)))) 18 | ).filter((p) => /\.(t|j)sx?$/.test(p) && !/\.control\.(t|j)sx?$/.test(p)); 19 | const perFileCacheGroups = paths.reduce((result, p, i) => { 20 | const name = p 21 | .split('.') 22 | .slice(0, -1) 23 | .join('.') 24 | .replace( 25 | new RegExp(`^${path.join(tsconfig.compilerOptions.rootDir)}`), 26 | 'project-files' 27 | ); 28 | result[`file${i + 1}`] = { 29 | name, 30 | test: RegExp(path.join(__dirname, p)), 31 | chunks: 'initial', 32 | enforce: true, 33 | }; 34 | return result; 35 | }, {}); 36 | 37 | module.exports = (_, { mode = 'production' }) => ({ 38 | mode, 39 | entry: entries, 40 | output: { 41 | path: path.resolve(__dirname, 'dist'), 42 | filename: '[name].js', 43 | globalObject: 'globalThis', 44 | clean: true, 45 | }, 46 | resolve: { 47 | extensions: ['.ts', '.tsx', '.js'], 48 | alias: { 49 | '@': path.resolve(__dirname, 'src'), 50 | }, 51 | }, 52 | // setup typescript loader for ts and js files 53 | module: { 54 | rules: [ 55 | { 56 | test: /\.[tj]sx?$/, 57 | exclude: /node_modules/, 58 | use: { 59 | loader: 'babel-loader', 60 | }, 61 | }, 62 | ], 63 | }, 64 | plugins: [ 65 | new BitwigWebpackPlugin(), // enables synchronous code splitting 66 | new CopyWebpackPlugin({ patterns: [{ from: 'README.md' }] }), // non JS things to copy 67 | ], 68 | optimization: { 69 | splitChunks: { 70 | cacheGroups: { 71 | // in dev mode output a chunk per src file to make debugging easier 72 | ...(mode === 'development' ? perFileCacheGroups : {}), 73 | // separate webpack manifest and vendor libs from project code 74 | vendor: { 75 | name: 'vendor.bundle', 76 | test: /node_modules/, 77 | chunks: 'initial', 78 | enforce: true, 79 | }, 80 | }, 81 | }, 82 | runtimeChunk: { name: "runtime" }, 83 | // makes output easy to read for debugging 84 | concatenateModules: true, 85 | }, 86 | devtool: false, // sourcemaps not supported in Bitwig's JavaScript engine 87 | stats: { 88 | assets: false, 89 | colors: true, 90 | chunks: false, 91 | version: false, 92 | hash: false, 93 | timings: false, 94 | modules: false, 95 | builtAt: false, 96 | cached: false, 97 | entrypoints: true, 98 | reasons: false, 99 | errors: true, 100 | }, 101 | }); 102 | -------------------------------------------------------------------------------- /docs/assets/react-bitwig-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joslarson/react-bitwig/5e34dee520a770f40549550a2db6d514eb01b93f/docs/assets/react-bitwig-header.png -------------------------------------------------------------------------------- /hooks.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/lib/hooks'; 2 | -------------------------------------------------------------------------------- /hooks.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/lib/hooks'); 2 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bitwig", 3 | "version": "1.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "react-bitwig", 9 | "version": "1.1.0", 10 | "license": "BSD-3-Clause", 11 | "dependencies": { 12 | "colors": "^1.4.0", 13 | "commander": "^10.0.0", 14 | "glob": "^9.2.1", 15 | "npm-run-all": "^4.1.5", 16 | "nunjucks": "^3.2.3", 17 | "prompt-sync": "^4.2.0", 18 | "react-reconciler": "0.29.0", 19 | "uuid": "^9.0.0" 20 | }, 21 | "bin": { 22 | "rb": "bin/react-bitwig.mjs", 23 | "react-bitwig": "bin/react-bitwig.mjs" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^16.0.0", 27 | "@types/react-reconciler": "0.28.2", 28 | "prettier": "^2.8.4", 29 | "typed-bitwig-api": "^17.0.1", 30 | "typescript": "^4.9.5" 31 | }, 32 | "engines": { 33 | "node": ">= 16.0.0" 34 | } 35 | }, 36 | "node_modules/@types/node": { 37 | "version": "16.18.14", 38 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.14.tgz", 39 | "integrity": "sha512-wvzClDGQXOCVNU4APPopC2KtMYukaF1MN/W3xAmslx22Z4/IF1/izDMekuyoUlwfnDHYCIZGaj7jMwnJKBTxKw==", 40 | "dev": true 41 | }, 42 | "node_modules/@types/prop-types": { 43 | "version": "15.7.5", 44 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", 45 | "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", 46 | "dev": true 47 | }, 48 | "node_modules/@types/react": { 49 | "version": "18.0.28", 50 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", 51 | "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", 52 | "dev": true, 53 | "dependencies": { 54 | "@types/prop-types": "*", 55 | "@types/scheduler": "*", 56 | "csstype": "^3.0.2" 57 | } 58 | }, 59 | "node_modules/@types/react-reconciler": { 60 | "version": "0.28.2", 61 | "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz", 62 | "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==", 63 | "dev": true, 64 | "dependencies": { 65 | "@types/react": "*" 66 | } 67 | }, 68 | "node_modules/@types/scheduler": { 69 | "version": "0.16.2", 70 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", 71 | "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", 72 | "dev": true 73 | }, 74 | "node_modules/a-sync-waterfall": { 75 | "version": "1.0.1", 76 | "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", 77 | "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" 78 | }, 79 | "node_modules/ansi-regex": { 80 | "version": "4.1.1", 81 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", 82 | "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", 83 | "engines": { 84 | "node": ">=6" 85 | } 86 | }, 87 | "node_modules/ansi-styles": { 88 | "version": "3.2.1", 89 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 90 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 91 | "dependencies": { 92 | "color-convert": "^1.9.0" 93 | }, 94 | "engines": { 95 | "node": ">=4" 96 | } 97 | }, 98 | "node_modules/asap": { 99 | "version": "2.0.6", 100 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 101 | "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" 102 | }, 103 | "node_modules/available-typed-arrays": { 104 | "version": "1.0.5", 105 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", 106 | "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", 107 | "engines": { 108 | "node": ">= 0.4" 109 | }, 110 | "funding": { 111 | "url": "https://github.com/sponsors/ljharb" 112 | } 113 | }, 114 | "node_modules/balanced-match": { 115 | "version": "1.0.2", 116 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 117 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 118 | }, 119 | "node_modules/brace-expansion": { 120 | "version": "2.0.1", 121 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 122 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 123 | "dependencies": { 124 | "balanced-match": "^1.0.0" 125 | } 126 | }, 127 | "node_modules/call-bind": { 128 | "version": "1.0.2", 129 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 130 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 131 | "dependencies": { 132 | "function-bind": "^1.1.1", 133 | "get-intrinsic": "^1.0.2" 134 | }, 135 | "funding": { 136 | "url": "https://github.com/sponsors/ljharb" 137 | } 138 | }, 139 | "node_modules/chalk": { 140 | "version": "2.4.2", 141 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 142 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 143 | "dependencies": { 144 | "ansi-styles": "^3.2.1", 145 | "escape-string-regexp": "^1.0.5", 146 | "supports-color": "^5.3.0" 147 | }, 148 | "engines": { 149 | "node": ">=4" 150 | } 151 | }, 152 | "node_modules/color-convert": { 153 | "version": "1.9.3", 154 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 155 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 156 | "dependencies": { 157 | "color-name": "1.1.3" 158 | } 159 | }, 160 | "node_modules/color-name": { 161 | "version": "1.1.3", 162 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 163 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 164 | }, 165 | "node_modules/colors": { 166 | "version": "1.4.0", 167 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 168 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 169 | "engines": { 170 | "node": ">=0.1.90" 171 | } 172 | }, 173 | "node_modules/commander": { 174 | "version": "10.0.0", 175 | "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", 176 | "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", 177 | "engines": { 178 | "node": ">=14" 179 | } 180 | }, 181 | "node_modules/concat-map": { 182 | "version": "0.0.1", 183 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 184 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 185 | }, 186 | "node_modules/cross-spawn": { 187 | "version": "6.0.5", 188 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 189 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 190 | "dependencies": { 191 | "nice-try": "^1.0.4", 192 | "path-key": "^2.0.1", 193 | "semver": "^5.5.0", 194 | "shebang-command": "^1.2.0", 195 | "which": "^1.2.9" 196 | }, 197 | "engines": { 198 | "node": ">=4.8" 199 | } 200 | }, 201 | "node_modules/csstype": { 202 | "version": "3.1.1", 203 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", 204 | "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", 205 | "dev": true 206 | }, 207 | "node_modules/define-properties": { 208 | "version": "1.2.0", 209 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", 210 | "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", 211 | "dependencies": { 212 | "has-property-descriptors": "^1.0.0", 213 | "object-keys": "^1.1.1" 214 | }, 215 | "engines": { 216 | "node": ">= 0.4" 217 | }, 218 | "funding": { 219 | "url": "https://github.com/sponsors/ljharb" 220 | } 221 | }, 222 | "node_modules/error-ex": { 223 | "version": "1.3.2", 224 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 225 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 226 | "dependencies": { 227 | "is-arrayish": "^0.2.1" 228 | } 229 | }, 230 | "node_modules/es-abstract": { 231 | "version": "1.21.1", 232 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", 233 | "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", 234 | "dependencies": { 235 | "available-typed-arrays": "^1.0.5", 236 | "call-bind": "^1.0.2", 237 | "es-set-tostringtag": "^2.0.1", 238 | "es-to-primitive": "^1.2.1", 239 | "function-bind": "^1.1.1", 240 | "function.prototype.name": "^1.1.5", 241 | "get-intrinsic": "^1.1.3", 242 | "get-symbol-description": "^1.0.0", 243 | "globalthis": "^1.0.3", 244 | "gopd": "^1.0.1", 245 | "has": "^1.0.3", 246 | "has-property-descriptors": "^1.0.0", 247 | "has-proto": "^1.0.1", 248 | "has-symbols": "^1.0.3", 249 | "internal-slot": "^1.0.4", 250 | "is-array-buffer": "^3.0.1", 251 | "is-callable": "^1.2.7", 252 | "is-negative-zero": "^2.0.2", 253 | "is-regex": "^1.1.4", 254 | "is-shared-array-buffer": "^1.0.2", 255 | "is-string": "^1.0.7", 256 | "is-typed-array": "^1.1.10", 257 | "is-weakref": "^1.0.2", 258 | "object-inspect": "^1.12.2", 259 | "object-keys": "^1.1.1", 260 | "object.assign": "^4.1.4", 261 | "regexp.prototype.flags": "^1.4.3", 262 | "safe-regex-test": "^1.0.0", 263 | "string.prototype.trimend": "^1.0.6", 264 | "string.prototype.trimstart": "^1.0.6", 265 | "typed-array-length": "^1.0.4", 266 | "unbox-primitive": "^1.0.2", 267 | "which-typed-array": "^1.1.9" 268 | }, 269 | "engines": { 270 | "node": ">= 0.4" 271 | }, 272 | "funding": { 273 | "url": "https://github.com/sponsors/ljharb" 274 | } 275 | }, 276 | "node_modules/es-set-tostringtag": { 277 | "version": "2.0.1", 278 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", 279 | "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", 280 | "dependencies": { 281 | "get-intrinsic": "^1.1.3", 282 | "has": "^1.0.3", 283 | "has-tostringtag": "^1.0.0" 284 | }, 285 | "engines": { 286 | "node": ">= 0.4" 287 | } 288 | }, 289 | "node_modules/es-to-primitive": { 290 | "version": "1.2.1", 291 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 292 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 293 | "dependencies": { 294 | "is-callable": "^1.1.4", 295 | "is-date-object": "^1.0.1", 296 | "is-symbol": "^1.0.2" 297 | }, 298 | "engines": { 299 | "node": ">= 0.4" 300 | }, 301 | "funding": { 302 | "url": "https://github.com/sponsors/ljharb" 303 | } 304 | }, 305 | "node_modules/escape-string-regexp": { 306 | "version": "1.0.5", 307 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 308 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 309 | "engines": { 310 | "node": ">=0.8.0" 311 | } 312 | }, 313 | "node_modules/for-each": { 314 | "version": "0.3.3", 315 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 316 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 317 | "dependencies": { 318 | "is-callable": "^1.1.3" 319 | } 320 | }, 321 | "node_modules/fs.realpath": { 322 | "version": "1.0.0", 323 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 324 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 325 | }, 326 | "node_modules/function-bind": { 327 | "version": "1.1.1", 328 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 329 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 330 | }, 331 | "node_modules/function.prototype.name": { 332 | "version": "1.1.5", 333 | "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", 334 | "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", 335 | "dependencies": { 336 | "call-bind": "^1.0.2", 337 | "define-properties": "^1.1.3", 338 | "es-abstract": "^1.19.0", 339 | "functions-have-names": "^1.2.2" 340 | }, 341 | "engines": { 342 | "node": ">= 0.4" 343 | }, 344 | "funding": { 345 | "url": "https://github.com/sponsors/ljharb" 346 | } 347 | }, 348 | "node_modules/functions-have-names": { 349 | "version": "1.2.3", 350 | "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", 351 | "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", 352 | "funding": { 353 | "url": "https://github.com/sponsors/ljharb" 354 | } 355 | }, 356 | "node_modules/get-intrinsic": { 357 | "version": "1.2.0", 358 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 359 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 360 | "dependencies": { 361 | "function-bind": "^1.1.1", 362 | "has": "^1.0.3", 363 | "has-symbols": "^1.0.3" 364 | }, 365 | "funding": { 366 | "url": "https://github.com/sponsors/ljharb" 367 | } 368 | }, 369 | "node_modules/get-symbol-description": { 370 | "version": "1.0.0", 371 | "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", 372 | "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", 373 | "dependencies": { 374 | "call-bind": "^1.0.2", 375 | "get-intrinsic": "^1.1.1" 376 | }, 377 | "engines": { 378 | "node": ">= 0.4" 379 | }, 380 | "funding": { 381 | "url": "https://github.com/sponsors/ljharb" 382 | } 383 | }, 384 | "node_modules/glob": { 385 | "version": "9.2.1", 386 | "resolved": "https://registry.npmjs.org/glob/-/glob-9.2.1.tgz", 387 | "integrity": "sha512-Pxxgq3W0HyA3XUvSXcFhRSs+43Jsx0ddxcFrbjxNGkL2Ak5BAUBxLqI5G6ADDeCHLfzzXFhe0b1yYcctGmytMA==", 388 | "dependencies": { 389 | "fs.realpath": "^1.0.0", 390 | "minimatch": "^7.4.1", 391 | "minipass": "^4.2.4", 392 | "path-scurry": "^1.6.1" 393 | }, 394 | "engines": { 395 | "node": ">=16 || 14 >=14.17" 396 | }, 397 | "funding": { 398 | "url": "https://github.com/sponsors/isaacs" 399 | } 400 | }, 401 | "node_modules/globalthis": { 402 | "version": "1.0.3", 403 | "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", 404 | "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", 405 | "dependencies": { 406 | "define-properties": "^1.1.3" 407 | }, 408 | "engines": { 409 | "node": ">= 0.4" 410 | }, 411 | "funding": { 412 | "url": "https://github.com/sponsors/ljharb" 413 | } 414 | }, 415 | "node_modules/gopd": { 416 | "version": "1.0.1", 417 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 418 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 419 | "dependencies": { 420 | "get-intrinsic": "^1.1.3" 421 | }, 422 | "funding": { 423 | "url": "https://github.com/sponsors/ljharb" 424 | } 425 | }, 426 | "node_modules/graceful-fs": { 427 | "version": "4.2.10", 428 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 429 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" 430 | }, 431 | "node_modules/has": { 432 | "version": "1.0.3", 433 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 434 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 435 | "dependencies": { 436 | "function-bind": "^1.1.1" 437 | }, 438 | "engines": { 439 | "node": ">= 0.4.0" 440 | } 441 | }, 442 | "node_modules/has-bigints": { 443 | "version": "1.0.2", 444 | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", 445 | "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", 446 | "funding": { 447 | "url": "https://github.com/sponsors/ljharb" 448 | } 449 | }, 450 | "node_modules/has-flag": { 451 | "version": "3.0.0", 452 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 453 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 454 | "engines": { 455 | "node": ">=4" 456 | } 457 | }, 458 | "node_modules/has-property-descriptors": { 459 | "version": "1.0.0", 460 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", 461 | "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", 462 | "dependencies": { 463 | "get-intrinsic": "^1.1.1" 464 | }, 465 | "funding": { 466 | "url": "https://github.com/sponsors/ljharb" 467 | } 468 | }, 469 | "node_modules/has-proto": { 470 | "version": "1.0.1", 471 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 472 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 473 | "engines": { 474 | "node": ">= 0.4" 475 | }, 476 | "funding": { 477 | "url": "https://github.com/sponsors/ljharb" 478 | } 479 | }, 480 | "node_modules/has-symbols": { 481 | "version": "1.0.3", 482 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 483 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 484 | "engines": { 485 | "node": ">= 0.4" 486 | }, 487 | "funding": { 488 | "url": "https://github.com/sponsors/ljharb" 489 | } 490 | }, 491 | "node_modules/has-tostringtag": { 492 | "version": "1.0.0", 493 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", 494 | "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", 495 | "dependencies": { 496 | "has-symbols": "^1.0.2" 497 | }, 498 | "engines": { 499 | "node": ">= 0.4" 500 | }, 501 | "funding": { 502 | "url": "https://github.com/sponsors/ljharb" 503 | } 504 | }, 505 | "node_modules/hosted-git-info": { 506 | "version": "2.8.9", 507 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 508 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" 509 | }, 510 | "node_modules/internal-slot": { 511 | "version": "1.0.5", 512 | "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", 513 | "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", 514 | "dependencies": { 515 | "get-intrinsic": "^1.2.0", 516 | "has": "^1.0.3", 517 | "side-channel": "^1.0.4" 518 | }, 519 | "engines": { 520 | "node": ">= 0.4" 521 | } 522 | }, 523 | "node_modules/is-array-buffer": { 524 | "version": "3.0.2", 525 | "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", 526 | "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", 527 | "dependencies": { 528 | "call-bind": "^1.0.2", 529 | "get-intrinsic": "^1.2.0", 530 | "is-typed-array": "^1.1.10" 531 | }, 532 | "funding": { 533 | "url": "https://github.com/sponsors/ljharb" 534 | } 535 | }, 536 | "node_modules/is-arrayish": { 537 | "version": "0.2.1", 538 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 539 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" 540 | }, 541 | "node_modules/is-bigint": { 542 | "version": "1.0.4", 543 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", 544 | "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", 545 | "dependencies": { 546 | "has-bigints": "^1.0.1" 547 | }, 548 | "funding": { 549 | "url": "https://github.com/sponsors/ljharb" 550 | } 551 | }, 552 | "node_modules/is-boolean-object": { 553 | "version": "1.1.2", 554 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", 555 | "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", 556 | "dependencies": { 557 | "call-bind": "^1.0.2", 558 | "has-tostringtag": "^1.0.0" 559 | }, 560 | "engines": { 561 | "node": ">= 0.4" 562 | }, 563 | "funding": { 564 | "url": "https://github.com/sponsors/ljharb" 565 | } 566 | }, 567 | "node_modules/is-callable": { 568 | "version": "1.2.7", 569 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", 570 | "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", 571 | "engines": { 572 | "node": ">= 0.4" 573 | }, 574 | "funding": { 575 | "url": "https://github.com/sponsors/ljharb" 576 | } 577 | }, 578 | "node_modules/is-core-module": { 579 | "version": "2.11.0", 580 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 581 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 582 | "dependencies": { 583 | "has": "^1.0.3" 584 | }, 585 | "funding": { 586 | "url": "https://github.com/sponsors/ljharb" 587 | } 588 | }, 589 | "node_modules/is-date-object": { 590 | "version": "1.0.5", 591 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", 592 | "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", 593 | "dependencies": { 594 | "has-tostringtag": "^1.0.0" 595 | }, 596 | "engines": { 597 | "node": ">= 0.4" 598 | }, 599 | "funding": { 600 | "url": "https://github.com/sponsors/ljharb" 601 | } 602 | }, 603 | "node_modules/is-negative-zero": { 604 | "version": "2.0.2", 605 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", 606 | "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", 607 | "engines": { 608 | "node": ">= 0.4" 609 | }, 610 | "funding": { 611 | "url": "https://github.com/sponsors/ljharb" 612 | } 613 | }, 614 | "node_modules/is-number-object": { 615 | "version": "1.0.7", 616 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", 617 | "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", 618 | "dependencies": { 619 | "has-tostringtag": "^1.0.0" 620 | }, 621 | "engines": { 622 | "node": ">= 0.4" 623 | }, 624 | "funding": { 625 | "url": "https://github.com/sponsors/ljharb" 626 | } 627 | }, 628 | "node_modules/is-regex": { 629 | "version": "1.1.4", 630 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", 631 | "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", 632 | "dependencies": { 633 | "call-bind": "^1.0.2", 634 | "has-tostringtag": "^1.0.0" 635 | }, 636 | "engines": { 637 | "node": ">= 0.4" 638 | }, 639 | "funding": { 640 | "url": "https://github.com/sponsors/ljharb" 641 | } 642 | }, 643 | "node_modules/is-shared-array-buffer": { 644 | "version": "1.0.2", 645 | "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", 646 | "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", 647 | "dependencies": { 648 | "call-bind": "^1.0.2" 649 | }, 650 | "funding": { 651 | "url": "https://github.com/sponsors/ljharb" 652 | } 653 | }, 654 | "node_modules/is-string": { 655 | "version": "1.0.7", 656 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", 657 | "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", 658 | "dependencies": { 659 | "has-tostringtag": "^1.0.0" 660 | }, 661 | "engines": { 662 | "node": ">= 0.4" 663 | }, 664 | "funding": { 665 | "url": "https://github.com/sponsors/ljharb" 666 | } 667 | }, 668 | "node_modules/is-symbol": { 669 | "version": "1.0.4", 670 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", 671 | "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", 672 | "dependencies": { 673 | "has-symbols": "^1.0.2" 674 | }, 675 | "engines": { 676 | "node": ">= 0.4" 677 | }, 678 | "funding": { 679 | "url": "https://github.com/sponsors/ljharb" 680 | } 681 | }, 682 | "node_modules/is-typed-array": { 683 | "version": "1.1.10", 684 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", 685 | "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", 686 | "dependencies": { 687 | "available-typed-arrays": "^1.0.5", 688 | "call-bind": "^1.0.2", 689 | "for-each": "^0.3.3", 690 | "gopd": "^1.0.1", 691 | "has-tostringtag": "^1.0.0" 692 | }, 693 | "engines": { 694 | "node": ">= 0.4" 695 | }, 696 | "funding": { 697 | "url": "https://github.com/sponsors/ljharb" 698 | } 699 | }, 700 | "node_modules/is-weakref": { 701 | "version": "1.0.2", 702 | "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", 703 | "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", 704 | "dependencies": { 705 | "call-bind": "^1.0.2" 706 | }, 707 | "funding": { 708 | "url": "https://github.com/sponsors/ljharb" 709 | } 710 | }, 711 | "node_modules/isexe": { 712 | "version": "2.0.0", 713 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 714 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 715 | }, 716 | "node_modules/js-tokens": { 717 | "version": "4.0.0", 718 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 719 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 720 | }, 721 | "node_modules/json-parse-better-errors": { 722 | "version": "1.0.2", 723 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 724 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" 725 | }, 726 | "node_modules/load-json-file": { 727 | "version": "4.0.0", 728 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 729 | "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", 730 | "dependencies": { 731 | "graceful-fs": "^4.1.2", 732 | "parse-json": "^4.0.0", 733 | "pify": "^3.0.0", 734 | "strip-bom": "^3.0.0" 735 | }, 736 | "engines": { 737 | "node": ">=4" 738 | } 739 | }, 740 | "node_modules/loose-envify": { 741 | "version": "1.4.0", 742 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 743 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 744 | "dependencies": { 745 | "js-tokens": "^3.0.0 || ^4.0.0" 746 | }, 747 | "bin": { 748 | "loose-envify": "cli.js" 749 | } 750 | }, 751 | "node_modules/lru-cache": { 752 | "version": "7.18.3", 753 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", 754 | "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", 755 | "engines": { 756 | "node": ">=12" 757 | } 758 | }, 759 | "node_modules/memorystream": { 760 | "version": "0.3.1", 761 | "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", 762 | "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", 763 | "engines": { 764 | "node": ">= 0.10.0" 765 | } 766 | }, 767 | "node_modules/minimatch": { 768 | "version": "7.4.2", 769 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.2.tgz", 770 | "integrity": "sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==", 771 | "dependencies": { 772 | "brace-expansion": "^2.0.1" 773 | }, 774 | "engines": { 775 | "node": ">=10" 776 | }, 777 | "funding": { 778 | "url": "https://github.com/sponsors/isaacs" 779 | } 780 | }, 781 | "node_modules/minipass": { 782 | "version": "4.2.5", 783 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", 784 | "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", 785 | "engines": { 786 | "node": ">=8" 787 | } 788 | }, 789 | "node_modules/nice-try": { 790 | "version": "1.0.5", 791 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 792 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 793 | }, 794 | "node_modules/normalize-package-data": { 795 | "version": "2.5.0", 796 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 797 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 798 | "dependencies": { 799 | "hosted-git-info": "^2.1.4", 800 | "resolve": "^1.10.0", 801 | "semver": "2 || 3 || 4 || 5", 802 | "validate-npm-package-license": "^3.0.1" 803 | } 804 | }, 805 | "node_modules/npm-run-all": { 806 | "version": "4.1.5", 807 | "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", 808 | "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", 809 | "dependencies": { 810 | "ansi-styles": "^3.2.1", 811 | "chalk": "^2.4.1", 812 | "cross-spawn": "^6.0.5", 813 | "memorystream": "^0.3.1", 814 | "minimatch": "^3.0.4", 815 | "pidtree": "^0.3.0", 816 | "read-pkg": "^3.0.0", 817 | "shell-quote": "^1.6.1", 818 | "string.prototype.padend": "^3.0.0" 819 | }, 820 | "bin": { 821 | "npm-run-all": "bin/npm-run-all/index.js", 822 | "run-p": "bin/run-p/index.js", 823 | "run-s": "bin/run-s/index.js" 824 | }, 825 | "engines": { 826 | "node": ">= 4" 827 | } 828 | }, 829 | "node_modules/npm-run-all/node_modules/brace-expansion": { 830 | "version": "1.1.11", 831 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 832 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 833 | "dependencies": { 834 | "balanced-match": "^1.0.0", 835 | "concat-map": "0.0.1" 836 | } 837 | }, 838 | "node_modules/npm-run-all/node_modules/minimatch": { 839 | "version": "3.1.2", 840 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 841 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 842 | "dependencies": { 843 | "brace-expansion": "^1.1.7" 844 | }, 845 | "engines": { 846 | "node": "*" 847 | } 848 | }, 849 | "node_modules/nunjucks": { 850 | "version": "3.2.3", 851 | "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", 852 | "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", 853 | "dependencies": { 854 | "a-sync-waterfall": "^1.0.0", 855 | "asap": "^2.0.3", 856 | "commander": "^5.1.0" 857 | }, 858 | "bin": { 859 | "nunjucks-precompile": "bin/precompile" 860 | }, 861 | "engines": { 862 | "node": ">= 6.9.0" 863 | }, 864 | "peerDependencies": { 865 | "chokidar": "^3.3.0" 866 | }, 867 | "peerDependenciesMeta": { 868 | "chokidar": { 869 | "optional": true 870 | } 871 | } 872 | }, 873 | "node_modules/nunjucks/node_modules/commander": { 874 | "version": "5.1.0", 875 | "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", 876 | "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", 877 | "engines": { 878 | "node": ">= 6" 879 | } 880 | }, 881 | "node_modules/object-inspect": { 882 | "version": "1.12.3", 883 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 884 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 885 | "funding": { 886 | "url": "https://github.com/sponsors/ljharb" 887 | } 888 | }, 889 | "node_modules/object-keys": { 890 | "version": "1.1.1", 891 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 892 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 893 | "engines": { 894 | "node": ">= 0.4" 895 | } 896 | }, 897 | "node_modules/object.assign": { 898 | "version": "4.1.4", 899 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", 900 | "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", 901 | "dependencies": { 902 | "call-bind": "^1.0.2", 903 | "define-properties": "^1.1.4", 904 | "has-symbols": "^1.0.3", 905 | "object-keys": "^1.1.1" 906 | }, 907 | "engines": { 908 | "node": ">= 0.4" 909 | }, 910 | "funding": { 911 | "url": "https://github.com/sponsors/ljharb" 912 | } 913 | }, 914 | "node_modules/parse-json": { 915 | "version": "4.0.0", 916 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 917 | "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", 918 | "dependencies": { 919 | "error-ex": "^1.3.1", 920 | "json-parse-better-errors": "^1.0.1" 921 | }, 922 | "engines": { 923 | "node": ">=4" 924 | } 925 | }, 926 | "node_modules/path-key": { 927 | "version": "2.0.1", 928 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 929 | "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", 930 | "engines": { 931 | "node": ">=4" 932 | } 933 | }, 934 | "node_modules/path-parse": { 935 | "version": "1.0.7", 936 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 937 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 938 | }, 939 | "node_modules/path-scurry": { 940 | "version": "1.6.1", 941 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.1.tgz", 942 | "integrity": "sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==", 943 | "dependencies": { 944 | "lru-cache": "^7.14.1", 945 | "minipass": "^4.0.2" 946 | }, 947 | "engines": { 948 | "node": ">=14" 949 | }, 950 | "funding": { 951 | "url": "https://github.com/sponsors/isaacs" 952 | } 953 | }, 954 | "node_modules/path-type": { 955 | "version": "3.0.0", 956 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", 957 | "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", 958 | "dependencies": { 959 | "pify": "^3.0.0" 960 | }, 961 | "engines": { 962 | "node": ">=4" 963 | } 964 | }, 965 | "node_modules/pidtree": { 966 | "version": "0.3.1", 967 | "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", 968 | "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", 969 | "bin": { 970 | "pidtree": "bin/pidtree.js" 971 | }, 972 | "engines": { 973 | "node": ">=0.10" 974 | } 975 | }, 976 | "node_modules/pify": { 977 | "version": "3.0.0", 978 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 979 | "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", 980 | "engines": { 981 | "node": ">=4" 982 | } 983 | }, 984 | "node_modules/prettier": { 985 | "version": "2.8.4", 986 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", 987 | "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", 988 | "dev": true, 989 | "bin": { 990 | "prettier": "bin-prettier.js" 991 | }, 992 | "engines": { 993 | "node": ">=10.13.0" 994 | }, 995 | "funding": { 996 | "url": "https://github.com/prettier/prettier?sponsor=1" 997 | } 998 | }, 999 | "node_modules/prompt-sync": { 1000 | "version": "4.2.0", 1001 | "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", 1002 | "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", 1003 | "dependencies": { 1004 | "strip-ansi": "^5.0.0" 1005 | } 1006 | }, 1007 | "node_modules/react": { 1008 | "version": "18.2.0", 1009 | "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", 1010 | "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", 1011 | "peer": true, 1012 | "dependencies": { 1013 | "loose-envify": "^1.1.0" 1014 | }, 1015 | "engines": { 1016 | "node": ">=0.10.0" 1017 | } 1018 | }, 1019 | "node_modules/react-reconciler": { 1020 | "version": "0.29.0", 1021 | "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz", 1022 | "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==", 1023 | "dependencies": { 1024 | "loose-envify": "^1.1.0", 1025 | "scheduler": "^0.23.0" 1026 | }, 1027 | "engines": { 1028 | "node": ">=0.10.0" 1029 | }, 1030 | "peerDependencies": { 1031 | "react": "^18.2.0" 1032 | } 1033 | }, 1034 | "node_modules/read-pkg": { 1035 | "version": "3.0.0", 1036 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", 1037 | "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", 1038 | "dependencies": { 1039 | "load-json-file": "^4.0.0", 1040 | "normalize-package-data": "^2.3.2", 1041 | "path-type": "^3.0.0" 1042 | }, 1043 | "engines": { 1044 | "node": ">=4" 1045 | } 1046 | }, 1047 | "node_modules/regexp.prototype.flags": { 1048 | "version": "1.4.3", 1049 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", 1050 | "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", 1051 | "dependencies": { 1052 | "call-bind": "^1.0.2", 1053 | "define-properties": "^1.1.3", 1054 | "functions-have-names": "^1.2.2" 1055 | }, 1056 | "engines": { 1057 | "node": ">= 0.4" 1058 | }, 1059 | "funding": { 1060 | "url": "https://github.com/sponsors/ljharb" 1061 | } 1062 | }, 1063 | "node_modules/resolve": { 1064 | "version": "1.22.1", 1065 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 1066 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 1067 | "dependencies": { 1068 | "is-core-module": "^2.9.0", 1069 | "path-parse": "^1.0.7", 1070 | "supports-preserve-symlinks-flag": "^1.0.0" 1071 | }, 1072 | "bin": { 1073 | "resolve": "bin/resolve" 1074 | }, 1075 | "funding": { 1076 | "url": "https://github.com/sponsors/ljharb" 1077 | } 1078 | }, 1079 | "node_modules/safe-regex-test": { 1080 | "version": "1.0.0", 1081 | "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", 1082 | "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", 1083 | "dependencies": { 1084 | "call-bind": "^1.0.2", 1085 | "get-intrinsic": "^1.1.3", 1086 | "is-regex": "^1.1.4" 1087 | }, 1088 | "funding": { 1089 | "url": "https://github.com/sponsors/ljharb" 1090 | } 1091 | }, 1092 | "node_modules/scheduler": { 1093 | "version": "0.23.0", 1094 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", 1095 | "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", 1096 | "dependencies": { 1097 | "loose-envify": "^1.1.0" 1098 | } 1099 | }, 1100 | "node_modules/semver": { 1101 | "version": "5.7.1", 1102 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1103 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 1104 | "bin": { 1105 | "semver": "bin/semver" 1106 | } 1107 | }, 1108 | "node_modules/shebang-command": { 1109 | "version": "1.2.0", 1110 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1111 | "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", 1112 | "dependencies": { 1113 | "shebang-regex": "^1.0.0" 1114 | }, 1115 | "engines": { 1116 | "node": ">=0.10.0" 1117 | } 1118 | }, 1119 | "node_modules/shebang-regex": { 1120 | "version": "1.0.0", 1121 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1122 | "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", 1123 | "engines": { 1124 | "node": ">=0.10.0" 1125 | } 1126 | }, 1127 | "node_modules/shell-quote": { 1128 | "version": "1.8.0", 1129 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", 1130 | "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", 1131 | "funding": { 1132 | "url": "https://github.com/sponsors/ljharb" 1133 | } 1134 | }, 1135 | "node_modules/side-channel": { 1136 | "version": "1.0.4", 1137 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1138 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1139 | "dependencies": { 1140 | "call-bind": "^1.0.0", 1141 | "get-intrinsic": "^1.0.2", 1142 | "object-inspect": "^1.9.0" 1143 | }, 1144 | "funding": { 1145 | "url": "https://github.com/sponsors/ljharb" 1146 | } 1147 | }, 1148 | "node_modules/spdx-correct": { 1149 | "version": "3.2.0", 1150 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", 1151 | "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", 1152 | "dependencies": { 1153 | "spdx-expression-parse": "^3.0.0", 1154 | "spdx-license-ids": "^3.0.0" 1155 | } 1156 | }, 1157 | "node_modules/spdx-exceptions": { 1158 | "version": "2.3.0", 1159 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", 1160 | "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" 1161 | }, 1162 | "node_modules/spdx-expression-parse": { 1163 | "version": "3.0.1", 1164 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 1165 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", 1166 | "dependencies": { 1167 | "spdx-exceptions": "^2.1.0", 1168 | "spdx-license-ids": "^3.0.0" 1169 | } 1170 | }, 1171 | "node_modules/spdx-license-ids": { 1172 | "version": "3.0.13", 1173 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", 1174 | "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==" 1175 | }, 1176 | "node_modules/string.prototype.padend": { 1177 | "version": "3.1.4", 1178 | "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz", 1179 | "integrity": "sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==", 1180 | "dependencies": { 1181 | "call-bind": "^1.0.2", 1182 | "define-properties": "^1.1.4", 1183 | "es-abstract": "^1.20.4" 1184 | }, 1185 | "engines": { 1186 | "node": ">= 0.4" 1187 | }, 1188 | "funding": { 1189 | "url": "https://github.com/sponsors/ljharb" 1190 | } 1191 | }, 1192 | "node_modules/string.prototype.trimend": { 1193 | "version": "1.0.6", 1194 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", 1195 | "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", 1196 | "dependencies": { 1197 | "call-bind": "^1.0.2", 1198 | "define-properties": "^1.1.4", 1199 | "es-abstract": "^1.20.4" 1200 | }, 1201 | "funding": { 1202 | "url": "https://github.com/sponsors/ljharb" 1203 | } 1204 | }, 1205 | "node_modules/string.prototype.trimstart": { 1206 | "version": "1.0.6", 1207 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", 1208 | "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", 1209 | "dependencies": { 1210 | "call-bind": "^1.0.2", 1211 | "define-properties": "^1.1.4", 1212 | "es-abstract": "^1.20.4" 1213 | }, 1214 | "funding": { 1215 | "url": "https://github.com/sponsors/ljharb" 1216 | } 1217 | }, 1218 | "node_modules/strip-ansi": { 1219 | "version": "5.2.0", 1220 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1221 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1222 | "dependencies": { 1223 | "ansi-regex": "^4.1.0" 1224 | }, 1225 | "engines": { 1226 | "node": ">=6" 1227 | } 1228 | }, 1229 | "node_modules/strip-bom": { 1230 | "version": "3.0.0", 1231 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 1232 | "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", 1233 | "engines": { 1234 | "node": ">=4" 1235 | } 1236 | }, 1237 | "node_modules/supports-color": { 1238 | "version": "5.5.0", 1239 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1240 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1241 | "dependencies": { 1242 | "has-flag": "^3.0.0" 1243 | }, 1244 | "engines": { 1245 | "node": ">=4" 1246 | } 1247 | }, 1248 | "node_modules/supports-preserve-symlinks-flag": { 1249 | "version": "1.0.0", 1250 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1251 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1252 | "engines": { 1253 | "node": ">= 0.4" 1254 | }, 1255 | "funding": { 1256 | "url": "https://github.com/sponsors/ljharb" 1257 | } 1258 | }, 1259 | "node_modules/typed-array-length": { 1260 | "version": "1.0.4", 1261 | "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", 1262 | "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", 1263 | "dependencies": { 1264 | "call-bind": "^1.0.2", 1265 | "for-each": "^0.3.3", 1266 | "is-typed-array": "^1.1.9" 1267 | }, 1268 | "funding": { 1269 | "url": "https://github.com/sponsors/ljharb" 1270 | } 1271 | }, 1272 | "node_modules/typed-bitwig-api": { 1273 | "version": "17.0.1", 1274 | "resolved": "https://registry.npmjs.org/typed-bitwig-api/-/typed-bitwig-api-17.0.1.tgz", 1275 | "integrity": "sha512-JseWff60xso8dgmH//U6b1P2k5PQHWDInpqE992Y2kIvKbppJgckc1vtHOLSwi1u31tJf5cs3IqMbW70gb4TeA==", 1276 | "dev": true 1277 | }, 1278 | "node_modules/typescript": { 1279 | "version": "4.9.5", 1280 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 1281 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 1282 | "dev": true, 1283 | "bin": { 1284 | "tsc": "bin/tsc", 1285 | "tsserver": "bin/tsserver" 1286 | }, 1287 | "engines": { 1288 | "node": ">=4.2.0" 1289 | } 1290 | }, 1291 | "node_modules/unbox-primitive": { 1292 | "version": "1.0.2", 1293 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", 1294 | "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", 1295 | "dependencies": { 1296 | "call-bind": "^1.0.2", 1297 | "has-bigints": "^1.0.2", 1298 | "has-symbols": "^1.0.3", 1299 | "which-boxed-primitive": "^1.0.2" 1300 | }, 1301 | "funding": { 1302 | "url": "https://github.com/sponsors/ljharb" 1303 | } 1304 | }, 1305 | "node_modules/uuid": { 1306 | "version": "9.0.0", 1307 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", 1308 | "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", 1309 | "bin": { 1310 | "uuid": "dist/bin/uuid" 1311 | } 1312 | }, 1313 | "node_modules/validate-npm-package-license": { 1314 | "version": "3.0.4", 1315 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 1316 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 1317 | "dependencies": { 1318 | "spdx-correct": "^3.0.0", 1319 | "spdx-expression-parse": "^3.0.0" 1320 | } 1321 | }, 1322 | "node_modules/which": { 1323 | "version": "1.3.1", 1324 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1325 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1326 | "dependencies": { 1327 | "isexe": "^2.0.0" 1328 | }, 1329 | "bin": { 1330 | "which": "bin/which" 1331 | } 1332 | }, 1333 | "node_modules/which-boxed-primitive": { 1334 | "version": "1.0.2", 1335 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 1336 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", 1337 | "dependencies": { 1338 | "is-bigint": "^1.0.1", 1339 | "is-boolean-object": "^1.1.0", 1340 | "is-number-object": "^1.0.4", 1341 | "is-string": "^1.0.5", 1342 | "is-symbol": "^1.0.3" 1343 | }, 1344 | "funding": { 1345 | "url": "https://github.com/sponsors/ljharb" 1346 | } 1347 | }, 1348 | "node_modules/which-typed-array": { 1349 | "version": "1.1.9", 1350 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", 1351 | "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", 1352 | "dependencies": { 1353 | "available-typed-arrays": "^1.0.5", 1354 | "call-bind": "^1.0.2", 1355 | "for-each": "^0.3.3", 1356 | "gopd": "^1.0.1", 1357 | "has-tostringtag": "^1.0.0", 1358 | "is-typed-array": "^1.1.10" 1359 | }, 1360 | "engines": { 1361 | "node": ">= 0.4" 1362 | }, 1363 | "funding": { 1364 | "url": "https://github.com/sponsors/ljharb" 1365 | } 1366 | } 1367 | }, 1368 | "dependencies": { 1369 | "@types/node": { 1370 | "version": "16.18.14", 1371 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.14.tgz", 1372 | "integrity": "sha512-wvzClDGQXOCVNU4APPopC2KtMYukaF1MN/W3xAmslx22Z4/IF1/izDMekuyoUlwfnDHYCIZGaj7jMwnJKBTxKw==", 1373 | "dev": true 1374 | }, 1375 | "@types/prop-types": { 1376 | "version": "15.7.5", 1377 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", 1378 | "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", 1379 | "dev": true 1380 | }, 1381 | "@types/react": { 1382 | "version": "18.0.28", 1383 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", 1384 | "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", 1385 | "dev": true, 1386 | "requires": { 1387 | "@types/prop-types": "*", 1388 | "@types/scheduler": "*", 1389 | "csstype": "^3.0.2" 1390 | } 1391 | }, 1392 | "@types/react-reconciler": { 1393 | "version": "0.28.2", 1394 | "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz", 1395 | "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==", 1396 | "dev": true, 1397 | "requires": { 1398 | "@types/react": "*" 1399 | } 1400 | }, 1401 | "@types/scheduler": { 1402 | "version": "0.16.2", 1403 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", 1404 | "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", 1405 | "dev": true 1406 | }, 1407 | "a-sync-waterfall": { 1408 | "version": "1.0.1", 1409 | "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", 1410 | "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" 1411 | }, 1412 | "ansi-regex": { 1413 | "version": "4.1.1", 1414 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", 1415 | "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" 1416 | }, 1417 | "ansi-styles": { 1418 | "version": "3.2.1", 1419 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 1420 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 1421 | "requires": { 1422 | "color-convert": "^1.9.0" 1423 | } 1424 | }, 1425 | "asap": { 1426 | "version": "2.0.6", 1427 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 1428 | "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" 1429 | }, 1430 | "available-typed-arrays": { 1431 | "version": "1.0.5", 1432 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", 1433 | "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" 1434 | }, 1435 | "balanced-match": { 1436 | "version": "1.0.2", 1437 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1438 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 1439 | }, 1440 | "brace-expansion": { 1441 | "version": "2.0.1", 1442 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 1443 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 1444 | "requires": { 1445 | "balanced-match": "^1.0.0" 1446 | } 1447 | }, 1448 | "call-bind": { 1449 | "version": "1.0.2", 1450 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 1451 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 1452 | "requires": { 1453 | "function-bind": "^1.1.1", 1454 | "get-intrinsic": "^1.0.2" 1455 | } 1456 | }, 1457 | "chalk": { 1458 | "version": "2.4.2", 1459 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 1460 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 1461 | "requires": { 1462 | "ansi-styles": "^3.2.1", 1463 | "escape-string-regexp": "^1.0.5", 1464 | "supports-color": "^5.3.0" 1465 | } 1466 | }, 1467 | "color-convert": { 1468 | "version": "1.9.3", 1469 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 1470 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 1471 | "requires": { 1472 | "color-name": "1.1.3" 1473 | } 1474 | }, 1475 | "color-name": { 1476 | "version": "1.1.3", 1477 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 1478 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 1479 | }, 1480 | "colors": { 1481 | "version": "1.4.0", 1482 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 1483 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 1484 | }, 1485 | "commander": { 1486 | "version": "10.0.0", 1487 | "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", 1488 | "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==" 1489 | }, 1490 | "concat-map": { 1491 | "version": "0.0.1", 1492 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1493 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 1494 | }, 1495 | "cross-spawn": { 1496 | "version": "6.0.5", 1497 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 1498 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 1499 | "requires": { 1500 | "nice-try": "^1.0.4", 1501 | "path-key": "^2.0.1", 1502 | "semver": "^5.5.0", 1503 | "shebang-command": "^1.2.0", 1504 | "which": "^1.2.9" 1505 | } 1506 | }, 1507 | "csstype": { 1508 | "version": "3.1.1", 1509 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", 1510 | "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", 1511 | "dev": true 1512 | }, 1513 | "define-properties": { 1514 | "version": "1.2.0", 1515 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", 1516 | "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", 1517 | "requires": { 1518 | "has-property-descriptors": "^1.0.0", 1519 | "object-keys": "^1.1.1" 1520 | } 1521 | }, 1522 | "error-ex": { 1523 | "version": "1.3.2", 1524 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 1525 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 1526 | "requires": { 1527 | "is-arrayish": "^0.2.1" 1528 | } 1529 | }, 1530 | "es-abstract": { 1531 | "version": "1.21.1", 1532 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", 1533 | "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", 1534 | "requires": { 1535 | "available-typed-arrays": "^1.0.5", 1536 | "call-bind": "^1.0.2", 1537 | "es-set-tostringtag": "^2.0.1", 1538 | "es-to-primitive": "^1.2.1", 1539 | "function-bind": "^1.1.1", 1540 | "function.prototype.name": "^1.1.5", 1541 | "get-intrinsic": "^1.1.3", 1542 | "get-symbol-description": "^1.0.0", 1543 | "globalthis": "^1.0.3", 1544 | "gopd": "^1.0.1", 1545 | "has": "^1.0.3", 1546 | "has-property-descriptors": "^1.0.0", 1547 | "has-proto": "^1.0.1", 1548 | "has-symbols": "^1.0.3", 1549 | "internal-slot": "^1.0.4", 1550 | "is-array-buffer": "^3.0.1", 1551 | "is-callable": "^1.2.7", 1552 | "is-negative-zero": "^2.0.2", 1553 | "is-regex": "^1.1.4", 1554 | "is-shared-array-buffer": "^1.0.2", 1555 | "is-string": "^1.0.7", 1556 | "is-typed-array": "^1.1.10", 1557 | "is-weakref": "^1.0.2", 1558 | "object-inspect": "^1.12.2", 1559 | "object-keys": "^1.1.1", 1560 | "object.assign": "^4.1.4", 1561 | "regexp.prototype.flags": "^1.4.3", 1562 | "safe-regex-test": "^1.0.0", 1563 | "string.prototype.trimend": "^1.0.6", 1564 | "string.prototype.trimstart": "^1.0.6", 1565 | "typed-array-length": "^1.0.4", 1566 | "unbox-primitive": "^1.0.2", 1567 | "which-typed-array": "^1.1.9" 1568 | } 1569 | }, 1570 | "es-set-tostringtag": { 1571 | "version": "2.0.1", 1572 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", 1573 | "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", 1574 | "requires": { 1575 | "get-intrinsic": "^1.1.3", 1576 | "has": "^1.0.3", 1577 | "has-tostringtag": "^1.0.0" 1578 | } 1579 | }, 1580 | "es-to-primitive": { 1581 | "version": "1.2.1", 1582 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 1583 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 1584 | "requires": { 1585 | "is-callable": "^1.1.4", 1586 | "is-date-object": "^1.0.1", 1587 | "is-symbol": "^1.0.2" 1588 | } 1589 | }, 1590 | "escape-string-regexp": { 1591 | "version": "1.0.5", 1592 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 1593 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" 1594 | }, 1595 | "for-each": { 1596 | "version": "0.3.3", 1597 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 1598 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 1599 | "requires": { 1600 | "is-callable": "^1.1.3" 1601 | } 1602 | }, 1603 | "fs.realpath": { 1604 | "version": "1.0.0", 1605 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1606 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 1607 | }, 1608 | "function-bind": { 1609 | "version": "1.1.1", 1610 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1611 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 1612 | }, 1613 | "function.prototype.name": { 1614 | "version": "1.1.5", 1615 | "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", 1616 | "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", 1617 | "requires": { 1618 | "call-bind": "^1.0.2", 1619 | "define-properties": "^1.1.3", 1620 | "es-abstract": "^1.19.0", 1621 | "functions-have-names": "^1.2.2" 1622 | } 1623 | }, 1624 | "functions-have-names": { 1625 | "version": "1.2.3", 1626 | "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", 1627 | "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" 1628 | }, 1629 | "get-intrinsic": { 1630 | "version": "1.2.0", 1631 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", 1632 | "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", 1633 | "requires": { 1634 | "function-bind": "^1.1.1", 1635 | "has": "^1.0.3", 1636 | "has-symbols": "^1.0.3" 1637 | } 1638 | }, 1639 | "get-symbol-description": { 1640 | "version": "1.0.0", 1641 | "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", 1642 | "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", 1643 | "requires": { 1644 | "call-bind": "^1.0.2", 1645 | "get-intrinsic": "^1.1.1" 1646 | } 1647 | }, 1648 | "glob": { 1649 | "version": "9.2.1", 1650 | "resolved": "https://registry.npmjs.org/glob/-/glob-9.2.1.tgz", 1651 | "integrity": "sha512-Pxxgq3W0HyA3XUvSXcFhRSs+43Jsx0ddxcFrbjxNGkL2Ak5BAUBxLqI5G6ADDeCHLfzzXFhe0b1yYcctGmytMA==", 1652 | "requires": { 1653 | "fs.realpath": "^1.0.0", 1654 | "minimatch": "^7.4.1", 1655 | "minipass": "^4.2.4", 1656 | "path-scurry": "^1.6.1" 1657 | } 1658 | }, 1659 | "globalthis": { 1660 | "version": "1.0.3", 1661 | "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", 1662 | "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", 1663 | "requires": { 1664 | "define-properties": "^1.1.3" 1665 | } 1666 | }, 1667 | "gopd": { 1668 | "version": "1.0.1", 1669 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 1670 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 1671 | "requires": { 1672 | "get-intrinsic": "^1.1.3" 1673 | } 1674 | }, 1675 | "graceful-fs": { 1676 | "version": "4.2.10", 1677 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 1678 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" 1679 | }, 1680 | "has": { 1681 | "version": "1.0.3", 1682 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1683 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1684 | "requires": { 1685 | "function-bind": "^1.1.1" 1686 | } 1687 | }, 1688 | "has-bigints": { 1689 | "version": "1.0.2", 1690 | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", 1691 | "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" 1692 | }, 1693 | "has-flag": { 1694 | "version": "3.0.0", 1695 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1696 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" 1697 | }, 1698 | "has-property-descriptors": { 1699 | "version": "1.0.0", 1700 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", 1701 | "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", 1702 | "requires": { 1703 | "get-intrinsic": "^1.1.1" 1704 | } 1705 | }, 1706 | "has-proto": { 1707 | "version": "1.0.1", 1708 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 1709 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" 1710 | }, 1711 | "has-symbols": { 1712 | "version": "1.0.3", 1713 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 1714 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 1715 | }, 1716 | "has-tostringtag": { 1717 | "version": "1.0.0", 1718 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", 1719 | "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", 1720 | "requires": { 1721 | "has-symbols": "^1.0.2" 1722 | } 1723 | }, 1724 | "hosted-git-info": { 1725 | "version": "2.8.9", 1726 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 1727 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" 1728 | }, 1729 | "internal-slot": { 1730 | "version": "1.0.5", 1731 | "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", 1732 | "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", 1733 | "requires": { 1734 | "get-intrinsic": "^1.2.0", 1735 | "has": "^1.0.3", 1736 | "side-channel": "^1.0.4" 1737 | } 1738 | }, 1739 | "is-array-buffer": { 1740 | "version": "3.0.2", 1741 | "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", 1742 | "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", 1743 | "requires": { 1744 | "call-bind": "^1.0.2", 1745 | "get-intrinsic": "^1.2.0", 1746 | "is-typed-array": "^1.1.10" 1747 | } 1748 | }, 1749 | "is-arrayish": { 1750 | "version": "0.2.1", 1751 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1752 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" 1753 | }, 1754 | "is-bigint": { 1755 | "version": "1.0.4", 1756 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", 1757 | "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", 1758 | "requires": { 1759 | "has-bigints": "^1.0.1" 1760 | } 1761 | }, 1762 | "is-boolean-object": { 1763 | "version": "1.1.2", 1764 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", 1765 | "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", 1766 | "requires": { 1767 | "call-bind": "^1.0.2", 1768 | "has-tostringtag": "^1.0.0" 1769 | } 1770 | }, 1771 | "is-callable": { 1772 | "version": "1.2.7", 1773 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", 1774 | "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" 1775 | }, 1776 | "is-core-module": { 1777 | "version": "2.11.0", 1778 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 1779 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 1780 | "requires": { 1781 | "has": "^1.0.3" 1782 | } 1783 | }, 1784 | "is-date-object": { 1785 | "version": "1.0.5", 1786 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", 1787 | "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", 1788 | "requires": { 1789 | "has-tostringtag": "^1.0.0" 1790 | } 1791 | }, 1792 | "is-negative-zero": { 1793 | "version": "2.0.2", 1794 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", 1795 | "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" 1796 | }, 1797 | "is-number-object": { 1798 | "version": "1.0.7", 1799 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", 1800 | "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", 1801 | "requires": { 1802 | "has-tostringtag": "^1.0.0" 1803 | } 1804 | }, 1805 | "is-regex": { 1806 | "version": "1.1.4", 1807 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", 1808 | "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", 1809 | "requires": { 1810 | "call-bind": "^1.0.2", 1811 | "has-tostringtag": "^1.0.0" 1812 | } 1813 | }, 1814 | "is-shared-array-buffer": { 1815 | "version": "1.0.2", 1816 | "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", 1817 | "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", 1818 | "requires": { 1819 | "call-bind": "^1.0.2" 1820 | } 1821 | }, 1822 | "is-string": { 1823 | "version": "1.0.7", 1824 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", 1825 | "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", 1826 | "requires": { 1827 | "has-tostringtag": "^1.0.0" 1828 | } 1829 | }, 1830 | "is-symbol": { 1831 | "version": "1.0.4", 1832 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", 1833 | "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", 1834 | "requires": { 1835 | "has-symbols": "^1.0.2" 1836 | } 1837 | }, 1838 | "is-typed-array": { 1839 | "version": "1.1.10", 1840 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", 1841 | "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", 1842 | "requires": { 1843 | "available-typed-arrays": "^1.0.5", 1844 | "call-bind": "^1.0.2", 1845 | "for-each": "^0.3.3", 1846 | "gopd": "^1.0.1", 1847 | "has-tostringtag": "^1.0.0" 1848 | } 1849 | }, 1850 | "is-weakref": { 1851 | "version": "1.0.2", 1852 | "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", 1853 | "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", 1854 | "requires": { 1855 | "call-bind": "^1.0.2" 1856 | } 1857 | }, 1858 | "isexe": { 1859 | "version": "2.0.0", 1860 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1861 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 1862 | }, 1863 | "js-tokens": { 1864 | "version": "4.0.0", 1865 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1866 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 1867 | }, 1868 | "json-parse-better-errors": { 1869 | "version": "1.0.2", 1870 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 1871 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" 1872 | }, 1873 | "load-json-file": { 1874 | "version": "4.0.0", 1875 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 1876 | "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", 1877 | "requires": { 1878 | "graceful-fs": "^4.1.2", 1879 | "parse-json": "^4.0.0", 1880 | "pify": "^3.0.0", 1881 | "strip-bom": "^3.0.0" 1882 | } 1883 | }, 1884 | "loose-envify": { 1885 | "version": "1.4.0", 1886 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1887 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1888 | "requires": { 1889 | "js-tokens": "^3.0.0 || ^4.0.0" 1890 | } 1891 | }, 1892 | "lru-cache": { 1893 | "version": "7.18.3", 1894 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", 1895 | "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" 1896 | }, 1897 | "memorystream": { 1898 | "version": "0.3.1", 1899 | "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", 1900 | "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==" 1901 | }, 1902 | "minimatch": { 1903 | "version": "7.4.2", 1904 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.2.tgz", 1905 | "integrity": "sha512-xy4q7wou3vUoC9k1xGTXc+awNdGaGVHtFUaey8tiX4H1QRc04DZ/rmDFwNm2EBsuYEhAZ6SgMmYf3InGY6OauA==", 1906 | "requires": { 1907 | "brace-expansion": "^2.0.1" 1908 | } 1909 | }, 1910 | "minipass": { 1911 | "version": "4.2.5", 1912 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", 1913 | "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==" 1914 | }, 1915 | "nice-try": { 1916 | "version": "1.0.5", 1917 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 1918 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 1919 | }, 1920 | "normalize-package-data": { 1921 | "version": "2.5.0", 1922 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 1923 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 1924 | "requires": { 1925 | "hosted-git-info": "^2.1.4", 1926 | "resolve": "^1.10.0", 1927 | "semver": "2 || 3 || 4 || 5", 1928 | "validate-npm-package-license": "^3.0.1" 1929 | } 1930 | }, 1931 | "npm-run-all": { 1932 | "version": "4.1.5", 1933 | "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", 1934 | "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", 1935 | "requires": { 1936 | "ansi-styles": "^3.2.1", 1937 | "chalk": "^2.4.1", 1938 | "cross-spawn": "^6.0.5", 1939 | "memorystream": "^0.3.1", 1940 | "minimatch": "^3.0.4", 1941 | "pidtree": "^0.3.0", 1942 | "read-pkg": "^3.0.0", 1943 | "shell-quote": "^1.6.1", 1944 | "string.prototype.padend": "^3.0.0" 1945 | }, 1946 | "dependencies": { 1947 | "brace-expansion": { 1948 | "version": "1.1.11", 1949 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1950 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1951 | "requires": { 1952 | "balanced-match": "^1.0.0", 1953 | "concat-map": "0.0.1" 1954 | } 1955 | }, 1956 | "minimatch": { 1957 | "version": "3.1.2", 1958 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1959 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1960 | "requires": { 1961 | "brace-expansion": "^1.1.7" 1962 | } 1963 | } 1964 | } 1965 | }, 1966 | "nunjucks": { 1967 | "version": "3.2.3", 1968 | "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", 1969 | "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", 1970 | "requires": { 1971 | "a-sync-waterfall": "^1.0.0", 1972 | "asap": "^2.0.3", 1973 | "commander": "^5.1.0" 1974 | }, 1975 | "dependencies": { 1976 | "commander": { 1977 | "version": "5.1.0", 1978 | "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", 1979 | "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" 1980 | } 1981 | } 1982 | }, 1983 | "object-inspect": { 1984 | "version": "1.12.3", 1985 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 1986 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" 1987 | }, 1988 | "object-keys": { 1989 | "version": "1.1.1", 1990 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 1991 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" 1992 | }, 1993 | "object.assign": { 1994 | "version": "4.1.4", 1995 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", 1996 | "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", 1997 | "requires": { 1998 | "call-bind": "^1.0.2", 1999 | "define-properties": "^1.1.4", 2000 | "has-symbols": "^1.0.3", 2001 | "object-keys": "^1.1.1" 2002 | } 2003 | }, 2004 | "parse-json": { 2005 | "version": "4.0.0", 2006 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 2007 | "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", 2008 | "requires": { 2009 | "error-ex": "^1.3.1", 2010 | "json-parse-better-errors": "^1.0.1" 2011 | } 2012 | }, 2013 | "path-key": { 2014 | "version": "2.0.1", 2015 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 2016 | "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" 2017 | }, 2018 | "path-parse": { 2019 | "version": "1.0.7", 2020 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 2021 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 2022 | }, 2023 | "path-scurry": { 2024 | "version": "1.6.1", 2025 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.1.tgz", 2026 | "integrity": "sha512-OW+5s+7cw6253Q4E+8qQ/u1fVvcJQCJo/VFD8pje+dbJCF1n5ZRMV2AEHbGp+5Q7jxQIYJxkHopnj6nzdGeZLA==", 2027 | "requires": { 2028 | "lru-cache": "^7.14.1", 2029 | "minipass": "^4.0.2" 2030 | } 2031 | }, 2032 | "path-type": { 2033 | "version": "3.0.0", 2034 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", 2035 | "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", 2036 | "requires": { 2037 | "pify": "^3.0.0" 2038 | } 2039 | }, 2040 | "pidtree": { 2041 | "version": "0.3.1", 2042 | "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", 2043 | "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==" 2044 | }, 2045 | "pify": { 2046 | "version": "3.0.0", 2047 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 2048 | "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==" 2049 | }, 2050 | "prettier": { 2051 | "version": "2.8.4", 2052 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", 2053 | "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", 2054 | "dev": true 2055 | }, 2056 | "prompt-sync": { 2057 | "version": "4.2.0", 2058 | "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz", 2059 | "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==", 2060 | "requires": { 2061 | "strip-ansi": "^5.0.0" 2062 | } 2063 | }, 2064 | "react": { 2065 | "version": "18.2.0", 2066 | "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", 2067 | "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", 2068 | "peer": true, 2069 | "requires": { 2070 | "loose-envify": "^1.1.0" 2071 | } 2072 | }, 2073 | "react-reconciler": { 2074 | "version": "0.29.0", 2075 | "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz", 2076 | "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==", 2077 | "requires": { 2078 | "loose-envify": "^1.1.0", 2079 | "scheduler": "^0.23.0" 2080 | } 2081 | }, 2082 | "read-pkg": { 2083 | "version": "3.0.0", 2084 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", 2085 | "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", 2086 | "requires": { 2087 | "load-json-file": "^4.0.0", 2088 | "normalize-package-data": "^2.3.2", 2089 | "path-type": "^3.0.0" 2090 | } 2091 | }, 2092 | "regexp.prototype.flags": { 2093 | "version": "1.4.3", 2094 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", 2095 | "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", 2096 | "requires": { 2097 | "call-bind": "^1.0.2", 2098 | "define-properties": "^1.1.3", 2099 | "functions-have-names": "^1.2.2" 2100 | } 2101 | }, 2102 | "resolve": { 2103 | "version": "1.22.1", 2104 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 2105 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 2106 | "requires": { 2107 | "is-core-module": "^2.9.0", 2108 | "path-parse": "^1.0.7", 2109 | "supports-preserve-symlinks-flag": "^1.0.0" 2110 | } 2111 | }, 2112 | "safe-regex-test": { 2113 | "version": "1.0.0", 2114 | "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", 2115 | "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", 2116 | "requires": { 2117 | "call-bind": "^1.0.2", 2118 | "get-intrinsic": "^1.1.3", 2119 | "is-regex": "^1.1.4" 2120 | } 2121 | }, 2122 | "scheduler": { 2123 | "version": "0.23.0", 2124 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", 2125 | "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", 2126 | "requires": { 2127 | "loose-envify": "^1.1.0" 2128 | } 2129 | }, 2130 | "semver": { 2131 | "version": "5.7.1", 2132 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 2133 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 2134 | }, 2135 | "shebang-command": { 2136 | "version": "1.2.0", 2137 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 2138 | "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", 2139 | "requires": { 2140 | "shebang-regex": "^1.0.0" 2141 | } 2142 | }, 2143 | "shebang-regex": { 2144 | "version": "1.0.0", 2145 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 2146 | "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" 2147 | }, 2148 | "shell-quote": { 2149 | "version": "1.8.0", 2150 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", 2151 | "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==" 2152 | }, 2153 | "side-channel": { 2154 | "version": "1.0.4", 2155 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 2156 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 2157 | "requires": { 2158 | "call-bind": "^1.0.0", 2159 | "get-intrinsic": "^1.0.2", 2160 | "object-inspect": "^1.9.0" 2161 | } 2162 | }, 2163 | "spdx-correct": { 2164 | "version": "3.2.0", 2165 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", 2166 | "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", 2167 | "requires": { 2168 | "spdx-expression-parse": "^3.0.0", 2169 | "spdx-license-ids": "^3.0.0" 2170 | } 2171 | }, 2172 | "spdx-exceptions": { 2173 | "version": "2.3.0", 2174 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", 2175 | "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" 2176 | }, 2177 | "spdx-expression-parse": { 2178 | "version": "3.0.1", 2179 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 2180 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", 2181 | "requires": { 2182 | "spdx-exceptions": "^2.1.0", 2183 | "spdx-license-ids": "^3.0.0" 2184 | } 2185 | }, 2186 | "spdx-license-ids": { 2187 | "version": "3.0.13", 2188 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", 2189 | "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==" 2190 | }, 2191 | "string.prototype.padend": { 2192 | "version": "3.1.4", 2193 | "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz", 2194 | "integrity": "sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==", 2195 | "requires": { 2196 | "call-bind": "^1.0.2", 2197 | "define-properties": "^1.1.4", 2198 | "es-abstract": "^1.20.4" 2199 | } 2200 | }, 2201 | "string.prototype.trimend": { 2202 | "version": "1.0.6", 2203 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", 2204 | "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", 2205 | "requires": { 2206 | "call-bind": "^1.0.2", 2207 | "define-properties": "^1.1.4", 2208 | "es-abstract": "^1.20.4" 2209 | } 2210 | }, 2211 | "string.prototype.trimstart": { 2212 | "version": "1.0.6", 2213 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", 2214 | "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", 2215 | "requires": { 2216 | "call-bind": "^1.0.2", 2217 | "define-properties": "^1.1.4", 2218 | "es-abstract": "^1.20.4" 2219 | } 2220 | }, 2221 | "strip-ansi": { 2222 | "version": "5.2.0", 2223 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 2224 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 2225 | "requires": { 2226 | "ansi-regex": "^4.1.0" 2227 | } 2228 | }, 2229 | "strip-bom": { 2230 | "version": "3.0.0", 2231 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 2232 | "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" 2233 | }, 2234 | "supports-color": { 2235 | "version": "5.5.0", 2236 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 2237 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 2238 | "requires": { 2239 | "has-flag": "^3.0.0" 2240 | } 2241 | }, 2242 | "supports-preserve-symlinks-flag": { 2243 | "version": "1.0.0", 2244 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 2245 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" 2246 | }, 2247 | "typed-array-length": { 2248 | "version": "1.0.4", 2249 | "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", 2250 | "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", 2251 | "requires": { 2252 | "call-bind": "^1.0.2", 2253 | "for-each": "^0.3.3", 2254 | "is-typed-array": "^1.1.9" 2255 | } 2256 | }, 2257 | "typed-bitwig-api": { 2258 | "version": "17.0.1", 2259 | "resolved": "https://registry.npmjs.org/typed-bitwig-api/-/typed-bitwig-api-17.0.1.tgz", 2260 | "integrity": "sha512-JseWff60xso8dgmH//U6b1P2k5PQHWDInpqE992Y2kIvKbppJgckc1vtHOLSwi1u31tJf5cs3IqMbW70gb4TeA==", 2261 | "dev": true 2262 | }, 2263 | "typescript": { 2264 | "version": "4.9.5", 2265 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 2266 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 2267 | "dev": true 2268 | }, 2269 | "unbox-primitive": { 2270 | "version": "1.0.2", 2271 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", 2272 | "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", 2273 | "requires": { 2274 | "call-bind": "^1.0.2", 2275 | "has-bigints": "^1.0.2", 2276 | "has-symbols": "^1.0.3", 2277 | "which-boxed-primitive": "^1.0.2" 2278 | } 2279 | }, 2280 | "uuid": { 2281 | "version": "9.0.0", 2282 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", 2283 | "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" 2284 | }, 2285 | "validate-npm-package-license": { 2286 | "version": "3.0.4", 2287 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 2288 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 2289 | "requires": { 2290 | "spdx-correct": "^3.0.0", 2291 | "spdx-expression-parse": "^3.0.0" 2292 | } 2293 | }, 2294 | "which": { 2295 | "version": "1.3.1", 2296 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 2297 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 2298 | "requires": { 2299 | "isexe": "^2.0.0" 2300 | } 2301 | }, 2302 | "which-boxed-primitive": { 2303 | "version": "1.0.2", 2304 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 2305 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", 2306 | "requires": { 2307 | "is-bigint": "^1.0.1", 2308 | "is-boolean-object": "^1.1.0", 2309 | "is-number-object": "^1.0.4", 2310 | "is-string": "^1.0.5", 2311 | "is-symbol": "^1.0.3" 2312 | } 2313 | }, 2314 | "which-typed-array": { 2315 | "version": "1.1.9", 2316 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", 2317 | "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", 2318 | "requires": { 2319 | "available-typed-arrays": "^1.0.5", 2320 | "call-bind": "^1.0.2", 2321 | "for-each": "^0.3.3", 2322 | "gopd": "^1.0.1", 2323 | "has-tostringtag": "^1.0.0", 2324 | "is-typed-array": "^1.1.10" 2325 | } 2326 | } 2327 | } 2328 | } 2329 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bitwig", 3 | "description": "A framework for building Bitwig Studio controller scripts in React", 4 | "version": "1.1.0", 5 | "repository": "joslarson/react-bitwig", 6 | "author": "Joseph Larson", 7 | "tags": [ 8 | "bitwig", 9 | "react", 10 | "control surface script", 11 | "typescript" 12 | ], 13 | "license": "BSD-3-Clause", 14 | "bugs": { 15 | "url": "https://github.com/joslarson/react-bitwig/issues" 16 | }, 17 | "homepage": "https://github.com/joslarson/react-bitwig#readme", 18 | "main": "./dist/index.js", 19 | "bin": { 20 | "react-bitwig": "./bin/react-bitwig.mjs", 21 | "rb": "./bin/react-bitwig.mjs" 22 | }, 23 | "dependencies": { 24 | "colors": "^1.4.0", 25 | "commander": "^10.0.0", 26 | "glob": "^9.2.1", 27 | "npm-run-all": "^4.1.5", 28 | "nunjucks": "^3.2.3", 29 | "prompt-sync": "^4.2.0", 30 | "react-reconciler": "0.29.0", 31 | "uuid": "^9.0.0" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "^16.0.0", 35 | "@types/react-reconciler": "0.28.2", 36 | "prettier": "^2.8.4", 37 | "typed-bitwig-api": "^17.0.1", 38 | "typescript": "^4.9.5" 39 | }, 40 | "scripts": { 41 | "start": "tsc --watch", 42 | "build": "tsc", 43 | "clean": "rm -rf ./dist", 44 | "test": "echo \"OK!\"", 45 | "test:ts": "tsc --noEmit", 46 | "format": "prettier --write ./src", 47 | "preversion": "npm test && npm run build", 48 | "postversion": "npm run clean", 49 | "prepack": "npm test && npm run build" 50 | }, 51 | "engines": { 52 | "node": ">= 16.0.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/controller-script.tsx: -------------------------------------------------------------------------------- 1 | export type ControllerScriptProps = { 2 | api: number; 3 | vendor: string; 4 | name: string; 5 | version: string; 6 | uuid: string; 7 | author: string; 8 | midi: 9 | | { inputs: number; outputs: number } 10 | | { inputs: string[]; outputs: string[] } 11 | | { inputs: string[]; outputs: string[] }[]; 12 | children?: React.ReactNode; 13 | }; 14 | export const ControllerScript: React.FC = (props) => { 15 | return <>{props.children}; 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/midi.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // object pattern 4 | 5 | export type MidiObjectPatternMSN = { msn: number }; 6 | export type MidiObjectPatternLSN = { lsn: number }; 7 | 8 | export type MidiObjectPatternByte = 9 | | number 10 | | MidiObjectPatternMSN 11 | | MidiObjectPatternLSN; 12 | 13 | export type MidiObjectPattern< 14 | S extends MidiObjectPatternByte = MidiObjectPatternByte, 15 | D1 extends MidiObjectPatternByte = MidiObjectPatternByte, 16 | D2 extends MidiObjectPatternByte = MidiObjectPatternByte 17 | > = { status?: S; data1?: D1; data2?: D2 }; 18 | 19 | // hex pattern 20 | 21 | // prettier-ignore 22 | type HexToIntegerStatusMSNMap = { 23 | '8': 0x8, '9': 0x9, 'A': 0xA, 'B': 0xB, 'C': 0xC, 'D': 0xD, 'E': 0xE, 'F': 0xF, 24 | } 25 | 26 | // prettier-ignore 27 | type HexToIntegerDataMSNMap = { 28 | '0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, '5': 0x5, '6': 0x6, '7': 0x7, 29 | } 30 | 31 | // prettier-ignore 32 | type HexToIntegerLSNMap = { 33 | '0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, '5': 0x5, '6': 0x6, '7': 0x7, 34 | '8': 0x8, '9': 0x9, 'A': 0xA, 'B': 0xB, 'C': 0xC, 'D': 0xD, 'E': 0xE, 'F': 0xF, 35 | } 36 | 37 | // prettier-ignore 38 | type MidiStatusIntegerMap = { 39 | '80': 0x80, '81': 0x81, '82': 0x82, '83': 0x83, '84': 0x84, '85': 0x85, '86': 0x86, '87': 0x87, '88': 0x88, '89': 0x89, '8A': 0x8A, '8B': 0x8B, '8C': 0x8C, '8D': 0x8D, '8E': 0x8E, '8F': 0x8F, 40 | '90': 0x90, '91': 0x91, '92': 0x92, '93': 0x93, '94': 0x94, '95': 0x95, '96': 0x96, '97': 0x97, '98': 0x98, '99': 0x99, '9A': 0x9A, '9B': 0x9B, '9C': 0x9C, '9D': 0x9D, '9E': 0x9E, '9F': 0x9F, 41 | 'A0': 0xA0, 'A1': 0xA1, 'A2': 0xA2, 'A3': 0xA3, 'A4': 0xA4, 'A5': 0xA5, 'A6': 0xA6, 'A7': 0xA7, 'A8': 0xA8, 'A9': 0xA9, 'AA': 0xAA, 'AB': 0xAB, 'AC': 0xAC, 'AD': 0xAD, 'AE': 0xAE, 'AF': 0xAF, 42 | 'B0': 0xB0, 'B1': 0xB1, 'B2': 0xB2, 'B3': 0xB3, 'B4': 0xB4, 'B5': 0xB5, 'B6': 0xB6, 'B7': 0xB7, 'B8': 0xB8, 'B9': 0xB9, 'BA': 0xBA, 'BB': 0xBB, 'BC': 0xBC, 'BD': 0xBD, 'BE': 0xBE, 'BF': 0xBF, 43 | 'C0': 0xC0, 'C1': 0xC1, 'C2': 0xC2, 'C3': 0xC3, 'C4': 0xC4, 'C5': 0xC5, 'C6': 0xC6, 'C7': 0xC7, 'C8': 0xC8, 'C9': 0xC9, 'CA': 0xCA, 'CB': 0xCB, 'CC': 0xCC, 'CD': 0xCD, 'CE': 0xCE, 'CF': 0xCF, 44 | 'D0': 0xD0, 'D1': 0xD1, 'D2': 0xD2, 'D3': 0xD3, 'D4': 0xD4, 'D5': 0xD5, 'D6': 0xD6, 'D7': 0xD7, 'D8': 0xD8, 'D9': 0xD9, 'DA': 0xDA, 'DB': 0xDB, 'DC': 0xDC, 'DD': 0xDD, 'DE': 0xDE, 'DF': 0xDF, 45 | 'E0': 0xE0, 'E1': 0xE1, 'E2': 0xE2, 'E3': 0xE3, 'E4': 0xE4, 'E5': 0xE5, 'E6': 0xE6, 'E7': 0xE7, 'E8': 0xE8, 'E9': 0xE9, 'EA': 0xEA, 'EB': 0xEB, 'EC': 0xEC, 'ED': 0xED, 'EE': 0xEE, 'EF': 0xEF, 46 | 'F0': 0xF0, 'F1': 0xF1, 'F2': 0xF2, 'F3': 0xF3, 'F4': 0xF4, 'F5': 0xF5, 'F6': 0xF6, 'F7': 0xF7, 'F8': 0xF8, 'F9': 0xF9, 'FA': 0xFA, 'FB': 0xFB, 'FC': 0xFC, 'FD': 0xFD, 'FE': 0xFE, 'FF': 0xFF, 47 | } 48 | 49 | // prettier-ignore 50 | type MidiDataIntegerMap = { 51 | '00': 0x00, '01': 0x01, '02': 0x02, '03': 0x03, '04': 0x04, '05': 0x05, '06': 0x06, '07': 0x07, '08': 0x08, '09': 0x09, '0A': 0x0A, '0B': 0x0B, '0C': 0x0C, '0D': 0x0D, '0E': 0x0E, '0F': 0x0F, 52 | '10': 0x10, '11': 0x11, '12': 0x12, '13': 0x13, '14': 0x14, '15': 0x15, '16': 0x16, '17': 0x17, '18': 0x18, '19': 0x19, '1A': 0x1A, '1B': 0x1B, '1C': 0x1C, '1D': 0x1D, '1E': 0x1E, '1F': 0x1F, 53 | '20': 0x20, '21': 0x21, '22': 0x22, '23': 0x23, '24': 0x24, '25': 0x25, '26': 0x26, '27': 0x27, '28': 0x28, '29': 0x29, '2A': 0x2A, '2B': 0x2B, '2C': 0x2C, '2D': 0x2D, '2E': 0x2E, '2F': 0x2F, 54 | '30': 0x30, '31': 0x31, '32': 0x32, '33': 0x33, '34': 0x34, '35': 0x35, '36': 0x36, '37': 0x37, '38': 0x38, '39': 0x39, '3A': 0x3A, '3B': 0x3B, '3C': 0x3C, '3D': 0x3D, '3E': 0x3E, '3F': 0x3F, 55 | '40': 0x40, '41': 0x41, '42': 0x42, '43': 0x43, '44': 0x44, '45': 0x45, '46': 0x46, '47': 0x47, '48': 0x48, '49': 0x49, '4A': 0x4A, '4B': 0x4B, '4C': 0x4C, '4D': 0x4D, '4E': 0x4E, '4F': 0x4F, 56 | '50': 0x50, '51': 0x51, '52': 0x52, '53': 0x53, '54': 0x54, '55': 0x55, '56': 0x56, '57': 0x57, '58': 0x58, '59': 0x59, '5A': 0x5A, '5B': 0x5B, '5C': 0x5C, '5D': 0x5D, '5E': 0x5E, '5F': 0x5F, 57 | '60': 0x60, '61': 0x61, '62': 0x62, '63': 0x63, '64': 0x64, '65': 0x65, '66': 0x66, '67': 0x67, '68': 0x68, '69': 0x69, '6A': 0x6A, '6B': 0x6B, '6C': 0x6C, '6D': 0x6D, '6E': 0x6E, '6F': 0x6F, 58 | '70': 0x70, '71': 0x71, '72': 0x72, '73': 0x73, '74': 0x74, '75': 0x75, '76': 0x76, '77': 0x77, '78': 0x78, '79': 0x79, '7A': 0x7A, '7B': 0x7B, '7C': 0x7C, '7D': 0x7D, '7E': 0x7E, '7F': 0x7F, 59 | } 60 | 61 | export type StatusHexPatternByte = `${keyof HexToIntegerStatusMSNMap | '?'}${ 62 | | keyof HexToIntegerLSNMap 63 | | '?'}`; 64 | export type DataHexPatternByte = `${keyof HexToIntegerDataMSNMap | '?'}${ 65 | | keyof HexToIntegerLSNMap 66 | | '?'}`; 67 | 68 | type MidiHexToObjectPatternByte< 69 | B extends StatusHexPatternByte | DataHexPatternByte 70 | > = B extends `${infer MSN}${infer LSN}` 71 | ? B extends keyof MidiStatusIntegerMap 72 | ? MidiStatusIntegerMap[B] 73 | : B extends keyof MidiDataIntegerMap 74 | ? MidiDataIntegerMap[B] 75 | : MSN extends keyof HexToIntegerStatusMSNMap 76 | ? LSN extends keyof HexToIntegerLSNMap 77 | ? never 78 | : { msn: HexToIntegerStatusMSNMap[MSN] } 79 | : MSN extends keyof HexToIntegerDataMSNMap 80 | ? LSN extends keyof HexToIntegerLSNMap 81 | ? never 82 | : { msn: HexToIntegerDataMSNMap[MSN] } 83 | : LSN extends keyof HexToIntegerLSNMap 84 | ? { lsn: HexToIntegerLSNMap[LSN] } 85 | : never 86 | : never; 87 | 88 | type MidiHexBytesToObjectPattern< 89 | S extends StatusHexPatternByte, 90 | D1 extends DataHexPatternByte, 91 | D2 extends DataHexPatternByte 92 | > = MidiHexToObjectPatternByte extends undefined 93 | ? MidiHexToObjectPatternByte extends undefined 94 | ? MidiHexToObjectPatternByte extends undefined 95 | ? {} 96 | : { data2: MidiHexToObjectPatternByte } 97 | : MidiHexToObjectPatternByte extends undefined 98 | ? { data1: MidiHexToObjectPatternByte } 99 | : { 100 | data1: MidiHexToObjectPatternByte; 101 | data2: MidiHexToObjectPatternByte; 102 | } 103 | : MidiHexToObjectPatternByte extends undefined 104 | ? MidiHexToObjectPatternByte extends undefined 105 | ? { status: MidiHexToObjectPatternByte } 106 | : { 107 | status: MidiHexToObjectPatternByte; 108 | data2: MidiHexToObjectPatternByte; 109 | } 110 | : MidiHexToObjectPatternByte extends undefined 111 | ? { 112 | status: MidiHexToObjectPatternByte; 113 | data1: MidiHexToObjectPatternByte; 114 | } 115 | : { 116 | status: MidiHexToObjectPatternByte; 117 | data1: MidiHexToObjectPatternByte; 118 | data2: MidiHexToObjectPatternByte; 119 | }; 120 | 121 | export type MidiHexPattern = string; 122 | 123 | type HexPatternToObjectPattern = 124 | T extends `${infer P0}${infer P1}${infer P2}${infer P3}${infer P4}${infer P5}${infer REST}` 125 | ? `${P0}${P1}` extends StatusHexPatternByte 126 | ? `${P2}${P3}` extends DataHexPatternByte 127 | ? `${P4}${P5}` extends DataHexPatternByte 128 | ? '' extends REST // should be only 6 character long 129 | ? MidiHexBytesToObjectPattern< 130 | `${P0}${P1}`, 131 | `${P2}${P3}`, 132 | `${P4}${P5}` 133 | > 134 | : {} 135 | : {} 136 | : {} 137 | : {} 138 | : {}; 139 | 140 | type MidiPattern = 141 | T extends MidiHexPattern ? HexPatternToObjectPattern : T; 142 | 143 | type FilterFlags = { 144 | [Key in keyof Base]: Base[Key] extends Condition ? Key : never; 145 | }; 146 | type AllowedNames = FilterFlags[keyof Base]; 147 | type SubType = Omit>; 148 | 149 | type Value = SubType< 150 | { 151 | [K in keyof Required]: T[K] extends MidiObjectPatternMSN 152 | ? MidiObjectPatternLSN 153 | : T[K] extends MidiObjectPatternLSN 154 | ? MidiObjectPatternMSN 155 | : T[K] extends number 156 | ? never 157 | : Extract extends never 158 | ? number 159 | : MidiObjectPatternByte; 160 | }, 161 | never 162 | >; 163 | 164 | type Input = { 165 | [K in keyof Required]: T[K] extends MidiObjectPatternMSN 166 | ? number 167 | : T[K] extends MidiObjectPatternLSN 168 | ? number 169 | : T[K] extends number 170 | ? T[K] 171 | : number; 172 | }; 173 | 174 | export type MidiProps = { 175 | label?: string; 176 | port?: number; 177 | pattern?: MidiHexPattern | MidiObjectPattern; 178 | value?: MidiObjectPattern; 179 | unmountValue?: MidiObjectPattern; 180 | onInput?: (message: MidiObjectPattern) => void; 181 | onChange?: (message: MidiObjectPattern) => void; 182 | cacheOnInput?: boolean; 183 | cacheOnOutput?: boolean; 184 | urgent?: boolean; 185 | }; 186 | 187 | export type InternalMidiProps = { 188 | sysex: false; 189 | label?: string; 190 | port?: number; 191 | pattern?: MidiHexPattern | MidiObjectPattern; 192 | defaultValue?: MidiObjectPattern; 193 | value?: MidiObjectPattern; 194 | unmountValue?: MidiObjectPattern; 195 | onInput?: (message: MidiObjectPattern) => void; 196 | onChange?: (message: MidiObjectPattern) => void; 197 | cacheOnInput?: boolean; 198 | cacheOnOutput?: boolean; 199 | urgent?: boolean; 200 | }; 201 | 202 | export type GenericMidiProps< 203 | T extends MidiObjectPattern | MidiHexPattern = {} 204 | > = { 205 | label?: string; 206 | port?: number; 207 | pattern?: T; 208 | defaultValue?: { 209 | [K in keyof Value>]: Value>[K]; 210 | }; 211 | value?: { [K in keyof Value>]: Value>[K] }; 212 | unmountValue?: { 213 | [K in keyof Value>]: Value>[K]; 214 | }; 215 | onInput?: (message: { 216 | [K in keyof Input>]: Input>[K]; 217 | }) => void; 218 | onChange?: (message: { 219 | [K in keyof Input>]: Input>[K]; 220 | }) => void; 221 | cacheOnInput?: boolean; 222 | cacheOnOutput?: boolean; 223 | urgent?: boolean; 224 | }; 225 | 226 | export const Midi = ( 227 | props: GenericMidiProps 228 | ) => React.createElement('midi', { sysex: false, ...props }); 229 | 230 | type MidiExtension

= Omit< 231 | MidiProps, 232 | 'pattern' | 'value' | 'unmountValue' | 'onInput' | 'onChange' 233 | > & { 234 | channel: number; 235 | value?: number; 236 | unmountValue?: number; 237 | onInput?: (value: number) => void; 238 | onChange?: (value: number) => void; 239 | } & P; 240 | 241 | Midi.NoteOn = ({ 242 | channel, 243 | note, 244 | value, 245 | unmountValue, 246 | onInput, 247 | onChange, 248 | ...rest 249 | }: MidiExtension<{ note: number }>) => ( 250 | onInput(data2) : undefined} 257 | onChange={onChange ? ({ data2 }) => onChange(data2) : undefined} 258 | {...rest} 259 | /> 260 | ); 261 | 262 | Midi.NoteOff = ({ 263 | channel, 264 | note, 265 | value, 266 | unmountValue, 267 | onInput, 268 | onChange, 269 | ...rest 270 | }: MidiExtension<{ note: number }>) => ( 271 | onInput(data2) : undefined} 278 | onChange={onChange ? ({ data2 }) => onChange(data2) : undefined} 279 | {...rest} 280 | /> 281 | ); 282 | 283 | Midi.KeyPressure = ({ 284 | channel, 285 | note, 286 | value, 287 | unmountValue, 288 | onInput, 289 | onChange, 290 | ...rest 291 | }: MidiExtension<{ note: number }>) => ( 292 | onInput(data2) : undefined} 299 | onChange={onChange ? ({ data2 }) => onChange(data2) : undefined} 300 | {...rest} 301 | /> 302 | ); 303 | 304 | Midi.CC = ({ 305 | channel, 306 | control, 307 | value, 308 | unmountValue, 309 | onInput, 310 | onChange, 311 | ...rest 312 | }: MidiExtension<{ control: number }>) => ( 313 | onInput(data2) : undefined} 320 | onChange={onChange ? ({ data2 }) => onChange(data2) : undefined} 321 | {...rest} 322 | /> 323 | ); 324 | 325 | Midi.ProgramChange = ({ 326 | channel, 327 | program, 328 | value, 329 | unmountValue, 330 | onInput, 331 | onChange, 332 | ...rest 333 | }: MidiExtension<{ program: number }>) => ( 334 | onInput(data1) : undefined} 341 | onChange={onChange ? ({ data1 }) => onChange(data1) : undefined} 342 | {...rest} 343 | /> 344 | ); 345 | 346 | Midi.ChannelPressure = ({ 347 | channel, 348 | value, 349 | unmountValue, 350 | onInput, 351 | onChange, 352 | ...rest 353 | }: MidiExtension) => ( 354 | onInput(data2) : undefined} 361 | onChange={onChange ? ({ data2 }) => onChange(data2) : undefined} 362 | {...rest} 363 | /> 364 | ); 365 | 366 | Midi.PitchBend = ({ 367 | channel, 368 | value, 369 | unmountValue, 370 | onInput, 371 | onChange, 372 | ...rest 373 | }: MidiExtension) => ( 374 | > 7) & 0x7f } 379 | : undefined 380 | } 381 | unmountValue={ 382 | value !== undefined 383 | ? { data1: value & 0x7f, data2: (value >> 7) & 0x7f } 384 | : undefined 385 | } 386 | onInput={ 387 | onInput ? ({ data1, data2 }) => onInput(128 * data2 + data1) : undefined 388 | } 389 | onChange={ 390 | onChange ? ({ data1, data2 }) => onChange(128 * data2 + data1) : undefined 391 | } 392 | {...rest} 393 | /> 394 | ); 395 | 396 | export type SysexProps = { 397 | label?: string; 398 | port?: number; 399 | pattern?: string; 400 | defaultValue?: string; 401 | value?: string; 402 | unmountValue?: string; 403 | onInput?: (data: string) => void; 404 | onChange?: (data: string) => void; 405 | cacheOnInput?: boolean; 406 | cacheOnOutput?: boolean; 407 | urgent?: boolean; 408 | }; 409 | 410 | Midi.Sysex = (props: SysexProps) => 411 | React.createElement('midi', { sysex: true, ...props }); 412 | -------------------------------------------------------------------------------- /src/controls/base.ts: -------------------------------------------------------------------------------- 1 | import { Session } from '../session'; 2 | 3 | export abstract class ControlInstanceBase

{ 4 | props: P; 5 | 6 | constructor(initialProps: P) { 7 | this.props = initialProps; 8 | } 9 | 10 | abstract commitUpdate(updatePayload: U, oldProps: P, newProps: P): void; 11 | } 12 | 13 | export abstract class ControlConnectorBase< 14 | T extends ControlInstanceBase = ControlInstanceBase 15 | > { 16 | session: Session; 17 | instances: T[] = []; 18 | 19 | constructor(session: Session) { 20 | this.session = session; 21 | } 22 | 23 | abstract addInstance(instance: T): void; 24 | abstract removeInstance(instance: T): void; 25 | } 26 | -------------------------------------------------------------------------------- /src/controls/midi/index.ts: -------------------------------------------------------------------------------- 1 | export * from './midi-message'; 2 | export * from './sysex-message'; 3 | export * from './midi-out-proxy'; 4 | export * from './midi-node'; 5 | -------------------------------------------------------------------------------- /src/controls/midi/midi-connector.ts: -------------------------------------------------------------------------------- 1 | import { Session } from '../../session'; 2 | import { ControlConnectorBase } from '../base'; 3 | import { deepEqual } from './utils'; 4 | import { MidiInstance } from './midi-instance'; 5 | import { MidiMessage } from './midi-message'; 6 | import { MidiNode } from './midi-node'; 7 | import { MidiOutProxy } from './midi-out-proxy'; 8 | import { SysexMessage } from './sysex-message'; 9 | import { MidiObjectPatternByte } from '../../components/midi'; 10 | 11 | export type MidiInstanceUpdatePayload = null | { 12 | midiNodeToConnect: MidiNode | null; 13 | midiOutput: 14 | | { 15 | label?: string; 16 | status: number; 17 | data1: number; 18 | data2: number; 19 | } 20 | | { 21 | label?: string; 22 | data: string; 23 | } 24 | | null; 25 | inputHandlerChanged: boolean; 26 | }; 27 | 28 | const getMidiMessageByteFromPatternAndValueBytes = ( 29 | patternByte?: MidiObjectPatternByte, 30 | valueByte?: MidiObjectPatternByte 31 | ): number => { 32 | if (typeof patternByte === 'number') { 33 | return patternByte; 34 | } else if (typeof valueByte === 'number') { 35 | return valueByte; 36 | } else if ( 37 | patternByte && 38 | valueByte && 39 | 'msn' in patternByte && 40 | 'lsn' in valueByte 41 | ) { 42 | return patternByte.msn * 16 + valueByte.lsn; 43 | } else if ( 44 | patternByte && 45 | valueByte && 46 | 'msn' in valueByte && 47 | 'lsn' in patternByte 48 | ) { 49 | return valueByte.msn * 16 + patternByte.lsn; 50 | } else { 51 | throw new Error( 52 | 'Something went wrong here. This error should never be reached because Midi match patterns and values should be complimentary and produce a full midi message when both exist' 53 | ); 54 | } 55 | }; 56 | 57 | const getMidiOutput = ({ 58 | midiNode, 59 | value, 60 | label, 61 | }: { 62 | midiNode?: MidiNode; 63 | value?: MidiInstance['props']['value']; 64 | label?: string; 65 | }) => { 66 | let result: NonNullable['midiOutput'] = null; 67 | if (value) { 68 | if (midiNode) { 69 | let output: 70 | | { 71 | label?: string; 72 | port?: number | undefined; 73 | status: number; 74 | data1: number; 75 | data2: number; 76 | } 77 | | { 78 | label?: string; 79 | port?: number | undefined; 80 | data: string; 81 | }; 82 | 83 | if (typeof value === 'string') { 84 | output = { 85 | label, 86 | port: midiNode.port, 87 | data: value, 88 | }; 89 | } else { 90 | output = { 91 | label, 92 | port: midiNode.port, 93 | status: getMidiMessageByteFromPatternAndValueBytes( 94 | midiNode.pattern?.status, 95 | value.status 96 | ), 97 | data1: getMidiMessageByteFromPatternAndValueBytes( 98 | midiNode.pattern?.data1, 99 | value.data1 100 | ), 101 | data2: getMidiMessageByteFromPatternAndValueBytes( 102 | midiNode.pattern?.data2, 103 | value.data2 104 | ), 105 | }; 106 | } 107 | 108 | if (midiNode.shouldOutputMessage(output)) { 109 | result = output; 110 | } 111 | } 112 | } 113 | return result; 114 | }; 115 | 116 | export class MidiConnector extends ControlConnectorBase { 117 | activeMidiNodes = new Set(); 118 | midiNodeMap = new Map(); 119 | 120 | midiOut: MidiOutProxy; 121 | 122 | constructor(session: Session) { 123 | super(session); 124 | // midi output 125 | this.midiOut = new MidiOutProxy(session); 126 | // midi input 127 | session.on('init', () => { 128 | const midiInPorts = this.midiInPorts; 129 | for (let port = 0; port < midiInPorts.length; port += 1) { 130 | midiInPorts[port].setMidiCallback( 131 | (status: number, data1: number, data2: number) => { 132 | this.handleMidiInput( 133 | new MidiMessage({ port, status, data1, data2 }) 134 | ); 135 | } 136 | ); 137 | midiInPorts[port].setSysexCallback((data: string) => { 138 | this.handleMidiInput(new SysexMessage({ port, data })); 139 | }); 140 | } 141 | }); 142 | // cleanup on exit 143 | session.on('exit', () => this.clearInstances()); 144 | } 145 | 146 | private disconnectInstanceFromNode( 147 | instance: MidiInstance, 148 | synchronous?: boolean 149 | ) { 150 | const midiNode = this.midiNodeMap.get(instance); 151 | if (midiNode) { 152 | // remove from midiNodes and midiNodeMap 153 | midiNode.instances.splice(midiNode.instances.indexOf(instance), 1); 154 | this.midiNodeMap.delete(instance); 155 | // send unmount value if no instances using node 156 | if (midiNode.instances.length === 0) { 157 | const unmountOutput = getMidiOutput({ 158 | label: instance.props.label, 159 | midiNode, 160 | value: instance.props.unmountValue, 161 | }); 162 | if (unmountOutput) { 163 | const sendUnmountOutput = () => { 164 | // for async, check if node instances are still 0 165 | if (midiNode.instances.length === 0) { 166 | if ('data' in unmountOutput) { 167 | this.midiOut.sendSysex(unmountOutput); 168 | } else { 169 | this.midiOut.sendMidi(unmountOutput); 170 | } 171 | midiNode.onIO('output', unmountOutput); 172 | } 173 | }; 174 | 175 | if (synchronous) { 176 | // synchronous unmount is use on exit, otherwise will exit before async is called 177 | sendUnmountOutput(); 178 | } else { 179 | // allows replacement instances (if any) to connect so we don't send unmount messages unnecessarily 180 | setTimeout(sendUnmountOutput, 0); 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | private connectInstanceToNode(instance: MidiInstance, midiNode: MidiNode) { 188 | const existingMidiNode = this.midiNodeMap.get(instance); 189 | // no change? early exit 190 | if (midiNode === existingMidiNode) return; 191 | // is change and has existing node? disconnect from existing node 192 | if (existingMidiNode) this.disconnectInstanceFromNode(instance); 193 | // update instance ref on node 194 | if (!midiNode.instances.includes(instance)) { 195 | midiNode.instances.push(instance); 196 | } 197 | // update midiNodeMap 198 | this.midiNodeMap.set(instance, midiNode); 199 | // update midiNodes list 200 | if (!this.activeMidiNodes.has(midiNode)) { 201 | this.activeMidiNodes.add(midiNode); 202 | } 203 | } 204 | 205 | addInstance(instance: MidiInstance): void { 206 | if (!this.instances.includes(instance)) { 207 | this.instances.push(instance); 208 | instance.connector = this; 209 | 210 | this.commitInstanceUpdate( 211 | instance, 212 | this.prepareInstanceUpdate( 213 | instance, 214 | instance.props, 215 | instance.props, 216 | true 217 | ), 218 | instance.props, 219 | instance.props 220 | ); 221 | } 222 | } 223 | 224 | prepareInstanceUpdate( 225 | instance: MidiInstance, 226 | oldProps: MidiInstance['props'], 227 | newProps: MidiInstance['props'], 228 | isMount?: boolean 229 | ): MidiInstanceUpdatePayload { 230 | let isMidiNodeNew = false; 231 | let midiNodeToConnect: MidiNode | null = null; 232 | // handle modified pattern 233 | if ( 234 | isMount || 235 | newProps.port !== oldProps.port || 236 | // TODO: look into more perf friendly way to solve this in the midi node 237 | (newProps.pattern === undefined && 238 | !deepEqual(newProps.value, oldProps.value)) || 239 | !deepEqual(newProps.pattern, oldProps.pattern) 240 | ) { 241 | // create new node 242 | let midiNodeForNewProps = new MidiNode(newProps); 243 | const currentMidiNode = this.midiNodeMap.get(instance); 244 | if ( 245 | !currentMidiNode || 246 | midiNodeForNewProps.string !== currentMidiNode.string || 247 | midiNodeForNewProps.cacheOnInput !== currentMidiNode.cacheOnInput || 248 | midiNodeForNewProps.cacheOnOutput !== currentMidiNode.cacheOnOutput 249 | // TODO: check unmountValue is consistent here as well (needs to be added to MidiNode) 250 | ) { 251 | // check for existing conflicting node 252 | for (const existingMidiNode of this.activeMidiNodes) { 253 | if (midiNodeForNewProps.conflictsWith(existingMidiNode)) { 254 | if ( 255 | midiNodeForNewProps.string === existingMidiNode.string && 256 | midiNodeForNewProps.cacheOnInput === 257 | existingMidiNode.cacheOnInput && 258 | midiNodeForNewProps.cacheOnOutput === 259 | existingMidiNode.cacheOnOutput 260 | ) { 261 | midiNodeToConnect = existingMidiNode; 262 | break; 263 | } else { 264 | throw new Error( 265 | `MidiNode conflicts with existing MidiNode ({ existing: ${existingMidiNode.string}, new: ${midiNodeForNewProps.string} })` 266 | ); 267 | } 268 | } 269 | } 270 | 271 | if (!midiNodeToConnect) { 272 | // set new midi node for update and add to prepared list (to check if other components conflict in the same batch) 273 | midiNodeToConnect = midiNodeForNewProps; 274 | this.activeMidiNodes.add(midiNodeToConnect); 275 | isMidiNodeNew = true; 276 | } 277 | } 278 | } 279 | 280 | // handle modified value 281 | const midiOutput = getMidiOutput({ 282 | label: newProps.label, 283 | midiNode: midiNodeToConnect || this.midiNodeMap.get(instance), 284 | value: 285 | isMount && isMidiNodeNew 286 | ? newProps.value || newProps.defaultValue // send default value if any on initial mount 287 | : newProps.value, 288 | }); 289 | 290 | // handle modified onInput handler 291 | let inputHandlerChanged = false; 292 | if (isMount || newProps.onInput !== oldProps.onInput) { 293 | inputHandlerChanged = true; 294 | } 295 | 296 | return midiNodeToConnect === null && 297 | midiOutput === null && 298 | !inputHandlerChanged 299 | ? null 300 | : { 301 | midiNodeToConnect: midiNodeToConnect, 302 | midiOutput, 303 | inputHandlerChanged, 304 | }; 305 | } 306 | 307 | commitInstanceUpdate( 308 | instance: MidiInstance, 309 | updatePayload: MidiInstanceUpdatePayload, 310 | oldProps: MidiInstance['props'], 311 | newProps: MidiInstance['props'] 312 | ): void { 313 | if (updatePayload === null) return; 314 | const { midiNodeToConnect, midiOutput } = updatePayload; 315 | 316 | // handle modified pattern 317 | if (midiNodeToConnect !== null) { 318 | this.connectInstanceToNode(instance, midiNodeToConnect); 319 | } 320 | 321 | // handle modified value 322 | if (midiOutput) { 323 | const midiNode = this.midiNodeMap.get(instance)!; 324 | if ('data' in midiOutput) { 325 | this.midiOut.sendSysex(midiOutput); 326 | } else { 327 | this.midiOut.sendMidi(midiOutput); 328 | } 329 | midiNode.onIO('output', midiOutput); 330 | 331 | // output here means the midi value changed and onChange should be called 332 | midiNode.instances.forEach((instance) => 333 | instance.props.onChange?.( 334 | 'data' in midiOutput ? midiOutput.data : midiOutput 335 | ) 336 | ); 337 | } 338 | 339 | instance.props = newProps; 340 | } 341 | 342 | removeInstance(instance: MidiInstance, synchronous?: boolean): void { 343 | this.disconnectInstanceFromNode(instance, synchronous); 344 | } 345 | 346 | clearInstances() { 347 | this.instances.forEach((instance) => this.removeInstance(instance, true)); 348 | host.requestFlush(); 349 | } 350 | 351 | /** The midi in ports available to the session */ 352 | get midiInPorts(): API.MidiIn[] { 353 | const midiInPorts = []; 354 | for (let i = 0; true; i += 1) { 355 | try { 356 | midiInPorts[i] = host.getMidiInPort(i); 357 | } catch (error) { 358 | break; 359 | } 360 | } 361 | return midiInPorts; 362 | } 363 | 364 | /** Handle midi input, routing it to the correct control object */ 365 | handleMidiInput(message: MidiMessage | SysexMessage) { 366 | const messageType = message instanceof MidiMessage ? '[MIDI] ' : '[SYSEX]'; 367 | 368 | let node: MidiNode | undefined; 369 | for (const n of this.activeMidiNodes) { 370 | if (n.test(message)) { 371 | node = n; 372 | node.onIO('input', message); 373 | break; 374 | } 375 | } 376 | 377 | // create local copy of instance list in case it changes when running onInput 378 | const instances = [...(node?.instances || [])]; 379 | const labels: string[] = []; 380 | instances.forEach((instance) => { 381 | // check that instance is still in instance list before calling (could be unmounted) 382 | if (node?.instances.includes(instance)) { 383 | instance.props.onInput && 384 | instance.props.onInput( 385 | message instanceof SysexMessage ? message.data : message 386 | ); 387 | instance.props.label && labels.push(instance.props.label); 388 | } 389 | }); 390 | 391 | if (message instanceof SysexMessage) { 392 | console.log( 393 | `${messageType} IN ${message.port} ==> ${ 394 | labels[0] ? `"${labels.join(',')}" ` : '' 395 | }${message.data}` 396 | ); 397 | } else { 398 | console.log( 399 | `${messageType} IN ${message.port} ==> ${message.shortHex}${ 400 | labels[0] ? ` "${labels.join(',')}"` : '' 401 | }` 402 | ); 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/controls/midi/midi-instance.ts: -------------------------------------------------------------------------------- 1 | import { MidiHexPattern, MidiObjectPattern } from '../../components/midi'; 2 | import { ControlInstanceBase } from '../base'; 3 | import { MidiInstanceUpdatePayload, MidiConnector } from './midi-connector'; 4 | 5 | export type MidiInstanceProps = ( 6 | | { 7 | sysex: true; 8 | label?: string; 9 | port?: number; 10 | pattern?: string; 11 | defaultValue?: string; 12 | value?: string; 13 | unmountValue?: string; 14 | } 15 | | { 16 | sysex: false; 17 | label?: string; 18 | port?: number; 19 | pattern?: MidiHexPattern | MidiObjectPattern; 20 | defaultValue?: MidiObjectPattern; 21 | value?: MidiObjectPattern; 22 | unmountValue?: MidiObjectPattern; 23 | } 24 | ) & { 25 | onInput?: (message: MidiObjectPattern | string) => void; 26 | onChange?: (message: MidiObjectPattern | string) => void; 27 | cacheOnInput?: boolean; 28 | cacheOnOutput?: boolean; 29 | urgent?: boolean; 30 | }; 31 | 32 | export class MidiInstance extends ControlInstanceBase< 33 | MidiInstanceProps, 34 | MidiInstanceUpdatePayload 35 | > { 36 | // connector is null until control has been added to the session 37 | connector: MidiConnector | null = null; 38 | 39 | commitUpdate( 40 | updatePayload: MidiInstanceUpdatePayload, 41 | oldProps: MidiInstanceProps, 42 | newProps: MidiInstanceProps 43 | ) { 44 | this.connector?.commitInstanceUpdate( 45 | this, 46 | updatePayload, 47 | oldProps, 48 | newProps 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/controls/midi/midi-message.ts: -------------------------------------------------------------------------------- 1 | export interface SimpleMidiMessage { 2 | port: number; 3 | status: number; 4 | data1: number; 5 | data2: number; 6 | } 7 | 8 | export interface MidiMessageConstructor { 9 | port?: number; 10 | status: number; 11 | data1: number; 12 | data2: number; 13 | urgent?: boolean; 14 | } 15 | 16 | export class MidiMessage implements SimpleMidiMessage { 17 | port: number; 18 | status: number; 19 | data1: number; 20 | data2: number; 21 | urgent: boolean; 22 | hex: string; 23 | 24 | constructor({ 25 | port = 0, 26 | status, 27 | data1, 28 | data2, 29 | urgent = false, 30 | }: MidiMessageConstructor) { 31 | this.port = port; 32 | this.status = status; 33 | this.data1 = data1; 34 | this.data2 = data2; 35 | this.urgent = urgent; 36 | this.hex = [port, status, data1, data2] 37 | .map((midiByte) => { 38 | let hexByteString = midiByte.toString(16).toUpperCase(); 39 | if (hexByteString.length === 1) hexByteString = `0${hexByteString}`; 40 | return hexByteString; 41 | }) 42 | .join(''); 43 | } 44 | 45 | get shortHex() { 46 | return this.hex.slice(2); 47 | } 48 | 49 | get channel() { 50 | return this.status & 0xf; 51 | } 52 | 53 | get pitchBendValue() { 54 | return (this.data2 << 7) | this.data1; 55 | } 56 | 57 | get isNote() { 58 | return (this.status & 0xf0) === 0x80 || (this.status & 0xf0) === 0x90; 59 | } 60 | 61 | get isNoteOff() { 62 | return ( 63 | (this.status & 0xf0) === 0x80 || 64 | ((this.status & 0xf0) === 0x90 && this.data2 === 0) 65 | ); 66 | } 67 | 68 | get isNoteOn() { 69 | return (this.status & 0xf0) === 0x90; 70 | } 71 | 72 | get isKeyPressure() { 73 | return (this.status & 0xf0) === 0xa0; 74 | } 75 | 76 | get isControlChange() { 77 | return (this.status & 0xf0) === 0xb0; 78 | } 79 | 80 | get isProgramChange() { 81 | return (this.status & 0xf0) === 0xc0; 82 | } 83 | 84 | get isChannelPressure() { 85 | return (this.status & 0xf0) === 0xd0; 86 | } 87 | 88 | get isPitchBend() { 89 | return (this.status & 0xf0) === 0xe0; 90 | } 91 | 92 | get isMTCQuarterFrame() { 93 | return this.status === 0xf1; 94 | } 95 | 96 | get isSongPositionPointer() { 97 | return this.status === 0xf2; 98 | } 99 | 100 | get isSongSelect() { 101 | return this.status === 0xf3; 102 | } 103 | 104 | get isTuneRequest() { 105 | return this.status === 0xf6; 106 | } 107 | 108 | get isTimingClock() { 109 | return this.status === 0xf8; 110 | } 111 | 112 | get isMIDIStart() { 113 | return this.status === 0xfa; 114 | } 115 | 116 | get isMIDIContinue() { 117 | return this.status === 0xfb; 118 | } 119 | 120 | get isMidiStop() { 121 | return this.status === 0xfc; 122 | } 123 | 124 | get isActiveSensing() { 125 | return this.status === 0xfe; 126 | } 127 | 128 | get isSystemReset() { 129 | return this.status === 0xff; 130 | } 131 | 132 | toString() { 133 | return this.hex; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/controls/midi/midi-node.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DataHexPatternByte, 3 | MidiObjectPattern, 4 | MidiObjectPatternByte, 5 | StatusHexPatternByte, 6 | } from '../../components/midi'; 7 | import { MidiInstance, MidiInstanceProps } from './midi-instance'; 8 | import { deepEqual } from './utils'; 9 | 10 | function isStatusHexPatternByte(byte: string): byte is StatusHexPatternByte { 11 | return /^[8-9A-F?][0-9A-F?]$/.test(byte); 12 | } 13 | 14 | function isDataHexPatternByte(byte: string): byte is DataHexPatternByte { 15 | return /^[0-7?][0-9A-F?]$/.test(byte); 16 | } 17 | 18 | function hexToObjectPatternByte( 19 | byte: StatusHexPatternByte | DataHexPatternByte 20 | ): MidiObjectPatternByte | undefined { 21 | if (/^[0-9A-F]{2,}$/.test(byte)) { 22 | return parseInt(byte, 16); 23 | } else if (/^[0-9A-F]\?$/.test(byte)) { 24 | return { msn: parseInt(byte[0], 16) }; 25 | } else if (/^\?[0-9A-F]$/.test(byte)) { 26 | return { lsn: parseInt(byte[1], 16) }; 27 | } else if (/^\?\?$/.test(byte)) { 28 | return undefined; 29 | } else { 30 | throw new Error(`Invalid hex pattern byte: "${byte}"`); 31 | } 32 | } 33 | 34 | function hexToObjectPattern(hexPattern: string): MidiObjectPattern { 35 | if (hexPattern.length !== 6) { 36 | throw new Error(`Invalid hex pattern length (> 6): "${hexPattern}"`); 37 | } 38 | 39 | const result: MidiObjectPattern = {}; 40 | 41 | const statusByte = hexPattern.slice(0, 2); 42 | const data1Byte = hexPattern.slice(2, 4); 43 | const data2Byte = hexPattern.slice(4, 6); 44 | 45 | if ( 46 | !isStatusHexPatternByte(statusByte) || 47 | !isDataHexPatternByte(data1Byte) || 48 | !isDataHexPatternByte(data2Byte) 49 | ) { 50 | throw new Error(`Invalid hex pattern: "${hexPattern}"`); 51 | } 52 | 53 | const status = hexToObjectPatternByte(statusByte); 54 | if (status) result.status = status; 55 | 56 | const data1 = hexToObjectPatternByte(data1Byte); 57 | if (data1) result.data1 = data1; 58 | 59 | const data2 = hexToObjectPatternByte(data2Byte); 60 | if (data2) result.data2 = data2; 61 | 62 | return result; 63 | } 64 | 65 | const getPatternStringFromMidiMessage = ( 66 | message: { port?: number } & (MidiObjectPattern | { data: string }) 67 | ) => { 68 | if ('data' in message) { 69 | const { port = 0, data } = message; 70 | return `${port.toString(16).toUpperCase()}${data}`; 71 | } else { 72 | const { port = 0, status, data1, data2 } = message; 73 | return [port, status, data1, data2] 74 | .map((byte) => { 75 | if (byte === undefined) { 76 | return '??'; 77 | } else if (typeof byte === 'number') { 78 | let hexByteString = byte.toString(16).toUpperCase(); 79 | if (hexByteString.length === 1) hexByteString = `0${hexByteString}`; 80 | return hexByteString; 81 | } else { 82 | return `${'msn' in byte ? byte.msn.toString(16) : '?'}${ 83 | 'lsn' in byte ? byte.lsn.toString(16) : '?' 84 | }`; 85 | } 86 | }) 87 | .join(''); 88 | } 89 | }; 90 | 91 | export class MidiNode { 92 | sysex: boolean; 93 | label?: string; 94 | port?: number; 95 | pattern?: MidiObjectPattern; 96 | 97 | cacheOnInput: boolean; 98 | cacheOnOutput: boolean; 99 | cachedValue?: 100 | | { 101 | port: number; 102 | status: number; 103 | data1: number; 104 | data2: number; 105 | } 106 | | { port: number; data: string }; 107 | 108 | string: string; 109 | regex: RegExp; 110 | 111 | instances: MidiInstance[] = []; 112 | 113 | constructor({ 114 | port = 0, 115 | sysex, 116 | pattern, 117 | value, 118 | cacheOnInput, 119 | cacheOnOutput, 120 | }: MidiInstanceProps) { 121 | this.sysex = sysex; 122 | this.port = port; 123 | this.cacheOnInput = !!cacheOnInput; 124 | this.cacheOnOutput = !!cacheOnOutput; 125 | 126 | if (sysex) { 127 | if (pattern) { 128 | if (typeof pattern !== 'string') { 129 | throw new Error('Invalid sysex MidiNode'); 130 | } 131 | this.string = getPatternStringFromMidiMessage({ port, data: pattern }); 132 | } else if (value) { 133 | this.string = getPatternStringFromMidiMessage({ port, data: value }); 134 | } else { 135 | throw new Error('MidiNode should have a pattern or a value.'); 136 | } 137 | } else if (pattern) { 138 | // convert string pattern to object pattern 139 | if (typeof pattern === 'string') { 140 | pattern = hexToObjectPattern(pattern); 141 | } 142 | 143 | this.pattern = pattern; 144 | 145 | this.string = getPatternStringFromMidiMessage({ 146 | port, 147 | ...pattern, 148 | }); 149 | } else if (value) { 150 | this.pattern = value; 151 | this.string = getPatternStringFromMidiMessage({ 152 | port, 153 | ...value, 154 | }); 155 | } else { 156 | throw new Error('MidiNode should have a pattern or a value.'); 157 | } 158 | 159 | this.regex = new RegExp(`^${this.string.slice().replace(/\?/g, '.')}$`); 160 | } 161 | 162 | toString() { 163 | return this.string; 164 | } 165 | 166 | conflictsWith({ string: stringB, regex: regexB }: MidiNode) { 167 | const { string: stringA, regex: regexA } = this; 168 | return regexA.test(stringB) || regexB.test(stringA); 169 | } 170 | 171 | test( 172 | message: 173 | | { 174 | port?: number; 175 | status: number; 176 | data1: number; 177 | data2: number; 178 | } 179 | | { 180 | port?: number; 181 | data: string; 182 | } 183 | ) { 184 | const testString = getPatternStringFromMidiMessage(message); 185 | 186 | return this.regex.test(testString); 187 | } 188 | 189 | handleCachedValueChange() { 190 | this.instances.forEach((instance) => { 191 | if (this.cachedValue && instance.props.onChange) { 192 | instance.props.onChange( 193 | 'data' in this.cachedValue ? this.cachedValue.data : this.cachedValue 194 | ); 195 | } 196 | }); 197 | } 198 | 199 | onIO( 200 | type: 'input' | 'output', 201 | { 202 | port = 0, 203 | ...rest 204 | }: 205 | | { 206 | port?: number; 207 | status: number; 208 | data1: number; 209 | data2: number; 210 | } 211 | | { port?: number; data: string } 212 | ) { 213 | let message: 214 | | { 215 | port: number; 216 | status: number; 217 | data1: number; 218 | data2: number; 219 | } 220 | | { port: number; data: string }; 221 | if ('data' in rest) { 222 | message = { 223 | port, 224 | data: rest.data, 225 | }; 226 | } else { 227 | message = { 228 | port, 229 | status: rest.status, 230 | data1: rest.data1, 231 | data2: rest.data2, 232 | }; 233 | } 234 | 235 | let shouldUpdateCache = false; 236 | if ( 237 | (type === 'input' && this.cacheOnInput) || 238 | (type === 'output' && this.cacheOnOutput) 239 | ) { 240 | shouldUpdateCache = !deepEqual(message, this.cachedValue); 241 | } 242 | 243 | if (shouldUpdateCache) { 244 | this.cachedValue = message; 245 | [...this.instances].forEach((instance) => { 246 | if ( 247 | this.cachedValue && 248 | instance.props.onChange && 249 | this.instances.includes(instance) 250 | ) { 251 | instance.props.onChange('data' in message ? message.data : message); 252 | } 253 | }); 254 | } 255 | } 256 | 257 | shouldOutputMessage({ 258 | port = 0, 259 | ...rest 260 | }: 261 | | { 262 | port?: number; 263 | status: number; 264 | data1: number; 265 | data2: number; 266 | } 267 | | { port?: number; data: string }) { 268 | let message: 269 | | { 270 | port: number; 271 | status: number; 272 | data1: number; 273 | data2: number; 274 | } 275 | | { port: number; data: string }; 276 | 277 | if ('data' in rest) { 278 | message = { 279 | port, 280 | data: rest.data, 281 | }; 282 | } else { 283 | message = { 284 | port, 285 | status: rest.status, 286 | data1: rest.data1, 287 | data2: rest.data2, 288 | }; 289 | } 290 | 291 | return !deepEqual(message, this.cachedValue); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/controls/midi/midi-out-proxy.ts: -------------------------------------------------------------------------------- 1 | import { MidiMessage, SimpleMidiMessage } from './midi-message'; 2 | import { Session } from '../../session'; 3 | 4 | export interface NaiveMidiMessage extends SimpleMidiMessage { 5 | label?: string; 6 | port: number; 7 | } 8 | 9 | export interface NaiveSysexMessage { 10 | label?: string; 11 | port: number; 12 | data: string; 13 | } 14 | 15 | export class MidiOutProxy { 16 | private session: Session; 17 | private _midiQueue: NaiveMidiMessage[] = []; 18 | private _sysexQueue: NaiveSysexMessage[] = []; 19 | 20 | constructor(session: Session) { 21 | this.session = session; 22 | session.on('flush', () => this._flushQueues()); 23 | } 24 | 25 | sendMidi({ 26 | label, 27 | port = 0, 28 | status, 29 | data1, 30 | data2, 31 | urgent = false, 32 | }: { 33 | label?: string; 34 | port?: number; 35 | status: number; 36 | data1: number; 37 | data2: number; 38 | urgent?: boolean; 39 | }) { 40 | // if urgent, fire midi message immediately, otherwise queue it up for next flush 41 | if (urgent || this.session.isExitPhase) { 42 | console.log( 43 | `[MIDI] OUT ${port} <== ${ 44 | new MidiMessage({ status, data1, data2 }).shortHex 45 | }${label ? ` "${label}"` : ''}` 46 | ); 47 | host.getMidiOutPort(port).sendMidi(status, data1, data2); 48 | } else { 49 | this._midiQueue.push({ label, port, status, data1, data2 }); 50 | } 51 | } 52 | 53 | sendSysex({ 54 | label, 55 | port = 0, 56 | data, 57 | urgent = false, 58 | }: { 59 | label?: string; 60 | port?: number; 61 | data: string; 62 | urgent?: boolean; 63 | }) { 64 | // if urgent, fire sysex immediately, otherwise queue it up for next flush 65 | if (urgent) { 66 | console.log( 67 | `[SYSEX] OUT ${port} <== ${data}${label ? ` "${label}"` : ''}` 68 | ); 69 | host.getMidiOutPort(port).sendSysex(data); 70 | } else { 71 | this._sysexQueue.push({ label, port, data }); 72 | } 73 | } 74 | 75 | sendNoteOn({ 76 | port = 0, 77 | channel, 78 | key, 79 | velocity, 80 | urgent = false, 81 | }: { 82 | port?: number; 83 | channel: number; 84 | key: number; 85 | velocity: number; 86 | urgent?: boolean; 87 | }) { 88 | this.sendMidi({ 89 | urgent, 90 | port, 91 | status: 0x90 | channel, 92 | data1: key, 93 | data2: velocity, 94 | }); 95 | } 96 | 97 | sendNoteOff({ 98 | port = 0, 99 | channel, 100 | key, 101 | velocity, 102 | urgent = false, 103 | }: { 104 | port?: number; 105 | channel: number; 106 | key: number; 107 | velocity: number; 108 | urgent?: boolean; 109 | }) { 110 | this.sendMidi({ 111 | urgent, 112 | port, 113 | status: 0x80 | channel, 114 | data1: key, 115 | data2: velocity, 116 | }); 117 | } 118 | 119 | sendKeyPressure({ 120 | port = 0, 121 | channel, 122 | key, 123 | pressure, 124 | urgent = false, 125 | }: { 126 | port?: number; 127 | channel: number; 128 | key: number; 129 | pressure: number; 130 | urgent?: boolean; 131 | }) { 132 | this.sendMidi({ 133 | urgent, 134 | port, 135 | status: 0xa0 | channel, 136 | data1: key, 137 | data2: pressure, 138 | }); 139 | } 140 | 141 | sendControlChange({ 142 | port = 0, 143 | channel, 144 | control, 145 | value, 146 | urgent = false, 147 | }: { 148 | port?: number; 149 | channel: number; 150 | control: number; 151 | value: number; 152 | urgent?: boolean; 153 | }) { 154 | this.sendMidi({ 155 | urgent, 156 | port, 157 | status: 0xb0 | channel, 158 | data1: control, 159 | data2: value, 160 | }); 161 | } 162 | 163 | sendProgramChange({ 164 | port = 0, 165 | channel, 166 | program, 167 | urgent = false, 168 | }: { 169 | port?: number; 170 | channel: number; 171 | program: number; 172 | urgent?: boolean; 173 | }) { 174 | this.sendMidi({ 175 | urgent, 176 | port, 177 | status: 0xc0 | channel, 178 | data1: program, 179 | data2: 0, 180 | }); 181 | } 182 | 183 | sendChannelPressure({ 184 | port = 0, 185 | channel, 186 | pressure, 187 | urgent = false, 188 | }: { 189 | port?: number; 190 | channel: number; 191 | pressure: number; 192 | urgent?: boolean; 193 | }) { 194 | this.sendMidi({ 195 | urgent, 196 | port, 197 | status: 0xd0 | channel, 198 | data1: pressure, 199 | data2: 0, 200 | }); 201 | } 202 | 203 | sendPitchBend({ 204 | port = 0, 205 | channel, 206 | value, 207 | urgent = false, 208 | }: { 209 | port?: number; 210 | channel: number; 211 | value: number; 212 | urgent?: boolean; 213 | }) { 214 | this.sendMidi({ 215 | urgent, 216 | port, 217 | status: 0xe0 | channel, 218 | data1: value & 0x7f, 219 | data2: (value >> 7) & 0x7f, 220 | }); 221 | } 222 | 223 | // flush queued midi and sysex messages 224 | protected _flushQueues() { 225 | while (this._midiQueue.length > 0 || this._sysexQueue.length > 0) { 226 | const midiMessage = this._midiQueue.shift() as NaiveMidiMessage; 227 | if (midiMessage) { 228 | const { label, port, status, data1, data2 } = midiMessage; 229 | console.log( 230 | `[MIDI] OUT ${port} <== ${ 231 | new MidiMessage({ status, data1, data2 }).shortHex 232 | }${label ? ` "${label}"` : ''}` 233 | ); 234 | host.getMidiOutPort(port).sendMidi(status, data1, data2); 235 | } 236 | 237 | const sysexMessage = this._sysexQueue.shift() as NaiveSysexMessage; 238 | if (sysexMessage) { 239 | const { label, port, data } = sysexMessage; 240 | console.log( 241 | `[SYSEX] OUT ${port} <== ${label ? `"${label}" ` : ''}${data}` 242 | ); 243 | host.getMidiOutPort(port).sendSysex(data); 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/controls/midi/note-input.ts: -------------------------------------------------------------------------------- 1 | const tables = { 2 | DEFAULT: Array.apply(null, Array(128)).map((_, i) => i), 3 | DISABLED: Array.apply(null, Array(128)).map(() => -1), 4 | }; 5 | 6 | export class NoteInputProxy { 7 | public noteInput: API.NoteInput; 8 | 9 | private _disabled = false; 10 | private _keyTranslationTable: (number | null)[] = tables.DEFAULT; 11 | private _shouldConsumeEvents: boolean = true; 12 | private _changeCallbacks: ((...args: any[]) => void)[] = []; 13 | 14 | constructor(noteInput: API.NoteInput) { 15 | this.noteInput = noteInput; 16 | } 17 | 18 | get keyTranslationTable() { 19 | return this._keyTranslationTable; 20 | } 21 | 22 | set keyTranslationTable(keyTranslationTable: (number | null)[]) { 23 | let isValid = keyTranslationTable.length === 128; 24 | const apiKeyTranslationTable = keyTranslationTable.map((note) => { 25 | // validate each slot each iteration 26 | if (!Number.isInteger(note) && note !== null) isValid = false; 27 | // filter out note values which are invalid to the API 28 | return note === null || note > 127 || note < 0 ? -1 : note; 29 | }); 30 | 31 | if (!isValid) { 32 | throw new Error( 33 | 'Invalid note table: must have a length of 128 and only contain null and integer values.' 34 | ); 35 | } 36 | 37 | this._keyTranslationTable = keyTranslationTable; 38 | if (!this._disabled) { 39 | this.noteInput.setKeyTranslationTable(apiKeyTranslationTable); 40 | // call the registered change callbacks 41 | this._changeCallbacks.forEach((callback) => callback()); 42 | } 43 | } 44 | 45 | get shouldConsumeEvents() { 46 | return this._shouldConsumeEvents; 47 | } 48 | 49 | set shouldConsumeEvents(shouldConsumeEvents: boolean) { 50 | this._shouldConsumeEvents = shouldConsumeEvents; 51 | this.noteInput.setShouldConsumeEvents(shouldConsumeEvents); 52 | } 53 | 54 | enable() { 55 | if (!this._disabled) return; 56 | this._disabled = false; 57 | this.keyTranslationTable = this.keyTranslationTable; 58 | } 59 | 60 | disable() { 61 | if (this._disabled) return; 62 | this._disabled = true; 63 | this.noteInput.setKeyTranslationTable(tables.DISABLED); 64 | // call the registered change callbacks 65 | this._changeCallbacks.forEach((callback) => callback()); 66 | } 67 | 68 | transpose(steps: number) { 69 | this.keyTranslationTable = this.keyTranslationTable.map( 70 | (note) => note && note + steps 71 | ); 72 | } 73 | 74 | onChange(callback: (...args: any[]) => void) { 75 | this._changeCallbacks.push(callback); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/controls/midi/sysex-message.ts: -------------------------------------------------------------------------------- 1 | export class SysexMessage { 2 | port: number; 3 | data: string; 4 | urgent: boolean; 5 | 6 | constructor({ 7 | port = 0, 8 | data, 9 | urgent = false, 10 | }: { 11 | port?: number; 12 | data: string; 13 | urgent?: boolean; 14 | }) { 15 | this.port = port; 16 | this.data = data.toUpperCase(); 17 | this.urgent = urgent; 18 | } 19 | 20 | toString() { 21 | return this.data.toUpperCase(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/controls/midi/utils.ts: -------------------------------------------------------------------------------- 1 | export const deepEqual = function (x: any, y: any) { 2 | if (x === y) { 3 | return true; 4 | } else if ( 5 | typeof x == 'object' && 6 | x != null && 7 | typeof y == 'object' && 8 | y != null 9 | ) { 10 | if (Object.keys(x).length != Object.keys(y).length) return false; 11 | 12 | for (const prop in x) { 13 | if (y.hasOwnProperty(prop)) { 14 | if (!deepEqual(x[prop], y[prop])) return false; 15 | } else return false; 16 | } 17 | 18 | return true; 19 | } else return false; 20 | }; 21 | -------------------------------------------------------------------------------- /src/env/delayed-task.ts: -------------------------------------------------------------------------------- 1 | export class DelayedTask { 2 | callback: Function; 3 | delay: number; 4 | repeat: boolean; 5 | cancelled = false; 6 | 7 | constructor(callback: (...args: any[]) => any, delay = 0, repeat = false) { 8 | this.callback = callback; 9 | this.delay = delay; 10 | this.repeat = repeat; 11 | } 12 | 13 | start(...args: any[]) { 14 | host.scheduleTask(() => { 15 | if (!this.cancelled) { 16 | this.callback.call(args); 17 | if (this.repeat) this.start(...args); 18 | } 19 | }, this.delay); 20 | return this; 21 | } 22 | 23 | cancel() { 24 | this.cancelled = true; 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/env/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { Logger } from './logger'; 4 | import { DelayedTask } from './delayed-task'; 5 | 6 | // specific env setup for bitwig environment 7 | // shim Timeout and Interval methods using DelayedTask class 8 | globalThis.setTimeout = function setTimeout( 9 | callback: (...args: any[]) => any, 10 | delay = 0, 11 | ...params: any[] 12 | ) { 13 | return new DelayedTask(callback, delay).start(...params); 14 | }; 15 | 16 | globalThis.clearTimeout = function clearTimeout(timeout: DelayedTask) { 17 | if (timeout) timeout.cancel(); 18 | }; 19 | 20 | globalThis.setInterval = function setInterval( 21 | callback: (...args: any[]) => any, 22 | delay = 0, 23 | ...params: any[] 24 | ) { 25 | return new DelayedTask(callback, delay, true).start(...params); 26 | }; 27 | 28 | globalThis.clearInterval = function clearInterval(interval: DelayedTask) { 29 | if (interval) interval.cancel(); 30 | }; 31 | 32 | // shim console with custom logger 33 | globalThis.console = new Logger(); 34 | 35 | // hookup dummy function to unsupported logger methods 36 | 37 | // Console-polyfill. MIT license. 38 | // https://github.com/paulmillr/console-polyfill 39 | // Make it safe to do console.log() always. 40 | const con = globalThis.console; 41 | let prop; 42 | let method; 43 | 44 | const dummy = function () {}; 45 | const properties = ['memory']; 46 | const methods = [ 47 | 'assert', 48 | 'clear', 49 | 'count', 50 | 'debug', 51 | 'dir', 52 | 'dirxml', 53 | 'error', 54 | 'exception', 55 | 'group', 56 | 'groupCollapsed', 57 | 'groupEnd', 58 | 'info', 59 | 'log', 60 | 'markTimeline', 61 | 'profile', 62 | 'profiles', 63 | 'profileEnd', 64 | 'show', 65 | 'table', 66 | 'time', 67 | 'timeEnd', 68 | 'timeline', 69 | 'timelineEnd', 70 | 'timeStamp', 71 | 'trace', 72 | 'warn', 73 | ]; 74 | 75 | while ((prop = properties.pop())) { 76 | if (!con[prop]) con[prop] = {}; 77 | } 78 | 79 | while ((method = methods.pop())) { 80 | if (typeof con[method] !== 'function') con[method] = dummy; 81 | } 82 | -------------------------------------------------------------------------------- /src/env/logger.ts: -------------------------------------------------------------------------------- 1 | import { Session } from '../session'; 2 | 3 | export type Level = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'; 4 | export type MidiLevel = 'Input' | 'Output' | 'Both' | 'None'; 5 | 6 | /** 7 | * Simple logger implementation including integration with the Bitwig 8 | * API's preferences system for setting log level, log filtering via 9 | * regular expressions, and Midi I/O filtering. 10 | */ 11 | export class Logger { 12 | private _levels = ['ERROR', 'WARN', 'INFO', 'DEBUG']; 13 | private _level!: Level; 14 | private _midiLevel!: MidiLevel; 15 | private _levelSetting!: API.SettableEnumValue; 16 | private _filter!: string; 17 | private _filterSetting!: API.SettableStringValue; 18 | private _initQueue: [Level | null, any[]][] = []; 19 | private _flushed = false; 20 | 21 | constructor(session?: Session) { 22 | session?.on('init', () => { 23 | host 24 | .getPreferences() 25 | .getEnumSetting( 26 | 'Log Midi', 27 | 'Development', 28 | ['None', 'Input', 'Output', 'Both'], 29 | 'None' 30 | ) 31 | .addValueObserver((midiLevel) => { 32 | this._midiLevel = midiLevel as MidiLevel; 33 | if (this._ready && !this._flushed) this._flushQueue(); 34 | }); 35 | 36 | this._levelSetting = host 37 | .getPreferences() 38 | .getEnumSetting('Log Level', 'Development', this._levels, 'ERROR'); 39 | 40 | this._levelSetting.addValueObserver((level) => { 41 | this._level = level as Level; 42 | if (this._ready && !this._flushed) this._flushQueue(); 43 | }); 44 | 45 | this._filterSetting = host 46 | .getPreferences() 47 | .getStringSetting('Log filter (Regex)', 'Development', 1000, ''); 48 | this._filterSetting.addValueObserver((value) => { 49 | this._filter = value; 50 | if (this._filter) { 51 | const message = ` Log filter regex set to "${value}"`; 52 | this.log(`╭───┬${'─'.repeat(message.length)}╮`); 53 | this.log(`│ i │${message}` + '│'); // prettier-ignore 54 | this.log(`╰───┴${'─'.repeat(message.length)}╯`); 55 | } 56 | if (this._ready && !this._flushed) this._flushQueue(); 57 | }); 58 | }); 59 | } 60 | 61 | private get _ready() { 62 | return ( 63 | this._filter !== undefined && 64 | this._level !== undefined && 65 | this._midiLevel !== undefined 66 | ); 67 | } 68 | 69 | set level(level: Level) { 70 | if (this._levelSetting !== undefined) { 71 | this._levelSetting.set(level); 72 | } else { 73 | this._level = level; 74 | } 75 | } 76 | 77 | get level() { 78 | return this._level; 79 | } 80 | 81 | set filter(value) { 82 | if (this._filterSetting !== undefined) { 83 | this._filterSetting.set(value); 84 | } else { 85 | this._filter = value; 86 | } 87 | } 88 | 89 | get filter() { 90 | return this._filter; 91 | } 92 | 93 | log(...messages: any[]) { 94 | this._log(null, ...messages); 95 | } 96 | 97 | error(...messages: any[]) { 98 | this._log('ERROR', ...messages); 99 | } 100 | 101 | warn(...messages: any[]) { 102 | this._log('WARN', ...messages); 103 | } 104 | 105 | info(...messages: any[]) { 106 | this._log('INFO', ...messages); 107 | } 108 | 109 | debug(...messages: any[]) { 110 | this._log('DEBUG', ...messages); 111 | } 112 | 113 | dir(...messages: any[]) { 114 | this._log(null, ...messages.map((m) => JSON.stringify(m, null, 2))); 115 | } 116 | 117 | private _log(level: Level | null, ...messages: any[]) { 118 | if (!this._ready) { 119 | this._initQueue.push([level, messages]); 120 | return; 121 | } 122 | 123 | if ( 124 | level && 125 | this._levels.indexOf(level) > this._levels.indexOf(this._level) 126 | ) 127 | return; 128 | 129 | const message = `${level ? `[${level.toUpperCase()}] ` : ''}${messages.join( 130 | ' ' 131 | )}`; 132 | if (level && this._filter) { 133 | const re = new RegExp(this._filter, 'gi'); 134 | if (!re.test(message)) return; 135 | } 136 | 137 | const isMidiInput = new RegExp('^\\[(MIDI|SYSEX)\\] ? IN', 'gi').test( 138 | message 139 | ); 140 | const isMidiOutput = new RegExp('^\\[(MIDI|SYSEX)\\] ? OUT', 'gi').test( 141 | message 142 | ); 143 | 144 | if (this._midiLevel === 'None' && (isMidiInput || isMidiOutput)) return; 145 | if (this._midiLevel === 'Input' && isMidiOutput) return; 146 | if (this._midiLevel === 'Output' && isMidiInput) return; 147 | 148 | level === 'ERROR' ? host.errorln(message) : host.println(message); 149 | } 150 | 151 | private _flushQueue() { 152 | while (this._initQueue.length > 0) { 153 | const [level, messages] = this._initQueue.shift() as [ 154 | Level | null, 155 | any[] 156 | ]; 157 | this._log(level, ...messages); 158 | } 159 | this._flushed = true; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import './env'; 2 | 3 | import { Session } from './session'; 4 | import { reconciler } from './reconciler'; 5 | import { Midi } from './components/midi'; 6 | import { 7 | ControllerScript, 8 | ControllerScriptProps, 9 | } from './components/controller-script'; 10 | import { 11 | createInitState, 12 | createInitValue, 13 | createInitObject, 14 | } from './init-helpers'; 15 | 16 | const session = new Session(); 17 | 18 | const LEGACY_ROOT = 0; // CONCURRENT_ROOT = 1 19 | 20 | const render = (rootNode: JSX.Element) => { 21 | // If ControllerScript component provided as rootNode, call related controller definition methods 22 | if (rootNode.type === ControllerScript) { 23 | const { api, author, name, uuid, vendor, version, midi } = 24 | rootNode.props as ControllerScriptProps; 25 | 26 | // 1. set bitwig api version 27 | 28 | host.loadAPI(api); 29 | 30 | // 2. define controller script 31 | 32 | host.defineController( 33 | vendor, // vendor 34 | name, // name 35 | version, // version 36 | uuid, // uuid 37 | author // author 38 | ); 39 | 40 | // 3. setup and discover midi controllers 41 | 42 | if (midi) { 43 | if (Array.isArray(midi)) { 44 | // handle multiple discovery pairs 45 | host.defineMidiPorts(midi[0].inputs.length, midi[0].outputs.length); 46 | midi.forEach(({ inputs, outputs }) => 47 | host.addDeviceNameBasedDiscoveryPair(inputs, outputs) 48 | ); 49 | } else if (Array.isArray(midi.inputs) && Array.isArray(midi.outputs)) { 50 | // handle single discovery pair 51 | host.defineMidiPorts(midi.inputs.length, midi.outputs.length); 52 | host.addDeviceNameBasedDiscoveryPair(midi.inputs, midi.outputs); 53 | } else if ( 54 | typeof midi.inputs === 'number' && 55 | typeof midi.outputs === 'number' 56 | ) { 57 | // handle simple midi port count 58 | host.defineMidiPorts(midi.inputs, midi.outputs); 59 | } 60 | } 61 | } 62 | 63 | session.on('init', () => { 64 | const fiberRoot = reconciler.createContainer( 65 | session, 66 | LEGACY_ROOT, 67 | null, 68 | false, 69 | null, 70 | '', 71 | () => {}, 72 | null 73 | ); 74 | reconciler.updateContainer(rootNode, fiberRoot, null, () => null); 75 | }); 76 | }; 77 | 78 | const ReactBitwig = { 79 | render, 80 | session, 81 | createInitState, 82 | createInitValue, 83 | createInitObject, 84 | }; 85 | 86 | export default ReactBitwig; 87 | 88 | export { 89 | render, 90 | session, 91 | Midi, 92 | ControllerScript, 93 | createInitState, 94 | createInitValue, 95 | createInitObject, 96 | }; 97 | export * from './components/midi'; 98 | export * from './components/controller-script'; 99 | export * from './init-helpers'; 100 | -------------------------------------------------------------------------------- /src/init-helpers.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { session } from './index'; 3 | 4 | export type InitValue = { get: () => V }; 5 | 6 | export function createInitValue(initializer: () => V): InitValue { 7 | let value: V; 8 | 9 | let initialized = false; 10 | let initializing = false; 11 | const initialize = () => { 12 | if (initializing) { 13 | throw new Error('Circular initialization dependency detected.'); 14 | } else if (!session.isInitPhase) { 15 | throw new Error( 16 | 'Access to init value not allowed until during or after the init phase.' 17 | ); 18 | } 19 | 20 | initializing = true; 21 | value = initializer(); 22 | initialized = true; 23 | initializing = false; 24 | }; 25 | 26 | session.on('init', () => { 27 | if (!initialized) initialize(); 28 | }); 29 | 30 | return { 31 | get: () => { 32 | if (!initialized) initialize(); 33 | return value; 34 | }, 35 | }; 36 | } 37 | 38 | export function createInitObject( 39 | initializer: () => O 40 | ): O { 41 | let object: O = {} as O; 42 | 43 | let initialized = false; 44 | let initializing = false; 45 | const initialize = () => { 46 | if (initializing) { 47 | throw new Error('Circular initialization dependency detected.'); 48 | } else if (!session.isInitPhase) { 49 | throw new Error( 50 | 'Access to init value not allowed until during or after the init phase.' 51 | ); 52 | } 53 | 54 | initializing = true; 55 | object = initializer(); 56 | initialized = true; 57 | initializing = false; 58 | }; 59 | 60 | const gettersObject = {} as O; 61 | 62 | session.on('init', () => { 63 | if (!initialized) initialize(); 64 | Object.keys(object).forEach((k) => { 65 | Object.defineProperty(gettersObject, k, { 66 | get: () => object[k], 67 | }); 68 | }); 69 | }); 70 | 71 | return gettersObject; 72 | } 73 | 74 | export type InitState = { 75 | get: () => S; 76 | set: (state: Partial | ((state: S) => Partial)) => void; 77 | subscribe: (listener: (value: S) => void) => void; 78 | unsubscribe: (listener: (value: S) => void) => void; 79 | use: { 80 | (): S; 81 | (selector: (state: S) => T): T; 82 | }; 83 | }; 84 | 85 | export function createInitState(initializer: () => S): InitState { 86 | let currentState: S; 87 | 88 | const listeners: ((state: S) => void)[] = []; 89 | 90 | const setState = (state: Partial | ((state: S) => Partial)) => { 91 | state = typeof state === 'function' ? state(currentState) : state; 92 | const newState = ( 93 | currentState === undefined 94 | ? state 95 | : typeof state === 'object' && !Array.isArray(state) 96 | ? { ...currentState, ...state } 97 | : state 98 | ) as S; 99 | if (currentState !== newState) { 100 | currentState = newState; 101 | listeners.forEach((listener) => { 102 | listener(newState); 103 | }); 104 | } 105 | }; 106 | 107 | let initialized = false; 108 | let initializing = false; 109 | const initialize = () => { 110 | if (initializing) { 111 | throw new Error('Circular initialization dependency detected.'); 112 | } 113 | if (!session.isInitPhase) { 114 | throw new Error( 115 | 'Access to init state not allowed until during or after the init phase.' 116 | ); 117 | } 118 | 119 | initializing = true; 120 | const initialState = initializer(); 121 | if (initialState !== undefined) { 122 | setState(initialState); 123 | } 124 | initialized = true; 125 | initializing = false; 126 | }; 127 | 128 | session.on('init', () => { 129 | if (!initialized) initialize(); 130 | }); 131 | 132 | const get = (): S => { 133 | // 1. make sure its initialized 134 | if (!initialized) initialize(); 135 | // 2. return initialized state as readonly state 136 | return currentState; 137 | }; 138 | const set = (state: Partial | ((state: S) => Partial)) => { 139 | setState(state); 140 | }; 141 | const subscribe = (listener: (state: S) => void) => { 142 | listeners.push(listener); 143 | }; 144 | const unsubscribe = (listener: (state: S) => void) => { 145 | listeners.splice(listeners.indexOf(listener), 1); 146 | }; 147 | const use = any) | undefined = undefined>( 148 | selector?: T 149 | ): T extends (state: S) => any ? ReturnType : S => { 150 | const [hookState, setHookState] = React.useState( 151 | selector ? selector(currentState) : currentState 152 | ); 153 | 154 | // layout effect to make sure the hook state is updated when the state changes on init 155 | React.useLayoutEffect(() => { 156 | const listener = (state: S) => { 157 | setHookState(selector ? selector(state) : state); 158 | }; 159 | subscribe(listener); 160 | return () => unsubscribe(listener); 161 | }, []); 162 | 163 | return hookState; 164 | }; 165 | return { get, set, subscribe, unsubscribe, use }; 166 | } 167 | -------------------------------------------------------------------------------- /src/lib/hooks.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const useButton = ({ 4 | onPress, 5 | onLongPress, 6 | onDoublePress, 7 | onRelease, 8 | onDoubleRelease, 9 | }: { 10 | onPress?: () => void; 11 | onLongPress?: () => void; 12 | onDoublePress?: () => void; 13 | onRelease?: () => void; 14 | onDoubleRelease?: () => void; 15 | }) => { 16 | const timeoutsRef = React.useRef({ 17 | longPress: 0, 18 | recentPress: 0, 19 | recentRelease: 0, 20 | }); 21 | 22 | React.useEffect(() => { 23 | return () => { 24 | // cancel timeouts on unmount 25 | clearTimeout(timeoutsRef.current.longPress); 26 | clearTimeout(timeoutsRef.current.recentPress); 27 | clearTimeout(timeoutsRef.current.recentRelease); 28 | }; 29 | }, []); 30 | 31 | const recentlyPressedRef = React.useRef(false); 32 | const recentlyReleasedRef = React.useRef(false); 33 | const hasBeenPressedRef = React.useRef(false); 34 | 35 | const fireButtonEvent = React.useCallback( 36 | (event: 'pressed' | 'released') => { 37 | event === 'pressed' 38 | ? recentlyPressedRef.current 39 | ? onDoublePress 40 | ? onDoublePress() 41 | : onPress && onPress() 42 | : onPress && onPress() 43 | : recentlyReleasedRef.current 44 | ? onDoubleRelease 45 | ? onDoubleRelease() 46 | : onRelease && onRelease() 47 | : hasBeenPressedRef.current && onRelease && onRelease(); 48 | 49 | // if there's a pending longPress timeout, press or release should cancel that 50 | clearTimeout(timeoutsRef.current.longPress); 51 | 52 | if (event === 'pressed') { 53 | if (!hasBeenPressedRef.current) hasBeenPressedRef.current = true; 54 | 55 | if (onLongPress) { 56 | timeoutsRef.current.longPress = setTimeout(onLongPress, 500); 57 | } 58 | 59 | // set recently pressed & schedule reset 60 | clearTimeout(timeoutsRef.current.recentPress); 61 | recentlyPressedRef.current = true; 62 | timeoutsRef.current.recentPress = setTimeout(() => { 63 | recentlyPressedRef.current = false; 64 | }, 250); 65 | } else { 66 | // set recently released & schedule reset 67 | clearTimeout(timeoutsRef.current.recentRelease); 68 | recentlyReleasedRef.current = true; 69 | timeoutsRef.current.recentRelease = setTimeout(() => { 70 | recentlyReleasedRef.current = false; 71 | }, 250); 72 | } 73 | }, 74 | [onPress, onLongPress, onDoublePress, onRelease, onDoubleRelease] 75 | ); 76 | 77 | return fireButtonEvent; 78 | }; 79 | -------------------------------------------------------------------------------- /src/reconciler.ts: -------------------------------------------------------------------------------- 1 | import ReactReconciler from 'react-reconciler'; 2 | import { DefaultEventPriority } from 'react-reconciler/constants'; 3 | 4 | import { MidiInstanceUpdatePayload } from './controls/midi/midi-connector'; 5 | import { MidiInstance, MidiInstanceProps } from './controls/midi/midi-instance'; 6 | import { Session } from './session'; 7 | 8 | export const reconciler = ReactReconciler< 9 | 'midi', // Type 10 | MidiInstanceProps, // Props 11 | Session, // Container 12 | MidiInstance, // Instance 13 | never, // TextInstance 14 | MidiInstance, // SuspenseInstance 15 | MidiInstance, // HydratableInstance 16 | MidiInstance, // PublicInstance 17 | null, // HostContext 18 | MidiInstanceUpdatePayload, // UpdatePayload 19 | unknown, // _ChildSet (Placeholder for undocumented API) 20 | number, // TimeoutHandle 21 | -1 // NoTimeout 22 | >({ 23 | supportsMutation: true, // we're choosing to build this in mutation mode 24 | supportsPersistence: false, // (not persistence mode) 25 | supportsHydration: false, // hydration does not apply in this env 26 | scheduleTimeout: setTimeout, // timeout scheduler for env 27 | cancelTimeout: clearTimeout, // timeout clear function for env 28 | noTimeout: -1, // value that can never be a timeout id 29 | isPrimaryRenderer: true, // this will be the only renderer 30 | 31 | createInstance(type, props) { 32 | if (type !== 'midi') { 33 | throw new Error(`Unsupported intrinsic element type "${type}"`); 34 | } 35 | return new MidiInstance(props); 36 | }, 37 | 38 | appendChildToContainer(container, child) { 39 | // add pattern to container if it hasn't already been registered with matching pattern and return it 40 | // if it has, increase count of components depending on it 41 | // or throw error because pattern doesn't fully match but conflicts with existing pattern 42 | // connect event listeners for new ones 43 | container.midi.addInstance(child); 44 | }, 45 | 46 | insertInContainerBefore(container, child, _beforeChild) { 47 | container.midi.addInstance(child); 48 | }, 49 | 50 | removeChildFromContainer(container, child) { 51 | // find child in container and decrement pointer count. 52 | // If decrementing results in count of 0, remove from container, cleanup 53 | container.midi.removeInstance(child); 54 | }, 55 | 56 | prepareUpdate(instance, type, oldProps, newProps, container) { 57 | // check if update is needed, if not return null, otherwise optionally return data for use in commit 58 | return container.midi.prepareInstanceUpdate(instance, oldProps, newProps); 59 | }, 60 | 61 | commitUpdate(instance, updatePayload, type, oldProps, newProps) { 62 | // commit changes to instance (instance should have access to container and node) 63 | instance.commitUpdate(updatePayload, oldProps, newProps); 64 | }, 65 | 66 | getPublicInstance(instance) { 67 | return instance; 68 | }, 69 | 70 | clearContainer(container) { 71 | // remove all instances from container 72 | container.midi.clearInstances(); 73 | }, 74 | 75 | // Required methods that are not needed in this env 76 | 77 | preparePortalMount() { 78 | throw new Error('ReactBitwig does not support portals.'); 79 | }, 80 | 81 | createTextInstance() { 82 | throw new Error('ReactBitwig does not support text instances.'); 83 | }, 84 | 85 | appendChild() { 86 | // should never reach this this 87 | throw new Error( 88 | 'ReactBitwig does not support nesting of intrinsic elements' 89 | ); 90 | }, 91 | 92 | appendInitialChild() { 93 | // should never reach this this 94 | throw new Error( 95 | 'ReactBitwig does not support nesting of intrinsic elements' 96 | ); 97 | }, 98 | 99 | removeChild() { 100 | // should never reach this this 101 | throw new Error( 102 | 'ReactBitwig does not support nesting of intrinsic elements' 103 | ); 104 | }, 105 | 106 | insertBefore() { 107 | // should never reach this this 108 | throw new Error( 109 | 'ReactBitwig does not support nesting of intrinsic elements' 110 | ); 111 | }, 112 | 113 | finalizeInitialChildren() { 114 | return false; // return false to skip this functionality 115 | }, 116 | 117 | getRootHostContext() { 118 | return null; 119 | }, 120 | 121 | getChildHostContext(parentHostContext) { 122 | return parentHostContext; 123 | }, 124 | 125 | prepareForCommit() { 126 | return null; 127 | }, 128 | 129 | resetAfterCommit() {}, 130 | 131 | shouldSetTextContent() { 132 | return false; 133 | }, 134 | 135 | getCurrentEventPriority() { 136 | return DefaultEventPriority; 137 | }, 138 | 139 | getInstanceFromNode() { 140 | throw new Error('Not implemented.'); 141 | }, 142 | 143 | prepareScopeUpdate() { 144 | throw new Error('Not implemented.'); 145 | }, 146 | 147 | getInstanceFromScope() { 148 | throw new Error('Not implemented.'); 149 | }, 150 | 151 | beforeActiveInstanceBlur() {}, 152 | 153 | afterActiveInstanceBlur() {}, 154 | 155 | detachDeletedInstance(node) {}, 156 | }); 157 | -------------------------------------------------------------------------------- /src/session/event-emitter.ts: -------------------------------------------------------------------------------- 1 | export class EventEmitter { 2 | listeners: { [key: string]: ((...args: any[]) => void)[] } = {}; 3 | 4 | on void>( 5 | label: string, 6 | callback: Callback 7 | ) { 8 | if (this.listeners[label] && this.listeners[label].indexOf(callback) > -1) { 9 | throw new Error('Duplicate event subscriptions not allowed'); 10 | } 11 | this.listeners = { 12 | ...this.listeners, 13 | [label]: [...(this.listeners[label] || []), callback], 14 | }; 15 | } 16 | 17 | addListener void>( 18 | label: string, 19 | callback: Callback 20 | ) { 21 | this.on(label, callback); 22 | } 23 | 24 | removeListener void>( 25 | label: string, 26 | callback: Callback 27 | ) { 28 | const listeners = this.listeners[label]; 29 | const index = listeners ? listeners.indexOf(callback) : -1; 30 | 31 | if (index > -1) { 32 | this.listeners = { 33 | ...this.listeners, 34 | [label]: [...listeners.slice(0, index), ...listeners.slice(index + 1)], 35 | }; 36 | return true; 37 | } 38 | return false; 39 | } 40 | 41 | emit(label: string, ...args: any[]) { 42 | try { 43 | const listeners = this.listeners[label]; 44 | 45 | if (listeners && listeners.length) { 46 | listeners.forEach((listener) => { 47 | listener(...args); 48 | }); 49 | return true; 50 | } 51 | } catch (e: any) { 52 | console.error( 53 | `${e.fileName}:${e.lineNumber}:${e.columnNumber} ${e.message}\n${e.stack}` 54 | ); 55 | } 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/session/index.ts: -------------------------------------------------------------------------------- 1 | export { Session } from './session'; 2 | -------------------------------------------------------------------------------- /src/session/session.ts: -------------------------------------------------------------------------------- 1 | import '../env'; 2 | import { EventEmitter } from './event-emitter'; 3 | import { MidiConnector } from '../controls/midi/midi-connector'; 4 | import { Logger } from '../env/logger'; 5 | 6 | declare const globalThis: { 7 | init: () => void; 8 | flush: () => void; 9 | exit: () => void; 10 | }; 11 | 12 | export interface Session extends EventEmitter { 13 | on(label: 'init' | 'flush' | 'exit', callback: () => void): void; 14 | 15 | addListener(label: 'init' | 'flush' | 'exit', callback: () => void): void; 16 | 17 | removeListener( 18 | label: 'init' | 'flush' | 'exit', 19 | callback: () => void 20 | ): boolean; 21 | } 22 | 23 | export class Session extends EventEmitter { 24 | private _isExitPhase: boolean = false; 25 | private _isInitPhase: boolean = false; 26 | private _isInitInitialized: boolean = false; 27 | 28 | midi: MidiConnector; 29 | 30 | constructor() { 31 | super(); 32 | 33 | // @ts-ignore 34 | globalThis.console = new Logger(this); 35 | 36 | this.midi = new MidiConnector(this); 37 | 38 | globalThis.init = () => { 39 | this._isInitPhase = true; 40 | 41 | // call the session init callbacks 42 | this.emit('init'); 43 | 44 | this._isInitPhase = false; 45 | this._isInitInitialized = true; 46 | }; 47 | 48 | globalThis.flush = () => { 49 | this.emit('flush'); 50 | }; 51 | 52 | globalThis.exit = () => { 53 | this._isExitPhase = true; 54 | 55 | // call registered exit callbacks 56 | this.emit('exit'); 57 | }; 58 | } 59 | 60 | /** Check if bitwig is currently in it's init startup phase */ 61 | get isInitPhase(): boolean { 62 | return this._isInitPhase; 63 | } 64 | 65 | get isExitPhase(): boolean { 66 | return this._isExitPhase; 67 | } 68 | 69 | get isInitialized(): boolean { 70 | return this._isInitInitialized; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare const Java: any; 2 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["src/**/*.test.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "rootDir": "src", 6 | "baseUrl": "./src", 7 | "jsx": "react-jsx", 8 | "lib": ["DOM", "ESNext", "ScriptHost"], 9 | "types": ["typed-bitwig-api"], 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "moduleResolution": "node", 15 | "outDir": "./dist", 16 | "declaration": true 17 | }, 18 | "include": ["src"] 19 | } 20 | --------------------------------------------------------------------------------